在PyTorch中实现Word Dropout:为UNK Token嵌入训练扩充样本
没问题,我来帮你把Word Dropout整合到你的CNN分类器里——这确实是训练UNK token嵌入的标准操作,能让模型更好地处理未见过的词汇。下面是完整的实现,默认UNK索引为0、Padding索引为1,如果你需要互换两者,只需要调整参数即可。
带Word Dropout的CNN分类器实现
我会基于你给出的Classifier类框架,补充完整的Word Dropout逻辑和CNN核心代码:
import torch import torch.nn as nn import torch.nn.functional as F class Classifier(nn.Module): def __init__(self, params): super(Classifier, self).__init__() # 从参数字典中提取必要配置 self.vocab_size = params['vocab_size'] self.embedding_dim = params['embedding_dim'] self.num_filters = params['num_filters'] self.filter_sizes = params['filter_sizes'] self.num_classes = params['num_classes'] # Word Dropout相关配置(可通过params灵活调整) self.word_dropout_prob = params.get('word_dropout_prob', 0.1) # 默认dropout概率设为0.1 self.unk_idx = params.get('unk_idx', 0) self.pad_idx = params.get('pad_idx', 1) # 嵌入层:指定padding_idx,让padding的嵌入固定为0(默认行为) self.embedding = nn.Embedding(self.vocab_size, self.embedding_dim, padding_idx=self.pad_idx) # 多尺寸卷积核的CNN层 self.convs = nn.ModuleList([ nn.Conv2d(1, self.num_filters, (fs, self.embedding_dim)) for fs in self.filter_sizes ]) # 最终分类全连接层 self.fc = nn.Linear(len(self.filter_sizes)*self.num_filters, self.num_classes) def word_dropout(self, input_ids): """ 核心Word Dropout逻辑:仅在训练模式下,随机将非Padding的token替换为UNK input_ids: 输入序列张量,形状为 [batch_size, seq_len] """ # 非训练模式或dropout概率为0时,直接返回原输入 if not self.training or self.word_dropout_prob == 0.0: return input_ids # 生成掩码:标记出非Padding的位置(这些位置才可能被替换) non_pad_mask = (input_ids != self.pad_idx) # 生成随机dropout掩码:按指定概率选中要替换的位置 dropout_mask = torch.rand(input_ids.shape, device=input_ids.device) < self.word_dropout_prob # 合并掩码:只有非Padding且被选中的位置才会被替换为UNK replace_mask = non_pad_mask & dropout_mask # 复制原输入并替换目标位置 output_ids = input_ids.clone() output_ids[replace_mask] = self.unk_idx return output_ids def forward(self, input_ids): # 第一步:应用Word Dropout(仅训练时生效) input_ids = self.word_dropout(input_ids) # 嵌入层转换:[batch_size, seq_len] -> [batch_size, seq_len, embedding_dim] embeds = self.embedding(input_ids) # 调整维度适配CNN输入:增加通道维度(CNN要求输入为[batch, channels, height, width]) embeds = embeds.unsqueeze(1) # 卷积+全局最大池化 conv_results = [] for conv in self.convs: # 卷积操作:输出形状为 [batch_size, num_filters, seq_len - filter_size + 1, 1] conv_out = F.relu(conv(embeds)) # 最大池化:对序列维度做池化,得到每个滤波器的全局最大值 pooled_out = F.max_pool2d(conv_out, (conv_out.shape[2], 1)).squeeze() conv_results.append(pooled_out) # 拼接所有尺寸滤波器的输出 concat_features = torch.cat(conv_results, dim=1) # 分类预测 logits = self.fc(concat_features) return logits
关键细节说明
- Word Dropout的边界控制:特意通过
non_pad_mask确保不会替换Padding token,因为Padding本身是填充的无意义符号,替换它对训练UNK嵌入没有帮助,还会干扰序列长度的一致性。 - 训练/推理模式适配:只有在
model.train()模式下才会启用Word Dropout,调用model.eval()时会自动跳过,保证推理时输入的稳定性。 - 参数灵活性:如果你需要互换UNK和Padding的索引,只需要在传入
params时修改unk_idx和pad_idx的值即可,比如params={'unk_idx':1, 'pad_idx':0, ...}。
实用建议
- 推荐把
word_dropout_prob设置在0.1~0.2之间,这个范围既能提供足够的UNK训练样本,又不会过度破坏输入的语义信息。 - 预处理数据集时,记得把低频词汇提前替换为UNK,结合Word Dropout能让模型对未见过的词汇具备更好的泛化能力。
- 训练时务必记得调用
model.train(),验证/测试时调用model.eval(),否则Word Dropout会在推理阶段错误生效。
内容的提问来源于stack exchange,提问作者Chum-Chum Scarecrows
相关产品推荐
相关产品推荐

