手寫算法-python代碼實現邏輯迴歸(帶L1、L2正則項)

邏輯迴歸原理解析

前面我們系統性的介紹了線性迴歸,初學者建議把我前面的文章看完,再來看邏輯迴歸。寫得應該算是容易看懂的了,且都有實例輔證,大家看的時候要自己跑一邊代碼,多動手、多思考。

今天,我們來講邏輯迴歸。

邏輯迴歸是LogisticRegression的直譯,它不是用來解決迴歸問題的,而是用來解決分類問題的,它其實是在線性迴歸的基礎上實現的。
我們知道,線性迴歸針對的是標籤爲連續值的機器學習任務,那怎樣纔可以用線性模型做分類任務呢?
例如二分類任務,標籤值只有0和1兩種。

思考:我們可以建立某種映射關係,將原先的連續值,轉爲0/1值,
現在請出sigmoid函數:
在這裏插入圖片描述
其函數圖像如下:
在這裏插入圖片描述
長的很優雅!在自變量實數範圍內,它的取值都在0-1之間,完美的映射了線性迴歸的連續值;
把線性迴歸的假設函數 z=X𝜃 作爲x傳入其中:





在這裏插入圖片描述
在這裏插入圖片描述
這就是邏輯迴歸的假設函數,預測函數。
實際上就是在線性迴歸線的結果上,加上sigmoid函數。


0.5作爲分類的邊界:
當z >= 0的時候 g(z) >= 0.5,其中z爲線性迴歸函數 z=X𝜃
最終類別爲1;

當z <= 0的時候g(z) <= 0.5,其中z爲線性迴歸函數 z=X𝜃
最終類別爲0;

z = 0是臨界點!!!

例如下圖:
在這裏插入圖片描述
-3 + x1 + x2 = 0這條線,就是臨界線。

在這裏插入圖片描述
其中 h(x) 的值,是樣本屬於1類別的概率值,
z = 0時,概率值爲0.5;
z > 0時,概率值大於0.5;
z < 0時,概率值小於0.5;



問題:邏輯迴歸和迴歸有沒有關係?
回答:有關係,對於二分類任務來說,我們對邏輯迴歸做一個變形,就會發現它本質上是對數機率迴歸,
金融評分卡就是根據這個公式映射的,所以說邏輯迴歸是一種廣義線性迴歸。

在這裏插入圖片描述

損失函數定義以及數學公式推導過程

有了假設函數,我們開始定義邏輯迴歸的損失函數,這裏繼續提出一個問題,用我們常用的最小二乘法作爲損失函數,可不可以?

從理論上講,可以。但是這個時候
在這裏插入圖片描述
,就沒有辦法用凸優化算法求解。
我們選用對數損失函數作爲損失函數,凸函數,好優化。


解釋1:通俗易懂的手推損失函數:

在這裏插入圖片描述

解釋2:最大似然估計求解參數

在這裏插入圖片描述

對損失函數推導梯度

在這裏插入圖片描述

python代碼實現邏輯迴歸

class LogisticRegression:
    
    #默認沒有正則化,正則項參數默認爲1,學習率默認爲0.001,迭代次數爲10001次
    def __init__(self,penalty = None,Lambda = 1,a = 0.001,epochs = 10001):
        self.W = None
        self.penalty = penalty
        self.Lambda = Lambda
        self.a = a
        self.epochs =epochs
        self.sigmoid = lambda x:1/(1 + np.exp(-x))
        
    def loss(self,x,y):
        m=x.shape[0]
        y_pred = self.sigmoid(x * self.W)
        return (-1/m) * np.sum((np.multiply(y, np.log(y_pred)) + np.multiply((1-y),np.log(1-y_pred))))
    
    def fit(self,x,y):
        lossList = []
        #計算總數據量
        m = x.shape[0]
        #給x添加偏置項
        X = np.concatenate((np.ones((m,1)),x),axis = 1)
        #計算總特徵數
        n = X.shape[1]
        #初始化W的值,要變成矩陣形式
        self.W = np.mat(np.ones((n,1)))
        #X轉爲矩陣形式
        xMat = np.mat(X)
        #y轉爲矩陣形式,這步非常重要,且要是m x 1的維度格式
        yMat = np.mat(y.reshape(-1,1))
        #循環epochs次
        for i in range(self.epochs):
            #預測值
            h = self.sigmoid(xMat * self.W)
            gradient = xMat.T * (h - yMat)/m
            
            
            #加入l1和l2正則項,和之前的線性迴歸正則化一樣
            if self.penalty == 'l2':
                gradient = gradient + self.Lambda * self.W
            elif self.penalty == 'l1':
                gradient = gradient + self.Lambda * np.sign(self.W)
          
            self.W = self.W-self.a * gradient
            if i % 50 == 0:
                lossList.append(self.loss(xMat,yMat))
		#返回係數,和損失列表
        return self.W,lossList

實例展示

下面我們繼續用sklearn生成數據集,來看看效果

from sklearn.datasets import make_classification
from matplotlib import pyplot as plt

#生成2特徵分類數據集
x,y =make_classification(n_features=2,n_redundant=0,n_informative=1,n_clusters_per_class=1,random_state=2043)

#第一個特徵作爲x軸,第二個特徵作爲y軸
plt.scatter(x[:,0],x[:,1],c=y)
plt.show()

在這裏插入圖片描述
數據分佈如上,現在用我們寫好的邏輯迴歸來做分類:

#默認參數
lr = LogisticRegression()
w,lossList = lr.fit(x,y)

#前面講過,z=0是線性分類臨界線
# w[0]+ x*w[1] + y* w[2]=0,求解y (x,y其實就是x1,x2)
x_test = [[-1],[0.7]]
y_test = (-w[0]-x_test*w[1])/w[2] 

plt.scatter(x[:,0],x[:,1],c=y)
plt.plot(x_test,y_test)
plt.show()

在這裏插入圖片描述

損失圖像:

#畫圖 loss值的變化
n = np.linspace(0,10000,201)
plt.plot(n,lossList,c='r')
plt.title('Train')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.show()

在這裏插入圖片描述
損失隨着迭代次數的增加,一直在減小,但是,很明顯,當前迭代次數,並沒有使得模型參數收斂。

迭代50000次,來看效果:

lr = LogisticRegression(epochs=50000)
w,lossList = lr.fit(x,y)

#前面講過,z=0是線性分類臨界線
# w[0]+ x*w[1] + y* w[2]=0,求解y (x,y其實就是x1,x2)
x_test = [[-1],[0.7]]
y_test = (-w[0]-x_test*w[1])/w[2] 

plt.scatter(x[:,0],x[:,1],c=y)
plt.plot(x_test,y_test)
plt.show()

在這裏插入圖片描述

#畫圖 loss值的變化
n=np.linspace(0,50000,1000)
plt.plot(n,lossList,c='r')
plt.title('Train')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.show()

在這裏插入圖片描述

此時模型基本上已經收斂,輸出模型參數,計算模型分類效果:

print('模型參數是:\n',w,'\n')

#這裏感覺其實處理x,y不應該放在封裝好的類裏面處理,應該拿出來,作爲全局變量使用,優化點
m = x.shape[0]
X = np.concatenate((np.ones((m,1)),x),axis = 1)
xMat = np.mat(X)
y_pred = [1 if x >= 0.5 else 0 for x in lr.sigmoid(xMat*w)]

from sklearn.metrics import classification_report
print(classification_report(y,y_pred))

在這裏插入圖片描述

準確率、召回率、F1的值如圖所示,整體分類效果還行。

sklearn對比

接下來,我們調用sklearn的邏輯迴歸庫,來分類數據集:

from sklearn.linear_model import LogisticRegression as LR
clf = LR(penalty='none') #查看係數可知,默認帶L2正則化,且正則項參數C=1,這裏的C是正則項倒數,越小懲罰越大,這裏也不用正則化
clf.fit(x,y)
print('sklearn擬合的參數是:\n','係數:',clf.coef_,'\n','截距:',clf.intercept_)

y_pred_1 = clf.predict(x)
print('\n')
print(classification_report(y,y_pred_1))

在這裏插入圖片描述
係數比較接近,分類效果也差不多。

plt.rcParams['font.sans-serif']=['SimHei'] #用來正常顯示中文標籤
plt.rcParams['axes.unicode_minus']=False #用來正常顯示負號

y_test_1 = (-clf.intercept_ - clf.coef_[0][0] * np.array(x_test))/clf.coef_[0][1]

fig =plt.figure()
ax1= fig.add_subplot()
ax1.scatter(x[:,0],x[:,1],c=y,label='樣本分佈')
ax1.plot(x_test,y_test,c='r',label='python代碼擬合')
ax1.plot(x_test,y_test_1,c='k',label='sklearn擬合')
ax1.legend(prop = {'size':10}) #此參數改變標籤字號的大小
plt.show()

在這裏插入圖片描述
兩條分類線基本上重合了。

L1、L2正則化作比較

上面的sklearn邏輯迴歸中,當clf = LR()即默認L2正則化,C=1時,兩個類別F1的值都是0.9。
正則化的比較這裏就不展示了,大家可以自行去測試一下,原理和線性迴歸的正則化原理一樣,效果也差不多。
只不過我們自己寫的python代碼裏面Lambda越大,懲罰越強;
而sklearn裏面,C越小,懲罰越強。


總結

邏輯迴歸作爲線性迴歸的變種,它的用途很廣,因此掌握它的原理是很有必要的,打好線性迴歸(邏輯迴歸也是廣義線性迴歸)的基礎,以後對我們學習其他算法大有裨益。

問題:
1、邏輯迴歸怎麼處理多分類問題;
2、應用邏輯迴歸算法,怎麼做樣本不均衡的二分類模型;
3、怎麼使用正則化;


這裏給上劉建平博士的博客鏈接,寫的很精煉:
鏈接: scikit-learn 邏輯迴歸類庫使用小結

以上問題我們這裏就暫不展開討論了,手寫算法系列,我們專注算法的底層原理和數學推導、python代碼實現,來幫助大家更好的理解這些算法;
應用層面的問題,我會在隨筆欄目中,慢慢補上。

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