Word2Vec + LSTM 良好的训练和验证但测试不佳

Word2Vec + LSTM Good Training and Validation but Poor on Test

目前我正在训练我的 Word2Vec + LSTM 以进行 Twitter 情绪分析。我使用预训练的 GoogleNewsVectorNegative300 词嵌入。我使用预训练的 GoogleNewsVectorNegative300 的原因是当我使用自己的数据集训练自己的 Word2Vec 时性能更差。问题是为什么我的训练过程中验证 acc 和 loss 分别停留在 0.88 和 0.34。然后,我的混淆矩阵似乎也不对。这是我在拟合模型之前完成的几个过程

文本预处理:

  1. 下壳
  2. 删除主题标签、提及、URL、数字、将单词更改为数字、非 ASCII 字符、转发“RT”
  3. 展开收缩
  4. 用反义词代替否定词
  5. 删除标点符号
  6. 删除停用词
  7. 词形还原

我将 train:test 的数据集拆分为 90:10,如下所示:

def split_data(X, y):
    X_train, X_test, y_train, y_test = train_test_split(X, 
                                                        y,
                                                        train_size=0.9, 
                                                        test_size=0.1, 
                                                        stratify=y,
                                                        random_state=0)
    return X_train, X_test, y_train, y_test

导致训练的拆分数据有 2060 个样本,其中 708 个正面情绪 class、837 个负面情绪 class 和 515 个中性情绪 class

然后,我在所有训练数据上实现了文本增强,即 EDA(Easy Data Augmentation),如下所示:

class TextAugmentation:
    def __init__(self):
        self.augmenter = EDA()

    def replace_synonym(self, text):
        augmented_text_portion = int(len(text)*0.1) 
        synonym_replaced = self.augmenter.synonym_replacement(text, n=augmented_text_portion)
        return synonym_replaced

    def random_insert(self, text):
        augmented_text_portion = int(len(text)*0.1) 
        random_inserted = self.augmenter.random_insertion(text, n=augmented_text_portion)
        return random_inserted

    def random_swap(self, text):
        augmented_text_portion = int(len(text)*0.1)
        random_swaped = self.augmenter.random_swap(text, n=augmented_text_portion)
        return random_swaped

    def random_delete(self, text):
        random_deleted = self.augmenter.random_deletion(text, p=0.5)
        return random_deleted

text_augmentation = TextAugmentation()

导致训练的数据扩充有 10300 个样本,其中 3540 个正面情绪 class、4185 个负面情绪 class 和 2575 个中性情绪 class

然后,我将序列标记如下:

# Tokenize the sequence
pfizer_tokenizer = Tokenizer(oov_token='OOV')
pfizer_tokenizer.fit_on_texts(df_pfizer_train['text'].values)

X_pfizer_train_tokenized = pfizer_tokenizer.texts_to_sequences(df_pfizer_train['text'].values)
X_pfizer_test_tokenized = pfizer_tokenizer.texts_to_sequences(df_pfizer_test['text'].values)

# Pad the sequence
X_pfizer_train_padded = pad_sequences(X_pfizer_train_tokenized, maxlen=100)
X_pfizer_test_padded = pad_sequences(X_pfizer_test_tokenized, maxlen=100)

pfizer_max_length = 100
pfizer_num_words = len(pfizer_tokenizer.word_index) + 1

# Encode label
y_pfizer_train_encoded = df_pfizer_train['sentiment'].factorize()[0]
y_pfizer_test_encoded = df_pfizer_test['sentiment'].factorize()[0]

y_pfizer_train_category = to_categorical(y_pfizer_train_encoded)
y_pfizer_test_category = to_categorical(y_pfizer_test_encoded)

产生 8869 个唯一单词和 100 个最大序列长度

最后,我使用预训练的 GoogleNewsVectorNegative300 词嵌入将其拟合到我的模型中,但只使用权重和 LSTM,我再次将训练数据分成 10% 以进行验证,如下所示:

# Build single LSTM model
def build_lstm_model(embedding_matrix, max_sequence_length):
    # Input layer
    input_layer = Input(shape=(max_sequence_length,), dtype='int32')
    
    # Word embedding layer
    embedding_layer = Embedding(input_dim=embedding_matrix.shape[0],
                                output_dim=embedding_matrix.shape[1],
                                weights=[embedding_matrix],
                                input_length=max_sequence_length,
                                trainable=True)(input_layer)
    
    # LSTM model layer
    lstm_layer = LSTM(units=128,
                      dropout=0.5,
                      return_sequences=True)(embedding_layer)
    batch_normalization = BatchNormalization()(lstm_layer)
    
    lstm_layer = LSTM(units=128,
                      dropout=0.5,
                      return_sequences=False)(batch_normalization)
    batch_normalization = BatchNormalization()(lstm_layer)

    # Dense model layer
    dense_layer = Dense(units=128, activation='relu')(batch_normalization)
    dropout_layer = Dropout(rate=0.5)(dense_layer)
    batch_normalization = BatchNormalization()(dropout_layer)
    
    output_layer = Dense(units=3, activation='softmax')(batch_normalization)

    lstm_model = Model(inputs=input_layer, outputs=output_layer)

    return lstm_model

# Building single LSTM model
sinovac_lstm_model = build_lstm_model(SINOVAC_EMBEDDING_MATRIX, SINOVAC_MAX_SEQUENCE)
sinovac_lstm_model.summary()
sinovac_lstm_model.compile(loss='categorical_crossentropy',
                               optimizer=Adam(learning_rate=0.001),
                               metrics=['accuracy'])
sinovac_lstm_history = sinovac_lstm_model.fit(x=X_sinovac_train,
                                                  y=y_sinovac_train,
                                                  batch_size=64,
                                                  epochs=20,
                                                  validation_split=0.1,
                                                  verbose=1)

训练结果:

评价结果:

我真的需要一些建议或见解才能使我的测试准确无误

在不检查所有内容的情况下,一些 high-order 可能会限制您的结果的事情:

  • GoogleNews 向量是根据 2012 年及更早的 media-outlet 新闻故事训练的。 2020 年以后的推文使用了一种截然不同的语言风格。我不一定期望那些来自不同时代和 domain-of-writing 的预训练向量能够非常擅长为您需要的单词建模。 well-trained word2vec 模型(使用大量现代推文数据,具有良好的 preprocessing/tokenization 和参数化选择)很有可能会更好地工作,因此您可能需要重新考虑该选择。

  • GoogleNews 训练文本预处理,而据我所知从来没有 fully-documented,似乎没有压平所有大小写,也没有删除停用词,也没有涉及词形还原。它没有将明显的否定变异为反义词,但它确实执行了一些 single-words 的统计组合,变成了多重标记。因此,您的某些步骤可能会导致您的标记与该集合的向量的一致性降低——甚至会丢弃信息,例如单词的屈折变化,而这些信息本可以被有益地保留下来。确保你采取的每一步都是值得的 - 请注意,在推文上使用相同的 word2vec 训练预处理和后续步骤构建的足够的现代 word2vec 模型将完美匹配词汇表。

  • word2vec 模型和任何更深层的神经网络通常都需要大量数据才能很好地训练,并避免过度拟合。即使忽略来自 GoogleNews 的 9 亿个参数,您也在尝试从初始的仅 2060 tweet-sized 文本集(可能是 100KB 的数据)训练约 130k 个参数——至少 520KB 的状态。从某种意义上说,学习可泛化事物的模型往往是数据的压缩,并且比训练数据大得多的模型会带来严重过度拟合的风险。 (您用同义词替换单词的机械过程可能并没有真正为模型提供 word-vector 同义词之间的相似性尚未提供的任何信息。)因此:考虑缩小您的模型,并获得更多的训练数据 - 可能即使来自您主要分类兴趣之外的其他领域,只要 use-of-language 相似。