目录
导语
在某个特定的数据场景下,事先并不知道什么样的模型可以近似刻画数据的规律。而模型选择可以有很多,比如:线性回归(n 元一次函数)、多项式回归(一元 n 次函数)等。即便是使用线性回归模型,在设置不同的超参数(如学习率、迭代次数)进行训练时,得到的模型参数也会有差异。(模型参数不同,实际上就是模型本身的不同);
既然可供选择的模型有很多,那必然就有好有坏,目标是要从中挑选出最能刻画数据规律的模型;用来训练模型的数据是有限的,我们希望通过对有限的数据进行建模,从而发现所有数据的一般性规律,使得模型对未知数据具备一定的预测能力。模型对未知数据的预测能力叫做模型的泛化能力。换言之,我们希望得到一个泛化能力较强的模型。
模型评估
对模型的泛化性能进行评估,需要有对应的评价标准,即:性能度量,性能度量反映了任务需求,在对比不同模型的能力时,使用不同的度量指标往往会导致不同的评判结果,这意味着模型的好坏是相对的。
回归任务的评估指标
回归任务中常用的评估指标有:平均绝对误差(Mean Absolute Error,简称 MAE)、均方误差(Mean Square Error,简称 MSE)、均方根误差(Root Mean Square Error,简称 RMSE),他们的取值范围都是零到正无穷,表达式如下:
M
A
E
=
1
m
∑
i
=
1
m
∣
f
(
x
i
)
−
y
i
∣
MAE=\frac{1}{m}\sum_{i=1}^{m}|f(x_{i})-y_{i}|
MAE=m1i=1∑m∣f(xi)−yi∣
M
S
E
=
1
m
∑
i
=
1
m
(
f
(
x
i
)
−
y
i
)
2
MSE=\frac{1}{m}\sum_{i=1}^{m}(f(x_{i})-y_{i})^{2}
MSE=m1i=1∑m(f(xi)−yi)2
R
M
S
E
=
1
m
∑
i
=
1
m
(
f
(
x
i
)
−
y
i
)
2
RMSE=\sqrt{\frac{1}{m}\sum_{i=1}^{m}(f(x_{i})-y_{i})^{2}}
RMSE=m1i=1∑m(f(xi)−yi)2
m m m是样本数量, f ( x i ) f(x_{i}) f(xi)是样本预测值, y i y_{i} yi是样本标记值;
分类任务的评估指标
分类任务中常用的评估指标有:混淆矩阵(confusion matrix)、准确率(accuracy)、精确率(precision)、召回率(recall)、F1-score、AUC等。AUC 是推荐系统中排序任务常用的模型评价指标;
准确率是指模型在给定数据集上正确分类的样本数与总样本数之比,公式如下:
A
c
c
u
r
a
c
y
=
1
m
∑
i
=
1
m
I
(
f
(
x
i
)
=
y
i
)
Accuracy=\frac{1}{m}\sum_{i=1}^{m}I(f(x_{i})=y_{i})
Accuracy=m1i=1∑mI(f(xi)=yi)
I
I
I是指示函数,
f
(
x
i
)
=
y
i
f(x_{i})=y_{i}
f(xi)=yi时值为1,否则为0;
在实际应用时,准确率、精确率、召回率、F1值都是基于混淆矩阵进行计算的,下面来看一个混淆矩阵的例子(问题为A和B两类的分类):
由混淆矩阵看出,真实的A类样本有5个,预测得到A类样本7个,类似的,真实B类样本6个,预测B类4个;
混淆矩阵主对角线上的 3 和 2 表示预测正确的样本数,由此我们可以计算出模型预测的准确率为:(3+2)/11 = 5/11
;混淆矩阵中暗蓝色区域的 4 表示有 4 个 B 类样本被预测为了 A 类;
假如比较关注 A 类样本,那么就可以通过混淆矩阵来计算模型对 A 类样本预测的精确率、召回率和 F1 值,计算过程如下:
P
r
e
c
i
s
i
o
n
(
A
)
=
3
7
Precision(A)=\frac{3}{7}
Precision(A)=73
R
e
c
a
l
l
(
A
)
=
3
5
Recall(A)=\frac{3}{5}
Recall(A)=53
F
1
(
A
)
=
2
P
r
e
c
i
s
i
o
n
(
A
)
R
e
c
a
l
l
(
A
)
P
r
e
c
i
s
i
o
n
(
A
)
+
R
e
c
a
l
l
(
A
)
=
0.5
F_{1}(A)=\frac{2Precision(A)Recall(A)}{Precision(A)+Recall(A)}=0.5
F1(A)=Precision(A)+Recall(A)2Precision(A)Recall(A)=0.5
精确率描述模型预测出的 7 个 A 类样本中有多少是预测正确的;召回率描述模型将 5 个 A 类样本预测出来的数量;F1 值是精确率和召回率的调和均值;
AUC一般用于二分类问题,混淆矩阵如下(以目标类别为阳类):
首先要计算真阳率 TP rate:
T
P
r
a
t
e
=
T
P
T
P
+
F
N
TPrate=\frac{TP}{TP+FN}
TPrate=TP+FNTP
然后计算假阳率 FP rate:
F
P
r
a
t
e
=
F
P
F
P
+
T
N
FPrate=\frac{FP}{FP+TN}
FPrate=FP+TNFP
AUC(Area under Curve)为ROC曲线下的面积,ROC曲线的横轴是FPrate,纵轴是TPrate,一个混淆矩阵只能得到一个(FPrate,TPrate)元组,得到的ROC曲线为以下形式:
当使用例如逻辑回归的模型输出计算混淆矩阵时,由于输出值不是具体的0或1,而是概率,于是可以将真实标签与阳类概率进行合并,比如有以下数据:
通过设置不同的概率阈值可以使样本分类情况发生改变,即改变了混淆矩阵,将所有阈值对应的混淆矩阵收集,计算出各个混淆矩阵对应的(FPrate,TPrate),便可以绘制出接近平滑的ROC曲线,求积分即为AUC;
AUC的计算方法同时考虑了分类器对于正例和负例的分类能力,在样本不平衡的情况下,依然能够对分类器作出合理的评价,AUC越大说明分类器越好;
类似的,当问题变成3类分类,有以下例子:
不管二分类还是多分类,通常把我们所关心的样本称为正样本,其他样本称为负样本。也可以理解为和我们的目标相关的就是正样本,和目标无关的就是负样本。比如广告系统的目标是通过算法提升商品的点击率,那么样本标记值为点击的就是正样本。一般来说,我们所计算的精确率、召回率和 F1 值都是相对于正样本而言的。
准确率、精确率、召回率、F1值等评价指标,主要是看模型在测试集上的表现,因为测试集不参与模型的训练,可以用来估计模型的泛化性能。当然也可以计算训练集上的对应指标,但是没有太大的意义,因为泛化能力是指模型对未知数据的预测能力
过拟合现象
前面说过,我们要选择泛化能力较强的模型,但是在模型训练中经常会出现过拟合的现象,具体表现为模型对已知数据预测得很好,但是对未知数据的预测效果却很差,下面我们通过多项式回归的一个例子来理解一下过拟合现象:
f
M
(
x
)
=
w
0
+
w
1
x
+
w
2
x
2
+
.
.
.
+
w
M
x
M
f_{M}(x)=w_{0}+w_{1}x+w_{2}x^{2}+...+w_{M}x^{M}
fM(x)=w0+w1x+w2x2+...+wMxM
由模型公式可知:多项式回归模型的输入只有一个特征
x
x
x ,
M
M
M 是多项式的最高次数,不同的取值代表了不同复杂度的多项式回归模型,
M
M
M取值越小,模型参数越少,复杂度越低:
图中的蓝色圆点是在
s
i
n
(
2
π
x
)
sin(2\pi x)
sin(2πx) 函数的基础上加了随机噪声生成的训练数据,红色曲线是不同复杂度的多项式回归模型曲线;当
M
=
9
M=9
M=9 时,多项式函数完美拟合训练集中的每个点,但曲线震荡,出现过拟合现象;
过拟合的原因
-
训练集数量太少。模型只在少量的数据上进行拟合,学不到数据的一般性规律;
-
样本里的噪声数据干扰过大,大到模型过分记住了噪声特征,反而忽略了真实的输入与输出间的关系;
-
训练集和测试集的特征分布不一致;
-
模型复杂度过高,如上面的多项式回归中 M = 9 M=9 M=9 时就产生了过拟合;
-
训练迭代的次数过多,导致模型拟合了训练数据中的噪声和没有代表性的特征。
过拟合解决办法
(1)增加训练集数量;
(2)重新清洗数据(删除稀疏特征、对噪声数据进行删除或替换);
(3)重新进行数据采样和特征筛选;
(4)正则化;
(5)交叉验证;
(6)在模型收敛之前停止迭代训练;
(7)使用集成学习算法;
(8)若是神经网络模型,可以使用 dropout。
模型选择与调整超参数
正则化
正则化项一般是模型复杂度的单调递增函数,模型越复杂,正则化值就越大。我们一般将模型参数向量的范数作为正则化项;先来看一下向量范数的函数表达式:
∣
∣
w
∣
∣
p
=
[
∑
i
=
0
n
∣
w
i
∣
p
]
1
p
||w||_{p}=[\sum_{i=0}^{n}|w_{i}|^{p}]^{\frac{1}{p}}
∣∣w∣∣p=[i=0∑n∣wi∣p]p1
公式中
w
w
w的一般性表示是一个向量,本篇特指模型的参数向量。该向量的
p
p
p范数是将向量各元素绝对值的
p
p
p 次方求和再开
p
p
p 次方根;机器学习中经常将参数向量的1范数和2范数作为正则化项,对应的正则化叫L1 正则和L2 正则;
结构风险是在经验风险上加上表示模型复杂度的正则化项,即:结构风险 = 经验风险+正则化项;因为结构风险中包含了正则化项,所以最小化结构风险可以对模型复杂度进行限制,从而防止模型复杂度过高导致的过拟合。
经验风险和结构风险都是模型参数的函数,最小化经验风险或结构风险得到对应的模型参数是机器学习的目标,因此经验风险和结构风险就成了这个最小化问题的目标函数;因为相同算法模型的差异主要体现在模型参数上,恰好正则化是对模型参数进行了限制,所以正则化可以进行模型选择;
进行模型选择的另一种方法是交叉验证,在学习交叉验证之前,我们来看一下和它有关的另一种模型选择方法:留出法。
留出法
一般来说,设置不同的超参数时,训练得到的模型是不一样的,不恰当的超参设置有可能会导致模型过拟合。因此,我们需要从已有的训练数据中再划分出一部分数据来进行超参的选择,这部分数据一开始不参与模型的训练过程,仅用来验证模型的泛化性能,我们称之为验证集:
举个例子,根据前面过拟合的原因分析可知,迭代次数过多有可能导致模型的过拟合。这时我们就可以通过模型每次迭代后在训练集 D1 和验证集 D2上的性能度量指标画两条学习曲线,通过曲线的变化来判断epoch超过多少会发生过拟合,进而确定epoch的最佳取值,如图所示:
当epoch>15时,验证集 D2 上的预测误差在慢慢变大,而训练集 D1上的误差还在减小,模型在过拟合训练集 D1 的数据。假定在原训练集 D 上也有这种规律,因此将 15 设置为 epoch 的最佳取值,然后在原训练集 D 上重新训练模型。这样一来,通过在验证集上的性能评估和超参选择,可以防止模型在全量训练数据的训练中发生过拟合。
实际上我们是根据 D1 上训练出的模型的泛化性能来估计 D 上训练出的模型的泛化性能,所以训练集和验证集的划分也比较重要,一般是将大约2/3~4/5
的样本用于训练,剩余的样本用于模型评估;
需要注意的是:在模型训练时,有时会出现不断增大 epoch 时,验证集上的误差也不会出现反弹的现象。原因可能是验证集数量太少,恰好模型学习到了训练集和验证集的一般性规律,如果扩展到无限的未知数据中,误差的变化不一定依然减小。
另一个原因是模型本身的复杂度不高,当训练数据比较充足时不容易过拟合。此外,过拟合的原因是多方面的,并不是 epoch 单个因素决定的。一般来说,深度学习模型由于参数量大,模型复杂,在训练数据较少且迭代次数过多时容易发生过拟合。
关于测试集
测试集用来评估最终的模型,一般情况下只使用一次;验证集用来评估不同的超参数下训练出的多个模型或者不同算法模型(如线性回归和神经网络)的泛化性能,最终还会参与模型的训练。
综上所述,我们使用验证集进行模型选择和超参的选择(也称为调参),然后在全量的训练数据上进行模型训练,最后在测试集上进行离线评估。
交叉验证
从训练数据中留出一部分数据作为验证集用于模型评估和选择的方法叫做留出法,一般在样本数据充足的情况下使用。当数据量不足时,用于模型评估的验证集也会不足,这将导致模型评估结果出现偏差。这时,我们可以使用交叉验证的方法进行模型的选择。
交叉验证的实现步骤如下:
- 先将训练集 D 划分为 K 个大小相同的互斥子集,每个子集都尽可能保持数据分布的一致性,即从 D 中通过分层采样得到;
- 选择 K-1 个子集的并集作为训练集,余下的那个子集作为验证集,一共可以得到 K 组训练/验证集,从而可以进行 K 次模型训练和验证,取 K 次验证结果的均值作为当前条件下模型泛化性能的估计值;
- 最后选择泛化性能最好的超参数或模型类型在全量的训练集 D 上进行模型训练。
交叉验证的评估结果在很大程度上会受 K 值的影响,为了强调这一点,通常把交叉验证称为K折交叉验证,K 常用的取值有 5 和 10 ,下面是 5 折交叉验证的示意图:
交叉验证对资源消耗比较大,因为每个模型都要验证 K 次,最后根据各个模型的K次平均结果选出最好的那个模型。当 K 的取值为样本总数时,称为留一交叉验证,也就是每次验证集的数量都为 1 。
网格搜索
5 折交叉验证要进行 5 次模型训练和验证。假如我们要选择的学习率有 3 个经验取值,分别是 0.1,0.01,0.001,则一共要进行 5 × 3 = 15 5\times 3=15 5×3=15 次模型训练,才能判断出哪个学习率取值比较合适。一般来说,模型的超参数不止一个,这时就要使用网格搜索来进行交叉验证。
例如:有两个超参数,分别有3个经验取值,则他们的组合取值有 9 种;不同的超参数组合被放在一个个网格中,我们需要在每种超参数组合下执行一次 K 折交叉验证,如图所示:
实验:线性回归预测股票走势
实验说明
本实验基于 Sklearn 中的线性回归模型实现 Google 股票走势预测:预测未来30天 Google 股票的收盘价。
(1) scikit-learn 是基于 Python 语言的机器学习工具
- 简单高效的数据挖掘和数据分析工具
- 可供开发者在各种环境中重复使用
- 建立在 NumPy ,SciPy 和 matplotlib 上
- 开源,可商业使用 - BSD许可证
(2) 可以通过下面途径深入sklearn的使用:
sklearn 英文文档:en
sklearn 中文文档:ch
(3)安装方式:
pip install sklearn
数据集
Quandl 是为投资专业人士提供金融、经济和替代数据的首选平台,拥有海量的经济和金融数据。Python 有 quandl 模块,通过 quandl 模块可直接使用平台上的数据。quandl 可以访问平台上所有免费的数据,但不是所有的数据都是免费的,部分数据需要付费才能使用。本实验用的谷歌股票数据需要通过 quandl 模块来获取,所以先安装这个模块:
pip install quandl
载入数据集:google.csv
(文件在我的"资源"中)
import quandl
import pandas as pd
data="./google.csv"
# 取第0列作为索引(表中第0列是Date)
df=pd.read_csv(data,index_col=0)
# 展示最后5行数据
df.tail()
表格中的列代表每天的股票指数,如:Open 开盘价、High 最高价、Low 最低价、Volume 成交量。Adj. Open、Adj. High、Adj. Low、Adj. Close、Adj. Volume 代表调整后的数据,Ex-Dividend 和 Split Ratio 尚不清楚,建模时用不到这两个特征,我们主要使用 Adj. Open、Adj. High、Adj. Low、Adj. Close、Adj. Volume 这五列的信息,下面对原始数据进行特征列的筛选:
# 特征列的筛选
df = df[['Adj. Open','Adj. High','Adj. Low','Adj. Close','Adj. Volume']]
df.tail()
进一步观察数据:
# 样本数与特征数
print(len(df),len(df.columns))
"""
3424 5
"""
# 各列特征的缺失值数量
columns=['Adj. Open','Adj. High','Adj. Low','Adj. Close','Adj. Volume']
for col in columns:
print("number of nan:{} at feature {}".format(df[col].isnull().sum(),col))
"""
number of nan:0 at feature Adj. Open
number of nan:0 at feature Adj. High
number of nan:0 at feature Adj. Low
number of nan:0 at feature Adj. Close
number of nan:0 at feature Adj. Volume
"""
特征提取与特征选择
新增特征并再次筛选特征:
# 新增特征:最高价相对最低价变化率
df['HL_change']=df['Adj. High']-df['Adj. Low']
# 新增特征:收盘价相对开盘价变化率
df['CO_change']=df['Adj. Close']-df['Adj. Open']
# 再次筛选特征
df=df[['Adj. Close','HL_change','CO_change','Adj. Volume']]
因为新增两个股价变化率特征,所以舍弃了最高价,最低价,开盘价。保留收盘价是因为它和预测目标关联性较强:实验基于当前收盘价预测未来收盘价。
样本数据处理:
# 特征标准化,各列均值为0,方差为1
from sklearn.preprocessing import scale
# 数据集划分
from sklearn.model_selection import train_test_split
# 获取标准化后的特征
X=scale(df.values)[:-30]
X_feature_30_days=scale(df.values)[-30:]
# 获取样本标记值:30天后的收盘价 (3394个样本)
# shift将该列向前移动30行,dropna去除缺省行
y=df['Adj. Close'].shift(-30).dropna().values
# 划分训练集和测试集,测试集占20%
X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.2)
模型训练及测试
使用线性回归模型:
from sklearn.linear_model import LinearRegression
# 实例化
model=LinearRegression()
model.fit(X_train,y_train)
y_pred=model.predict(X_test)
模型评估
MAE、RMSE在课程中都有提到, R 2 R^{2} R2 是回归问题中另一个用于衡量模型预测能力好坏的一个指标,预测数据和真实数据越接近, R 2 R^{2} R2 越大:
import numpy as np
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_score
MAE = mean_absolute_error(y_test,y_pred)
RMSE = mean_squared_error(y_test,y_pred)
R2 = r2_score(y_test,y_pred)
print('\nMAE:{}, RMSE:{}, R2:{}'.format(MAE,RMSE,R2))
# MAE:26.573571574009204, RMSE:1225.5255545010086, R2:0.9812099868554676
数据可视化
对结果进行可视化:
# 数据可视化
import matplotlib.pyplot as plt
# 显示中文:微软雅黑字体
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']
# 设置画布大小
plt.figure(figsize=(16, 12))
# 样式修改(为了画出的图好看一些)
from matplotlib import style
style.use('ggplot')
# 绘制历史收盘价真实曲线
plt.plot(range(y.shape[0]),y,label='历史收盘价真实曲线',color='red')
# 绘制历史收盘价预测曲线
y_pred=model.predict(X)
plt.plot(range(y_pred.shape[0]),y_pred,label='历史收盘价预测曲线',color='green')
# 计算未来30天股票的收盘价
y_future_30_days = model.predict(X_feature_30_days)
# 绘制未来30天收盘价预测曲线
plt.plot(range(y_pred.shape[0],y_pred.shape[0]+y_future_30_days.shape[0]),y_future_30_days,label='未来30天收盘价预测曲线',color='blue')
# 设置图例位置和字体大小
plt.legend(loc=4,fontsize='x-large')
# 设置图标题为:Google股票的收盘价走势预测
plt.title("Google股票的收盘价走势预测")
# 设置横坐标名称
plt.xlabel('Day')
# 设置纵坐标名称
plt.ylabel('Price')
# 显示图片
plt.show()