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

训练Chatbot模型出现Epoch损失为0异常,求问题排查方案

自定义Chatbot训练损失归零问题分析与解决思路

问题背景

作为深度学习领域新手,训练自定义Chatbot模型时遇到异常:100轮Epoch后损失变为0,模型完全失去训练价值。已自行编写preprocessing.py代码及training_data.json数据集,训练代码如下:

import tensorflow as tf
import json
import numpy as np
from tensorflow import keras
from keras.preprocessing.text import Tokenizer
from keras.utils.data_utils import pad_sequences
from keras.layers import Input, LSTM, Dense, Attention, Embedding, Dropout, LayerNormalization
from keras.models import Model
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping
from keras.initializers import GlorotUniform

def create_chatbot_model(vocab_size, max_seq_length):
    input_layer = Input(shape=(max_seq_length,))
    embedding_layer = Embedding(vocab_size, 512, embeddings_initializer=GlorotUniform(seed=42))(input_layer)
    lstm_layer = LSTM(1024, return_sequences=True, dropout=0.2, recurrent_dropout=0.2)(embedding_layer)
    attention_layer = Attention(use_scale=True)([lstm_layer, lstm_layer])
    normalized_attention = LayerNormalization()(attention_layer)
    dropout_layer = Dropout(0.2)(normalized_attention)
    output_layer = Dense(vocab_size, activation='softmax')(dropout_layer)

    model = Model(inputs=input_layer, outputs=output_layer)
    model.compile(loss='categorical_crossentropy', optimizer=Adam(learning_rate=0.0005))

    return model

def train_chatbot_model(model, x_train, y_train):
    optimizer = Adam(learning_rate=0.0005)
    loss_function = tf.keras.losses.CategoricalCrossentropy()

    train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(128)

    for epoch in range(100):
        epoch_loss = tf.keras.metrics.Mean()

        for x_batch, y_batch in train_dataset:
            with tf.GradientTape() as tape:
                logits = model(x_batch)
                loss_value = loss_function(y_batch, logits)
            
            gradients = tape.gradient(loss_value, model.trainable_variables)
            optimizer.apply_gradients(zip(gradients, model.trainable_variables))
            
            epoch_loss(loss_value)

        print(f'Epoch {epoch+1}, Loss: {epoch_loss.result()}')

# Load and preprocess the training data
def prepare_training_data():
    with open('training_data.json', 'r') as file:
        training_data = json.load(file)

    user_messages = []
    aim_responses = []
    for dialogues in training_data:
        user_dialogue = []
        aim_dialogue = []
        for turn in dialogues:
            if turn['role'] == 'user':
                user_dialogue.append(turn['content'])
            else:
                aim_dialogue.append(turn['content'])
        user_messages.append(user_dialogue)
        aim_responses.append(aim_dialogue)

    tokenizer = Tokenizer()
    tokenizer.fit_on_texts(np.concatenate(user_messages))

    max_seq_length = 30
    x_train = []
    y_train = []
    for user_dialogue, aim_dialogue in zip(user_messages, aim_responses):
        user_sequences = tokenizer.texts_to_sequences(user_dialogue)
        aim_sequences = tokenizer.texts_to_sequences(aim_dialogue)
        for i in range(1, len(user_sequences)):
            x_train.append(user_sequences[:i])
            y_train.append(aim_sequences[i])
    x_train = pad_sequences(x_train, maxlen=max_seq_length, padding='post')
    y_train = pad_sequences(y_train, maxlen=max_seq_length, padding='post')

    vocab_size = len(tokenizer.word_index) + 1

    return x_train, y_train, vocab_size, max_seq_length

# Main code execution
if __name__ == '__main__':
    x_train, y_train, vocab_size, max_seq_length = prepare_training_data()

    chatbot_model = create_chatbot_model(vocab_size, max_seq_length)

    train_chatbot_model(chatbot_model, x_train, y_train)

    chatbot_model.save('trained_chatbot_model.h5')

问题原因分析

  • 标签与损失函数不匹配:模型使用categorical_crossentropy损失,但y_train是整数索引序列,未做one-hot编码。该损失函数要求标签为one-hot格式,模型会错误将整数索引当作有效one-hot向量学习,最终输出对应位置的1,损失直接归零,但完全不具备泛化能力。
  • 数据预处理逻辑混乱:
    • 遍历对话时,假设用户消息和助手回复轮次完全匹配,若存在轮次不匹配的对话,会导致数据错位;
    • x_train.append(user_sequences[:i])将前i轮用户消息的序列列表直接作为输入,pad_sequences会把整个列表当成单个序列处理,输入格式完全错误;
    • Tokenizer仅在用户消息上训练,未包含助手回复的词汇,导致回复中的未知词汇被忽略,编码信息缺失。
  • 训练与模型结构的缺陷:
    • 未对pad的0位置做掩码处理,这些无效位置参与损失计算,模型会倾向于预测0来快速降低损失;
    • 自定义训练循环中重复定义优化器和损失函数,与模型compile时的配置冲突,可能导致优化状态异常;
    • 未使用EarlyStopping等回调,即使过拟合仍继续训练到100轮,加剧损失异常。

解决思路

  • 修正标签与损失函数匹配问题:
    改用SparseCategoricalCrossentropy损失(适配整数标签),同时移除输出层的softmax激活,使用from_logits=True让损失计算包含softmax,数值更稳定:

    # 修改模型输出层
    output_layer = Dense(vocab_size)(dropout_layer)
    # 修改损失函数
    loss_function = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
    
  • 修复数据预处理逻辑:

    • 确保用户消息与助手回复轮次匹配,过滤掉不匹配的对话:
      for user_dialogue, aim_dialogue in zip(user_messages, aim_responses):
          # 只保留轮次一致的对话
          if len(user_dialogue) != len(aim_dialogue):
              continue
          # 单轮对话配对
          for user_msg, aim_msg in zip(user_dialogue, aim_dialogue):
              x_train.append(user_msg)
              y_train.append(aim_msg)
      
    • Tokenizer同时拟合用户消息和助手回复的文本,覆盖全量词汇:
      # 展平所有文本
      user_flat = [msg for dialog in user_messages for msg in dialog]
      aim_flat = [msg for dialog in aim_responses for msg in dialog]
      tokenizer.fit_on_texts(user_flat + aim_flat)
      
  • 优化模型与训练逻辑:

    • 在Embedding层添加mask_zero=True,自动忽略pad的0位置:
      embedding_layer = Embedding(vocab_size, 512, mask_zero=True, embeddings_initializer=GlorotUniform(seed=42))(input_layer)
      
    • 使用model.fit()替代自定义训练循环,避免配置冲突,同时加入EarlyStopping防止过拟合:
      early_stopping = EarlyStopping(monitor='loss', patience=5, restore_best_weights=True)
      model.fit(x_train, y_train, batch_size=128, epochs=100, callbacks=[early_stopping])
      

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 20:57:40