参考资料:github: textcnn.
数据处理
参考另一条博客的利用torchtext处理文本分类数据
torchtext: 数据处理.
定义分词函数,这里用jieba分词工具自定义分词函数
def word_cut(text):
text = regex.sub(' ', text)
return [word for word in jieba.cut(text) if word.strip()]
根据文件路径和预定义的文本预处理,生成文本数据集
def get_dataset(args, text_field, label_field):
text_field.tokenize = word_cut
train, val = data.TabularDataset.splits(
path=args.path, format='tsv', skip_header=True,
train='train.tsv', validation='valid.tsv',
fields=[
('label', label_field),
('text', text_field)
]
)
return train, val
定义迭代器生成迭代数据集来给模型输送批量数据
这里记得生成词表,然后统计词表数量为了后面embedding层做准备
def get_iter(args):
LABEL = data.Field(sequential=False, unk_token=None)
TEXT = data.Field(sequential=True, tokenize=word_cut)
train_iter, val_iter = load_dataset(TEXT, LABEL, args)
args.vocabulary_size = len(TEXT.vocab)
args.class_num = len(LABEL.vocab)
args.vocab = TEXT.vocab.stoi
args.label_vocab = LABEL.vocab.stoi
return train_iter, val_iter
模型搭建
基于cnn搭建一个文本分类的模型,理论知识可以看这篇论文
Convolutional Neural Networks for Sentence Classification.
模型的架构定义,首先设置embedding层的维度,行为词表数量,词嵌入的维度自己定义。然后定义cnn卷积层,分别定义三个卷积,分别是1,2,3,最后定义dropout层。最后通过线性层来将数据转化为为类别数量的向量维度。
class TextCNN(nn.Module):
def __init__(self, args):
super(TextCNN, self).__init__()
class_num = args.class_num
filter_num = args.filter_num
filter_sizes = args.filter_sizes
vocabulary_size = args.vocabulary_size
embedding_dimension = args.embedding_dim
self.embedding = nn.Embedding(vocabulary_size, embedding_dimension)
self.convs = nn.ModuleList(
[nn.Conv2d(1, filter_num, (size, embedding_dimension)) for size in filter_sizes])
self.dropout = nn.Dropout(args.dropout)
self.fc = nn.Linear(len(filter_sizes) * filter_num, class_num)
接着定义forward方法,首先,接受的数据定义是(batch_size, stence_len)维度的,经过embedding层,成为(batch_size, stence_len, embedding_dim)。这里提一下这个unsqueeze和sqeeze这两个函数,是为了调整张量数据维度的函数,unsqueeze就是增添多一个维度为1,而sqeeze就是将某个值为1的维度直接消除。这里为什么在第二个维度加1?因为这里设置了同一结构的卷积核是100个,那卷积之后的结果就有一百种不同的结果,需要多一个维度存储。然后卷积之后为什么要取消最后一个维度?因为文本的cnn卷积的dim设置就是embedding_dim,那卷积之后最后一维就为1,所以直接消除。最后再将三种不同结构的卷积核的结果连接起来。
def forward(self, x):
x = self.embedding(x)
x = x.unsqueeze(1)
x = [F.relu(conv(x)).squeeze(3) for conv in self.convs]
x = [F.max_pool1d(item, item.size(2)).squeeze(2) for item in x]
x = torch.cat(x, 1)
x = self.dropout(x)
output = self.fc(x)
return output
训练模型
模型和数据都定义好了,我们需要的是训练了。
首先要定义一个优化器,它能设置梯度更新的方式,然后让它来帮助我们更行模型的参数。然后将数据喂给模型得到最后的结果再输入到损失函数中去,这里定义的是交叉熵损失函数。注意这里优化器的一开始梯度归0和最后的梯度更新。
def train(train_iter, model, args):
optimizer = torch.optim.Adam(model.parameters(), lr=args.lr)
steps = 0
model.to(torch.device("cpu"))
model.train()
for epoch in tqdm(range(1, args.epochs + 1), desc='epoch:', colour='BLUE'):
for batch in train_iter:
feature, target = batch.text, batch.label
feature.data.t_()
optimizer.zero_grad()
output = model(feature)
loss = F.cross_entropy(output, target)
loss.backward()
optimizer.step()
steps += 1
save(model, "save_model", 'best2', steps)
接着定义模型的存储方法,主要还是torch.save方法。
def save(model, save_dir, save_prefix, steps):
if not os.path.isdir(save_dir):
os.makedirs(save_dir)
save_prefix = os.path.join(save_dir, save_prefix)
save_path = '{}_steps_{}.pt'.format(save_prefix, steps)
torch.save(model.state_dict(), save_path)
未完待续。。。。