TextCNN
作者:互联网
TextCNN
链接: 数据集 提取码: 6cgu
基于神经网络的文本分类
《Convolutional Neural Networks for Sentence Classification》
—基于卷积神经网络的句子分类
作者:Yoon Kim
单位:New York University
发表会议及时间:EMNLP 2014
本篇文章是卷积神经网络作为文本分类的开山之作
四 问题思索
一 论文导读
1.文本分类的简介
2.卷积神经网络相关技术
1.文本分类的简介
文本分类,指的是给定分类体系,将文本分类到某个或者某几个类别中。
根据其目标类别的数量,文本分类涵盖了二分类、多分类、多标签分类等常见分类场景。
文本分类任务是计算机语言学的一个分支,同时也是自然语言处理中最基础的任务。涵盖了新闻主题分类,情感分类,关系分类,意图识别等等,是具有重大的理论意义和应用价值。
文本分类中的关键问题在于文本表示,文本表示指的是,通过某种方式将自然语言文本编码成计算机可以处理的形式,向量。
主要有两种方式:1.基于独热的词袋子文本表示,2.基于词嵌入的文本表示
文本分类发展历史
基于规则的文本分类 --》基于特征的文本分类 --》 基于神经网络的文本分类
趋势:对文本中的关键信息捕获的更准确
一 基于规则的文本分类
基本思想:使用人工编写的特定规则进行分类,一般情况下,含有特定的词语、短语或者模式时就将其判断为相应的类别,是最早和最简单的一种分类方法。
大体流程:输入文本–》规则匹配–》输出类别
二 基于特征的文本分类
基本思想:通过人工设计和提取,例如:词法特征、句法特征等,使用机器学习模型来捕获句子中所蕴涵的关键信息,从而来减少噪声对最终结果的影响。
以空间向量模型为例:
- 使用词袋子模型来表示每个词
- 使用词项作为特征项,使用词在文档中的TF-IDF值作为词的权重(特征权重)
TF-IDF(词频-逆文档频率)算法是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
- 使用加权求和得到文本表示
- 训练一个分类器(LR\SVM )进行文本分类
三 基于神经网络的文本分类
基本思想:首先将输入的文本进行分词等一系列基础操作,随后将句子中的单词转化为低维的词表示,使用编码器(卷积神经网络、循环神经网络)得到句子表示,最终得到文本的目标类别。
大体流程:输入文本–》词表示–》编码器–》文本表示–》输出类别
总结:
基于规则的文本分类方法:
优点:易于实现、无需训练数据
缺点:人工成本较高,效果较差
基于特征的文本分类方法:
优点:易于复用、能够进行信息的筛选
缺点:人工提取特征的成本很高
基于神经网络的文本分类方法:
优点:无需人工特征、一般效果较好
缺点:可解释性差、训练资源消耗大
2.卷积神经网络相关技术
卷积神经网络:多层感知机是一种全连接结构,但是全连接的网路会存在一定的冗余,卷积神经网络通过局部连接和权重共享的方法来实现对多层感知机的共享。
具体操作:
卷积操作
池化操作:池化本身是一种降采样操作,用于降低维度并保留有效信息。
- 减少模型参数,避免过拟合,提高训练速度
- 保证特征的位置、旋转、伸缩不变性(cv方向)
- 将变长的输入转换为固定长度的输入(NLP方向)
二 论文精读
1.论文整体框架
2.传统和经典的方法
3.论文提出的模型
4.实验和结果
5.讨论和总结
1.论文整体框架
0.摘要
1.引言
2.模型
3.数据集和实验设置
4.实验结果和讨论
5.结论
2.传统和经典的方法
基于规则的文本分类方法:
优点:易于实现、无需训练数据
缺点:人工成本较高,效果较差
基于特征的文本分类方法:
优点:易于复用、能够进行信息的筛选
缺点:人工提取特征的成本很高
基于神经网络的文本分类方法:
优点:无需人工特征、一般效果较好
缺点:可解释性差、训练资源消耗大
3.论文提出的模型
模型结构
一 输入层
第i个词表示:一个k维实值向量
长度为n的序列表示:
其中的符号为拼接操作,n为最大维度,如果小于n的句子,则补充为全0向量
二 卷积层
卷积操作
上述公式表示使用窗口大小为h的滤波器
得到特征图
即当前滤波器所有输出的拼接
一个滤波器得到一个结果,通常我们会使用多个滤波器(调整感受野)来捕获多种特征
三 输出层
全连接层
将句子向量转化为一个维度和类别相同的数量
随机失活(dropout)
r是失活向量,以一定概率取值为1,否则取值为0
4.实验和结果
数据集
MR:电影评论极性判断数据集(正负极性两个类别)
SST-1:斯坦福情感分类标准数据集
SST-2:斯坦福情感二分类数据集
Subj:主客观判断数据集
TREC:TREC问题类型数据集
CR:商品评价性判断数据集
MOQA: MPQA的观点极性判断数据集
实验设置:
词向量参数会在训练时进行优化
实验结果:
结论:
- 实验对比了之前基于机器学习和人工特征的方法。整体来看,随机初始化词向量(CNN-rand)的方法性能不好,而使用预训练词向量的方法普遍较好。
- CNN-multichannel在小规模的数据集上有更好的表现,体现了一种折中思想,即不希望微调后的词向量距离原始值太远,有允许有一定的变化。
3. 原始词向量训练结果中,bad对应最相近的词为good,因为其上下文语境是极其类似的;在non-static的版本中,bad对应相似的词为terrible,因为在微调的过程中,词向量值发生变化,从而更加贴近数据集
4. 句子中!最接近较为激进的词汇,如lush等,而‘,’更接近于连接词
5.讨论和总结
- 开创性地使用神经网络来解决文本分类问题
- 进行充分的实验分析,探讨词向量对模型性能的影响
- 探讨神经网络中的一些操作对当前文本分类任务的影响
推荐相关论文:
- 《A Sensitivity Analysis of Convolutional Nerual Networks for Sentence Classification》
作者:Ye zhang and Byron C. Wallace
发表会议与时间 IJCNLP 2017
较充分探讨了cnn在文本分类任务上的各种情况,实验分析、挑战指导
三 代码实现
源代码网址:https://github.com/galsang/CNN-sentence-classification-pytorch
1.回顾
2.准备工作
3.数据预处理
4.训练模型
5.测试模型
6.讨论与总结
1.回顾
基于卷积神经网络的文本分类模型(TextCNN)
- 词向量分别使用预训练固定、预训练微调和随机初始化三种方案
- 分析预训练微调后的词向量和原始预训练词向量的差异
- TextCNN:
卷积模型:使用不同大小的滤波器
全连接层:dropout
2.准备工作
数据集链接中包含了上诉7个数据集,本实验只采用第一个数据集
3.数据预处理
3.1 读取数据集MR
def read_MR():
data = {}
x, y = [], []
with open("data/MR/rt-polarity.pos", "r", encoding="utf-8") as f:
for line in f: # 逐行添加
if line[-1] == "\n":
line = line[:-1]
x.append(line.split())
y.append(1) # 添加groudtruth
with open("data/MR/rt-polarity.neg", "r", encoding="utf-8") as f:
for line in f:
if line[-1] == "\n":
line = line[:-1]
x.append(line.split())
y.append(0) # 添加groudtruth
3.2划分训练集、验证集、测试集
x, y = shuffle(x, y) # shuffle是一种打乱操作,一定程度可以提高精确度,看不懂不重要
dev_idx = len(x) // 10 * 8
test_idx = len(x) // 10 * 9
data["train_x"], data["train_y"] = x[:dev_idx], y[:dev_idx] # 训练集
data["dev_x"], data["dev_y"] = x[dev_idx:test_idx], y[dev_idx:test_idx] # 验证集
data["test_x"], data["test_y"] = x[test_idx:], y[test_idx:] # 测试集
return data
4.训练模型
1.加载词向量和处理UNK 和 PAD
if params["MODEL"] != "rand":
# load word2vec
print("loading word2vec...")
word_vectors = KeyedVectors.load_word2vec_format("GoogleNews-vectors-negative300.bin", binary=True)
wv_matrix = []
for i in range(len(data["vocab"])):
word = data["idx_to_word"][i]
if word in word_vectors.vocab:
wv_matrix.append(word_vectors.word_vec(word))
else:
wv_matrix.append(np.random.uniform(-0.01, 0.01, 300).astype("float32"))
# one for UNK and one for zero padding
wv_matrix.append(np.random.uniform(-0.01, 0.01, 300).astype("float32"))
wv_matrix.append(np.zeros(300).astype("float32"))
wv_matrix = np.array(wv_matrix)
params["WV_MATRIX"] = wv_matrix
2.初始化相关参数
class CNN(nn.Module):
def __init__(self, **kwargs):
super(CNN, self).__init__()
self.MODEL = kwargs["MODEL"] # 模型状态
self.BATCH_SIZE = kwargs["BATCH_SIZE"] # batch大小
self.MAX_SENT_LEN = kwargs["MAX_SENT_LEN"] # 最长句子长度
self.WORD_DIM = kwargs["WORD_DIM"] # 词向量维度
self.VOCAB_SIZE = kwargs["VOCAB_SIZE"] # 字典大小
self.CLASS_SIZE = kwargs["CLASS_SIZE"] # 类别总数
self.FILTERS = kwargs["FILTERS"] # 滤波器宽度(感受野)
self.FILTER_NUM = kwargs["FILTER_NUM"] # 滤波器个数
self.DROPOUT_PROB = kwargs["DROPOUT_PROB"] #dropout概率
self.IN_CHANNEL = 1 # 词向量类型数
3.初始化卷积层和全连接层
# one for UNK and one for zero padding
self.embedding = nn.Embedding(self.VOCAB_SIZE + 2, self.WORD_DIM, padding_idx=self.VOCAB_SIZE + 1)
if self.MODEL == "static" or self.MODEL == "non-static" or self.MODEL == "multichannel":
self.WV_MATRIX = kwargs["WV_MATRIX"]
self.embedding.weight.data.copy_(torch.from_numpy(self.WV_MATRIX))
if self.MODEL == "static":
self.embedding.weight.requires_grad = False
elif self.MODEL == "multichannel":
self.embedding2 = nn.Embedding(self.VOCAB_SIZE + 2, self.WORD_DIM, padding_idx=self.VOCAB_SIZE + 1)
self.embedding2.weight.data.copy_(torch.from_numpy(self.WV_MATRIX))
self.embedding2.weight.requires_grad = False
self.IN_CHANNEL = 2
for i in range(len(self.FILTERS)):
conv = nn.Conv1d(self.IN_CHANNEL, self.FILTER_NUM[i], self.WORD_DIM * self.FILTERS[i], stride=self.WORD_DIM)
setattr(self, f'conv_{i}', conv)
self.fc = nn.Linear(sum(self.FILTER_NUM), self.CLASS_SIZE)
4.输入层+卷积层+池化层+输出层
def get_conv(self, i):
return getattr(self, f'conv_{i}')
def forward(self, inp):
x = self.embedding(inp).view(-1, 1, self.WORD_DIM * self.MAX_SENT_LEN)
if self.MODEL == "multichannel":
x2 = self.embedding2(inp).view(-1, 1, self.WORD_DIM * self.MAX_SENT_LEN)
x = torch.cat((x, x2), 1)
conv_results = [
F.max_pool1d(F.relu(self.get_conv(i)(x)), self.MAX_SENT_LEN - self.FILTERS[i] + 1)
.view(-1, self.FILTER_NUM[i])
for i in range(len(self.FILTERS))]
x = torch.cat(conv_results, 1)
x = F.dropout(x, p=self.DROPOUT_PROB, training=self.training)
x = self.fc(x)
return x
5.训练部分:
model = CNN(**params).cuda(params["GPU"])
parameters = filter(lambda p: p.requires_grad, model.parameters())
optimizer = optim.Adadelta(parameters, params["LEARNING_RATE"])
criterion = nn.CrossEntropyLoss() # 使用交叉熵损失函数
pre_dev_acc = 0
max_dev_acc = 0
max_test_acc = 0
for e in range(params["EPOCH"]):
data["train_x"], data["train_y"] = shuffle(data["train_x"], data["train_y"])
for i in range(0, len(data["train_x"]), params["BATCH_SIZE"]):
batch_range = min(params["BATCH_SIZE"], len(data["train_x"]) - i)
# 数据读取
batch_x = [[data["word_to_idx"][w] for w in sent] +
[params["VOCAB_SIZE"] + 1] * (params["MAX_SENT_LEN"] - len(sent))
for sent in data["train_x"][i:i + batch_range]]
batch_y = [data["classes"].index(c) for c in data["train_y"][i:i + batch_range]]
batch_x = Variable(torch.LongTensor(batch_x)).cuda(params["GPU"])
batch_y = Variable(torch.LongTensor(batch_y)).cuda(params["GPU"])
optimizer.zero_grad()
model.train()
pred = model(batch_x)
loss = criterion(pred, batch_y)
loss.backward()
nn.utils.clip_grad_norm(parameters, max_norm=params["NORM_LIMIT"]) # 计算损失并回传
optimizer.step()
dev_acc = test(data, model, params, mode="dev")
test_acc = test(data, model, params)
print("epoch:", e + 1, "/ dev_acc:", dev_acc, "/ test_acc:", test_acc)
if params["EARLY_STOPPING"] and dev_acc <= pre_dev_acc:
print("early stopping by dev_acc!")
break
else:
pre_dev_acc = dev_acc
if dev_acc > max_dev_acc: # 根据验证集来选择模型
max_dev_acc = dev_acc
max_test_acc = test_acc
best_model = copy.deepcopy(model)
print("max dev acc:", max_dev_acc, "test acc:", max_test_acc)
6.模型测试:词转为ID 、模型测试、 统计结果
def test(data, model, params, mode="test"):
model.eval()
if mode == "dev":
x, y = data["dev_x"], data["dev_y"]
elif mode == "test":
x, y = data["test_x"], data["test_y"]
x = [[data["word_to_idx"][w] if w in data["vocab"] else params["VOCAB_SIZE"] for w in sent] +
[params["VOCAB_SIZE"] + 1] * (params["MAX_SENT_LEN"] - len(sent))
for sent in x]
x = Variable(torch.LongTensor(x)).cuda(params["GPU"])
y = [data["classes"].index(c) for c in y]
pred = np.argmax(model(x).cpu().data.numpy(), axis=1)
acc = sum([1 if p == y else 0 for p, y in zip(pred, y)]) / len(pred)
return acc
5.测试模型
5.讨论和总结
标签:self,分类,dev,TextCNN,test,文本,data 来源: https://blog.csdn.net/qq_41375318/article/details/103210165