机器学习第三课: 线性回归

14 阅读13分钟

有了前两节的基础, 那么可以继续往下学习,如果还有不了解的,可以搜索“全栈派森”公众号,关注后继续学习, 本公众号旨在以学习为目的, 不写与技术or知识方向无关的内容;

本章重点

  • 数据收集
  • 数据可视化与分析
  • 数据清洗与规范化
  • 选择机器学习模型
  • 梯度下降
  • 调试超参数

本章会从0到1的学习机器模型的底层原理, 学习平台是在线上平台kaggle上完成的, 当然其他渠道也是可以的。

数据收集

数据集是非常重要的一环,没有大量的数据, 就没有后面的建模, 分析等等流程, 所以数据收集是重中之重。
这里直接采用线上分享的广告投放金额和销售额数据的csv文件,直接搜索“advertising-simple-dataset”,在当前数据集中, 新建“notebook”即可。

数据采集

可以看到微信,微博,其他广告,销售额一共四个栏位, 代码示例:

import numpy as np # 导入Numpy库
import pandas as pd # 导入Pandas库

df_ads = pd.read_csv("../input/advertising-simple-dataset/advertising.csv")
df_ads.head()

从数据栏位上看, 一共有3个特征值(微信,微博,其他广告投放金额),一个标签值(销售额), 它是一个多元回归问题。

数据的相关分析

相关分析是统计学中研究两个或多个随机变量之间线性相关程度的方法。通过分析后,可以通过相关系数了解数据集中任意一对变量(a,b)之间的相关性。
相关系数是-1~+1的值,正值表示正相关,负值表示负相关。数值越大,相关性越强。如果a和b的相关系数是1, 则a和b总是相等的。如果a和b的相关系数是0.9,则b会显著地随着a的变化而变化, 而且变化的趋势保持一致。如果a和b的相关系数是0.3,则说明两者之间并没有什么明显的关系。

  • 热力图

在Python中,相关分析用几行代码即可实现,并可以用热力图(heatmap)的方式非常直观的展示处理:

# Matplotlib 为 Python画图工具库
import matplotlib.pyplot as plt 
# Seaborn 为统计学数据可视化工具库
import seaborn as sns
# 对所有的标签和特征两两显示其相关性的热力图
# dataframe = df_ads.corr(), cmap = 颜色映射方案, annot= 是否在单元格内显示数据
sns.heatmap(df_ads.corr(), cmap="YlGnBu", annot=True)
plt.show() # plt代表英文 plot, 就是画图

热力分析

运行代码之后,3个特征家一个标签共4组变量之间的相关性系数全部以矩阵的形式显示,而且相关性越高,对应的颜色越深。此处相关性分析结果很明确地显示——将有限的钱投入到微信公众号里面做广告是最为合理的选择。

  • 数据散点图

通过散点图(scatter plot)两两一组显示商品销售额和各种广告投放金额之间的对应关系,将重点聚焦。散点图是回归分析中,数据点在直角坐标系平面上的分布图,是相当有效的数据可视化工具。

# Seaborn 为统计学数据可视化工具库
import seaborn as sns
# 显示销售额和各种广告投放金额的散点图
sns.pairplot(df_ads, 
             x_vars=['wechat', 'weibo', 'others'], 
             y_vars='sales', height=4, aspect=1, kind='scatter')
plt.show()

散点图

数据集清洗与规范化

  • 数据清洗

通过观察相关性和散点图,发现案例中3个特征中,微信广告投放金额和商品销售额的相关性比较高。为了简化模型,暂时只留下微信广告投放金额,这样,就可以把多变量的回归分析简化为单变量的回归分析。
下面把df_ads中的微信广告投放字段读入一个Numpy数组X, 也就是清洗了其他两个特征字段,并把标签读入数组y:

# 构建特征集 只含有微信公众号广告投放金额一个特征
X = np.array(df_ads.wechat) 
# 构建标签集, 销售额
y = np.array(df_ads.sales) 

print("张量X的阶", X.ndim)
print("张量X的形状", X.shape)
print("张量X的内容", X)

单变量数据集

对于回归问题的数值类型数据集,机器学习模型所读入的规范格式应该是2D张量, 也就是矩阵, 其形状为(样本数,标签数)。其中的行是数据,而其中的列是特征。可以将其想象成Excel表格的格式。现在就要把它的形状从(200,)变成(200,1),因此需要用reshape方法进行变形:

# 通过reshape方法把向量转换为矩阵, len函数返回样本个数
X = X.reshape(len(X), 1)
y = y.reshape(len(y),  1)

print("张量X的阶", X.ndim)
print("张量X的形状", X.shape)
print("张量X的内容", X)

向量数据集

现在数据格式从(200,)变成了(200,1)。尽管还是200个数字,但是数据的结构从一个1D数组变成了有行有列的矩阵。再次强调,对于常见的连续性数值数据集(也叫向量数据集), 输入特征集是2D矩阵,包含两个轴。

  1. 第一个轴是样本轴, 也叫矩阵的行, 本例一共200行数据。
  2. 第二个轴是特征轴,也叫矩阵的列,本例中只有一个特征。
  • 拆分数据集为训练集和测试集

在开始建模之前,需要把数据集拆分为两个部分: 训练集和测试集。在普通的机器学习中,至少要包含这两个数据集,一个用于训练机器,确定模型(目标函数),另一个用于测试模型的准确性。不仅如此, 往往还需要一个验证集, 以在最终测试之前增加验证环节。

# 将数据集进行80%(训练集)和20%(测试集)的分割
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
print(X_train)
  • 数据归一化

数据归一化是一种预处理技术,用于将数值型数据缩放到一个特定的范围,通常是 [0, 1] 或 [-1, 1]。其目的是消除不同特征之间由于量纲(单位)和数值范围差异过大而导致的影响,使得不同特征具有可比性。
通常情况下, 通过Sklearn库中的preprocessing(数据预处理)工具中的MinMaxScaler可以实现数据的归一化。在这里我们自定一个归一化函数:

# 定义归一化函数, 进行数据压缩
def scaler(train, test):
    min = train.min(axis=0) # 训练集最小值
    max = train.max(axis=0) # 训练集最大值
    gap = max - min # 最大值和最小值的差
    train -= min # 所有数据减去最小值
    train /= gap # 所有数据除以最大值和最小值的差
    test -= min  # 把训练集最小值应用于测试集
    test /= gap  # 把训练集最大值和最小值的差应用于测试集
    return train, test  # 返回压缩后的数据

X_train, X_test = scaler(X_train, X_test)  # 对特征归一化
y_train, y_test = scaler(y_train, y_test)  # 对标签归一化

在建立机器学习模型时,理论上测试集还没有出现,所以这个步骤一定要在拆分数据集之后进行。有很多人先对整个数据集进行特征压缩,然后拆分数据集,这种做法是不谨慎的,会把测试集中的部分信息泄漏到机器学习的建模过程之中。
下面的代码显示数据被压缩处理之后的散点图, 形状和之前的图完全一致, 只是数值已被限制在一个较小的区间:

# 用上面导入 matplotlib.pyplot中的plot显示散点图
plt.plot(X_train, y_train, 'r.', label='Training data') # 显示训练数据
plt.xlabel('wechat') # x轴标签
plt.ylabel('scales') # y轴标签
plt.legend() # 显示图例
plt.show()  # 显示绘图结果

归一化散点图

目前的数据准备,分析,包括简单的特征工程工作已经全部完成。

选择机器学习模型

  • 确定线性回归模型

对于本例,使用什么模型,早就已经确认了,当然从上面的函数直线也能反应出特征(微信投放)和标签(销售额)之间的关系,拟合程度还是挺不错的。
简单的模型就是一元线性函数:

一元线性函数

y = ax + b
其中,参数a的数字含义是直线的斜率(陡峭程度), b则是截距(与y轴相交的位置)。
在机器学习中,会稍微修改下,模型表述为:

y = wx + b

  • 假设(预测)函数 - h(x)

假设函数的方程式:

y‘ = wx + b

也可以写成:

h(x) = wx + b

其中, 需要注意以下两点:

  • y’指的是预测出的标签,读作y帽(y-hat)
  • h(x) 就是机器学习所得到的函数模型,能根据输入的特征进行标签的预测。

机器学习过程, 是一个不断假设,探寻,优化的过程,在找到最佳的函数f(x)之前,所有的函数模型不一定是很准确的。它是很多种可能的模型之中的一种——因此假设函数得出的结果是y‘,而不是y本身。所以假设函数有时也被叫作预测函数(predication function)。

有一定数学了解都知道, 只要确定了w(weight 权重) 和 b(bias 截距), 整个函数模型就确定了。 那么w和b要如何得到呢 ??

  • 损失(误差)函数——L(w, b)

损失,是对糟糕预测的惩罚。损失也就是误差,也称为成本(cost)或代价,也就是当前预测值和真实值之间的差距的体现。

左边是平均损失较大的模型, 右边是平均损失较小的模型

针对每一组不同的参数,机器都会针对样本数据集算一次平均损失。计算平均损失是每一个机器学习项目的必要环节。
如果平均损失小,参数就好,如果平均损失大,模型或者参数就还要继续调整。

机器学习中的损失函数有很多, 主要包括以下:

  • 用于回归的损失函数
    • 均方误差(Mean Square Error, MSE)函数
    • 平均绝对误差(Mean Absolute Error, MAE)函数, 也叫L1损失函数
    • 平均偏差误差(Mean Bias Error) 函数
  • 用于分类的损失函数
    • 交叉熵损失(Cross-Entropy Loss)函数
    • 多分类SVM损失(Hinge Loss)函数

在实际项目开发中,MSE函数是不需要手写的,直接调用python数据库函数即可。
使用MSE函数做损失函数的线性回归算法,有时被称为最小二乘法。

# 手工定义一个均方误差函数
def loss_function(X, y, weight, bias):
    # 这是假设函数, 其中已经应用了 python的广播功能
    y_hat = weight * X + bias
    loss = y_hat - y  # 求出每一个y‘ 和 训练集中真实的y之间的差异
    cost = np.sum(loss**2) / (2 * len(X)) # 这是均方误差函数的代码实现
    return cost   # 返回当前模型的均方误差值

print("当权重为5, 偏置为3, 损失为:", loss_function(X_train, y_train, weight = 5, bias = 3))
print("当权重为100, 偏置为1, 损失为:", loss_function(X_train, y_train, weight = 100, bias = 1))

损失函数

梯度下降找到最佳参数

上节有说到梯度下降就是找到函数最优解, 而损失函数和预测函数之间就是一个不断循环的迭代过程。损失越小,拟合的越好。

计算参数更新的过程是一个迭代的过程,也就是训练机器的过程

均方误差函数的损失曲线是一个凸函数。凸函数的图像会流畅,连续额形成相对于y轴的全局最低点,也就是说存在着全局最小损失点。这也是选择MSE 作为线性回归的损失函数的原因。

均方误差函数的损失曲线是一个凸函数

通过梯度下降法,如果初始估计的w值落在最优值左边,那么梯度下降会将w增加大, 以趋近最低值; 如果初始估计的w值落在最优值的右边,那么梯度下降会将w减小,以趋近最低值。这个趋渐趋近于最优值的过程也叫作损失函数的收敛。
梯度下降公示, 代码示例:

# 定一个实现梯度下降的函数
def gradient_descent(X, y, w, b, lr, iter):
    l_history = np.zeros(iter) # 初始化记录梯度下降过程中损失的数组
    w_history = np.zeros(iter) # 初始化记录梯度下降过程中权重的数组
    b_history = np.zeros(iter) # 初始化记录梯度下降过程中偏置的数组
    for i in range(iter):
        y_hat = w*X + b # 这是向量化运算实现的假设函数
        loss = y_hat - y # 这是中间过程,求得的是假设函数预测的y‘ 和 真正的 y值之间的差值
        derivative_w = X.T.dot(loss) / len(X)  # 对权重求导, len(X) 是样本总数
        derivative_b = sum(loss)*1 / len(X) # 对偏置求导
        w = w - lr * derivative_w  # 结合学习速率alpha更新权重
        b = b - lr * derivative_b    # 结合学习速率alpha更新偏置
        l_history[i] = loss_function(X, y, w, b)  # 梯度下降过程中损失的历史记录
        w_history[i] = w  # 梯度下降过程中权重的历史记录
        b_history[i] = b  # 梯度下降过程中偏置的历史记录 
    return l_history, w_history, b_history   # 返回梯度下降过程中的数据

超参数调优

在线性回归中, 权重和偏置的初始值的选择是可以随机的

# 首先确定参数的初始值
iterations = 100 # 迭代次数
alpha = 1 # 初始学习速率设为 1
weight = -5 # 权重
bias = 3 # 偏置
# 计算一下初始权重和偏置所带来的损失
print("当前损失:", loss_function(X_train, y_train, weight, bias))

对初始值输出回归函数的图像

plt.plot(X_train, y_train, 'r.', label="Training data") # 显示训练数据
line_X = np.linspace(X_train.min(), X_train.max(), 500) # X 值域
line_y = [weight*xx + bias for xx in line_X] # 假设函数y_hat
plt.plot(line_X, line_y, 'b--', label="Current hypothesis") # 显示当前假设函数
plt.xlabel('wechat') # x轴标签
plt.ylabel('sales')  # y轴标签
plt.legend() # 显示图例
plt.show() # 显示函数图像

还没开始机器学习之前: 随机选择初始参数的函数图像

拟合效果不是很好, 后面就要进行调节参数进行调优学习就好, 最终确定w和b即可。

学习后函数图

讲到这里线性回归已经讲完了,和之前的房价预测(直接采用Sklearn库函数)基本是一样的。

from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
# 线性回归模型
from sklearn.linear_model import LinearRegression
model = LinearRegression()
# 根据训练集数据,训练机器,拟合函数
model.fit(X_train, y_train)

总结

利用python库,几行代码就可以搞定线性回归模型,但是理解其中原理是更难的,也让我们在后期的学习过程中,更加轻松解决一些难点。

如果对你有帮助, 请点赞+关注鼓励下

全栈派森