NLP实战高手课学习笔记(14):文本分类实践1--预处理与模型定义

232 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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上的文本预处理流程并定义了我们自己模型,下篇博客将进行训练和评估。