机器学习——训练MNIST数据集

MNIST数据集介绍

MNIST是一个手写数字数据集,数据集包含60000个训练数据与10000个测试数据,由500位作者手写而成,其中250位的手写作为训练数据,其余250位的手写作为测试数据。
通过这里MNIST数据集获取数据。

根据官方描述,该数据集的每个数据都被统一格式化成28 * 28像素大小,并已将手写数字居中于图像中央,即我们无需对数据集进行过多的预处理。

观察数据集可以发现,训练集60000,测试集10000,每张图片拥有784个特征。

X_train, y_train = load_train()
X_test, y_test = load_test()

print(X_train.shape)	# (60000, 784) (10000, 784)
print(y_train.shape)	# (60000,) (10000,)

使用matplotlib绘制其中一张图如下

image = X_train[0].reshape(28, 28)
plt.imshow(image, cmap='binary', interpolation="nearest")
plt.show()

mnist
显而易见,训练集的第一张手写图是数字“5”

训练思路

降维

通过观察数据发现,每张图片的边缘存在大量空白,即使删掉这部分内容,也不会丢失太多信息。针对这种情况,考虑使用降维来去除这些无关信息,降维还能够将两个相邻特征合并为一个,以此来降低维度,提升训练速度。

选择模型

本文将尝试使用几种模型对数据集进行训练:K邻近、SVM(支持向量机)、随机森林
其中,由于SVM是一个二元分类器,所以将会对10个数字分别训练10个分类器。
为了给模型找到合适的超参数,以更好的拟合数据,则使用GridSearchCV(网格搜索)对超参数进行微调。
最后采用模型集成来做预测。

评估模型

通过两种方式来评估模型性能,分别是交叉验证与混淆矩阵。

训练模型

PCA

使用PCA(主成分分析)降维,问题在于如何设置合适的维度,n_components参数用于设置需要保留的主成分数;
若不确定其主成分数,可以设置值为[0.0, 1.0],此时值表示要保留的方差率。

from sklearn.decomposition import PCA

# 使用主成分分析降维,保留95%的可解释方差比
pca = PCA(n_components=0.95)
X_train = pca.fit_transform(X_train)

print(pca.n_components_) # 输出154,即保留了154个主成分
KNN

KNN(K-邻近值)感觉是比较适合这项分类任务的模型。
使用sklearn提供的网格搜索,找出合适的模型超参数。

网格搜索

from sklearn.model_selection import GridSearchCV

# 尝试以下参数组合
param_grid= [
        {'weights': ['uniform', 'distance'], 'n_neighbors': [5, 10, 15]}
    ]
grid_search = GridSearchCV(model, param_grid, cv=3, scoring='neg_mean_squared_error', return_train_score=True, n_jobs=-1)
grid_search.fit(X_train, y_train)

# 打印最佳超参数组合
print("best_params : ", grid_search.best_params_)
# 打印最佳超参数模型
print("best_estimator : ", grid_search.best_estimator_)
# 打印所有超参数组合分值
cvres = grid_search.cv_results_
for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
    print(np.sqrt(-mean_score), params)
    
# 输出结果如下
best_params :  {'n_neighbors': 5, 'weights': 'distance'}
best_estimator :  KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
                     metric_params=None, n_jobs=None, n_neighbors=5, p=2,
                     weights='distance')
1.07060730429042 {'n_neighbors': 5, 'weights': 'uniform'}
1.031649165171959 {'n_neighbors': 5, 'weights': 'distance'}
1.1418844074598795 {'n_neighbors': 10, 'weights': 'uniform'}
1.1020435563080073 {'n_neighbors': 10, 'weights': 'distance'}
1.20797350964332 {'n_neighbors': 15, 'weights': 'uniform'}
1.1664904628842878 {'n_neighbors': 15, 'weights': 'distance'}

查看搜索结果可以发现,最佳的超参数组合是{'n_neighbors': 5, 'weights': 'distance'},接下来可以设置n_neighbors小于5的值继续尝试,直到找到最佳模型的超参数。

最终找到合适的超参数时,训练KNN模型,并使用交叉验证观察结果

from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score

kn_clf = KNeighborsClassifier(n_neighbors=4, weights='distance', n_jobs=-1)

# 进行3次交叉验证
scores = cross_val_score(kn_clf, X_train, y_train, cv=3, n_jobs=-1, scoring='accuracy')
# 输出每次验证的正确率
print(scores)
# 输出平均正确率
print(scores.mean())

# 输出结果如下
[0.97330534 0.97069853 0.97154573]
0.9718498685729129

97%,可以看出,KNN模型在交叉验证上获取了不错的正确率。

继续使用同样的思路训练以下模型

SVM

SVM是二元分类器,有一种策略是为每个数字训练一个二元分类器,比如数字“0”分类器,只判断是0与不是0;针对该数据集则需要训练10个分类器。
使用OneVsRestClassifier类将模型包装成一对多的二元分类器。

from sklearn.svm import SVC
from sklearn.multiclass import OneVsRestClassifier

svm_clf = OneVsRestClassifier(SVC())
随机森林
from sklearn.ensemble import RandomForestClassifier

# 训练500颗决策树,最大深度为16
forest_clf = RandomForestClassifier(n_estimators=500, max_depth=16, random_state=42, n_jobs=-1)

# 交叉验证结果如下
[0.92420611 0.94269427 0.96455392]
0.9438181003223488

随机森林在测试数据集上获得了94.3%的正确率。

集成模型

将多个模型集成为一个模型,以此预测出的结果是综合了子模型的结果得出的,这种方法有助于提高正确性。

from sklearn.ensemble import VotingClassifier

kn_clf = KNeighborsClassifier(n_neighbors=4, weights='distance', n_jobs=-1)
svm_clf = SVC(probability=True)
forest_clf = RandomForestClassifier(n_estimators=500, max_depth=16, random_state=42, n_jobs=-1)

voting = VotingClassifier(estimators=[
    ('knn', kn_clf), 
    ('svm', svm_clf), 
    ('tree', forest_clf)
], voting='soft')
voting.fit(X_train, y_train)

评估模型

通常训练好的模型在投入生产使用之前,需要对其性能做评估,虽然我们的模型通过交叉验证得到了不错的结果,但模型很可能过拟合,还有一种评估分类器性能的好方法是混淆矩阵

混淆矩阵

该方法的思路是检查A类别被判断为B类别的次数,若次数过高,则需要采取措施降低混淆的次数。
为了建立混淆矩阵,首先需要一组预测数据,然后调用confusion_matrix()建立混淆矩阵

from sklearn.model_selection import cross_val_predict
from sklearn.metrics import confusion_matrix

# 得到一组预测值
y_predict = cross_val_predict(kn_clf, X_train, y_train, cv=3)
# 建立混淆矩阵
matrix = confusion_matrix(y_train, y_predict)

混淆矩阵输出的是所有类别两两之间判断正确与错误的个数,用MNIST的测试集计算出的混淆矩阵做个示例:

[[ 962    1    0    0    0    5   10    1    1    0]
 [   0 1127    2    2    0    0    4    0    0    0]
 [  11   20  947    8    3    3    5   28    5    2]
 [   0    3    8  950    0   16    1   12   14    6]
 [   0   16    2    0  911    1    5    1    0   46]
 [   3    8    1   30    2  822   11    2    5    8]
 [  10    4    1    1    3    2  936    0    1    0]
 [   0   34    0    0    1    3    0  965    1   24]
 [   7   10    7   20   14   24    4   10  868   10]
 [   5    8    3    9   22    4    1   29    6  922]]

可以看到,这是一个10 * 10的矩阵,1-10列代表数字0-9,第一行表示数字0被正确分类的个数为962,被误分类成数字1的个数是1,被误分类成数字2的个数是0,以此类推…

用matplotlib将矩阵画出来,可以直观地看出数据情况,颜色越亮表示该数字被正确分类的个数越多,我们需要关注的是颜色相比暗淡的数字,比如数字5,说明数字5被正确分类的数量较少,即被错误分类的数量较多。
metrix
通过观察混淆矩阵能够有针对性的继续改进模型,比如对数据进行预处理,或者适当地增加样本数量等。

测试集

当模型在交叉验证与混淆矩阵表现都不错的时候,可以用上测试数据集了,测试集对于模型来说是陌生的,若模型过拟合训练数据,则很容易在测试集上表现差。

# 计算模型在测试集上的准确性
predict = model.predict(X_test)
correct = sum(predict == y_test)
print(correct / len(y_test))

结语

机器学习的过程,需要不停地探索,从数据预处理,到模型优化,甚至是花更多精力在收集整理数据集上,需要不断通过结果反馈来修正训练过程。有经验的大佬们,应该会花更少的时间就能训练出很好的模型,训练一个模型的成本还是比较高的,即又花费硬件资源,又花费时间。

  • 2
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值