黄佳 《零基础学机器学习》 chap3笔记
第3课 线性回归——预测网店的销售额
文章目录
- 黄佳 《零基础学机器学习》 chap3笔记
- 第3课 线性回归——预测网店的销售额
- 3.1 问题定义:小冰的网店广告该如何投放
- 3.2 数据的收集和预处理
- 3.2.1 收集网店销售额数据
- 3.2.2 数据读取和可视化
- 3.2.3 数据的相关分析
- 3.2.4 数据的散点图
- 3.2.5 数据集清洗和规范化
- 数据清洗
- 数据规范化
- 3.2.6 拆分数据集为训练集和测试集
- 3.2.7 把数据归一化
- 3.3 选择机器学习模型
- 3.3.1 确定线性回归模型
- 3.3.2 假设(预测)函数--h(x)
- 3.3.3 损失(误差)函数--L(w,b)
- 机器学习常用的一些损失函数
- 均方误差函数的实现过程MSE(Mean Square Error)
- 损失函数L说明
- 3.4 通过梯度下降找到最佳参数
- 3.4.1 训练机器要有正确的方向
- 3.4.2 凸函数确保有最小损失点
- 3.4.3 梯度下降的实现
- 3.4.4 学习速率也很重要
- 3.5 实现一元线性回归模型并调试超参数
- 3.5.1 权重和偏置的初始值
- 3.5.2 进行梯度下降
- 3.5.3 调试学习速率
- 3.5.4 调试迭代次数
- 3.5.5 在测试集上进行测试
- 3.5.6 用轮廓图描绘L、w和b的关系
- 3.6 实现多元线性回归模型
- 3.7 本课内容小结
- 3.8 练习题
- 1.调用sklearn的线性回归实现网店销量预测(只考虑微信)
- 2.波士顿房价预测(手写梯度下降和线性回归)
-
回顾chap1所学的机器学习的实战架构
-
本课重点
-
情景引入
-
明确定义所要解决的问题—网店销售额的预测。
-
在数据的收集和预处理环节,分5个小节完成数据的预处理工作,分别如下。
- 收集数据─需要小冰提供网店的相关记录。
- 将收集到的数据可视化,显示出来看一看。
- 做特征工程,使数据更容易被机器处理。
- 拆分数据集为训练集和测试集。
- 做特征缩放,把数据值压缩到比较小的区间。
-
选择机器学习模型的环节,其中有3个主要内容。
- 确定机器学习的算法—这里也就是线性回归算法。
- 确定线性回归算法的假设函数。
- 确定线性回归算法的损失函数。
-
通过梯度下降训练机器,确定模型内部参数的过程。
-
进行超参数调试和性能优化。
为了简化模型,上面的5个机器学习环节,将先用于实现单变量(仅有一个特征)的线性回归,在本课最后,还会扩展到多元线性回归。此处,先看看本课重点。
-
3.1 问题定义:小冰的网店广告该如何投放
-
小冰已经准备好了她的问题。这些问题都与广告投放金额和商品销售额有关,她希望通过机器学习算法找出答案。
- (1)各种广告和商品销售额的相关度如何?
- (2)各种广告和商品销售额之间体现出一种什么关系
- (3) 哪一种广告对于商品销售额的影响最大?
- (4)分配特定的广告投放金额,预测出未来的商品销售额。
-
机器学习算法正是通过分析已有的数据,发现两者之间的关系,也就是发现一个能由“此”推知“彼”的函数。本课通过回归分析来寻找这个函数。
-
所谓回归分析(regression analysis),是确定两种或两种以上变量间相互依赖的定量关系的一种统计分析方法,也就是研究当自变量x变化时,因变量y以何种形式在变化。在机器学习领域,回归应用于被预测对象具有连续值特征的情况(如客流量、降雨量、销售量等), 所以用它来解决小冰的这几个问题非常合适。
-
最基本的回归分析算法是线性回归,它是通过线性函数对变量间定量关系进行统计分析。比如,一个简单函数y=2x+1,就体现了一个一元 (只有一个自变量)的线性回归,其中2是斜率,1是y轴上的截距。
-
我们初学者常常见到的入门案例就是房价预测
不难理解,房屋的售价与某些因素呈现比较直接的线性关系,比如房屋面积越大,售价越高。如下图所示,线性函数对此例的拟合效果比较好。
- 在机器学习的线性回归分析中,如果只包括一个自变量(特征x) 和一个因变量(标签y),且两者的关系可用一条直线近似表示,这种回归分析就称为一元线性回归分析。如果回归分析中包括两个或两个以上的自变量,且因变量和自变量之间是线性关系,则称为多元线性回归分析 。
3.2 数据的收集和预处理
3.2.1 收集网店销售额数据
- 小冰把过去每周的广告投放金额和销售额数据整理成一个Excel表格(如下图所示),并保存为advertising.csv文件(这是以逗号为分隔符的一种文件格式,比较容易被Python读取)。基本上每周的各种广告投放金额和商品销售额都记录在案。
3.2.2 数据读取和可视化
import numpy as np #导入NumPy数学工具箱
import pandas as pd #导入Pandas数据处理工具箱
#读入数据并显示前面几行的内容,确保已经成功的读入数据
#注意路径 如当数据集和代码文件位于相同本地目录,路径名应为'./advertising.csv',或直接放'advertising.csv'亦可
df_ads = pd.read_csv('../input/text3adverse/advertising.csv')
df_ads.head()
-
这里的变量命名为df_ads,df代表这是一个Pandas Dataframe格式数据,ads是广告的缩写。
-
输出结果(如下图所示)显示数据已经成功地读入了Dataframe
3.2.3 数据的相关分析
-
然后对数据进行相关分析correlation analysis。相关分析后我们可以通过相关性系数了解数据集中任意一对变量(a,b)之间的相关性。相关性系数是一个-1~1的值,正值表示正相关,负值表示负相关。数值越大,相关性越强。
- 如果a和b的相关性系数是1,则a和b总是相等的。
- 如果a和b的相关性系数是0.9,则b会显著地随着a的变化而变化,而且变化的趋势保持一致。
- 如果a和b的相关性系数是0.3,则说明两者之间并没有什么明显的联系。
-
在Python中,相关分析用几行代码即可实现,并可以用热力图 (heatmap)的方式非常直观地展示出来:
import matplotlib.pyplot as plt import seaborn as sns #Seaborn – 统计学数据可视化工具库 #对所有的标签和特征两两显示其相关性的热力图(heatmap) sns.heatmap(df_ads.corr(), cmap="YlGnBu", annot = True) plt.show() #plt代表英文plot,就是画图的意思
-
运行代码之后,3个特征加一个标签共4组变量之间的相关性系数全部以矩阵形式显示,而且相关性越高,对应的颜色越深。此处相关性分析结果很明确地向我们显示—将有限的金钱投放到微信公众号里面做广告是最为合理的选择
3.2.4 数据的散点图
- 下面,通过散点图(scatter plot)两两一组显示商品销售额和各种广告投放金额之间的对应关系,来将重点聚焦。
- 散点图是回归分析中, 数据点在直角坐标系平面上的分布图,它是相当有效的数据可视化工具。
#显示销量和各种广告投放量的散点图
sns.pairplot(df_ads,
x_vars=['wechat', 'weibo', 'others'],
y_vars='sales',
height=4, aspect=1, kind='scatter')
plt.show()
代码运行之后输出的散点图清晰地展示出了销售额随各种广告投放金额而变化的大致趋势,根据这个信息,就可以选择合适的函数对数据点进行拟合。
3.2.5 数据集清洗和规范化
通过观察相关性和散点图,发现在本案例的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)
数据规范化
-
(200,)这种表述形式代表一个有200个样本数据为1阶的张量数组,也就是一个向量。
目前X数组中只有一个特征,张量的阶为1,那么这个1D的特征张量,是机器学习算法能够接受的格式吗?
-
对于回归问题的数值类型数据集,机器学习模型所读入的规范格式应该是2D张量,也就是矩阵,其形状为 (样本数,标签数)。其中的行是数据,而其中的列是特征。
-
那么就现在的特征张量X而言,则是要把它的形状从(200,)变成(200,1),然后再进行机器学习。
-
因此需要用 reshape方法给上面的张量变形:
X = X.reshape((len(X),1)) #通过reshape函数把向量转换为矩阵,len函数返回样本个数 y = y.reshape((len(y),1)) #通过reshape函数把向量转换为矩阵,len函数返回样本个数print ("张量X的阶:",X.ndim) print ("张量X的形状:", X.shape) print ("张量X的内容:", X)
-
现在数据格式从(200,)变成了(200,1)。尽管还是200个数字,但是数据的结构从一个1D数组变成了有行有列的矩阵。再次强调,对于常见的连续性数值数据集(也叫向量数据集),输入特征集是 2D矩阵,包含两个轴。
-
第一个轴是样本轴(NumPy里面索引为0),也叫作矩阵的行, 本例中一共200行数据。
-
第二个轴是特征轴(NumPy里面索引为1),也叫作矩阵的列, 本例中只有1个特征。 对于标签张量y,第二个轴的维度总是1,因为标签值只有一个。这里也可以把它转换为2阶张量。
-
3.2.6 拆分数据集为训练集和测试集
-
在开始建模之前,还需要把数据集拆分为两个部分:训练集和测试集。
- 在普通的机器学习项目中,至少要包含这两个数据集,一个用于训练机器,确定模型,另一个用于测试模型的准确性。
- 不仅如此,往往还需要一个验证集,以在最终测试之前增加验证环节。目前这个问题比较简单,数据量也少,我们简化了流程,合并了验证和测试环节。
-
这两个数据集需要随机分配,两者间不可以出现明显的差异性。因此,在拆分之前,要注意数据是否已经被排序或者分类,如果是,还要先进行打乱。
-
使用下面的代码段将数据集进行80%(训练集)和20%(测试集) 的分割:
#将数据集进行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)
- Sklearn中的train_test_split函数,是机器学习中拆分数据集的常用工具
- test_size=0.2,表示拆分出来的测试集占总样本量的20%。
- 用print语句输出拆分之后的新数据集(如X_train、 X_test)的内容,会发现这个工具已经为数据集进行了乱序(重新随机 排序)的工作,因为其中的shuffle参数默认值为True。
- 而其中的random_state参数,则用于数据集拆分过程的随机化设定。如果指定了一个整数,那么这个数叫作随机化种子,每次设定固定的种子能够保证得到同样的训练集和测试集,否则进行随机分割。
- test_size=0.2,表示拆分出来的测试集占总样本量的20%。
- Sklearn中的train_test_split函数,是机器学习中拆分数据集的常用工具
3.2.7 把数据归一化
- 第1课中曾经介绍过几种特征缩放的方法,包括示准化、数据的压缩(也叫归一化),以及规范化等。
- 特征缩放对于机器学习特别重要,可以让机器在读取数据的时候感觉更“舒服”,训练起来效率更高。
- 这里就对数据进行归一化。归一化是按比例的线性缩放。数据归一化之后,数据分布不变,但是都落入一个小的特定区间,比如0~1或者-1~+1,如图所示。
-
一个常见的归一化的公式如下
X ′ = X − min ( X ) max ( X ) − min ( X ) X^{\prime}=\frac{X-\min (X)}{\max (X)-\min (X)} X′=max(X)−min(X)X−min(X) -
通过Sklearn库中preprocessing(数据预处理)工具中的Min Max Scaler可以实现数据的归一化。
-
但是在此处,我们先用Python代码自己来手写定义一个归一化函数:
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_norm = (x_data - np.min(x_data))/(np.max(x_data)-np.min(x_data)).values
- 上面的代码中,特别需要注意的是归一化过程中的最大值(max)、最小值(min),以及最大值和最小值之间的差(gap),全都来自训练集。不能使用测试集中的数据信息进行特征缩放中间步骤 中任何值的计算。
举例来说,如果训练集中的广告投放金额最大值是 350,测试集中的广告投放金额最大值是380,尽管380大于350,但归一化函数还是要以350作为最大值,来处理训练集和测试集的所有数据。
- 为什么非要这样做呢?因为,在建立机器学习模型时,理论上测试集还没有出现,所以这个步骤一定要在拆分数据集之后进行。有很多人先对整个数据集进行特征缩放,然后拆分数据集,这种做法是不谨慎的,会把测试集中的部分信息泄露到机器学习的建模过程之中。下面的代码使用刚才定义的归一化函数对特征和标签进行归一化。
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 Ads') # x轴Label plt.ylabel('Sales') # y轴Label plt.legend() # 显示图例 plt.show() # 显示绘图结果
3.3 选择机器学习模型
-
机器学习模型的确立过程中有两个主要环节。
-
确定选用什么类型的模型。
-
确定模型的具体参数。
下面我们先聚焦于第一个问题
-
3.3.1 确定线性回归模型
-
其中,参数a的数学含义是直线的斜率(陡峭程度),b则是截距(与y轴相交的位置)。
-
在机器学习中,会稍微修改一下参数的代号,把模型表述为:y=wx+ b
-
此处,方程式中的a变成了w,在机器学习中,这个参数代表权重。因为在多元变量(多特征)的情况下,一个特征对应的w参数值越大,就表示权重越大。而参数b,在机器学习中称为偏置。
不要小看这个简单的线性函数,在后续的机器学习过程中,此函数会作为一个基本运算单元反复地发挥威力
其他很多资料写的 θ 0 、 θ 1 \theta_{0} 、 \theta_{1} θ0、θ1 和这里的 w 、 b 其实是一回事儿
使用 w 和 b 来表示这些参数会使它们的意义更清晰一些: weight是权重, bias是偏置, 各取首字母
-
3.3.2 假设(预测)函数–h(x)
-
先来看一个与线性函数稍有差别的方程式 : y’=wx+b
-
也可以写成 :h (x)=wx+b
-
其中,需要注意以下两点
-
y’指的是所预测出的标签,读作y帽(y-hat)或y撇。
-
h(x)就是机器学习所得到的函数模型,它能根据输入的特征进行标签的预测
- 我们把它称为假设函数,英文是hypothesis function(所以选用首字母h作为函数符号)
-
-
-
所以,机器学习的具体目标就是确定假设函数h(x),也就是要确定w和b
- 确定b,也就是y轴截距,这里称为偏置,有些机器学习文档中, 称它为 w 0 w_0 w0(或 θ 0 θ_0 θ0)。
- 确定w,也就是斜率,这里称为特征x的权重,有些机器学习文档 中,称它为 w 1 w_1 w1(或 θ 1 θ_1 θ1)。 一旦找到了参数w和b的值,整个函数模型也就被确定了。那么这些参数w和b的具体值怎么得到呢?👇
3.3.3 损失(误差)函数–L(w,b)
-
在继续寻找最优参数之前,需要先介绍损失和损失函数。
-
如果现在已经有了一个假设函数,就可以进行标签的预测了。那么,怎样才能够量化这个模型是不是足够好?
- 比如,一个模型是3x+5, 另一个是100x+1,怎样评估哪一个更好? 这里就需要引入**损失(loss)**这个概念
-
损失,是对糟糕预测的惩罚。损失也就是误差,也称为成本 (cost)或代价。名字虽多,但都是一个意思,也就是当前预测值和真实值之间的差距的体现。它是一个数值,表示对于单个样本而言模型预测的准确程度。
- 如果模型的预测完全准确,则损失为0
- 如果不准确, 就有损失。在机器学习中,我们追求的当然是比较小的损失
-
不过,模型好不好还不能仅看单个样本,而是要针对所有数据样本找到一组平均损失“较小”的函数模型。样本的损失的大小,从几何意义上基本上可以理解为y和y’之间的几何距离。平均距离越大,说明误差越大,模型越离谱。如下图所示,左边模型所有数据点的平均损失很明显大过右边模型。
-
因此,针对每一组不同的参数,机器都会针对样本数据集算一次平均损失。计算平均损失是每一个机器学习项目的必要环节。
-
损失函数(loss function) L ( w , b ) L(w, b) L(w,b) 就是用来计算平均损失的
-
有些地方把损失函数记作 J ( θ ) J(\theta) J(θ), 也叫代价函数、成本函数 ( cost function)。刚才说过, θ \theta θ 就是 w 和 b, J ( θ ) J(\theta) J(θ) 就是 L ( w , b ) L(w, b) L(w,b), 符号有别, 但意思相同。
-
这里要强调一下: 损失函数 L 是参数 w 和 b 的函数, 不是针对 x 的函 数。我们会有一种思维定势, 总觉得函数一定是表示 x 和 y 之间的关系。
-
现在需要换一个角度去思考问题, 暂时忘掉 x 和 y, 聚焦于参数。对于一个给定的数据集来说, 所有的特征和标签都是已经确定的, 那么此时损失值的大小就只随着参数 w 和 b 而变。也就是说, 现在 x 和 y 不再是变 量, 而是定值, 而 w 和 b 在损失函数中成为了变量。
这里书上讲的真棒哇!
-
-
计算数据集的平均损失非常重要,简而言之就是:如果平均损失小,参数就好;如果平均损失大,模型或者参数就还要继续调整。 这个计算当前假设函数所造成的损失的过程,就是前面提到过的模型内部参数的评估的过程。
机器学习常用的一些损失函数
机器学习中的损失函数很多,主要包括以下几种。
-
用于回归的损失函数
- 均方误差(Mean Square Error, MSE)函数
- 也叫平方损失或L2损失函数误差
- 平均绝对误差(Mean Absolute Error, MAE)函数
- 也叫L1损失函数
- 平均偏差误差(mean bias error)函数
- 均方误差(Mean Square Error, MSE)函数
-
用于分类的损失函数
- 交叉熵损失(cross_entropy loss)函数
- 多分类SVM损失(hinge loss)函数
均方误差函数的实现过程MSE(Mean Square Error)
-
一般来说,选择最常用的损失函数就可以达到评估参数的目的。下面给出线性回归模型的常用损失函数—均方误差函数的实现过程。
-
首先,对于每一个样本,其预测值和真实值的差异为 ( y − y ′ ) (y−y') (y−y′), 而 y ′ = w x + b y'=wx+b y′=wx+b,所以损失值与参数w和b有关。
-
如果将损失值 ( y − y ′ ) (y−y') (y−y′)夸张一下,进行平方(平方之后原来有正 有负的数值就都变成正数),就变成 ( y − y ′ ) ² (y−y')² (y−y′)²。我们把这个值叫作单个样本的平方损失。
-
然后,需要把所有样本(如本章示例一共记录了200周的数据, 即200个样本)的平方损失都相加,即
( y ( x ( 1 ) ) − y ′ ( x ( 1 ) ) ) 2 + ( y ( x ( 2 ) ) − y ′ ( x ( 2 ) ) ) 2 + … + ( y ( x ( 200 ) ) − y ′ ( x ( 200 ) ) ) 2 (y(x^{(1)})−y'(x^{(1)}))^2+ (y(x^{(2)})−y'(x^{(2)}))^2+…+\\(y(x^{(200)})−y'(x^{(200)}))^2 (y(x(1))−y′(x(1)))2+(y(x(2))−y′(x(2)))2+…+(y(x(200))−y′(x(200)))2此处公式里带小括号的上标 x ( 1 ) x^{(1)} x(1), 代表样本的维度索引,即整个数据集中的第几个样本
前面的 x 1 x_1 x1中的下标代表的是标签的维度索引,即第几个标签,这个例子目前只有一个标签,因此省略了下标 -
写成求和的形式就是
∑ ( x , y ) ∈ D ( y − h ( x ) ) 2 \sum_{(x, y) \in D}(y-h(x))^{2} (x,y)∈D∑(y−h(x))2
损失函数L说明
- 最后根据样本的数量求平均值,则损失函数 L L L为:
L ( w , b ) = M S E = 1 2 N ∑ ( x , y ) ∈ D ( y − h ( x ) ) 2 L(w, b)=M S E=\frac{1}{2 N} \sum_{(x, y) \in D}(y-h(x))^{2} L(w,b)=MSE=2N1(x,y)∈D∑(y−h(x))2
-
关于以上公式
- (x,y)为样本,x是特征(微信公众号广告投放金额),y是标签(销售额)。
- h (x)是假设函数wx+b,也就是y’。
- D指的是包含多个样本的数据集。
- N指的是样本数量(此例为200)。N前面还有常量2,是为了在求梯度的时候,抵消二次方后产生的系数,方便后续进行计算,同时增加的这个常量并不影响梯度下降的最效结果。
- L,对于一个给定的训练样本集而言,它是权重w和偏置b的函数,它的大小随着w和b的变化而变。
-
MSE函数可直接调用,但这里我们还是用Python手写定义一个MSE函数,并将其封装起来
- 使用MSE函数做损失函数的线性回归算法,有时被称为最小二乘法
def loss_function(X, y, weight, bias): # 手工定义一个MSE均方误差函数 y_hat = weight*X + bias # 这是假设函数,其中已经应用了Python的广播功能 loss = y_hat-y # 求出每一个y’和训练集中真实的y之间的差异 cost = np.sum(loss**2)/(2*len(X)) # 这是均方误差函数的代码实现 return cost # 返回当前模型的均方误差值
-
其中,利用了Python的广播功能以及向量化运算。
-
在
weight*X + bias
中,X是一个2D张量,共200行,1列,但是此处X可以直接与标量weight相乘,并与标量bias相加,之后仍然得到形状 为(200,1)的2D张量。- 在运行期间weight和bias自动复制自身,形成X 形状匹配的张量。这就是广播。
-
y_hat是上面广播计算的结果,形状与标签集y相同。若对当前张量形状有疑惑,可以通过shape方法输出其形状。
- y_hat可以和 y 进行直接的向量化的加减运算,不需要任何for循环参与。这种向量化运算既减少了代码量,又提高了运算效率。
-
损失函数代码中的loss或者cost,都代表当前模型的误差(或称为 损失、成本) 值。
-
np.sum(loss**2)/2*len(X)
是MSE函数的实现,其中包含以下内容。loss**2
代表对误差值进行平方。sum(loss**2)
是对张量所有元素求和。len(X)
则返回数据集大小,例如200。- 有了这个损失函数,我们就可以判断不同参数的优与劣了。MSE函数值越小越好,越大就说明误差越大。
-
下面随便设定了两组参数,看看其均方误差大小:
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))
当权重5,偏置3时,损失为: 12.796390970780058 当权重100,偏置1时,损失为: 1577.9592615030556
因此,线性函数y=3x+5相对于线性函数y=100x+1而言是更优的模型
3.4 通过梯度下降找到最佳参数
现在,数据集已读入张量,我们也选定了以线性回归作为机器学习模型,并且准备好了损失函数MSE,下面要正式开始训练机器
3.4.1 训练机器要有正确的方向
-
所谓训练机器,也称拟合的过程,也就是确定模型内部参数的过程。具体到线性模型,也就是确定y’=wx+b函数中的w和b。
-
那么怎样才能知道它们的最佳值呢?刚才我们随便设定了两组参数,(3,5)和 (100,1),通过损失函数来比较两组参数带来的误差,发现(3,5) 这一组参数好一些。对于这种简单的线性关系,数学功底强的人,通过 观察数据和直觉也许就能够给出比较好的参数值。
-
但机器没有直觉,只能通过算法减小损失。一个最简单无脑的算法是让计算机随机生成一万个w和b的不同组合,然后挨个计算损失函数, 最后确定其中损失最小的参数,并宣布:这是一万个组合里面的最优模型。这也是一种算法,也许结果还真不错。
-
如下图所示,每生成一组参数就通过假设函数求y’,然后计算损失,记录下来并更新参数,形成新的假设函数—这是一个不断循环的迭代过程
-
However 😎
漫无目的去猜测一万次,然后给出一个损失最小的模型,告诉别人说这个是我随机猜测一万次里面最好的结果,这实在
是谈不上任何‘智能’。如果机器是利用它们天文级的‘算力’做这样的事情,那简直太让人失望了。因此,**比较理想的情况是,每一次猜测都应该比上一次更好,更接近真相,也就是每次的损失都应该减小,而不是好一次,坏一次地乱猜。**好消息是,对于线性回归来说,有一种方法可以使猜测沿着正确的方向前进,因此总能找到比起上一次猜测时误差更小的w和b组合。这种方法就是针对损失函数的梯度下降(gradient descent)。
3.4.2 凸函数确保有最小损失点
回忆一下均方误差函数
L
(
w
,
b
)
=
M
S
E
=
1
2
N
∑
(
x
,
y
)
∈
D
(
y
−
h
(
x
)
)
2
L(w, b)=M S E=\frac{1}{2 N} \sum_{(x, y) \in D}(y-h(x))^{2}
L(w,b)=MSE=2N1(x,y)∈D∑(y−h(x))2
这是一个凸函数。凸函数的图像会流畅、连续地形成全局最低点,也就是说存在着全局最小损失点。这也是此处选择MSE作为线性回归的损失函数的原因。
这种“存在着底部最低点”的函数为梯度下降奠定了基础。不管再增加多少个维度(特征,也就是相应地增加参数w的个数),二次函数都是有最低点的。如果没有这个最低点,那么梯度下降到了一定程度,停是停了,但是根本没法判断此时的损失是不是最小的
3.4.3 梯度下降的实现
-
梯度下降的过程就是在程序中一点点变化参数w和b,使L,也就是损失值,逐渐趋近最低点(也称为机器学习中的最优解)。
- 这个过程经常用“下山”来比喻:想象你站在一座山的山腰上,正在寻找一条下山 的路,这时你环望四周,找到一个最低点并向那个方向迈出一步;接着再环望四周,朝最低点方向再迈出一步……一步接一步,走到最低点。 这里用图来详细解释比较清楚,为了简化说明,暂时只考虑权重w和损失L之间的关系。给w随机分配一个初始值(如5)的时候,损失曲线上对应的点就是下图中有小猴子的地方
- 这个过程经常用“下山”来比喻:想象你站在一座山的山腰上,正在寻找一条下山 的路,这时你环望四周,找到一个最低点并向那个方向迈出一步;接着再环望四周,朝最低点方向再迈出一步……一步接一步,走到最低点。 这里用图来详细解释比较清楚,为了简化说明,暂时只考虑权重w和损失L之间的关系。给w随机分配一个初始值(如5)的时候,损失曲线上对应的点就是下图中有小猴子的地方
-
秘密武器正是导数。导数描述了函数在某点附近的变化率(L正在随着w增大而增大还是减小),而这正是进一步猜测更好的权重时所需要的全部内容。
-
程序中用梯度下降法通过求导来计算损失曲线在起点处的梯度。此时,梯度就是损失曲线导数的矢量,它可以让我们了解哪个方向距离目标“更近”或“更远”
-
如果求导后梯度为正值,则说明L正在随着w增大而增大,应该减小w,以得到更小的损失。
-
如果求导后梯度为负值,则说明L正在随着w增大而减小,应该增大w,以得到更小的损失
此处在单个权重参数的情况下,损失相对于权重的梯度就称为导数;若考虑偏置,或存在多个权重参数时,损失相对于单个权重的梯度就称为偏导数
-
-
方向(也就是梯度的正负)、大小(也就是切线倾斜的幅度),这两个重要的特征,尤其是方向特征确保了梯度始终指向损失函数中增长最为迅猛的方向。梯度下降法会沿着负梯度的方向走一步,以降低损失,如图所示
-
公式
梯 度 = ∂ ∂ w L ( w ) = ∂ ∂ w 1 2 N ∑ ( X , y ) ∈ D ( y − h ( X ) ) 2 = 1 2 N ∑ ( X , y ) ∈ D ( y − ( w ⋅ X ) ) ⋅ X 也 可 以 写 成 梯度= \frac{\partial}{\partial w} L(w)=\frac{\partial}{\partial w} \frac{1}{2 N} \sum_{(X, y) \in D}(y-h(X))^{2}=\frac{1}{2 N} \sum_{(X, y) \in D}(y-(w \cdot X)) \cdot X 也可以写成\\ 梯度=∂w∂L(w)=∂w∂2N1(X,y)∈D∑(y−h(X))2=2N1(X,y)∈D∑(y−(w⋅X))⋅X也可以写成梯 度 = 1 2 N ∑ i = 1 N ( y ( i ) − ( w ⋅ X ( i ) ) ) ⋅ X ( i ) 梯度 =\frac{1}{2 N} \sum_{i=1}^{N}\left(y^{(i)}-\left(w \cdot X^{(i)}\right)\right) \cdot X^{(i)} 梯度=2N1i=1∑N(y(i)−(w⋅X(i)))⋅X(i)
-
此处的N是数据集的数目。符号 Σ \Sigma Σ代表对所有训练数据集中的特征和标签进行处理并求和,这是已经推导出来的求梯度的具体步骤。如果不熟悉导数(也就是对损失函数的微分)的演算也没有什么影响。因为梯度的计算过程都已经封装在各种机器学习框架中,并不用我们自己写代码实现。
-
而且即使要通过Python来实现梯度下降公式,代码同样是非常的简洁:
y_hat = weight*X + bias #向量化运算实现的假设函数 loss = y_hat - y #中间过程,求得的是假设函数预测的y'和真正的y值之间的差值 derivative_weight = X.T.dot(loss)/len(X) #对权重求导,len(X)是样本总数 derivative_bias = sum(loss)*1/len(X) #对偏置求导,len(X)是样本总数
weight*X+bias
是求出X数据集中的全部数据的y值,就是 w ⋅ x ( i ) w·x^{(i)} w⋅x(i)的实现,是对数组的整体操作,不用通过循环去分别操作每一个数据。对- weight求导的过程中,使用了上一课中介绍过的多项式点积规则—两个相同维度的向量对应元素先相乘,后相加。这其中的两个向量是X和loss,也就是 ( y ( i ) − ( w ⋅ x ( i ) ) ) ⋅ x ( i ) (y(i)- (w · x^{(i)}))\cdot x^{(i)} (y(i)−(w⋅x(i)))⋅x(i)的实现。
- 对偏置b求导并不需要与特征X相乘,因为偏置与权重不同,它与特征并不相关。
- 另外还有一种思路,是把偏置看作w,那么就需要给X特征矩阵添加一行数字1,形成 x 0 x_0 x0,与偏置相乘,同时确保偏置值不变
- 后面会在多变量线性回归的代码中试一下这个技巧。
-
-
3.4.4 学习速率也很重要
-
最关键的问题已经通过求导的方法解决了,我们知道了权重w应该往哪个方向走。
-
下一个问题是小猴子应该以多快的速度下山。这在机器学习中被称为学习速率(learning rate)的确定。学习速率也记作α。
-
学习速率乘以损失曲线求导之后的微分值,就是一次梯度变化的步长(step size)。它控制着当前梯度下降的节奏,或快或慢,w将在每一次迭代过程中被更新、优化。 引入学习速率之后,用数学语言描述参数w随梯度更新的公式如下:
w = w − α ⋅ ∂ ∂ w L ( w ) 即 w = w − α N ∑ i = 1 N ( y ( i ) − ( w ⋅ x ( i ) ) ) ⋅ x ( i ) w=w-\alpha \cdot \frac{\partial}{\partial w} L(w)\\\\即\\\\w=w-\frac{\alpha}{N} \sum_{i=1}^{N}\left(y^{(i)}-\left(w \cdot x^{(i)}\right)\right) \cdot x^{(i)} w=w−α⋅∂w∂L(w)即w=w−Nαi=1∑N(y(i)−(w⋅x(i)))⋅x(i)
weight = weight - alpha*derivative_wight # 结合学习速率alpha更新权重
bias = bias - alpha*derivative_bias # 结合学习速率alpha更新偏置
本课中,为了学习过程中的理解,给出了求导、梯度下降的实现、 损失函数的计算的细节。然而在实战中,这些内容基本不需要编程人员 自己写代码实现。而大多数机器学习从业者真正花费相当多的时间来调试的, 是像学习速率、迭代次数这样的参数,我们称这类位于模型外部 的人工可调节的参数为超参数。而权重w、偏置b,当然都是模型内部 参数,由梯度下降负责优化,不需要人工调整。
-
如果所选择的学习速率过小,机器就会花费很长的学习时间,需要迭代很多次才能到达损失函数的最底点,如下面左图所示。
相反,如果学习速率过大,导致L的变化过大,越过了损失曲线的最低点,则下一 个点将永远在U形曲线的底部随意弹跳,损失可能越来越大,如下面右图所示。在机器学习实战中,这种损失不仅不会随着迭代次数减小,反而会越来越大的情况时有发生。
-
最佳学习速率(如👇图所示)与具体问题相关。因为在不同问题中,损失函数的平坦程度不同。如果我们知道损失函数的梯度较小,则可以放心地试着采用更大的学习速率,以补偿较小的梯度并获得更大的步长
-
寻找最佳学习速率很考验经验和感觉。
一个常见的策略是,在机器学习刚刚开始的时候,学习速率可以设置得大一些,快速几步达到靠近 最佳权重的位置,当逐渐地接近最佳权重时,可以减小学习速率,防止 一下子越过最优值。
-
下面给出梯度下降的完整代码(已经封装在一个自定义的函数 gradient_descent中):
derivative_w和derivative_b那里动手算一下就彳亍
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 # 返回梯度下降过程数据
注意梯度下降的代码在程序中实现时,会被置入一个循环中,比如 下降50次、100次甚至10000次,调试程序时,需要观察损失曲线是否已经开始收敛。具体迭代多少次合适,和学习速率一样,需要具体问题具体分析,还需要根据程序运行情况及时调整,这是在下一小节中即将详细介绍的内容
3.5 实现一元线性回归模型并调试超参数
3.5.1 权重和偏置的初始值
-
在线性回归中,权重和偏置的初始值的选择可以是随机的,这对结果的影响不大,因为我们知道无论怎么选择,梯度下降总会带领机器 “走”到最优结果(差别只是步数的多少而已)
-
通过下面的代码设置初始参数值:
# 首先确定参数的初始值 iterations = 200; # 迭代250次 alpha = 2; weight = -5 # 权重 bias = 3 # 偏置 # 计算一下初始权重和偏置值所带来的损失 print ('当前损失:',loss_function(X_train, y_train, weight, bias))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xSeO87t1-1670399339437)(https://cdn.jsdelivr.net/gh/xin007-kong/picture_new/img/20221206021234.png)]
-
下面画出当前回归函数的图像:
# 绘制当前的函数模型 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 Ads') # X轴Label plt.ylabel('Sales') # y轴Label plt.legend() # 显示图例 plt.show() # 显示绘图
- 接下来看看梯度下降怎样把它纠正回来
3.5.2 进行梯度下降
-
下面就基于这个平均损失比较大的初始参数值,进行梯度下降,也就是开始训练机器,拟合函数。调用刚才已经定义好的梯度下降函数 gradient_descent,并迭代100次(在上一节参数初始化的代码中已设定),也就是下100级台阶
# 根据初始参数值,进行梯度下降,也就是开始训练机器,拟合函数 loss_history, weight_history, bias_history = gradient_descent( X_train, y_train, weight, bias, alpha, iterations)
-
在训练机器的过程中,已经通过变量loss_history记录了每一次迭代的损失值。下面把损失大小和迭代次数的关系通过函数图像显示出来, 看看损失是不是如同所预期的那样,随着梯度下降而逐渐减小并趋近最佳状态。通过下面的代码绘制损失曲线
plt.plot(loss_history,'g--',label='Loss Curve') plt.xlabel('Iterations') # x轴Label plt.ylabel('Loss') # y轴Label plt.legend() # 显示图例 plt.show() # 显示损失曲线
损失达到了惊人的数量级!
-
此时去看一下拟合效果,必然也是很离谱
# 绘制当前的函数模型 plt.plot(X_train, y_train,'r.', label='Training data') # 显示训练集散点图 line_X = np.linspace(X_train.min(), X_train.max(), 500) # X值域 # 关于weight_history[-1],这里的索引[-1],就代表迭代500次后的最后一个W值 line_y = [weight_history[-1]*xx + bias_history[-1] for xx in line_X] # 假设函数 plt.plot(line_X,line_y,'b--', label='Current hypothesis' ) # 显示当前函数 plt.xlabel('Wechat Ads') # x轴Label plt.ylabel('Sales') # y轴Label plt.legend() # 显示图例 plt.show() # 显示函数图像
-
这个数据集比较简单,没有什么潜在的数据问题。而且模型也 比较简单,如果损失函数、梯度下降代码和求导过程都没有出现错误的 话,那么此处基本上可以确定,问题出在学习速率α的设定方面。”
-
下一步就调试学习率
3.5.3 调试学习速率
-
之前设置的是2,调成1试试
# 首先确定参数的初始值 iterations = 200; # 迭代250次 alpha = 1 ; # 此处初始学习速率设为0.5, 如果调整为1,你会看到不同的结果 weight = -5 # 权重 bias = 3 # 偏置 # 计算一下初始权重和偏置值所带来的损失 print ('当前损失:',loss_function(X_train, y_train, weight, bias)) # 根据初始参数值,进行梯度下降,也就是开始训练机器,拟合函数 loss_history, weight_history, bias_history = gradient_descent( X_train, y_train, weight, bias, alpha, iterations) plt.plot(loss_history,'g--',label='Loss Curve') plt.xlabel('Iterations') # x轴Label plt.ylabel('Loss') # y轴Label plt.legend() # 显示图例 plt.show() # 显示损失曲线 # 绘制当前的函数模型 plt.plot(X_train, y_train,'r.', label='Training data') # 显示训练集散点图 line_X = np.linspace(X_train.min(), X_train.max(), 500) # X值域 # 关于weight_history[-1],这里的索引[-1],就代表迭代500次后的最后一个W值 line_y = [weight_history[-1]*xx + bias_history[-1] for xx in line_X] # 假设函数 plt.plot(line_X,line_y,'b--', label='Current hypothesis' ) # 显示当前函数 plt.xlabel('Wechat Ads') # x轴Label plt.ylabel('Sales') # y轴Label plt.legend() # 显示图例 plt.show() # 显示函数图像
-
再调成0.01试试(还是迭代两百次)
步子迈的太小了,迭代两百次之后效果不佳
- 0.5呢,咋样
- 当把α从1调整为0.01后,损失开始随着迭代次数而下降,但是下降速度不快,迭代200次后没有出现明显的收敛现象,如下面左图所示。反复调整α,发现在α=0.5的情况下损失曲线在迭代80~100次之后开始出现比较好的收敛现象,此时梯度已经极为平缓,接近凸函数的底部最优解,对权重求导时斜率几乎为0,因此继续增加迭代次数,损失值也不会再发生什么大的变化。
3.5.4 调试迭代次数
-
学习率为0.01,迭代两百次的效果图片在上面,肉眼可见的不太好,那我换成2000,20000呢?
-
2000👇
- 看到20000的那个图里,5000次还行,试试看
-
-
那学习率为0.5的时候来看看捏
- 20次
- 200次
-
500次
-
从图像显示可知,迭代20次显然太少了,损失值还在持续减少,训练不应停止。大概在迭代80~100次之后,损失已经达到了比较小的值,继续迭代下去没有太大意义,只是浪费资源,所以迭代500次没有必要。
-
就此例而言,以0.5的学习速率来说,为了安全起见,我们迭代100~200次差不多就可以了,最后确定迭代200次
-
下面就输出α=0.5时,迭代200次之后的损失值,以及参数w和b的值:
print ('当前损失:',loss_function(X_train, y_train, weight_history[-1], bias_history[-1])) print ('当前权重:',weight_history[-1]) print ('当前偏置:',bias_history[-1])
3.5.5 在测试集上进行测试
print ('测试集损失:',loss_function(X_test, y_test,
weight_history[-1], bias_history[-1]))
测试集损失比训练集损失还低,这种情形并不是机器学习的常态, 但在比较小的数据集上是有可能出现的
3.5.6 用轮廓图描绘L、w和b的关系
-
至此,机器学习建模过程已经完成
-
损失曲线描绘的是损失和迭代次数之 间的关系,而轮廓图(contour plot)则描绘的是L、w和b这3者之间的关系,这样才能够清楚地知道损失值是怎样随着w和b的变化而逐步下降的
-
这个动态的图真的很帅
- gif传了几次失败了…就先不放了
3.6 实现多元线性回归模型
-
在机器学习的程序设计中, 这个公式可以被向量化地实现, 以表示 任意维的特征:
y ′ = h ( x ) = W ⊤ ⋅ X + b \\\\y^{\prime}=h(x)=W^{\top} \cdot X+b y′=h(x)=W⊤⋅X+b
-
其中, W T ⋅ X W^{\mathrm{T}} \cdot X WT⋅X 就是 w 1 x 1 + w 2 x 2 + w 3 x 3 + ⋯ + w N x N w_{1} x_{1}+w_{2} x_{2}+w_{3} x_{3}+\cdots+w_{N} x_{N} w1x1+w2x2+w3x3+⋯+wNxN
-
还可以把公式进一步简化,就是把b也看作权重 w 0 w_0 w0,那么需要引入 x 0 x_0 x0,这样公式就是:
-
y ′ = w 0 x 0 + w 1 x 1 + w 2 x 2 + w 3 x 3 + ⋯ + w n x n y'=w_{0} x_{0}+w_{1} x_{1}+w_{2} x_{2}+w_{3} x_{3}+\cdots+w_nx_n y′=w0x0+w1x1+w2x2+w3x3+⋯+wnxn
-
引入 x 0 x_0 x0,就是给数据集添加一个新的哑(dummy)特征,值为1,b 和这个哑特征相乘,值不变:
w 0 x 0 = b × 1 = b w_0x_0=b\times 1 = b w0x0=b×1=b
-
-
新的公式变为:
y ′ = h ( x ) = W ⊤ ⋅ X \\\\y^{\prime}=h(x)=W^{\top} \cdot X y′=h(x)=W⊤⋅X
-
-
上面的表述形式令多元回归的程序实现过程更为简洁。多元回归的代码实现和前面的单变量回归非常相似
- 读取数据
import numpy as np #导入NumPy数学工具箱 import pandas as pd #导入Pandas数据处理工具箱 df_ads = pd.read_csv('../input/text3adverse/advertising.csv') df_ads.head(100)
-
构建特征集和标签
因为X特征集已经是2阶的,不需要再进行reshape操作,所以只需要 把标签张量y进行reshape操作
X = np.array(df_ads) # 构建特征集,含全部特征 X = np.delete(X, [3], axis = 1) # 删除掉标签 y = np.array(df_ads.sales) #构建标签集,销售金额 print ("张量X的阶:",X.ndim) print ("张量X的形状:", X.shape) print (X) y = y.reshape(-1,1) #通过reshape函数把向量转换为矩阵,-1就是len(y),返回样本个数 print ("张量y的形状:", y.shape)
多变量线性回归实现过程中的重点就是W和X点积运算的实现,因 为W不再是一个标量,而是变成了一个1D向量 [ w 0 , w 1 , w 2 , w 3 ] [w_0,w_1,w_2,w_3] [w0,w1,w2,w3]。
-
拆分数据集
# 将数据集进行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)
-
数据归一化处理函数
y_min, y_max, y_gap = min_max_gap(y_train) 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 # 返回压缩后的数据
def min_max_gap(train): # 计算训练集最大,最小值以及他们的差,用于后面反归一化过程 min = train.min(axis=0) # 训练集最小值 max = train.max(axis=0) # 训练集最大值 gap = max - min # 最大值和最小值的差 return min, max, gap y_min, y_max, y_gap = min_max_gap(y_train)
X_train_original = X_train.copy() # 保留一份训练集数据副本,用于对要预测数据归一化
X_train,X_test = scaler(X_train,X_test) # 对特征归一化 y_train,y_test = scaler(y_train,y_test) # 对标签也归一化
-
添加 x 0 x_0 x0特征
x0_train = np.ones((len(X_train),1)) # 构造X_train长度的全1数组配合对Bias的点积 X_train = np.append(x0_train, X_train, axis=1) #把X增加一系列的1 x0_test = np.ones((len(X_test),1)) # 构造X_test长度的全1数组配合对Bias的点积 X_test = np.append(x0_test, X_test, axis=1) #把X增加一系列的1 print ("张量X的形状:", X_train.shape) print (X_train)
-
梯度下降
这里要注意捋清楚每一个shape
loss_function和gradient_descent输入的X都是(160,4)
def loss_function(X, y, W): # 手工定义一个MSE均方误差函数,W此时是一个向量 y_hat = X.dot(W.T) # 点积运算 h(x)=w_0*x_0 + w_1*x_1 + w_2*x_2 + w_3*x_3 loss = y_hat.reshape((len(y_hat),1))-y # 中间过程,求出当前W和真值的差异 cost = np.sum(loss**2)/(2*len(X)) # 这是平方求和过程, 均方误差函数的代码实现 return cost # 返回当前模型的均方误差值 def gradient_descent(X, y, W, lr, iterations): # 定义梯度下降函数 l_history = np.zeros(iterations) # 初始化记录梯度下降过程中损失的数组 W_history = np.zeros((iterations,len(W))) # 初始化权重数组 for iter in range(iterations): # 进行梯度下降的迭代,就是下多少级台阶 y_hat = X.dot(W.T) # 这个是向量化运行实现的假设函数 loss = y_hat.reshape((len(y_hat),1))-y # 中间过程, y_hat和y真值的差 derivative_W = X.T.dot(loss)/len(X) #求出多项式的梯度向量 derivative_W = derivative_W.reshape(len(W)) W = W - lr*derivative_W # 结合下降速率更新权重 l_history[iter] = loss_function(X, y, W) # 损失的历史记录 W_history[iter] = W # 梯度下降过程中权重的历史记录 return l_history, W_history # 返回梯度下降过程数据 #首先确定参数的初始值 iterations = 300; # 迭代300次 alpha = 0.15; #学习速率设为0.15 weight = np.array([0.5,1,1,1]) # 权重向量,w[0] = bias #计算一下初始值的损失 print ('当前损失:',loss_function(X_train, y_train, weight))
l_histroy的shape是 (300,)
w_history的shape是(300,4) ,每行是每一次迭代过程中 4个方向的四个参数的值, w[0]是b,w[1]是微信的权重,w[2]是微博的权重,w[3]是others的权重
for iter开始循环
y_hat预测值 为X.dot(W.T)
X是X_train也就是(160,4), W是weight也就是(4,) , W.T也是 (4,) y_hat是(160,)
注意这里,其实W不转置算出来也是一样的
- 行向量和列向量应该是2D张量-矩阵的特殊情况 W和W.T应该都是普通的1D张量
- numpy产生行向量与列向量的方法
- Numpy中的行向量和列向量
接着分析
y_hat之前是(160,) 要reshape变为(160,1) y也就是y_train是(160,1)
loss也是(160,1)
derivative_W 用推导出来的公式,直接是 X.T.dot(loss) /len(X) , X是X_train, len(X)是160, X.T是(4,160),loss是(160,1) 点乘出来是(4,1)
对derivate_W进行reshape,又变为(4,)
对权重W进行迭代,W是(4,) 迭代后还是(4,)
l_history[iter]记录该次迭代的损失值, 是loss_function返回的一个值
那么来分析一下loss_function
y_hat是(160,) ,loss是(160,1), loss的平方还是(160,1)
np.sum会整个和,每个元素加起来求和
得到cost
再回到gradient_descent W_history记录下刚才迭代的W
接着继续迭代
-
构建线性回归模型
下面这段是书上的代码(我觉得有错误),不过先分析一下👇
# 定义线性回归模型 def linear_regression(X, y, weight, alpha, iterations): loss_history, weight_history = gradient_descent(X, y, weight, alpha, iterations) print("训练最终损失:", loss_history[-1]) # 打印最终损失 y_pred = X.dot(weight_history[-1]) # 进行预测 traning_acc = 100 - np.mean(np.abs(y_pred - y))*100 # 计算准确率 print("线性回归训练准确率: {:.2f}%".format(traning_acc)) # 打印准确率 return loss_history, weight_history # 返回训练历史记录
y_pred是(160,) y是(160,1)
这里涉及到numpy广播的知识
数组广播(array broadcasting)是NumPy用来支持形状不同但兼容的多个数组之间的矢量化数学操作的功能。具体来讲,NumPy会假装数组的内容在某个合适的维度被复制,使得这个复制出来,更高维度的数组能够完成这里的数学操作
- 嘶,这里有点像矩阵乘法,但注意区分
y_pred-y是(160,160), 先把y_pred变为(1,160), 和y对应的维度的大小是1,然后向下复制159行,变成(160,160)
y向右复制159列,变为(160,160),但是这样子意义在哪里??? 不同的sales互相减去没有意义呀
y_pred应该先reshape再减去y吧???
-
修改了一下代码
# 定义线性回归模型 def linear_regression(X, y, weight, alpha, iterations): loss_history, weight_history = gradient_descent(X, y, weight, alpha, iterations) print("训练最终损失:", loss_history[-1]) # 打印最终损失 y_pred = X.dot(weight_history[-1]) # 进行预测 y_pred = y_pred.reshape(-1,1) #加了个reshape traning_acc = 100 - np.mean(np.abs(y_pred - y))*100 # 计算准确率 print("线性回归训练准确率: {:.2f}%".format(traning_acc)) # 打印准确率 return loss_history, weight_history # 返回训练历史记录
-
初始化权重并训练机器
#首先确定参数的初始值 iterations = 300; # 迭代300次 alpha = 0.15; #学习速率设为0.15 weight = np.array([0.5,1,1,1]) # 权重向量,w[0] = bias #计算一下初始值的损失 print ('当前损失:',loss_function(X_train, y_train, weight))
# 调用刚才定义的线性回归模型 loss_history, weight_history = linear_regression(X_train, y_train, weight, alpha, iterations) #训练机器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1XkbdFLg-1670399339448)(https://cdn.jsdelivr.net/gh/xin007-kong/picture_new/img/20221207142729.png)]
print("权重历史记录:", weight_history) print("损失历史记录:", loss_history) print("最终权重:", weight_history[-1]) print("最终损失:", loss_history[-1])
按照书上原先代码,没有reshape得到的结果是:
这表示w0约为 0.028,w1约为0.65,w2约为0.21,w3约为0.20。
-
最后,回答本课开始时,小冰同学提出的问题:在未来的某周,当 我将各种广告投放金额做一个分配(比如,我决定用250元、50元、50 元)来进行一周的广告投放时,我将大概实现多少元的商品销售额?
X_plan = [250,50,50] # 要预测的X特征数据 X_train,X_plan = scaler(X_train_original,X_plan) # 对预测数据也要归一化缩放 X_plan = np.append([1], X_plan ) # 加一个哑特征X0 = 1 y_plan = np.dot(weight_history[-1],X_plan) # [-1] 即模型收敛时的权重 # 对预测结果要做反向缩放,才能得到与原始广告费用对应的预测值 y_value = y_plan*y_gap + y_min # y_gap是当前y_train中最大值和最小值的差,y_min是最小值 print ("预计商品销售额: ",y_value, "千元")
3.7 本课内容小结
-
本课完成了第一个机器学习模型的项目设计, 实现了整个机器学习 流程。我们学到了以下内容。
-
数据的收集与分析。
-
机器学习模型的确定。
-
假设函数一 h ( x ) = w x + b h(x)=wx+b h(x)=wx+b 或写成 h ( x ) = w 0 + w 1 x h(x)=w_{0}+w_{1} x h(x)=w0+w1x, 很多地方使用 h ( x ) = θ 0 + θ 1 x 0 h(x)=\theta_{0}+\theta_{1} x_{0} h(x)=θ0+θ1x0
-
损失函数一 M S E = L ( w , b ) = 1 N ∑ ( x , y ) ∈ D ( y − h ( x ) ) 2 MSE =L(w, b)=\frac{1}{N} \sum_{(x, y) \in D}(y-h(x))^{2} MSE=L(w,b)=N1∑(x,y)∈D(y−h(x))2
很多地方 使用 J ( θ 0 , θ 1 ) J\left(\theta_{0}, \theta_{1}\right) J(θ0,θ1) 表示损失函数
-
通过梯度下降训练机器, 目标是最小化 L ( w , b ) L(w, b) L(w,b), 即 J ( θ 0 , θ 1 ) J\left(\theta_{0}\right.,\left.\theta_{1}\right) J(θ0,θ1)
-
权重和偏置的初始化
-
参数的确定与调试:学习速率、迭代次数。
-
针对测试集应用机器学习的训练结果(即得到的模型)
-
-
在学习书上的代码并进行调试时,由于封装在函数里面,可以
-
把封装在函数里面的代码可以单独拿出来,换一些小例子来尝试
-
也可以在函数里面多加一些print获取自己想得到的信息,如shape ndim等
-
对那个linear_regression函数里面的y_pred-y那里的维度和广播一开始没太理解,尝试了上面两种方式来理解
- 然后感觉书上是错的,改成了先reshape
-
-
jupyter注意变量命名,有时候如果重复命名,乱了,就把单元格从头开始重新运行一下
-
学完本课内容之后,面对线性回归问题,有两个选择,要 么自己构建模型,要么直接调用机器学习函数库里现成的模型,然后用 fit方法训练机器,确定参数
3.8 练习题
1.调用sklearn的线性回归实现网店销量预测(只考虑微信)
-
读取数据
import numpy as np #导入NumPy数学工具箱 import pandas as pd #导入Pandas数据处理工具箱 # 读入数据并显示前面几行的内容,这是为了确保我们的文件读入正确性 # 示例代码是在Kaggle中数据集中读入文件,如果在本机中需要指定具体本地路径 df_ads = pd.read_csv('../input/text3adverse/advertising.csv') df_ads.head()
-
可视化查看关联度
# 导入数据可视化所需要的库 import matplotlib.pyplot as plt # matplotlib – Python画图工具库 import seaborn as sns # seaborn – 统计学数据可视化工具库 # 对所有的标签和特征两两显示其相关性热力图(heatmap) sns.heatmap(df_ads.corr(), cmap="YlGnBu", annot = True) plt.show() # plt 英文意为plot,就是画图的意思
-
显示散点图看大致的趋势
# 显示销量和各种广告投放量的散点图。 sns.pairplot(df_ads, x_vars=['wechat', 'weibo', 'others'], y_vars='sales', height=4, aspect=1, kind='scatter') plt.show()
-
构建特征集和标签集
X = np.array(df_ads.wechat) #构建特征集,只有微信广告一个特征 y = np.array(df_ads.sales) #构建标签集,销售金额 print ("张量X的阶:",X.ndim) print ("张量X的形状:", X.shape) print (X)
-
修改张量格式
# X = X.reshape((len(X),1)) #通过reshape函数把向量转换为矩阵,len函数返回样本个数 # y = y.reshape((len(y),1)) #通过reshape函数把向量转换为矩阵,len函数返回样本个数 X = X.reshape(-1,1) #也可以利用索引-1通过reshape函数把向量转换为矩阵 y = y.reshape(-1,1) #也可以利用索引-1通过reshape函数把向量转换为矩阵 print ("张量X的形状:", X.shape) print (X)
-
拆分数据集
# 将数据集进行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)
-
数据归一化
def range_0_1(data_train, data_test): # 定义归一化函数 ,进行数据压缩 # 数据的压缩 min = data_train.min(axis=0) # 训练集最小值 max = data_train.max(axis=0) # 训练集最大值 gap = max - min # 最大值和最小值的差 data_train -= min # 所有数据减最小值 data_train /= gap # 所有数据除以大小值差 data_test -= min #把训练集最小值应用于测试集 data_test /= gap #把训练集大小值差应用于测试集 return data_train, data_test # 返回压缩后的数据 X_train,X_test = range_0_1(X_train,X_test) # 对特征归一化 y_train,y_test = range_0_1(y_train,y_test) # 对标签也归一化
-
查看归一化之后的散点图
plt.plot(X_train,y_train,'r.', label='Training data') # 用matplotlib.pyplot的plot方法显示散点图 plt.xlabel('Wechat Ads') # x轴Label plt.ylabel('Sales') # y轴Label plt.legend() # 显示图例 plt.show() # 显示绘图结果!
-
调用sklearn 线性回归
from sklearn.linear_model import LinearRegression #导入线性回归算法模型 model = LinearRegression() #使用线性回归算法 model.fit(X_train, y_train) #用训练集数据,训练机器,拟合函数,确定参数
-
利用模型进行预测
y_pred = model.predict(X_test) #预测测试集的Y值 print ('销量的真值(测试集)',y_test) print ('预测的销量(测试集)',y_pred) print("线性回归预测评分:", model.score(X_test, y_test)) #评估预测结果
-
试试线性岭回归
from sklearn.linear_model import Ridge #导入线性岭回归算法模型 model = Ridge() #使用线性回归算法 model.fit(X_train, y_train) #用训练集数据,训练机器,拟合函数,确定参数 y_pred = model.predict(X_test) #预测测试集的Y值 print("线性回归预测评分:", model.score(X_test, y_test)) #评估预测结果
2.波士顿房价预测(手写梯度下降和线性回归)
-
载入数据
import numpy as np # 导入NumPy数学工具箱 import pandas as pd # 导入Pandas数据处理工具箱 from keras.datasets import boston_housing #从Keras中导入mnist数据集 #读入训练集和测试集 (X_train, y_train), (X_test, y_test) = boston_housing.load_data()
-
查看格式
-
选择损失函数,这里选用MSE均方误差函数
注释里的4是针对前面的 网店数据集
注意这里y_hat没有reshape是因为y没有reshape (前面网店的例子是因为y 进行了reshape所以y_hat也要reshape)
def cost_function(X, y, W): # 手工定义一个MSE均方误差函数,W此时是一个向量 # X -> 是一个矩阵,形状是(N,4),N是数据集大小,4是特征数量 # W -> 是一个向量,形状是(4,1)(1*) # y_hat = X.dot(weight) # 这是假设函数,其中已经应用了Python的广播功能 # y_hat = np.dot(X,weight) # 也是正确的 y_hat = X.dot(W.T) # 也是正确的 点积运算 h(x)=w_0*x_0 + w_1*x_1 + w_2*x_2 + w_3*x_3 # y_hat = np.dot(X,weight.T) # 也是正确的 # y_hat = weight.dot(X) # 错误 shapes (4,) and (160,4) not aligned: 4 (dim 0) != 160 (dim 0) # y_hat = np.dot(weight,X) # 错误 shapes (4,) and (160,4) not aligned: 4 (dim 0) != 160 (dim 0) loss = y_hat-y # 求出每一个y’和训练集中真实的y之间的差异 cost = np.sum(loss**2)/len(X) # 这是均方误差函数的代码实现 return cost # 返回当前模型的均方误差值
加了些print调试了一下
-
定义梯度下降函数
def gradient_descent(X, y, W, lr, iter): # 定义梯度下降函数 l_history = np.zeros(iter) # 初始化记录梯度下降过程中损失的数组 W_history = np.zeros((iter,len(W))) # 初始化权重数组 for iter in range(iter): # 进行梯度下降的迭代,就是下多少级台阶 y_hat = X.dot(W) # 这个是向量化运行实现的假设函数 loss = y_hat-y # 中间过程, y_hat和y真值的差 derivative_W = X.T.dot(loss)/(2*len(X)) #求出多项式的梯度向量 derivative_W = derivative_W.reshape(len(W)) W = W - lr*derivative_W # 结合下降速率更新权重 l_history[iter] = cost_function(X, y, W) # 损失的历史记录 W_history[iter] = W # 梯度下降过程中权重的历史记录 return l_history, W_history # 返回梯度下降过程数据
那一步reshape其实不需要
X.T是(13,404), loss是(404,) 结果是(13,) 并不需要reshape
来看个例子
-
确定初始参数
#首先确定参数的初始值 iterations = 12000; # 迭代12000次 alpha = 0.00001; #学习速率设为0.00001 weight = np.array([0.5,1.2,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1]) # 权重向量 #计算一下初始值的损失 print ('当前损失:',cost_function(X_train, y_train, weight))
-
定义线性回归模型
# 定义线性回归模型 def linear_regression(X, y, weight, alpha, iterations): loss_history, weight_history = gradient_descent(X, y, weight, alpha, iterations) print("训练最终损失:", loss_history[-1]) # 打印最终损失 y_pred = X.dot(weight_history[-1]) # 预测 traning_acc = 100 - np.mean(np.abs(y_pred - y)/y)*100 # 计算准确率 print("线性回归训练准确率: {:.2f}%".format(traning_acc)) # 打印准确率 return loss_history, weight_history # 返回训练历史记录
前面网店那里我觉得书上y_pred没有reshape是不对的,波士顿这里确实不用reshape
-
查看结果