TensorFlow|咖啡豆识别

时间:2024-11-08 13:51:57
  • ???? 本文为????365天深度学习训练营中的学习记录博客
  • ???? 原作者:K同学啊

???? 要求:

  1. 自己搭建VGG-16网络框架
  2. 调用官方的VGG-16网络框架

???? 拔高(可选):

  1. 验证集准确率达到100%
  2. 使用PPT画出VGG-16算法框架图(发论文需要这项技能)

???? 探索(难度有点大)

  1. 在不影响准确率的前提下轻量化模型
    ○ 目前VGG16的Total params是134,276,932

一、前期工作

1. 设置GPU

如果使用的是CPU可以忽略这步

import tensorflow as tf

gpus = tf.config.list_physical_devices("GPU")

if gpus:
    tf.config.experimental.set_memory_growth(gpus[0], True)  #设置GPU显存用量按需使用
    tf.config.set_visible_devices([gpus[0]],"GPU")

2. 导入数据

from tensorflow       import keras
from tensorflow.keras import layers,models
import numpy             as np
import matplotlib.pyplot as plt
import os,PIL,pathlib

data_dir = "C:/Users/76967/.keras/datasets/49-data/"
data_dir = pathlib.Path(data_dir)
image_count = len(list(data_dir.glob('*/*.png')))

print("图片总数为:",image_count)

图片总数为: 1200

二、数据预处理

1. 加载数据

使用image_dataset_from_directory方法将磁盘中的数据加载到tf.data.Dataset

batch_size = 32
img_height = 224
img_width = 224

"""
关于image_dataset_from_directory()的详细介绍可以参考文章:https://mtyjkh.blog.****.net/article/details/117018789
"""
train_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="training",
    seed=123,
    image_size=(img_height, img_width),
    batch_size=batch_size)

Found 1200 files belonging to 4 classes.
Using 960 files for training.

这段代码使用了 TensorFlow 的 image_dataset_from_directory() 函数来从指定的目录中加载图像数据,并将其转换为一个适合模型训练的数据集。这一步的目的和作用主要包括以下几点:

1. 从文件夹中加载图像数据

image_dataset_from_directory() 会从指定的 data_dir 目录中自动读取图像文件。它会根据文件夹结构将每个子文件夹视为一个类(例如,每个文件夹对应一个标签),并且从每个文件夹中加载图像数据。每张图像会被自动标记为该子文件夹的标签。

2. 数据集划分(训练集和验证集)

validation_split=0.2 参数指定将数据集的 20% 用于验证(用于模型训练过程中进行验证),剩余的 80% 用于训练。通过这种方式,image_dataset_from_directory() 可以自动将数据集分成训练集和验证集。

subset="training" 参数指示当前数据集是训练集。另一个常用的 subset="validation" 用于指定验证集。

3. 随机种子控制数据分割

seed=123 设置了一个随机种子,以确保数据划分的可重复性。如果没有指定种子,每次执行时数据集的划分可能会不同,设置种子保证每次运行时数据集划分的一致性。

4. 调整图像尺寸

image_size=(img_height, img_width) 参数指定了加载的图像的尺寸(即缩放后的目标尺寸)。通常,神经网络输入需要统一的尺寸image_size 参数用来将所有图像调整为指定的大小。

5. 设置批次大小

batch_size=batch_size 用于指定每个批次的样本数量。神经网络训练时,数据通常会被分成多个小批次进行训练。较大的批次有时可以加速训练,但会增加内存使用。批次大小的选择取决于硬件资源和训练数据的特性。

总结

此步骤的目的是:

  • 加载图像数据并将其转换为 TensorFlow 可处理的格式。
  • 自动从文件夹结构中提取标签(类别)。
  • 将数据划分为训练集和验证集。
  • 调整图像尺寸,使其符合网络输入要求。
  • 设置批次大小以便进行高效训练。

通过这一操作,你可以方便地将数据集加载并进行必要的预处理,直接用于模型训练和验证。

"""
关于image_dataset_from_directory()的详细介绍可以参考文章:https://mtyjkh.blog.****.net/article/details/117018789
"""
val_ds = tf.keras.preprocessing.image_dataset_from_directory(
    data_dir,
    validation_split=0.2,
    subset="validation",
    seed=123,
    image_size=(img_height, img_width),
    batch_size=batch_size)

Found 1200 files belonging to 4 classes.
Using 240 files for validation.

我们可以通过class_names输出数据集的标签。标签将按字母顺序对应于目录名称

class_names = train_ds.class_names
print(class_names)

['Dark', 'Green', 'Light', 'Medium']

2. 可视化数据

plt.figure(figsize=(10, 4))  # 图形的宽为10高为5

for images, labels in train_ds.take(1):
    for i in range(10):
        
        ax = plt.subplot(2, 5, i + 1)  

        plt.imshow(images[i].numpy().astype("uint8"))
        plt.title(class_names[labels[i]])
        
        plt.axis("off")

这段代码的目的是在 Python 中使用 matplotlib 库展示图像,并给每张图像加上对应的标题。

代码解析:

  1. plt.imshow(images[i].numpy().astype("uint8"))

    • images[i]:这是从数据集中获取的第 i 张图像。假设 images 是一个张量(Tensor),这一步的作用是提取第 i 张图像数据。
    • .numpy():将 TensorFlow 张量转换为 NumPy 数组。TensorFlow 张量是高效的计算对象,但为了使用 matplotlib 来展示图像,通常需要将其转换为 NumPy 数组。
    • .astype("uint8"):将图像数据类型转换为 uint8(8位无符号整数),因为图像的像素值通常是 0 到 255 之间的整数,适合用 uint8 类型表示。
  2. plt.title(class_names[labels[i]])

    • labels[i]:这是图像的标签,通常是一个整数,表示图像所属的类别。假设 labels 是一个包含类别标签的列表或张量,这一步就是获取第 i 张图像的标签。
    • class_names[labels[i]]class_names 是一个类别名称的列表或数组,其中的元素是类别的名称或描述。labels[i] 提供了该图像对应类别的索引,class_names[labels[i]] 会返回这个类别对应的名称。
    • plt.title():设置图像的标题为该图像的类别名称。

目的:

这段代码的目的是:

  • 显示图像:通过 plt.imshow() 来显示 images[i] 这张图像。
  • 设置标题:通过 plt.title() 为每张图像设置一个标题,标题显示的是该图像所属的类别名称(通过 class_names[labels[i]] 获取)。

for image_batch, labels_batch in train_ds:
    print(image_batch.shape)
    print(labels_batch.shape)
    break

(32, 224, 224, 3)
(32,)

3. 配置数据集

  • shuffle() :打乱数据,关于此函数的详细介绍可以参考:https://zhuanlan.zhihu.com/p/42417456
  • prefetch() :预取数据,加速运行,其详细介绍可以参考我前两篇文章,里面都有讲解。
  • cache() :将数据集缓存到内存当中,加速运行

AUTOTUNE = tf.data.AUTOTUNE

train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds   = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
normalization_layer = layers.experimental.preprocessing.Rescaling(1./255)

train_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
val_ds   = val_ds.map(lambda x, y: (normalization_layer(x), y))
image_batch, labels_batch = next(iter(val_ds))
first_image = image_batch[0]

# 查看归一化后的数据
print(np.min(first_image), np.max(first_image))

0.0 1.0

 优化数据加载

AUTOTUNE = tf.data.AUTOTUNE

train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds   = val_ds.cache().prefetch(buffer_size=AUTOTUNE)

这段代码的目的是通过 TensorFlowtf.data API 来优化数据加载过程,使训练和验证数据集的加载更高效。让我们逐行分析这些操作:

1. AUTOTUNE = tf.data.AUTOTUNE

  • AUTOTUNE 是一个 TensorFlow 的常量,表示自动调优。AUTOTUNE 会让 TensorFlow 自动选择最合适的预取(prefetch)缓冲区大小。这可以根据系统的资源和硬件动态调整,以优化性能,通常是在数据加载时利用 CPU 和 GPU 的并行处理能力。
  • 在后续代码中,我们使用 AUTOTUNE 来优化数据预处理。

2. train_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)

  • train_ds.cache()

    • cache() 方法会将数据集加载到内存中。如果数据集能适应内存,缓存可以加速数据加载过程,因为这样 TensorFlow 就不需要每次从磁盘重新读取数据。

    • 这对于训练集来说尤为重要,因为数据集通常会被多次重复读取,缓存能显著提高速度。

  • train_ds.shuffle(1000)

    • shuffle(buffer_size=1000) 会对训练数据进行随机打乱,buffer_size 参数控制打乱时使用的缓冲区大小。1000 表示将从数据集中随机选取 1000 个样本进行打乱。

    • 这样可以防止模型看到数据的顺序,从而提高模型的泛化能力。

  • train_ds.prefetch(buffer_size=AUTOTUNE)

    • prefetch() 方法会提前加载数据到内存中,以减少训练过程中的等待时间。指定 buffer_size=AUTOTUNE 让 TensorFlow 自动选择最适合的预取缓冲区大小。

    • 通过预取数据,模型在进行一次训练时能够同时准备下一批次的数据,从而提高训练效率。

目的和总结:

  • cache():将数据集缓存到内存中,避免每次读取时都从磁盘加载。
  • shuffle():对训练数据进行随机打乱,避免模型过拟合于数据的顺序。
  • prefetch():提前加载数据,确保训练过程中的数据加载与模型训练并行进行,减少等待时间。

这些优化操作的目的是提升数据加载和处理的效率,尤其是在处理大规模数据集时,能够显著缩短训练时间并提高模型的训练速度。

 归一化

normalization_layer = layers.experimental.preprocessing.Rescaling(1./255)

train_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))
val_ds   = val_ds.map(lambda x, y: (normalization_layer(x), y))

这段代码的目的是对训练集和验证集的数据进行 归一化 处理,使得图像的像素值位于 [0, 1] 的范围内,以便在训练过程中提高模型的稳定性和收敛速度。归一化是神经网络训练中的一个常见步骤,尤其是对于图像数据。让我们逐步分析这段代码:

1. normalization_layer = layers.experimental.preprocessing.Rescaling(1./255)

  • layers.experimental.preprocessing.Rescaling(1./255) 创建了一个归一化层,它将输入的像素值除以 255,缩放到 [0, 1] 的范围。图像的原始像素值通常是整数,范围在 [0, 255] 之间。通过将每个像素除以 255,得到的是一个浮动范围为 [0.0, 1.0] 的值。这样的归一化可以帮助神经网络更快收敛,尤其是当使用基于梯度的优化算法时,归一化能提高训练的效率和稳定性。

2. train_ds = train_ds.map(lambda x, y: (normalization_layer(x), y))

  • train_ds.map()tf.data API 中的一个方法,它将指定的操作应用到数据集的每个元素。

  • lambda x, y: (normalization_layer(x), y):这里的 xy 分别代表图像和对应的标签。在这个 lambda 函数中:

    • normalization_layer(x):对每张图像 x 应用归一化操作,将其像素值缩放到 [0, 1] 范围。

    • y:图像的标签不需要改变,因此它直接传递给输出。

    这个操作会对整个训练集中的每张图像应用归一化处理,但保持标签不变。

3. val_ds = val_ds.map(lambda x, y: (normalization_layer(x), y))

  • 对验证集 val_ds 执行类似的操作。验证集中的图像同样需要归一化,以确保模型在训练和验证时处理一致的数据。

  • lambda x, y: (normalization_layer(x), y) 对每张图像进行归一化,并保持标签不变。

为什么需要归一化?

  • 提升训练稳定性:图像的原始像素值范围通常是 [0, 255],这样的数值可能导致模型在训练过程中梯度变化剧烈,难以收敛。通过将像素值缩放到 [0, 1],可以使得数据在一个更适合神经网络的范围内。

  • 加速收敛:当输入特征的尺度一致时,神经网络的训练过程通常会更加平稳,梯度下降方法的收敛速度也会更快。大多数优化算法(例如 Adam、SGD)都假设输入特征的值处于较小范围内。

总结:

  • Rescaling(1./255) 将图像像素值从 [0, 255] 范围缩放到 [0, 1]

  • 通过 map() 方法,对训练集和验证集的每张图像应用归一化操作,而标签 y 不变。

这个步骤是数据预处理的一部分,目的是为神经网络提供一个标准化的输入,帮助模型更快、更稳定地训练。

三、构建VGG-16网络

在官方模型与自建模型之间进行二选一就可以了,选着一个注释掉另外一个。

VGG优缺点分析:

  • VGG优点

VGG的结构非常简洁,整个网络都使用了同样大小的卷积核尺寸(3x3)和最大池化尺寸(2x2)

  • VGG缺点

1)训练时间过长,调参难度大。2)需要的存储容量大,不利于部署。例如存储VGG-16权重值文件的大小为500多MB,不利于安装到嵌入式系统中。

1. 官方模型

官网模型调用这块我放到后面几篇文章中,下面主要讲一下VGG-16

# model = tf.keras.applications.VGG16(weights='imagenet')
# model.summary()

2. 自建模型

from tensorflow.keras import layers, models, Input
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Dense, Flatten, Dropout

def VGG16(nb_classes, input_shape):
    input_tensor = Input(shape=input_shape)
    # 1st block
    x = Conv2D(64, (3,3), activation='relu', padding='same',name='block1_conv1')(input_tensor)
    x = Conv2D(64, (3,3), activation='relu', padding='same',name='block1_conv2')(x)
    x = MaxPooling2D((2,2), strides=(2,2), name = 'block1_pool')(x)
    # 2nd block
    x = Conv2D(128, (3,3), activation='relu', padding='same',name='block2_conv1')(x)
    x = Conv2D(128, (3,3), activation='relu', padding='same',name='block2_conv2')(x)
    x = MaxPooling2D((2,2), strides=(2,2), name = 'block2_pool')(x)
    # 3rd block
    x = Conv2D(256, (3,3), activation='relu', padding='same',name='block3_conv1')(x)
    x = Conv2D(256, (3,3), activation='relu', padding='same',name='block3_conv2')(x)
    x = Conv2D(256, (3,3), activation='relu', padding='same',name='block3_conv3')(x)
    x = MaxPooling2D((2,2), strides=(2,2), name = 'block3_pool')(x)
    # 4th block
    x = Conv2D(512, (3,3), activation='relu', padding='same',name='block4_conv1')(x)
    x = Conv2D(512, (3,3), activation='relu', padding='same',name='block4_conv2')(x)
    x = Conv2D(512, (3,3), activation='relu', padding='same',name='block4_conv3')(x)
    x = MaxPooling2D((2,2), strides=(2,2), name = 'block4_pool')(x)
    # 5th block
    x = Conv2D(512, (3,3), activation='relu', padding='same',name='block5_conv1')(x)
    x = Conv2D(512, (3,3), activation='relu', padding='same',name='block5_conv2')(x)
    x = Conv2D(512, (3,3), activation='relu', padding='same',name='block5_conv3')(x)
    x = MaxPooling2D((2,2), strides=(2,2), name = 'block5_pool')(x)
    # full connection
    x = Flatten()(x)
    x = Dense(4096, activation='relu',  name='fc1')(x)
    x = Dense(4096, activation='relu', name='fc2')(x)
    output_tensor = Dense(nb_classes, activation='softmax', name='predictions')(x)

    model = Model(input_tensor, output_tensor)
    return model

model=VGG16(len(class_names), (img_width, img_height, 3))
model.summary()

3. 网络结构图

关于卷积的相关知识可以参考文章:卷积的计算_卷积核2x2-****博客文章浏览阅读3.7k次,点赞5次,收藏21次。1. 卷积4×44×44×4 的输入矩阵 III 和 3 × 3 的卷积核 KKK:在步长(stride)为 1 时,输出的大小为 ( 4 − 3 + 1 ) × ( 4 − 3 + 1 )计算公式:输入图片矩阵 III 大小: w×ww × ww×w卷积核 KKK:k×kk × kk×k步长SSS:sss填充大小(padding):pppo=(w−k+2p)s+1o = \frac{(w − k + 2p )}{s}+1o=s(w−k+2p)​+1输出图片大小为:o×oo × ._卷积核2x2https://mtyjkh.blog.****.net/article/details/114278995

结构说明:

  • 13个卷积层(Convolutional Layer),分别用blockX_convX表示
  • 3个全连接层(Fully connected Layer),分别用fcXpredictions表示
  • 5个池化层(Pool layer),分别用blockX_pool表示

VGG-16包含了16个隐藏层(13个卷积层和3个全连接层),故称为VGG-16

四、编译

在准备对模型进行训练之前,还需要再对其进行一些设置。以下内容是在模型的编译步骤中添加的:

  • 损失函数(loss):用于衡量模型在训练期间的准确率。
  • 优化器(optimizer):决定模型如何根据其看到的数据和自身的损失函数进行更新。
  • 指标(metrics):用于监控训练和测试步骤。以下示例使用了准确率,即被正确分类的图像的比率。
# 设置初始学习率
initial_learning_rate = 1e-4

lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
        initial_learning_rate, 
        decay_steps=30,      # 敲黑板!!!这里是指 steps,不是指epochs
        decay_rate=0.92,     # lr经过一次衰减就会变成 decay_rate*lr
        staircase=True)

# 设置优化器
opt = tf.keras.optimizers.Adam(learning_rate=initial_learning_rate)

model.compile(optimizer=opt,
              loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
              metrics=['accuracy'])

SparseCategoricalCrossentropy函数注意事项:

from_logits参数:

  • 布尔值,默认值为 False
  • 当为 True 时,函数假设传入的预测值是未经过激活函数处理的原始 logits 值。如果模型的最后一层没有使用 softmax 激活函数(即返回 logits),需要将 from_logits 设置为 True
  • 当为 False 时,函数假设传入的预测值已经是经过 softmax 处理的概率分布。

这段代码主要涉及了设置 学习率调度器优化器,然后将它们应用到模型编译过程中。以下是详细的解析:

1. 设置初始学习率

 

initial_learning_rate = 1e-4

  • initial_learning_rate = 1e-4 设置了优化器的初始学习率为 0.0001。学习率是控制每一步优化过程中参数更新幅度的超参数,较小的学习率有助于细致调整参数,但可能会导致收敛速度较慢。

2. 学习率调度器(ExponentialDecay)

 

lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay( initial_learning_rate, decay_steps=30, # 这里是指 steps,不是指 epochs decay_rate=0.92, # lr经过一次衰减就会变成 decay_rate*lr staircase=True)

  • ExponentialDecay:这是 TensorFlow 中的一种学习率衰减方式。学习率会随着训练步数的增加而按指数衰减。每经过一定的步数,学习率都会乘以一个衰减因子(decay_rate)。这个调度器的作用是随着训练进程逐渐减小学习率,避免训练后期学习率过大导致跳跃性更新,帮助网络收敛。

  • initial_learning_rate:初始学习率,设定为 1e-4

  • decay_steps=30:这表示学习率每隔 30 个训练步骤(steps)就会进行一次衰减。注意,decay_steps 这里是按照步骤(steps)来计算的,而不是按照训练轮次(epochs)。如果你使用的是批量训练,则 decay_steps 与每个批次的训练步骤数量有关。

  • decay_rate=0.92:这表示每经过 decay_steps 步,学习率会乘以 0.92,即衰减到原来的 92%。所以,如果初始学习率是 1e-4,经过 30 个步骤后,新的学习率将变为 1e-4 * 0.92

  • staircase=True:这表示学习率的衰减是阶梯型的,即每 decay_steps 步,学习率突然减小为原来的 decay_rate 倍,而不是平滑的指数衰减。如果设置为 False,则学习率会平滑地下降。

3. 设置优化器

 

opt = tf.keras.optimizers.Adam(learning_rate=initial_learning_rate)

  • Adam:这里选择了 Adam 优化器,Adam 是一种自适应学习率的优化算法,它结合了 MomentumRMSProp 的优点,能够根据不同参数的梯度调整学习率。Adam 是最常用的深度学习优化器之一,适用于大部分问题。

  • learning_rate=initial_learning_rate:这里初始化时传入的学习率是 1e-4,但接下来通过学习率调度器 lr_schedule 来调整学习率。

4. 编译模型

 

model.compile(optimizer=opt, loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False), metrics=['accuracy'])

  • optimizer=opt:在这里,将 Adam 优化器(带有学习率调度器)传递给 compile 方法。

  • loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False)

    • SparseCategoricalCrossentropy 是一种常用于分类任务的损失函数。它适用于处理整数标签(不是 one-hot 编码标签)。如果 from_logits=True,则表示模型输出的是未经 softmax 激活的 logits;如果 from_logits=False,表示模型输出的是经过 softmax 激活的概率分布。
    • 这里选择 from_logits=False,假设你的模型输出已经是经过 softmax 归一化的概率。
  • metrics=['accuracy']:指定训练和评估时使用准确度作为衡量指标。

总结

这段代码完成了以下几个任务:

  1. 设置学习率调度:通过 ExponentialDecay 调度器,使得学习率随训练步数逐渐衰减,以便在训练过程中提高模型的稳定性和收敛效果。
  2. 选择优化器:使用 Adam 优化器,并结合学习率调度器。
  3. 编译模型:使用 SparseCategoricalCrossentropy 作为损失函数,并监控训练过程中的准确度。

这样,模型在训练过程中会根据每 30 步调整一次学习率,帮助模型更好地收敛。

学习率有啥用

学习率(Learning Rate)是训练深度学习模型时的一个超参数,它控制着每次参数更新的步长,即每次迭代中模型的权重调整幅度。学习率的大小直接影响到模型的训练效果和速度,因此选择合适的学习率对于成功训练模型至关重要。

学习率的作用
  1. 控制模型权重更新的幅度

    • 学习率决定了每次梯度更新时,模型参数(如权重和偏置)改变的幅度。如果学习率太大,可能会导致每次更新的步伐过大,错过最优解,甚至可能导致训练过程中出现震荡或发散(损失函数不收敛)。
    • 如果学习率太小,模型每次更新的步伐过小,可能导致训练过程非常缓慢,甚至无法在合理的时间内收敛到较好的解。
  2. 影响模型的收敛速度

    • 学习率对模型的收敛速度有很大影响。如果学习率合适,模型能较快地找到损失函数的最小值;如果学习率太小,收敛速度会非常慢,训练时间会变得非常长。
    • 但如果学习率过大,模型可能会错过最优解,甚至无法收敛。
  3. 优化的稳定性

    • 在训练过程中,学习率过大可能导致优化过程不稳定,模型的损失值可能在优化过程中震荡,甚至增加,无法收敛到最优解。
    • 适当的学习率能够使得优化过程稳定,最终收敛到一个较好的最优解。
  4. 影响局部最小值的跳跃能力

    • 选择较大的学习率可能会使模型跳过局部最小值,找到全局最优解。然而,过大的学习率可能使得模型在全局最优解附近震荡,无法收敛。
    • 较小的学习率有助于细致的调整,但容易在局部最小值处停滞,导致模型无法进一步优化。
学习率的调度

在训练深度学习模型时,通常会采用 学习率调度(Learning Rate Scheduling)技术,随着训练的进行动态调整学习率。这种方法能够提高训练效率、加速收敛并避免过拟合。常见的学习率调度方法有:

  1. 指数衰减(Exponential Decay): 学习率随训练步骤或轮次按指数衰减,通常表现为:

    lrnew=lrinitial×decay_rate(step/decay_steps)\text{lr}_{new} = \text{lr}_{initial} \times \text{decay\_rate}^{(\text{step} / \text{decay\_steps})}lrnew​=lrinitial​×decay_rate(step/decay_steps)

    随着训练进程,学习率逐渐减小,使得训练过程越来越稳定。

  2. 阶梯衰减(Step Decay): 每经过一定数量的训练步骤,学习率会突然下降为原来的某个比例。可以有效地避免过拟合,并让模型更好地收敛。

  3. 循环学习率(Cyclical Learning Rate): 学习率在训练过程中不断循环增大和减小,这种方法能够使模型跳出局部最小值并找到全局最优解。

  4. 自适应学习率(如 Adam、RMSprop 等): 这些优化器会根据梯度的大小和历史信息动态调整每个参数的学习率,使得每个参数的更新步长更加智能和自适应。

学习率的选择
  • 学习率过大
    • 可能导致模型的损失函数值不稳定,甚至无法收敛。
    • 训练过程可能出现震荡(loss波动)或发散(loss增大)。
  • 学习率过小
    • 模型的收敛速度会非常慢,可能需要很长时间才能达到较好的解。
    • 可能会在某些局部最小值处停滞,导致模型无法进一步优化。
实际应用中如何选择学习率
  1. 调参

    • 可以尝试使用 网格搜索(Grid Search)或 随机搜索(Random Search)等方法来测试不同的学习率,并选择最适合的值。
    • 通常从较小的学习率开始,例如 1e-31e-4,然后通过实验逐渐调整。
  2. 学习率热身(Warmup)

    • 在训练的初期,使用较小的学习率,随着训练的进行逐渐增大学习率,然后再开始衰减。这有助于模型稳定地进入训练过程。
  3. 使用学习率调度

    • 如果使用了如 ExponentialDecayReduceLROnPlateau 等调度器,可以让学习率在训练过程中自动调整,以便在训练后期微调模型。
总结
  • 学习率是影响模型训练效果的关键超参数之一,它直接决定了优化过程中参数更新的步长。
  • 选择合适的学习率能够加速模型收敛、提高训练稳定性,并帮助模型达到更好的效果。
  • 通过使用学习率调度器,我们可以在训练过程中动态调整学习率,进一步提升模型的表现和训练效率。

五、训练模型

????注:从本周开始,网络越来越复杂,对算力要求也更高,CPU训练模型时间会很长,建议尽可能的使用GPU来跑。

epochs = 20

history = model.fit(
    train_ds,
    validation_data=val_ds,
    epochs=epochs
)

六、可视化结果

acc = history.history['accuracy']
val_acc = history.history['val_accuracy']

loss = history.history['loss']
val_loss = history.history['val_loss']

epochs_range = range(epochs)

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')

plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()