傳送門:點我。
前言
大家好,這裡是 Codeman 。這是本文的第二次修訂,在前文的基礎之上,這次我增加了很多公式的推導,從數學原理到程式碼實現,本文提供了一站式服務,希望能幫助讀者從根上理解邏輯迴歸,一勞永逸地解決問題!
多次修訂,只源於我精益求精的人生態度。寫部落格,我是認真的!如果你覺得本文確實對你有幫助,請點個贊支援我一下吧 ?
正文
邏輯迴歸在社會和自然科學中應用非常廣泛,它其實是一種統計學習方法,因為它的底層方法就是線性迴歸。它們不同的地方在於:線性迴歸適用於解決迴歸問題,而邏輯迴歸適用於解決分類問題。這是為什麼呢?別急,看完本文你就懂了。因此,我們可以說邏輯迴歸是基於迴歸的偽迴歸演算法!
在自然語言處理中,邏輯迴歸是用於分類的基線監督機器學習演算法,它與神經網路也有非常密切的關係,神經網路可以被視為一系列堆疊在一起的邏輯迴歸分類器。
由於後文涉及到很多數學公式的推導,因此我們先做一個符號約定:
符號 | 含義 |
---|---|
\(?\) | 訓練集中樣本的數量 |
\(?\) | 特徵的數量 |
\(?\) | 特徵/輸入變數 |
\(?\) | 目標變數/輸出變數 |
\((?,?)\) | 訓練集中的樣本 |
\((?^{(?)},?^{(?)})\) | 第 \(?\) 個觀察樣本 |
\(?_j^{(i)}\) | 第 \(?\) 個觀察樣本的第 \(j\) 個特徵 |
\(ℎ\) | 學習演算法的解決方案或函式,也稱為假設(hypothesis) |
\(\widehat{?}=ℎ(?)\) | 預測值 |
我們先從線性迴歸開始講起,一步一步地領略邏輯迴歸的全貌。
1.線性迴歸
線性迴歸是一種使用特徵屬性的線性組合來預測響應的方法。它的目標是找到一個線性函式,以儘可能準確地描述特徵或自變數(\(x\))與響應值(\(y\))之間的關係,使得預測值與真實值之間的誤差最小化。
1.1 數學定義
在數學上,線性迴歸要找的這個線性函式叫回歸方程,其定義如下:
其中,\(h(x^{(i)})\)表示第 \(i\) 個樣本的預測響應值。\(b_0\) 和 \(b_1\) 是迴歸係數,分別代表迴歸線的 \(y\) 軸截距和斜率。這種形式通常見於特徵只有單個屬性的時候,也就是一元線性迴歸,我們初高中所學的就是這種。
在機器學習中,通常每個樣本都有 \(n\) 個特徵屬性,每個特徵 \(x_i\) 都有一個對應的權值 \(w_i\),此時我們需要的就是多元線性迴歸:
其中,\(x_0=1\) 沒有實義,只是為了方便寫成矩陣的形式,\(w_0\) 則等價於式 (1.1) 中的 \(\beta_0\),把 \(\beta_0\) 融入矩陣中,不僅為了看起來簡潔,也是為了方便計算。
若損失函式採用平方和損失:
則代價函式定義如下:
損失函式(Loss Function)度量單樣本預測的誤差,代價函式(Cost Function)度量全部樣本的平均誤差。常用的代價函式有均方誤差、均方根誤差、平均絕對誤差等。
損失函式的係數 1/2 是為了便於計算,使對平方項求導後的常數係數為 1。
我們的目標是要找到一組 \(?(?_0,?_1,?_2,...,?_?)\),使得代價函式 \(J(w)\) 最小,即最小化 \(\frac{\partial {?(?)}}{\partial ?}\)。
下面我們將詳細描述用最小二乘法求 \(?\) 的推導過程。
1.2 最小二乘法
為了方便敘述,我們將\(J(w)\)用矩陣形式表達,即:
其中,\(?\) 為 \(?\) 行 \(?+1\) 列的矩陣(第一列全為 \(1\),即式 (1.2) 中的 \(x_0\)),\(?\) 為 \(?+1\) 行 \(1\) 列的矩陣(包含了 \(?_0\) ),\(?\) 為 \(?\) 行 \(1\) 列的矩陣。
對式 (1.5) 求導,可得:
又 \(Y^TXw=(w^TX^TY)^T\),\(\frac{??^??}{??}=2?\),所以
令\(\frac{\partial {?(?)}}{\partial ?}=0\),則:
由上式可知,最小二乘法需要計算\((?^??)^{−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 為例,推導一遍引數的更新過程。
又 \(w_j=w_j-\alpha\frac{\partial}{\partial w_{j}} J(w)\),所以
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,說明模型擬合得越好。計算公式如下:
其中,\(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\) 之差的平方和。
從圖中不難看出,三者的關係是: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=?^??+?\),其實是一樣的,因為 \(b\) 可以融入到 \(w_0\) 中。
我們觀察式 (2.1),不難發現,\(z\) 的輸出範圍沒有任何限制,即 \((-∞, +∞)\)。而作為一個分類器,我們需要輸出的是位於 0 和 1 之間的合法概率值。而為了完成這一步轉變,我們就需要 Sigmoid函式 \(σ(z)\)。Sigmoid 函式(命名是因為它的影像呈?形)也稱為邏輯函式,邏輯迴歸的名稱也由此而來。
Sigmoid 有許多優點,它能將任意實數對映到 \([0,1]\) 範圍內,而且任意階可導。它在 0 附近幾乎是線性的,但在兩端趨於平緩,它能將異常值壓向 0 或 1。
我們將式 (2.1) 的值代入上式,就能得到一個介於 0 和 1 之間的概率。對於一個二元邏輯迴歸,我們只需要確保 \(P(y = 1)+P(y = 0)=1\) 即可。我們可以這樣做:
根據 Sigmoid 函式的性質:
我們也可以將 \(P(y = 0)\) 表示為 \(σ(−z)\)。
好了,現在我們已經有了概率值,那麼概率值大於多少我們認為它是屬於 1 類呢?通常,如果概率 \(P(y = 1|x)\) 大於 \(0.5\),我們認為是,否則就不是。這個 0.5 被稱為決策邊界。
總結:邏輯迴歸的總體思路就是,先用邏輯函式把線性迴歸的結果 (-∞,∞) 對映到 (0,1),再通過決策邊界建立與分類的概率聯絡。
邏輯函式還有一個很好的特性就是,其導函式可以轉化成本身的一個表示式,推導過程如下:
在二分類模型中,事件發生與不發生的概率之比 \(\frac{?}{1−?}\) 稱為事件的機率(odds)。令\(\sigma(z)=p\),解得:
也就是說,線性迴歸的結果(即 \(z\) )等於對數機率。
2.2 代價函式
下面我們講講模型的引數如何更新。我們使用極大似然估計法來求解。極大似然估計法利用已知樣本結果,反推最有可能導致這樣結果的原因。我們的目標就是找到一組引數,使得在這組引數下,我們的樣本的似然度(概率)最大。
對於一個二分類模型,已知 \(P(y=1)=\sigma(z),P(y=0)=1-\sigma(z)\), 則似然函式為:
等式兩邊同時取對數:
則代價函式為:
其實,式(2.9)前面再加一個負號,就是我們常用的損失函式——交叉熵損失(cross-entropy loss)。
代價函式之所以要加負號,是因為機器學習的目標是最小化損失函式,而極大似然估計法的目標是最大化似然函式。那麼加個負號,正好使二者等價。
2.3 梯度下降法求解
求解邏輯迴歸的方法有非常多,我們這裡主要了解一下梯度下降法。
將式 (2.2) 代入式 (2.10),得:
對 \(J(w)\) 求偏導,得:
所以:
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 。其定義如下:
因此,輸入向量 \(z = [z_1,z_2,\dots,z_K]\) 的 softmax輸出如下:
舉個例子,若 \(\mathbf{z}=[0.6,1.1,-1.5,1.2,3.2,-1.1]\),則
與 sigmoid 一樣,softmax 也具有將異常值壓向 0 或 1 的特性。因此,如果其中一個輸入遠大於其他輸入,它將傾向於將其概率推向 1,並抑制較小輸入的概率。
由於現在有 \(K\) 個類別,而且每一個類別都有一個單獨的權重向量 \(w_k\),則每個輸出類別的概率估計 \(\widehat{y_k}\) 應該這樣計算:
上式看起來我們將分別每個類別計算輸出。但是,我們可以將 \(K\) 個權重向量表示為權重矩陣 \(W\)。\(W\) 的第 \(k\) 列對應於權重向量 \(w_k\)。因此,\(W\) 的形狀為 \([(n+1) \times K]\),其中,\(K\) 是輸出類的數量,\(n\) 是輸入特徵的數量,加 \(1\) 對應的就是偏置。這樣一來,我們還是可以通過一個優雅的公式計算 \(\widehat{y}\):
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
對傳入的資料做線性變換,weight
和 bias
是它的兩個變數,分別代表學習的權值和偏置。
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行。
- 模型訓練
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
計算目標值和預測值之間的二進位制交叉熵損失函式,數學公式如下:
其中,\(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%
結語
一個非常基礎的入門級的機器學習演算法,但是要完全講透還是非常困難,尤其是公式的推導部分,光用文字描述很難講清楚,但是總體上公式的推導都沒問題,只是說第一眼看不是那麼容易懂,多看幾遍或者自己在紙上寫一寫還是不難理解的。另外,也請讀者發現錯誤後能在評論區指出,我看到後會及時更正。
全文寫下來洋洋灑灑六千字,也花費了好幾天的時間,從自己理解到寫出來讓別人看懂,這之間差別真的很大,可能也是我不善於表達,詞不達意,也請大家見諒。
篇幅這麼長,公式這麼多,看完本文你可能什麼也記不住,但是這兩點你一定要記住:
1. Logistic迴歸不適用於解決迴歸問題,它適用於解決分類問題。千萬不要被它的名稱迷惑!
2. Logistic迴歸 = 線性迴歸 + Sigmoid 函式。當然了,如果是多元迴歸的話就是 softmax 函式。
例行公事?:如果你覺得本文確實對你有幫助,請點個贊支援我一下吧 ?