0%

[DL] PyTorch 折桂 17:使用 TorchText 和 transformers 进行情感分类(1)

我们已经了解了 PyTorch 的基本操作和功能,现在让我们实践一下。自从 transformer 横空出世以后,在 NLP 领域有”大一统“ 的趋势。但 transformer 的本质是什么?transformer 的本质是一个能够有效提取语义信息的词嵌入生成器,它比前辈 word2vec、GloVe 等等能够更有效地提取词语的语义信息,所以以 transformer 生成的词嵌入可以有 SOTA(state-of-the-art,最高水平)的性能。这等于电脑可以更好地理解文本中每个词语的意思,理解了每个词语的意思自然就可以更好地理解文本的整体意思。所以 transformer 只是取代了以前用的 Embedding 层,根据具体的任务的不同还可以接上 CNN、RNN 等层。

本文及下一篇文章中,我们将使用 PyTorch,TorchText 和 transformers 库里的 Bert 预训练模型来进行一个基本的情感分类任务:IMDB 影片评论的情感分类。

Bert 之类的 transformer 预训练模型虽然性能强大,它也有一个致命的缺点,即资源的消耗非常巨大。简单的模型跑小数据库还可以在个人 PC 上运行,Bert 之类的模型必须用到 GPU。写这篇文章的时候,我是在 Google Colab 上完成模型的训练的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>>  !nvidia-smi
Wed Jun 10 18:00:42 2020
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.82 Driver Version: 418.67 CUDA Version: 10.1 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 Tesla P100-PCIE... Off | 00000000:00:04.0 Off | 0 |
| N/A 69C P0 48W / 250W | 9149MiB / 16280MiB | 0% Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes: GPU Memory |
| GPU PID Type Process name Usage |
|=============================================================================|
+-----------------------------------------------------------------------------+

本文的代码来自 Transformers for Sentiment Analysis

*注:本文的很多资源可能需要科学上网,不能科学上网的话我也没办法哈。

1. 必要库的加载

首先安装最新版 PyTorch,TorchText 和 transformers。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
!pip install -U torchtext
!pip install -U torch
!pip install -U transformers

import torch

import random

SEED = 1234
# 初始化随机种子
random.seed(SEED)
np.random.seed(SEED)
torch.manual_seed(SEED)
torch.backends.cudnn.deterministic = True # 可以加快训练速度一点点

虽然每一步都会加载必需的库,我还是会把完整路径写出来,方便确认从属。

2. 获取数据:

在 Kaggle 上下载数据

3. 分词器的准备

我们将使用 Bert 预训练模型的分词器。

1
2
3
from transformers import BertTokenizer

tokenizer = transformers.BertTokenizer.from_pretrained('bert-base-uncased')

‘uncased’ 意味着这个分词器是不区分大小写的,意味着仅仅处理小写字母。不用担心,分词器会自动把文本转换成小写形式再处理。

1
2
3
4
5
6
7
8
9
>>> tokens = tokenizer.tokenize('Hello WORLD how ARE yoU?') # 大小写不敏感的预训练模型会自动转换大小写

>>> print(tokens)
['hello', 'world', 'how', 'are', 'you', '?']

>>> indexes = tokenizer.convert_tokens_to_ids(tokens) # 找到 token 对应的 id

>>> print(indexes)
[7592, 2088, 2129, 2024, 2017, 1029]

接下来我们需要指定 4 个特殊的 token:句起始 token,句结束 token,填充 token 和未知词语 token 备用。

1
2
3
4
5
6
7
>>> init_token_idx = tokenizer.cls_token_id # 起始
>>> eos_token_idx = tokenizer.sep_token_id # 结束
>>> pad_token_idx = tokenizer.pad_token_id # 填充
>>> unk_token_idx = tokenizer.unk_token_id # 未知

>>> print(init_token_idx, eos_token_idx, pad_token_idx, unk_token_idx)
101 102 0 100

然后我们还需要获得预训练 Bert 模型的序列长度。

1
2
3
4
>>> max_input_length = tokenizer.max_model_input_sizes['bert-base-uncased']

>>> print(max_input_length)
512

4. 构建分词器

上一篇文章里说我们可以使用 spacy 作为分词器,这里我们使用 BertTokenizer

1
2
3
4
def tokenize_and_cut(sentence):
tokens = tokenizer.tokenize(sentence)
tokens = tokens[:max_input_length-2]
return tokens

因为预训练 Bert 模型的最长序列为 512,为给数据点加上 [CLS][EOS],我们需要把分词后的序列长度减 2.

5. 定义 field

1
2
3
4
5
6
7
8
9
10
11
12
from torchtext import data

TEXT = torchtext.data.Field(batch_first = True,
use_vocab = False,
tokenize = tokenize_and_cut,
preprocessing = tokenizer.convert_tokens_to_ids,
init_token = init_token_idx,
eos_token = eos_token_idx,
pad_token = pad_token_idx,
unk_token = unk_token_idx)

LABEL = torchtext.data.LabelField(dtype = torch.float)

因为 batch 在第一维,所以我们设定 batch_first = True。由于我们已经有了单词表(bert 的 embedding),所以需要设置 use_vocab = False。然后我们在 preprocessing 这里将 token 转换成对应的 id。最后,我需要定义特殊的 token id。

6. 加载数据

1
2
3
4
5
from torchtext import datasets

train_data, test_data = torchtext.datasets.IMDB.splits(TEXT, LABEL)

train_data, valid_data = train_data.split(random_state = random.seed(SEED))

IMDB 数据库使用 splts 方法创建训练集和测试集,具体参数如下:

1
splits(text_field, label_field, root='.data', train='train', test='test', **kwargs)

两个必需参数 text_fieldlabel_field 分别对应了文本与标签的 field。

7. 建立标签的词汇表

虽然我们已经在定义 field 的时候定义了 TEXT 的词汇表,我们还需要将 LABEL 转换为数字。

1
LABEL.build_vocab(train_data)

8. 创建训练集、验证集和测试集

最后我们使用 BucketIterator 创建训练集、验证集和测试集的迭代器。这里我们使用 128 作为 batch size。

1
2
3
4
5
6
7
8
BATCH_SIZE = 128

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,
device = device)

欢迎关注我的微信公众号“花解语 NLP”:

欢迎关注我的其它发布渠道