機器學習5- 對數幾率回歸+Python實現
1. 對數幾率回歸
考慮二分類任務,其輸出標記 \(y \in \{0, 1\}\),記線性回歸模型產生的預測值 \(z=\boldsymbol{w}^T\boldsymbol{x} + b\) 是實值,於是我們需要一個將實值 \(z\) 轉換為 \(0/1\) 的 \(g^{-}(\cdot)\)。
最理想的單位階躍函數(unit-step function)
0, & z < 0 \\
0.5, & z = 0 \\
1, & z > 0 \\
\end{cases}
\tag{1.1}
\]
並不是連續函數,因此不能作為 \(g^-(\cdot)\) 。於是我們選用對數幾率函數(logistics function)作為單位階躍函數的替代函數(surrogate function):
\]
如下圖所示:
對數幾率函數是 Sigmoid 函數(即形似 S 的函數)的一種。
將對數幾率函數作為 \(g^-(\cdot)\) 得到
\]
\]
若將 \(y\) 視為樣本 \(\boldsymbol{x}\) 為正例的可能性,則 \(1-y\) 是其為反例的可能性,兩者的比值為
\]
稱為幾率(odds),反映了 \(\boldsymbol{x}\) 作為正例的相對可能性。對幾率取對數得到對數幾率(log odds,或 logit):
\]
所以,式 (1.3) 實際上是用線性回歸模型的預測結果取逼近真實標記的對數幾率,因此其對應的模型又稱為對數幾率回歸(logistic regression, 或 logit regression)。
這種分類學習方法直接對分類可能性進行建模,無需事先假設數據分布,避免了假設分布不準確帶來的問題;
它能得到近似概率預測,這對需要利用概率輔助決策的任務很有用;
對率函數是任意階可導的凸函數,有很好的數學性質,許多數值優化演算法都可直接用於求解最優解。
1.1 求解 ω 和 b
將式 (1.3) 中的 \(y\) 視為類後驗概率估計 \(p(y = 1 | \boldsymbol{x})\),則式 (1.4) 可重寫為
\]
有
\]
\]
通過極大似然法(maximum likelihood method)來估計 \(\boldsymbol{w}\) 和 \(b\) 。
給定數據集 \(\{(\boldsymbol{x}_i, y_i)\}^m_{i=1}\),對率回歸模型最大化對數似然(log-likelihood):
\]
即令每個樣本屬於其真實標記的概率越大越好。
令 \(\boldsymbol{\beta} = (\boldsymbol{w};b)\),\(\hat{\boldsymbol{x}} = (\boldsymbol{x};1)\),則 \(\boldsymbol{w}^T\boldsymbol{x} + b\) 可簡寫為 \(\boldsymbol{\beta}^T\hat{\boldsymbol{x}}\)。再令 \(p_1(\hat{\boldsymbol{x}};\boldsymbol{\beta}) = p(y=1|\hat{\boldsymbol{x}};\boldsymbol{\beta})\),\(p_0(\hat{\boldsymbol{x}};\boldsymbol{\beta}) = p(y=0|\hat{\boldsymbol{x}};\boldsymbol{\beta}) = 1-p_1(\hat{\boldsymbol{x}};\boldsymbol{\beta})\) 。則式 (1.10) 可簡寫為:
\]
將式 (1.11) 帶入 (1.10),並根據式 (1.8) 和 (1.9) 可知,最大化式 (1.10) 等價於最小化
\]
式 (1.12) 是關於 \(\boldsymbol{\beta}\) 的高階可導凸函數,根據凸優化理論,經典的數值優化演算法如梯度下降法(gradient descent method)、牛頓法(Newton method)等都可求得其最優解,於是得到:
\]
以牛頓法為例, 其第 \(t+1\) 輪迭代解的更新公式為:
\]
其中關於 \(\boldsymbol{\beta}\) 的一階、二階導數分別為:
\]
\]
2. 對數幾率回歸進行垃圾郵件分類
2.1 垃圾郵件分類
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model.logistic import LogisticRegression
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.feature_extraction.text import TfidfVectorizer
from matplotlib.font_manager import FontProperties
df = pd.read_csv("SMSSpamCollection", delimiter='\t', header=None)
df.head()
print("spam 數量: ", df[df[0] == 'spam'][0].count())
print("ham 數量: ", df[df[0] == 'ham'][0].count())
spam 數量: 747
ham 數量: 4825
X_train_raw, X_test_raw, y_train, y_test = train_test_split(df[1], df[0])
# 計算TF-IDF權重
vectorizer = TfidfVectorizer()
X_train = vectorizer.fit_transform(X_train_raw)
X_test = vectorizer.transform(X_test_raw)
# 建立模型
classifier = LogisticRegression()
classifier.fit(X_train, y_train)
y_preds = classifier.predict(X_test)
for i, y_pred in enumerate(y_preds[-10:]):
print("預測類型: %s -- 資訊: %s" % (y_pred, X_test_raw.iloc[i]))
預測類型: ham -- 資訊: Aight no rush, I'll ask jay
預測類型: ham -- 資訊: Sos! Any amount i can get pls.
預測類型: ham -- 資訊: You unbelievable faglord
預測類型: ham -- 資訊: Carlos'll be here in a minute if you still need to buy
預測類型: spam -- 資訊: Meet after lunch la...
預測類型: ham -- 資訊: Hey tmr maybe can meet you at yck
預測類型: ham -- 資訊: I'm on da bus going home...
預測類型: ham -- 資訊: O was not into fps then.
預測類型: ham -- 資訊: Yes..he is really great..bhaji told kallis best cricketer after sachin in world:).very tough to get out.
預測類型: ham -- 資訊: Did you show him and wot did he say or could u not c him 4 dust?
2.2 模型評估
混淆舉證
test = y_test
test[test == "ham"] = 0
test[test == "spam"] = 1
pred = y_preds
pred[pred == "ham"] = 0
pred[pred == "spam"] = 1
from sklearn.metrics import confusion_matrix
test = test.astype('int')
pred = pred.astype('int')
confusion_matrix = confusion_matrix(test.values, pred)
print(confusion_matrix)
plt.matshow(confusion_matrix)
font = FontProperties(fname=r"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc")
plt.title(' 混淆矩陣',fontproperties=font)
plt.colorbar()
plt.ylabel(' 實際類型',fontproperties=font)
plt.xlabel(' 預測類型',fontproperties=font)
plt.show()
[[1191 1]
[ 50 151]]
精度
from sklearn.metrics import accuracy_score
print(accuracy_score(test.values, pred))
0.9633883704235463
交叉驗證精度
df = pd.read_csv("sms.csv")
df.head()
X_train_raw, X_test_raw, y_train, y_test = train_test_split(df['message'], df['label'])
vectorizer = TfidfVectorizer()
X_train = vectorizer.fit_transform(X_train_raw)
X_test = vectorizer.transform(X_test_raw)
classifier = LogisticRegression()
classifier.fit(X_train, y_train)
scores = cross_val_score(classifier, X_train, y_train, cv=5)
print(' 精度:',np.mean(scores), scores)
精度: 0.9562200956937799 [0.94736842 0.95933014 0.95574163 0.95574163 0.96291866]
準確率召回率
precisions = cross_val_score(classifier, X_train, y_train, cv=5, scoring='precision')
print('準確率:', np.mean(precisions), precisions)
recalls = cross_val_score(classifier, X_train, y_train, cv=5, scoring='recall')
print('召回率:', np.mean(recalls), recalls)
準確率: 0.9920944081237428 [0.98550725 1. 1. 0.98701299 0.98795181]
召回率: 0.6778796653796653 [0.61261261 0.69642857 0.66964286 0.67857143 0.73214286]
F1 度量
f1s = cross_val_score(classifier, X_train, y_train, cv=5, scoring='f1')
print(' 綜合評價指標:', np.mean(f1s), f1s)
綜合評價指標: 0.8048011339652206 [0.75555556 0.82105263 0.80213904 0.8042328 0.84102564]
ROC AUC
from sklearn.metrics import roc_curve, auc
predictions = classifier.predict_proba(X_test)
false_positive_rate, recall, thresholds = roc_curve(y_test, predictions[:, 1])
roc_auc = auc(false_positive_rate, recall)
plt.title('Receiver Operating Characteristic')
plt.plot(false_positive_rate, recall, 'b', label='AUC = %0.2f' % roc_auc)
plt.legend(loc='lower right')
plt.plot([0, 1], [0, 1], 'r--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.0])
plt.ylabel('Recall')
plt.xlabel('Fall-out')
plt.show()