基于LSTM的TensorFlow验证码模型训练形状不兼容问题求助
问题分析与解决方案
核心错误原因
- 标签处理逻辑错误:使用
class_mode='input'会将输入图像本身作为标签,而你的实际标签是验证码字符串,直接导致模型接收的标签形状与输出形状完全不匹配。 - 标签未做序列编码:模型输出形状为
(None, 10, 21)(对应10个字符位置,每个位置21类),但原始标签是未编码的字符串,未转换为匹配的序列格式。 - 变量名笔误:模型定义时赋值给
self.model,但编译和训练时误用self.modelo,导致训练的是未正确初始化的模型。
分步解决方案
1. 重构数据加载与标签编码
放弃ImageDataGenerator的默认标签处理,改用tf.data.Dataset构建适配序列标签的数据集,确保标签形状与模型输出匹配:
import tensorflow as tf # 复用已有的字符转数字层,确保词汇表正确 char_to_num = tf.keras.layers.StringLookup(vocabulary=list(self.char_to_num.get_vocabulary()), mask_token=None) num_classes = len(char_to_num.get_vocabulary()) + 1 # 包含padding类别 # 定义标签处理函数:字符串转10位数字序列,再转one-hot编码 def process_label(label): # 拆分字符串为单个字符 chars = tf.strings.unicode_split(label, input_encoding='UTF-8') # 转换为数字序列 num_seq = char_to_num(chars) # 强制序列长度为10(与模型RepeatVector参数一致),不足补0,过长截断 num_seq = tf.ensure_shape(num_seq, (10,)) # 转one-hot编码,匹配模型输出形状 return tf.one_hot(num_seq, depth=num_classes) # 定义图像加载与预处理函数 def load_image(filename, label): img = tf.io.read_file(tf.strings.join([self.datadir, '/', filename])) img = tf.image.decode_png(img, channels=3) # 若图像是jpg格式,改用decode_jpeg img = tf.image.resize(img, (224, 224)) img = img / 255.0 # 归一化处理 return img, process_label(label) # 构建并划分数据集 def load_data(): image_names = os.listdir(self.datadir) labels = [name.split('.')[0] for name in image_names] # 过滤长度不为10的验证码(与模型参数匹配) valid_pairs = [(img, lbl) for img, lbl in zip(image_names, labels) if len(lbl) == 10] image_names, labels = zip(*valid_pairs) dataset = tf.data.Dataset.from_tensor_slices((list(image_names), list(labels))) dataset = dataset.map(load_image, num_parallel_calls=tf.data.AUTOTUNE) # 划分训练/验证集 total_size = len(image_names) train_size = int(0.8 * total_size) self.train_dataset = dataset.take(train_size).shuffle(1000).batch(32).prefetch(tf.data.AUTOTUNE) self.valid_dataset = dataset.skip(train_size).batch(32).prefetch(tf.data.AUTOTUNE)
2. 修正模型定义的笔误与细节
将模型编译的变量名从self.modelo改为self.model,同时统一类别数定义:
mobilnet = hub.KerasLayer(self.url, input_shape=(224, 224, 3), dtype="float32", name="img") self.model = keras.Sequential( [ mobilnet, layers.Dropout(0.25), layers.Flatten(), layers.RepeatVector(10), # 必须与验证码长度一致 layers.LSTM(128, return_sequences=True), layers.Dense(num_classes, activation="softmax"), # 使用统一的类别数 ] ) self.model.summary() self.model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.001), loss="categorical_crossentropy", metrics=["accuracy"])
3. 修正训练代码的变量名
把训练时的self.modelo.fit改为self.model.fit:
log_dir = "logs/fit/" + datetime.datetime.now().strftime("%Y%m%d-%H%M%S") callbacks = [ keras.callbacks.EarlyStopping(monitor="val_loss", patience=10, restore_best_weights=True), keras.callbacks.ModelCheckpoint(filepath='best_model_weights.h5', save_best_only=True, save_weights_only=True), tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1) ] self.model.fit( self.train_dataset, validation_data=self.valid_dataset, epochs=self.epochs, callbacks=callbacks, )
额外优化建议
- 如果不想使用one-hot编码,可改用
SparseCategoricalCrossentropy损失函数,此时只需保留数字序列标签(去掉process_label中的tf.one_hot步骤),损失函数修改为:self.model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.001), loss=tf.keras.losses.SparseCategoricalCrossentropy(), metrics=["accuracy"]) - 如果验证码长度不是10,需同步修改
RepeatVector参数、process_label中的序列长度限制。
内容的提问来源于stack exchange,提问作者antoniobueno
相关产品推荐
相关产品推荐

