【導(dǎo)語(yǔ)】學(xué)習(xí)邏輯回歸模型,今天的內(nèi)容輕松帶你從0到100!阿里巴巴達(dá)摩院算法專(zhuān)家、阿里巴巴技術(shù)發(fā)展專(zhuān)家、阿里巴巴數(shù)據(jù)架構(gòu)師聯(lián)合撰寫(xiě),從技術(shù)原理、算法和工程實(shí)踐3個(gè)維度系統(tǒng)展開(kāi),既適合零基礎(chǔ)讀者快速入門(mén),又適合有基礎(chǔ)讀者理解其核心技術(shù);寫(xiě)作方式上避開(kāi)了艱澀的數(shù)學(xué)公式及其推導(dǎo),深入淺出。
0、前言
簡(jiǎn)單理解邏輯回歸,就是在線性回歸基礎(chǔ)上加一個(gè) Sigmoid 函數(shù)對(duì)線性回歸的結(jié)果進(jìn)行壓縮,令其最終預(yù)測(cè)值 y 在一個(gè)范圍內(nèi)。這里 Sigmoid 函數(shù)的作用就是將一個(gè)連續(xù)的數(shù)值壓縮到一定范圍內(nèi),它將最終預(yù)測(cè)值 y 的范圍壓縮到在 0 到 1 之間。雖然邏輯回歸也有回歸這個(gè)詞,但由于這里的自變量和因變量呈現(xiàn)的是非線性關(guān)系,因此嚴(yán)格意義上講邏輯回歸模型屬于非線性模型。邏輯回歸模型通常用來(lái)處理二分類(lèi)問(wèn)題,如圖 4-4 所示。在邏輯回歸中,計(jì)算出的預(yù)測(cè)值是一個(gè) 0 到 1 的概率值,通常的,我們以 0.5 為分界線,如果預(yù)測(cè)的概率值大于 0.5 則會(huì)將最終結(jié)果歸為 1 這個(gè)類(lèi)別,如果預(yù)測(cè)的概率值小于等于 0.5 則會(huì)將最終結(jié)果歸為 0 這個(gè)類(lèi)別。而 1 和 0 在實(shí)際項(xiàng)目中可能代表了很多含義,比如 1 代表惡性腫瘤,0 代表良性腫瘤,1 代表銀行可以給小王貸款,0 代表銀行不能給小王貸款等等。
圖4-4 邏輯回歸分類(lèi)示意圖
雖然邏輯回歸很簡(jiǎn)單,但它被廣泛應(yīng)用在實(shí)際生產(chǎn)之中,而且通過(guò)改造邏輯回歸也可以處理多分類(lèi)問(wèn)題。邏輯回歸不僅本身非常受歡迎,它同樣也是我們將在第 5 章介紹的神經(jīng)網(wǎng)絡(luò)的基礎(chǔ)。普通神經(jīng)網(wǎng)絡(luò)中,常常使用 Sigmoid 對(duì)神經(jīng)元進(jìn)行激活。關(guān)于神經(jīng)網(wǎng)絡(luò)的神經(jīng)元,第 5 章會(huì)有詳細(xì)的介紹(第 5 章會(huì)再次提到 Sigmoid 函數(shù)),這里只是先提一下邏輯回歸和神經(jīng)網(wǎng)絡(luò)的關(guān)系,讀者有個(gè)印象。
Sigmoid 的函數(shù)表達(dá)式如下:
該公式中,e 約等于 2.718,z 則是線性回歸的方程式,p 為計(jì)算出來(lái)的概率,范圍在 0 到 1 之間。接下來(lái)我們將這個(gè)函數(shù)繪制出來(lái),看看它的形狀。使用 Python 的 Numpy 以及 Matplotlib 庫(kù)進(jìn)行編寫(xiě),代碼如下:
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
y = 1.0 / (1.0 np.exp(-x))
return y
plot_x = np.linspace(-10, 10, 100)
plot_y = sigmoid(plot_x)
plt.plot(plot_x, plot_y)
plt.show()
效果如圖 4-5 所示:
圖4-5 Sigmoid函數(shù)
我們對(duì)上圖做一個(gè)解釋?zhuān)?dāng) x 為 0 的時(shí)候,Sigmoid 函數(shù)值為 0.5,隨著 x 的不斷增大,對(duì)應(yīng)的 Sigmoid 值將無(wú)線逼近于 1;而隨著 x 的不斷的減小,Sigmoid 值將不斷逼近于 0 。所以它的值域是在 (0,1) 之間。由于 Sigmoid 函數(shù)將實(shí)數(shù)范圍內(nèi)的數(shù)值壓縮到(0,1)之間,因此也被稱(chēng)為壓縮函數(shù)。但這里多提一下,壓縮函數(shù)其實(shí)可以有很多,比如 tanh 可以將實(shí)數(shù)范圍內(nèi)的數(shù)值壓縮到(-1,1)之間,因此 tanh 有時(shí)也會(huì)被成為壓縮函數(shù)。
在學(xué)習(xí) 4.1.1 小節(jié)的時(shí)候,我們?cè)诮榻B一元線性回歸模型的數(shù)學(xué)表達(dá)之后又介紹了一元線性回歸模型的訓(xùn)練過(guò)程。類(lèi)似的,在 4.2.1 小節(jié)學(xué)習(xí)完邏輯回歸模型的數(shù)學(xué)表達(dá)之后我們來(lái)學(xué)習(xí)邏輯回歸模型的訓(xùn)練方法。首先與 4.1.1 小節(jié)類(lèi)似,我們首先需要確定邏輯回歸模型的評(píng)價(jià)方式,也就是模型的優(yōu)化目標(biāo)。有了這個(gè)目標(biāo),我們才能更好地“教”模型學(xué)習(xí)出我們想要的東西。這里的目標(biāo)也和 4.1.1 一樣,定義為
接下來(lái)是選擇優(yōu)化這個(gè)目標(biāo)的方法,也就是本小節(jié)中重點(diǎn)要介紹的梯度下降法。
首先帶大家簡(jiǎn)單認(rèn)識(shí)一下梯度下降法。梯度下降算法(Gradient Descent Optimization)是常用的最優(yōu)化方法之一。“最優(yōu)化方法”屬于運(yùn)籌學(xué)方法,它指在某些約束條件下,為某些變量選取哪些的值,使得設(shè)定的目標(biāo)函數(shù)達(dá)到最優(yōu)的問(wèn)題。最優(yōu)化方法有很多,常見(jiàn)的有梯度下降法、牛頓法、共軛梯度法等等。由于本書(shū)重點(diǎn)在于帶大家快速掌握“圖像識(shí)別”技能,因此暫時(shí)不對(duì)最優(yōu)化方法進(jìn)行展開(kāi),感興趣的讀者可以自行查閱相關(guān)資料進(jìn)行學(xué)習(xí)。由于梯度下降是一種比較常見(jiàn)的最優(yōu)化方法,而且在后續(xù)第 5 章、第 7 章的神經(jīng)網(wǎng)絡(luò)中我們也將用到梯度下降來(lái)進(jìn)行優(yōu)化,因此我們將在本章詳細(xì)介紹該方法。
接下來(lái)我們以圖形化的方式帶領(lǐng)讀者學(xué)習(xí)梯度下降法。
我們?cè)?Pycharm 新建一個(gè) python 文件,然后鍵入以下代碼:
import numpy as np
import matplotlib.pyplot as plt
if __name__ == '__main__':
plot_x = np.linspace(-1, 6, 141) #從-1到6選取141個(gè)點(diǎn)
plot_y = (plot_x - 2.5) ** 2 – 1 #二次方程的損失函數(shù)
plt.scatter(plot_x[5], plot_y[5], color='r') #設(shè)置起始點(diǎn),顏色為紅色
plt.plot(plot_x, plot_y)
# 設(shè)置坐標(biāo)軸名稱(chēng)
plt.xlabel('theta', fontproperties='simHei', fontsize=15)
plt.ylabel('損失函數(shù)', fontproperties='simHei', fontsize=15)
plt.show()
通過(guò)上述代碼,我們就能畫(huà)出如圖 4-6 所示的損失函數(shù)示意圖,其中 x 軸代表的是我們待學(xué)習(xí)的參數(shù) (theta),y 軸代表的是損失函數(shù)的值(即 loss 值),曲線 y 代表的是損失函數(shù)。我們的目標(biāo)是希望通過(guò)大量的數(shù)據(jù)去訓(xùn)練和調(diào)整參數(shù),使損失函數(shù)的值最小。想要達(dá)到二次方程的最小值點(diǎn),可以通過(guò)求導(dǎo)數(shù)的方式,使得導(dǎo)數(shù)為 0 即可。也就是說(shuō),橫軸上 2.5 的位置對(duì)應(yīng)損失最小,在該點(diǎn)上一元二次方程
切線的斜率則為 0。暫且將導(dǎo)數(shù)描述為 ,其中 J 為損失函數(shù),為待求解的參數(shù)。梯度下降中有個(gè)比較重要的參數(shù):學(xué)習(xí)率
(讀作eta,有時(shí)也稱(chēng)其為步長(zhǎng)),它控制著模型尋找最優(yōu)解的速度。加入學(xué)習(xí)率后的數(shù)學(xué)表達(dá)為 。圖4-6 損失函數(shù)示意圖
接下來(lái)我們畫(huà)圖模擬梯度下降的過(guò)程。
1. 首先定義損失函數(shù)及其導(dǎo)數(shù)
def J(theta): #損失函數(shù)
return (theta-2.5)**2 -1
def dJ(theta): #損失函數(shù)的導(dǎo)數(shù)
return 2 * (theta - 2.5)
2. 通過(guò) Matplotlib 繪制梯度下降迭代過(guò)程,具體代碼如下:
theta = 0.0 #初始點(diǎn)
theta_history = [theta]
eta = 0.1 #步長(zhǎng)
epsilon = 1e-8 #精度問(wèn)題或者eta的設(shè)置無(wú)法使得導(dǎo)數(shù)為0
while True:
gradient = dJ(theta) #求導(dǎo)數(shù)
last_theta = theta #先記錄下上一個(gè)theta的值
theta = theta - eta * gradient #得到一個(gè)新的theta
theta_history.append(theta)
if(abs(J(theta) - J(last_theta)) < epsilon):
break #當(dāng)兩個(gè)theta值非常接近的時(shí)候,終止循環(huán)
plt.plot(plot_x,J(plot_x),color='r')
plt.plot(np.array(theta_history),J(np.array(theta_history)),color='b',marker='x')
plt.show() #一開(kāi)始的時(shí)候?qū)?shù)比較大,因?yàn)樾甭时容^陡,后面慢慢平緩了
print(len(theta_history)) #一共走了46步
我們來(lái)看下所繪制的圖像是什么樣子的,可以觀察到 從初始值 0.0 開(kāi)始不斷的向下前進(jìn),一開(kāi)始的幅度比較大,之后慢慢趨于緩和,逐漸接近導(dǎo)數(shù)為 0,一共走了 46 步。如圖 4-7 所示:
圖4-7 一元二次損失函數(shù)梯度下降過(guò)程示意圖
上一小節(jié)我們主要介紹了什么是梯度下降法,本小節(jié)主要介紹學(xué)習(xí)率對(duì)于梯度下降法的影響。
第一個(gè)例子,我們將 設(shè)置為 0.01(之前是 0.1 ),我們會(huì)觀察到,步長(zhǎng)減少之后,藍(lán)色的標(biāo)記更密集,說(shuō)明步長(zhǎng)減少之后,從起始點(diǎn)到導(dǎo)數(shù)為 0 的步數(shù)增加了。步數(shù)變?yōu)榱?424 步,這樣整個(gè)學(xué)習(xí)的速度就變慢了。效果如圖 4-8 所示:
圖4-8 學(xué)習(xí)率時(shí),一元二次損失函數(shù)梯度下降過(guò)程示意圖
第二個(gè)例子,我們將
設(shè)置為 0.8,我們會(huì)觀察到,代表藍(lán)色的步長(zhǎng)在損失函數(shù)之間跳躍了,但在跳躍過(guò)程中,損失函數(shù)的值依然在不斷的變小。步數(shù)是 22 步,因此當(dāng)學(xué)習(xí)率為 0.8 時(shí),優(yōu)化過(guò)程時(shí)間縮短,但是最終也找到了最優(yōu)解。效果如圖 4-9 所示:圖4-9 學(xué)習(xí)率 時(shí),一元二次損失函數(shù)梯度下降過(guò)程示意圖
第三個(gè)例子,我們將設(shè)置為1.1,看一下效果。這里注意,學(xué)習(xí)率本身是一個(gè) 0 到 1 的概率,因此 1.1 是一個(gè)錯(cuò)誤的值,但為了展示梯度過(guò)大會(huì)出現(xiàn)的情況,我們暫且用這個(gè)值來(lái)畫(huà)圖示意。我們會(huì)發(fā)現(xiàn)程序會(huì)報(bào)這個(gè)錯(cuò)誤 OverflowError: ( 34, 'Result too large' )。我們可以想象得到,這個(gè)步長(zhǎng)跳躍的方向?qū)е铝藫p失函數(shù)的值越來(lái)越大,所以才報(bào)了“Result too large”效果,我們需要修改下求損失函數(shù)的程序:
def J(theta):
try:
return (theta-2.5)**2 -1
except:
return float('inf')
i_iter= 0
n_iters = 10
while i_iter < n_iters:
gradient = dJ(theta)
last_theta = theta
theta = theta - eta * gradient
i_iter = 1
theta_history.append(theta)
if (abs(J(theta) - J(last_theta)) < epsilon):
break # 當(dāng)兩個(gè)theta值非常接近的時(shí)候,終止循環(huán)
另外我們需要增加一下循環(huán)的次數(shù)。
我們可以很明顯的看到,我們損失函數(shù)在最下面,學(xué)習(xí)到的損失函數(shù)的值在不斷的增大,也就是說(shuō)模型不會(huì)找到最優(yōu)解。如圖 4-10 所示:
圖4-10 學(xué)習(xí)率時(shí),一元二次損失函數(shù)不收斂
通過(guò)本小節(jié)的幾個(gè)例子,簡(jiǎn)單講解了梯度下降法,以及步長(zhǎng) 的作用。從三個(gè)實(shí)驗(yàn)我們可以看出,學(xué)習(xí)率是一個(gè)需要認(rèn)真調(diào)整的參數(shù),過(guò)小會(huì)導(dǎo)致收斂過(guò)慢,而過(guò)大可能導(dǎo)致模型不收斂。
邏輯回歸中的 Sigmoid 函數(shù)用來(lái)使值域在(0,1)之間,結(jié)合之前所講的線性回歸,我們所得到的完整的公式其實(shí)是:
,其中的 就是之前所介紹的多元線性回歸。現(xiàn)在的問(wèn)題就比較簡(jiǎn)單明了了,對(duì)于給定的樣本數(shù)據(jù)集 X,y,我們?nèi)绾握业絽?shù) theta ,來(lái)獲得樣本數(shù)據(jù)集 X 所對(duì)應(yīng)分類(lèi)輸出 y(通過(guò)p的概率值)
需要求解上述這個(gè)問(wèn)題,我們就需要先了解下邏輯回歸中的損失函數(shù),假設(shè)我們的預(yù)測(cè)值為:
損失函數(shù)假設(shè)為下面兩種情況,y 表示真值;表示為預(yù)測(cè)值:
結(jié)合上述兩個(gè)假設(shè),我們來(lái)分析下,當(dāng) y 真值為 1 的時(shí)候,p 的概率值越?。ㄔ浇咏?),說(shuō)明y的預(yù)測(cè)值偏向于0,損失函數(shù) cost 就應(yīng)該越大;當(dāng) y 真值為 0 的時(shí)候,如果這個(gè)時(shí)候 p 的概率值越大則同理得到損失函數(shù) cost 也應(yīng)該越大。在數(shù)學(xué)上我們想使用一個(gè)函數(shù)來(lái)表示這種現(xiàn)象,可以使用如下這個(gè):
我們對(duì)上面這個(gè)函數(shù)做一定的解釋?zhuān)瑸榱烁庇^的觀察上述兩個(gè)函數(shù),我們通過(guò) Python 中的 Numpy 以及 Matplotlib 庫(kù)進(jìn)行繪制。
我們先繪制下
,代碼如下:import numpy as np
import matplotlib.pyplot as plt
def logp(x):
y = -np.log(x)
return y
plot_x = np.linspace(0.001, 1, 50) #取0.001避免除數(shù)為0
plot_y = logp(plot_x)
plt.plot(plot_x, plot_y)
plt.show()
如下圖4-9所示:
圖4-9 損失函數(shù)if y=1
當(dāng)p=0的時(shí)候,損失函數(shù)的值趨近于正無(wú)窮,根據(jù)
說(shuō)明y的預(yù)測(cè)值 偏向于0,但實(shí)際上我們的 y 真值為 1 。當(dāng) p 達(dá)到 1 的時(shí)候,y 的真值和預(yù)測(cè)值相同,我們能夠從圖中觀察到損失函數(shù)的值趨近于 0 代表沒(méi)有任何損失。我們?cè)賮?lái)繪制一下
,代碼如下:import numpy as np
imort matplotlib.pyplot as plt
def logp2(x):
y = -np.log(1-x)
return y
plot_x = np.linspace(0, 0.99, 50) #取0.99避免除數(shù)為0
plot_y = logp2(plot_x)
plt.plot(plot_x, plot_y)
plt.show()
效果如圖4-10所示:
圖4-10 損失函數(shù) if y=0
當(dāng)p=1的時(shí)候,損失函數(shù)的值趨近于正無(wú)窮,根據(jù) 說(shuō)明y的預(yù)測(cè)值 偏向于 1,但實(shí)際上我們的 y 真值為 0 。當(dāng) p 達(dá)到 0 的時(shí)候,y 的真值和預(yù)測(cè)值相同,我們能夠從圖中觀察到損失函數(shù)的值趨近于 0 代表沒(méi)有任何損失。
我們?cè)賹?duì)這兩個(gè)函數(shù)稍微整理下,使之合成一個(gè)損失函數(shù):
對(duì)這個(gè)函數(shù)稍微解釋下,當(dāng) y=1 的時(shí)候,后面的式子
就變?yōu)榱?0 ,所以整個(gè)公式成為了;當(dāng) y=0 的時(shí)候前面的式子變?yōu)榱?0,整個(gè)公式就變?yōu)榱?/span>。最后就變?yōu)榱?,?duì)m個(gè)樣本,求一組值使得損失函數(shù)最小。
公式如下:
(其中
= sigmoi;其中 代表了;恒等于1;為列向量)。當(dāng)公式變?yōu)樯鲜龅臅r(shí)候,對(duì)于我們來(lái)說(shuō),只需要求解一組
使得損失函數(shù)最小就可以了,那么對(duì)于如此復(fù)雜的損失函數(shù),我們一般使用的是梯度下降法進(jìn)行求解。結(jié)合之前講的理論,本小節(jié)開(kāi)始動(dòng)手實(shí)現(xiàn)一個(gè)邏輯回歸算法。首先我們定義一個(gè)類(lèi),名字為 LogisticRegressionSelf ,其中初始化一些變量:維度、截距、theta 值,代碼如下:
class LogisticRegressionSelf:
def __init__(self):
'''初始化Logistic regression模型'''
self.coef_ = None #維度
self.intercept_ = None #截距
self._theta = None
接著我們實(shí)現(xiàn)下在損失函數(shù)中的 這個(gè)函數(shù),我們之前在
Sigmoid 函數(shù)那個(gè)小節(jié)已經(jīng)實(shí)現(xiàn)過(guò)了,對(duì)于這個(gè)函數(shù)我們輸入的值為多元線性回歸中的
(其中恒等于1),為了增加執(zhí)行效率,我們建議使用向量化來(lái)處理,而盡量避免使用 for 循環(huán),所以對(duì)于我們使用來(lái)代替,具體代碼如下:def _sigmoid(x):
y = 1.0 / (1.0 np.exp(-x))
return y
接著我們來(lái)實(shí)現(xiàn)損失函數(shù),
代碼如下:
#計(jì)算損失函數(shù)
def J(theta,X_b,y):
p_predcit = self._sigmoid(X_b.dot(theta))
try:
return -np.sum(y*np.log(p_predcit) (1-y)*np.log(1-p_predcit)) / len(y)
except:
return float('inf')
然后我們需要實(shí)現(xiàn)下?lián)p失函數(shù)的導(dǎo)數(shù)。具體求導(dǎo)過(guò)程讀者可以自行百度,我們這邊直接給出結(jié)論,對(duì)于損失函數(shù)cost,得到的導(dǎo)數(shù)值為:
,其中,之前提過(guò)考慮計(jì)算性能盡量避免使用 for 循環(huán)實(shí)現(xiàn)累加,所以我們使用向量化計(jì)算。完整代碼如下:
import numpy as np
class LogisticRegressionSelf:
def __init__(self):
'''初始化Logistic regression模型'''
self.coef_ = None #維度
self.intercept_ = None #截距
self._theta = None
#sigmoid函數(shù),私有化函數(shù)
def _sigmoid(self,x):
y = 1.0 / (1.0 np.exp(-x))
return y
def fit(self,X_train,y_train,eta=0.01,n_iters=1e4):
assert X_train.shape[0] == y_train.shape[0], '訓(xùn)練數(shù)據(jù)集的長(zhǎng)度需要和標(biāo)簽長(zhǎng)度保持一致'
#計(jì)算損失函數(shù)
def J(theta,X_b,y):
p_predcit = self._sigmoid(X_b.dot(theta))
try:
return -np.sum(y*np.log(p_predcit) (1-y)*np.log(1-p_predcit)) / len(y)
except:
return float('inf')
#求sigmoid梯度的導(dǎo)數(shù)
def dJ(theta,X_b,y):
x = self._sigmoid(X_b.dot(theta))
return X_b.T.dot(x-y)/len(X_b)
#模擬梯度下降
def gradient_descent(X_b,y,initial_theta,eta,n_iters=1e4,epsilon=1e-8):
theta = initial_theta
i_iter = 0
while i_iter < n_iters:
gradient = dJ(theta,X_b,y)
last_theta = theta
theta = theta - eta * gradient
i_iter = 1
if (abs(J(theta,X_b,y) - J(last_theta,X_b,y)) < epsilon):
break
return theta
X_b = np.hstack([np.ones((len(X_train),1)),X_train])
initial_theta = np.zeros(X_b.shape[1]) #列向量
self._theta = gradient_descent(X_b,y_train,initial_theta,eta,n_iters)
self.intercept_ = self._theta[0] #截距
self.coef_ = self._theta[1:] #維度
return self
def predict_proba(self,X_predict):
X_b = np.hstack([np.ones((len(X_predict), 1)), X_predict])
return self._sigmoid(X_b.dot(self._theta))
def predict(self,X_predict):
proba = self.predict_proba(X_predict)
return np.array(proba > 0.5,dtype='int')
小結(jié)
以上內(nèi)容主要講述了線性回歸模型和邏輯回歸模型,并做了相應(yīng)的實(shí)現(xiàn)。其中線性回歸是邏輯回歸的基礎(chǔ),而邏輯回歸經(jīng)常被當(dāng)做神經(jīng)網(wǎng)絡(luò)的神經(jīng)元,因此邏輯回歸又是神經(jīng)網(wǎng)絡(luò)的基礎(chǔ)。我們借邏輯回歸模型介紹了機(jī)器學(xué)習(xí)中離不開(kāi)的最優(yōu)化方法,以及最常見(jiàn)的最優(yōu)化方法——梯度下降。了解本節(jié)內(nèi)容會(huì)對(duì)接下來(lái)第 5 章神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)有著很大的幫助。
以上內(nèi)容主要講述了線性回歸模型和邏輯回歸模型,并做了相應(yīng)的實(shí)現(xiàn)。其中線性回歸是邏輯回歸的基礎(chǔ),而邏輯回歸經(jīng)常被當(dāng)做神經(jīng)網(wǎng)絡(luò)的神經(jīng)元,因此邏輯回歸又是神經(jīng)網(wǎng)絡(luò)的基礎(chǔ)。我們借邏輯回歸模型介紹了機(jī)器學(xué)習(xí)中離不開(kāi)的最優(yōu)化方法,以及最常見(jiàn)的最優(yōu)化方法——梯度下降。了解本節(jié)內(nèi)容會(huì)對(duì)接下來(lái)第 5 章神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)有著很大的幫助。本文摘自《深度學(xué)習(xí)與圖像識(shí)別:原理與實(shí)踐》,經(jīng)出版方授權(quán)發(fā)布。
聯(lián)系客服