吳恩達機器學習邏輯迴歸python實現(未正則化)[對應ex2-ex2data2.txt資料集]
寫在前面:
1.筆記重點是python程式碼實現,不敘述如何推導。參考本篇筆記前,要有邏輯迴歸的基礎(熟悉代價函式、梯度下降、矩陣運算和python等知識),沒有基礎的同學可通過網易雲課堂上吳恩達老師的機器學習課程學習。網上也有一些對吳恩達老師課後作業的python實現,大多數都是用Jupyter Notebook寫的,一些重點的細節處沒有做詳細的說明而且基本上沒有繪製圖像的程式碼(我自以為我的筆記和程式碼解決了這些問題 ^ _ ^ ),因此非常不利於新手學習。我把自己學習過程中整理的筆記和程式碼放到CSDN上希望對初學者有所幫助。
2.此筆記講基於ex2data2.txt資料集的程式碼實現,ex2data2.txt資料集與ex2data1.txt資料集的格式基本一致,程式碼實現的流程上一致,不同處是特徵提取和邊界函式的影象繪製上。因此本篇筆記的程式碼是基於我的上一篇筆記《吳恩達機器學習邏輯迴歸python實現[對應ex2-ex2data1.txt資料集]》中提供的程式碼基礎上修改,此篇筆記不再詳細敘述其流程,重點敘述要修改的地方。ps:本篇筆記的程式碼中未進行正則化處理。
3.這篇筆記的重點:這篇筆記的案例是基於吳恩達機器學習邏輯迴歸課後作業中的ex2-ex2data2.txt資料集的。在ex2data1.txt資料集中邊界函式是一個一元一次函式,因此,基於ex2data1.txt資料集畫邊界函式非常簡單。而在ex2data2.txt資料集中我們要預設的邊界函式是一個高階函式,因此再使用上一篇筆記中的程式碼不能繪製出高階函式的曲線,此外,在此筆記的案例中,兩個特徵不能滿足邊界函式的要求,需要通過資料集中的資料計算出多個新的特徵,以滿足邊界函式的要求。這是此案例中要解決的兩個難點。
1.訓練資料對應的散點圖
對應的python程式碼:
# 資料匯入
def import_data():
# 定義x y資料 x1 y1:未通過 x2 y2:通過
x1 = []
y1 = []
x2 = []
y2 = []
# 匯入訓練資料
train_data = open("data/ex2data2.txt")
# 獲取資料集的行數
lines = train_data.readlines()
# 迴圈處理所有的資料
for line in lines:
scores = line.split(",")
isQualified = scores[2].replace("\n", "")
if isQualified == "0":
x1.append(float(scores[0]))
y1.append(float(scores[1]))
else:
x2.append(float(scores[0]))
y2.append(float(scores[1]))
return x1, y1, x2, y2
# 圖表繪製
def draw(x1, y1, x2, y2):
plt.xlabel("Microchip Test 1")
plt.ylabel("Microchip Test 2")
# 記號形狀 顏色 點的大小 設定標籤
plt.scatter(x1, y1, marker='o', color='red', s=15, label='Rejected')
plt.scatter(x2, y2, marker='x', color='green', s=15, label='Accepted')
# 註釋的顯示位置:右上角
plt.legend(loc='upper right')
# 設定座標軸上刻度的精度
plt.gca().xaxis.set_major_formatter(ticker.FormatStrFormatter('%.1f'))
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter('%.1f'))
# 繪製
plt.show()
2.決策邊界的定義
觀察影象可知,此處的決策邊界是非線性的,需要將決策邊界定義為高階函式。即定義
在本次實驗中定義了最高為6階函式,階數可自行定義(階數不能小於2),階數越高模型會越過擬合(這個規律大家可在程式碼中自行測試,改變程式碼main函式中的degree的大小觀察預測錯誤個數和繪製的影象),設定為6階後,輸入的特徵由兩個變為28個,因此需要對輸入的特徵進行重新構造,由兩個特徵構造為28個。
因為使用矩陣進行運算,特徵數量的變化不會影響程式碼中決策邊界函式的發生變化,即無論特徵的個數如何變化,決策邊界函式永遠是θX的形式。因此決策邊界函式的仍為:
# 定義邊界函式
def hypothesis(theta, x):
return np.dot(x, theta)
由於在決策邊界函式中,特徵的個數由2個變為28個,而給出的資料集中只有兩個特徵,因此需要基於本節(2.決策邊界的定義)開頭給出的決策邊界函式,計算出θ3至θ27所對應的特徵。修改init_data函式,在讀取資料集到矩陣中時把需要計算的特徵計算出來並新增到矩陣中,程式碼如下:
# 初始化資料
def init_data(file, degree):
# 兩次測試對應的特徵矩陣
data = []
# 標記對應的矩陣
label = []
# 讀取檔案
train_data = open(file)
lines = train_data.readlines()
for line in lines:
scores = line.split(",")
# 去除標記後面的換行符
isQualified = scores[2].replace("\n", "")
# 新增特徵x0,設定為1
data.append([1, float(scores[0]), float(scores[1])])
label.append(int(isQualified))
# 新增特徵量
feature_handler(data, degree)
# 標記矩陣轉置,返回特徵矩陣和標記矩陣
return np.array(data), np.array(label).transpose()
# 新增特徵量
def feature_handler(data, degree):
for i in range(0, len(data)):
for j in range(2, degree + 1):
for k in range(0, j + 1):
data[i].extend([np.power(data[i][1], k) * np.power(data[i][2], (j - k))])
3. sigmoid函式、代價函式、梯度下降函式、求解theta與上篇筆記中的程式碼一致,沒有改變
4. 新增驗證函式
在上一篇筆記中,直接通過繪製邊界函式的方式驗證了最終的模型預測效果,而在這篇筆記中,我添加了驗證函式,通過驗證函式的結果和繪製邊界影象兩種方式一起驗證最終的模型的預測效果。驗證函式就是把求得的theta和特徵帶入到sigmoid函式中,如果計算的值大於等於0.5那麼認為預測結果為1,即通過測試,否則認為預測結果為0,未通過測試。程式碼如下:
# 驗證函式
def check(theta, check_file, degree):
#讀取特徵
data, label = init_data(check_file, degree)
# 將資料匯入模型
check_label = sigmoid(theta, data)
# 對預測結果進行處理
error = 0
for i in range(0, len(check_label)):
if check_label[i] >= 0.5:
if label[i] != 1:
print("資料序號:", i + 1, "預測為 1 實際值為 ", label[i], "預測值:", check_label[i])
error += 1
else:
if label[i] != 0:
print("資料序號:", i + 1, "預測為 0 實際值為 ", label[i], "預測值:", check_label[i])
error += 1
print("預測錯誤個數:", error, "錯誤率:", error / len(check_label) * 100, "%")
5.繪製決策邊界影象
因為邊界函式是一個高階的方程,因此直接畫出圖形不好繪製。因此可以利用等高線 np.contour()繪圖,高階的函式得到的曲線一定是立體的,你可以想象成一個不規則的碗的形狀扣下二維座標軸的正上方。然後可以在碗上畫出一圈一圈的等高線,那麼在高度為0的那一條線正式與XY座標軸平面相交的那條線,因此我們通過xy以及設定x y的值,並通過已經求得theta值結合預測邊界函式求得z值,通過 np.contour()繪出高度為0的等高線就是我們的預測邊界函式的影象了。可參考:https://blog.csdn.net/cowry5/article/details/80261260
程式碼如下:
# 決策邊界影象繪製
def decision_boundary(theta):
x = np.linspace(-.8, 1.0, 20) # x座標
y = np.linspace(-.8, 1.0, 20) # y座標
xx, yy = np.meshgrid(x, y) # 生成網格資料
# 構造[1, x1, x2]格式的資料
feature = []
for i in range(0, x.shape[0]):
for j in range(0, x.shape[0]):
feature.append([1, x[i], x[j]])
# 新增剩餘的特徵
feature_handler(feature, 6)
# 計算z值
z = feature @ theta
# 保持維度一致
z = z.reshape(xx.shape)
# 繪製高度為0的等高線
plt.contour(xx, yy, z, 0)
#繪製測試資料的散點圖
x1, y1, x2, y2 = import_data()
draw(x1, y1, x2, y2)
plt.show()
需要注意的是xx, yy = np.meshgrid(x, y) # 生成網格資料 這行程式碼生成了20*20個點,因此我們求得的z也應該是20個點,因此在 # 構造[1, x1, x2]格式的資料這一步時我們要構造40個點的座標,而不是20個點的座標。
6.主函式
'''主函式'''
if __name__ == '__main__':
# 資料觀察
# x1, y1, x2, y2 = import_data()
# draw(x1, y1, x2, y2)
#預測邊界函式的階數
degree = 6
data, label = init_data("data/ex2data2.txt", degree)
# 初始化theta, 特徵個數 = (階數+1)*(階數+2)/2
feature_number = int((degree+1)*(degree + 2)/2)
theta = np.zeros((feature_number, 1))
# 使用minimize函式求解
result = opt.minimize(fun=cost, x0=theta, args=(data, label), method='Newton-CG', jac=gradient)
# 資料校驗
check(result.x, "data/ex2data2.txt", degree)
# 繪製邊界函式影象
decision_boundary(np.mat(result.x).transpose(), degree)
在主函式中,我並沒有將資料集按70%作為訓練資料,30%作為驗證資料,而是將所有的資料作為訓練資料,而且驗證函式驗證時也將所有的訓練資料進行驗證。大家在實驗時可按照7:3的比例將資料集分開。
邊界函式繪製曲線如下圖所示:
驗證函式輸出的結果:
7. 完整程式碼
from matplotlib import pyplot as plt
from numpy import *
import matplotlib.ticker as ticker
import numpy as np
import scipy.optimize as opt
# 資料匯入
def import_data():
# 定義x y資料 x1 y1:未通過 x2 y2:通過
x1 = []
y1 = []
x2 = []
y2 = []
# 匯入訓練資料
train_data = open("data/ex2data2.txt")
# 獲取資料集的行數
lines = train_data.readlines()
# 迴圈處理所有的資料
for line in lines:
scores = line.split(",")
isQualified = scores[2].replace("\n", "")
if isQualified == "0":
x1.append(float(scores[0]))
y1.append(float(scores[1]))
else:
x2.append(float(scores[0]))
y2.append(float(scores[1]))
return x1, y1, x2, y2
# 圖表繪製
def draw(x1, y1, x2, y2):
plt.xlabel("Microchip Test 1")
plt.ylabel("Microchip Test 2")
# 記號形狀 顏色 點的大小 設定標籤
plt.scatter(x1, y1, marker='o', color='red', s=15, label='Rejected')
plt.scatter(x2, y2, marker='x', color='green', s=15, label='Accepted')
# 註釋的顯示位置:右上角
plt.legend(loc='upper right')
# 設定座標軸上刻度的精度
plt.gca().xaxis.set_major_formatter(ticker.FormatStrFormatter('%.1f'))
plt.gca().yaxis.set_major_formatter(ticker.FormatStrFormatter('%.1f'))
# 繪製
# plt.show()
# 初始化資料
def init_data(file, degree):
# 兩次測試對應的特徵矩陣
data = []
# 標記對應的矩陣
label = []
# 讀取檔案
train_data = open(file)
lines = train_data.readlines()
for line in lines:
scores = line.split(",")
# 去除標記後面的換行符
isQualified = scores[2].replace("\n", "")
# 新增特徵x0,設定為1
data.append([1, float(scores[0]), float(scores[1])])
label.append(int(isQualified))
# 新增特徵量
feature_handler(data, degree)
# 標記矩陣轉置,返回特徵矩陣和標記矩陣
return np.array(data), np.array(label).transpose()
# 新增特徵量
def feature_handler(data, degree):
for i in range(0, len(data)):
for j in range(2, degree + 1):
for k in range(0, j + 1):
data[i].extend([np.power(data[i][1], k) * np.power(data[i][2], (j - k))])
# 定義邊界函式
def hypothesis(theta, x):
return np.dot(x, theta)
# 定義sigmoid函式
def sigmoid(theta, x):
z = hypothesis(theta, x)
return 1.0 / (1 + exp(-z))
# 定義代價函式
def cost(theta, X, y):
return np.mean(-y * np.log(sigmoid(theta, X)) - (1 - y) * np.log(1 - sigmoid(theta, X)))
# 梯度下降函式
def gradient(theta, X, y):
return (1 / len(X)) * X.T @ (sigmoid(theta, X) - y)
# 驗證函式
def check(theta, check_file, degree):
#讀取特徵
data, label = init_data(check_file, degree)
# 將資料匯入模型
check_label = sigmoid(theta, data)
# 對預測結果進行處理
error = 0
for i in range(0, len(check_label)):
if check_label[i] >= 0.5:
if label[i] != 1:
print("資料序號:", i + 1, "預測為 1 實際值為 ", label[i], "預測值:", check_label[i])
error += 1
else:
if label[i] != 0:
print("資料序號:", i + 1, "預測為 0 實際值為 ", label[i], "預測值:", check_label[i])
error += 1
print("預測錯誤個數:", error, "錯誤率:", error / len(check_label) * 100, "%")
# 決策邊界影象繪製
def decision_boundary(theta, degree):
x = np.linspace(-.8, 1.0, 20) # x座標
y = np.linspace(-.8, 1.0, 20) # y座標
xx, yy = np.meshgrid(x, y) # 生成網格資料
# 構造[1, x1, x2]格式的資料
feature = []
for i in range(0, x.shape[0]):
for j in range(0, x.shape[0]):
feature.append([1, x[i], x[j]])
# 新增剩餘的特徵
feature_handler(feature, degree)
# 計算z值
z = feature @ theta
# 保持維度一致
z = z.reshape(xx.shape)
# 繪製高度為0的等高線
plt.contour(xx, yy, z, 0)
# 繪製測試資料的散點圖
x1, y1, x2, y2 = import_data()
draw(x1, y1, x2, y2)
plt.show()
'''主函式'''
if __name__ == '__main__':
# 資料觀察
# x1, y1, x2, y2 = import_data()
# draw(x1, y1, x2, y2)
#預測函式的階數
degree = 6
data, label = init_data("data/ex2data2.txt", degree)
# 初始化theta, 特徵個數 = (階數+1)*(階數+2)/2
feature_number = int((degree+1)*(degree + 2)/2)
theta = np.zeros((feature_number, 1))
# 使用minimize函式求解
result = opt.minimize(fun=cost, x0=theta, args=(data, label), method='Newton-CG', jac=gradient)
# 資料校驗
check(result.x, "data/ex2data2.txt", degree)
# 繪製預測影象
decision_boundary(np.mat(result.x).transpose(), degree)