邏輯迴歸算法實現



算法原理

模型

邏輯迴歸模型是一個二分類的對數線性模型。令樣本數據集爲
{xi;yi}i=1n,xiRt,yi{0,1} \{x_i;y_i\}^n_{i=1},x_i\in\mathbb{R}^t,y_i\in\{0,1\}
其中,xix_i是樣本的特徵,yiy_i是樣本的類別,限定爲0或1。邏輯迴歸模型的數學表達式爲
f(xi)=11+e(ωTxi+b),i=1,2,...,n f(x_i)=\frac{1}{1+e^{-(\omega^T x_i +b)}},i=1,2,...,n
單單看這個表達式可以發現,它跟上節講到的感知機模型是很類似的,感知機使用sign函數對線性表達式進行轉化,而邏輯迴歸是用了sigmoid函數,這也是爲什麼感知機中的類別標籤是1和-1,而邏輯迴歸的類別標籤是0和1。但是在算法思想上兩者卻完全不同,感知機利用了錯誤驅動,通過錯誤分類集來構造損失函數,然後使用梯度下降法進行求解。而接下來我們會講到,邏輯迴歸是通過構造概率模型並最大化概率的方式來進行分類。

原理

首先,模型的前提是所有樣本數據都是獨立同分布的。

單個樣本xix_i

現在已知有一個樣本數據(xix_iyiy_i),我們的目的是希望將xix_i代入模型
f(xi)=11+e(ωTxi+b) f(x_i)=\frac{1}{1+e^{-(\omega^T x_i +b)}}
得到的f(xi)f(x_i)剛好等於yiy_i。所以在這裏要先定義一個條件概率,表示在已知樣本是xix_i的條件下,類別yiy_i是1的概率
p1=P(yi=1xi)=11+e(ωTxi+b) p_1=P(y_i=1|x_i)=\frac{1}{1+e^{-(\omega^T x_i +b)}}
當類別yiy_i等於1時,p1p_1要儘可能的大。由p1p_1的公式可以得到在已知樣本是xix_i的條件下,類別yiy_i是0的概率爲
p0=P(yi=0xi)=1P(yi=1xi)=e(ωTxi+b)1+e(ωTxi+b) p_0=P(y_i=0|x_i)=1-P(y_i=1|x_i)=\frac{e^{-(\omega^T x_i +b)}}{1+e^{-(\omega^T x_i +b)}}
當類別yiy_i等於0時,p0p_0要儘可能的大。

可以將p1p_1p0p_0整合起來,令
p=P(yixi)=yip1+(1yi)p0={p1yi=1p0yi=0 p=P(y_i|x_i)=y_i p_1+(1-y_i) p_0=\begin{cases} p_1 & y_i=1 \\ p_0 & y_i=0 \end{cases}
這樣,無論yiy_i是0還是1,都是要讓pp儘可能的大,即最大化。

整個數據集

對於單個樣本數據(xix_iyiy_i),需要讓pp儘可能的大,那推廣到整個數據集上,就是要求解
maxω,bi=1nP(yixi) \max_{\omega,b}\prod_{i=1}^nP(y_i|x_i)
這裏之所以將每個樣本數據對應的pp相乘,是因爲每個樣本數據都是獨立同分布的。另外,由於每個pp的數值都在0和1之間,因此它們的積也是在0和1之間。爲了將乘法拆開,需要做一個負對數的轉換(一般採用自然對數)
maxω,bi=1nP(yixi)maxω,b[log(i=1nP(yixi))]minω,b[log(i=1nP(yixi))]minω,b[i=1n(logP(yixi))] \max_{\omega,b}\prod_{i=1}^nP(y_i|x_i) \rightarrow\max_{\omega,b}[log(\prod_{i=1}^nP(y_i|x_i))] \rightarrow\min_{\omega,b}[-log(\prod_{i=1}^nP(y_i|x_i))] \rightarrow\min_{\omega,b}[-\sum_{i=1}^n(logP(y_i|x_i))]
爲了避免誤會,箭頭代表問題的等價轉換。第一個箭頭轉換是因爲對數函數是增函數,第二個箭頭是因爲添加負號,同時變成最小化問題,第三個箭頭是根據對數的性質。進一步,我們就得到了邏輯迴歸的損失函數,也就是第三個箭頭右邊的公式的均值化,詳細展開如下。
L(ω,b)=1ni=1n(logP(yixi))=1ni=1n[yilog(p1)+(1yi)log(p0)]=1ni=1n[yilog(11+e(ωTxi+b))+(1yi)log(e(ωTxi+b)1+e(ωTxi+b))] L(\omega,b) =-\frac{1}{n}\sum_{i=1}^n(logP(y_i|x_i)) =-\frac{1}{n}\sum_{i=1}^n[y_i log(p_1)+(1-y_i) log(p_0)] =-\frac{1}{n}\sum_{i=1}^n[y_i log(\frac{1}{1+e^{-(\omega^T x_i +b)}})+(1-y_i) log(\frac{e^{-(\omega^T x_i +b)}}{1+e^{-(\omega^T x_i +b)}})]

模型求解

採用梯度下降法求解,更新ω\omegabb的值,不斷迭代,從而最小化損失函數。
ωωλLω \omega\rightarrow\omega-\lambda\frac{\partial L}{\partial\omega}
bbλLb b\rightarrow b-\lambda\frac{\partial L}{\partial b}
其中,λ\lambda是學習步長
Lω=1ni=1nxi(yif(xi)) \frac{\partial L}{\partial\omega}=-\frac{1}{n}\sum_{i=1}^{n}x_i(y_i-f(x_i))
Lb=1ni=1n(yif(xi)) \frac{\partial L}{\partial b}=-\frac{1}{n}\sum_{i=1}^{n}(y_i-f(x_i))

程序實現

sigmoid函數

def sigmoid(X):
    y = 1 / (1+np.exp(-X))
    return y

初始化函數

def __init__(self, alpha=0.01, iteration=1000):
    """
    alpha     學習步長
    w         權重向量
    iteration 最大迭代次數
    error     記錄損失函數值
    """
    self.alpha = alpha
    self.iteration = iteration

訓練函數

def fit(self, X, y):
    [data_num, fea_num] = X.shape
    X_ = np.vstack((np.ones(data_num).reshape(1,-1), X.T))
    y_ = y.reshape(-1,1)
    self.w = np.zeros(fea_num+1).reshape(-1,1)
    self.error = []
    i = 1

    while i<self.iteration:
        # 迭代w值
        dif_y = y_ - sigmoid(np.dot(self.w.T,X_)).reshape(-1,1)
        self.w = self.w + self.alpha * 1/data_num * np.dot(X_, dif_y)

        # 記錄損失
        p1 = sigmoid(np.dot(self.w.T,X_).T)
        temp = y_*np.log(p1) + (1-y_)*np.log(1-p1)
        self.error.append(-temp.mean())

        # 迭代次數
        if (i+1)%100==0:
            print('epoch--', i+1)
        i += 1

訓練函數的核心代碼在第十一和第十二行,是參數ω\omega的迭代更新公式,參考模型求解部分的公式,代碼中將權重參數ω\omega和偏置參數bb合併在一起進行計算。可轉化成矩陣計算的形式如下圖(*代表矩陣乘法)。

圖中顯示的各矩陣的維數分別爲:
ω:(t+1)1 \omega:(t+1)* 1
X:(t+1)n X_:(t+1)* n
yf(X):n1 y和f(X):n* 1
其中,ω\omega包含了偏置bb,所以需要再加上一維變成t+1t+1維。另外,邏輯迴歸不一定能將樣本全部正確分類,所以需要設置迭代次數。

預測函數

def predict(self, X):
    X_ = np.vstack((np.ones(X.shape[0]).reshape(1,-1), X.T))
    sig = sigmoid(np.dot(self.w.T, X_))[0]
    return (sig>=0.5)*1

整合全部代碼

class LogisticRegression:

    def __init__(self, alpha=0.01, iteration=1000):
        """
        alpha     學習步長
        w         權重向量
        iteration 最大迭代次數
        error     記錄損失函數值
        """
        self.alpha = alpha
        self.iteration = iteration
    
    def fit(self, X, y):
        [data_num, fea_num] = X.shape
        X_ = np.vstack((np.ones(data_num).reshape(1,-1), X.T))
        y_ = y.reshape(-1,1)
        self.w = np.zeros(fea_num+1).reshape(-1,1)
        self.error = []
        i = 1
        
        while i<self.iteration:
            # 迭代w值
            dif_y = y_ - sigmoid(np.dot(self.w.T,X_)).reshape(-1,1)
            self.w = self.w + self.alpha * 1/data_num * np.dot(X_, dif_y)
                
            # 記錄損失
            p1 = sigmoid(np.dot(self.w.T,X_).T)
            temp = y_*np.log(p1) + (1-y_)*np.log(1-p1)
            self.error.append(-temp.mean())
            
            # 迭代次數
            if (i+1)%100==0:
                print('epoch--', i+1)
            i += 1
    
    def predict(self, X):
        X_ = np.vstack((np.ones(X.shape[0]).reshape(1,-1), X.T))
        sig = sigmoid(np.dot(self.w.T, X_))[0]
        return (sig>=0.5)*1

實例化演示

導入相關庫和數據並查看數據分佈

import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris

iris = load_iris()
X = iris.data[:100,:2]
y = (iris.target[:100]==1)*1

plt.scatter(X[y==0,0], X[y==0,1], color='red')
plt.scatter(X[y==1,0], X[y==1,1], color='green')

實例化函數並畫出劃分平面

# 訓練
clf=LogisticRegression()
clf.fit(X,y)
line1 = X[:,0]
line2 = -(clf.w[0]+clf.w[1]*line1)/clf.w[2]

# 畫圖
plt.scatter(X[y==0,0],X[y==0,1],color='red')
plt.scatter(X[y==1,0],X[y==1,1],color='green')
plt.plot(line1,line2)

至此,邏輯迴歸算法講解完畢,以上是個人對邏輯迴歸算法的一些理解,如有錯誤歡迎指出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章