项目实训(十三)
本文记录在项目中的textcnn
引言
对于文本分类问题,常见的方法无非就是抽取文本的特征,比如使用doc2evc或者LDA模型将文本转换成一个固定维度的特征向量,然后在基于抽取的特征训练一个分类器。 然而研究证明,TextCnn在文本分类问题上有着更加卓越的表现。从直观上理解,TextCNN通过一维卷积来获取句子中n-gram的特征表示。TextCNN对文本浅层特征的抽取能力很强,在短文本领域如搜索、对话领域专注于意图分类时效果很好,应用广泛,且速度快,一般是首选;对长文本领域,TextCNN主要靠filter窗口抽取特征,在长距离建模方面能力受限,且对语序不敏感。这点可以引入注意力机制解决。
CNN可以识别出当前任务中具有预言性的n元语法(且如果使用特征哈希可以使用无约束的n元语法词汇,同时保持词嵌入矩阵的约束);CNN卷积结构还允许有相似成分的n元语法分享预测行为,即使在预测过程中遇见未登录的特定n元语法;层次化的CNN每层有效着眼于句子中更长的n元语法,使得模型还可以对非连续n元语法敏感。
结构
步骤
首先将输入的数据进行预处理,将句子处理为相同的长度seq_len;
将处理好的句子输入embedding层进行向量化处理。在这一层中,我们可以加载使用预训练的词向量模型,也可以进行随机初始化。在这一层,词向量的维度为embed_size;这一层的输出是每个句字序列的矩阵X;
将矩阵X输入卷积层,在卷积层设置不同的卷积核,用于提取不同维度的特征。由于句子中相邻的单词关联性总是很高的,因此可以使用一维卷积。在textcnn中,卷积层包含了不同窗口大小的卷积核,卷积核宽度一致,为embed_size;这一层可以得到多个特征图;
将特征图输入池化层进行最大池化,池化后的结果进行拼接,输入全连接层并做softmax处理,最后得到最终结果。
代码实现
def __init__(self, dataset, embedding):
# 各类路径
self.model_name = 'TextCNN'
self.train_path = dataset + '/data/t_train.txt' # 训练集
self.dev_path = dataset + '/data/t_test.txt' # 验证集
self.test_path = dataset + '/data/t_test.txt' # 测试集
self.class_list = [x.strip() for x in open(
dataset + '/data/class2.txt', encoding='utf-8').readlines()] # 分类
self.save_path = dataset + '/saved_dict/' + self.model_name + '.ckpt' # 模型训练结果
self.log_path = dataset + '/log/' + self.model_name
# textcnn使用预先训练好的词向量作embedding layer。对于数据集里的所有词,因为每个词都可以表征成一个向量,因此我们可以得到一个嵌入矩阵
self.vocab_path = dataset + '/data/vocab.pkl' # 词表
self.embedding_pretrained = torch.tensor(
np.load(dataset + '/data/' + embedding)["embeddings"].astype('float32')) \
if embedding != 'random' else None # 预训练词向量
# CPU OR GPU
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 训练预测参数
self.dropout = 0.5 # 随机失活
self.require_improvement = 1000 # 若超过1000batch效果还没提升,则提前结束训练
self.num_classes = len(self.class_list) # 类别数
self.n_vocab = 0 # 词表大小,在运行时赋值
self.num_epochs = 75 # epoch数
self.batch_size = 128 # mini-batch大小
self.pad_size = 32 # 每句话处理成的长度(短填长切)
self.learning_rate = 1e-3 # 学习率
# 卷积网络参数
self.embed = self.embedding_pretrained.size(1) \
if self.embedding_pretrained is not None else 300 # 字向量维度
self.filter_sizes = (2, 3, 4) # 卷积核尺寸
self.num_filters = 256 # 卷积核数量(channels数)
def __init__(self, config):
super(Model, self).__init__()
# 加载预训练词向量或者随机初始化, 词向量维度为embed_size
# 在本模型中,使用预训练的词向量对字符进行初始化embedding
if config.embedding_pretrained is not None:
self.embedding = nn.Embedding.from_pretrained(config.embedding_pretrained, freeze=False)
else:
self.embedding = nn.Embedding(config.n_vocab, config.embed, padding_idx=config.n_vocab - 1)
# 储存不同 module,并自动将每个 module 的 parameters 添加到网络之中
# 输入一个句子,首先对这个句子进行切词,假设有s个单词。
# 对每个词,跟句嵌入矩阵M, 可以得到词向量。
# 假设词向量一共有d维。那么对于这个句子,便可以得到s行d列的矩阵A
# 把矩阵A看成是一幅图像,使用卷积神经网络去提取特征。
# 由于句子中相邻的单词关联性总是很高的,因此可以使用一维卷积。
# 即文本卷积与图像卷积的不同之处在于只在文本序列的一个方向(垂直)做卷积
# textcnn包含了不同窗口大小的卷积核,在这里设置的是(2,3,4),便于多特征提取
self.convs = nn.ModuleList(
[nn.Conv2d(1, config.num_filters, (k, config.embed)) for k in config.filter_sizes])
self.dropout = nn.Dropout(config.dropout)
self.fc = nn.Linear(config.num_filters * len(config.filter_sizes), config.num_classes)
# 卷积池化层,对三个特征图做最大池化
# 不同尺寸的卷积核得到的特征(feature map)大小也是不一样的,因此我们对每个特征图使用池化函数,使它们的维度相同。
# 常用的就是1-max pooling,提取出feature map照片那个的最大值,通过选择每个feature map的最大值,可捕获其最重要的特征。
# 这样每一个卷积核得到特征就是一个值,对所有卷积核使用1-max pooling。
def conv_and_pool(self, x, conv):
x = F.relu(conv(x)).squeeze(3)
x = F.max_pool1d(x, x.size(2)).squeeze(2)
return x
def forward(self, x):
out = self.embedding(x[0])
out = out.unsqueeze(1)
# 将池化层输出级联起来,可以得到最终的特征向量,这个特征向量再输入全连接做分类。
# 这个地方可以使用drop out防止过拟合。
out = torch.cat([self.conv_and_pool(out, conv) for conv in self.convs], 1)
out = self.dropout(out)
out = self.fc(out)
return out
将项目的数据通过textcnn进行分类后,取得了超过90%的准确度。成为整个项目中准确率最高的模型。
参考
https://blog.csdn.net/pipisorry/article/details/85076712
https://zhuanlan.zhihu.com/p/73176084