從 0 開始機器學習 - 邏輯迴歸識別手寫字符!

之前的邏輯迴歸文章:從 0 開始機器學習 - 邏輯迴歸原理與實戰!跟大家分享了邏輯迴歸的基礎知識和分類一個簡單數據集的方法。

今天登龍再跟大家分享下如何使用邏輯迴歸來分類手寫的 [0 - 9] 這 10 個字符,數據集如下:

下面我就帶着大家一步一步寫出關鍵代碼,完整的代碼在我的 Github 倉庫中:logistic_reg

一、加載手寫字符數據

1.1 讀取數據集

raw_X, raw_y = load_data('ex3data1.mat')

# 5000 x 400
print(raw_X.shape)

# 5000
print(raw_y.shape)

數據集有 5000 個樣本,每個樣本是一個 20 x 20 = 400 像素的手寫字符圖像:

這個識別手寫字符問題屬於有監督學習,所以我們有訓練集的真實標籤 y,維度是 5000,表示訓練集中 5000 個樣本的真實數字:

1.2 添加全 1 向量

老規矩,在訓練樣本的第一列前添加一列全 1 的向量(爲了與 θ0\theta_0 相乘進行向量化表示):

# 添加第一列全 1 向量
X = np.insert(raw_X, 0, values = np.ones(raw_X.shape[0]), axis = 1)
# 5000 行 401 列
X.shape

添加一列後,樣本變爲 5000 行 401 列

1.3 向量化標籤

把原標籤(5000 行 1 列)變爲(5000 行 10 列),相當於把每個真實標籤用 10 個位置的向量替換:

# 把原標籤中的每一類用一個行向量表示
y_matrix = []

# k = 1 ... 10
# 當 raw_y == k 時把對應位置的值設置爲 1,否則爲 0
for k in range(1, 11):
    y_matrix.append((raw_y == k).astype(int))

改變後向量標籤的每一行代表一個標籤,只不過用 10 個位置來表示,比如數字 1 對應第一個位置爲 1,數字 2 對應第二個位置爲 1 ,以此類推,不過注意數字 0 對應第 10 個位置爲 1

而每一列代表原始標籤值中所有相同的字符,比如第一列表示所有數字 1 的真實標籤值,第二列表示所有數字 2 的真實標籤值,以此類推,第 10 列表示數字 0 的真實值:

因爲我們加載的是 .mat 類型的 Matlab 數據文件,而 Matlab 中索引是從 1 開始的,因此原數據集中用第 10 列表示數字 0,但是爲了方便 Python 處理,我們這裏把第 10 列表示的數字 0 移動到第一列,使得列數按照數字順序 [0 - 9] 排列:

# 因爲 Matlab 下標從 1 開始,所以 raw_y 中用 10 表示標籤 0
# 這裏把標籤 0 的行向量移動到第一行
y_matrix = [y_matrix[-1]] + y_matrix[:-1]

這是原實驗配的圖片,原理是一樣的,可以對比理解下(這裏沒有移動第 10 列哦):

爲何要這樣做呢?主要是爲了完成後面一次預測多個數字的任務。

二、訓練模型

邏輯迴歸和正則化的原理之前都講過了,沒看過的同學可以複習下:

這裏我就直接放關鍵的函數,然後稍加解釋下。

2.1 邏輯迴歸假設函數

假設函數使用常用的 sigmoid 函數:

g(z)=11+ez g(z)=\frac{1}{1+{e^{-z}}}

def sigmoid(z):
    return 1 / (1 + np.exp(-z))

2.2 邏輯迴歸代價函數

J(θ)=1mi=1m[y(i)log(hθ(x(i)))+(1y(i))log(1hθ(x(i)))] J(\theta) = -\frac{1}{m}\sum\limits_{i = 1}^{m}{[{y^{(i)}}\log ({h_\theta}({x^{(i)}}))+( 1-{y^{(i)}})\log ( 1 - h_\theta({x^{(i)}}))]}

def cost(theta, X, y):
    return np.mean(-y * np.log(sigmoid(X @ theta)) - (1 - y) * np.log(1 - sigmoid(X @ theta)))

2.3 邏輯迴歸正則化代價函數

J(θ)=1mi=1m[y(i)log(hθ(x(i)))(1y(i))log(1hθ(x(i)))]+λ2mj=1nθj2 J(\theta) = \frac{1}{m}\sum\limits_{i = 1}^{m}{[-{y^{(i)}}\log ({h_\theta}({x^{(i)}}))-( 1-{y^{(i)}})\log ( 1 - h_\theta({x^{(i)}}))]} + \frac{\lambda}{2m} \sum\limits_{j=1}^{n}{\theta_j^2}

def regularized_cost(theta, X, y, l=1):
    theta_j1_to_n = theta[1:]
    
    # 正則化代價
    regularized_term = (l / (2 * len(X))) * np.power(theta_j1_to_n, 2).sum()

    return cost(theta, X, y) + regularized_term

2.4 梯度計算

J(θ0,θ1)=212mi=1m(hθ(xi)y(i))hθ(xi) J( \theta_0, \theta_1)' = 2 * \frac{1}{2m}\sum\limits_{i=1}^m \left( h_{\theta}(x_i)-y^{(i)} \right) * h_\theta(x_i)'

hθ(xi)=(θ0x0+θ1x1)=xi h_\theta(x_i)' = (\theta_0x_0 + \theta_1x_1)' = x_i

def gradient(theta, X, y):
    return (1 / len(X)) * X.T @ (sigmoid(X @ theta) - y)

2.5 正則化梯度

在原梯度後面加上正則化梯度即可:

λmj=1nθj \frac{\lambda}{m} \sum\limits_{j=1}^{n}{\theta_j}

def regularized_gradient(theta, X, y, l=1):
    theta_j1_to_n = theta[1:]
    
    # 正則化梯度
    regularized_theta = (l / len(X)) * theta_j1_to_n

    # 不對 theta_0 正則化
    regularized_term = np.concatenate([np.array([0]), regularized_theta])

    return gradient(theta, X, y) + regularized_term

2.6 邏輯迴歸訓練函數

使用 scipy.optimize 來優化:

"""邏輯迴歸函數
    args:
        X: 特徵矩陣, (m, n + 1),第一列爲全 1 向量
        y: 標籤矩陣, (m, )
        l: 正則化係數

    return: 訓練的參數向量
"""
def logistic_regression(X, y, l = 1):
    # 保存訓練的參數向量,維度爲特徵矩陣的列數,即特徵數 + 1
    theta = np.zeros(X.shape[1])

    # 使用正則化代價和梯度訓練
    res = opt.minimize(fun = regularized_cost,
                       x0 = theta,
                       args = (X, y, l),
                       method = 'TNC',
                       jac = regularized_gradient,
                       options = {'disp': True})
    
    # 得到最終訓練參數
    final_theta = res.x

    return final_theta

三、訓練模型

我們先來訓練模型,使得能識別單個數字 0,y[0] (5000 行 1 列)代表所有真實標籤值爲 0 的樣本,參考之前講的向量化標籤:

theta_0 = logistic_regression(X, y[0])

預測的結果 theta_0(401 行 1 列) 是手寫字符 0 對應的參數向量。

四、預測訓練集數字 0

我們用訓練的 theta_0 參數來預測下訓練集中所有的字符圖像爲 0 的準確度:

def predict(x, theta):
    prob = sigmoid(x @ theta)
    return (prob >= 0.5).astype(int)
# 字符 0 的預測值,也是 5000 行 1 列
y_pred = predict(X, theta_0)

y_pred 是 5000 行 1 列的向量,元素只有 0 和 1,1 表示樣本預測值爲數字 0,0 表示預測值不是數字 0。

我們再把預測值和真實值進行比較,計算下誤差的平均值作爲輸出精度:

# 打印預測數字 1 的精度
print('Accuracy = {}'.format(np.mean(y[0] == y_pred)))

Accuracy = 0.9974

顯示該模型識別訓練集中手寫數字 0 的圖像正確率約爲 99.74%!這只是分類一個數字,下面再來一次把 10 個數字都進行分類。

五、分類 10 個數字

上面只訓練並預測了一個字符 0,我們可以使用 for 循環來訓練全部的 10 個字符,每個字符的訓練方法都和上面單個數字相同:

# 訓練 0 - 9 這 10 個類別的 theta_[0 -> 9] 參數向量
theta_k = np.array([logistic_regression(X, y[k]) for k in range(10)])

theta_k 是 10 個數字對應的參數向量(每行代表一個參數向量):

# 10 行 401 列
print(theta_k.shape)

對特徵矩陣進行預測,注意這裏對 theta_k 進行了轉置,是爲了進行矩陣的乘法運算:

# X(5000, 401), theta_k.T(401, 10)
prob_matrix = sigmoid(X @ theta_k.T)

# prob_matrix(5000, 10)
prob_matrix

打印下預測的矩陣(5000 行 10 列):

將每行中的最大一列的索引放入 y_pred 中,用來表示預測的數字:

y_pred = np.argmax(prob_matrix, axis = 1)

# (5000, 1)
print(y_pred.shape)

y_pred

此時的 y_pred 變爲 5000 行 1 列,每一行就是模型預測的數字識別結果,再把真實標籤中的 10 替換爲 0:

# 用 0 代替 10
y_answer[y_answer == 10] = 0

打印出訓練集中每個手寫數字的預測精度:

print(classification_report(y_answer, y_pred))

可以看到每個字符在訓練集上的預測效果都能達到 90% 以上,說明這個模型在訓練集上的預測效果比較不錯。

OK,今天就分享這些,希望大家多多實踐!完整可運行代碼鏈接:logistic_reg

學會了記得回來給我一個 Star 哦 ()!

本文原創首發於微信公號「登龍」,分享機器學習、算法編程、Python、機器人技術等原創文章,掃碼關注回覆「1024」你懂的!

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