天天看點

【機器學習與算法】python手寫算法:softmax回歸

【機器學習與算法】python手寫算法:softmax回歸

        • 算法原理
        • python實作算法
        • 結果展示
        • sklearn實作softmax回歸

算法原理

softmax回歸用于解決多分類問題。它的基本思想是計算樣本屬于每一個類别的機率,屬于哪個類别的機率最大,則預測輸出為哪一類。softmax計算機率的方式為:

P ( y ( i ) = j ∣ x i ; θ ) = e θ j T ⋅ x ( i ) ∑ l = 1 K e θ l T ⋅ x ( i ) P(y^{(i)}=j|x^{i};\theta) = \frac{e^{\theta_j^T\cdot x^{(i)}}}{\sum_{l=1}^{K}e^{\theta_l^T\cdot x^{(i)}}} P(y(i)=j∣xi;θ)=∑l=1K​eθlT​⋅x(i)eθjT​⋅x(i)​

表示樣本 x ( i ) x^{(i)} x(i)屬于第j個類别的機率。

softmax回歸的損失函數如下:

J ( θ ) = − 1 m ∑ i = 1 m ∑ j = 1 K l ( y ( i ) = j ) ∗ l n e θ j T ⋅ x ( i ) ∑ l = 1 K e θ l T ⋅ x ( i ) J(\theta) = -\frac{1}{m}\sum_{i=1}^{m}\sum_{j=1}^{K}l(y^{(i)}=j)*ln\frac{e^{\theta_j^T\cdot x^{(i)}}}{\sum_{l=1}^{K}e^{\theta_l^T\cdot x^{(i)}}} J(θ)=−m1​i=1∑m​j=1∑K​l(y(i)=j)∗ln∑l=1K​eθlT​⋅x(i)eθjT​⋅x(i)​

其中 l ( ⋅ ) l(\cdot) l(⋅)是一個示性函數,括号裡面的邏輯判斷為真,則函數傳回1,否則傳回0。直覺上了解就是讓每個樣本預測正确類别的聯合機率最大化。這個損失函數叫交叉熵損失,為什麼呢,我們看下交叉熵的公式:

H ( p , q ) = − ∑ i = 1 n p ( x i ) ∗ l n ( q ( x i ) ) H(p,q) = -\sum_{i=1}^{n}p(x_i)*ln(q(x_i)) H(p,q)=−i=1∑n​p(xi​)∗ln(q(xi​))

其中p表示真實機率,q表示預測機率,和softmax損失函數是一緻的。

當然,邏輯回歸的損失函數 J ( θ ) = − ∑ i = i m ( y ∗ l n ( y ^ ) + ( 1 − y ) ∗ l n ( 1 − y ^ ) ) J(\theta) = -\sum_{i=i}^{m}(y*ln(\hat{y})+(1-y)*ln(1-\hat{y})) J(θ)=−∑i=im​(y∗ln(y^​)+(1−y)∗ln(1−y^​))也叫交叉熵損失,其實就是類别數K=2時候的一種交叉熵的特殊形式而已。

softmax損失函數的梯度如下:

∂ ∂ θ j J ( θ ) = − 1 m ∑ i = 1 m [ x ( i ) ( l ( y ( i ) = j ) − P ( y ( i ) = j ∣ x i ; θ ) ) ] + λ θ j \frac{\partial}{\partial \theta_j}J(\theta) = -\frac{1}{m}\sum_{i=1}^{m}[x^{(i)}(l(y^{(i)}=j)-P(y^{(i)}=j|x^{i};\theta))] + \lambda \theta_j ∂θj​∂​J(θ)=−m1​i=1∑m​[x(i)(l(y(i)=j)−P(y(i)=j∣xi;θ))]+λθj​

具體的推導不再贅述,如果對softmax原理的更詳細解釋和推導有興趣,包括softmax損失函數梯度的求導,參數備援的特性和softmax回歸如何在K=2時推演為邏輯回歸,請參閱這篇部落格:softmax回歸(Softmax Regression)

python實作算法

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas as pd
import matplotlib.patches as mpatches
from sklearn.preprocessing import PolynomialFeatures
from sklearn import datasets
import copy


class softmax:
    
    def __init__(self, K, alpha=0.0001, lamda=0.0001, tol = 1e-4, max_iter=1000):
        #類别數
        self.K = K
        #學習率
        self.alpha = alpha
        #正則項參數
        self.lamda = lamda
        #停止疊代的标準
        self.tol = tol
        #最大疊代輪數
        self.max_iter = max_iter

        
    def cal_p(self,W,X,k):
    	'''
    	計算所有樣本在每個類别上的機率
    	'''

    	#分子:樣本在該類别上線性方程的指數轉換
        numerator = np.exp(np.dot(W[k],X.T))
        #分母:樣本在所有類别上線性方程指數轉換的加和
        denominator = np.sum(np.exp(np.dot(W,X.T)),axis=0)
        #傳回機率
        return numerator/denominator
        
        
    def fit(self, X, Y):
    	'''
		訓練模型
		'''

        n = len(X[0])
        #初始化參數W,是一個類别數 X 特征數的矩陣
        W = np.zeros((self.K,n))
        #初始化一個w_com用于和W進行比較,在差異小于tol的時候停止疊代
        w_com = copy.deepcopy(W)

        for times in range(self.max_iter):
            for k in range(self.K):
            	#計算示性函數項
                l = [1 if item==k else 0 for item in Y]
                #計算所有樣本在該類别上的機率
                p = self.cal_p(W,X,k)
                #目标函數的梯度項
                g = np.sum((l-p)*X.T,axis=1)
                #沿着梯度進行下降
                W[k] = W[k] - (-self.alpha*g + self.lamda*W[k])
                
            #判斷是否滿足停止疊代的條件,滿足即跳出循環
            if abs(np.sum(w_com)-np.sum(W))<self.tol:
                break
            else:
                w_com = copy.deepcopy(W)
        
        #把拟合好的參數W存到softmax類的屬性中    
        self.__setattr__('W',W)
    
    
    def predict(self,X):
    	'''
    	預測
    	'''
        
        n = len(X)
        #計算樣本屬于每個類别k的機率
        prob = np.zeros((self.K, n))
        for k in range(self.K):
            prob[k] = self.cal_p(self.W, X, k)
        
        #傳回類别中最大機率的下标,作為預測輸出    
        res = np.argmax(prob,axis=0)
        
        return res
           

結果展示

為了友善用二維圖檔展示結果,隻取‘花瓣長度’和’花瓣寬度’兩個特征來訓練。但是隻選兩個特征并不代表我們隻有兩個特征可用,我們可以使用sklearn裡面的PolynomialFeatures來給這兩個特征做升維。2個特征x1,x2升維後變6個:1, x 1 x_1 x1​, x 2 x_2 x2​, x 1 2 x_1^2 x12​, x 2 2 x_2^2 x22​, x 1 ∗ x 2 x1*x2 x1∗x2。

if __name__ == '__main__':

	#用鸢尾花資料試驗
    iris = datasets.load_iris()
    #為了友善用二維圖檔展示結果,隻取‘花瓣長度'和'花瓣寬度'兩個特征來訓練
    X = iris.data[:,2:]
    Y = iris.target

    #用polynomial函數對特征進行二階升維,升維後2個特征變6個:x1,x2升維到1,x1,x2,x1**2,x2**2, x1*x2
    poly = PolynomialFeatures(2)
    x_p = poly.fit_transform(X)

    #初始化softmax類并訓練
    sf = softmax(K=3)
    sf.fit(x_p,Y)

    #這個是為了給最後的圖檔加個邊
    def extend(a, b):
        return 1.05*a-0.05*b, 1.05*b-0.05*a

    N, M = 200, 200     # 橫縱各采樣多少個值
    x1_min, x1_max = extend(X[:, 0].min(), X[:, 0].max())   # 第0列的範圍
    x2_min, x2_max = extend(X[:, 1].min(), X[:, 1].max())   # 第1列的範圍
    t1 = np.linspace(x1_min, x1_max, N)
    t2 = np.linspace(x2_min, x2_max, M)
    x1, x2 = np.meshgrid(t1, t2)

    x_show = np.stack((x1.flat, x2.flat), axis=1)   # 測試點
    x_show_p = poly.fit_transform(x_show)

    y_hat = sf.predict(x_show_p)
    y_hat = y_hat.reshape(x1.shape)  # 使之與輸入的形狀相同


    cm_light = mpl.colors.ListedColormap(['#77E0A0', '#FF8080', '#A0A0FF'])
    cm_dark = mpl.colors.ListedColormap(['g', 'r', 'b'])
    mpl.rcParams['font.sans-serif'] = u'SimHei'
    mpl.rcParams['axes.unicode_minus'] = False
    plt.figure(facecolor='w')
    plt.pcolormesh(x1, x2, y_hat, cmap=cm_light)  # 預測值的顯示
    plt.scatter(X[:, 0], X[:, 1], s=30, c=Y, edgecolors='k', cmap=cm_dark)  # 樣本的顯示
    x1_label, x2_label = '花瓣長度', '花瓣寬度'
    plt.xlabel(x1_label, fontsize=14)
    plt.ylabel(x2_label, fontsize=14)
    plt.xlim(x1_min, x1_max)
    plt.ylim(x2_min, x2_max)
    plt.grid(b=True, ls=':')

    patchs = [mpatches.Patch(color='#77E0A0', label='Iris-setosa'),
              mpatches.Patch(color='#FF8080', label='Iris-versicolor'),
              mpatches.Patch(color='#A0A0FF', label='Iris-virginica')]
    plt.legend(handles=patchs, fancybox=True, framealpha=0.8, loc='lower right')
    plt.title(u'鸢尾花softmax回歸分類', fontsize=17)
    plt.show()
           
【機器學習與算法】python手寫算法:softmax回歸

sklearn實作softmax回歸

sklearn上沒有softmax這樣一個接口,那怎麼使用softmax算法實作多分類呢?其實sklearn把softmax算法統一封裝到LogisticRegression這個接口裡了,我們隻需要調用這個接口,然後改下multi_class參數為‘multinomial’就可以了,記得solver參數不要選用‘liblinear’:

from sklearn.linear_model import LogisticRegression

sf_sklearn = LogisticRegression(multi_class='multinomial', solver='saga',max_iter=1000)
sf_sklearn.fit(x_p,Y)
sf_sklearn.predict(x_p)
           

我們會發現multi_class這個參數不光‘multinomial’一個選擇啊,還有一個‘ovr’,同樣可以解決多分類問題。ovr的意思就是one versus all,就是預測每一類機率的時候,把這一類樣本給個标簽1,剩下的類别樣本給個标簽0,然後用二分類去預測,最後同樣把機率值最高的類别作為輸出。那這兩種方法分别适用于什麼情況呢,經驗性來講,softmax适用于各個類别互相獨立的情況,而ovr适用于各個類别互有交集的情況。

最後,歡迎閱讀其它算法的python實作:

【機器學習與算法】python手寫算法:Cart樹

【機器學習與算法】python手寫算法:帶正則化的邏輯回歸

【機器學習與算法】python手寫算法:xgboost算法

【機器學習與算法】python手寫算法:Kmeans和Kmeans++算法