线性回归
回归分析
回归分析是用来评估变量之间关系的统计过程。用来解释自变量X与因变量Y的关系。即当自变量X发生改变时,因变量Y会如何发生改变。
线性回归
回归分析的一种,评估的自变量X与因变量Y之间是一种线性关系。当只有一个自变量时,称为一元线性回归,当具有多个自变量时,称为多元线性回归。
线性关系的理解:
- 画出来的图像是直的。
- 每个自变量的最高次项为1。
拟合
拟合,是指构建一种算法(数学函数),使得该算法能够符合真实的数据。从机器学习角度讲,线性回归就是要构建一个线性函数,使得该函数与目标值之间的拟合性最好。从空间的角度来看,就是要让函数的直线(面),尽可能穿过空间中的数据点。线性回归会输出一个连续值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BpVCf85j-1589124267012)(images/01.png “线性回归”)]
一元线性回归
我们从简单的一元线性回归开始。这里,我们以房屋面积(x)与房屋价格(y)为例,显而易见,二者是一种线性关系,房屋价格正比于房屋面积,我们假设比例为
w * xy^=w∗x
然而,这种线性方程一定是过原点的,即当x为0时,y也一定为0。这可能并不符合现实中某些场景。为了能够让方程具有更广泛的适应性,我们这里再增加一个截距,设为b,即之前的方程变为:
y^=w∗x+b
而以上方程,就是我们数据建模的模型。方程中的w与b,就是模型的参数。
假定数据集如下:
房屋面积 | 房屋价格 |
30 | 100 |
40 | 120 |
40 | 115 |
50 | 130 |
50 | 132 |
60 | 147 |
线性回归是用来解释自变量与因变量之间的关系,但是,这种关系并非严格的函数映射关系。从数据集中,我们也看到了这一点。相同面积的房屋,价格并不完全相同,但是,也不会相差过大。
解决方法
我们现在的目的就是,从现有的数据(经验)中,去学习(确定)w与b的值。一旦w与b的值确定,我们就能够确定拟合数据的线性方程,这样就可以对未知的数据x(房屋面积)进行预测y(房屋价格)。
多元线性回归
然而,现实中的数据可能是比较复杂的,自变量也很可能不只一个。例如,影响房屋价格也很可能不只房屋面积一个因素,可能还有距地铁距离,距市中心距离,房间数量,房屋所在层数,房屋建筑年代等诸多因素。不过,这些因素,对房屋价格影响的力度(权重)是不同的,例如,房屋所在层数对房屋价格的影响就远不及房屋面积,因此,我们可以使用多个权重来表示多个因素与房屋价格的关系:
说明:在机器学习中,习惯用上标表示样本,用下标表示特征。
多元线性回归在空间中,可以表示为一个超平面,去拟合空间中的数据点。
损失函数
通过之前的介绍,我们得知,对机器学习来讲,就是从已知数据(经验)去建立一个模型,使得该模型能够对未知的数据进行预测。实际上,机器学习的过程,就是确定(学习)模型参数(即模型的权重与偏置)的过程,因为只要模型的参数确定了,我们就可以利用模型进行预测(有参数模型)。
那么,模型的参数该如果求解呢?对于监督学习来说,我们可以通过建立损失函数来实现。损失函数,也称目标函数或代价函数,简单的说,就是关于误差的一个函数。损失函数用来衡量模型预测值与真实值之间的差异。机器学习的目标,就是要建立一个损失函数,使得该函数的值最小。
也就是说,损失函数是一个关于模型参数的函数(以模型参数作为自变量的函数),自变量可能的取值组合通常是无限的,我们的目标,就是要在众多可能的组合中,找到一组最合适的自变量组合(值),使得损失函数的值最小。
损失函数我们习惯使用J来表示,例如,J(w)则表示以w为自变量的函数。
损失函数分类
损失函数有很多种,常见的损失函数为:
- 平方和损失函数
- 交叉熵损失函数
参数求解
误差与分布
接下来,我们来看一下线性回归模型中的误差。正如我们之前所提及的,线性回归解释的变量(现实中存在的样本),是存在线性关系的。然而,这种关系并不是严格的函数映射关系,但是,我们构建的模型(方程)却是严格的函数映射关系的,因此,对于每个样本来说,我们拟合的结果会与真实值之间存在一定的误差,我们可以将误差表示为:
# 中心极限定理 # 如果随机变量X(x1, x2, x3……)是独立同分布的,则变量之间的和 # 是服从正态分布的。 # 玩骰子 三粒 取值 3 - 18 import numpy as np import pandas as pd result = [] for i in range(10000): result.append(np.sum(np.random.randint(1, 7, size=3))) s = pd.Series(result) s.plot(kind="kde")
最大似然估计
以上是一个样本的概率密度,我们假设数据集中共有m个样本,每个样本具有n个特征,则多个样本的联合密度函数(似然函数)为:
根据最大似然估计,我们让所有样本出现的联合概率最大,则此时参数w的值,就是我们要求解的值。不过,累计乘积的方式不利于求解,我们这里使用对数似然函数,即在联合密度函数上取对数操作(取对数操作不会改变函数的极值点),这样就可以将累计乘积转换为累计求和的形式。
上式中,前半部分都是常数,我们的目的是为了让联合密度概率值最大,因此,我们只需要让后半部分的值最小即可,因此,后半部分,就可以作为模型的损失函数。该函数是二次函数,具有唯一极小值。
最小二乘法
最小二乘法,即最小平方法(也许这样称呼或许更合理),通过让样本数据的预测值与真实值之间的误差平方和最小,进而求解参数的方法。
一方面,我们通过极大似然估计,寻找出目标函数,不过,我们也可以直观的进行分析。从简单的角度看,我们的目的,其实就是要寻找一条合适的直线(平面),使得所有样本距离直线(平面)的距离,达到最小化即可。因此,我们可以采用每个样本的预测值与真实值做差,然后取平方和的方式,能够让该平方和最小的w,就是我们需要求解的w)。思考:为什么要用平方和,而不是直接求和,或者绝对值求和?
因此:
我们要求该目标函数的最小值,只需要对自变量w进行求导,导数为0时w的值,就是我们要求解的值。
根据矩阵与向量的求导公式,有:
特别的,如果A=AT(A为对称矩阵),则:
令导函数的值为0,则:
以上的求解方式,就是最小二乘法。使用最小二乘法求解的时候,要求矩阵XTX必须是可逆的。
一元线性回归程序示例
接下来,我们来实现一元线性回归。
# sklearn 命名惯例: # 矩阵 使用大写字母 # 向量 使用小写字母 # 所有模型的拟合(训练)方法都叫fit。 # 所有模型的测试方法都叫predict。 import numpy as np # LinearRegression 线性回归的类,我们可以使用该类实现线性回归。 # 底层就是使用最小二乘公式来进行求解的。 from sklearn.linear_model import LinearRegression # 该方法可以用来对数据集进行切分,分为训练集与测试集。训练集用于训练模型(求解出模型的参数w与b), # 测试集用来验证我们建立模型的效果如何。 from sklearn.model_selection import train_test_split # 设置随机种子。 np.random.seed(0) x = np.linspace(-10, 10, 1000) # 将x转换为二维的格式。之所以要将x转换为二维矩阵的格式,是因为稍后的模型fit方法中,第一个 # 参数(X)需要是二维的格式。 x = x.reshape(-1, 1) # 自行构建一个方程,生成y值。 y = 0.85 * x - 0.72 # 生成正态分布的噪音。 e = np.random.normal(scale=1.5, size=x.shape) # 令y值加入噪音,这样,更符合现实中数据的情况。(并不是完美的函数映射关系。) y += e # 创建线性回归类的对象。 lr = LinearRegression() # 将样本数据切分为训练集与测试集。 # 该函数接收若干个数组,将每个数组切分为两部分,并返回。 # 相关参数: # shuffle 是否进行洗牌,默认为True。 # random_state: 洗牌时候所使用的随机种子,相同的随机种子,一定可以产生相同的洗牌序列。 # test_size: 测试集数据所占有的比例。 train_X, test_X, train_y, test_y = train_test_split(x, y, test_size=0.25, random_state=0) # 拟合数据,即使用训练数据(X与y)去训练模型。实际上,拟合(训练)就是求解模型的参数(w与b)。 lr.fit(train_X, train_y) # coef_ 返回模型训练好的权重w值。 print(f"权重:{lr.coef_}") # intercept_ 返回模型训练好的截距(偏置)b。 print(f"截距:{lr.intercept_}") # 测试方法。模型经过拟合训练后,就可以得到(求解出)w与b。一旦w与b确定,模型就能够进行预测。 # 根据参数传递过来的X,返回预测的结果(目标值)y_hat。 y_hat = lr.predict(test_X) print(f"实际值:{test_y.ravel()[:10]}") print(f"预测值:{y_hat.ravel()[:10]}")
import matplotlib as mpl import matplotlib.pyplot as plt mpl.rcParams["font.family"] = "SimHei" mpl.rcParams["axes.unicode_minus"] = False # 绘制数据集样本。 plt.scatter(x, y, s=2) # 绘制线性回归的拟合直线。即模型y = w * x + b的那条线。也就是 y = 0.84850287 * x + -0.78380631 plt.plot(x, lr.predict(x), "r-")
线性回归模型评估
当我们建立好模型后,模型的效果如何呢?我们可以采用如下的指标来进行衡量。
- MSE
- RMSE
- MAE
- R2
MSE
MSE(Mean Squared Error),平均平方误差,为所有样本误差(真实值与预测值之差)的平方和,然后取均值。
从公式定义可知,最理想情况,所有的样本的预测值与真实值相同,即RSS为0,此时RR2为1。
# sklearn.metrics 该模块提供很多模型度量(评估)的方法。 # mean_squared_error MSE # mean_absolute_error MAE # r2_score 求R ^ 2 from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score # y_true 标签的真实值(所有样本) # y_pred 标签的预测值(所有样本) mse = mean_squared_error(y_true=test_y, y_pred=y_hat) print(f"均方误差(MSE):{mse}") rmse = np.sqrt(mse) print(f"均方根误差(RMSE):{rmse}") mae = mean_absolute_error(y_true=test_y, y_pred=y_hat) print(f"平均绝对值误差(MAE):{mae}") # 我们也可以自行计算MSE,RMSE,MAE。 # print(np.mean((test_y - y_hat) ** 2)) # 计算模型的评分制。 # 从结果可以看出,通过R^2进行评估,可以比其他几种方式更加直观。 r2 = r2_score(y_true=test_y, y_pred=y_hat) print(f"R^2的值(测试集):{r2:.3f}") # 以上指标(MSE, RMSE, MAE, R^2)也可以在训练集上进行评估。 r2_train = r2_score(y_true=train_y, y_pred=lr.predict(train_X)) print(f"R^2的值(训练集):{r2_train:.3f}") # R^ 2求解的另外一种方式,可以使用模型的score方法。 # 总结:R ^ 2可以采用两种方式进行求解。 # 1 r2_score # 2 模型对象.socre方法。 # 注意:尽管两个方法都能够求解R ^ 2值,但是,两个方法传递的参数意义是不同的。 # 对于r2_score,需要传递的是真实值(标签)与预测值(标签)。 # 对于lr.score,需要传递的是样本数据(X)与对应的真实标签。 train_score = lr.score(train_X, train_y) test_score = lr.score(test_X, test_y) print(f"测试集R^2:{test_score:.3f}") print(f"训练集R^2:{train_score:.3f}")
多元线性回归程序示例
类似的,我们也可以实现多元线性回归。这里,我们需要创建多个特征(x),我们也可以像之前程序那样,随机生成多个特征,不过,这里,我们使用sklearn库提供的更方面的方法。
make_regression(n_samples=5, n_features=2, coef=False, bias=5.5, random_state=0, noise=10) • 1
from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error from sklearn.model_selection import train_test_split # 可以生成用于回归分析的样本数据与对应的标签。 from sklearn.datasets import make_regression # 参数说明: # n_samples:生成的样本数量。 # n_features:生成的特征数量。(有几个x) # coef:是否返回方程的权重(w)。如果该值为True,则返回X,y与coef。 # 如果该值为False,仅返回X与y。默认为False。 # bias:偏置值(截距),也就是方程中的b。 # random_state:设置随机种子。 # noise:噪声的值。该值会影响数据的波动情况。值越大,波动性越大。 X, y, coef = make_regression(n_samples=1000, n_features=2, coef=True, bias=5.5, random_state=0, noise=10) train_X, test_X, train_y, test_y = train_test_split(X, y, test_size=0.25, random_state=0) print(f"实际权重:{coef}") lr = LinearRegression() lr.fit(train_X, train_y) print(f"权重:{lr.coef_}") print(f"截距:{lr.intercept_}") y_hat = lr.predict(test_X) print(f"均方误差:{mean_squared_error(test_y, y_hat)}") print(f"训练集R^2:{lr.score(train_X, train_y)}") print(f"测试集R^2:{lr.score(test_X, test_y)}")
# meshgrid的演示 x1 = np.array([2, 3, 4]) x2 = np.array([2, 3, 4, 5]) X1, X2 = np.meshgrid(x1, x2) display(X1) display(X2)
import matplotlib as mpl import matplotlib.pyplot as plt mpl.rcParams["font.family"] = "SimHei" mpl.rcParams["axes.unicode_minus"] = False #Axes3D 用来绘制3D图形的类。 from mpl_toolkits.mplot3d import Axes3D # 设置绘图后,以弹出框的形式显示。因为嵌入显示(默认)的形式,图像为一张图片, # 无法进行拖动而转换角度。这在三维图中不利于观察。 %matplotlib qt # 分别返回x1与x2两个特征的最大值。 max1, max2 = np.max(X, axis=0) # 分别返回x1与x2两个特征的最小值。 min1, min2 = np.min(X, axis=0) # 在x1轴的区间上取若干点。 x1 = np.linspace(min1, max1, 30) # 在x2轴的区间上取若干点。 x2 = np.linspace(min2, max2, 30) # 接收两个一维数组。进行网格扩展。过程:将x1沿着行进行扩展,扩展的行数与x2含有的元素 # 个数相同。将x2沿着列进行扩展,扩展的列数与x1含有的元素个数相同。返回扩展之后的X1与X2(并非 # 就地修改)。X1与X2的形状(shape)是相同的。 # 扩展的目的:依次对位取出X1与X2中的每个元素,就能够形成x1与x2(原始的一维数组x1与x2)中任意 # 两个元素的组合,进而就能够形成网格的结构。 X1, X2 = np.meshgrid(x1, x2) # 创建一个画布对象,用于绘制图像。 fig = plt.figure() # 创建Axes3D对象,用于绘制3D图。参数指定画布对象,表示要在哪个 # 画布上进行绘制。(要绘制图像,就不能离开画布对象的支持。) ax = Axes3D(fig) # 绘制3D散点图。 ax.scatter(X[:, 0], X[:, 1], y, color="b") # 绘制3D空间平面(曲面)。预测的y_hat值需要与X1或X2的形状相同。 # rstride 在行(row)的方向上前进的距离。 # cstride 在列(column)的方向上前进的距离。 # 距离越大,网格越宽。 # cmap colormap,指定绘制的风格。可以通过plt.cm.颜色图对象进行指定。 # alpha 指定透明度。 surf = ax.plot_surface(X1, X2, lr.predict(np.array([X1.ravel(), X2.ravel()]).T).reshape(X1.shape), rstride=5, cstride=5, cmap=plt.cm.rainbow, alpha=0.5) # 显示颜色条。可以观察到每种颜色所对应的值。参数指定为那个图像对象所设定。 fig.colorbar(surf) plt.show()
很多人都想学人工智能,它比黑客技术更加神秘更加吸引人。但是很多人都不敢学,因为传统教材的门槛都很高——需要编程,高数,统计学等等知识。所以我才创建了这个教学网站,力求通俗易懂,接地气,无需高数,只要你上过高中,就可以学!而且,传统教学很枯燥,即使老师讲得很易懂但依然很少人能坚持学下去,因为太枯燥了。所以我主张快乐学习,尽量加入搞笑的成分在教学里面,其实我生活中就很乐观幽默,歌手李健就是我性格的一个写照吧,只不过我能说黄段子他不能。
我希望通过这个网站使越来越多的中国人掌握人工智能技术,人工智能是中国的一次机遇,国家已经认识到了这一点并将人工智能提升到了国家战略级别。发展好了人工智能,那我们就有可能超越美国;人工智能落后,我们就可能会再次被八国联军欺辱,大刀对火枪的屈辱历史又会重演。我希望为我的民族为我的同胞出一份力。我也希望有缘看到这篇文章的你也为我们的民族为我们的同胞出一份力。
网站里将我十多年积累的知识都教给大家了,而且每小节后面还提供了实战编程代码。从网站毕业后,找份人工智能方面的工作不是问题,有天赋的更可以开发出各种颇具经济价值的人工智能程序。
在开始学习前,你可能会担心自己没有基础,担心自己数学不好,担心自己编程功底差等等。不用担心!我会用最通俗易懂的描述让你理解那些所需的数学知识以及教会你相关的编程技巧!
朋友们,让我们向着人工智能领域出发吧!