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

M1 Mac上TensorFlow函数式CNN分类器AUC徘徊0.5,Colab训练正常

M1 MacBook Pro vs Google Colab TensorFlow训练结果异常差异排查

问题背景

使用TensorFlow构建二分类图像CNN模型,损失函数为binary cross-entropy,评估指标为ROC AUC。同一Jupyter Notebook在两个平台运行结果完全不同:

  • Google Colab:训练集指标2-3个epoch后达到峰值,随后严重过拟合
  • M1 MacBook Pro(GPU):训练集ROC AUC始终在0.5左右波动,验证集AUC固定为0.5,模型完全无法学习

已设置随机种子保证可复现性,且M1上其他模型架构训练同一数据集可正常运行。M1环境通过pip安装tensorflow、tensorflow-macos、tensorflow-metal配置GPU支持,曾出现模型编译卡顿问题。

代码重现

import numpy as np
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.preprocessing import image_dataset_from_directory
import tensorflow as tf
import matplotlib.pyplot as plt
import datetime

POSITIVE_COUNT = 393
NEGATIVE_COUNT = 400666
TOTAL_COUNT = 401059
IMG_SIZE = (224, 224)
BATCH_SIZE = 32
SEED = 12
pos_dataset = image_dataset_from_directory('nvm/1', labels=[1] * POSITIVE_COUNT,
                                      seed=SEED, image_size=IMG_SIZE, batch_size=BATCH_SIZE, validation_split=0.3, subset='both')
neg_dataset = image_dataset_from_directory('nvm/0', labels=[0] * NEGATIVE_COUNT,
                                      seed=SEED, image_size=IMG_SIZE, batch_size=BATCH_SIZE, validation_split=0.3, subset='both')

train_ds = tf.data.Dataset.sample_from_datasets(
    [neg_dataset[0], pos_dataset[0].repeat(1019)], seed=SEED, stop_on_empty_dataset=False, rerandomize_each_iteration=True
)
val_ds = tf.data.Dataset.sample_from_datasets(
    [neg_dataset[1], pos_dataset[1]], seed=SEED, stop_on_empty_dataset=False
)

augmentation = keras.Sequential([
    layers.RandomRotation((-0.3, 0.3), seed=SEED),
    layers.RandomTranslation((-0.1, 0.1), (-0.1, 0.1), seed=SEED),
    layers.RandomZoom(0.2, seed=SEED),
    layers.RandomFlip(mode='horizontal', seed=SEED)
])

def create_model():
    inputs = keras.Input(shape=(*IMG_SIZE, 3))
    x = augmentation(inputs)
    x = layers.Rescaling(1./255)(inputs)
    x = layers.Conv2D(filters=32, kernel_size=(3, 3), activation="relu")(x)
    x = layers.MaxPooling2D(pool_size=(2, 2))(x)
    x = layers.Conv2D(filters=64, kernel_size=(3, 3), activation="relu")(x)
    x = layers.MaxPooling2D(pool_size=(2, 2))(x)
    x = layers.Conv2D(filters=64, kernel_size=(3, 3), activation="relu")(x)
    x = layers.MaxPooling2D(pool_size=(2, 2))(x)
    x = layers.Conv2D(filters=128, kernel_size=(3, 3), activation="relu")(x)
    x = layers.MaxPooling2D(pool_size=(2, 2))(x)
    x = layers.Dropout(0.5)(x)
    x = layers.Flatten()(x)
    x = layers.Dense(64, activation="relu")(x)
    x = layers.Dropout(0.3)(x)
    outputs = layers.Dense(1, activation="sigmoid")(x)
    model = keras.Model(inputs=inputs, outputs=outputs)
    optimizer = tf.keras.optimizers.Adam(learning_rate=0.001, ema_momentum=0.99)
    model.compile(loss="binary_crossentropy", optimizer=optimizer, metrics=[keras.metrics.AUC()])
    return model

log_dir = "logs/fit/" + 'exp2_' + datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)

model_concat = create_model()
history = model_concat.fit(train_ds, epochs=20, validation_data=val_ds, callbacks=[tensorboard_callback])

原因分析及解决方案

1. 修复代码中的核心bug

代码中数据增强层的结果被完全丢弃,这是导致模型行为异常的关键问题:

# 原错误代码
x = augmentation(inputs)  # 执行增强,但结果未使用
x = layers.Rescaling(1./255)(inputs)  # 直接使用原始输入,增强失效

# 修正后
x = augmentation(inputs)
x = layers.Rescaling(1./255)(x)  # 使用增强后的张量继续处理

该bug在两个平台都存在,但Colab上数据集本身可能因为类别不平衡(正样本极少),即使无增强也能快速过拟合,而M1平台叠加其他兼容性问题导致完全无法学习。

2. M1 TensorFlow-metal兼容性适配

禁用EMA优化器参数

TensorFlow-metal对Adam优化器的ema_momentum参数支持存在兼容性问题,尝试移除该参数使用标准Adam:

optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)

替换类别不平衡处理方式

使用class_weight替代数据集重复的方式,避免repeat与sample_from_datasets在M1上的交互异常:

# 计算类别权重
weight_for_0 = (1 / NEGATIVE_COUNT) * (TOTAL_COUNT) / 2.0
weight_for_1 = (1 / POSITIVE_COUNT) * (TOTAL_COUNT) / 2.0
class_weight = {0: weight_for_0, 1: weight_for_1}

# 修改训练集构建,无需重复正样本
train_ds = tf.data.Dataset.sample_from_datasets(
    [neg_dataset[0], pos_dataset[0]], seed=SEED, weights=[0.5, 0.5], 
    stop_on_empty_dataset=False, rerandomize_each_iteration=True
)

# 训练时传入类别权重
history = model_concat.fit(
    train_ds, epochs=20, validation_data=val_ds, 
    callbacks=[tensorboard_callback], class_weight=class_weight
)

验证GPU兼容性问题

临时禁用GPU,改用CPU训练,确认是否为TensorFlow-metal的问题:

tf.config.set_visible_devices([], 'GPU')

若CPU训练正常,更新到最新版本的TensorFlow组件:

pip install --upgrade tensorflow-macos tensorflow-metal

检查模型初始化

M1上部分层的初始化可能存在异常,手动指定初始化器并验证权重分布:

# 修改Conv2D层使用he_normal初始化
layers.Conv2D(filters=32, kernel_size=(3,3), activation="relu", kernel_initializer="he_normal")

# 打印初始权重均值,确认分布正常
model = create_model()
for layer in model.layers:
    if hasattr(layer, 'kernel'):
        print(f"{layer.name}: {layer.kernel.numpy().mean():.6f}")

3. 强化随机种子一致性

确保所有随机环节的种子覆盖,消除平台间随机数实现差异:

tf.random.set_seed(SEED)
np.random.seed(SEED)
import random
random.seed(SEED)

# 可选:启用tf.data调试模式,排查数据管道问题
tf.data.experimental.enable_debug_mode()

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 02:47:02