卷积神经网络:从基础到时尚数据集分类实战
1. 池化层与特征处理基础
在处理图像数据时,降低数据的维度空间至关重要。卷积层是实现这一目标的有效手段,而最大池化层(Max pooling layers)也有相同的作用,能进一步降低维度。
最大池化的操作很直观,就像其名字所暗示的那样,我们在特征图上滑动一个窗口,然后取窗口内的最大值。以一个对角线特征图为例,使用 2x2 的窗口进行最大池化:
1. 计算窗口内值的最大值,如 max(0, 255, 255, 0),结果为 255。
2. 依此类推完成后续窗口的操作。
通过 2x2 窗口的最大池化,我们能将一个 3x3 的特征图变为 2x2 的,成功减少了一行一列。除了最大池化,还有平均池化(average pooling)和最小池化(min pooling),不过最大池化是最常用的。
接下来是扁平化(Flattening)操作。我们之前通过卷积神经网络和最大池化层,尽可能地构建了简洁且有表现力的特征表示。而扁平化就是将卷积和最大池化后的多维数组(如 2x2 矩阵)转换为一行训练数据。以下是代码示例:
import numpy as np
max_pooled = np.array([[255, 255], [255, 255]])
flattened = max_pooled.flatten()
原本 5x5 的像素强度矩阵现在变成了具有四个特征的单行数据,这样就可以输入到全连接神经网络中。
2. 全连接层与输出
全连接层(Fully-connected layers)的作用是将卷积、最大池化和扁平化后的输入映射到目标类别。在全连接层中,每个输入都与下一层的每个神经元或节点相连。连接的强度(权重)和网络中每个节点的偏置项是模型的参数,在训练过程中会不断优化这些参数,以最小化目标函数。
模型的最后一层是输出层(output layer),它给出模型的预测结果。输出层的神经元数量和应用的激活函数取决于要解决的问题类型,如回归、二分类或多分类。
3. 使用 Keras 构建卷积神经网络对 Zalando 研究数据集进行图像分类
Zalando 研究的时尚数据集(Fashion MNIST)包含 70,000 张灰度图像,代表 10 种不同的服装类别,包括 T 恤/上衣、裤子、毛衣等。这个数据集是德国电商公司 Zalando 发布的,旨在为研究人员提供一个替代经典手写数字 MNIST 数据集的选择,并且它的预测难度相对更高。
以下是构建卷积神经网络对该数据集进行图像分类的详细步骤:
1.
克隆仓库并安装必要的库
- 从终端运行以下命令克隆仓库到桌面:
cd ~/Desktop/
git clone git@github.com:zalandoresearch/fashion - mnist.git
- 安装 Keras 和 TensorFlow:
pip install keras
pip install tensorflow
- 导入所需的库
import sys
import numpy as np
import pandas as pd
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPool2D
from keras.utils import np_utils, plot_model
from PIL import Image
import matplotlib.pyplot as plt
Keras 是一个流行的 Python 深度学习库,它可以运行在 TensorFlow、CNTK 或 Theano 等机器学习框架之上。Python 图像处理库(PIL)用于可视化 Keras 网络的拓扑结构。
3.
加载数据
- 确保
fashion - mnist/utils/
在路径中:
sys.path.append('/Users/Mike/Desktop/fashion - mnist/utils/')
import mnist_reader
- 使用辅助脚本加载数据:
X_train, y_train = mnist_reader.load_mnist('/Users/Mike/Desktop/fashion - mnist/data/fashion')
X_test, y_test = mnist_reader.load_mnist('/Users/Mike/Desktop/fashion - mnist/data/fashion')
- 查看数据形状和类型
print(X_train.shape, y_train.shape)
print(X_test.shape, y_test.shape)
print(type(X_train))
print(type(y_train))
print(type(X_test))
print(type(y_test))
训练集有 60,000 张图像,测试集有 10,000 张图像,每张图像当前是长度为 784 的向量。
5.
可视化数据
- 由于图像是灰度的,我们将向量重塑为 28x28 矩阵来可视化:
image_1 = X_train[0].reshape(28, 28)
plt.axis('off')
plt.imshow(image_1, cmap='gray')
- 查看图像所属的类别:
y_train[0]
- 创建编码值到类别名称的映射:
mapping = {0: "T - shirt/top", 1: "Trouser", 2: "Pullover", 3: "Dress", 4: "Coat", 5: "Sandal", 6: "Shirt", 7: "Sneaker", 8: "Bag", 9: "Ankle Boot"}
- 定义一个辅助函数来可视化多个图像:
def show_fashion_mnist(plot_rows, plot_columns, feature_array, target_array, cmap='gray', random_seed=None):
if random_seed is not None:
np.random.seed(random_seed)
feature_array_indices = np.random.randint(0, feature_array.shape[0], size=plot_rows * plot_columns)
fig, ax = plt.subplots(plot_rows, plot_columns, figsize=(18, 18))
reshaped_images_list = []
for feature_array_index in feature_array_indices:
reshaped_image = feature_array[feature_array_index].reshape((28, 28))
image_class = mapping[target_array[feature_array_index]]
reshaped_images_list.append((reshaped_image, image_class))
counter = 0
for row in range(plot_rows):
for col in range(plot_columns):
ax[row, col].axis('off')
ax[row, col].imshow(reshaped_images_list[counter][0], cmap=cmap)
ax[row, col].set_title(reshaped_images_list[counter][1])
counter += 1
show_fashion_mnist(4, 4, X_train, y_train, random_seed=72)
运行此函数可以随机显示多个图像,多次运行且不指定随机种子可以观察不同的图像。我们会发现有些类别在视觉上很相似,有些则差异较大,这对理解模型的强弱表现很有帮助。
6.
查看目标类别的分布
y = pd.Series(np.concatenate((y_train, y_test)))
plt.figure(figsize=(10, 6))
plt.bar(x=[mapping[x] for x in y.value_counts().index], height=y.value_counts())
plt.xlabel("Class")
plt.ylabel("Number of Images per Class")
plt.title("Distribution of Target Classes")
结果显示各类别图像数量均衡,无需进行上采样或下采样。
7.
数据预处理
- 确认图像像素值范围在 0 到 255 之间:
print(X_train.max())
print(X_train.min())
print(X_test.max())
print(X_test.min())
- 对数据进行归一化,将像素值缩放到 0 到 1 之间:
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
X_train /= 255
X_test /= 255
- 重塑数据为 28x28 矩阵,并明确声明通道数为 1:
X_train = X_train.reshape(X_train.shape[0], 28, 28, 1)
X_test = X_test.reshape(X_test.shape[0], 28, 28, 1)
- 对目标向量进行 one - hot 编码:
y_train = np_utils.to_categorical(y_train, 10)
y_test = np_utils.to_categorical(y_test, 10)
- 构建模型
model = Sequential()
model.add(Conv2D(filters=35, kernel_size=(3, 3), input_shape=(28, 28, 1), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Conv2D(filters=35, kernel_size=(3, 3), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Conv2D(filters=45, kernel_size=(3, 3), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(64, activation='relu'))
model.add(Dense(32, activation='relu'))
model.add(Dense(10, activation='softmax'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
- 逐行解释代码:
- 第一行:实例化模型对象。
- 第二行:添加第一个卷积层,有 35 个 3x3 的卷积核,输入形状为 28x28x1,激活函数为 ReLU。
- 第三行:添加第一个最大池化层,窗口大小为 2x2。
- 第四行和第五行:添加第二个卷积层和最大池化层。
- 第六行和第七行:添加第三个卷积层和最大池化层,第三个卷积层有 45 个卷积核。
- 第八行:扁平化卷积神经网络的输出。
- 第九行和第十行:添加全连接层,分别有 64 和 32 个神经元,激活函数为 ReLU。
- 第十一行:输出层有 10 个神经元,对应 10 个目标类别,激活函数为 Softmax。
- 第十二行:编译模型,使用 Adam 优化器,分类交叉熵损失函数,评估指标为准确率。
- 查看模型摘要:
model.summary()
- 可视化模型拓扑结构(需安装 pydot):
plot_model(model, to_file='Conv_model1.png', show_shapes=True)
Image.open('Conv_model1.png')
- 训练模型
my_fit_model = model.fit(X_train, y_train, epochs=25, validation_data=(X_test, y_test))
如果担心硬件性能,可以将训练轮数减少到 10 轮。
10.
可视化训练和验证损失及准确率
plt.plot(my_fit_model.history['val_loss'], label="Validation")
plt.plot(my_fit_model.history['loss'], label="Train")
plt.xlabel("Epoch", size=15)
plt.ylabel("Cat. Crossentropy Loss", size=15)
plt.title("Conv Net Train and Validation loss over epochs", size=18)
plt.legend()
plt.plot(my_fit_model.history['val_acc'], label="Validation")
plt.plot(my_fit_model.history['acc'], label="Train")
plt.xlabel("Epoch", size=15)
plt.ylabel("Accuracy", size=15)
plt.title("Conv Net Train and Validation accuracy over epochs", size=18)
plt.legend()
从损失和准确率曲线可以看出,模型存在过拟合问题,但验证准确率达到了 80% 以上。
print(max(my_fit_model.history['val_acc']))
print(my_fit_model.history['val_acc'].index(max(my_fit_model.history['val_acc'])))
模型在第 21 个 epoch 达到了 89.48% 的最大分类准确率。
11.
使用 Dropout 正则化解决过拟合问题
model = Sequential()
model.add(Conv2D(filters=35, kernel_size=(3, 3), input_shape=(28, 28, 1), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Conv2D(filters=35, kernel_size=(3, 3), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Conv2D(filters=45, kernel_size=(3, 3), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(64, activation='relu'))
model.add(Dropout(0.35))
model.add(Dense(32, activation='relu'))
model.add(Dropout(0.35))
model.add(Dense(10, activation='softmax'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
在全连接层添加 Dropout 层,随机丢弃 35% 的神经元。重新训练模型后,训练和验证损失的差距缩小,过拟合问题得到改善。
print(max(my_fit_model.history['val_acc']))
print(my_fit_model.history['val_acc'].index(max(my_fit_model.history['val_acc'])))
应用正则化后,最佳验证准确率为 88.85%,虽然略低于未正则化的模型,但仍远高于基线准确率(该数据集的基线准确率为 10%)。
通过以上步骤,我们完成了从数据加载、预处理到模型构建、训练和优化的全过程。后续还可以尝试构建更深的模型,对模型的超参数进行网格搜索,或者构建混淆矩阵来评估模型在不同类别上的表现。
卷积神经网络:从基础到时尚数据集分类实战
12. 模型评估与后续探索
经过前面的步骤,我们已经构建并训练了卷积神经网络,还使用 Dropout 正则化改善了过拟合问题。接下来,我们可以进一步评估模型的性能,并探索更多的优化方向。
12.1 混淆矩阵分析
混淆矩阵是评估分类模型性能的重要工具,它可以帮助我们了解模型在各个类别上的预测情况。以下是构建混淆矩阵的示例代码:
from sklearn.metrics import confusion_matrix
import seaborn as sns
# 预测测试集
y_pred = model.predict(X_test)
y_pred_classes = np.argmax(y_pred, axis=1)
y_true = np.argmax(y_test, axis=1)
# 构建混淆矩阵
confusion_mtx = confusion_matrix(y_true, y_pred_classes)
# 可视化混淆矩阵
plt.figure(figsize=(10, 8))
sns.heatmap(confusion_mtx, annot=True, fmt='d', cmap='Blues',
xticklabels=mapping.values(), yticklabels=mapping.values())
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.title('Confusion Matrix')
plt.show()
通过混淆矩阵,我们可以清晰地看到模型在哪些类别上预测准确,哪些类别容易混淆。例如,如果某个类别在对角线上的值较大,说明模型对该类别的预测准确率较高;如果某个非对角线元素的值较大,则表示模型容易将该类别与其他类别混淆。
12.2 超参数调优
我们在模型构建过程中使用了一些超参数,如卷积核数量、全连接层神经元数量、Dropout 率等。这些超参数的选择会影响模型的性能,因此可以通过网格搜索或随机搜索等方法来寻找最优的超参数组合。以下是一个简单的网格搜索示例:
from sklearn.model_selection import GridSearchCV
from keras.wrappers.scikit_learn import KerasClassifier
# 定义模型构建函数
def create_model(filters1=35, filters2=35, filters3=45, neurons1=64, neurons2=32, dropout_rate=0.35):
model = Sequential()
model.add(Conv2D(filters=filters1, kernel_size=(3, 3), input_shape=(28, 28, 1), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Conv2D(filters=filters2, kernel_size=(3, 3), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Conv2D(filters=filters3, kernel_size=(3, 3), activation='relu'))
model.add(MaxPool2D(pool_size=(2, 2)))
model.add(Flatten())
model.add(Dense(neurons1, activation='relu'))
model.add(Dropout(dropout_rate))
model.add(Dense(neurons2, activation='relu'))
model.add(Dropout(dropout_rate))
model.add(Dense(10, activation='softmax'))
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
return model
# 创建 Keras 分类器
model = KerasClassifier(build_fn=create_model, epochs=10, batch_size=32, verbose=0)
# 定义超参数网格
param_grid = {
'filters1': [30, 35, 40],
'filters2': [30, 35, 40],
'filters3': [40, 45, 50],
'neurons1': [60, 64, 70],
'neurons2': [30, 32, 35],
'dropout_rate': [0.3, 0.35, 0.4]
}
# 进行网格搜索
grid = GridSearchCV(estimator=model, param_grid=param_grid, cv=3)
grid_result = grid.fit(X_train, y_train)
# 输出最佳参数和最佳得分
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
通过网格搜索,我们可以找到一组最优的超参数组合,从而进一步提高模型的性能。
12.3 构建更深的模型
除了超参数调优,我们还可以尝试构建更深的模型,增加卷积层和全连接层的数量,以提高模型的表达能力。以下是一个更深模型的示例代码:
model_deep = Sequential()
model_deep.add(Conv2D(filters=35, kernel_size=(3, 3), input_shape=(28, 28, 1), activation='relu'))
model_deep.add(MaxPool2D(pool_size=(2, 2)))
model_deep.add(Conv2D(filters=35, kernel_size=(3, 3), activation='relu'))
model_deep.add(MaxPool2D(pool_size=(2, 2)))
model_deep.add(Conv2D(filters=45, kernel_size=(3, 3), activation='relu'))
model_deep.add(MaxPool2D(pool_size=(2, 2)))
model_deep.add(Conv2D(filters=55, kernel_size=(3, 3), activation='relu'))
model_deep.add(MaxPool2D(pool_size=(2, 2)))
model_deep.add(Flatten())
model_deep.add(Dense(128, activation='relu'))
model_deep.add(Dropout(0.35))
model_deep.add(Dense(64, activation='relu'))
model_deep.add(Dropout(0.35))
model_deep.add(Dense(32, activation='relu'))
model_deep.add(Dropout(0.35))
model_deep.add(Dense(10, activation='softmax'))
model_deep.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
# 训练更深的模型
my_fit_model_deep = model_deep.fit(X_train, y_train, epochs=25, validation_data=(X_test, y_test))
# 可视化训练和验证损失及准确率
plt.plot(my_fit_model_deep.history['val_loss'], label="Validation")
plt.plot(my_fit_model_deep.history['loss'], label="Train")
plt.xlabel("Epoch", size=15)
plt.ylabel("Cat. Crossentropy Loss", size=15)
plt.title("Deep Conv Net Train and Validation loss over epochs", size=18)
plt.legend()
plt.plot(my_fit_model_deep.history['val_acc'], label="Validation")
plt.plot(my_fit_model_deep.history['acc'], label="Train")
plt.xlabel("Epoch", size=15)
plt.ylabel("Accuracy", size=15)
plt.title("Deep Conv Net Train and Validation accuracy over epochs", size=18)
plt.legend()
plt.show()
构建更深的模型可能会提高模型的性能,但也可能会增加过拟合的风险,因此需要适当调整 Dropout 率等正则化方法。
13. 总结与展望
通过本次实践,我们深入了解了卷积神经网络的基本原理和构建过程,包括最大池化、扁平化、全连接层等操作。我们使用 Zalando 研究的时尚数据集进行图像分类任务,从数据加载、预处理到模型构建、训练和优化,完成了一个完整的机器学习流程。
在模型训练过程中,我们发现了过拟合问题,并使用 Dropout 正则化进行了改善。通过混淆矩阵分析、超参数调优等方法,我们可以进一步评估和优化模型的性能。此外,尝试构建更深的模型也是提高性能的一种有效途径。
未来,我们可以继续探索更多的深度学习技术和方法,如使用不同的卷积核大小、激活函数、优化器等,以进一步提高模型的准确率和泛化能力。同时,我们还可以将这些技术应用到其他图像分类任务中,如医学图像分析、自动驾驶等领域,为实际问题提供更有效的解决方案。
以下是整个流程的 mermaid 流程图:
graph LR
A[数据加载] --> B[数据预处理]
B --> C[模型构建]
C --> D[模型训练]
D --> E[模型评估]
E --> F{是否需要优化?}
F -- 是 --> G[超参数调优/构建更深模型]
G --> D
F -- 否 --> H[模型应用]
总之,卷积神经网络在图像分类领域具有巨大的潜力,通过不断的学习和实践,我们可以更好地掌握这些技术,为解决实际问题提供更强大的工具。
超级会员免费看
62

被折叠的 条评论
为什么被折叠?



