机器学习算法学习笔记


学习视频地址:第一章:线性回归原理推导 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回归损失函数为:

 

 未完待续