之前的邏輯迴歸文章:從 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 的向量(爲了與 相乘進行向量化表示):
# 添加第一列全 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 函數:
def sigmoid(z):
return 1 / (1 + np.exp(-z))
2.2 邏輯迴歸代價函數
def cost(theta, X, y):
return np.mean(-y * np.log(sigmoid(X @ theta)) - (1 - y) * np.log(1 - sigmoid(X @ theta)))
2.3 邏輯迴歸正則化代價函數
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 梯度計算
def gradient(theta, X, y):
return (1 / len(X)) * X.T @ (sigmoid(X @ theta) - y)
2.5 正則化梯度
在原梯度後面加上正則化梯度即可:
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」你懂的!