機器學習演算法(一):1. numpy從零實現線性迴歸

hejuzs發表於2024-06-07

系列文章目錄

機器學習演算法(一):1. numpy從零實現線性迴歸
機器學習演算法(一):2. 線性迴歸之多項式迴歸(特徵選取)

@

目錄
  • 系列文章目錄
  • 前言
  • 一、理論介紹
  • 二、程式碼實現
    • 1、匯入庫
    • 2、準備資料集
    • 3、定義預測函式(predict)
    • 4 代價(損失)函式
    • 5 計算引數梯度
    • 6 批次梯度下降
    • 7 訓練
    • 8 視覺化一下損失
  • 總結


前言

最近,想將本科學過的一些機器學習演算法從零開始實現一下,畢竟天天當調包俠,雖然也能夠很愉快的玩耍,但對於我這個強迫症患者是很難受的,底層邏輯是怎麼樣的,還是需要知道的。接下來我會從最簡單的多元線性迴歸開始,一步一步在\(jupyter\)裡面實現。
【注1】:本文預設讀者具有基本的機器學習基礎、\(numpy\)基礎,如果沒有建議看完吳恩達老師的機器學習課程在來看本文。
【注2】:本文大部分程式碼會採用向量化加速程式設計,當然部分情況下也會用到迴圈遍歷的情況。後面有時間會再出一起迴圈遍歷實現的。
【注3】:作者實力有限,有錯在所難免,歡迎大家指出。

一、理論介紹

線性迴歸的預測函式:

\[y = w_1x_1+w_2x_2+...+w_nx_n+b \]

寫成向量的形式:$$y = \mathbf{w^Tx}+b $$
其中:\(\mathbf{w} = (w_1,...,w_n)^T,\mathbf{x}=(x_1,...,x_n)^T\)
為了程式設計方便,將\(b\)收縮到\(w\)向量裡面去,有$$\mathbf{w} = (w_0,w_1,...,w_n)^T,w_0=b$$

\[\mathbf{x}=(x_0,x_1,...,x_n)^T,x_0=1 \]

上面模型變為$$y = \mathbf{w^Tx}$$
損失函式和偏導公式如下:

\[J(\mathbf{w})=\frac{1}{2m} (\mathbf{Xw}-\mathbf{y} )^T(\mathbf{Xw}-\mathbf{y} ) \]

\[\frac{\partial J}{\partial \mathbf{w} } =\frac{1}{m} \mathbf{X^T} (\mathbf{Xw}-\mathbf{y} ) \]

有了以上的理論操作,下面可以開始操作了
【注】:上述理論推導本文不作說明,有需要可參考周志華老師西瓜書。

二、程式碼實現

1、匯入庫

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

庫就不用介紹了,很常見的機器學習會用到的庫。

2、準備資料集

生成資料集的方式很多,random庫,或者你有資料(csv等)都可以。本文重點不是這個,因此簡單用numpy手打生成了。

X_train = np.array([[2104, 5, 1, 45], [1416, 3, 2, 40], [852, 2, 1, 35]])
y_train = np.array([[460], [232], [178]])
print(X_train)
print(y_train)

輸出結果:
image

注:我一般喜歡將向量是列向量還是行向量與數學公式中嚴格對應起來,否則,廣播機制出現了問題真的很頭疼的。

# 訓練集第一列插入全為1的1列
new_column = np.ones((X_train.shape[0], 1))  # 建立一列全是 1 的陣列
X_train = np.concatenate((new_column, X_train), axis=1)  # 在索引為 0 的位置插入新列
print(X_train)

image

3、定義預測函式(predict)

def predict(x,w):
    '''
    
    :param x: 要預測的樣本點向量,x是行向量
    :param w: 訓練出來的引數向量,w,是列向量
    :param b: 訓練出來的引數 b ,b可以是標量,也可以是一個元素的陣列
    :return: prediction,預測值
    '''
    return np.dot(x,w)
w_init = np.array([ [785.1811367994083],[0.39133535], [18.75376741], [-53.36032453], [-26.42131618]])
x_vec = X_train[0,:]
print(f"x_vec shape {x_vec.shape}, x_vec value: {x_vec}")

# make a prediction
f_wb = predict(x_vec,w_init)
print(f"f_wb shape {f_wb.shape}, prediction: {f_wb}")

輸出:image

4 代價(損失)函式

向量化加速:

\[J(\mathbf{w},b )=\frac{1}{2m} (\mathbf{Xw}-\mathbf{y} )^T(\mathbf{Xw}-\mathbf{y} ) \]

def loss(X,y,w):
    m = X.shape[0]
    err = np.dot(X,w) -y
    return (1/(2*m))*np.dot(err.T,err)
cost = loss(X_train, y_train, w_init)
print(f'Cost at optimal w : {cost}')

輸出:image

5 計算引數梯度

向量化加速:

\[\frac{\partial J}{\partial \mathbf{w} } =\frac{1}{m} \mathbf{X^T} (\mathbf{Xw}-\mathbf{y} ) \]


def compute_gradient(X, y, w):
    err = np.dot(X,w) - y
    return (1/len(X))*np.dot(X.T,err)
g = compute_gradient(X_train,y_train,w_init)
print(g)

輸出:image

6 批次梯度下降

def gradient_descent(X, y, w_in, loss, gradient_function, alpha, num_iters): 
    J_history = []
    # 用來存每進行一次梯度後的損失,方便檢視損失變化。如果要畫損失變化也是用這個
    # w = copy.deepcopy(w_in)  #avoid modifying global w within function
    w = w_in
    
    for i in range(num_iters):
        dj_dw = gradient_function(X, y, w)   
        w = w - alpha * dj_dw                              
        if i<100000:      
            J_history.append( loss(X, y, w))
        if i % math.ceil(num_iters / 10) == 0:
            # 控制間隔 列印出一次損失結果
            # 總迭代次數分成均分10組,在每組最後列印一次損失
            print(f"迭代次數(梯度下降次數) {i}: Cost {J_history[-1]}")
        
    return w,J_history 

7 訓練

initial_w = np.zeros_like(w_init)
iterations = 1000
alpha = 5.0e-7
# run gradient descent 
w_final,J_hist = gradient_descent(X_train, y_train, initial_w,loss, compute_gradient, 
                                                    alpha, iterations)
print(f"b,w found by gradient descent: {w_final} ")
m = X_train.shape[0]
for i in range(m):
    print(f"prediction: {np.dot(X_train[i], w_final)}, target value: {y_train[i]}")

輸出:
image

8 視覺化一下損失

J_hist = [i[0,0] for i in J_hist]
# 畫一下損失變化圖
fig, (ax1, ax2) = plt.subplots(1, 2, constrained_layout=True, figsize=(12, 4))
ax1.plot(J_hist)
ax2.plot(100 + np.arange(len(J_hist[100:])), J_hist[100:])
ax1.set_title("Cost vs. iteration");  ax2.set_title("Cost vs. iteration (tail)")
ax1.set_ylabel('Cost')             ;  ax2.set_ylabel('Cost') 
ax1.set_xlabel('iteration step')   ;  ax2.set_xlabel('iteration step') 
plt.show()

輸出:
image

明顯看到,隨便取的資料集,\(loss\)迭代到後面就穩定600多了,線性迴歸不太適合該資料集。有時間在用多項式迴歸試試效果。

總結

以上就是基本的線性迴歸實現思路了,這裡都是用矩陣向量化實現的,大家也可以試試迭代實現。後面有時間,我也會將迭代版本上傳的。
【注】:需要\(jupyter\)原始檔的可以評論區留言,看到我會回覆的。
【注】:最後,作者實力有限,有錯誤在所難免,歡迎大家指出,會慢慢修改。

相關文章