1.4 线性回归代码实战
掌握了线性回归模型的构建方式与使用梯度下降算法来求解模型的最优参数的方法后,接下来使用代码实现线性回归模型,并对其进行训练。为了实现可视化,数据集中的每一个样本只有一个特征,与其对应的标签使用来表示。
接下来分别使用两个不同的线性回归模型来拟合数据,这样能对线性回归模型有全面的理解和掌握。在对机器学习中的模型进行训练时,会经常遇到模型在训练过程中出现过拟合(overfitting)的现象。本节的第2个模型在训练时会出现过拟合的现象,在实际遇到过拟合的现象后再来学习过拟合出现的原因与解决办法会加深对过拟合的理解。
1.4.1 线性回归模型的构建与训练
首先在程序中加载数据集,数据集中的全部样本存储在dataset.csv
文件中,文件中有两列数据,列名分别为X
与y
。X
这一列的数据为所有样本的特征值,y
这一列的数据为所有样本的标签值。可以使用Pandas模块将数据集加载到程序中,然后将数据集中所有样本的特征值与标签值取出,分别使用变量X
与变量y
来保存,如以下代码所示。
import pandas as pd
import numpy as np
# 加载数据集
dataset = pd.read_csv('dataset.csv')
# 取出每个样本的特征值
X = np.array(dataset['X'])
# 取出每个样本的标签值
y = np.array(dataset['y'])
在实际项目中,通常不是使用数据集中全部的样本数据对线性回归模型进行训练,而是将整个数据集按照一定比例分为训练集(train set)与测试集(test set),然后只使用训练集的数据对模型进行训练。使用训练集中的数据训练好模型以后,再使用测试集数据对模型进行评估。因为模型在训练时没有接触过测试集的数据,所以能够保证使用测试集对模型评估时结果更准确。例如,对于一个学生来说,在平时学习过程中做的练习题需要和考试中出现的题尽可能地不同,这样才能够检测出这个学生在平时是否学得好。如果练习题与考试的题完全一样,学生可以背下每一道题目的答案,而不是掌握题目的解题思路,那么会直接导致当遇到练习题以外的题目时,完全不知道怎么解决。这就是为什么要把数据集分成训练集与测试集。
在刚刚加载的数据集中,共有40个样本数据。可以把其中的前30个样本数据作为训练集,训练集中所有样本的特征值与标签值分别使用X_train
与y_train
变量来存储,使用变量n_train
来表示训练集中的样本个数。将数据集中最后10个样本作为测试集,测试集中所有样本中的特征值与标签值分别使用X_test
与y_test
变量来存储,使用n_test
变量来表示测试集中的样本个数。实现方式如以下代码所示。
# 训练集
X_train = X[0: 30]
y_train = y[0: 30]
n_train = len(X_train)
# 测试集
X_test = X[30:]
y_test = y[30:]
n_test = len(X_test)
将数据集划分为训练集与测试集以后,构建一个线性回归模型来拟合训练集数据。这个线性回归模型的表达式为。其中,与分别表示样本的特征值与线性回归模型对样本的预测值,、为模型的参数。
为了使用梯度下降算法对这个线性回归模型进行训练,进而找到模型的最优参数值、,需要使用损失函数来衡量模型的预测值与样本实际标签值之间的差值。这个模型的损失函数如下。
其中,为训练集中的样本个数,与分别为模型对第个样本的预测值与样本的标签值。为了应用梯度下降算法求让损失函数取值最小的参数值,需要对模型参数、求梯度,参数、的梯度分别为
求出线性回归模型中参数的梯度值后,再对参数进行初始化,然后就可以使用合适的学习率来利用梯度下降算法对模型参数进行多次迭代更新,直到找到模型的最优参数值和。
现在,可以利用梯度下降算法,使用训练集中的数据对模型进行多次迭代训练,最终得到最优参数值。首先构建线性回归模型,并对模型的参数进行随机初始化。将模型的参数、分别初始化为−0.3与0.6。将在梯度下降算法中使用的学习率的值设置为0.001。指定模型使用梯度下降算法迭代更新参数的次数为5 000。最后构建线性回归模型,如以下代码所示。
# 把模型的参数w与b分别随机初始化为-0.3和0.6
w = -0.3
b = 0.6
# 指定学习率的值
lr = 0.001
# 指定模型使用梯度下降算法迭代更新参数的次数
epochs = 5000
# 构建线性回归模型
def model(x):
y_hat = w * x + b
return y_hat
将线性回归模型构建好,并对其参数进行初始化以后,使用梯度下降算法对其训练。按照上面推导出的对损失函数求梯度的计算公式,更新参数。首先,从训练集中依次取出每一个样本的特征值,使用线性回归模型对其进行预测得到预测值,将样本的预测值与标签值相减,并乘以对应样本的特征值。然后,将每一个样本的计算结果相加,再除以训练集中样本的个数。最后,乘以2,结果即为参数的梯度值。求参数的梯度值与求参数的梯度值的方式类似。得到了参数的梯度值以后,就可以对参数使用梯度下降算法进行更新。具体实现方式如以下代码所示。
for epoch in range(epochs):
# sum_w与sum_b用于存储计算梯度时相加的值
sum_w = 0.0
sum_b = 0.0
# 求参数w与b的梯度值
for i in range(n_train):
xi = X_train[i]
yi = y_train[i]
yi_hat = model(xi)
sum_w += (yi_hat - yi) * xi
sum_b += (yi_hat - yi)
# grad_w与grad_b分别为参数w、b对应的梯度值
grad_w = (2.0 / n_train) * sum_w
grad_b = (2.0 / n_train) * sum_b
# 使用梯度下降算法更新模型参数
w = w - lr * grad_w
b = b - lr * grad_b
训练好模型以后,为了直观地看出这个线性回归模型对数据的拟合程度,可以将数据集中的样本与线性回归模型的图像画在一张图上。首先,在程序中加载用于数据可视化的Matplotlib库,为了能够在可视化的图像中显示中文字体,需要对其中的字体参数进行配置。然后,在图像中依次以散点图的方式画出数据集中的样本、以线条的形式画出函数的图像。具体实现方式如以下代码所示。
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif']=['SimHei']
%matplotlib inline
def plots(w, b, X, y):
fig, ax = plt.subplots()
# 画出数据集中的样本
ax.scatter(X, y)
# 画出线性回归模型的图像
ax.plot([i for i in range(0, 20)],
[model(i) for i in range(0, 20)])
plt.legend(('模型', '数据'),
loc='upper left',
prop={'size': 15})
plt.title("线性回归模型", fontsize=15)
plt.show()
plots(w, b, X, y)
运行以上代码,将数据集中的样本与线性回归模型的图像可视化的结果如图1.2所示。从图1.2中可以看出,数据集中几乎所有的样本点分布在线性回归模型的图像两侧,这说明线性回归模型能够较好地拟合数据集中的数据。
图1.2 数据集中的样本与线性回归模型的图像的可视化结果
对线性回归模型有一个直观的理解后,使用损失函数来分别计算出模型在训练集与测试集上的损失值,并将损失值进行对比,进而分析出模型在训练集数据上的预测效果。损失函数的定义如以下代码所示。首先依次取出传入损失函数中数据集的每一个样本,使用线性回归模型对其进行预测,然后将模型对样本的预测值与标签值相减并求平方,接下来将模型对每个样本的预测误差值累加,最后除以样本的个数得到平均损失值。
def loss_funtion(X, y):
total_loss = 0
# 数据集中样本的个数
n_samples = len(X)
# 依次取出每一个数据中的每一个样本
for i in range(n_samples):
xi = X[i]
yi = y[i]
# 使用模型根据样本特征值进行预测
yi_hat = model(xi)
# 计算模型预测值与标签值的差值的平方
total_loss += (yi_hat - yi) ** 2
# 对于给定数据集,计算模型预测的平均损失值
avg_loss = (1 / n_samples) * total_loss
return avg_loss
定义好损失函数以后,分别使用其计算出训练好的线性回归模型在训练集与测试集上的平均损失值,如以下代码所示。
train_loss = loss_funtion(X_train, y_train)
test_loss = loss_funtion(X_test, y_test)
运行以上代码,可以得出,模型在训练集上的平均损失值为95.2,在测试集上的平均损失值为96.2,说明模型在训练集与测试集上的效果都比较好。
在1.4.2节中,我们将构建另一个较复杂的线性回归模型来拟合训练集数据。介绍这个模型的构建与训练主要为了讲解过拟合现象,在后续模型的学习中我们会经常遇到过拟合现象,所有在本章中通过人为制造过拟合现象的方式可以让我们更加深入理解过拟合现象出现的原因,以及知道如何对过拟合现象进行处理。
1.4.2 复杂线性回归模型的构建
这个复杂线性回归模型的表达式为。其中,与分别表示样本的特征值与线性回归模型对样本的预测值,和为模型的参数。
为了找到模型的最优参数值和,需要使用梯度下降算法对这个线性回归模型进行训练,因此需要构建这个模型在训练时使用的损失函数。这个模型的损失函数如下。
其中,为训练集样本个数,与分别为模型对第个样本的预测值与样本的标签值。为了应用梯度下降算法求让损失函数取最小值的参数值,需要对模型参数和求梯度,参数、的梯度分别为如下。
求出损失函数对线性回归模型中参数的梯度值后,再对参数进行初始化,然后就可以使用合适的学习率来利用梯度下降算法对模型参数进行多次迭代更新,直到找到模型的最优参数值、。
由此就可以利用计算出的梯度值对模型的参数值进行更新。首先将模型的参数值进行初始化,然后利用梯度下降算法对模型的参数进行多次迭代更新,更新的方式与上文中的方式一致,在这里就不赘述了,如以下代码所示。
import numpy as np
# 把模型的参数w与b进行随机初始化
w = np.random.rand(2)
b = 1.1
# 指定学习率的值
lr = 1e-6
# 指定模型使用梯度下降算法迭代更新参数的次数
epochs = 50000
# 构建复杂线性回归模型
def model(x):
y_hat = w[0]*x + w[1]*(x**2) + b
return y_hat
# 使用梯度下降算法更新模型参数
for epoch in range(epochs):
sum_w = np.zeros(2)
sum_b = 0.0
for i in range(n_train):
xi = X_train[i]
yi = y_train[i]
yi_hat = model(xi)
sum_w[0] += (yi_hat - yi) * xi
sum_w[1] += (yi_hat - yi) * (xi**2)
sum_b += (yi_hat - yi)
grad_w = (2.0 / n_train) * sum_w
grad_b = (2.0 / n_train) * sum_b
w = w - lr * grad_w
b = b - lr * grad_b
将这个复杂线性回归模型训练好了以后,调用之前定义的plots
函数在一张图中同时画出数据中的样本与复杂线性回归模型的图形,如以下代码所示。
plots(w, b, X, y)
可视化的结果如图1.3所示,可以很明显地看出,这个复杂的线性回归模型对数据中的部分数据没能很好地拟合。
接下来,使用上一节中定义的损失函数来分别查看模型在训练集与测试集上的平均损失值,如以下代码所示。
train_loss = loss_funtion(X_train, y_train)
test_loss = loss_funtion(X_test, y_test)
运行以上代码可以得到,在训练集上模型的平均损失值为230.6,在测试集上模型的平均损失值为1 705.6。可以发现,模型在测试集上得到的平均损失值比训练集上的平均损失值大很多。
模型在测试集上的效果比在训练集上的效果差很多的这种现象称为过拟合。过拟合在模型的训练过程中经常发生,尤其在模型较复杂的情况下,如对于本节使用的数据集来说,线性回归模型比较复杂,因此出现了较严重的过拟合现象。
图1.3 复杂线性回归模型的可视化
1.4.3 使用正则项防止过拟合
这个复杂线性回归模型经过训练以后,得到模型的权重值,。这个模型出现严重过拟合,除了模型较复杂以外,还因为模型的权重值较大。模型固定了以后,一种常用的防止模型出现过拟合的方法为在损失函数中加入正则项。
在机器学习中,通常在损失函数中加入正则项来防止过拟合现象的发生。正则项通过“惩罚”模型中值过大的权重,使得模型的权重值变小,从而有效防止过拟合现象的发生。
因为损失函数用来衡量模型对样本的预测值与实际值之间的差距,所以可使用梯度下降算法来找到合适的权重值,使得这些权重值能够让损失函数取得最小值。为了防止过拟合现象的出现,需要减小模型的权重值。所以可以把权重值放到损失函数中组成新的损失函数,这样在通过梯度下降算法来降低新的损失函数值的时候,就可以同时降低损失值与模型的权重值,一举两得。在损失函数中加入模型权重值后,组成的新的损失函数如下。
其中,称为L2正则(L2-regularization)项。常数为权衡模型损失值与模型权重值的重要程度的一个超参数。模型的超参数为在模型进行训练之前需要人为设定的值,这个值在模型训练过程中保持不变。如果希望新的损失函数在训练的时候将模型参数值降低得多一些,就设置为较大的值,如1 000;如果希望在训练的时候主要降低模型的预测损失值,就设置为较小的值,如0.1。
构建了这个新的损失函数以后,同样利用梯度下降算法来求使得损失函数取最小值时的权重值。首先求模型参数的梯度值,对新的损失函数中权重求梯度的方法如下所示。
同理,对其他权重求偏导数与求 的偏导数类似。但是因为防止过拟合时一般不考虑参数,所以对参数求梯度的公式与之前的一样。
掌握了在损失函数中加入正则项来防止过拟合的原理以后,将其应用到实际的代码中。首先对模型的参数进行随机初始化,将模型的权重使用随机函数进行初始化,将偏移项的值随机初始化为1.1。指定好模型在使用梯度下降算法进行训练时需要使用的学习率的值与模型迭代训练的次数以后,定义与上文一样的复杂线性回归模型。实现方式如以下代码所示。
import numpy as np
w = np.random.rand(2)
b = 1.1
lr = 1e-6
epochs = 10000000
# 定义复杂线性回归模型
def model(x):
y_hat = w[0]*x + w[1]*(x**2) + b
return y_hat
接下来在损失函数中加入正则项,利用梯度下降算法对模型的参数进行更新。在加入了正则项以后,在梯度下降算法中唯一需要改动的地方为在求权重对损失函数的梯度时,需要加入的值。其余部分均保持不变,如以下代码所示。
# 指定正则项中lambda的值
reg = 10000
for epoch in range(epochs):
sum_w = np.zeros(2)
sum_b = 0.0
for i in range(n_train):
xi = X_train[i]
yi = y_train[i]
yi_hat = model(xi)
sum_w[0] += (yi_hat - yi) * xi
sum_w[1] += (yi_hat - yi) * (xi**2)
sum_b += (yi_hat - yi)
# 正则项在梯度下降算法中的应用
grad_w = (2.0 / n_train) * sum_w + (2.0 * reg * w)
grad_b = (2.0 / n_train) * sum_b
w = w - lr * grad_w
b = b - lr * grad_b
在使用正则项来防止模型在训练过程中出现过拟合的现象以后,这个模型分别在训练集与测试集上的平均预测损失值可以通过之前定义的loss_function
函数得到,如以下代码所示。
train_loss = loss_funtion(X_train, y_train)
test_loss = loss_funtion(X_test, y_test)
运行以上代码,得到模型在训练集上的平均预测损失值为348.3,在测试集上的平均预测损失值为490.4。当应用正则项来防止过拟合现象发生时,模型在测试集上的平均预测损失值为1 705.6,过拟合现象得到了很大程度的缓解。最重要的是,使用了正则项以后,模型经过训练以后的权重值,相对,于之前没有使用正则项时训练后得到的权重值,它减小了很多。正是权重值的减小,有效地降低了过拟合现象发生的概率。