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

基于Keras的CNN+GRU地中海飓风序列图像检测模型优化咨询

问题描述

我正尝试在Keras中构建结合CNN结构与GRU/LSTM层的神经网络,用于地中海飓风(Medicanes)气候图分类:无飓风标0,存在标1。因飓风形成有时间关联性,模型需含CNN特征提取器与GRU时序层,且数据集过大需分批处理。

当前实现流程及代码如下:

数据导入

batch_size=120

train_ds = tf.keras.preprocessing.image_dataset_from_directory(
    "./Figures_1/Train",
    validation_split=None,
    subset=None,
    labels="inferred",
    label_mode="binary",
    color_mode="rgb",
    interpolation='bilinear',
    batch_size=batch_size,
    image_size=(600, 600),
    shuffle=False,
    seed=123
)

生成图像序列

sequence_lengh=60

def sequence_x(train_dataset):
    
    x_numpy = np.asarray(list(map(lambda x: x[0], tfds.as_numpy(train_dataset))),dtype=object)
    
    for element in range(0,x_numpy.shape[0]):
        for i in range(0, x_numpy.shape[0],sequence_lengh):
            x_seq = x_numpy[element][i:i+sequence_lengh]
            yield x_seq
        
def sequence_y(train_dataset):
    
    y_numpy = np.asarray(list(map(lambda x: x[1], tfds.as_numpy(train_dataset))),dtype=object)
    
    for element in range(0,y_numpy.shape[0]):
        for i in range(0, y_numpy.shape[0],sequence_lengh):
            y_seq = y_numpy[element][i:i+sequence_lengh]
            yield y_seq

CNN特征提取模型

from keras.layers import TimeDistributed, GRU

def build_convnet(shape=(600, 600, 3)):
    
    inputs = keras.Input(shape = shape)
    x = inputs

    # preprocessing
    x = keras.applications.densenet.preprocess_input(x)

    #Convbase
    x = convBase(x)
    x = layers.Flatten()(x)

    # Fine tuning
    x = keras.layers.Dense(1024, activation='relu')(x)
    x = layers.Dropout(0.2)(x)
    x = keras.layers.Dense(512, activation='relu')(x)
    x = keras.layers.GlobalMaxPool2D()
    
    return x

GRU时序模型

def action_model(shape=(15, 600, 600, 3), nbout=15):
    # Create our convnet with (112, 112, 3) input shape
    convnet = build_convnet(shape[1:]) #[1:]
    
    # then create our final model
    model = keras.Sequential()
    # add the convnet with (5, 112, 112, 3) shape
    model.add(TimeDistributed(convnet, input_shape=shape))
    # here, you can also use GRU or LSTM
    model.add(GRU(64))
    # and finally, we make a decision network
    model.add(Dense(1024, activation='relu'))
    model.add(Dropout(.5))
    model.add(Dense(512, activation='relu'))
    model.add(Dropout(.5))
    model.add(Dense(128, activation='relu'))
    model.add(Dropout(.5))
    model.add(Dense(64, activation='relu'))
    model.add(Dense(15, activation='softmax'))
    return model

迁移学习设置

convBase = DenseNet121(include_top=False, weights=None, input_shape=(600,600,3), pooling="avg")

for layer in convBase.layers: 
  if 'conv5' in layer.name:
    layer.trainable = True
for layer in convBase.layers: 
  if 'conv4' in layer.name:
    layer.trainable = True

模型编译

INSHAPE=(15, 600, 600, 3) # (5, 112, 112, 3)
model = action_model(INSHAPE, 1)
optimizer = keras.optimizers.Adam(0.001)

model.compile(
    optimizer,
    'categorical_crossentropy',
    metrics='accuracy'
)

模型训练

epochs = 10

for value in range(0, epochs):
    
    train_x, train_y = sequence_x(train_ds), sequence_y(train_ds)
    val_x, val_y = sequence_x(validation_ds), sequence_y(validation_ds)
    
    for i in range(0,278): #
        
        x = next(train_x, "none")
        y = next(train_y, "none")
        
        if (x!="none" or y!="none"):

            if (np.any(x) and np.any(y)):

                x_stack = np.stack((x[:15], x[15:30], x[30:45], x[45:]))
                y_stack = np.stack((y[:15], y[15:30], y[30:45], y[45:]))
                y_stack=y_stack.reshape(4,15)

                model.fit(x=x_stack, y=y_stack, 
                            validation_data=None, 
                            batch_size=None,
                            shuffle=False
                            )

            else:
                continue
        else:
            continue

模型可正常编译,但训练效果极差。请问我的实现存在哪些错误?是否有更高效的实现方式?


核心错误与优化方案

一、数据处理逻辑硬伤

  1. 序列生成逻辑完全混乱
    • sequence_x/sequence_y的嵌套循环逻辑错误:外层遍历数据集的batch数量,内层却用batch数作为步长切分单个batch内的图像,生成的序列完全没有时间连续性,根本无法让模型学习飓风的时序演化规律。
    • 一次性把所有数据转成object数组加载到内存,违背了分批处理的初衷,大数据集直接会爆内存。
  2. 训练时的序列拆分无意义
    把60长度的序列硬拆成4个15长度的子序列堆叠,相当于把连续的时间序列切碎打乱,模型完全学不到时序关联。

二、模型结构与配置错误

  1. CNN特征提取器输出失效
    • build_convnet最后一行x = keras.layers.GlobalMaxPool2D()没有调用层(缺少括号传入输入张量),导致输出是层对象而非特征张量,特征提取完全失效。
    • 已经用了DenseNet的pooling="avg",还额外加Flatten()和GlobalMaxPool2D,属于重复且错误的特征压缩,丢失大量空间特征。
  2. 分类头与任务不匹配
    • 任务是二分类(0/1),但模型最后一层用Dense(15, activation='softmax'),编译用categorical_crossentropy,完全不符合任务需求。应该改成Dense(1, activation='sigmoid'),损失用binary_crossentropy。
    • 调用action_model时传入nbout=1,但函数内部硬编码Dense(15),参数完全没生效。
  3. 迁移学习等于没用
    • DenseNet设置weights=None等于从头训练,完全没用到预训练权重,600x600的图像会让训练计算量爆炸,收敛极慢。应该改成weights="imagenet",再冻结前序层。
    • 解冻conv4和conv5的写法重复低效,可合并成一次循环判断。
  4. 模型结构冗余过拟合
    GRU输出后接4层Dense+Dropout,参数过多,对于二分类任务完全没必要,极易过拟合。

三、训练流程逻辑混乱

  1. 手动循环训练效率极低
    每次epoch重新生成序列生成器,手动调用next循环278次,完全没利用Keras的fit自动分批功能,而且每次只训练一个batch就结束,模型根本学不到有效特征。
  2. 验证集完全没正确使用
    代码里定义了val_x/val_y但没传入fit,无法监控模型泛化能力,也没法早停或调整参数。

高效实现方案

1. 正确的时序数据集生成(流式处理,不占内存)

用tf.data.Dataset的窗口函数生成时间序列,完全不需要手动写生成器:

import tensorflow as tf
from tensorflow import keras

sequence_length = 15
batch_size = 4

# 把批量数据集拆成单个样本
train_ds_single = train_ds.unbatch()

# 生成连续时间窗口:每个窗口包含sequence_length个连续样本,步长为1
train_seq_ds = train_ds_single.window(sequence_length, shift=1, drop_remainder=True)

# 将窗口转换为序列张量,这里取序列最后一个标签作为该序列的分类结果
def window_to_sequence(window):
    x = window.map(lambda img, lbl: img)
    y = window.map(lambda img, lbl: lbl)
    x_seq = tf.stack(list(x), axis=0)
    y_seq = tf.stack(list(y), axis=0)
    return x_seq, y_seq[-1]

train_seq_ds = train_seq_ds.flat_map(lambda w: tf.data.Dataset.from_tensor_slices(window_to_sequence(w)))

# 分批并预取,提升训练效率
train_seq_ds = train_seq_ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)

# 验证集用同样逻辑处理
val_ds_single = validation_ds.unbatch()
val_seq_ds = val_ds_single.window(sequence_length, shift=1, drop_remainder=True)
val_seq_ds = val_seq_ds.flat_map(lambda w: tf.data.Dataset.from_tensor_slices(window_to_sequence(w)))
val_seq_ds = val_seq_ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)

2. 修正后的模型结构

from tensorflow.keras.applications import DenseNet121
from tensorflow.keras import layers

# 修正CNN特征提取器
def build_convnet(shape=(600, 600, 3)):
    inputs = keras.Input(shape=shape)
    x = keras.applications.densenet.preprocess_input(inputs)
    
    # 使用ImageNet预训练权重
    convBase = DenseNet121(include_top=False, weights="imagenet", input_shape=shape, pooling="avg")
    # 冻结前3个卷积块,只训练conv4和conv5
    for layer in convBase.layers:
        if 'conv4' not in layer.name and 'conv5' not in layer.name:
            layer.trainable = False
    
    x = convBase(x)
    x = layers.Dense(512, activation='relu')(x)
    x = layers.Dropout(0.3)(x)
    return keras.Model(inputs, x)

# 修正后的时序模型
def action_model(shape=(15, 600, 600, 3)):
    convnet = build_convnet(shape[1:])
    model = keras.Sequential([
        layers.TimeDistributed(convnet, input_shape=shape),
        layers.GRU(64, return_sequences=False),
        layers.Dense(256, activation='relu'),
        layers.Dropout(0.3),
        layers.Dense(1, activation='sigmoid')  # 二分类输出
    ])
    return model

3. 正确的模型编译与训练

INSHAPE=(15, 600, 600, 3)
model = action_model(INSHAPE)
# 迁移学习用更小的学习率
optimizer = keras.optimizers.Adam(learning_rate=1e-4)

model.compile(
    optimizer=optimizer,
    loss='binary_crossentropy',
    metrics=['accuracy']
)

# 直接用处理好的时序数据集训练
history = model.fit(
    train_seq_ds,
    validation_data=val_seq_ds,
    epochs=10
)

额外优化建议

  • 把图像尺寸从600x600 resize到224x224(DenseNet默认输入尺寸),大幅降低计算量,同时预训练权重的适配性更好。
  • 尝试GRU设置return_sequences=True,后续加GlobalMaxPool1D捕捉整个序列的特征,可能比只取最后一个GRU输出效果更好。
  • 加入数据增强:在image_dataset_from_directory后添加layers.RandomFlip()、layers.RandomRotation()等,提升模型泛化能力。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 00:20:30