一、什麼是 softmax 迴歸?
softmax 迴歸(softmax regression)其實是 logistic 迴歸的一般形式,logistic 迴歸用於二分類,而 softmax 迴歸用於多分類,關於 logistic 迴歸可以看我的這篇博客👉[機器學習-logistic迴歸原理與實現]。
對於輸入數據{(x1,y1),(x2,y2),…,(xm,ym)}有 k 個類別,即yi∈{1,2,…,k},那麼 softmax 迴歸主要估算輸入數據 xi 歸屬於每一類的概率,即
hθ(xi)=⎣⎢⎢⎢⎡p(yi=1∣xi;θ)p(yi=2∣xi;θ)⋮p(yi=k∣xi;θ)⎦⎥⎥⎥⎤=∑j=1keθjTxi1⎣⎢⎢⎢⎢⎡eθ1Txieθ2Txi⋮eθkTxi⎦⎥⎥⎥⎥⎤(1)
其中,θ1,θ2,…,θk∈θ是模型的參數,乘以∑j=1keθjTxi1是爲了讓概率位於[0,1]並且概率之和爲 1,softmax 迴歸將輸入數據 xi 歸屬於類別 j 的概率爲
p(yi=j∣xi;θ)=∑l=1keθlTxieθjTxi(2)
上面的式子可以用下圖形象化的解析(來自臺大李宏毅《一天搞懂深度學習》)。
二、原理
2.1 梯度下降法參數求解
softmax 迴歸的參數矩陣 θ 可以記爲
θ=⎣⎢⎢⎢⎡θ1Tθ2T⋮θkT⎦⎥⎥⎥⎤(3)
定義 softmax 迴歸的代價函數
L(θ)=−m1[i=1∑mj=1∑k1{yi=j}log∑l=1keθlTxieθjTxi](4)
其中,1{·}是示性函數,即1{值爲真的表達式}=1,1{值爲假的表達式}=0。跟 logistic 函數一樣,利用梯度下降法最小化代價函數,下面求解 θ 的梯度。L(θ)關於 θj 的梯度求解爲
∂θj∂L(θ)=−m1∂θj∂[i=1∑mj=1∑k1{yi=j}log∑l=1keθlTxieθjTxi]=−m1∂θj∂[i=1∑mj=1∑k1{yi=j}(θjTxi−logl=1∑keθlTxi)]=−m1[i=1∑m1{yi=j}(xi−j=1∑k∑l=1keθlTxieθjTxi⋅xi)]=−m1[i=1∑mxi1{yi=j}(1−j=1∑k∑l=1keθlTxieθjTxi)]=−m1[i=1∑mxi(1{yi=j}−j=1∑k1{yi=j}∑l=1keθlTxieθjTxi)]=−m1[i=1∑mxi(1{yi=j}−∑l=1keθlTxieθjTxi)]=−m1[i=1∑mxi(1{yi=j}−p(yi=j∣xi;θ))](5)
感謝 CSDN 博主[2]提供了另外一種求解方法,具體如下
∂θj∂L(θ)=−m1⎣⎡i=1∑m∂θj∂⎝⎛1{yi=j}log∑l=1keθlTxieθjTxi+c=j∑k1{yi=c}log∑l=1keθlTxieθcTxi⎠⎞⎦⎤=−m1⎣⎡i=1∑m⎝⎛1{yi=j}(xi−∑l=1keθlTxieθjTxi⋅xi)+c=j∑k1{yi=c}(−∑l=1keθlTxieθjTxi⋅xi)⎠⎞⎦⎤=−m1⎣⎡i=1∑mxi⎝⎛1{yi=j}(1−∑l=1keθlTxieθjTxi)−c=j∑k1{yi=c}∑l=1keθlTxieθjTxi⎠⎞⎦⎤=−m1⎣⎡i=1∑mxi⎝⎛1{yi=j}−1{yi=j}p(yi=j∣xi;θ)−c=j∑k1{yi=c}p(yi=j∣xi;θ)⎠⎞⎦⎤=−m1[i=1∑mxi(1{yi=j}−j=1∑k1{yi=j}p(yi=j∣xi;θ))]=−m1[i=1∑mxi(1{yi=j}−p(yi=j∣xi;θ))](6)
2.2 模型參數特點
softmax 迴歸有一個不尋常的特點:它有一個“冗餘“的參數集。爲了便於闡述這一特點,假設我們從參數向量 θj 中減去向量 ψ ,那麼對於概率函數,我們有
(yi=j∣xi;θ)=∑l=1ke(θl−ψ)Txie(θj−ψ)Txi=∑l=1keθlTxie−ψTxieθjTxie−ψTxi=∑l=1keθlTxieθjTxi(7)
換句話說,從參數向量中的每個元素 θj 中減去 ψ 一點也不會影響到假設的類別預測!這表明了 softmax 迴歸的參數中是有多餘的。正式地說, softmax 模型是過參數化的( overparameterized 或參數冗餘的),這意味着對任何一個擬合數據的假設而言,多種參數取值有可能得到同樣的假設 hθ,即從輸入 x 經過不同的模型參數的假設計算從而得到同樣的分類預測結果。
進一步說,若代價函數 L(θ) 被某組模型參數 (θ1,θ2,…,θk) 最小化,那麼對任意的 ψ ,代價函數也可以被 (θ1−ψ,θ2−ψ,…,θk−ψ) 最小化。因此, L(θ) 的最小值時的參數並不唯一。(有趣的是, L(θ) 仍是凸的,並且在梯度下降中不會遇到局部最優的問題,但是 Hessian 矩陣是奇異或不可逆的,這將會導致在牛頓法的直接實現上遇到數值問題。)
注意到,通過設定 ψ=θk ,總是可以用 θk−ψ=0代替 θk ,而不會對假設函數有任何影響。因此,可以去掉參數向量 θ 中的最後一個(或該向量中任意其它任意一個)元素 θk ,而不影響假設函數的表達能力。實際上,因參數冗餘的特性,與其優化全部的 k⋅n 個參數 (θ1,θ2,…,θk) (其中 θk∈ℜn),也可令 θk=0 ,只優化剩餘的 (k−1)⋅n 個參數,算法依然能夠正常工作。
2.3 正則化
當訓練數據不夠多的時候,容易出現過擬合現象,擬合係數往往非常大👉[過擬合原因],爲此在損失函數後面加上一個正則項,即
L(θ)=−m1[i=1∑mj=1∑k1{yi=j}log∑l=1keθlTxieθjTxi]+λi=1∑kj=1∑nθij2(8)
那麼新的損失函數的梯度爲
∂θj∂L(θ)=−m1[i=1∑mxi(1{yi=j}−p(yi=j∣xi;θ))]+λθj(9)
⚠️注意:上式中的 θj 中的 θj0 不應該被懲罰,因爲他是一個常數項,所以在實際使用的時候僅僅需要對 θj1,θj2,…,θjn 進行懲罰即可,這個會在後面的 python 代碼中提到😃。
2.4 softmax 與 logistic 迴歸的關係
文章開頭說過,softmax 迴歸是 logistic 迴歸的一般形式,logistic 迴歸是 softmax 迴歸在 k=2 時的特殊形式,下面通過公式推導來看下當 k=2 時 softmax 迴歸是如何退化成 logistic 迴歸。
當 k=2 時,softmax 迴歸的假設函數爲
hθ(xi)=[p(yi=1∣xi;θ)p(yi=2∣xi;θ)]=eθ1Txi+eθ2Txi1[eθ1Txieθ2Txi](10)
前面說過 softmax 迴歸的參數具有冗餘性,從參數向量 θ1,θ2 中減去向量 θ1完全不影響結果。現在我們令 θ′=θ2−θ1,並且兩個參數向量都減去 θ1,則有
hθ(xi)=e0Txi+e(θ2−θ1)Txi1[e0Txie(θ2−θ1)Txi]=[1+e(θ2−θ1)Txi11+e(θ2−θ1)Txie(θ2−θ1)Txi]=[1+e(θ2−θ1)Txi11−1+e(θ2−θ1)Txi1]=[1+e(θ′)Txi11−1+e(θ′)Txi1](11)
這樣就化成了 logistic 迴歸。
三、實現
3.1 python 手動實現
這裏的數據使用的是 sklearn 的算法包生成的隨機數據,其中,訓練數據爲 3750×2 的數據,測試數據爲 1250×2 的數據,生成代碼如下
def gen_dataset():
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.datasets import make_blobs
import matplotlib.pyplot as plt
np.random.seed(13)
X, y = make_blobs(centers=4, n_samples = 5000)
plt.figure(figsize=(6,4))
plt.scatter(X[:,0], X[:,1],c=y)
plt.title("Dataset")
plt.xlabel("First feature")
plt.ylabel("Second feature")
plt.show()
y = y.reshape((-1,1))
X_train, X_test, y_train, y_test = train_test_split(X, y)
train_dataset = np.append(X_train,y_train, axis = 1)
test_dataset = np.append(X_test,y_test, axis = 1)
np.savetxt("train_dataset.txt", train_dataset, fmt="%.4f %.4f %d")
np.savetxt("test_dataset.txt", test_dataset, fmt="%.4f %.4f %d")
數據分佈情況如下圖所示
softmax 算法的核心部分就是求解梯度矩陣,我們設輸入數據爲 X={x1,x2,…,xm},這是一個 m×n 的矩陣,輸出類別爲 y={y1,y2,…,ym},其中 yi 是一個 1×k 的one-hot 矩陣,k 表示類別個數,那麼 y 其實是一個 m×k 的矩陣,輸入數據對應的概率爲 P={p1,p2,…,pm}, 同樣的這也是一個 m×k 的矩陣。那麼根據公式(9),可以知道 θj 的梯度爲
∂θj∂L(θ)=−m1(yi−Pi)TX+λθj(12)
由此可以推導出 θ 的參數矩陣爲
∂θ∂L(θ)=−m1(y−P)TX+λθ(13)
注意到這裏也考慮了 θj 的第 0 項 ,所以在寫代碼的時候需要把 θ 的第 0 列的懲罰項減去。
softmax 迴歸的代碼如下
def load_dataset(file_path):
dataMat = []
labelMat = []
fr = open(file_path)
for line in fr.readlines():
lineArr = line.strip().split()
dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
labelMat.append(int(lineArr[2]))
return dataMat, labelMat
def train(data_arr, label_arr, n_class, iters = 1000, alpha = 0.1, lam = 0.01):
'''
@description: softmax 訓練函數
@param {type}
@return: theta 參數
'''
n_samples, n_features = data_arr.shape
n_classes = n_class
weights = np.random.rand(n_class, n_features)
all_loss = list()
y_one_hot = one_hot(label_arr, n_samples, n_classes)
for i in range(iters):
scores = np.dot(data_arr, weights.T)
probs = softmax(scores)
loss = - (1.0 / n_samples) * np.sum(y_one_hot * np.log(probs))
all_loss.append(loss)
dw = -(1.0 / n_samples) * np.dot((y_one_hot - probs).T, data_arr) + lam * weights
dw[:,0] = dw[:,0] - lam * weights[:,0]
weights = weights - alpha * dw
return weights, all_loss
def softmax(scores):
sum_exp = np.sum(np.exp(scores), axis = 1,keepdims = True)
softmax = np.exp(scores) / sum_exp
return softmax
def one_hot(label_arr, n_samples, n_classes):
one_hot = np.zeros((n_samples, n_classes))
one_hot[np.arange(n_samples), label_arr.T] = 1
return one_hot
def predict(test_dataset, label_arr, weights):
scores = np.dot(test_dataset, weights.T)
probs = softmax(scores)
return np.argmax(probs, axis=1).reshape((-1,1))
if __name__ == "__main__":
data_arr, label_arr = load_dataset('train_dataset.txt')
data_arr = np.array(data_arr)
label_arr = np.array(label_arr).reshape((-1,1))
weights, all_loss = train(data_arr, label_arr, n_class = 4)
test_data_arr, test_label_arr = load_dataset('test_dataset.txt')
test_data_arr = np.array(test_data_arr)
test_label_arr = np.array(test_label_arr).reshape((-1,1))
n_test_samples = test_data_arr.shape[0]
y_predict = predict(test_data_arr, test_label_arr, weights)
accuray = np.sum(y_predict == test_label_arr) / n_test_samples
print(accuray)
fig = plt.figure(figsize=(8,5))
plt.plot(np.arange(1000), all_loss)
plt.title("Development of loss during training")
plt.xlabel("Number of iterations")
plt.ylabel("Loss")
plt.show()
函數輸出的測試數據準確率爲
0.9952
程序中記錄了每個循環的損失函數,其變化曲線如下圖所示。
3.2 sklearn 算法包實現
sklearn
的實現比較簡單,與 logistic 迴歸的代碼類似。
def softmax_lib():
data_arr, label_arr = load_dataset('train_dataset.txt')
from sklearn import linear_model
model_softmax_regression = linear_model.LogisticRegression(solver='lbfgs',multi_class="multinomial",max_iter=10)
model_softmax_regression.fit(data_arr, label_arr)
test_data_arr, test_label_arr = load_dataset('test_dataset.txt')
y_predict = model_softmax_regression.predict(test_data_arr)
accurcy = np.sum(y_predict == test_label_arr) / len(test_data_arr)
print(accurcy)
輸出結果爲
0.9848
本文的完整代碼和數據去👉[我的 github]查看
四、參考
[1] https://zhuanlan.zhihu.com/p/34520042
[2] https://blog.csdn.net/u012328159/article/details/72155874
[3] https://zhuanlan.zhihu.com/p/56139075
[4] https://zh.wikipedia.org/wiki/Softmax%E5%87%BD%E6%95%B0
[5] http://deeplearning.stanford.edu/tutorial/supervised/SoftmaxRegression/