機器學習-softmax 迴歸原理與實現

一、什麼是 softmax 迴歸?

softmax 迴歸(softmax regression)其實是 logistic 迴歸的一般形式,logistic 迴歸用於二分類,而 softmax 迴歸用於多分類,關於 logistic 迴歸可以看我的這篇博客👉[機器學習-logistic迴歸原理與實現]

對於輸入數據{(x1,y1),(x2,y2),,(xm,ym)}\{(x_1,y_1),(x_2,y_2),\ldots,(x_m,y_m)\}kk 個類別,即yi{1,2,,k}y_i \in \{1,2,\ldots,k\},那麼 softmax 迴歸主要估算輸入數據 xix_i 歸屬於每一類的概率,即
hθ(xi)=[p(yi=1xi;θ)p(yi=2xi;θ)p(yi=kxi;θ)]=1j=1keθjTxi[eθ1Txieθ2TxieθkTxi](1) h_{\theta}\left(x_i\right)=\left[\begin{array}{c}{p\left(y_i=1 | x_i ; \theta\right)} \\ {p\left(y_i=2 | x_i ; \theta\right)} \\ {\vdots} \\ {p\left(y_i=k | x_i ; \theta\right)}\end{array}\right]=\frac{1}{\sum_{j=1}^{k} e^{\theta_{j}^{T} x_i}}\left[\begin{array}{c}{e^{\theta_{1}^{T} x_i}} \\ {e^{\theta_{2}^{T} x_i}} \\ {\vdots} \\ {e^{\theta_{k}^{T} x_i}}\end{array}\right]\tag{1}
其中,θ1,θ2,,θkθ\theta_1,\theta_2,\ldots,\theta_k \in \theta是模型的參數,乘以1j=1keθjTxi\frac{1}{\sum_{j=1}^{k} e^{\theta_{j}^{T} x_i}}是爲了讓概率位於[0,1]並且概率之和爲 1,softmax 迴歸將輸入數據 xix_i 歸屬於類別 jj 的概率爲
p(yi=jxi;θ)=eθjTxil=1keθlTxi(2) p\left(y_i=j | x_i ; \theta\right)=\frac{e^{\theta_{j}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\tag{2}
上面的式子可以用下圖形象化的解析(來自臺大李宏毅《一天搞懂深度學習》)。

img

二、原理

2.1 梯度下降法參數求解

softmax 迴歸的參數矩陣 θ\theta 可以記爲
θ=[θ1Tθ2TθkT](3) \theta=\left[\begin{array}{c}{\theta_{1}^{T}} \\ {\theta_{2}^{T}} \\ {\vdots} \\ {\theta_{k}^{T}}\end{array}\right]\tag{3}
定義 softmax 迴歸的代價函數
L(θ)=1m[i=1mj=1k1{yi=j}logeθjTxil=1keθlTxi](4) L(\theta)=-\frac{1}{m}\left[\sum_{i=1}^{m} \sum_{j=1}^{k} 1\left\{y_i=j\right\} \log \frac{e^{\theta_{j}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right]\tag{4}
其中,1{·}是示性函數,即1{值爲真的表達式}=1,1{值爲假的表達式}=0。跟 logistic 函數一樣,利用梯度下降法最小化代價函數,下面求解 θ\theta 的梯度。L(θ)L(\theta)關於 θj\theta_{j} 的梯度求解爲
L(θ)θj=1mθj[i=1mj=1k1{yi=j}logeθjTxil=1keθlTxi]=1mθj[i=1mj=1k1{yi=j}(θjTxilogl=1keθlTxi)]=1m[i=1m1{yi=j}(xij=1keθjTxixil=1keθlTxi)]=1m[i=1mxi1{yi=j}(1j=1keθjTxil=1keθlTxi)]=1m[i=1mxi(1{yi=j}j=1k1{yi=j}eθjTxil=1keθlTxi)]=1m[i=1mxi(1{yi=j}eθjTxil=1keθlTxi)]=1m[i=1mxi(1{yi=j}p(yi=jxi;θ))](5) \begin{aligned} \frac{\partial L(\theta)}{\partial \theta_{j}} &=-\frac{1}{m} \frac{\partial}{\partial \theta_{j}}\left[\sum_{i=1}^{m} \sum_{j=1}^{k} 1\left\{y_i=j\right\} \log \frac{e^{\theta_{j}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right] \\ &=-\frac{1}{m} \frac{\partial}{\partial \theta_{j}}\left[\sum_{i=1}^{m} \sum_{j=1}^{k} 1\left\{y_i=j\right\}\left(\theta_{j}^{T} x_i-\log \sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}\right)\right] \\ &=-\frac{1}{m}\left[\sum_{i=1}^{m} 1\left\{y_i=j\right\}\left(x_i-\sum_{j=1}^{k} \frac{e^{\theta_{j}^{T} x_i} \cdot x_i}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right)\right] \\ &=-\frac{1}{m}\left[\sum_{i=1}^{m} x_i1\left\{y_i=j\right\}\left(1-\sum_{j=1}^{k} \frac{e^{\theta_{j}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right)\right] \\ &=-\frac{1}{m}\left[\sum_{i=1}^{m} x_i\left(1\left\{y_i=j\right\}-\sum_{j=1}^{k} 1\left\{y_i=j\right\} \frac{e^{\theta_{j}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right)\right] \\ &=-\frac{1}{m}\left[\sum_{i=1}^{m} x_i\left(1\left\{y_i=j\right\}- \frac{e^{\theta_{j}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right)\right] \\ &=-\frac{1}{m}\left[\sum_{i=1}^{m} x_i\left(1\left\{y_i=j\right\}-p\left(y_i=j | x_i ; \theta\right)\right)\right] \end{aligned}\tag{5}
感謝 CSDN 博主[2]提供了另外一種求解方法,具體如下
L(θ)θj=1m[i=1mθj(1{yi=j}logeθjTxil=1keθlTxi+cjk1{yi=c}logeθcTxil=1keθlTxi)]=1m[i=1m(1{yi=j}(xieθjTxixil=1keθlTxi)+cjk1{yi=c}(eθjTxixil=1keθlTxi))]=1m[i=1mxi(1{yi=j}(1eθjTxil=1keθlTxi)cjk1{yi=c}eθjTxil=1keθlTxi)]=1m[i=1mxi(1{yi=j}1{yi=j}p(yi=jxi;θ)cjk1{yi=c}p(yi=jxi;θ))]=1m[i=1mxi(1{yi=j}j=1k1{yi=j}p(yi=jxi;θ))]=1m[i=1mxi(1{yi=j}p(yi=jxi;θ))](6) \begin{aligned} \frac{\partial L(\theta)}{\partial \theta_{j}} &=-\frac{1}{m}\left[\sum_{i=1}^{m} \frac{\partial}{\partial \theta_{j}}\left(1\left\{y_i=j\right\} \log \frac{e^{\theta_{j}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}+\sum_{c \neq j}^{k} 1\left\{y_i=c\right\} \log \frac{e^{\theta_{c}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right)\right]\\ &=-\frac{1}{m}\left[\sum_{i=1}^{m}\left(1\left\{y_i=j\right\}\left(x_i-\frac{e^{\theta_{j}^{T} x_i} \cdot x_i}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right)+\sum_{c \neq j}^{k} 1\left\{y_i=c\right\}\left(-\frac{e^{\theta_{j}^{T} x_i} \cdot x_i}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right)\right)\right]\\ &=-\frac{1}{m}\left[\sum_{i=1}^{m} x_i\left(1\left\{y_i=j\right\}\left(1-\frac{e^{\theta_{j}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right)-\sum_{c \neq j}^{k} 1\left\{y_i=c\right\}\frac{e^{\theta_{j}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right)\right] \\ &=-\frac{1}{m}\left[\sum_{i=1}^{m} x_i\left(1\left\{y_i=j\right\}-1\left\{y_i=j\right\} p\left(y_i=j | x_i ; \theta\right)-\sum_{c \neq j}^{k} 1\left\{y_i=c\right\}p\left(y_i=j | x_i ; \theta\right)\right)\right] \\ &=-\frac{1}{m}\left[\sum_{i=1}^{m} x_i\left(1\left\{y_i=j\right\}-\sum_{j=1}^{k} 1\left\{y_i=j\right\} p\left(y_i=j | x_i ; \theta\right)\right)\right] \\ &=-\frac{1}{m}\left[\sum_{i=1}^{m} x_i\left(1\left\{y_i=j\right\}-p\left(y_i=j | x_i ; \theta\right)\right)\right] \end{aligned}\tag{6}

2.2 模型參數特點

softmax 迴歸有一個不尋常的特點:它有一個“冗餘“的參數集。爲了便於闡述這一特點,假設我們從參數向量 θj\theta_{j} 中減去向量 ψ\psi ,那麼對於概率函數,我們有
(yi=jxi;θ)=e(θjψ)Txil=1ke(θlψ)Txi=eθjTxieψTxil=1keθlTxieψTxi=eθjTxil=1keθlTxi(7) \begin{aligned} \left(y_i=j | x_i ; \theta\right) &=\frac{e^{\left(\theta_{j}- \psi\right)^{T} x_i}}{\sum_{l=1}^{k} e^{\left(\theta_{l}- \psi\right)^{T} x_i}}\\ &=\frac{e^{\theta_{j}^{T} x_i }e^{-\psi^{T} x_i }}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}e^{-\psi^{T} x_i}}\\ &=\frac{e^{\theta_{j}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}} \end{aligned} \tag{7}
換句話說,從參數向量中的每個元素 θj\theta_j 中減去 ψ\psi 一點也不會影響到假設的類別預測!這表明了 softmax 迴歸的參數中是有多餘的。正式地說, softmax 模型是過參數化的( overparameterized​ 或參數冗餘的),這意味着對任何一個擬合數據的假設而言,多種參數取值有可能得到同樣的假設 hθh_\theta,即從輸入 xx 經過不同的模型參數的假設計算從而得到同樣的分類預測結果。

進一步說,若代價函數 L(θ)L(\theta) 被某組模型參數 (θ1,θ2,,θk)(\theta_1, \theta_2,\ldots, \theta_k) 最小化,那麼對任意的 ψ\psi ,代價函數也可以被 (θ1ψ,θ2ψ,,θkψ)(\theta_1 - \psi, \theta_2 - \psi,\ldots, \theta_k - \psi) 最小化。因此, L(θ)L(\theta) 的最小值時的參數並不唯一。(有趣的是, L(θ)L(\theta) 仍是凸的,並且在梯度下降中不會遇到局部最優的問題,但是 Hessian​ 矩陣是奇異或不可逆的,這將會導致在牛頓法的直接實現上遇到數值問題。)

注意到,通過設定 ψ=θk\psi = \theta_k ,總是可以用 θkψ=0\theta_k - \psi = \vec{0}代替 θk\theta_k ,而不會對假設函數有任何影響。因此,可以去掉參數向量 θ\theta 中的最後一個(或該向量中任意其它任意一個)元素 θk\theta_{k} ,而不影響假設函數的表達能力。實際上,因參數冗餘的特性,與其優化全部的 knk\cdot n 個參數 (θ1,θ2,,θk)(\theta_1,\theta_2,\ldots,\theta_k) (其中 θkn\theta_k \in \Re^{n}),也可令 θk=0\theta_k = \vec{0} ,只優化剩餘的 (k1)n(k-1) \cdot n 個參數,算法依然能夠正常工作。

2.3 正則化

當訓練數據不夠多的時候,容易出現過擬合現象,擬合係數往往非常大👉[過擬合原因],爲此在損失函數後面加上一個正則項,即
L(θ)=1m[i=1mj=1k1{yi=j}logeθjTxil=1keθlTxi]+λi=1kj=1nθij2(8) L(\theta)=-\frac{1}{m}\left[\sum_{i=1}^{m} \sum_{j=1}^{k} 1\left\{y_i=j\right\} \log \frac{e^{\theta_{j}^{T} x_i}}{\sum_{l=1}^{k} e^{\theta_{l}^{T} x_i}}\right]+\lambda\sum_{i=1}^{k} \sum_{j=1}^{n}\theta_{ij}^{2} \tag{8}
那麼新的損失函數的梯度爲
L(θ)θj=1m[i=1mxi(1{yi=j}p(yi=jxi;θ))]+λθj(9) \frac{\partial L(\theta)}{\partial \theta_{j}} =-\frac{1}{m}\left[\sum_{i=1}^{m} x_i\left(1\left\{y_i=j\right\}-p\left(y_i=j | x_i ; \theta\right)\right)\right]+\lambda\theta_j \tag{9}
⚠️注意:上式中的 θj\theta_j 中的 θj0\theta_{j0} 不應該被懲罰,因爲他是一個常數項,所以在實際使用的時候僅僅需要對 θj1,θj2,,θjn\theta_{j1},\theta_{j2},\dots,\theta_{jn} 進行懲罰即可,這個會在後面的 python 代碼中提到😃。

2.4 softmax 與 logistic 迴歸的關係

文章開頭說過,softmax 迴歸是 logistic 迴歸的一般形式,logistic 迴歸是 softmax 迴歸在 k=2k=2 時的特殊形式,下面通過公式推導來看下當 k=2k=2 時 softmax 迴歸是如何退化成 logistic 迴歸。

k=2k=2 時,softmax 迴歸的假設函數爲
hθ(xi)=[p(yi=1xi;θ)p(yi=2xi;θ)]=1eθ1Txi+eθ2Txi[eθ1Txieθ2Txi](10) h_{\theta}\left(x_i\right)=\left[\begin{array}{c}{p\left(y_i=1 | x_i ; \theta\right)} \\ {p\left(y_i=2 | x_i ; \theta\right)} \end{array}\right]=\frac{1}{e^{\theta_{1}^{T} x_i}+e^{\theta_{2}^{T} x_i}}\left[\begin{array}{c}{e^{\theta_{1}^{T} x_i}} \\ {e^{\theta_{2}^{T} x_i}} \end{array}\right]\tag{10}
前面說過 softmax 迴歸的參數具有冗餘性,從參數向量 θ1,θ2\theta_1,\theta_2 中減去向量 θ1\theta_1完全不影響結果。現在我們令 θ=θ2θ1\theta'=\theta_2-\theta_1,並且兩個參數向量都減去 θ1\theta_1,則有
hθ(xi)=1e0Txi+e(θ2θ1)Txi[e0Txie(θ2θ1)Txi]=[11+e(θ2θ1)Txie(θ2θ1)Txi1+e(θ2θ1)Txi]=[11+e(θ2θ1)Txi111+e(θ2θ1)Txi]=[11+e(θ)Txi111+e(θ)Txi](11) \begin{aligned} h_{\theta}(x_i) &=\frac{1}{e^{\vec{0}^{T} x_i}+e^{\left(\theta_{2}-\theta_{1}\right)^{T} x_i}}\left[\begin{array}{c}{e^{\vec{0}^{T} x_i}} \\ {e^{\left(\theta_{2}-\theta_{1}\right)^{T} x_i}}\end{array}\right] \\ &=\left[\begin{array}{c}{\frac{1}{1+e^{\left(\theta_{2}-\theta_{1}\right)^{T} x_i}}} \\ {\frac{e^{\left(\theta_{2}-\theta_{1}\right)^{T} x_i}}{1+e^{\left(\theta_{2}-\theta_{1}\right)^{T} x_i}}}\end{array}\right] \\ &=\left[\begin{array}{c}{\frac{1}{1+e^{\left(\theta_{2}-\theta_{1}\right)^{T} x_i}}} \\ 1-{\frac{1}{1+e^{\left(\theta_{2}-\theta_{1}\right)^{T} x_i}}}\end{array}\right] \\ &=\left[\begin{array}{c}{\frac{1}{1+e^{\left(\theta'\right)^{T} x_i}}} \\ 1-{\frac{1}{1+e^{\left(\theta'\right)^{T} x_i}}}\end{array}\right] \\ \end{aligned}\tag{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()

    # 重塑目標以獲得具有 (n_samples, 1)形狀的列向量
    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")

數據分佈情況如下圖所示

dataset

softmax 算法的核心部分就是求解梯度矩陣,我們設輸入數據爲 X={x1,x2,,xm}X=\{x_1,x_2,\ldots,x_m\},這是一個 m×nm×n 的矩陣,輸出類別爲 y={y1,y2,,ym}y=\{y_1,y_2,\ldots,y_m\},其中 yiy_i 是一個 1×k1×k 的one-hot 矩陣,kk 表示類別個數,那麼 yy 其實是一個 m×km×k 的矩陣,輸入數據對應的概率爲 P={p1,p2,,pm}P=\{p_1,p_2,\ldots,p_m\}, 同樣的這也是一個 m×km×k 的矩陣。那麼根據公式(9),可以知道 θj\theta_j 的梯度爲
L(θ)θj=1m(yiPi)TX+λθj(12) \frac{\partial L(\theta)}{\partial \theta_{j}} =-\frac{1}{m}\left(y_i-P_i\right)^TX+\lambda\theta_j \tag{12}
由此可以推導出 θ\theta 的參數矩陣爲
L(θ)θ=1m(yP)TX+λθ(13) \frac{\partial L(\theta)}{\partial \theta} =-\frac{1}{m}\left(y-P\right)^TX+\lambda\theta \tag{13}
注意到這裏也考慮了 θj\theta_j 的第 0 項 ,所以在寫代碼的時候需要把 θ\theta 的第 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()
    # 計算 one-hot 矩陣
    y_one_hot = one_hot(label_arr, n_samples, n_classes)
    for i in range(iters):
        # 計算 m * k 的分數矩陣
        scores = np.dot(data_arr, weights.T)
        # 計算 softmax 的值
        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__":
    #gen_dataset()
    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

程序中記錄了每個循環的損失函數,其變化曲線如下圖所示。

loss function

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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章