参考:
https://blog.csdn.net/fioletfly/article/details/101345549
Python深度学习
代码清单 5-2 在卷积神经网络上添加分类器
代码清单 5-3 在 MNIST 图像上训练卷积神经网络
代码清单 5-4 将图像复制到训练、验证和测试的目录
代码清单 5-5 将猫狗分类的小型卷积神经网络实例化
代码清单 5-6 配置模型用于训练
5.2.4 数据预处理
代码清单 5-7 使用 ImageDataGenerator 从目录中读取图像
代码清单 5-8 利用批量生成器拟合模型
代码清单 5-9 保存模型
代码清单 5-10 绘制训练过程中的损失曲线和精度曲线
5.2.5 使用数据增强
代码清单 5-11 利用 ImageDataGenerator 来设置数据增强
代码清单 5-12 显示几个随机增强后的训练图像
代码清单 5-13 定义一个包含 dropout 的新卷积神经网络
代码清单 5-14 利用数据增强生成器训练卷积神经网络
代码清单 5-15 保存模型
在这里插入代码片
```sql
import keras
keras.__version__
import os, shutil
# 我们将重点讨论猫狗图像分类,数据集中包含 4000 张猫和狗的图像
# (2000 张猫的图像, 2000 张狗的图像)。我们将 2000 张图像用于训练, 1000 张用于验证, 1000
# 张用于测试。
# The path to the directory where the original
# dataset was uncompressed
# 原始数据集解压目录的路径
original_dataset_dir = '/Users/fchollet/Downloads/kaggle_original_data'
# The directory where we will
# store our smaller dataset
# 保存较小数据集的目录
base_dir = '/Users/fchollet/Downloads/cats_and_dogs_small'
os.mkdir(base_dir)
# 分别对应划分后的训练、# 验证和测试的目录
# Directories for our training,
# validation and test splits
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)
# 猫的训练图像目录
# Directory with our training cat pictures
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
# 狗的训练图像目录
# Directory with our training dog pictures
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)
# 猫的验证图像目录
# Directory with our validation cat pictures
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)
# 狗的验证图像目录
# Directory with our validation dog pictures
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)
# 猫的测试图像目录
# Directory with our validation cat pictures
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
# 狗的测试图像目录
# Directory with our validation dog pictures
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)
# 将前 1000 张猫的图像复制
# 到 train_cats_dir
# Copy first 1000 cat images to train_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_cats_dir, fname)
shutil.copyfile(src, dst)
# 将接下来 500 张猫的图像复
# 制到 validation_cats_dir
# Copy next 500 cat images to validation_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_cats_dir, fname)
shutil.copyfile(src, dst)
# 将接下来的 500 张猫的图像
# 复制到 test_cats_dir
# Copy next 500 cat images to test_cats_dir
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_cats_dir, fname)
shutil.copyfile(src, dst)
# Copy first 1000 dog images to train_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_dogs_dir, fname)
shutil.copyfile(src, dst)
# Copy next 500 dog images to validation_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_dogs_dir, fname)
shutil.copyfile(src, dst)
# Copy next 500 dog images to test_dogs_dir
fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_dogs_dir, fname)
shutil.copyfile(src, dst)
As a sanity check, let's count how many pictures we have in each training split (train/validation/test):
# 我们来检查一下,看看每个分组(训练 / 验证 / 测试)中分别包含多少张图像
print('total training cat images:', len(os.listdir(train_cats_dir)))
total training cat images: 1000
print('total training dog images:', len(os.listdir(train_dogs_dir)))
total training dog images: 1000
print('total validation cat images:', len(os.listdir(validation_cats_dir)))
total validation cat images: 500
print('total validation dog images:', len(os.listdir(validation_dogs_dir)))
total validation dog images: 500
print('total test cat images:', len(os.listdir(test_cats_dir)))
total test cat images: 500
print('total test dog images:', len(os.listdir(test_dogs_dir)))
total test dog images: 500
# 构建网络
# 在前一个 MNIST 示例中,我们构建了一个小型卷积神经网络,所以你应该已经熟悉这
# 种网络。我们将复用相同的总体结构,即卷积神经网络由 Conv2D 层(使用 relu 激活)和
# MaxPooling2D 层交替堆叠构成。
# 但由于这里要处理的是更大的图像和更复杂的问题,你需要相应地增大网络,即再增加一
# 个 Conv2D+MaxPooling2D 的组合。这既可以增大网络容量,也可以进一步减小特征图的尺寸,
# 使其在连接 Flatten 层时尺寸不会太大。本例中初始输入的尺寸为 150×150(有些随意的选
# 择),所以最后在 Flatten 层之前的特征图大小为 7×7。
# 注意 网络中特征图的深度在逐渐增大(从 32 增大到 128),而特征图的尺寸在逐渐减小(从
# 150×150 减小到 7×7)。这几乎是所有卷积神经网络的模式
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
Let's take a look at how the dimensions of the feature maps change with every successive layer:
model.summary()
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_1 (Conv2D) (None, 148, 148, 32) 896
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 74, 74, 32) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 72, 72, 64) 18496
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 36, 36, 64) 0
_________________________________________________________________
conv2d_3 (Conv2D) (None, 34, 34, 128) 73856
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 17, 17, 128) 0
_________________________________________________________________
conv2d_4 (Conv2D) (None, 15, 15, 128) 147584
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 7, 7, 128) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 6272) 0
_________________________________________________________________
dense_1 (Dense) (None, 512) 3211776
_________________________________________________________________
dense_2 (Dense) (None, 1) 513
=================================================================
Total params: 3,453,121
Trainable params: 3,453,121
Non-trainable params: 0
_________________________________________________________________
For our compilation step, we'll go with the RMSprop optimizer as usual. Since we ended our network with a single sigmoid unit, we will use binary crossentropy as our loss (as a reminder, check out the table in Chapter 4, section 5 for a cheatsheet on what loss function to use in various situations).
在编译这一步,和前面一样,我们将使用 RMSprop 优化器。因为网络最后一层是单一 sigmoid
# 单元,所以我们将使用二元交叉熵作为损失函数(提醒一下,表 4-1 列出了各种情况下应该使
# 用的损失函数)。
# 在编译这一步,和前面一样,我们将使用 RMSprop 优化器。因为网络最后一层是单一 sigmoid
# 单元,所以我们将使用二元交叉熵作为损失函数(提醒一下,表 4-1 列出了各种情况下应该使
# 用的损失函数)。
from keras import optimizers
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])
因为使用了 binary_crossentropy
# 损失,所以需要用二进制标签
# 数据预处理
# 你现在已经知道,将数据输入神经网络之前,应该将数据格式化为经过预处理的浮点数张量。
# 现在,数据以 JPEG 文件的形式保存在硬盘中,所以数据预处理步骤大致如下。108 第 5 章 深度学习用于计算机视觉
# (1) 读取图像文件。
# (2) 将 JPEG 文件解码为 RGB 像素网格。
# (3) 将这些像素网格转换为浮点数张量。
# (4) 将像素值(0~255 范围内)缩放到 [0, 1] 区间(正如你所知,神经网络喜欢处理较小的输
# 入值)。
# 这些步骤可能看起来有点吓人,但幸运的是, Keras 拥有自动完成这些步骤的工具。 Keras
# 有一个图像处理辅助工具的模块,位于 keras.preprocessing.image。特别地,它包含
# ImageDataGenerator 类,可以快速创建 Python 生成器,能够将硬盘上的图像文件自动转换
# 为预处理好的张量批量。下面我们将用到这个类。
from keras.preprocessing.image import ImageDataGenerator
# 将所有图像乘以 1/255 缩放
# All images will be rescaled by 1./255
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
# 将所有图像的大小调整为 150×150
# 因为使用了 binary_crossentropy
# 损失,所以需要用二进制标签
train_generator = train_datagen.flow_from_directory(
# This is the target directory
train_dir,
# All images will be resized to 150x150
target_size=(150, 150),
batch_size=20,
# Since we use binary_crossentropy loss, we need binary labels
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=20,
class_mode='binary')
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
Let's take a look at the output of one of these generators: it yields batches of 150x150 RGB images (shape (20, 150, 150, 3)) and binary labels (shape (20,)). 20 is the number of samples in each batch (the batch size). Note that the generator yields these batches indefinitely: it just loops endlessly over the images present in the target folder. For this reason, we need to break the iteration loop at some point.
for data_batch, labels_batch in train_generator:
print('data batch shape:', data_batch.shape)
print('labels batch shape:', labels_batch.shape)
break
# 我们来看一下其中一个生成器的输出:它生成了 150×150 的 RGB 图像[形状为 (20,
# 150, 150, 3)]与二进制标签[形状为 (20,)]组成的批量。每个批量中包含 20 个样本(批
# 量大小)。注意,生成器会不停地生成这些批量,它会不断循环目标文件夹中的图像。因此,你
# 需要在某个时刻终止(break)迭代循环。
data batch shape: (20, 150, 150, 3)
labels batch shape: (20,)
# 利用生成器,我们让模型对数据进行拟合。我们将使用 fit_generator 方法来拟合,它
# 在数据生成器上的效果和 fit 相同。它的第一个参数应该是一个 Python 生成器,可以不停地生
# 成输入和目标组成的批量,比如 train_generator。因为数据是不断生成的,所以 Keras 模型
# 要知道每一轮需要从生成器中抽取多少个样本。这是 steps_per_epoch 参数的作用:从生成
# 器中抽取 steps_per_epoch 个批量后(即运行了 steps_per_epoch 次梯度下降),拟合过程
# 将进入下一个轮次。本例中,每个批量包含 20 个样本,所以读取完所有 2000 个样本需要 100
# 个批量。
# 使用 fit_generator 时,你可以传入一个 validation_data 参数,其作用和在 fit 方
# 法中类似。值得注意的是,这个参数可以是一个数据生成器,但也可以是 Numpy 数组组成的元
# 组。如果向 validation_data 传入一个生成器,那么这个生成器应该能够不停地生成验证数
# 据批量,因此你还需要指定 validation_steps 参数,说明需要从验证生成器中抽取多少个批
# 次用于评估。
# 代码清单 5-8 利用批量生成器拟合模型
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=30,
validation_data=validation_generator,
validation_steps=50)
Epoch 1/30
100/100 [==============================] - 9s - loss: 0.6898 - acc: 0.5285 - val_loss: 0.6724 - val_acc: 0.5950
Epoch 2/30
100/100 [==============================] - 8s - loss: 0.6543 - acc: 0.6340 - val_loss: 0.6565 - val_acc: 0.5950
Epoch 3/30
100/100 [==============================] - 8s - loss: 0.6143 - acc: 0.6690 - val_loss: 0.6116 - val_acc: 0.6650
Epoch 4/30
100/100 [==============================] - 8s - loss: 0.5626 - acc: 0.7125 - val_loss: 0.5774 - val_acc: 0.6970
Epoch 5/30
100/100 [==============================] - 8s - loss: 0.5266 - acc: 0.7335 - val_loss: 0.5726 - val_acc: 0.6960
# 保存模型
model.save('cats_and_dogs_small_1.h5')
Let's plot the loss and accuracy of the model over the training and validation data during training:
从这些图像中都能看出过拟合的特征。训练精度随着时间线性增加,直到接近 100%,而验
# 证精度则停留在 70%~72%。验证损失仅在 5 轮后就达到最小值,然后保持不变,而训练损失则
# 一直线性下降,直到接近于 0。
# 因为训练样本相对较少(2000 个),所以过拟合是你最关心的问题。前面已经介绍过几种
# 降低过拟合的技巧,比如 dropout 和权重衰减(L2 正则化)。现在我们将使用一种针对于计算
# 机视觉领域的新方法,在用深度学习模型处理图像时几乎都会用到这种方法,它就是数据增强
# (data augmentation)。
# 代码清单 5-10 绘制训练过程中的损失曲线和精度曲线
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
# 从这些图像中都能看出过拟合的特征。训练精度随着时间线性增加,直到接近 100%,而验
# 证精度则停留在 70%~72%。验证损失仅在 5 轮后就达到最小值,然后保持不变,而训练损失则
# 一直线性下降,直到接近于 0。
# 因为训练样本相对较少(2000 个),所以过拟合是你最关心的问题。前面已经介绍过几种
# 降低过拟合的技巧,比如 dropout 和权重衰减(L2 正则化)。现在我们将使用一种针对于计算
# 机视觉领域的新方法,在用深度学习模型处理图像时几乎都会用到这种方法,它就是数据增强
# (data augmentation)。
# 5.2.5 使用数据增强
# 过拟合的原因是学习样本太少,导致无法训练出能够泛化到新数据的模型。如果拥有无限
# 的数据,那么模型能够观察到数据分布的所有内容,这样就永远不会过拟合。数据增强是从现
# 有的训练样本中生成更多的训练数据,其方法是利用多种能够生成可信图像的随机变换来增加
# (augment)样本。其目标是,模型在训练时不会两次查看完全相同的图像。这让模型能够观察
# 到数据的更多内容,从而具有更好的泛化能力。
# 在 Keras 中,这可以通过对 ImageDataGenerator 实例读取的图像执行多次随机变换来实
# 现。我们先来看一个例子。
# 这里只选择了几个参数(想了解更多参数,请查阅 Keras 文档)。我们来快速介绍一下这些
# 参数的含义。
# • rotation_range 是角度值(在 0~180 范围内),表示图像随机旋转的角度范围。
# • width_shift 和 height_shift 是图像在水平或垂直方向上平移的范围(相对于总宽
# 度或总高度的比例)。
# • shear_range 是随机错切变换的角度。
# • zoom_range 是图像随机缩放的范围。
# • horizontal_flip 是随机将一半图像水平翻转。如果没有水平不对称的假设(比如真
# 实世界的图像),这种做法是有意义的。
# • fill_mode是用于填充新创建像素的方法,这些新像素可能来自于旋转或宽度/高度平移。
# 我们来看一下增强后的图像(见图 5-11)
datagen = ImageDataGenerator(
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,
fill_mode='nearest')
如果你使用这种数据增强来训练一个新网络,那么网络将不会两次看到同样的输入。但网
# 络看到的输入仍然是高度相关的,因为这些输入都来自于少量的原始图像。你无法生成新信息,
# 而只能混合现有信息。因此,这种方法可能不足以完全消除过拟合。为了进一步降低过拟合,
# 你还需要向模型中添加一个 Dropout 层,添加到密集连接分类器之前
# 显示几个随机增强后的训练图像
# 图像预处理工具的模块
# This is module with image preprocessing utilities
from keras.preprocessing import image
fnames = [os.path.join(train_cats_dir, fname) for fname in os.listdir(train_cats_dir)]
# We pick one image to "augment"
# 选择一张图像进行增强
# 读取图像并调整大小
# 将其转换为形状 (150, 150, 3) 的 Numpy 数组
# 将其形状改变为 (1, 150, 150, 3)
# 生成随机变换后的图像批量。
# 循环是无限的,因此你需要
# 在某个时刻终止循环
img_path = fnames[3]
# Read the image and resize it
img = image.load_img(img_path, target_size=(150, 150))
# Convert it to a Numpy array with shape (150, 150, 3)
x = image.img_to_array(img)
# Reshape it to (1, 150, 150, 3)
x = x.reshape((1,) + x.shape)
# The .flow() command below generates batches of randomly transformed images.
# It will loop indefinitely, so we need to `break` the loop at some point!
i = 0
for batch in datagen.flow(x, batch_size=1):
plt.figure(i)
imgplot = plt.imshow(image.array_to_img(batch[0]))
i += 1
if i % 4 == 0:
break
plt.show()
# 如果你使用这种数据增强来训练一个新网络,那么网络将不会两次看到同样的输入。但网
# 络看到的输入仍然是高度相关的,因为这些输入都来自于少量的原始图像。你无法生成新信息,
# 而只能混合现有信息。因此,这种方法可能不足以完全消除过拟合。为了进一步降低过拟合,
# 你还需要向模型中添加一个 Dropout 层,添加到密集连接分类器之前
# 定义一个包含 dropout 的新卷积神经网络
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',
optimizer=optimizers.RMSprop(lr=1e-4),
metrics=['acc'])
Let's train our network using data augmentation and dropout:
# 我们来训练这个使用了数据增强和 dropout 的网络。
# 代码清单 5-14 利用数据增强生成器训练卷积神经网络
train_datagen = ImageDataGenerator(
rescale=1./255,
rotation_range=40,
width_shift_range=0.2,
height_shift_range=0.2,
shear_range=0.2,
zoom_range=0.2,
horizontal_flip=True,)
# Note that the validation data should not be augmented!
# 注意,不能增强验证数据
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
# This is the target directory
train_dir,
# All images will be resized to 150x150
# 将所有图像的大小调整为 150×150
target_size=(150, 150),
batch_size=32,
# 因为使用了 binary_crossentropy损失,所以需要用二进制标签
# Since we use binary_crossentropy loss, we need binary labels
class_mode='binary')
validation_generator = test_datagen.flow_from_directory(
validation_dir,
target_size=(150, 150),
batch_size=32,
class_mode='binary')
history = model.fit_generator(
train_generator,
steps_per_epoch=100,
epochs=100,
validation_data=validation_generator,
validation_steps=50)
Found 2000 images belonging to 2 classes.
Found 1000 images belonging to 2 classes.
Epoch 1/100
100/100 [==============================] - 24s - loss: 0.6857 - acc: 0.5447 - val_loss: 0.6620 - val_acc: 0.5888
Epoch 2/100
100/100 [==============================] - 23s - loss: 0.6710 - acc: 0.5675 - val_loss: 0.6606 - val_acc: 0.5825
Epoch 3/100
100/100 [==============================] - 22s - loss: 0.6609 - acc: 0.5913 - val_loss: 0.6663 - val_acc: 0.5711.594 - ETA: 7s - loss: 0.6655 - ETA: 5s - los - ETA: 1s - loss: 0.6620 - acc:
Epoch 4/100
100/100 [==============================] - 22s - loss: 0.6446 - acc: 0.6178 - val_loss: 0.6200 - val_acc: 0.6379
# 保存模型
model.save('cats_and_dogs_small_2.h5')
Let's plot our results again:
通过进一步使用正则化方法以及调节网络参数(比如每个卷积层的过滤器个数或网络中的
# 层数),你可以得到更高的精度,可以达到 86%或 87%。但只靠从头开始训练自己的卷积神经网络,
# 再想提高精度就十分困难,因为可用的数据太少。想要在这个问题上进一步提高精度,下一步
# 需要使用预训练的模型,这是接下来两节的重点
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
# 我们再次绘制结果(见图 5-12 和图 5-13)。使用了数据增强和 dropout 之后,模型不再过拟合:
# 训练曲线紧紧跟随着验证曲线。现在的精度为 82%,比未正则化的模型提高了 15%(相对比例)
# 通过进一步使用正则化方法以及调节网络参数(比如每个卷积层的过滤器个数或网络中的
# 层数),你可以得到更高的精度,可以达到 86%或 87%。但只靠从头开始训练自己的卷积神经网络,
# 再想提高精度就十分困难,因为可用的数据太少。想要在这个问题上进一步提高精度,下一步
# 需要使用预训练的模型,这是接下来两节的重点