机器学习的目的是使模型能很好地适用于“新样本”,即使该模型具有泛化能力,但在机器学习中,优化和泛化之间是对立的,如果优化过于强大,进而把训练样本本身的特有性质当作所有潜在样本都具有的一般性质,则导致泛化能力减小,出现“过拟合”的情况。如果学习器没有通过训练样本学习到一般性质则会出现“欠拟合”。
评估模型的重点是将数据划分为三个集合:训练集(训练数据)、验证集(验证数据)和测试集(测试数据)。在训练数据上训练模型,在验证数据上评估模型。如果找到了最佳参数,即在测试数据上进行最后一次测试。
如果数据很少,可以用简单的留出验证、K折验证,以及带有打乱数据的重复K折验证这三种经典评估方法进行数据划分。
留出验证(hold-out validation)是最简单、最直接的方法,它将留出一定比例的数据作为测试集,接着将剩余的数据进行模型训练,最后在测试集上评估模型。图1-9为留出验证示意。
图1-9 留出验证示意
如果用sklearn包实现留出验证,使用的Python代码是非常简单的:
from sklearn.model_selection import train_test_split #使用train_test_split划分训练集和测试集 train_X,test_X,train_Y,test_Y=train_test_split( X,Y,test_size=0.2,random_state=0) ''' X为原始数据的自变量,Y为原始数据因变量; train_X,test_X是将X按照8:2划分所得; train_Y,test_Y是将X按照8:2划分所得; test_size是划分比例; random_state设置是否使用随机数 '''
需要注意的是,如果数据太少,那么验证集和测试集包含的样本就少,因此,无法在统计学上代表数据。存在这个问题的原因是在划分数据前进行不同的随机打乱,最终得到的模型性能差别大。
【例1-1】 利用留出验证训练数据集。
from sklearn.model_selection import train_test_split from sklearn.model_selection import StratifiedShuffleSplit from sklearn.model_selection import ShuffleSplit from collections import Counter from sklearn.datasets import load_iris def test01(): #加载数据集 x,y=load_iris(return_X_y=True) print('原始类别比例:',Counter(y)) #留出验证(随机分割) x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2) print('随机类别分割:',Counter(y_train),Counter(y_test)) #留出验证(分层分割) x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.2,stratify=y) print('分层类别分割:',Counter(y_train),Counter(y_test)) def test02(): #加载数据集 x,y=load_iris(return_X_y=True) print('原始类别比例:',Counter(y)) print(' * ' * 40) #多次划分(随机分割) spliter=ShuffleSplit(n_splits=5,test_size=0.2,random_state=0) for train,test in spliter.split(x,y): print('随机多次分割:',Counter(y[test])) print(' * ' * 40) #多次划分(分层分割) spliter=StratifiedShuffleSplit(n_splits=5,test_size=0.2,random_state=0) for train,test in spliter.split(x,y): print('分层多次分割:',Counter(y[test])) if__name__== '__main__': test01() test02() 运行程序,输出如下: 原始类别比例:Counter({0:50,1:50,2:50}) 随机类别分割:Counter({1:42,0:41,2:37})Counter({2:13,0:9,1:8}) 分层类别分割:Counter({1:40,2:40,0:40})Counter({1:10,2:10,0:10}) 原始类别比例:Counter({0:50,1:50,2:50}) *************************************** 随机多次分割:Counter({1:13,0:11,2:6}) 随机多次分割:Counter({1:12,2:10,0:8}) 随机多次分割:Counter({1:11,0:10,2:9}) 随机多次分割:Counter({2:14,1:9,0:7}) 随机多次分割:Counter({2:13,0:12,1:5}) *************************************** 分层多次分割:Counter({0:10,1:10,2:10}) 分层多次分割:Counter({2:10,0:10,1:10}) 分层多次分割:Counter({0:10,1:10,2:10}) 分层多次分割:Counter({1:10,2:10,0:10}) 分层多次分割:Counter({1:10,2:10,0:10})
K折验证(K-fold validation)将数据划分为大小相同的 K 个分区。对于每个分区 i ,在剩余的 K -1个分区上训练模型,然后在分区 i 上评估模型,最终分数等于 K 个分数的平均值。对于不同的训练集-测试集划分,如果模型性能变化很大,那么这种方法很有用。与留出验证一样,这种方法也需要独立的验证集进行模型校正。
K折验证示意如图1-10所示。
图1-10 K折验证示意
【例1-2】 K折交叉验证。
解析:先获取数据,把数据分为训练数据和测试数据,然后在不同 K 值的模型下分别训练和测试,得出不同 K 值情况下的模型预测准确性,最后把准确性可视化输出进行整体评估。
'''获取数据''' from sklearn import datasets iris=datasets.load_iris() x=iris.data y=iris.target '''分离数据''' from sklearn.model_selection import train_test_split x_train,x_test,y_train,y_test=train_test_split(x,y,test_size=0.3) #test_size=0.3相当于测试数据的占比是0.3,而训练数据的占比是0.7,也可以通过下面代码验证 print(x_train.shape,x_test.shape,y_train.shape,y_test.shape) (105,4)(45,4)(105,)(45,) '''不同k值的模型下分别训练和测试,得出不同k值情况下的模型预测准确性''' k_range=list(range(1,16)) #创建一个1~16的列表 print(k_range) [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15] '''分别创建训练数据得分准确率空列表和测试数据得分准确率空列表,用于接收遍历循环后的数据''' train_score=[] test_score=[] '''导入建立模型模块和判断准确率模块''' from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score '''开始不同的k值遍历循环''' for k in k_range: knn=KNeighborsClassifier(n_neighbors=k) knn.fit(x_train,y_train) y_train_predict=knn.predict(x_train) y_test_predict=knn.predict(x_test) a=accuracy_score(y_train,y_train_predict) b=accuracy_score(y_test,y_test_predict) train_score.append(a) test_score.append(b) '''因为一个模型的评估主要是针对测试数据进行预测的准确率的高低,所以只提取测试数据准确率''' for k in k_range: print(k,test_score[k-1]) 1 0.9111111111111111 2 0.9333333333333333 3 0.9555555555555556 4 0.9333333333333333 5 0.9555555555555556 6 0.8888888888888888 7 0.9111111111111111 8 0.8888888888888888 9 0.9333333333333333 10 0.9111111111111111 11 0.9333333333333333 12 0.9333333333333333 13 0.9555555555555556 14 0.9333333333333333 15 0.9333333333333333 '''评估数据图表可视化''' import matplotlib.pyplot as plt #导入绘图库模块 plt.rcParams['font.sans-serif']=['SimHei'] #图形中显示中文 #开始输出训练数据准确率图,如图1-11所示 plt.plot(k_range,train_score) plt.xlabel('k值') plt.ylabel('分数') plt.show() Text(0,0.5,'分数')
图1-11 训练数据准确率
#开始输出测试数据准确率,如图1-12所示 plt.plot(k_range,test_score) plt.xlabel('k_value') plt.ylabel('score') plt.xlabel('k值') plt.ylabel('分数') Text(0,0.5,'分数')
图1-12 测试数据准确率
从测试数据得分率看出,不是 k 越小或者越大准确率越高。从图1-12中可以看出,整体来说呈现先上升后下降的趋势。 k 值越小,模型越过于拟合,越复杂。
如果可用的数据相对较少,而又需要尽可能精确地评估模型,那么可以选择带有打乱数据的重复K折验证(interated K-fold validation with shuffling)。具体做法是多次使用K折验证,在每次数据划分为 K 个分区之前都先将数据打乱。最终分数是每次K折验证分数的平均值。注意,这种方法一共要训练和评估 P × K 个模型( P 是重复次数),计算代价很大。