1. 程式人生 > >邏輯迴歸:原理+程式碼

邏輯迴歸:原理+程式碼

(作者:陳玓玏)

邏輯迴歸算是傳統機器學習中最簡單的模型了,它的基礎是線性迴歸,為了弄明白邏輯迴歸,我們先來看線性迴歸。

一、線性迴歸

假設共N個樣本,每個樣本有M個特徵,這樣就產生了一個N*M大小的樣本矩陣。令矩陣為X,第i個樣本為Xi,第i個樣本的第j個特徵為Xij。令樣本的觀測向量為Y,第i個樣本的觀測值為Yi,那麼就會有以下公式:
(X+[1]N*1)*W = Y
也就是說,對於一批已經存在的樣本,我們通過其X和Y之間的關係,求得一個係數矩陣W,對於那些Y值未知的變數,我們通過X和W求得其可能的Y,也就是對其進行預測。

線性迴歸的合理性是已經證明過的,那就是任何變數之間的關係都是可以通過多項式進行擬合的,線性迴歸其實就是在求解一個多元一次方程。

二、邏輯迴歸

邏輯迴歸和線性迴歸很像,差別在於邏輯迴歸線上性迴歸的基礎上加了一個啟用函式。邏輯迴歸所使用的啟用函式也分兩種,一個是sigmoid函式,一個是softmax函式,前者用於二分類,稱為邏輯迴歸,後者用於多分類,一般稱為邏輯分類。線性迴歸輸出的y值通過sigmoid函式,會產生N*1維的大小均在0和1之間的向量,也就是每個樣本屬於正樣本的概率值,而y值通過softmax函式,會輸出N*C維的矩陣,C表示樣本預測值可能的類別數。

為什麼要線上性迴歸的基礎上用這兩個啟用函式呢?我的理解是,其一,算是對y值進行了一個歸一化,因為單純的線性迴歸,如果不對x進行歸一化,那麼擬合出來的y值可能會差別很大,那麼在分類問題上,你閾值取多少才合適呢?這個範圍就很大了,而sigmoid函式和softmax函式能夠把y值對映到[0,1]區間上,表示樣本屬於正樣本的概率大小,這個問題就解決了。其二,這兩個函式都具有近似階躍函式的特性,也就是能夠把樣本區分得足夠開,這樣能夠讓分類的準確率更高些,否則y的線性迴歸預測值靠近0時,很容易錯分。

三、邏輯迴歸應用場景

邏輯迴歸適用於:
1、變數少的場景;
2、要求模型解釋性強的場景。

適用於變數少的場景是因為邏輯迴歸基於線性迴歸,那麼在用梯度下降法求解係數矩陣時,要求x矩陣是滿秩的,也就是行列式不為0,這樣才能求得唯一解,而變數很多的情況下,變數間很容易產生強相關,如果你使用sklearn,相關變數很多的情況下就會報singular error的錯誤。

適用於模型解釋解釋性強的場景是因為邏輯迴歸的本質其實是給每個變數分配了一個權重,那麼當你模型結果產生錯誤時,你能夠很容易地分析出是哪個變數導致了錯誤,而決策樹這類的演算法相對而言就要難分析一些,尤其是整合學習演算法,因為它的建模過程、迭代過程沒有那麼明朗,也沒有那麼容易分析。

鑑於第二點因素,邏輯迴歸常常用於風控建模,因為風控建模對變數和模型的解釋性要求極高。

四、邏輯迴歸程式碼

下面這段程式碼跑出來結果的正確率是0.8,還是可以接受的,嘿嘿。

import numpy as np
import pandas as pd
import math


#初始化權重矩陣
def initWeight(data):
    # x = len(data)
    y = len(data[0])
    w = np.ones((y,1))*1
    w = np.mat(w)
    print(w)

    return w

def gradOptimize(data_x,data_y,w,minLoss,stepLen,maxTurn,turn):
    turn = turn+1
    loss = 0
    y_val = []
    y_val = np.dot(data_x,w)
    # stepLen = stepLen / 1.1
    # print(y_val)
    # for j in range(len(y_seperate)):
    #     for i in range(len(y_seperate[0])):
    #         y_val.append(y_seperate[j,i]+y_seperate[j,i+1])
    # print(y_val.shape)
    for i in range(y_val.shape[0]):
        #print(i)
        p = 1/(1+math.exp(-y_val[i]))
        y_val[i] = p
        # loss = loss+i*math.log(p,2)+(1-i)*math.log((1-p),2)
        loss = loss + i * math.log(p, 2) + (1 - i) * math.log((1 - p), 2)
    # print(y_val.shape[0])
    loss = -loss/y_val.shape[0]
    # print(y_val)
    # print(loss)
    # print(w.shape)
    if abs(loss)>minLoss and turn<maxTurn:
        w = w-stepLen*np.dot(data_x.T,y_val-data_y)
        # print(w)
        # for i in range(w.shape[0]):
        #     for j in range(w.shape[1]):
        #         stepLen = stepLen / 1.1
        #         w[i,j] = w[i,j]-stepLen*(1/(1+math.exp(-i)))*(1-1/(1+math.exp(-i)))
        w, y_val = gradOptimize(data_x,data_y,w,minLoss,stepLen,maxTurn,turn)
        return w, y_val
    else:
        print('===============')
        print(y_val)
        return w,y_val

def oneScale(data):
    newdata = pd.DataFrame(data)
    for i in range(newdata.shape[1]-1):
        newdata.ix[:,i] = newdata.ix[:,i].apply(lambda x:x/abs(newdata.ix[:,i]).max())
    data = newdata.values
    return data

if __name__=='__main__':

    data=[
        [-0.017612,14.053064,0],
        [-0.752157,6.538620,0],
        [-1.322371,7.152853,0],
        [0.423363,11.054677,0],
        [0.569411,9.548755,0],
        [-0.026632,10.427743,0],
        [0.667394,12.741452,0],
        [1.347183,13.175500,0],
        [1.217916,9.597015,0],
        [-0.733928,9.098687,0],
        [1.416614,9.619232,0],
        [1.388610,9.341997,0],
        [0.317029,14.739025,0],
        [-0.576525,11.778922,0],
        [-1.781871,9.097953,0],
        [-1.395634,4.662541,1],
        [0.406704,7.067335,1],
        [-2.460150,6.866805,1],
        [0.850433,6.920334,1],
        [1.176813,3.167020,1],
        [-0.566606,5.749003,1],
        [0.931635,1.589505,1],
        [-0.024205,6.151823,1],
        [-0.036453,2.690988,1],
        [-0.196949,0.444165,1],
        [1.014459,5.754399,1],
        [1.985298,3.230619,1],
        [-1.693453,-0.557540,1],
        [-0.346811,-1.678730,1],
        [-2.124484,2.672471,1]
    ]
    #對變數值做歸一化
    data = oneScale(data)
    X = np.mat(data.copy())[:, 0:-1]
    data_y = np.mat(data.copy())[:, -1]
    b = np.ones(data_y.shape)  # 新增全1列向量代表b偏量
    data_x = np.column_stack((b, X))  # 特徵屬性集和b偏量組成x
    # data_x = oneScale(data_x)
    # print(data_x)
    #print(data_x)

    # print(data_x,data_y)
    w = initWeight(data)
    w,y_val = gradOptimize(data_x,data_y,w,0.1,0.02,300,0)
    print(w)
    print(y_val)
    #print(np.multiply(data_x,w)[-1])

    # data = pd.DataFrame(np.dot(data_x,w),columns=['predict_y'])
    # data['predict_y'] = data['predict_y'].apply(lambda x:1/(1+math.exp(-x)))
    data = pd.DataFrame(y_val,columns=['predict_y'])
    # data['predict_y'] = y_val
    data['real_y'] = data_y
    correct_ratio = data[abs(data['predict_y']-data['real_y'])<=0.5].count()/data.count()
    print(data)
    print(correct_ratio)