模型的改善与泛化(手写体识别)
跟我一起机器学习系列文章将首发于公众号:月来客栈,欢迎文末扫码关注!
经过前面五篇文章的介绍,我们对模型的改善与泛化算了有了一定的认识与了解。下面笔者就通过一个实际的手写体分类任务来做一个示范,介绍一下常见的操作流程。并同时顺便介绍一下sklearn
和matplotlib
中常见方法的使用。
1 数据集
1.1 导入数据集
本次我们用到的是sklearn中内置的一个数据集digits
手写体,可以通过如下代码进行导入:
from sklearn.datasets import load_digits
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import KFold, train_test_split
def load_data(scale=True):
data = load_digits()
x, y = data.data, data.target
x_train, x_test, y_train, y_test = \
train_test_split(x, y, test_size=0.3,
shuffle=True, random_state=20)
if scale:
ss = StandardScaler()
x_train = ss.fit_transform(x_train)
x_test = ss.transform(x_test)
return x_train, x_test, y_train, y_test
对于第4-5行代码,如果是sklearn中内置的数据集都可以这样来导入进行使用。对于第6-8行代码,train_test_split()
的作用主要是对数据集进行分割,test_size=0.3
表示取其中作为测试集,剩下的为训练集;shuffle=True
表示要将数据集进行打乱;random_state=20
表示设置一个状态值,它的作用是使得每次打乱的结果都是一样的,同时也可以设置其它值。同时,对于包含随机操作的函数或者方法,一般都有这个参数,固定下来便于其他人重现你的结果。
对于最后4行代码,StandardScaler()
类主要用于对特征进行标准化,使得标准后特征值的均值为,标准差为。同时,需要特别说明一下的是在整个sklearn的实现中,所有模型的拟合(也包括各类数据的处理)都是用.fit()
这个方法进行操作。但是对于数据处理类的方法,通常还有.transform()
这个方法,该方法是用.fit()
拟合后的参数对新的数据进行处理。例如上面的ss.fit_transform(x_train)
也就等价于先.fit(x_train)
,然后再.transform(x_train)
。最后一行则是返回预处理好的数据。
1.2 查看数据信息
通常来说在进行建模前,我们都需要看一下数据集的相关信息。例如输出看看数据的特征维度、样本数量和类别数等等。同时,如果能可视化的话也可以画出来看看,便于更好的理解数据。当然并不是所有的数据都能可视化,所以这不是必须的,但是本次的数据刚好也能可视化,所以下面就进行展示一下。
def visualization(x):
images = x.reshape(-1, 8, 8)
# reshape成一张图片的形状
fig, ax = plt.subplots(3, 5)
for i, axi in enumerate(ax.flat):
image = images[i]
axi.imshow(image)
axi.set(xticks=[], yticks=[])
plt.tight_layout()
plt.show()
if __name__ == '__main__':
x_train, x_test, y_train, y_test = load_data(scale=True)
print(x_train.shape)
print(x_test.shape)
visualization(x_train)
# 输出结果:
#(1257, 64)
#(540, 64)
可以看出训练集和测试集样本数分别为1257和540,样本的特征维度为64,即把一个的图片拉成一个向量表示一张图片来进行建模。如图所示为数据可视化后的结果,由于只有的像素,所以看起来并不清晰。
2 模型选择
2.1 筛选模型
正如前面介绍,不同的模型是由不同的超参数所导致的,所以选择模型的第一步就是确定好有哪些超参数,以及可对于的可能取值。由于此处我们将采用逻辑回归算法对图片进行分类,所以涉及到的超参数有学习率和惩罚系数。但是,由于sklearn在实现的时候对于学习率的设置采用了其它通过计算的方法得到,所以此处就只有惩罚系数一个超参数。
此处以5折交叉验证为例,便可以通过如下代码来进行模型选择:
def model_selection(X, y, k=5):
alphas = [1e-5,3e-5,1e-4,3e-4,1e-3,3e-3,
0.01, 0.03, 0.1, 0.3, 1, 3]
all_models = []
for al in alphas:
model = SGDClassifier(loss='log',
penalty='l2', alpha=al)
kf = KFold(n_splits=k, shuffle=True, random_state=20)
model_score = []
for train_index, test_index in kf.split(X):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
model.fit(X_train, y_train)
s = model.score(X_test, y_test)
model_score.append(s)
all_models.append([np.mean(model_score), al])
print("The best model: ", sorted(all_models, reverse=True)[0])
#输出结果
#The best model: [0.9506608486688168, 3e-05]
第2行代码我们列出了可能的惩罚系数取值,在接下来将通过一个循环来测试不同取值下模型的性能。第5行代码为定义模型,其中SGDClassifier()
表示一系列通过梯度下降算法来进行优化的分类算法(之前的LogisticRegression()
方法并不是),我们可以通过参数设置loss=log
,来选择逻辑回归进行分类;penalty='l2'
表示使用进行正则化;alpha
表示惩罚系数。同时,值得一提的是,按住ctrl
然后点击SGDClassifier()
跳转到其定义处可以发现有其它很多的参数,但是我们在前面的介绍过程中并没有出现这些参数。这是因为sklearn在实现对于算法时,为了提高其性能所以做了很多细节上的考虑,所以就多出了很多可控制的参数,其作用也都进行了注明。
接下来第7行代码KFold()
的作用是定义K折交叉验证的一个类,然后通过kf.split(x)
方法对数据集进行划分并返回每个部分对应的索引,然后第10-11行代码取出对应的样本进行拟合。第14行代码保存同一个模型在不同测试集上的准确率,然后第15行代码取每个模型K折交叉验证后的平均准确率。第16行代码输出其中最好模型对应的准确率以及超参数。
2.2 测试模型
从上面的输出结果可知,当超参数取值为alpha=0.00003
时对应的模型表现最好。现在我们就再用完整训练集对该模型进行训练,然后在测试集上测试其泛化误差。
if __name__ == '__main__':
x_train, x_test, y_train, y_test = load_data(scale=True)
model = SGDClassifier(loss='log', penalty='l2', alpha=0.00003)
model.fit(x_train, y_train)
y_pred = model.predict(x_test)
print(classification_report(y_test, y_pred))
#输出结果
accuracy 0.95 540
macro avg 0.95 0.95 0.95 540
weighted avg 0.95 0.95 0.95 540
3 总结
在这一篇文章中,笔者通过用逻辑回归算法进行手写体分类的实例,介绍了如何通过sklearn提供的方法来实现模型的选择,包括如何载入数据sklearn内置数据集、如何通过sklearn进行特征标准化与交叉验证等。在这一讲中,笔者通过五篇文章大致介绍了如何选择模型以及提高模型泛化能力的基本知识,因此对于这部分的介绍就暂时告一段落。但是在后续的学习过程中,笔者也会时不时的继续补充关于提高模型性能的方法。本次内容就到此结束,感谢阅读!
若有任何疑问与见解,请发邮件至aaa@qq.com并附上文章链接,青山不改,绿水长流,月来客栈见!
引用
[1]示例代码:关注公众号回复“示例代码”即可直接获取!
交流群:883638095