携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第30天,点击查看活动详情
说明
本系列博客将记录自己学习的课程:NLP实战高手课,链接为:time.geekbang.org/course/intr… 本篇为27-28节的课程笔记,主要介绍Pytorch中使用torchtext进行文本分类的示例,本篇博客将介绍torchtext对文本的预处理操作和使用的模型基本定义。
导语
本篇博客记录了该课程的第一次项目代码示例,主要是让大家熟悉NLP的项目结构,此节课中使用的一些函数或者API可能在实际开发中并不常用。
首先,导师给出了如何入手或者进入某个新的研究方向时的入门方式:
- 不建议的方式:找本书,从头看,一点一点模块自己写,这将会消耗大量时间;
- 建议的方式:找个比较好的开源项目示例,进行修改:
- 找到开源项目代码,先跑通代码
- 理清项目代码的整体逻辑,不要太注重细节
- 检查每个模块,熟悉代码
本节课使用Pytorch官方提供的torchtext库进行IMDB数据集上的影评分类,这是一个二分类问题。
代码实现
预处理与准备
首先,我们导入分词器并使用torchtext进行IMDB数据集加载和划分,其相应的代码如下:
import nltk
from nltk.tokenize import word_tokenize
nltk.download('punkt')
tokenizer = word_tokenize
接下来,我们将随机数种子固定,并定义数据集两个field为TEXT和LABEL。
import torch
from torchtext import data
from torchtext import datasets
SEED = 1234
torch.manual_seed(SEED)
torch.backends.cudnn.deterministic = True
TEXT = data.Field(tokenize = tokenizer, include_lengths = True)
LABEL = data.LabelField(dtype = torch.float)
数据集加载
首先,使用torchtext中的数据集加载函数进行数据集加载,
from torchtext import datasets
train_data, test_data = datasets.IMDB.splits(TEXT, LABEL)
同时,我们需要从训练集中划分一部分当做验证集。
import random
train_data, valid_data = train_data.split(random_state = random.seed(SEED))
接着,我们需要构建词表,这里选取了出现频次最高的MAX_VOCAB_SIZE = 25000个单词,这些单词之外的都将被映射为unknown词汇。这里,我们的词向量选取的是glove词汇表中的词汇。
MAX_VOCAB_SIZE = 25000
TEXT.build_vocab(train_data,
max_size = MAX_VOCAB_SIZE,
vectors = "glove.6B.300d",
unk_init = torch.Tensor.normal_)
LABEL.build_vocab(train_data)
最后,我们定义batch_size和Dataloader
BATCH_SIZE = 64
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits(
(train_data, valid_data, test_data),
batch_size = BATCH_SIZE,
sort_within_batch = True,
device = device)
网络与优化器、损失函数、评估指标定义
首先,我们定义要用到的网络结构:
import torch.nn as nn
class RNN(nn.Module):
def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim, n_layers,
bidirectional, dropout, pad_idx):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embedding_dim, padding_idx = pad_idx)
self.rnn = nn.LSTM(embedding_dim,
hidden_dim,
num_layers=n_layers,
bidirectional=bidirectional,
dropout=dropout)
self.fc = nn.Linear(hidden_dim * 2, output_dim)
self.dropout = nn.Dropout(dropout)
def forward(self, text, text_lengths):
embedded = self.embedding(text)
packed_embedded = nn.utils.rnn.pack_padded_sequence(embedded, text_lengths)
packed_output, (hidden, cell) = self.rnn(packed_embedded)
output, output_lengths = nn.utils.rnn.pad_packed_sequence(packed_output)
hidden = self.dropout(torch.cat((hidden[-2,:,:], hidden[-1,:,:]), dim = 1))
return self.fc(hidden)
这里使用的是RNN模型的一个变体LSTM作为整个模型的backbone,整体模型主要由Embedding层、LSTM层和最后的Linear层组成,其中网络中设置dropout来避免过拟合。
接下来,我们实例化一个模型并设置相应的超参数:
INPUT_DIM = len(TEXT.vocab)
EMBEDDING_DIM = 300
HIDDEN_DIM = 256
OUTPUT_DIM = 1
N_LAYERS = 2
BIDIRECTIONAL = True
DROPOUT = 0.2
PAD_IDX = TEXT.vocab.stoi[TEXT.pad_token]
model = RNN(INPUT_DIM,
EMBEDDING_DIM,
HIDDEN_DIM,
OUTPUT_DIM,
N_LAYERS,
BIDIRECTIONAL,
DROPOUT,
PAD_IDX)
这里主要设置了Embedding的维度为300,LSTM中的隐藏层的维度为256,输出为1个数值标量,使用2层的LSTM层进行堆叠,同时这个网络层是双向的LSTM,并设置好了PAD的token用于填充无意义的位置。
进行完模型实例化后,我们需要定义和使用优化器和损失函数,其相关定义代码如下所示:
import torch.optim as optim
optimizer = optim.Adam(model.parameters())
criterion = nn.BCEWithLogitsLoss()
model = model.to(device)
criterion = criterion.to(device)
这里,我们使用了Adam优化器,损失函数则是选取了传统的二分类BCELoss。
总结
本文介绍了torchtext上的文本预处理流程并定义了我们自己模型,下篇博客将进行训练和评估。