機器學習-初級入門(分類算法-邏輯迴歸、softmax詳解)

一、邏輯迴歸實現二分類

  1. sigmod函數
    g(z) = 1 / (1 + e-z)

    在這裏插入圖片描述

  2. 替換推導

    令z = WTX則g(z) = 1 / (1 + e-)因爲g(z)函數的特性,它輸出的結果也不再是預測結果,而是一個值預測爲正例的概率,預測爲負例的概率就是1-g(z)。
    函數形式表達:

    P(y=0|w,x) = 1 – g(z)
    P(y=1|w,x) =  g(z)
    

    在這裏插入圖片描述
    sigmod函數預測結果爲一個0到1之間的小數,選定閾值的第一反應,大多都是選0.5,其實實際工作中並不一定是0.5,閾值的設定往往是根據實際情況來判斷的。本小節我們只舉例讓大家理解爲什麼不完全是0.5,並不會有一個萬能的答案,都是根據實際工作情況來定的。

  3. 最大似然估計求損失函數

    若想讓預測出的結果全部正確的概率最大,根據最大似然估計,就是所有樣本預測正確的概率相乘得到的P(總體正確)最大,此時我們讓在這裏插入圖片描述數學表達形式如下:
    在這裏插入圖片描述
    通過兩邊同時取log的形式讓其變成連加.
    在這裏插入圖片描述得到的這個函數越大,證明我們得到的W就越好.因爲在函數最優化的時候習慣讓一個函數越小越好,所以我們在前邊加一個負號.得到公式如下:
    在這裏插入圖片描述
    這個函數就是我們邏輯迴歸(logistics regression)的損失函數,我們叫它交叉熵損失函數.

  4. 求解交叉熵損失函數
    求解步驟如下:

    1. 隨機一組W.
    2. 將W帶入交叉熵損失函數,讓得到的點沿着負梯度的方向移動.
    3. 循環第二步.
    

    求解梯度部分同樣是對損失函數求偏導,過程如下:
    在這裏插入圖片描述交叉熵損失函數的梯度和最小二乘的梯度形式上完全相同,區別在於,此時的
    在這裏插入圖片描述,而最小二乘的在這裏插入圖片描述

  5. 代碼實現:

    from sklearn.preprocessing import LabelEncoder, OneHotEncoder
    from sklearn.metrics import confusion_matrix, accuracy_score
    from matplotlib.colors import ListedColormap
    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    import random
    
    
    def dataset_format(filename):
        """
        劃分訓練測試集
        :param filename:文件路徑
        :return:
        """
        dataset = pd.read_csv(filename)
        # M = dataset["Gender"] == "Male"
        # dataset["Gender"] = M
        train_data = dataset.sample(frac=0.75, random_state=100, axis=0)
        test_data = dataset[~dataset.index.isin(train_data.index)]
        X_train = train_data.iloc[:, 2:-1].values
        y_train = train_data.iloc[:, -1].values
        X_test = test_data.iloc[:, 2:-1].values
        y_test = test_data.iloc[:, -1].values
        return X_train, y_train, X_test, y_test
    
    
    def initialize_weights(X):
        """
        初始化權重
        :param X:
        :return:
        """
        # 初始化參數
        # 參數範圍[-1/sqrt(N), 1/sqrt(N)]
        n_features = X.shape[1]
        limit = np.sqrt(1 / n_features)
        w = np.random.uniform(-limit, limit, (n_features, 1))
        w = np.insert(w, 0, 0, axis=0)  # 加入偏值
        return w
    
    
    def sigmoid(x):
        """
        sigmoid函數
        :param x:
        :return:
        """
        return 1 / (1 + np.exp(-x))
    
    
    def stander(X):
        """
        標準化數據,特徵縮放
        :param X:
        :return:
        """
        X = np.mat(X, dtype=float)
        # regularize
        x_mean = np.mean(X, 0)
        x_std = np.std(X, 0)
        np.seterr(divide="ignore", invalid='ignore')  # xVar中存在0元素
        # 特徵標準化: (特徵-均值)/方差
        X = (X - x_mean) / x_std
        return X
    
    
    def fit(X, y, learning_rate=0.001, n_iterations=500):
        """
        梯度下降訓練數據
        :param X: 訓練集自變量
        :param y: 訓練集因變量
        :param learning_rate: 學習率
        :param n_iterations: 迭代次數
        :return: 最優權重
        """
        W = initialize_weights(X)
        # 爲X增加一列特徵x1,x1 = 0
        X = np.insert(X, 0, 1, axis=1)
        y = np.reshape(y, (len(X), 1))
        # 梯度訓練n_iterations輪
        for i in range(n_iterations):
            h_x = X.dot(W)
            h_x = np.mat(h_x, dtype=float)
            y_pred = sigmoid(h_x)
            w_grad = X.T.dot(y_pred - y)
            W = W - learning_rate * w_grad
        return W
    
    
    def predict(X, w):
        """
        預測測試數據
        :param X: 測試集自變量
        :param w: 最優權重
        :return:
        """
        X = np.insert(X, 0, 1, axis=1)
        h_x = X.dot(w)
        h_x = np.mat(h_x, dtype=float)
        y_pred = np.round(sigmoid(h_x))
        return y_pred.astype(int)
    
    
    X_train, y_train, X_test, y_test = dataset_format("Social_Network_Ads.csv")
    X_train = stander(X_train)
    X_test = stander(X_test)
    W = fit(X_train, y_train)
    y_test_hat = predict(X_test, W)
    y_train_hat = predict(X_train, W)
    
    cm = confusion_matrix(y_test, y_test_hat)
    accuracy = accuracy_score(y_test, y_test_hat)
    X_set, y_set = X_test, y_test
    X1, X2 = np.meshgrid(np.arange(start=X_set[:, 0].min() - 1, stop=X_set[:, 0].max() + 1, step=0.01),
                         np.arange(start=X_set[:, 1].min() - 1, stop=X_set[:, 1].max() + 1, step=0.01))
    plt.contourf(X1, X2, predict(np.array([X1.ravel(), X2.ravel()]).T, W).reshape(X1.shape),
                 alpha=0.75, cmap=ListedColormap(('red', 'green')))
    plt.xlim(X1.min(), X1.max())
    plt.ylim(X2.min(), X2.max())
    for i, j in enumerate(np.unique(y_set)):
        plt.scatter(X_set[y_set == j, 0].tolist(), X_set[y_set == j, 1].tolist(),
                    c=ListedColormap(('orange', 'blue'))(i), label=j)
    plt.title('Logistic Regression (Test set)')
    plt.xlabel('Age')
    plt.ylabel('Estimated Salary')
    plt.legend()
    plt.show()
    

    分類結果:
    在這裏插入圖片描述

二、多分類實現(softmax)

  1. softmax的定義

    在這裏插入圖片描述
    假設有一個數組V,Vi表示V中的第i個元素,那麼這個元素的softmax值爲:
    在這裏插入圖片描述

  2. 交叉熵(softmax的損失函數):
    交叉熵,其用來衡量在給定的真實分佈下,使用非真實分佈所指定的策略消除系統的不確定性所需要付出的努力的大小。使用交叉熵做分類問題中的損失函數,可以在一定程度上減少梯度消散。
    softmax中使用的交叉熵公式如下:
    在這裏插入圖片描述
    在softmax中,ti表示真實值,yi表示求出的softmax值,輸入一個樣本,那麼只有一個神經元對應了該樣本的正確類別;若這個神經元輸出的概率值越高,則按照以上的函數公式,其產生的損失就越小;反之,則產生的損失就越高。
    舉個簡單的計算例子:
    在這裏插入圖片描述

  3. 對損失函數進行求導

    當預測第i個時,可以認爲ti=1。此時損失函數變成了
    在這裏插入圖片描述
    對Loss求導。根據定義
    在這裏插入圖片描述
    將數值映射到了0-1之間,並且和爲1,則有
    在這裏插入圖片描述
    具體求導過程
    在這裏插入圖片描述
    通過上面的求導結果,可以發現結果恰好爲通過softmax函數求出了概率減1,那麼我們就可以得到反向更新的梯度了。
    舉個計算的例子:

    通過若干層的計算,最後得到的某個訓練樣本的向量的分數是[ 2, 3, 4 ],
    那麼經過softmax函數作用後概率分別就是 [0.0903, 0.2447, 0.665],
    如果這個樣本正確的分類是第二個的話,
    那麼計算出來的偏導就是[0.0903, 0.2447-1, 0.665]=[0.0903, -0.7553, 0.665](劃重點),
    然後再根據這個進行back propagation就可以了。
    
  4. 代碼實現
    用到的公式:
    在這裏插入圖片描述

    def dataset_split(filename):
        """
        劃分數據集
        :param filename:
        :return:
        """
        dataset = pd.read_csv(filename)
        # M = dataset["Gender"] == "Male"
        # dataset["Gender"] = M
        train_data = dataset.sample(frac=0.75, random_state=100, axis=0)
        test_data = dataset[~dataset.index.isin(train_data.index)]
        X_train = train_data.iloc[:, :-1].values
        y_train = train_data.iloc[:, -1].values
        X_test = test_data.iloc[:, :-1].values
        y_test = test_data.iloc[:, -1].values
        return X_train, y_train, X_test, y_test
    
    
    def label_encoder(y):
        """
        對標籤進行編碼
        :param y:
        :return:
        """
        label_encoder_y = LabelEncoder()
        y = label_encoder_y.fit_transform(y)
        return np.mat(y).T
    
    
    def cal_e(x, w, i):
        """
        公式三: e^(θi.xi)
        :param x: 隨機的某行樣本特徵, 需轉化成 特徵個數行 x 1列
        :param w: 分類個數行k x 特徵個數列
        :param i: 第i個類別
        :return: 生成 1 x 1 矩陣
        """
        theata_l = w[i]  # 1行 x 特徵個數列
        return np.exp(theata_l.dot(x.T))
    
    
    def cal_probability(x, w, k, i):
        """
        公式二: P(yi = j|xi;θ) = e^(θi.xi) / ∑(j=1~k) e^(θj.xi)
        :param x: 隨機的某行樣本特徵
        :param w: 權值向量
        :param k: k個分類
        :param i: 第i個分類
        :return: 某個特徵的比率
        """
        molecule = cal_e(x, w, i)  # 1行 x 1列
        denominator = sum([cal_e(x, w, j) for j in range(k)])
        return molecule / denominator
    
    
    def cal_partial_derivative(x, y, w, k, i, weight_lambda):
        """
        公式一: △θjJ(θ) = -xi * (1{yi = j} - P(yi = j|xi;θ)) + λθ
        :param x: 隨機的某行樣本特徵
        :param y: 隨機的某行樣本標籤
        :param w: 分類個數行k x 特徵個數列
        :param k: k個分類
        :param i: 第i個分類
        :param weight_lambda: 衰退權重
        :return: △θjJ(θ)
        """
        first = (y[0, 0] == i)  # 計算示性函數
        second = cal_probability(x, w, k, i)[0, 0]
        return -x * (first - second) + weight_lambda * w[i]
    
    
    def softmax_train(X, y, max_iteration=5000, weight_lambda=0.01, learning_step = 0.1):
        """
        :param X: 特徵
        :param y: 標籤
        :param k: 幾種類型標籤
        :return:
        """
        X = np.insert(X, 0, 1, axis=1)
        k = y.max() + 1
        w = np.zeros((k, X.shape[1]))
        random_max = y.shape[0] - 1
        w = np.mat(w)
        for time in range(max_iteration):
            index = random.randint(0, random_max)
            l_x = X[index]
            l_y = y[index]
            derivatives = [cal_partial_derivative(l_x, l_y, w, k, j, weight_lambda) for j in range(k)]
            for c in range(k):
                w[c] -= learning_step * derivatives[c]
        return w
    
    
    def softmax_predict(X, w):
        X = np.insert(X, 0, 1, axis=1)
        r, c = X.shape
        y_t = np.zeros((r, 1))
        for i in range(r):
            x_T = X[i]
            result = w.dot(x_T.T)
            positon = result.argmax()
            y_t[i] = positon
        return y_t
    
    
    X_train, y_train, X_test, y_test = dataset_split("iris.csv")
    X_train = stander(X_train)
    X_test = stander(X_test)
    y_train = label_encoder(y_train)
    y_test = label_encoder(y_test)
    
    w = softmax_train(X_train, y_train)
    y_test_hat = softmax_predict(X_test, w)
    
    cm = confusion_matrix(y_test, y_test_hat)
    accuracy = accuracy_score(y_test, y_test_hat)
    
    print("混淆矩陣\n", cm)
    print("準確度: ", accuracy)
    

    輸出結果:

    混淆矩陣
     [[ 8  0  0]
     [ 0 19  0]
     [ 0  1  9]]
    準確度:  0.972972972972973
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章