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

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

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 plt

fig, 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()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iky7U4xf-1607432961001)(素材/手写数字/PCA将数据实际绘制成文本,而不是散点图.png)]

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()
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zuxu2xMu-1607432961003)(素材/手写数字/t-SNE绘制sklearn散点图.png)]

  2. 保距映射法(Isomap

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

    # Isomap
    from sklearn.manifold import Isomap
    
    iso = 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 MDS
    
    mds = 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 plt

digits = 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   support
    
               0       1.00      0.97      0.99        38
               1       1.00      0.98      0.99        44
               2       0.95      1.00      0.98        42
               3       0.98      0.96      0.97        46
               4       0.97      1.00      0.99        37
               5       0.98      0.98      0.98        48
               6       1.00      1.00      1.00        52
               7       1.00      0.96      0.98        50
               8       0.94      0.98      0.96        46
               9       0.98      0.98      0.98        47
    
        accuracy                           0.98       450
       macro 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.83333333333333340.9910.980.9560.0840.9897000193595886
特征提取后的数据测试集准确率0.940.9222222222222223

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

  • 9
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值