機器學習演算法學習筆記

過客匆匆,沉沉浮浮發表於2023-03-13

學習影片地址:第一章:線性迴歸原理推導 1-迴歸問題概述_嗶哩嗶哩_bilibili

個人練習程式碼倉庫地址:

 跟隨唐宇迪的機器學習演算法精講的程式碼練習: 跟隨唐宇迪博士學習機器學習演算法做過的一些程式碼練習 影片連結:https://www.bilibili.com/video/BV1514y1K7ty?p=1&vd_source=18458dbc1f8ed0a2b88f8f31064e2bba (gitee.com)

一、線性迴歸原理推導

1、線性迴歸問題的數學表達可以寫作如下方程式(1)

 

 

 其中y(i) 和x(i)均是資料集已知的量,ε是需要推導的引數矩陣,ε是隨機誤差矩陣,獨立且具有相同的憤怒,服從均值為0方差為θ2的高斯分佈。

由於ε服從高斯分佈,所以可以得到一個方程式(2)

 將方程式(1)代入到方程式(2)中,可以消去誤差ε,得到方程式(3)

 

我們可以找到一個關於θ的似然函式(4)

 

由於我們僅僅關注極值點的位置,並不關注極值的大小,所以可以給兩邊取對數,就可以將累乘變成累加,得到方程式(5)

 

 

提出常數項可以得到方程式(6)

 

 方程式(6)可以看作是一個常數減去一個平方數的形式,所以要找方程式(6)的極大值,就可以看作找後面平方數的極小值,我們得到方程式(7)(最小二乘法的公式)

 

 

 對J(θ)求偏導,並且令偏導為0,我們就可以求出了θ的值為(可以當作一個巧合,機器學習是需要迭代的,直接求出不符合機器學習的思想)

 

2、機器學習的套路:給機器一堆資料,然後告訴機器什麼樣的學習結果是對的(目標函式),讓他朝著這個方向進行迭代。

3、梯度下降演算法

首先由上面的推導,我們可以得出線性迴歸的目標函式為方程式(8)

 

 其中m為樣本的數量,我們對J求關於θ的偏導得到梯度方程式(9)

 

 梯度下降演算法原理就是每次迭代過程對引數向梯度反方向前進梯度個步數生成新的引數、直到找到最擬合目標函式的引數為止。

3.1批次梯度下降:每次前進總樣本的平均梯度,容易得到最優解,但是速度很慢,在資料量大的時候一般不使用,引數迭代方程式(10)如下:

 

 3.2隨機梯度下降:每次找一個樣本的梯度進行迭代,速度快,但是隨機性強,每次迭代不一定向著收斂的方向,引數迭代方程式(11)如下:

 

 3.3小批次梯度下降:每次使用一小部分的平均梯度進行迭代,速度快,迭代方向也比較好,經常被使用,引數迭代方程式(12)如下:

 

 其中α為學習率

二、線性迴歸程式碼實現

資料集我們使用阿里天池的幸福度預測資料集

資料集地址:快來一起挖掘幸福感!_學習賽_天池大賽-阿里雲天池 (aliyun.com)

下載好資料集後我們先不處理它,先來搭建模型

#!/usr/bin/env pytorch
# -*- coding: UTF-8 -*-
"""
@Project     :Follow-Tang-Yudi-machine-learning-algorithm-intensive-code-practice 
@File        :linear_model.py
@IDE         :PyCharm 
@Author      :張世航
@Date        :2023/3/7 9:08 
@Description :一個線性迴歸的模型
"""
import numpy as np

import prepare_train


class LinearRegression:
    def __init__(self, data, labels, polynomial_degree=0, sinusoid_degree=0, normalize_data=True):
        """
        1、對資料進行預處理操作
        2、得到特徵個數
        3、初始化引數矩陣
        :param data:原始資料
        :param labels:資料的標籤
        :param polynomial_degree:多項式維度
        :param sinusoid_degree:正弦維度
        :param normalize_data:是否進行歸一化
        """
        (data_processed, features_mean, features_deviation) = prepare_train.prepare_for_train(data, polynomial_degree,
                                                                                              sinusoid_degree,
                                                                                              normalize_data)

        self.data = data_processed
        self.labels = labels
        self.features_mean = features_mean
        self.features_deviation = features_deviation
        self.polynomial_degree = polynomial_degree
        self.sinusoid_degree = sinusoid_degree
        self.normalize_data = normalize_data

        num_features = self.data.shape[1]
        self.theta = np.zeros((num_features, 1))

    def train(self, alpha, num_epoch=500):
        """
        開始訓練
        :param alpha: 學習速率
        :param num_epoch: 迭代次數
        :return: 訓練好的引數,損失值記錄
        """
        cost_history = self.gradient_descent(alpha, num_epoch)
        return self.theta, cost_history

    def gradient_descent(self, alpha, num_epoch):
        """
        小批次梯度下降演算法
        :param alpha: 學習速率
        :param num_epoch: 迭代次數
        :return: 損失值的記錄
        """
        cost_history = []
        for i in range(num_epoch):
            self.gradient_step(alpha)
            cost_history.append(self.cost_function(self.data, self.labels))
        return cost_history

    def gradient_step(self, alpha):
        """
        梯度下降引數更新方法
        :param alpha: 學習率
        :return: 無
        """
        num_examples = self.data.shape[0]
        prediction = LinearRegression.hypothesis(self.data, self.theta)
        delta = prediction - self.labels
        theta = self.theta
        theta = theta - alpha * (1 / num_examples) * (np.dot(delta.T, self.data)).T
        self.theta = theta

    def cost_function(self, data, labels):
        """
        計算損失值函式(最小二乘法)
        :param data:當前資料
        :param labels:當前標籤
        :return:預測損失值
        """
        num_example = data.shape[0]
        delta = LinearRegression.hypothesis(data, self.theta) - labels
        cost = (1 / 2) * np.dot(delta.T, delta)
        return cost[0][0]

    @staticmethod
    def hypothesis(data, theta):
        """
        求預測值
        :param data: 輸入資料
        :param theta: 當前引數
        :return: 預測值
        """
        prediction = np.dot(data, theta)
        return prediction

    def get_cost(self, data, labels):
        """
        獲取損失值
        :param data: 傳入的資料
        :param labels: 資料的標籤
        :return: 當前模型預測資料的損失值
        """
        (data_processed, _, _) = prepare_train.prepare_for_train(data, self.polynomial_degree, self.sinusoid_degree,
                                                                 self.normalize_data)
        return self.cost_function(data_processed, labels)

    def predict(self, data):
        """
        預測函式
        :param data: 輸入資料
        :return: 預測的值
        """
        (data_processed, _, _) = prepare_train.prepare_for_train(data, self.polynomial_degree, self.sinusoid_degree,
                                                                 self.normalize_data)
        predictions = LinearRegression.hypothesis(data_processed, self.theta)
        return predictions

其中在模型初始化時候對資料進行了預處理

#!/usr/bin/env pytorch
# -*- coding: UTF-8 -*-
"""
@Project     :Follow-Tang-Yudi-machine-learning-algorithm-intensive-code-practice 
@File        :prepare_train.py
@IDE         :PyCharm 
@Author      :張世航
@Date        :2023/3/7 9:24 
@Description :資料預處理
"""
import numpy as np
import normalize
import generate_polynomials
import generate_sinusoids

def prepare_for_train(data, polynomial_degree=0, sinusoid_degree=0, normalize_data=True):
    """
    對資料進行預處理
    :param data: 原始資料
    :param polynomial_degree: 多項式維度
    :param sinusoid_degree: 正弦維度
    :param normalize_data: 是否進行歸一化
    :return: 處理後的資料,特徵均值,特徵方差
    """
    # 獲取樣本總數
    num_examples = data.shape[0]
    data_processed = np.copy(data)

    # 預處理
    features_mean = 0
    features_deviation = 0
    data_normalized = data_processed
    if normalize_data:
        data_normalized, features_mean, features_deviation = normalize.normalize(data_processed)
        data_processed = data_normalized

    # 特徵變數正弦變換
    if sinusoid_degree > 0:
        sinusoids = generate_sinusoids.generate_sinusoids(data_normalized, sinusoid_degree)
        data_processed = np.concatenate((data_processed, sinusoids), axis=1)

    # 特徵變數多項式變換
    if polynomial_degree > 0:
        polynomials = generate_polynomials.generate_polynomials(data_processed, polynomial_degree, normalize_data)
        data_processed = np.concatenate((data_processed, polynomials), axis=1)

    # 加一列1
    data_processed = np.hstack((np.ones((num_examples, 1)), data_processed))

    return data_processed, features_mean, features_deviation

預處理時候使用了正弦變換和多項式變換增加資料的維度,增加收斂速度,但是如果維度過高可能會過擬合

#!/usr/bin/env pytorch
# -*- coding: UTF-8 -*-
"""
@Project     :Follow-Tang-Yudi-machine-learning-algorithm-intensive-code-practice 
@File        :generate_polynomials.py
@IDE         :PyCharm 
@Author      :張世航
@Date        :2023/3/8 8:34 
@Description :進行多項式變換增加變數複雜度
"""
import numpy as np
import normalize


def generate_polynomials(dataset, polynomials_degree, normalize_data=False):
    """
    變換方法:x1, x2, x1^2, x2^2, x1 * x2, x1 * x2^2, etc.
    :param dataset:原始資料
    :param polynomials_degree:多項式的維度
    :param normalize_data:是否歸一化
    :return:生成的多項式引數
    """
    features_split = np.array_split(dataset, 2, axis=1)
    dataset_1 = features_split[0]
    dataset_2 = features_split[1]

    (num_examples_1, num_features_1) = dataset_1.shape
    (num_examples_2, num_features_2) = dataset_2.shape

    if num_examples_1 != num_examples_2:
        raise ValueError("can not generate polynomials for two sets with different number")
    if num_features_1 == 0 and num_features_2 == 0:
        raise ValueError("can not generate polynomials for two sets with no colums")
    if num_features_1 == 0:
        dataset_1 = dataset_2
    elif num_features_2 == 0:
        dataset_2 = dataset_1

    num_features = num_features_1 if num_features_1 < num_examples_2 else num_features_2
    dataset_1 = dataset_1[:, :num_features]
    dataset_2 = dataset_2[:, :num_features]

    polynomials = np.empty((num_examples_1, 0))
    for i in range(1, polynomials_degree + 1):
        for j in range(i + 1):
            polynomial_feature = (dataset_1 ** (i - j)) * (dataset_2 ** j)
            polynomials = np.concatenate((polynomials, polynomial_feature), axis=1)

    if normalize_data:
        polynomials = normalize.normalize(polynomials)[0]

    return polynomials
#!/usr/bin/env pytorch
# -*- coding: UTF-8 -*-
"""
@Project     :Follow-Tang-Yudi-machine-learning-algorithm-intensive-code-practice 
@File        :generate_sinusoids.py
@IDE         :PyCharm 
@Author      :張世航
@Date        :2023/3/8 8:35 
@Description :對引數進行正弦變換
"""
import numpy as np


def generate_sinusoids(dataset, sinusoid_degree):
    """
    變換方式 sin(x)
    :param dataset: 原始資料
    :param sinusoid_degree:變換維度
    :return: 變換後的引數
    """
    num_examples = dataset.shape[0]
    sinusoids = np.empty((num_examples, 0))

    for degree in range(1, sinusoid_degree + 1):
        sinusoid_fatures = np.sin(degree * dataset)
        sinusoids = np.concatenate((sinusoids, sinusoid_fatures), axis=1)

    return sinusoids

預處理時候對資料進行歸一化操作,讓資料更適合訓練

#!/usr/bin/env pytorch
# -*- coding: UTF-8 -*-
"""
@Project     :Follow-Tang-Yudi-machine-learning-algorithm-intensive-code-practice 
@File        :normalize.py
@IDE         :PyCharm 
@Author      :張世航
@Date        :2023/3/7 16:02 
@Description :歸一化資料
"""
import numpy as np


def normalize(features):
    """
    特徵歸一化
    :param features: 傳入特徵
    :return: 歸一化後的特徵,特徵均值,特徵標準差
    """
    features_normalized = np.copy(features).astype(float)
    # 計算均值
    features_mean = np.mean(features, 0)
    # 計算標準差
    features_deviation = np.std(features, 0)
    # 標準化操作
    if features.shape[0] > 1:
        features_normalized -= features_mean
    # 防止除以0
    features_deviation[features_deviation == 0] = 1
    features_normalized /= features_deviation
    return features_normalized, features_mean, features_deviation

搭建好網路模型後我們就可以進行模型的訓練了

首先載入資料集資料

plotly.offline.init_notebook_mode()
data_train = pd.read_csv("happiness_train_complete_prepared.csv", encoding="ISO-8859-1")
data_test = pd.read_csv("happiness_test_complete_prepared.csv", encoding="ISO-8859-1")
data_submit = pd.read_csv("happiness_submit.csv", encoding="ISO-8859-1")

print(data_train.shape, data_test.shape, data_submit.shape)

data_train1 = data_train.sample(frac=0.8)
data_train2 = data_train.drop(data_train1.index)

input_name = ["edu", "income"]
output_name = "happiness"

x_train1 = data_train1[input_name].values
y_train1 = data_train1[[output_name]].values
x_train2 = data_train2[input_name].values
y_train2 = data_train2[[output_name]].values
x_test = data_test[input_name].values

由於是個小練習,所以我們僅僅考慮了學歷和收入兩個維度來預測幸福度,由於測試集沒有幸福度答案,所以我們將訓練集分為兩部分,80%作為訓練集進行訓練,20%作為測試集來判斷模型訓練的好壞

設定模型的超引數如下

num_epoch = 500
learning_rate = 0.02
polynomial_degree = 5
sinusoid_degree = 15
normalize_data = True

進行模型的訓練並且繪製損失值

linear_model = LinearRegression(x_train1, y_train1, polynomial_degree, sinusoid_degree, normalize_data)
theta, cost_history = linear_model.train(learning_rate, num_epoch)
print("{}->{}".format(cost_history[0], cost_history[-1]))
plt.plot(range(num_epoch), cost_history)
plt.xlabel("epoch")
plt.ylabel("cost")
plt.title = "cost curve"
plt.show()

使用模型對測試集進行預測並且獲取得分

z_test = linear_model.predict(x_train2)
sorce = 0.0
num_test = z_test.shape[0]
for index, value in enumerate(z_test):
    sorce += (round(value[0]) - y_train2[index]) ** 2
rate = sorce / num_test
print(rate)

預測下載好的測試集的幸福度並且儲存到csv中

test_pridict = linear_model.predict(x_test)
result = list(test_pridict)
result = list(map(lambda x: round(x[0]), result))

data_submit["happiness"] = result
data_submit.to_csv("happiness.csv", index=False)

好了,我們可以執行一下試試了

 

 得到如上圖的損失曲線

 

 列印出來的得分為0.65

那麼我們將預測出的幸福度提交到阿里天池看看我們的小練習的得分吧

不出所料,僅僅考慮兩個維度得分結果是0.7,比我們列印出來的得分還要差,如果感興趣的同學,可以多考慮幾個維度試試,不過需要注意下載的資料集有的列資料有空值,需要進行預處理,因為線性迴歸模型沒辦法處理空值的。

 三、模型評估方法

1、sklearn工具包提供了機器學習的一些模型、損失函式以及資料集切分的方法

2、一般將資料集80%切分出來作為訓練集,20%切分出來作為測試集

3、交叉驗證:將訓練集又分為幾個部分,在訓練中輪流取一部分作為內部測試集,其餘作為訓練集,進行驗證,可以使用cross_val_score得到驗證結果,也可以使用stratified kfold手動進行分割,自己計算結果

4、混淆矩陣(二分類問題):測試結果可以列出下面矩陣

  正類 負類
被檢測出 TP FP
未被檢測出 FN TN

可以使用cross_val_predict獲取交叉驗證的估計值,也可以使用coufusion_matrixlai來獲取混淆矩陣

精度的計算方式為TP/(TP+FP)表明模型的精準度,迴歸的計算方式為TP/(TP + FN)表明模型的泛化能力,可以使用內建函式precision_sorce來計算模型的精度,recall_score來獲取模型的泛化能力

F1指標是綜合考慮精度和迴歸能力的一個指標計算方法如下:

 

 可以使用內建函式F1_score直接獲取F1的得分

5、閾值

decision_function可以得到當前樣本每一個分類的得分值。

一個模型設定的閾值越低,精度越低但是泛化能力越強,閾值越高,精度越高但是泛化能力會變弱

6、ROC曲線

precision_recall_curve可以得到不同閾值下的精度和迴歸值

可以計算正確預測值TPR = TP / (TP + FN),錯誤預測值FPR = FP / (FP+TN)

以TPR為x,FPR為y做的曲線叫做ROC曲線,ROC曲線所圍成的面積越大說明模型就越好

roc_curve函式可以得到不同閾值下TPR和FPR的結果

四、線性迴歸實驗

1、sklearn中,也內建了線性迴歸的模型LinearRegression

2、標準化的作用:取值範圍不同的值會導致模型收斂速度變慢,進行標準化的操作有利於模型的收斂

3、學習率的作用:小的學習率收斂速度慢,大的學習率會產生抖動,所以一般先設定大的學習率,然後隨著訓練次數增加慢慢降低學習率

4、多項式迴歸:內建了PolonomialFeatures方法來將輸入進行多項式展開增加引數的維度,但是維度太高會導致過擬合

5、metrics模組提供了方法進行評估模型,資料樣本越大過擬合的機率也就越小

6、正則化:嶺迴歸和lasso迴歸在損失函式中加入了正則項,可以消除過擬合

嶺迴歸的損失函式為:

 

 其中α為正則化懲罰力度

lasso迴歸損失函式為:

 

 未完待續

 

相關文章