一文徹底理解邏輯迴歸:從公式推導到程式碼實現

wen發表於2022-03-04

傳送門:點我

前言

大家好,這裡是 Codeman 。這是本文的第二次修訂,在前文的基礎之上,這次我增加了很多公式的推導,從數學原理到程式碼實現,本文提供了一站式服務,希望能幫助讀者從根上理解邏輯迴歸,一勞永逸地解決問題!

多次修訂,只源於我精益求精的人生態度。寫部落格,我是認真的!如果你覺得本文確實對你有幫助,請點個贊支援我一下吧 ?

正文

邏輯迴歸在社會和自然科學中應用非常廣泛,它其實是一種統計學習方法,因為它的底層方法就是線性迴歸。它們不同的地方在於:線性迴歸適用於解決迴歸問題,而邏輯迴歸適用於解決分類問題。這是為什麼呢?別急,看完本文你就懂了。因此,我們可以說邏輯迴歸是基於迴歸的偽迴歸演算法!

image.png

在自然語言處理中,邏輯迴歸是用於分類的基線監督機器學習演算法,它與神經網路也有非常密切的關係,神經網路可以被視為一系列堆疊在一起的邏輯迴歸分類器。

由於後文涉及到很多數學公式的推導,因此我們先做一個符號約定:

符號 含義
\(?\) 訓練集中樣本的數量
\(?\) 特徵的數量
\(?\) 特徵/輸入變數
\(?\) 目標變數/輸出變數
\((?,?)\) 訓練集中的樣本
\((?^{(?)},?^{(?)})\) \(?\) 個觀察樣本
\(?_j^{(i)}\) \(?\) 個觀察樣本的第 \(j\) 個特徵
\(ℎ\) 學習演算法的解決方案或函式,也稱為假設(hypothesis)
\(\widehat{?}=ℎ(?)\) 預測值

我們先從線性迴歸開始講起,一步一步地領略邏輯迴歸的全貌。

1.線性迴歸

線性迴歸是一種使用特徵屬性的線性組合來預測響應的方法。它的目標是找到一個線性函式,以儘可能準確地描述特徵或自變數(\(x\))與響應值(\(y\))之間的關係,使得預測值與真實值之間的誤差最小化。

image.png

1.1 數學定義

在數學上,線性迴歸要找的這個線性函式叫回歸方程,其定義如下:

\[h(x^{(i)})=\beta_0+\beta_1x^{(i)}\tag{1.1} \]

其中,\(h(x^{(i)})\)表示第 \(i\) 個樣本的預測響應值。\(b_0\)\(b_1\) 是迴歸係數,分別代表迴歸線的 \(y\) 軸截距和斜率。這種形式通常見於特徵只有單個屬性的時候,也就是一元線性迴歸,我們初高中所學的就是這種。

在機器學習中,通常每個樣本都有 \(n\) 個特徵屬性,每個特徵 \(x_i\) 都有一個對應的權值 \(w_i\),此時我們需要的就是多元線性迴歸:

\[ℎ(?)=?_0x_0 +?_1?_1 +?_2?_2 +...+?_??_?=w^TX\tag{1.2} \]

其中,\(x_0=1\) 沒有實義,只是為了方便寫成矩陣的形式,\(w_0\) 則等價於式 (1.1) 中的 \(\beta_0\),把 \(\beta_0\) 融入矩陣中,不僅為了看起來簡潔,也是為了方便計算。

損失函式採用平方和損失:

\[?(?^{(?)})=\frac{1}{2}(ℎ(?^{(?)})−?^{(?)})^2\tag{1.3} \]

代價函式定義如下:

\[J(w)=\frac{1}{2}\sum_{?=1}^?(ℎ(?^{(?)})−?^{(?)})^2\tag{1.4} \]

損失函式(Loss Function)度量單樣本預測的誤差,代價函式(Cost Function)度量全部樣本的平均誤差。常用的代價函式有均方誤差、均方根誤差、平均絕對誤差等。

損失函式的係數 1/2 是為了便於計算,使對平方項求導後的常數係數為 1。

我們的目標是要找到一組 \(?(?_0,?_1,?_2,...,?_?)\),使得代價函式 \(J(w)\) 最小,即最小化 \(\frac{\partial {?(?)}}{\partial ?}\)

下面我們將詳細描述用最小二乘法求 \(?\) 的推導過程。

1.2 最小二乘法

為了方便敘述,我們將\(J(w)\)用矩陣形式表達,即:

\[J(?)=\frac{1}{2}(??−?)^2=\frac{1}{2}(??−?)^?(??−?)\tag{1.5} \]

其中,\(?\)\(?\)\(?+1\) 列的矩陣(第一列全為 \(1\),即式 (1.2) 中的 \(x_0\)),\(?\)\(?+1\)\(1\) 列的矩陣(包含了 \(?_0\) ),\(?\)\(?\)\(1\) 列的矩陣。

\[X=\left[\begin{array}{cccccc} 1 & x_{1}^{(1)} & x_{2}^{(1)} & x_{3}^{(1)} & \ldots & x_{n}^{(1)} \\ 1 & x_{1}^{(2)} & x_{2}^{(2)} & x_{3}^{(2)} & \ldots & x_{n}^{(2)} \\ \vdots & \vdots & \vdots & \vdots & \vdots & \vdots \\ 1 & x_{1}^{(m)} & x_{2}^{(m)} & x_{3}^{(m)} & \ldots & x_{n}^{(m)} \end{array}\right],\quad w=\left[\begin{array}{c}w_0\\w_2\\\vdots \\w_n \end{array}\right],\quad Y=\left[\begin{array}{c}y^{(1)} \\y^{(2)} \\\vdots \\y^{(m)}\end{array}\right] \]

對式 (1.5) 求導,可得:

\[\frac{\partial {?(?)}}{\partial ?}=\frac{1}{2}\frac{\partial}{\partial ?}(w^TX^TXw-Y^TXw-w^TX^TY+Y^TY)\tag{1.6} \]

\(Y^TXw=(w^TX^TY)^T\)\(\frac{??^??}{??}=2?\),所以

\[\begin{aligned} \frac{\partial {?(?)}}{\partial ?} &=\frac{1}{2}\frac{\partial}{\partial ?}(w^TX^TXw-2w^TX^TY+Y^TY)\\ &=\frac{1}{2}(2X^TXw-2X^TY+0)\\ &=X^TXw-X^TY \end{aligned}\tag{1.7} \]

\(\frac{\partial {?(?)}}{\partial ?}=0\),則:

\[?=(?^??)^{−1}?^??\tag{1.8} \]

由上式可知,最小二乘法需要計算\((?^??)^{−1}\),但是矩陣求逆的時間複雜度為 \(?(?3)\),因此當特徵數量 \(?\) 較大時,其運算代價非常大。所以這種方法只適用於特徵數量較少的線性模型,不適用於其他模型。

現代機器學習中常用的引數更新方法是梯度下降法。

1.3 梯度下降法

根據梯度下降的每一步中用到的樣本數量,可以將梯度下降法分為以下三類:

類別 樣本數量 公式
批量梯度下降(Batch Gradient Descent,BGD) 所有 \(w_{j}:=w_{j}-\alpha\frac{1}{m}\sum_{i=1}^{m}\left(\left(h\left(x^{(i)}\right)-y^{(i)}\right)\cdot x_{j}^{(i)}\right)\)
隨機梯度下降(Stochastic Gradient Descent,SGD) 一個 \(w_{j}:=w_{j}-\alpha\left(h\left(x^{(i)}\right)-y^{(i)}\right)\cdot x_{j}^{(i)}\)
小批量梯度下降(Mini-Batch Gradient Descent,MBGD) 部分 \(w_{j}:=w_{j}-\alpha\frac{1}{b}\sum_{k=i}^{i+b-1}\left(h\left(x^{(k)}\right)-y^{(k)}\right) x_{j}^{(k)}\)

其中,\(\alpha\) 稱為學習率。根據公式,不難看出,BGD 和 SGD 其實是 MBGD 的 \(b\) 取值為 \(m\)\(1\) 時的特殊情況。我們以 SGD 為例,推導一遍引數的更新過程。

\[\begin{aligned} \frac{\partial}{\partial w_{j}} J(w) &=\frac{\partial}{\partial w_{j}} \frac{1}{2}\left(h\left(x^{(i)}\right)-y^{(i)}\right)^{2}\\ &=2 \cdot \frac{1}{2}\left(h\left(x^{(i)}\right)-y^{(i)}\right) \cdot \frac{\partial}{\partial w_{j}}\left(h\left(x^{(i)}\right)-y^{(i)}\right) \\ &=\left(h\left(x^{(i)}\right)-y^{(i)}\right) \cdot \frac{\partial}{\partial w_{j}}\left(\sum_{i=0}^{n}\left(w_{i} x_{i}^{(i)}-y^{(i)}\right)\right) \\ &=\left(h\left(x^{(i)}\right)-y^{(i)}\right) x_{j}^{(i)} \end{aligned}\tag{1.9} \]

\(w_j=w_j-\alpha\frac{\partial}{\partial w_{j}} J(w)\),所以

\[w_{j}:=w_{j}-\alpha\left(h\left(x^{(i)}\right)-y^{(i)}\right) \cdot x_{j}^{(i)}\tag{1.10} \]

1.4 迴歸的評價指標

監督學習主要分迴歸和分類兩種,二者的評價指標是截然不同的。我們先在此介紹迴歸的評價指標。

首先是誤差要越小越好,主要是下面幾種:

指標 公式
均方誤差(Mean Square Error,MSE) \(MSE=\frac{1}{m} \sum_{i=1}^{m}\left(y^{(i)}-\widehat{y}^{(i)}\right)^{2}\)
均方根誤差(Root Mean Square Error,RMSE) \(RMSE(y, \widehat{y})=\sqrt{\frac{1}{m} \sum_{i=1}^{m}\left(y^{(i)}-\widehat{y}^{(i)}\right)^{2}}\)
平均絕對誤差(Mean Absolute Error,MAE) \(MAE(y, \widehat{y})=\frac{1}{m} \sum_{i=1}^{n} |y^{(i)}-\widehat{y}^{(i)} |\)

其次是 \(R^2\) 要越大越好,它越接近於 1,說明模型擬合得越好。計算公式如下:

\[R^{2}(y, \widehat{y})=1-\frac{\sum_{i=0}^{m}\left(y^{(i)}-\widehat{y}^{(i)}\right)^{2}}{\sum_{i=0}^{m}\left(y^{(i)}-\bar{y}\right)^{2}}=1-\frac{\mathrm{SSE}}{\mathrm{SST}}=\frac{\mathrm{SSR}}{\mathrm{SST}}\tag{1.11} \]

其中,\(SSR=\sum_{i=0}^{m}\left(\widehat{y}^{(i)}-\bar{y}\right)^{2}\),\(SSE=\sum_{i=0}^{m}\left(y^{(i)}-\widehat{y}^{(i)}\right)^{2}\),\(SST=\sum_{i=0}^{m}\left(y^{(i)}-\bar{y}\right)^{2}\)

SST 是 Sum of Squares Total 的縮寫,含義是總平方和。它是變數 \(y\) 與其平均值 \(\bar{y}\) 之差的平方和。我們可以將其視為對於變數 \(y\) 在其平均值 \(\bar{y}\) 周圍的分散程度的一種度量,很像描述性統計中的方差。

SSR 是 Sum of Squares Regression 的縮寫,含義是迴歸的平方和。它是預測值 \(\widehat{y}\) 與平均值 \(\bar{y}\) 之差的平方和。我們可以將其視為描述迴歸線與資料的擬合程度的一種度量。

SSE 是 Sum of Squares Error 的縮寫,含義是誤差的平方和。它是預測值 \(\widehat{y}\) 與觀測值 \(y\) 之差的平方和。

image.png

從圖中不難看出,三者的關係是:SST = SSR + SSE。如果 SSR 的值等於 SST,這意味著我們的迴歸模型是完美的。

2.邏輯迴歸

我們前面說過,邏輯迴歸和線性迴歸不同的地方在於:線性迴歸適用於解決迴歸問題,而邏輯迴歸適用於解決分類問題。本節我們就講講造成這種差異的原因。

2.1 Sigmoid函式

我們知道邏輯迴歸的目標是訓練一個分類器,該分類器可以對輸入資料的類別做出決策。以二元邏輯迴歸為例,對於一個輸入 \(x(x_1,x_2,...,x_n)\),我們希望分類器能夠輸出 \(y\) 是 1(是某類的成員)或 0(不是某類的成員)。也就是說,我們想知道這個輸入 \(x\) 是該類成員的概率 \(P(y = 1|x)\)

我們知道邏輯迴歸的底層就是線性迴歸,但是線性迴歸解決的是迴歸問題,那我們如何將一個迴歸問題轉化為一個分類問題呢?為了方便討論,我們再對式 (1.2) 做一點形式變換,即令

\[z=ℎ(?)=?_0 +?_1?_1 +?_2?_2 +...+?_??_?=w^TX\tag{2.1} \]

有些地方寫的是 \(z=?^??+?\),其實是一樣的,因為 \(b\) 可以融入到 \(w_0\) 中。

我們觀察式 (2.1),不難發現,\(z\) 的輸出範圍沒有任何限制,即 \((-∞, +∞)\)。而作為一個分類器,我們需要輸出的是位於 0 和 1 之間的合法概率值。而為了完成這一步轉變,我們就需要 Sigmoid函式 \(σ(z)\)。Sigmoid 函式(命名是因為它的影像呈?形)也稱為邏輯函式,邏輯迴歸的名稱也由此而來。

image.png

Sigmoid 有許多優點,它能將任意實數對映到 \([0,1]\) 範圍內,而且任意階可導。它在 0 附近幾乎是線性的,但在兩端趨於平緩,它能將異常值壓向 0 或 1。

\[\sigma(z)=\frac{1}{1+e^{-z}}=\frac{1}{1+\exp (-z)}\tag{2.2} \]

我們將式 (2.1) 的值代入上式,就能得到一個介於 0 和 1 之間的概率。對於一個二元邏輯迴歸,我們只需要確保 \(P(y = 1)+P(y = 0)=1\) 即可。我們可以這樣做:

\[\begin{aligned} P(y=1) &=\sigma(z)=\frac{1}{1+\exp (-z)} \\ P(y=0) &=1-\sigma(z)=1-\frac{1}{1+\exp (-z)}=\frac{\exp (-z)}{1+\exp (-z)} \end{aligned}\tag{2.3} \]

根據 Sigmoid 函式的性質:

\[1−σ(x)=σ(−x)\tag{2.4} \]

我們也可以將 \(P(y = 0)\) 表示為 \(σ(−z)\)

好了,現在我們已經有了概率值,那麼概率值大於多少我們認為它是屬於 1 類呢?通常,如果概率 \(P(y = 1|x)\) 大於 \(0.5\),我們認為是,否則就不是。這個 0.5 被稱為決策邊界

\[\operatorname{decision}(x)= \begin{cases}1 & \text { if } P(y=1 \mid x)>0.5 \\ 0 & \text { otherwise }\end{cases}\tag{2.5} \]

總結:邏輯迴歸的總體思路就是,先用邏輯函式把線性迴歸的結果 (-∞,∞) 對映到 (0,1),再通過決策邊界建立與分類的概率聯絡。

邏輯函式還有一個很好的特性就是,其導函式可以轉化成本身的一個表示式,推導過程如下:

\[\begin{aligned} \sigma^{\prime}(z)&=\left(\frac{1}{1+e^{-z}}\right)^{\prime} \\ &=\frac{e^{-z}}{\left(1+e^{-z}\right)^{2}} \\ &=\frac{1+e^{-z}-1}{\left(1+e^{-z}\right)^{2}} \\ &=\frac{1}{\left(1+e^{-z}\right)}\left(1-\frac{1}{\left(1+e^{-z}\right)}\right) \\ &=\sigma(z)(1-\sigma(z)) \end{aligned}\tag{2.6} \]

在二分類模型中,事件發生與不發生的概率之比 \(\frac{?}{1−?}\) 稱為事件的機率(odds)。令\(\sigma(z)=p\),解得:

\[z=log(\frac{p}{1-p})\tag{2.7} \]

也就是說,線性迴歸的結果(即 \(z\) )等於對數機率

2.2 代價函式

下面我們講講模型的引數如何更新。我們使用極大似然估計法來求解。極大似然估計法利用已知樣本結果,反推最有可能導致這樣結果的原因。我們的目標就是找到一組引數,使得在這組引數下,我們的樣本的似然度(概率)最大。

對於一個二分類模型,已知 \(P(y=1)=\sigma(z),P(y=0)=1-\sigma(z)\), 則似然函式為:

\[L(w)=\prod_{i=1}^{m} P\left(y^{(i)} \mid x^{(i)} ; w\right)=\prod_{i=1}^{m}\left(\sigma\left(x^{(i)}\right)\right)^{y^{(i)}}\left(1-\sigma\left(x^{(i)}\right)\right)^{1-y^{(i)}}\tag{2.8} \]

等式兩邊同時取對數:

\[l(w)=\log L(w)=\sum_{i=1}^{m}\left(y^{(i)} \log \left(\sigma\left(x^{(i)}\right)\right)+\left(1-y^{(i)}\right) \log \left(1-\sigma\left(x^{(i)}\right)\right)\right)\tag{2.9} \]

則代價函式為:

\[J(w)=-\frac{1}{m} l(w)\tag{2.10} \]

其實,式(2.9)前面再加一個負號,就是我們常用的損失函式——交叉熵損失(cross-entropy loss)

代價函式之所以要加負號,是因為機器學習的目標是最小化損失函式,而極大似然估計法的目標是最大化似然函式。那麼加個負號,正好使二者等價。

2.3 梯度下降法求解

求解邏輯迴歸的方法有非常多,我們這裡主要了解一下梯度下降法。

將式 (2.2) 代入式 (2.10),得:

\[\begin{aligned} \because & y^{(i)} \log \left(\sigma\left(x^{(i)}\right)\right)+\left(1-y^{(i)}\right) \log \left(1-\sigma\left(x^{(i)}\right)\right)\\ &=y^{(i)} \log \left(\frac{1}{1+e^{-w^{T} x^{(i)}}}\right)+\left(1-y^{(i)}\right) \log \left(1-\frac{1}{1+e^{-w^{T} x^{(i)}}}\right)\\ &=-y^{(i)} \log \left(1+e^{-w^{T} x^{(i)}}\right)-\left(1-y^{(i)}\right) \log \left(1+e^{w^{T} x^{(i)}}\right)\\\therefore J(w)&=-\frac{1}{m}\sum_{i=1}^{m}\left(-y^{(i)} \log \left(1+e^{-w^{T} x^{(i)}}\right)-\left(1-y^{(i)}\right) \log \left(1+e^{w^{T} x^{(i)}}\right)\right) \end{aligned}\tag{2.11} \]

\(J(w)\) 求偏導,得:

\[\begin{aligned} \frac{\partial}{\partial w_{j}} J(w)&=\frac{\partial}{\partial w_{j}}\left(-\frac{1}{m} \sum_{i=1}^{m}\left(-y^{(i)} \log \left(1+e^{-w^{T} x^{(i)}}\right)-\left(1-y^{(i)}\right) \log \left(1+e^{w^{T} x^{(i)}}\right)\right)\right) \\ &=-\frac{1}{m} \sum_{i=1}^{m}\left(-y^{(i)} \frac{-x_{j}^{(i)} e^{-w^{T} x^{(i)}}}{1+e^{-w^{T} x^{(i)}}}-\left(1-y^{(i)}\right) \frac{x_{j}^{(i)} e^{w^{T} x^{(i)}}}{1+e^{w^{T} x^{(i)}}}\right) \\ &=-\frac{1}{m} \sum_{i=1}^{m}\left(y^{(i)}-\sigma\left(x^{(i)}\right)\right) x_{j}^{(i)} \\ &=\frac{1}{m} \sum_{i=1}^{m}\left(\sigma\left(x^{(i)}\right)-y^{(i)}\right) x_{j}^{(i)} \end{aligned}\tag{2.12} \]

所以:

\[w_{j}:=w_{j}-\frac{\partial}{\partial w_{j}} J(w)=w_{j}-\alpha \frac{1}{m} \sum_{i=1}^{m}\left(\sigma\left(x^{(i)}\right)-y^{(i)}\right) x_{j}^{(i)}\tag{2.13} \]

2.4 邏輯迴歸的分類

邏輯迴歸對特徵變數(x)和分類響應變數(y)之間的關係進行建模,在給定一組預測變數的情況下,它能給出落入特定類別響應水平的概率。也就是說,你給它一組資料(特徵),它告訴你這組資料屬於某一類別的概率。根據分類響應變數(y)的性質,我們可以將邏輯迴歸分為三類:

  • 二元邏輯迴歸(Binary Logistic Regression)
    當分類結果只有兩種可能的時候,我們就稱為二元邏輯迴歸。例如,考試通過或未通過,回答是或否,血壓高或低。
  • 名義邏輯迴歸(Nominal Logistic Regression)
    當存在三個或更多類別且類別之間沒有自然排序時,我們就稱為名義邏輯迴歸。例如,企業的部門有策劃、銷售、人力資源等,顏色有黑色、紅色、藍色、橙色等。
  • 序數邏輯迴歸(Ordinal Logistic Regression)
    當存在三個或更多類別且類別之間有自然排序時,我們就稱為序數邏輯迴歸。例如,評價有好、中、差,身材有偏胖、中等、偏瘦。注意,類別的排名不一定意味著它們之間的間隔相等。

2.5 Softmax Regression

我們以上討論都是以二元分類為前提展開的,但有時可能有兩個以上的類,例如情緒分類有正面、負面或中性三種,手寫數字識別有 0-9 總共十種分類。

在這種情況下,我們需要多元邏輯迴歸,也稱為 Softmax Regression。在多元邏輯迴歸中,我們假設總共有 \(K\) 個類,每個輸入 \(x\) 只能屬於一個類。也就是說,每個輸入 \(x\) 的輸出 \(y\) 將是一個長度為 \(K\) 的向量。如果類 \(c\) 是正確的類,則設定\(\mathbf{y}_c=1\),其他元素設定為 0,即\(\mathbf{y}_{c}=1,\mathbf{y}_{j}=0\quad\forall j \neq c\)。這種編碼方式稱為 one-hot 編碼。此時,分類器的工作是產生一個估計向量。對於每個類 \(k\),產生一個概率估計 \(P(y_k = 1|x)\)

多元邏輯分類器採用 sigmoid 的泛化函式 softmax 來計算 \(P(y_k = 1|x)\)。它能將 \(K\) 個任意值的向量 \(z = [z_1,z_2,\dots,z_K]\) 對映到 (0,1) 範圍內的概率分佈上,並且所有值的總和為 1 。其定義如下:

\[\operatorname{softmax}\left(\mathbf{z}_{i}\right)=\frac{\exp \left(\mathbf{z}_{i}\right)}{\sum_{j=1}^{K} \exp \left(\mathbf{z}_{j}\right)} 1 \leq i \leq K\tag{2.14} \]

因此,輸入向量 \(z = [z_1,z_2,\dots,z_K]\) 的 softmax輸出如下:

\[\operatorname{softmax}(\mathbf{z})=\left[\frac{\exp \left(\mathbf{z}_{1}\right)}{\sum_{i=1}^{K} \exp \left(\mathbf{z}_{i}\right)}, \frac{\exp \left(\mathbf{z}_{2}\right)}{\sum_{i=1}^{K} \exp \left(\mathbf{z}_{i}\right)}, \ldots, \frac{\exp \left(\mathbf{z}_{K}\right)}{\sum_{i=1}^{K} \exp \left(\mathbf{z}_{i}\right)}\right]\tag{2.15} \]

舉個例子,若 \(\mathbf{z}=[0.6,1.1,-1.5,1.2,3.2,-1.1]\),則

\[\operatorname{softmax}(\mathbf{z})=[0.055,0.090,0.006,0.099,0.74,0.010] \]

與 sigmoid 一樣,softmax 也具有將異常值壓向 0 或 1 的特性。因此,如果其中一個輸入遠大於其他輸入,它將傾向於將其概率推向 1,並抑制較小輸入的概率。

由於現在有 \(K\) 個類別,而且每一個類別都有一個單獨的權重向量 \(w_k\),則每個輸出類別的概率估計 \(\widehat{y_k}\) 應該這樣計算:

\[P\left(\mathbf{y}_{k}=1 \mid \mathbf{x}\right)=\frac{\exp \left(\mathbf{w}_{k}^T \cdot \mathbf{x}\right)}{\sum_{j=1}^{K} \exp \left(\mathbf{w}_{j}^T \cdot \mathbf{x}\right)}\tag{2.16} \]

上式看起來我們將分別每個類別計算輸出。但是,我們可以將 \(K\) 個權重向量表示為權重矩陣 \(W\)\(W\) 的第 \(k\) 列對應於權重向量 \(w_k\)。因此,\(W\) 的形狀為 \([(n+1) \times K]\),其中,\(K\) 是輸出類的數量,\(n\) 是輸入特徵的數量,加 \(1\) 對應的就是偏置。這樣一來,我們還是可以通過一個優雅的公式計算 \(\widehat{y}\)

\[\widehat{y}=\operatorname{softmax}{(W^TX)}\tag{2.17} \]

3. 程式碼實現(Pytorch)

class LogisticRegression(torch.nn.Module):

    def __init__(self, num_features):
        super(LogisticRegression, self).__init__() #呼叫父類的建構函式
        self.linear = torch.nn.Linear(num_features, 1)
        
        self.linear.weight.detach().zero_() #權值初始化為0
        self.linear.bias.detach().zero_() #偏置初始化為0
        
    def forward(self, x):
        logits = self.linear(x)
        probas = torch.sigmoid(logits)
        return probas

在 Pytorch 中可以通過繼承torch.nn.Module類來實現自定義模型,在__init__中定義每一層的構造,在forward中定義每一層的連線關係,是實現模型功能的核心,且必須重寫,否則模型將由於無法找到各層的連線關係而無法執行。

torch.nn.Linear對傳入的資料做線性變換,weightbias 是它的兩個變數,分別代表學習的權值和偏置。

4. 鳶尾花分類

我們以鳶尾花資料的分類為例,做一個簡單地用 Logistic 迴歸進行分類的任務。

  • 資料集獲取與劃分
import matplotlib.pyplot as plt
import numpy as np
from io import BytesIO

import torch
import torch.nn.functional as F

ds = np.lib.DataSource()
fp = ds.open('http://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data')

x = np.genfromtxt(BytesIO(fp.read().encode()), delimiter=',', usecols=range(2), max_rows=100)
y = np.zeros(100)
y[50:] = 1

np.random.seed(1)
idx = np.arange(y.shape[0])
np.random.shuffle(idx)
X_test, y_test = x[idx[:25]], y[idx[:25]]
X_train, y_train = x[idx[25:]], y[idx[25:]]
mu, std = np.mean(X_train, axis=0), np.std(X_train, axis=0)
X_train, X_test = (X_train - mu) / std, (X_test - mu) / std

fig, ax = plt.subplots(1, 2, figsize=(7, 2.5))
ax[0].scatter(X_train[y_train == 1, 0], X_train[y_train == 1, 1])
ax[0].scatter(X_train[y_train == 0, 0], X_train[y_train == 0, 1])
ax[1].scatter(X_test[y_test == 1, 0], X_test[y_test == 1, 1])
ax[1].scatter(X_test[y_test == 0, 0], X_test[y_test == 0, 1])
plt.show()

實際的資料共有152行,我們只取前100行。按照下標隨機打亂之後來劃分訓練集和測試集。其中,訓練集有75行,測試集有25行。

image.png

  • 模型訓練
model = LogisticRegression(num_features=2).to(device)
cost_fn = torch.nn.BCELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

def custom_where(cond, x_1, x_2):
    return (cond * x_1) + ((1-cond) * x_2)
    
def comp_accuracy(label_var, pred_probas):
    pred_labels = custom_where((pred_probas > 0.5).float(), 1, 0).view(-1)
    acc = torch.sum(pred_labels == label_var.view(-1)).float() / label_var.size(0)
    return acc


num_epochs = 10

X_train_tensor = torch.tensor(X_train, dtype=torch.float32, device=device)
y_train_tensor = torch.tensor(y_train, dtype=torch.float32, device=device).view(-1, 1)


for epoch in range(num_epochs):
    
    #### Compute outputs ####
    out = model(X_train_tensor)
    
    #### Compute gradients ####
    cost = cost_fn(out, y_train_tensor)
    optimizer.zero_grad()
    cost.backward()
    
    #### Update weights ####  
    optimizer.step()
    
    #### Logging ####      
    pred_probas = model(X_train_tensor)
    acc = comp_accuracy(y_train_tensor, pred_probas)
    print('Epoch: %03d' % (epoch + 1), end="")
    print(' | Train ACC: %.3f' % acc, end="")
    print(' | Cost: %.3f' % cost_fn(pred_probas, y_train_tensor))


    
print('\nModel parameters:')
print('  Weights: %s' % model.linear.weight)
print('  Bias: %s' % model.linear.bias)

BCELoss計算目標值和預測值之間的二進位制交叉熵損失函式,數學公式如下:

\[Loss = -w[ylog(y')+(1-y)log(1-y')] \]

其中,\(w\)\(y\)\(y'\) 分別表示權值、標籤(target)、預測概率值(input probabilities)。reduction取值為sum表明對樣本的損失值進行求和。

輸出如下:

Epoch: 001 | Train ACC: 0.987 | Cost: 5.581
Epoch: 002 | Train ACC: 0.987 | Cost: 4.882
Epoch: 003 | Train ACC: 1.000 | Cost: 4.381
Epoch: 004 | Train ACC: 1.000 | Cost: 3.998
Epoch: 005 | Train ACC: 1.000 | Cost: 3.693
Epoch: 006 | Train ACC: 1.000 | Cost: 3.443
Epoch: 007 | Train ACC: 1.000 | Cost: 3.232
Epoch: 008 | Train ACC: 1.000 | Cost: 3.052
Epoch: 009 | Train ACC: 1.000 | Cost: 2.896
Epoch: 010 | Train ACC: 1.000 | Cost: 2.758

Model parameters:
  Weights: Parameter containing:
tensor([[ 4.2267, -2.9613]], requires_grad=True)
  Bias: Parameter containing:
tensor([0.0994], requires_grad=True)
  • 模型評估
X_test_tensor = torch.tensor(X_test, dtype=torch.float32, device=device)
y_test_tensor = torch.tensor(y_test, dtype=torch.float32, device=device)

pred_probas = model(X_test_tensor)
test_acc = comp_accuracy(y_test_tensor, pred_probas)

print('Test set accuracy: %.2f%%' % (test_acc*100))

輸出如下:

Test set accuracy: 100.00%

image.png

結語

一個非常基礎的入門級的機器學習演算法,但是要完全講透還是非常困難,尤其是公式的推導部分,光用文字描述很難講清楚,但是總體上公式的推導都沒問題,只是說第一眼看不是那麼容易懂,多看幾遍或者自己在紙上寫一寫還是不難理解的。另外,也請讀者發現錯誤後能在評論區指出,我看到後會及時更正。

全文寫下來洋洋灑灑六千字,也花費了好幾天的時間,從自己理解到寫出來讓別人看懂,這之間差別真的很大,可能也是我不善於表達,詞不達意,也請大家見諒。

篇幅這麼長,公式這麼多,看完本文你可能什麼也記不住,但是這兩點你一定要記住:

1. Logistic迴歸不適用於解決迴歸問題,它適用於解決分類問題。千萬不要被它的名稱迷惑!

2. Logistic迴歸 = 線性迴歸 + Sigmoid 函式。當然了,如果是多元迴歸的話就是 softmax 函式。

例行公事?:如果你覺得本文確實對你有幫助,請點個贊支援我一下吧 ?

相關文章