概述
文本分类
文本情感分类数据
我们使用斯坦福的IMDb数据集(Stanford’s Large Movie Review Dataset)作为文本情感分类的数据集。
读取数据
def read_imdb(folder='train', data_root="/home/kesci/input/IMDB2578/aclImdb_v1/aclImdb"):
data = []
for label in ['pos', 'neg']:
folder_name = os.path.join(data_root, folder, label)
for file in tqdm(os.listdir(folder_name)):
with open(os.path.join(folder_name, file), 'rb') as f:
review = f.read().decode('utf-8').replace('n', '').lower()
data.append([review, 1 if label == 'pos' else 0])
random.shuffle(data)
return data
预处理数据
读取数据后,我们先根据文本的格式进行单词的切分,再利用 torchtext.vocab.Vocab 创建词典。
def get_tokenized_imdb(data): #分词
def tokenizer(text):
return [tok.lower() for tok in text.split(' ')]
return [tokenizer(review) for review, _ in data]
def get_vocab_imdb(data): # 建立词典
tokenized_data = get_tokenized_imdb(data)
counter = collections.Counter([tk for st in tokenized_data for tk in st])
return Vocab.Vocab(counter, min_freq=5)
def preprocess_imdb(data, vocab): # 索引化并截断或补足训练集
max_l = 500 # 将每条评论通过截断或者补0,使得长度变成500
def pad(x):
return x[:max_l] if len(x) > max_l else x + [0] * (max_l - len(x))
tokenized_data = get_tokenized_imdb(data)
features = torch.tensor([pad([vocab.stoi[word] for word in words]) for words in tokenized_data])
labels = torch.tensor([score for _, score in data])
return features, labels
创建数据迭代器
利用 torch.utils.data.TensorDataset,可以创建 PyTorch 格式的数据集,从而创建数据迭代器。
train_set = Data.TensorDataset(*preprocess_imdb(train_data, vocab))
test_set = Data.TensorDataset(*preprocess_imdb(test_data, vocab))
batch_size = 64
train_iter = Data.DataLoader(train_set, batch_size, shuffle=True)
test_iter = Data.DataLoader(test_set, batch_size)
使用循环神经网络
双向循环神经网络
H
→
t
=
ϕ
(
X
t
W
x
h
(
f
)
+
H
→
t
−
1
W
h
h
(
f
)
+
b
h
(
f
)
)
H
←
t
=
ϕ
(
X
t
W
x
h
(
b
)
+
H
←
t
+
1
W
h
h
(
b
)
+
b
h
(
b
)
)
begin{aligned} &overrightarrow{boldsymbol{H}}_{t}=phileft(boldsymbol{X}_{t} boldsymbol{W}_{x h}^{(f)}+overrightarrow{boldsymbol{H}}_{t-1} boldsymbol{W}_{h h}^{(f)}+boldsymbol{b}_{h}^{(f)}right)\ &overleftarrow{boldsymbol{H}}_{t}=phileft(boldsymbol{X}_{t} boldsymbol{W}_{x h}^{(b)}+overleftarrow{boldsymbol{H}}_{t+1} boldsymbol{W}_{h h}^{(b)}+boldsymbol{b}_{h}^{(b)}right) end{aligned}
Ht=ϕ(XtWxh(f)+Ht−1Whh(f)+bh(f))Ht=ϕ(XtWxh(b)+Ht+1Whh(b)+bh(b))
其中
X
t
∈
R
n
×
d
boldsymbol{X}_tinmathbb{R}^{ntimes d}
Xt∈Rn×d为时间步(批量大小为
n
n
n,输入维度为
d
d
d),
H
→
t
∈
R
n
×
h
,
H
←
t
∈
R
n
×
h
overrightarrow{boldsymbol{H}}_{t} in mathbb{R}^{n times h},overleftarrow{boldsymbol{H}}_{t} in mathbb{R}^{n times h}
Ht∈Rn×h,Ht∈Rn×h
权重
W
x
h
(
f
)
∈
R
d
×
h
,
W
h
h
(
f
)
∈
R
h
×
h
,
W
x
h
(
b
)
∈
R
d
×
h
,
W
h
h
(
b
)
∈
R
h
×
h
boldsymbol{W}_{x h}^{(f)} in mathbb{R}^{d times h}, boldsymbol{W}_{h h}^{(f)} in mathbb{R}^{h times h}, boldsymbol{W}_{x h}^{(b)} in mathbb{R}^{d times h}, boldsymbol{W}_{h h}^{(b)} in mathbb{R}^{h times h}
Wxh(f)∈Rd×h,Whh(f)∈Rh×h,Wxh(b)∈Rd×h,Whh(b)∈Rh×h和偏差
b
h
(
f
)
∈
R
1
×
h
,
b
h
(
b
)
∈
R
1
×
h
boldsymbol{b}_{h}^{(f)} in mathbb{R}^{1 times h}, boldsymbol{b}_{h}^{(b)} in mathbb{R}^{1 times h}
bh(f)∈R1×h,bh(b)∈R1×h均未模型参数,
ϕ
phi
ϕ为隐藏层激活函数。
然后我们连结两个方向的隐藏状态
H
→
t
overrightarrow{boldsymbol{H}}_{t}
Ht和
H
←
t
overleftarrow{boldsymbol{H}}_{t}
Ht来得到隐藏状态
H
t
∈
R
n
×
2
h
boldsymbol{H}_{t} in mathbb{R}^{n times 2 h}
Ht∈Rn×2h,并将其输入到输出层。输出层计算输出
O
t
∈
R
n
×
q
boldsymbol{O}_{t} in mathbb{R}^{n times q}
Ot∈Rn×q(输出维度为
q
q
q):
O
t
=
H
t
W
h
q
+
b
q
boldsymbol{O}_{t}=boldsymbol{H}_{t} boldsymbol{W}_{h q}+boldsymbol{b}_{q}
Ot=HtWhq+bq
权重
W
h
q
∈
R
2
h
×
q
boldsymbol{W}_{h q} in mathbb{R}^{2 h times q}
Whq∈R2h×q,偏差
b
q
∈
R
1
×
q
boldsymbol{b}_{q} in mathbb{R}^{1 times q}
bq∈R1×q
利用 torch.nn.RNN 或 torch.nn.LSTM 模组,我们可以很方便地实现双向循环神经网络,下面是以 LSTM 为例的代码。
class BiRNN(nn.Module):
def __init__(self, vocab, embed_size, num_hiddens, num_layers):
super(BiRNN, self).__init__()
self.embedding = nn.Embedding(len(vocab), embed_size)
self.encoder = nn.LSTM(input_size=embed_size,
hidden_size=num_hiddens,
num_layers=num_layers,
bidirectional=True) # bidirectional设为True即得到双向循环神经网络
self.decoder = nn.Linear(4*num_hiddens, 2) # 初始时间步和最终时间步的隐藏状态作为全连接层输入
def forward(self, inputs):
'''
@params:
inputs: 词语下标序列,形状为 (batch_size, seq_len) 的整数张量
@return:
outs: 对文本情感的预测,形状为 (batch_size, 2) 的张量
'''
# 因为LSTM需要将序列长度(seq_len)作为第一维,所以需要将输入转置
embeddings = self.embedding(inputs.permute(1, 0)) # (seq_len, batch_size, d)
# rnn.LSTM 返回输出、隐藏状态和记忆单元,格式如 outputs, (h, c)
outputs, _ = self.encoder(embeddings) # (seq_len, batch_size, 2*h)
encoding = torch.cat((outputs[0], outputs[-1]), -1) # (batch_size, 4*h)
outs = self.decoder(encoding) # (batch_size, 2)
return outs
加载预训练的词向量
由于预训练词向量的词典及词语索引与我们使用的数据集并不相同,所以需要根据目前的词典及索引的顺序来加载预训练词向量。
cache_dir = "/home/kesci/input/GloVe6B5429"
glove_vocab = Vocab.GloVe(name='6B', dim=100, cache=cache_dir)
def load_pretrained_embedding(words, pretrained_vocab):
'''
@params:
words: 需要加载词向量的词语列表,以 itos (index to string) 的词典形式给出
pretrained_vocab: 预训练词向量
@return:
embed: 加载到的词向量
'''
embed = torch.zeros(len(words), pretrained_vocab.vectors[0].shape[0]) # 初始化为0
oov_count = 0 # out of vocabulary
for i, word in enumerate(words):
try:
idx = pretrained_vocab.stoi[word]
embed[i, :] = pretrained_vocab.vectors[idx]
except KeyError:
oov_count += 1
if oov_count > 0:
print("There are %d oov words." % oov_count)
return embed
net.embedding.weight.data.copy_(load_pretrained_embedding(vocab.itos, glove_vocab)) # 即取与训练好的词向量集中我们需要的那一部分
net.embedding.weight.requires_grad = False # 直接加载预训练好的, 所以不需要更新它
训练模型
训练时可以调用之前编写的 train 及 evaluate_accuracy 函数。
评价模型
def predict_sentiment(net, vocab, sentence):
device = list(net.parameters())[0].device # 读取模型所在的环境
sentence = torch.tensor([vocab.stoi[word] for word in sentence], device=device)
label = torch.argmax(net(sentence.view((1, -1))), dim=1)
return 'positive' if label.item() == 1 else 'negative'
predict_sentiment(net, vocab, ['this', 'movie', 'is', 'so', 'great'])
使用卷积神经网络
一维卷积层
def corr1d(X, K):
w = K.shape[0] # 卷积窗口宽度
Y = torch.zeros((X.shape[0] - w + 1))
for i in range(Y.shape[0]): # 滑动窗口
Y[i] = (X[i: i + w] * K).sum()
return Y
def corr1d_multi_in(X, K):
# 首先沿着X和K的通道维遍历并计算一维互相关结果。然后将所有结果堆叠起来沿第0维累加
return torch.stack([corr1d(x, k) for x, k in zip(X, K)]).sum(dim=0)
时序最大池化层
TextCNN 中使用的时序最大池化(max-over-time pooling)层实际上对应一维全局最大池化层:假设输入包含多个通道,各通道由不同时间步上的数值组成,各通道的输出即该通道所有时间步中最大的数值。
因此,时序最大池化层的输入在各个通道上的时间步数可以不同。
但一般来说,为提升计算性能,我们常常将不同长度的时序样本组成一个小批量,并通过在较短序列后附加特殊字符(如0)令批量中各时序样本长度相同。这些人为添加的特殊字符当然是无意义的。由于时序最大池化的主要目的是抓取时序中最重要的特征,它通常能使模型不受人为添加字符的影响。
class GlobalMaxPool1d(nn.Module):
def __init__(self):
super(GlobalMaxPool1d, self).__init__()
def forward(self, x):
'''
@params:
x: 输入,形状为 (batch_size, n_channels, seq_len) 的张量
@return: 时序最大池化后的结果,形状为 (batch_size, n_channels, 1) 的张量
'''
return F.max_pool1d(x, kernel_size=x.shape[2]) # kenerl_size=seq_len
TextCNN 模型
1、定义多个一维卷积核,并使用这些卷积核对输入分别做卷积计算。宽度不同的卷积核可能会捕捉到不同个数的相邻词的相关性。
2.对输出的所有通道分别做时序最大池化,再将这些通道的池化输出值连结为向量。
3.通过全连接层将连结后的向量变换为有关各类别的输出。这一步可以使用丢弃层应对过拟合。
与上一个模型相比,TextCNN 模型除了用一维卷积层替换循环神经网络外,还使用了两个嵌入层,一个的权重固定,另一个则参与训练。
class TextCNN(nn.Module):
def __init__(self, vocab, embed_size, kernel_sizes, num_channels):
super(TextCNN, self).__init__()
self.embedding = nn.Embedding(len(vocab), embed_size) # 参与训练的嵌入层
self.constant_embedding = nn.Embedding(len(vocab), embed_size) # 不参与训练的嵌入层
self.pool = GlobalMaxPool1d() # 时序最大池化层没有权重,所以可以共用一个实例
self.convs = nn.ModuleList() # 创建多个一维卷积层
for c, k in zip(num_channels, kernel_sizes):
self.convs.append(nn.Conv1d(in_channels = 2*embed_size, # 注意用的是一维卷积conv1d
out_channels = c,
kernel_size = k))
self.decoder = nn.Linear(sum(num_channels), 2)
self.dropout = nn.Dropout(0.5) # 丢弃层用于防止过拟合
def forward(self, inputs):
'''
@params:
inputs: 词语下标序列,形状为 (batch_size, seq_len) 的整数张量
@return:
outputs: 对文本情感的预测,形状为 (batch_size, 2) 的张量
'''
embeddings = torch.cat((
self.embedding(inputs),
self.constant_embedding(inputs)), dim=2) # (batch_size, seq_len, 2*embed_size)
# 根据一维卷积层要求的输入格式,需要将张量进行转置 2*embed_size相当于通道数
embeddings = embeddings.permute(0, 2, 1) # (batch_size, 2*embed_size, seq_len)
encoding = torch.cat([
self.pool(F.relu(conv(embeddings))).squeeze(-1) for conv in self.convs], dim=1)
# encoding = []
# for conv in self.convs:
# out = conv(embeddings) # (batch_size, out_channels, seq_len-kernel_size+1)
# out = self.pool(F.relu(out)) # (batch_size, out_channels, 1)
# encoding.append(out.squeeze(-1)) # (batch_size, out_channels)
# encoding = torch.cat(encoding) # (batch_size, out_channels_sum)
# 应用丢弃法后使用全连接层得到输出
outputs = self.decoder(self.dropout(encoding))
return outputs
embed_size, kernel_sizes, nums_channels = 100, [3, 4, 5], [100, 100, 100]
net = TextCNN(vocab, embed_size, kernel_sizes, nums_channels)
训练并评价模型
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=lr)
loss = nn.CrossEntropyLoss()
train(train_iter, test_iter, net, loss, optimizer, device, num_epochs)
数据增强
图像增广
1.图像增广(image augmentation)技术通过对训练图像做一系列随机改变,来产生相似但又不同的训练样本,从而扩大训练数据集的规模。
2.图像增广的另一种解释是,随机改变训练样本可以降低模型对某些属性的依赖,从而提高模型的泛化能力。
常用的图像增广方法
torchvision.transforms.RandomHorizontalFlip() # 左右翻转
torchvision.transforms.RandomVerticalFlip() # 上下翻转
torchvision.transforms.Resize(size=256),
torchvision.transforms.CenterCrop(size=224),
shape_aug = torchvision.transforms.RandomResizedCrop(200, scale=(0.1, 1), ratio=(0.5, 2) #长宽比裁剪与缩放
color_aug = torchvision.transforms.ColorJitter(brightness=0.5, contrast=0, saturation=0, hue=0) # 变化颜色
叠加多个图像增广方法
augs = torchvision.transforms.Compose([
torchvision.transforms.RandomHorizontalFlip(), color_aug, shape_aug])
使用图像增广训练模型
flip_aug = torchvision.transforms.Compose([
torchvision.transforms.RandomHorizontalFlip(),
torchvision.transforms.ToTensor()])
no_aug = torchvision.transforms.Compose([
torchvision.transforms.ToTensor()])
数据微调
获取数据集
import os
data_dir = '/home/kesci/input/hotdog4014'
os.listdir(os.path.join(data_dir, "hotdog"))
train_imgs = ImageFolder(os.path.join(data_dir, 'hotdog/train'))
test_imgs = ImageFolder(os.path.join(data_dir, 'hotdog/test'))
hotdogs = [train_imgs[i][0] for i in range(8)] # ImageFolder返回的结果
not_hotdogs = [train_imgs[-i - 1][0] for i in range(8)]
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
train_augs = transforms.Compose([
transforms.RandomResizedCrop(size=224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
normalize
])
定义和初始化模型
我们使用在ImageNet数据集上预训练的ResNet-18作为源模型。
这里指定pretrained=True来自动下载并加载预训练的模型参数,在第一次使用时需要联网下载模型参数。
pretrained_net = models.resnet18(pretrained=False)
pretrained_net.load_state_dict(torch.load('/home/kesci/input/resnet185352/resnet18-5c106cde.pth'))
pretrained_net.fc = nn.Linear(512, 2) # 将全连接层改为输出2类,判别是否是hotdog
output_params = list(map(id, pretrained_net.fc.parameters()))
feature_params = filter(lambda p: id(p) not in output_params, pretrained_net.parameters())
lr = 0.01
optimizer = optim.SGD([{'params': feature_params},
{'params': pretrained_net.fc.parameters(), 'lr': lr * 10}],
lr=lr, weight_decay=0.001) #输出参数的lr是特征参数的10倍
最后
以上就是魔幻纸飞机为你收集整理的8.文本分类、数据增强与数据微调文本分类的全部内容,希望文章能够帮你解决8.文本分类、数据增强与数据微调文本分类所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复