手写数字图像识别各种算法的比较

1、准备工作

1.1、数据集介绍

使用到了两个手写数字的数据集:

  1. scikit-learn 中的手写数字数据集;
  2. mnist 中的手写数字数据集。

1.1.1、scikit-learn 中的手写数字数据集

首先来看一下 scikit-learn 中的手写数字数据集:

# scikit-learn 中的数据集
from sklearn.datasets import load_digits
digits = load_digits()
print(digits.keys())
dict_keys(['data', 'target', 'frame', 'feature_names', 'target_names', 'images', 'DESCR'])

包含在 scikit-learn 中的数据集通常被保存为 Bunch 对象,里面包含真实数据以及一些数据集信息。

print(digits.images.shape)
# (1797, 8, 8)

可以看到这份图像数据是一个三维矩阵:一共有 1797 个样本,每张图像都是 8 像素 × 8 像素。对前 100 张图片进行可视化:

# 对前 100 张图片进行可视化
import matplotlib.pyplot as pltfig, axes = plt.subplots(10, 10, figsize=(8, 8),subplot_kw={'xticks':[], 'yticks':[]},gridspec_kw=dict(hspace=0.1, wspace=0.1))for i, ax in enumerate(axes.flat):ax.imshow(digits.images[i], cmap='binary', interpolation='nearest')ax.text(0.05, 0.05, str(digits.target[i]),transform=ax.transAxes, color='green')plt.show()

为了在 scikit-learn 中使用数据,我们需要一个维度为 [n_samples, n_features] 的二维特征矩阵——可以将每个样本图像的所有像素都作为特征,也就是将每个 8 像素 × 8 像素平铺成长度为 64 的一维数组。另外,还需要一个目标数组,用来表示每个数字的真实值(标签)。这两份数据已经放在手写数字数据集的 datatarget 属性中:

# 特征矩阵
X = digits.data
print(X.shape)      # (1797, 64)# 目标矩阵
y = digits.target
print(y.shape)      # (1797,)

一共有 1797 个样本和 64 个特征

1.1.2、mnist 中的手写数字数据集

再来看一下 mnist 中的手写数字数据集:

# mnist 中的数据集
from keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
print(train_images.shape, test_images.shape)
# (60000, 28, 28) (10000, 28, 28)

可以看到这份图像数据也是一个三维矩阵:一共有 70000 个样本,每张图片都是 28 像素 × 28 像素。其中训练样本 60000 个,测试样本 10000 个

print(train_labels[0])
# 5
plt.imshow(train_images[0])
plt.show()

1.2、特征提取

1.2.1、主成分分析(PCA)

1、主成分分析(PCA)是一种旋转数据集的方法,旋转后的特征在统计上不相关。在做完这种旋转后,通常是根据新特征对解释数据的重要性来选择它的一个子集。PCA 最常见的一个应用就是高维数据可视化。现在我们来使用 PCA 对两种手写数据数据集进行降维可视化:

# 使用 PCA 对 digits 可视化
from sklearn.decomposition import PCA
# 保留数据的前两个主成分
pca = PCA(n_components=2)
# 对 sklearn 中的手写数字数据集拟合 PCA 模型
pca.fit(digits.data)
# 将数据变换到前两个主成分的方向上
digits_pca = pca.transform(digits.data)print("原始维度:{}".format(str(digits.data.shape)))
# 原始维度:(1797, 64)
print("降维后的维度:{}".format(str(digits_pca.shape)))
# 降维后的维度:(1797, 2)

现在我们对前两个主成分作图:

# 对第一个和第二个主成分作图,按类别着色
plt.scatter(digits_pca[:, 0], digits_pca[:, 1], c=digits.target,edgecolors='none', alpha=5,cmap=plt.cm.get_cmap('Spectral', 10))
plt.colorbar(label='digit label', ticks=range(10))
plt.clim(-0.5, 9.5)
plt.show()

我们还可以将数据实际绘制成文本,而不是散点:

# 构建一个 PCA 模型
pca = PCA(n_components=2)
pca.fit(digits.data)
# 将数据变换到前两个主成分的方向上
digits_pca = pca.transform(digits.data)
colors = ['#476A2A', '#7851B8', '#BD3430', '#4A2D4E', '#875525','#A83683', '#4E655E', '#853541', '#3A3120', '#535D8E']
plt.figure(figsize=(10, 10))
plt.xlim(digits_pca[:, 0].min(), digits_pca[:, 0].max())
plt.ylim(digits_pca[:, 1].min(), digits_pca[:, 1].max())
for i in range(len(digits.data)):# 将数据实际绘制成文本,而不是散点图plt.text(digits_pca[i, 0], digits_pca[i, 1], str(digits.target[i]),color = colors[digits.target[i]],fontdict={'weight':'bold','size':9})
plt.xlabel("First principal component")
plt.ylabel("Second principal component")
plt.show()

2、PCA 的另一个应用是特征提取特征提取背后的思想是,可以找到一种数据表示,比给定的原始表示更适合于分析特征提取很有用,它的一个很好的应用实例就是图像。图像由像素组成,通常存储为红绿蓝(RGB)强度。图像中的对象通常由上千个像素组成,它们只有放在一起才有意义

这里我们启用 PCA白化whitening)选项,它将主成分缩放到相同的尺度。变换后的结果与使用 StandardScaler 相同。白化不仅对应于旋转数据,还对应于缩放数据使其形状是圆形而不是椭圆

from sklearn.decomposition import PCA
pca = PCA(n_components=40, whiten=True, random_state=0).fit(X_train)    # 使用40个主成分表示原特征
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)print("X_train_pca.shape: {}".format(X_train_pca.shape))
# X_train_pca.shape: (1347, 40)
print("X_test_pca.shape: {}".format(X_test_pca.shape))
# X_test_pca.shape: (450, 40)

我们可以使用原数据(64 个特征)与特征提取后的数据分别进行拟合,查看模型的泛化效果。

1.2.2、流形学习

  1. t-SNE

    t-SNE 的思想是找到数据的一个二维表示,然后尝试让在原始特征空间中距离较近的点更加靠近,原始特征中相距较远的点更加远离。

    # t-SNE
    from sklearn.manifold import TSNE
    tsne = TSNE(random_state=42)
    # 使用 fit_transform 方法
    digits_tsne = tsne.fit_transform(digits.data)plt.figure(figsize=(10, 10))
    plt.xlim(digits_tsne[:, 0].min(), digits_tsne[:, 0].max())
    plt.ylim(digits_tsne[:, 1].min(), digits_tsne[:, 1].max())
    for i in range(len(digits.data)):# 将数据实际绘制成文本,而不是散点图plt.text(digits_tsne[i, 0], digits_tsne[i, 1], str(digits.target[i]),color = colors[digits.target[i]],fontdict={'weight':'bold','size':9})
    plt.xlabel("t-SNE feature 0")
    plt.ylabel("t-SNE feature 1")
    plt.show()
    

  2. 保距映射法(Isomap

    Isomap 寻求一种低维表示,以保持点之间的 “距离”。距离是曲面距离的推广。因此,Isomap 不是用毕达哥拉斯定理导出的距离公式来测量纯欧几里德距离,而是沿着发现的流形优化距离。

    # Isomap
    from sklearn.manifold import Isomapiso = Isomap(n_components=2)
    iso.fit(digits.data)
    digits_iso = iso.transform(digits.data)plt.scatter(digits_iso[:, 0], digits_iso[:, 1], c=digits.target,edgecolors='none', alpha=5,cmap=plt.cm.get_cmap('Spectral', 10))
    plt.colorbar(label='digit label', ticks=range(10))
    plt.clim(-0.5, 9.5)
    plt.show()
    

  3. 多维标度法(MDS

    # MDS
    from sklearn.manifold import MDSmds = MDS(n_components=2)
    # mds.fit(digits.data)
    digits_mds = mds.fit_transform(digits.data)plt.scatter(digits_mds[:, 0], digits_mds[:, 1], c=digits.target,edgecolors='none', alpha=5,cmap=plt.cm.get_cmap('Spectral', 10))
    plt.colorbar(label='digit label', ticks=range(10))
    plt.clim(-0.5, 9.5)
    plt.show()
    

各个数字在参数空间中的分离程度还是令人满意的,这其实告诉我们:用一个简单的有监督分类算法就可以完成分类。

2、高斯朴素贝叶斯分类

2.1、加载数据集

数据集选择:

选择的是使用 scikit-learn 中自带的手写数字数据集。

# 加载并可视化手写数字
from sklearn.datasets import load_digits
digits = load_digits()
print(digits.images.shape)
# (1797, 8, 8)
# 特征矩阵
X = digits.data
print(X.shape)      # (1797, 64)# 目标矩阵
y = digits.target
print(y.shape)      # (1797,)

2.2、数字分类

先将数据集分成训练集和测试集,然后用高斯朴素贝叶斯模型来拟合:

# 将数据分成训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)# 用高斯朴素贝叶斯模型来拟合
from sklearn.naive_bayes import GaussianNB
model = GaussianNB()
model.fit(X_train, y_train)     # 拟合数据,构建模型
y_model = model.predict(X_test)     # 做出预测

2.3、评估模型

  1. 准确率

    # 训练集的准确率
    print(model.score(X_train, y_train))    # 0.8574610244988864
    # 测试集的准确率
    print(model.score(X_test, y_test))  # 0.8333333333333334
    
  2. 混淆矩阵

    # 混淆矩阵
    import seaborn as sns
    from sklearn.metrics import confusion_matrix
    mat = confusion_matrix(y_test, y_model)sns.heatmap(mat, square=True, annot=True, cbar=False)
    plt.xlabel('predicted value')
    plt.ylabel('true value')
    

    可以看到,误判的原因在于许多数字 2 被误判成了数字 1 或 8。

  3. 交叉验证

    # 五轮交叉验证
    from sklearn.model_selection import cross_val_score
    print(cross_val_score(model, X, y, cv=5))
    '''
    [0.78055556 0.78333333 0.79387187 0.8718663  0.80501393]
    '''
    
  4. 更加直观的方式

    另一种显示模型特征的直观方式是将样本画出来,然后把预测标签放在左下角,用绿色表示预测正确,用红色表示预测错误:

    # 更直观的方式
    fig, axes = plt.subplots(10,10,figsize=(8,8),subplot_kw={'xticks':[],'yticks':[]},gridspec_kw=dict(hspace=0.1,wspace=0.1))
    test_images = X_test.reshape(-1,8,8)for i, ax in enumerate(axes.flat):ax.imshow(test_images[i],cmap='binary',interpolation='nearest')ax.text(0.05,0.05,str(y_model[i]),transform=ax.transAxes,color='green' if (y_test[i] == y_model[i]) else 'red')
    

2.4、使用特征提取后的数据进行分类

from sklearn.decomposition import PCA
pca = PCA(n_components=40, whiten=True, random_state=0).fit(X_train)
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)# 用高斯朴素贝叶斯模型来拟合
from sklearn.naive_bayes import GaussianNB
model = GaussianNB()
model.fit(X_train_pca, y_train)     # 构建模型
y_model = model.predict(X_test_pca)     # 做出预测# 准确率
# 训练集的准确率
print(model.score(X_train_pca, y_train))    # 0.9621380846325167
# 测试集的准确率
print(model.score(X_test_pca, y_test))      # 0.94

可以看到,准确率有了很大的提升。这说明主成分可能提供了一种更好的数据表示

2.5、总结

GaussianNB 主要用于高维数据。朴素贝叶斯模型的训练和预测速度都很快。该模型对高维稀疏数据的效果很好,对参数的鲁棒性也相对较好。朴素贝叶斯模型是很好的基准模型,常用于非常大的数据集。

附:完整代码

# 加载并可视化手写数字
from sklearn.datasets import load_digits
import matplotlib.pyplot as pltdigits = load_digits()# 特征矩阵
X = digits.data# 目标矩阵
y = digits.target# 将数据分成训练集和测试集
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)# 用高斯朴素贝叶斯模型来拟合
from sklearn.naive_bayes import GaussianNB
model = GaussianNB()
model.fit(X_train, y_train)     # 构建模型
y_model = model.predict(X_test)     # 做出预测# 评估模型# 准确率
# 训练集的准确率
print(model.score(X_train, y_train))
# 测试集的准确率
print(model.score(X_test, y_test))# 混淆矩阵
import seaborn as sns
from sklearn.metrics import confusion_matrix
mat = confusion_matrix(y_test,y_model)sns.heatmap(mat, square=True,annot=True, cbar=False)
plt.xlabel('predicted value')
plt.ylabel('true value')plt.show()# 更直观的方式
fig, axes = plt.subplots(10, 10, figsize=(8, 8),subplot_kw={'xticks':[], 'yticks':[]},gridspec_kw=dict(hspace=0.1, wspace=0.1))
test_images = X_test.reshape(-1, 8, 8)for i, ax in enumerate(axes.flat):ax.imshow(test_images[i], cmap='binary', interpolation='nearest')ax.text(0.05,0.05,str(y_model[i]),transform=ax.transAxes,color='green' if (y_test[i] == y_model[i]) else 'red')plt.show()# 五轮交叉验证
from sklearn.model_selection import cross_val_score
print(cross_val_score(model, X, y, cv=5))# 两轮交叉验证
from sklearn.model_selection import cross_val_score
print(cross_val_score(model, X, y, cv=2))'--------------------------------------------------------------------'
# 使用特征提取后的数据
from sklearn.decomposition import PCA
pca = PCA(n_components=40, whiten=True, random_state=0).fit(X_train)
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)# print("X_train_pca.shape: {}".format(X_train_pca.shape))
# print("X_test_pca.shape: {}".format(X_test_pca.shape))# 用高斯朴素贝叶斯模型来拟合
from sklearn.naive_bayes import GaussianNB
model = GaussianNB()
model.fit(X_train_pca, y_train)     # 构建模型
y_model = model.predict(X_test_pca)     # 做出预测# 准确率
# 训练集的准确率
print(model.score(X_train_pca, y_train))
# 测试集的准确率
print(model.score(X_test_pca, y_test))

3、KNN 算法

3.1、加载数据集

数据集选择:

选择的是使用 scikit-learn 中自带的手写数字数据集。

# 加载并可视化手写数字
from sklearn.datasets import load_digits
digits = load_digits()

3.2、使用 KNN 算法分类

# 使用 KNN 构建模型
from sklearn.model_selection import train_test_split
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)from sklearn.neighbors import KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_train, y_train)   # 拟合数据

3.3、评估模型

# 评估模型
print("Accuracy on training set: {:.3f}".format(clf.score(X_train, y_train)))
# Accuracy on training set: 0.992
print("Accuracy on test set: {:.3f}".format(clf.score(X_test, y_test)))
# Accuracy on test set: 0.987

我们可以调整 “邻居” 的参数,来观察模型的效果:

# 调整参数
clf = KNeighborsClassifier(n_neighbors=5)
clf.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(clf.score(X_train, y_train)))
# Accuracy on training set: 0.991
print("Accuracy on test set: {:.3f}".format(clf.score(X_test, y_test)))
# Accuracy on test set: 0.980
clf = KNeighborsClassifier(n_neighbors=1)
clf.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(clf.score(X_train, y_train)))
# Accuracy on training set: 1.000
print("Accuracy on test set: {:.3f}".format(clf.score(X_test, y_test)))
# Accuracy on test set: 0.991

而距离默认使用的是欧式距离

3.4、总结

  • 参数

    1. 邻居个数

      使用较小的邻居个数(比如 3 个 或 5 个)往往能得到不错的性能。

    2. 数据点之间距离的度量方法

      默认使用欧氏距离

  • 优点

    1. 容易理解,在使用复杂的技术之前,尝试此算法是一种很好地基准方法;
    2. 构建最近邻模型的速度很快,但如果训练集很大(特征数很多或者样本数很大),预测速度可能会很慢。
  • 缺点

    1. 使用 k-NN 算法时,对数据的预处理是很重要的;
    2. 对于很多特征的数据集往往效果不好,对于大多数特征取值为 0 的数据集(所谓的稀疏矩阵)来说,这一算法的效果尤其不好。

附:完整代码

# 加载并可视化手写数字
from sklearn.datasets import load_digits
digits = load_digits()# 使用 KNN 构建模型
from sklearn.model_selection import train_test_split
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)from sklearn.neighbors import KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_train, y_train)# 评估模型
print("Accuracy on training set: {:.3f}".format(clf.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(clf.score(X_test, y_test)))# 调整参数
clf = KNeighborsClassifier(n_neighbors=5)
clf.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(clf.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(clf.score(X_test, y_test)))clf = KNeighborsClassifier(n_neighbors=1)
clf.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(clf.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(clf.score(X_test, y_test)))'----------------------------------------------------------------'from sklearn.decomposition import PCA
pca = PCA(n_components=40, whiten=True, random_state=0).fit(X_train)
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)# 使用 SVM
from sklearn.neighbors import KNeighborsClassifier
clf = KNeighborsClassifier(n_neighbors=3)
clf.fit(X_train_pca, y_train)
clf.predict(X_test_pca)# 准确率
# 训练集的准确率
print(clf.score(X_train_pca, y_train))
# 测试集的准确率
print(clf.score(X_test_pca, y_test))

4、用随机森林识别手写数字

4.1、加载数据集

数据集选择:

选择的是使用 scikit-learn 中自带的手写数字数据集。

# 加载并可视化手写数字
from sklearn.datasets import load_digits
digits = load_digits()

4.2、用随机森林分类

# 用随机森林分类
from sklearn.model_selection import train_test_split
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=1000)
model.fit(X_train, y_train)     # 拟合数据,构建模型
ypred = model.predict(X_test)  # 做出预测

4.3、评估模型

  1. 查看分类器的分类结果报告

    # 分类结果报告
    from sklearn import metrics
    print(metrics.classification_report(ypred, y_test))
    '''precision    recall  f1-score   support0       1.00      0.97      0.99        381       1.00      0.98      0.99        442       0.95      1.00      0.98        423       0.98      0.96      0.97        464       0.97      1.00      0.99        375       0.98      0.98      0.98        486       1.00      1.00      1.00        527       1.00      0.96      0.98        508       0.94      0.98      0.96        469       0.98      0.98      0.98        47accuracy                           0.98       450macro avg       0.98      0.98      0.98       450
    weighted avg       0.98      0.98      0.98       450
    '''# 准确率
    print(model.score(X_train, y_train))    # 1.0
    print(model.score(X_test, y_test))      # 0.98
    

    在训练集上的准确率为 1.0,可能存在过拟合。但在测试集上的表现也还可以。

  2. 混淆矩阵

    # 混淆矩阵
    import seaborn as sns
    from sklearn.metrics import confusion_matrix
    mat = confusion_matrix(y_test, ypred)
    sns.heatmap(mat.T, square=True, annot=True, fmt='d', cbar=False)
    plt.xlabel('true label')
    plt.ylabel('predicted label')
    

4.4、总结

  • 参数

    需要调节的重要参数有 n_estimatorsmax_features,可能还包括预剪枝选项(如 max_depth

    • n_estimators 总是越大越好。对更多的树取平均可以降低过拟合,从而得到鲁棒性更好的集成。不过收益是递减的,而且树越多需要的内存也越多,训练时间也越长。常用的经验法则就是“在你的时间 / 内存允许的情况下尽量多”。
    • max_features 决定每棵树的随机性大小,较小的 max_features 可以降低过拟合。一般来说,好的经验就是使用默认值:对于分类,默认值是 max_features=sqrt(n_features)对于回归,默认值是 max_features=n_features。增大 max_featuresmax_leaf_nodes 有时也可以提高性能。它还可以大大降低用于训练和预测的时间和空间要求。
  • 优点

    用于回归和分类的随机森林是目前应用最广泛的机器学习方法之一。这种方法非常强大,通常不需要反复调节参数就可以给出很好的结果,也不需要对数据进行缩放

    从本质上看,随机森林拥有决策树的所有优点,同时弥补了决策树的一些缺陷。

  • 缺点

    仍然使用决策树的一个原因是需要决策过程的紧凑表示。基本上不可能对几十棵甚至上百棵树做出详细解释,随机森林中树的深度往往比决策树还要大(因为用到了特征子集)。因此,如果你需要以可视化的方式向非专家总结预测过程,那么选择单棵决策树可能更好。

    虽然在大型数据集上构建随机森林可能比较费时间,但在一台计算机的多个 CPU 内核上并行计算也很容易。如果你用的是多核处理器(几乎所有的现代化计算机都是),你可以用 n_jobs 参数来调节使用的内核个数。使用更多的 CPU 内核,可以让速度线性增加(使用 2 个内核,随机森林的训练速度会加倍),但设置 n_jobs 大于内核个数是没有用的。你可以设置 n_jobs=-1 来使用计算机的所有内核

    你应该记住,随机森林本质上是随机的,设置不同的随机状态(或者不设置 random_state 参数)可以彻底改变构建的模型。森林中的树越多,它对随机状态选择的鲁棒性就越好。如果你希望结果可以重现,固定 random_state 是很重要的

    对于维度非常高的稀疏数据(比如文本数据),随机森林的表现往往不是很好。对于这种数据,使用线性模型可能更合适。即使是非常大的数据集,随机森林的表现通常也很好,训练过程很容易并行在功能强大的计算机的多个 CPU 内核上。不过,随机森林需要更大的内存,训练和预测的速度也比线性模型要慢对一个应用来说,如果时间和内存很重要的话,那么换用线性模型可能更为明智

附:完整代码

# 加载并可视化手写数字
import matplotlib.pyplot as plt
from sklearn.datasets import load_digits
digits = load_digits()# 用随机森林分类
from sklearn.model_selection import train_test_split
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=1000)
model.fit(X_train, y_train)
ypred = model.predict(X_test)# 分类结果报告
from sklearn import metrics
print(metrics.classification_report(ypred, y_test))# 混淆矩阵
import seaborn as sns
from sklearn.metrics import confusion_matrix
mat = confusion_matrix(y_test, ypred)
sns.heatmap(mat.T, square=True, annot=True, fmt='d', cbar=False)
plt.xlabel('true label')
plt.ylabel('predicted label')plt.show()

5、梯度提升回归树(梯度提升机)

5.1、加载数据集

数据集选择:

选择的是使用 scikit-learn 中自带的手写数字数据集。

# 加载并可视化手写数字
from sklearn.datasets import load_digits
digits = load_digits()

5.2、用梯度提升回归树(梯度提升机)进行分类

# 用梯度提升回归树(梯度提升机)分类
from sklearn.model_selection import train_test_split
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)from sklearn.ensemble import GradientBoostingClassifier
gbrt = GradientBoostingClassifier(random_state=0)
gbrt.fit(X_train, y_train)  # 拟合数据

5.3、评估模型

# 评估模型
print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))
# Accuracy on training set: 1.000
print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))
# Accuracy on test set: 0.956

由于训练精度达到 100%,所以很可能存在过拟合。为了降低过拟合,我们可以限制最大深度来加强预剪枝,也可以降低学习率

# 限制最大深度来加强预剪枝
gbrt = GradientBoostingClassifier(random_state=0, max_depth=1)
gbrt.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))
# Accuracy on training set: 0.975
print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))
# Accuracy on test set: 0.913
# 降低学习率
gbrt = GradientBoostingClassifier(random_state=0, learning_rate=0.01)
gbrt.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))
# Accuracy on training set: 0.965
print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))
# Accuracy on test set: 0.891

5.4、总结

  • 参数

    梯度提升树模型的主要参数包括树的数量 n_estimators 和学习率 learning_ratelearning_rate 用于控制每棵树对前一棵树的错误的纠正强度。这两个参数高度相关,因为 learning_rate 越低,就需要更多的树来构建具有相似复杂度的模型。随机森林的 n_estimators 值总是越大越好,但梯度提升不同,增大 n_estimators 会导致模型更加复杂,进而可能导致过拟合。通常的做法是根据时间和内存的预算选择合适的 n_estimators,然后对不同的 learning_rate 进行遍历

    另一个重要参数是 max_depth(或 max_leaf_nodes),用于降低每棵树的复杂度。梯度提升模型的 max_depth 通常都设置得很小,一般不超过 5。

  • 优点

    梯度提成回归树是另一种集成方法,通过合并对个决策树来构建一个更为强大的模型。与随机森林方法不同,梯度提升采用连续的方式构造树,每棵树都试图纠正前一棵树的错误。默认情况下,梯度提升回归树没有随机化,而是采用了强预剪枝。梯度提升树通常使用深度很小(1 到 5 之间)的树,这样模型占用的内存更少,预测速度也更快。

    梯度提升背后的主要思想是合并许多简单的模型(在这个语境中叫做弱学习器),比如深度很小的树。每棵树只能对部分数据做出好的预测,因此,添加的树越来越多,可以不断迭代提高性能。

  • 缺点

    其主要缺点是需要仔细调参,而且训练时间可能会比较长。与其他基于树的模型类似,这一算法不需要对数据进行缩放就可以表现得很好,而且也适用于二元特征与连续特征同时存在的数据集。与其他基于树的模型相同,它也通常不适用于高维稀疏数据

附:完整代码

# 加载并可视化手写数字
from sklearn.datasets import load_digits
digits = load_digits()# 用梯度提升回归树(梯度提升机)分类
from sklearn.model_selection import train_test_split
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)from sklearn.ensemble import GradientBoostingClassifier
gbrt = GradientBoostingClassifier(random_state=0)
gbrt.fit(X_train, y_train)# 评估模型
print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))# 限制最大深度来加强预剪枝
gbrt = GradientBoostingClassifier(random_state=0, max_depth=1)
gbrt.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))# 降低学习率
gbrt = GradientBoostingClassifier(random_state=0, learning_rate=0.1, n_estimators=5)
gbrt.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(gbrt.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(gbrt.score(X_test, y_test)))

6、SVM

6.1、加载数据集

数据集选择:

选择的是使用 scikit-learn 中自带的手写数字数据集。

# 加载并可视化手写数字
from sklearn.datasets import load_digits
digits = load_digits()

6.2、使用 SVM 进行分类

# 使用 SVM
from sklearn.model_selection import train_test_split
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)from sklearn.svm import SVC
svm = SVC(kernel='rbf', C=10, gamma=0.1)  # 使用 “高斯核”,gamma 系数取 0.1
svm.fit(X_train, y_train)

6.3、评估数据

print("Accuracy on training set: {:.3f}".format(svm.score(X_train, y_train)))
# Accuracy on training set: 1.000
print("Accuracy on test set: {:.3f}".format(svm.score(X_test, y_test)))
# Accuracy on test set: 0.084

训练集准确率为 1.000,而测试集准确率很小,说明模型太过拟合。我们需要调整参数。

svm = SVC(kernel='rbf', C=100, gamma=0.001)
svm.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(svm.score(X_train, y_train)))
# Accuracy on training set: 1.000
print("Accuracy on test set: {:.3f}".format(svm.score(X_test, y_test)))
# Accuracy on test set: 0.993

虽然可能会存在一些过拟合,但在测试集上表现的也不错。

6.4、使用特征提取过的数据

from sklearn.decomposition import PCA
pca = PCA(n_components=40, whiten=True, random_state=0).fit(X_train)
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)# 使用 SVM
from sklearn.svm import SVC
svm = SVC(kernel='rbf', C=10, gamma=0.1)
svm.fit(X_train_pca, y_train)# 准确率
# 训练集的准确率
print(svm.score(X_train_pca, y_train))  # # 1.0
# 测试集的准确率
print(svm.score(X_test_pca, y_test))    # 0.9222222222222223

相比如之前系数为 “kernel='rbf', C=10, gamma=0.1” 时的准确率提高了更多。SVM 对数据的缩放十分敏感,这里我们使用白化(系数 “whiten=True”)也起到了缩放的作用。

6.4、总结

  • 参数

    核 SVM 的重要参数正则化参数 C核的选择以及与核相关的参数。虽然我们主要讲的是 RBF 核,但 scikit-learn 中还有其他选择。RBF 核只有一个参数 gamma,它是高斯核宽度的倒数gammaC 控制的都是模型复杂度,较大的值都对应更为复杂的模型。因此,这两个参数的设定通常是强烈相关的,应该同时调节。

  • 优点

    核支持向量机是非常强大的模型,在各种数据集上的表现都很好。SVM 允许决策边界很复杂,即使数据只有几个特征它在低维数据和高维数据(即很少特征和很多特征)上的表现都很好

  • 缺点

    对样本个数的缩放表现不好。在有多达 10 000 个样本的数据上运行 SVM 可能表现良好,但如果数据量达到 100 000 甚至更大,在运行时间和内存使用方面可能会面临挑战。

    SVM 的另一个缺点是,预处理数据和调参都需要非常小心。这也是为什么如今很多应用中用的都是基于树的模型,比如随机森林或梯度提升(需要很少的预处理,甚至不需要预处理)。此外,SVM 模型很难检查,可能很难理解为什么会这么预测,而且也难以将模型向非专家进行解释。

附:完整代码

# 加载并可视化手写数字
from sklearn.datasets import load_digits
digits = load_digits()# 使用 SVM
from sklearn.model_selection import train_test_split
X = digits.data
y = digits.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)from sklearn.svm import SVC
svm = SVC(kernel='rbf', C=10, gamma=0.1)
svm.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(svm.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(svm.score(X_test, y_test)))svm = SVC(kernel='rbf', C=100, gamma=0.001)
svm.fit(X_train, y_train)print("Accuracy on training set: {:.3f}".format(svm.score(X_train, y_train)))
print("Accuracy on test set: {:.3f}".format(svm.score(X_test, y_test)))'--------------------------------------------------------------------------'
# 使用特征提取后的数据
from sklearn.decomposition import PCA
pca = PCA(n_components=40, whiten=True, random_state=0).fit(X_train)
X_train_pca = pca.transform(X_train)
X_test_pca = pca.transform(X_test)# 使用 SVM
from sklearn.svm import SVC
svm = SVC(kernel='rbf', C=10, gamma=0.1)
svm.fit(X_train_pca, y_train)# 准确率
# 训练集的准确率
print(svm.score(X_train_pca, y_train))
# 测试集的准确率
print(svm.score(X_test_pca, y_test))

7、神经网络

7.1、加载数据集

数据集选择:

选择使用 minst 数据集中的手写数字数据集。

导入相应的库:

from keras.utils import to_categorical
from keras import models, layers, regularizers
from keras.optimizers import RMSprop
from keras.datasets import mnist
import matplotlib.pyplot as plt
# 加载数据集
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
print(train_images.shape, test_images.shape)
# (60000, 28, 28) (10000, 28, 28)

将图片由二维铺开成一维:

# 将图片由二维铺开成一维
train_images = train_images.reshape((60000, 28*28)).astype('float')
test_images = test_images.reshape((10000, 28*28)).astype('float')
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

7.2、搭建一个神经网络(示例)

# 搭建神经网络
network = models.Sequential()
network.add(layers.Dense(units=15, activation='relu', input_shape=(28*28, ),))
network.add(layers.Dense(units=10, activation='softmax'))

7.3、神经网络训练

7.3.1、编译确定优化器和损失函数

# 编译步骤
network.compile(optimizer=RMSprop(lr=0.001), loss='categorical_crossentropy', metrics=['accuracy'])

7.3.2、训练网络:确定训练的数据、训练的轮数和每次训练的样本数等

# 训练网络,用fit函数, epochs表示训练多少个回合, batch_size表示每次训练给多大的数据
network.fit(train_images, train_labels, epochs=20, batch_size=128, verbose=2)

7.4、用训练好的模型进行预测,并在测试集上做出评价

# 来在测试集上测试一下模型的性能吧
y_pre = network.predict(test_images[:5])
print(y_pre, test_labels[:5])
test_loss, test_accuracy = network.evaluate(test_images, test_labels)
print("test_loss:", test_loss, "    test_accuracy:", test_accuracy)

7.5、完整的代码

  • 使用全连接神经网络

    from keras.utils import to_categorical
    from keras import models, layers, regularizers
    from keras.optimizers import RMSprop
    from keras.datasets import mnist
    import matplotlib.pyplot as plt# 加载数据集
    (train_images, train_labels), (test_images, test_labels) = mnist.load_data()train_images = train_images.reshape((60000, 28*28)).astype('float')
    test_images = test_images.reshape((10000, 28*28)).astype('float')
    train_labels = to_categorical(train_labels)
    test_labels = to_categorical(test_labels)network = models.Sequential()
    network.add(layers.Dense(units=128, activation='relu', input_shape=(28*28, ),kernel_regularizer=regularizers.l1(0.0001)))
    network.add(layers.Dropout(0.01))
    network.add(layers.Dense(units=32, activation='relu',kernel_regularizer=regularizers.l1(0.0001)))
    network.add(layers.Dropout(0.01))
    network.add(layers.Dense(units=10, activation='softmax'))# 编译步骤
    network.compile(optimizer=RMSprop(lr=0.001), loss='categorical_crossentropy', metrics=['accuracy'])# 训练网络,用fit函数, epochs表示训练多少个回合, batch_size表示每次训练给多大的数据
    network.fit(train_images, train_labels, epochs=20, batch_size=128, verbose=2)# 来在测试集上测试一下模型的性能吧
    test_loss, test_accuracy = network.evaluate(test_images, test_labels)
    print("test_loss:", test_loss, "    test_accuracy:", test_accuracy)
    
    Epoch 1/20
    469/469 - 1s - loss: 2.6369 - accuracy: 0.7405
    Epoch 2/20
    469/469 - 0s - loss: 0.7591 - accuracy: 0.8705
    Epoch 3/20
    469/469 - 0s - loss: 0.6083 - accuracy: 0.9010
    Epoch 4/20
    469/469 - 0s - loss: 0.5309 - accuracy: 0.9165
    Epoch 5/20
    469/469 - 0s - loss: 0.4678 - accuracy: 0.9299
    Epoch 6/20
    469/469 - 0s - loss: 0.4241 - accuracy: 0.9391
    Epoch 7/20
    469/469 - 0s - loss: 0.3930 - accuracy: 0.9429
    Epoch 8/20
    469/469 - 0s - loss: 0.3764 - accuracy: 0.9462
    Epoch 9/20
    469/469 - 0s - loss: 0.3455 - accuracy: 0.9530
    Epoch 10/20
    469/469 - 0s - loss: 0.3166 - accuracy: 0.9566
    Epoch 11/20
    469/469 - 0s - loss: 0.3156 - accuracy: 0.9560
    Epoch 12/20
    469/469 - 0s - loss: 0.2935 - accuracy: 0.9600
    Epoch 13/20
    469/469 - 0s - loss: 0.2822 - accuracy: 0.9618
    Epoch 14/20
    469/469 - 0s - loss: 0.2698 - accuracy: 0.9623
    Epoch 15/20
    469/469 - 0s - loss: 0.2666 - accuracy: 0.9636
    Epoch 16/20
    469/469 - 0s - loss: 0.2548 - accuracy: 0.9657
    Epoch 17/20
    469/469 - 0s - loss: 0.2482 - accuracy: 0.9661
    Epoch 18/20
    469/469 - 0s - loss: 0.2415 - accuracy: 0.9663
    Epoch 19/20
    469/469 - 0s - loss: 0.2359 - accuracy: 0.9687
    Epoch 20/20
    469/469 - 0s - loss: 0.2315 - accuracy: 0.9680
    313/313 [==============================] - 0s 389us/step - loss: 0.2709 - accuracy: 0.9667
    test_loss: 0.27094602584838867     test_accuracy: 0.96670001745224
    
  • 使用卷积神经网络

    from keras.utils import to_categorical
    from keras import models, layers
    from keras.optimizers import RMSprop
    from keras.datasets import mnist
    # 加载数据集
    (train_images, train_labels), (test_images, test_labels) = mnist.load_data()# 搭建LeNet网络
    def LeNet():network = models.Sequential()network.add(layers.Conv2D(filters=6, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1)))network.add(layers.AveragePooling2D((2, 2)))network.add(layers.Conv2D(filters=16, kernel_size=(3, 3), activation='relu'))network.add(layers.AveragePooling2D((2, 2)))network.add(layers.Conv2D(filters=120, kernel_size=(3, 3), activation='relu'))network.add(layers.Flatten())network.add(layers.Dense(84, activation='relu'))network.add(layers.Dense(10, activation='softmax'))return network
    network = LeNet()
    network.compile(optimizer=RMSprop(lr=0.001), loss='categorical_crossentropy', metrics=['accuracy'])train_images = train_images.reshape((60000, 28, 28, 1)).astype('float') / 255
    test_images = test_images.reshape((10000, 28, 28, 1)).astype('float') / 255
    train_labels = to_categorical(train_labels)
    test_labels = to_categorical(test_labels)# 训练网络,用fit函数, epochs表示训练多少个回合, batch_size表示每次训练给多大的数据
    network.fit(train_images, train_labels, epochs=10, batch_size=128, verbose=2)
    test_loss, test_accuracy = network.evaluate(test_images, test_labels)
    print("test_loss:", test_loss, "    test_accuracy:", test_accuracy)
    
    Epoch 1/10
    469/469 - 4s - loss: 0.3614 - accuracy: 0.8931
    Epoch 2/10
    469/469 - 4s - loss: 0.0982 - accuracy: 0.9702
    Epoch 3/10
    469/469 - 4s - loss: 0.0617 - accuracy: 0.9811
    Epoch 4/10
    469/469 - 4s - loss: 0.0478 - accuracy: 0.9851
    Epoch 5/10
    469/469 - 4s - loss: 0.0380 - accuracy: 0.9883
    Epoch 6/10
    469/469 - 4s - loss: 0.0313 - accuracy: 0.9900
    Epoch 7/10
    469/469 - 4s - loss: 0.0267 - accuracy: 0.9916
    Epoch 8/10
    469/469 - 4s - loss: 0.0233 - accuracy: 0.9926
    Epoch 9/10
    469/469 - 4s - loss: 0.0196 - accuracy: 0.9938
    Epoch 10/10
    469/469 - 4s - loss: 0.0173 - accuracy: 0.9946
    313/313 [==============================] - 0s 1ms/step - loss: 0.0325 - accuracy: 0.9897
    test_loss: 0.03245333582162857     test_accuracy: 0.9897000193595886
    

7.6、总结

  • 参数

    估计神经网络的复杂度最重要的参数是层数和每层的隐单元个数。你应该首先设置 1 个或 2 个隐层,然后可以逐步增加。每个隐层的结点个数通常与输入特征个数接近,但在几千个结点时很少会多于特征个数。

    在考虑神经网络的模型复杂度时,一个有用的度量是学到的权重(或系数)的个数

    神经网络调参的常用方法是,首先创建一个大到足以过拟合的网络,确保这个网络可以对任务进行学习。知道训练数据可以被学习之后,要么缩小网络,要么增大 alpha 来增强正则化,这可以提高泛化性能。

    在我们的实验中,主要关注模型的定义:层数、每层的结点个数、正则化和非线性。这些内容定义了我们想要学习的模型。还有一个问题是,如何学习模型或用来学习参数的算法,这一点由 solver 参数设定。solver 有两个好用的选项。默认选项是 'adam',在大多数情况下效果都很好,但对数据的缩放相当敏感(因此,始终将数据缩放为均值为 0、方差为 1 是很重要的)。另一个选项是 'lbfgs',其鲁棒性相当好,但在大型模型或大型数据集上的时间会比较长。还有更高级的 'sgd' 选项,许多深度学习研究人员都会用到。'sgd' 选项还有许多其他参数需要调节,以便获得最佳结果。你可以在用户指南中找到所有这些参数及其定义。当你开始使用 MLP 时,我们建议使用 'adam''lbfgs'

  • 优点

    它的主要优点之一是能够获取大量数据中包含的信息,并构建无比复杂的模型。给定足够的计算时间和数据,并且仔细调节参数,神经网络通常可以打败其他机器学习算法(无论是分类任务还是回归任务)。

  • 缺点

    神经网络——特别是功能强大的大型神经网络——通常需要很长的训练时间。它还需要仔细地预处理数据,正如我们这里所看到的。与 SVM 类似,神经网络在“均匀”数据上的性能最好,其中“均匀”是指所有特征都具有相似的含义。如果数据包含不同种类的特征,那么基于树的模型可能表现得更好。神经网络调参本身也是一门艺术。


8、对比

高斯朴素贝叶斯模型 KNN 随机森林 梯度提升回归树 SVM 神经网络
原数据测试集准确率 0.8333333333333334 0.991 0.98 0.956 0.084 0.9897000193595886
特征提取后的数据测试集准确率 0.94 0.9222222222222223

注:表中的准确率都是在默认参数下。

综合案例——手写数字图像处理算法比较相关推荐

  1. 机器学习之算法案例手写数字识别

    算法案例手写数字识别 MNIST数据集是机器学习领域中非常经典的一个数据集,由60000个 训练样本和10000个测试样本组成,每个样本都是一张28 * 28像素的灰度 手写数字图片. 选择算法,并保 ...

  2. 基于深度学习的手写数字识别算法Python实现

    摘 要 深度学习是传统机器学习下的一个分支,得益于近些年来计算机硬件计算能力质的飞跃,使得深度学习成为了当下热门之一.手写数字识别更是深度学习入门的经典案例,学习和理解其背后的原理对于深度学习的理解有 ...

  3. 手写识别python_Python徒手实现识别手写数字—图像识别算法(K最近邻)

    Python徒手实现识别手写数字-图像识别算法(K最近邻) 写在前面 这一段的内容可以说是最难的一部分之一了,因为是识别图像,所以涉及到的算法会相比之前的来说比较困难,所以我尽量会讲得清楚一点. 而且 ...

  4. Python徒手实现识别手写数字—图像识别算法(K最近邻)

    Python徒手实现识别手写数字-图像识别算法(K最近邻) 写在前面 这一段的内容可以说是最难的一部分之一了,因为是识别图像,所以涉及到的算法会相比之前的来说比较困难,所以我尽量会讲得清楚一点. 而且 ...

  5. 毕业设计 手写数字识别算法研究与实现(源码+论文)

    文章目录 0 项目说明 1 系统概述 1.1 系统实现环境 2 研究方法 2.1 图像预处理阶段 2.2 特征提取阶段 2.3 数字识别阶段 3 研究结论 4 论文概览 5 最后 0 项目说明 手写数 ...

  6. pytorch实战案例-手写数字分类-卷积模型——深度AI科普团队

    文章目录 数据准备 导入需要的模块 使用GPU训练 将数据转换为tensor 导入训练集和测试集 数据加载器 数据展示 创建模型 将模型复制到GPU 损失函数 定义训练和测试函数 开始训练 源码已经上 ...

  7. pytorch实战案例-手写数字分类-全链接模型——深度AI科普团队

    文章目录 @[TOC] 数据准备 导入需要的模块 将数据转换为tensor 导入训练集和测试集 数据加载器 数据展示 创建模型 定义损失函数 定义优化函数 定义训练和测试函数 开始训练 源码已经上传: ...

  8. 手写数字识别——算法

    识别方法一:基于结构模式--脱机识别 例子:基于基元和七段式数字的手写数字识别 日常生活中经常可以看到用七段式数字表示数字,如电子手表的数字显示等.它用火柴棍拼图的方式能够简单有效的表示出0~9,10 ...

  9. 2 机器学习 K近邻算法(KNN) 学习曲线 交叉验证 手写数字识别

    机器学习 1 K-近邻算法介绍 1.1 分类问题 分类问题:根据已知样本的某些特征,判断一个未知样本属于哪种样本类别. 与回归问题相比,分类问题的输出结果是离散值,用于指定输入的样本数据属于哪个类别. ...

最新文章

  1. jvm十一:类加载器双亲委托机制
  2. 敢问路在何方——项目经理成长手记
  3. 【转载】redis开启远程访问
  4. ubuntu下安装并配置VIM编辑器
  5. zigbee 初识zigbee,ieee802,论坛
  6. java inputstream字节流_Java输入输出流ByteArrayInputStream和ByteArrayOutPutStream
  7. swift学习笔记-----swift中的指针
  8. leetcode 628. 三个数的最大乘积(排序)
  9. selenium-python:运行后报浏览器不兼容 disconnected: unable to connect to renderer
  10. 系统学习 TypeScript(四)——变量声明的初步学习
  11. 容器已成运维必备能力,你知道它是如何出现的吗?
  12. 如何解决生产环境 Flink 应用的技术难题?
  13. 虚拟化安全怎么做?靠安全设备虚拟化还是换个思路?
  14. 一次追踪h5游戏中google广告的经历
  15. 360浏览器打不开html5文件,电脑360浏览器打不开网页原因-浏览器打不开解决方法 - 系统家园...
  16. Kali学习 | 漏洞扫描:3.1 Nessus安装、配置和新建扫描任务
  17. Python深度学习-第一章、什么是深度学习
  18. MySQL运行机制原理架构
  19. 星空主题设计理念_请星星设计理念
  20. 微信公众号开发本地环境搭建

热门文章

  1. php不显示notice,解决PHP显示Warning和Notice等问题
  2. 非线性混合效应 NLME模型对抗哮喘药物茶碱动力学研究
  3. 互联网历史上50个最重要的时刻
  4. 思科模拟器 --- 三层交换机的基本配置
  5. Linux电脑睡眠后黑屏打不开,Win10系统下电脑休眠或睡眠无法唤醒屏幕黑屏打不开的解决方法...
  6. 牛逼!腾讯竟然打响了反对996的第一枪....
  7. mac os下 Ruby环境安装方法
  8. 坎坎坷坷,跌跌撞撞,我的2019年度总结
  9. 计算机的开机键的英语,每次开机出现英文(按任意键),才能进入电脑,好烦...
  10. 云课里计算机,【241天】网易云课堂计算机专业基础课程系列——计算机专业导论(6)...