You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MobileNetV2食品图像分类迁移学习遇过拟合及验证精度停滞

问题描述

使用MobileNetV2做食品图像分类迁移学习时遭遇严重过拟合:训练准确率达95%以上,训练过程中验证准确率卡在67%左右,但实际预测时验证集准确率仅19%(训练时的val_accuracy与手动预测结果严重不符)。此前用ResNet也存在相同问题,因MobileNet速度更快选择继续使用。相关代码及结果如下:

数据生成代码

# Create an ImageDataGenerator with data augmentation for training
data_generator = keras.preprocessing.image.ImageDataGenerator(
    preprocessing_function=keras.applications.mobilenet.preprocess_input,
    # rescale=1./255,
    validation_split=0.3,
    rotation_range=30,  # Random rotations
    width_shift_range=0.2,  # Horizontal shifts
    height_shift_range=0.2,  # Vertical shifts
    shear_range=0.2,  # Shear transformations
    zoom_range=0.2,  # Zoom
    horizontal_flip=True,  # Horizontal flips
)

# Load and preprocess training data
train_data_flow = data_generator.flow_from_directory(
    dataset_path,
    target_size=(128, 128),  # Resize images to 224x224
    batch_size=32,
    class_mode='categorical',
    subset='training'  # Use training subset
)

# Load and preprocess validation data
val_data_flow = data_generator.flow_from_directory(
    dataset_path,
    target_size=(128, 128),  # Resize images to 224x224
    batch_size=32,
    class_mode='categorical',
    subset='validation'  # Use validation subset
)

模型构建代码

# Load the model from TensorFlow Hub
model_url = "https://www.kaggle.com/models/google/mobilenet-v2/TensorFlow2/035-128-classification/2"
hub_layer = hub.KerasLayer(model_url, input_shape=(128, 128, 3) ,  trainable=True, arguments=dict(batch_norm_momentum=0.997))

# Create a Sequential model with dropout and batch normalization
model = keras.Sequential([
    hub_layer,
    layers.Dropout(0.2),  # Lower dropout rate
    layers.Dense(256, activation='relu'),
    layers.BatchNormalization(),  # Batch normalization
    layers.Dropout(0.2),  # Dropout
    layers.Dense(9, activation='softmax')
])

# Build the Sequential model
model.build((None, 128, 128, 3))

# Summary of the model
model.summary()

训练代码

# Define the EarlyStopping callback
early_stopping_callback = keras.callbacks.EarlyStopping(
    monitor='val_loss',  # Monitor validation loss
    patience=20,          # Number of epochs with no improvement after which training will be stopped
    restore_best_weights=True  # Restore model weights from the epoch with the best value of the monitored quantity
)

# Fit the model with early stopping
history = model.fit(
    train_data_flow,
    validation_data=val_data_flow,
    epochs=100,
    callbacks=[early_stopping_callback],  # Pass the EarlyStopping callback as a list
    shuffle= True
)

训练日志片段

Epoch 1/100
113/113 [==============================] - 73s 641ms/step - loss: 0.5302 - accuracy: 0.8623 - val_loss: 3.2597 - val_accuracy: 0.4723
...
Epoch 8/100
113/113 [==============================] - 71s 625ms/step - loss: 0.3877 - accuracy: 0.9062 - val_loss: 1.4624 - val_accuracy: 0.6630
...
Epoch 44/100
113/113 [==============================] - 71s 632ms/step - loss: 0.2272 - accuracy: 0.9527 - val_loss: 1.3801 - val_accuracy: 0.6760

预测代码与结果

from sklearn.metrics import classification_report

predictions = model.predict(val_data_flow)

# Convert predictions to class labels
predicted_classes = np.argmax(predictions, axis=1)

# Step 4: Evaluate the Model

# Get the true labels from the test data
true_labels = val_data_flow.classes  

# Calculate accuracy
accuracy = np.mean(predicted_classes == true_labels)  

# Accuracy as the score
print("Model Accuracy on Test Set:", accuracy)

# sklearn's classification_report for more detailed metrics
print("Classification Report:")
print(classification_report(true_labels, predicted_classes, target_names=list(val_data_flow.class_indices.keys())))

输出结果:

Model Accuracy on Test Set: 0.19388418998048146
Classification Report:
              precision    recall  f1-score   support

           1       0.32      0.30      0.31       495
           2       0.00      0.00      0.00        21
           3       0.17      0.21      0.19       199
           4       0.13      0.09      0.11       154
           5       0.00      0.00      0.00         8
           6       0.11      0.06      0.08        32
           7       0.11      0.07      0.08       199
           8       0.16      0.19      0.17       231
           9       0.13      0.18      0.15       198

    accuracy                           0.19      1537
   macro avg       0.12      0.12      0.12      1537
weighted avg       0.19      0.19      0.19      1537

解决思路

1. 先修正「训练val_accuracy与实际预测不符」的核心问题

训练时显示67%验证准确率,但手动预测只有19%,本质是验证集数据处理逻辑不一致:

  • 你给验证集也用了带数据增强的ImageDataGenerator,训练时验证集被随机变换,但val_data_flow.classes是原始标签,预测时用增强后的图像与原始标签对应,完全不匹配。
  • 修正方案:
    1. 单独创建验证集专用的生成器,仅做预处理,不做任何增强,且关闭shuffle保证顺序一致:
      val_data_generator = keras.preprocessing.image.ImageDataGenerator(
          preprocessing_function=keras.applications.mobilenet.preprocess_input
      )
      val_data_flow = val_data_generator.flow_from_directory(
          dataset_path,
          target_size=(128, 128),
          batch_size=32,
          class_mode='categorical',
          subset='validation',
          shuffle=False  # 必须关闭,保证标签和预测结果一一对应
      )
      
    2. 训练和预测都使用这个新的val_data_flow,才能得到真实的验证指标。

2. 针对性解决过拟合问题

(1)调整迁移学习策略:分层解冻预训练层

当前将整个MobileNetV2设为trainable=True,预训练权重被过度修改,极易过拟合。正确步骤:

  • 第一步:冻结所有预训练层,只训练顶部全连接层:
    hub_layer = hub.KerasLayer(model_url, input_shape=(128, 128, 3), trainable=False)
    
    训练10-20轮,等验证准确率稳定后,再解冻预训练层的顶部部分(比如最后20层),用极小学习率微调:
    hub_layer.trainable = True
    # 仅解冻最后20层预训练层
    for layer in hub_layer.layers[:-20]:
        layer.trainable = False
    # 重新编译,用小学习率
    model.compile(optimizer=keras.optimizers.Adam(learning_rate=1e-5),
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])
    

(2)强化正则化

  • 提高Dropout比例:将现有Dropout(0.2)改为Dropout(0.5),可在全连接层前额外加一层Dropout。
  • 给全连接层加L2正则:
    layers.Dense(256, activation='relu', kernel_regularizer=keras.regularizers.l2(1e-4)),
    
  • 缩小全连接层维度:256维容量过大,可改为128或64,减少模型复杂度。

(3)优化数据增强

  • 加大增强力度:提升旋转、平移范围,加入亮度/对比度调整:
    data_generator = keras.preprocessing.image.ImageDataGenerator(
        preprocessing_function=keras.applications.mobilenet.preprocess_input,
        validation_split=0.3,
        rotation_range=45,
        width_shift_range=0.3,
        height_shift_range=0.3,
        shear_range=0.2,
        zoom_range=[0.7, 1.3],
        horizontal_flip=True,
        brightness_range=[0.8, 1.2],
        contrast_range=[0.8, 1.2]
    )
    
  • 注意:验证集绝对不能加任何增强,仅做预处理。

(4)处理类别不平衡

从分类报告看,类别2(21样本)、类别5(8样本)严重稀缺,导致模型偏向大类:

  • 对小类别做过采样:复制样本或用数据增强生成更多变体。
  • 训练时使用类别权重:
    from sklearn.utils.class_weight import compute_class_weight
    class_weights = compute_class_weight('balanced', classes=np.unique(train_data_flow.classes), y=train_data_flow.classes)
    class_weights_dict = dict(enumerate(class_weights))
    # 训练时传入权重
    history = model.fit(
        train_data_flow,
        validation_data=val_data_flow,
        epochs=100,
        callbacks=[early_stopping_callback],
        shuffle=True,
        class_weight=class_weights_dict
    )
    

(5)调整训练细节

  • 降低学习率:初始学习率改用1e-4,微调时用1e-5,避免权重更新过快。
  • 增大Batch Size:硬件允许的话,将batch_size从32改为64/128,提升梯度稳定性。

内容的提问来源于stack exchange,提问作者angryhorse

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 01:52:32