【16】简单文本分类【词嵌入、文本向量化、文本分类模型】
目录
118-文本表示与词嵌入
词嵌入
119-文本向量化流程与分词
使用词嵌入
文本的词嵌入表示处理流程
121-文本分类模型&embedingbag
文本分类预处理的步骤
词嵌入表示
分类模型示例
127-文本分类模型实战
118-文本表示与词嵌入
文本的理解
深度学习序列模型(如RNN及其变体)可以解决类似以下领域中的问题:自然语言理解;文献分类、情感分类、 问答系统等。
文本向量化的方法
深度学习模型并不能理解文本,因此需要将文本转换为数值的表示形式。
将文本转换为数值表示形式的过程称为向量化过程,可以用不同的方式来完成
词嵌入
词嵌入是单词的一种数值化表示方式,一般情况下会将一个单词映射到一个高维的向量中(词向量)来代表这个单词 。【单词之间的距离,反应单词之间的相似性】
机器学习表示为[1, 2, 3]
深度学习'表示为[1, 3, 3]
日月光华表示为[9, 9, 6]
对于词向量,手我们可以使用余弦相似度在计算机中来判断单词之间的距离
词嵌入实际上是一种将各个单词在预定的向量空间中表示为实值向量的一类技术。
每个单词被映射成一个向量(初始随机化),并且这个向量可以通过神经网络的方式来学习更新。因此这项技术基本集中应用与深度学习领域
词嵌入用密集的分布式向量来表示每个单词。这样做的好处在于与one-hot这样的编码对比,使用词嵌入表示的单词向量往往只有几十或者几百个维度。极大的减少了计算和储存量。
词向量表示方式依赖于单词的使用习惯,这就使得具有相似使用方式的单词具有相似的表示形式。首都’和'北京'是向量空间中很相近的2个词。光华老师'和'北京'是向量空间离的很远。
词嵌入是从文本语料中学习到的一种将单词表示为预定义大小的实值向量形式。学习过程一般与某个神经网络的模型任务一同进行,比如文档分类。
119-文本向量化流程与分词
使用词嵌入
在自然语言处理项目中使用单词嵌入时,你可以选择下面两种方式:
1.自己学一个词嵌入
2.使用别人训练好的词嵌入
文本的词嵌入表示处理流程
文本数据在表示为独热编码和词嵌入之前,首先需要表示成token,每个较小的文本单元称为token,将文本分解成token的过程称为分词(tokenization)。在Python中有很多强大的库可以用来进行分词。【英文单词数值化:1-字母数值化、2-单词数值化】
一旦将文本数据转换为token序列,那么就需要将每个token映射到向量。【1-转为token、2-映射向量】
one-hot(独热)绸编码和词嵌入是将token映射到向量最流行的两种方法。
121-文本分类模型&embedingbag
文本分类预处理的步骤
-分词【分出每个单词】
-创建词表词【每个单词处理成一个索引,每个单词就是一种分类】
-嵌入表示【词映射到张量,张量之间可以计算关系,使用torch内置的Embeding,直接输入到模型中】
-输入模型【lstm,一维卷积】
词嵌入表示
简单的PythonPyTorch框架中为我们内置了EmbeddingLayer层,调用代码如下:torch.nn.Embedding().
PyTorch还为我们提供了torch.nn.EmbeddingBag()这个聚合方法,EmbeddingBag会对一个序列中文字的embeding输出做聚合,聚合的方法有求和、求均值、求最大值等。【I love you:求和得到数值100,100可以表示这段文本,可以进行文本分类】
当我们输入一个文本序列词表到EmbedingBag层时,这个层会将序列中每一个单词作词嵌入表示,并将结果根据指定的聚和计算方法计算后作为最后的输出。【将单词的索引输入到embeding层后,会进行词嵌入表示,然后进行聚合。这就是Bag的作用】
简单文本分类模型中可使用EmbedingBag层加Linear层快速创建文本分类模型。这样的模型创建起来非常便捷,计算效率也很高。【精度不高,忽略了】
我们将使用EmbeddingBag层对文本做词嵌入聚合这个层会对每一条评论中的文本单词做embeding词嵌入,并使用默认模式“mean”计算embeding的平均值,最后输出一个聚合结果。在这一层基础上添加分类器即可快速创建一个文本分类模型。
分类模型示例
上图中wordlook-uptable表示词表,也就是上面已经创建好的vocab,它会将每一个单词对应到一个索引,这一步是在预处理阶段完成的。我们已经知道,深度学习训练是小批量数据同时训练的,本例数据集中每一条评论的长度并不统一,但是我们并不需要填充并创建批次。
【深度学习,小批量训练】
由于使用了EmbeddingBag层,我们可以将一个批次中全部文本创建为-一个序列,然后告诉EmbeddingBag层每-条评论的偏移值(也就是每一条评论相对开始所在位置)即可。这样做可显著提高文本处理的效率。我们只需将每一个批次的文本整合为一条长的文本序列,并记录其中每一条评论的偏移值。【文本分类模型,不需要创建同样长度的批次,将一个批次的文本,创建到一个序列中。】
torch.utils.data.DataLoader类中有一个collate_fn参数,该参数接收一个函数,此函数可对每个批次的数据作处理,因此我们可以定义一个批次处理函数并将它交给collate_fn。这个批次处理函数中需要做的就是对文本的预处理、记录偏移量、转换标签.
127-文本分类模型实战
【参考文件】:16-1文本表示&词嵌入.ipynb
【参考文档】:16-2简单文本分类.ipynb
【导出代码】
# %%
import torch
import torchtext
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
from torch.utils import data
# %% [markdown]
# # 122-Torch text内置数据集&分词工具
# %%
'Life is not easy for any of us. We must work, and above all we must belive in ourselves. We must belive that each one of us is able to do some thing well. And that we must work until we succeed.'
# %% [markdown]
# 安装torchtext:pip install torchtext
# %%
torch.__version__
# %%
torchtext.__version__
# %% [markdown]
# ## 加载torchtext内置数据集
# %%
# torchtext.datasets.IMDB #电影评价数据集
# %%
train_data, test_data = torchtext.datasets.IMDB()
# %% [markdown]
# iter:生成得迭代器
# %%
train_iter = iter(train_data)
# %%
next(train_iter)
# %% [markdown]
# 1:负面评价
# %%
train_data, test_data = torchtext.datasets.IMDB() #重新加载
# %%
train_data, test_data = list(test_data), list(test_data)
# %%
train_data[:2]
# %%
all_classes = [label for (label, text) in train_data] #取出所以类别
# %%
all_classes = set(all_classes) #取出唯一值
# %%
all_classes
# %%
num_class = len(all_classes)
# %% [markdown]
# ## 内置分词工具
# %%
# 导入分词工具
from torchtext.data.utils import get_tokenizer
# %%
# 分词工具初始化
tokenizer = get_tokenizer(tokenizer='basic_english') #分词工具
# %% [markdown]
# tokenizer:大->小
# %%
tokenizer('Life is not easy for any of us. We must work, and above all we must belive in ourselves. We must belive that each one of us is able to do some thing well. And that we must work until we succeed.')
# %% [markdown]
# # 123-torchtext创建词表
# %%
#导入词表工具
from torchtext.vocab import build_vocab_from_iterator
# %%
# 定义生成器方法
def yield_tokens(data):
for (_, text) in data:
yield tokenizer(text)
# %%
vocab = build_vocab_from_iterator(iterator=yield_tokens(train_data), specials=['<pad>','<unk>'], min_freq=3)
# %% [markdown]
# 【生成器、填充|不认识的词、min_freq=3:大于三才是常见单词才会创建词表,小于的为unk】
# %%
vocab['we']
# %% [markdown]
# 设置默认索引
# %%
vocab.set_default_index(vocab['<unk>'])
# %%
vocab['qwer']
# %% [markdown]
# # 124-torchtext处理文本
# %% [markdown]
# 分词方法
# %%
tokenizer(' the file is very good')
# %% [markdown]
# 索引方法
# %%
vocab(['the', 'ffile', 'is', 'very', 'good'])
# %%
vocab(tokenizer(' the file is very good'))
# %% [markdown]
# ### 构造一个方法
# %%
text_pipeline = lambda x: vocab(tokenizer(x))
# %%
text_pipeline('the file is very good')
# %% [markdown]
# 标签文本数值转换
# %%
lable_pipeline = lambda x: int(x == '1') #标签1:pos,标签2:neg
# %%
lable_pipeline('2')
# %% [markdown]
# # 125-Dataloader批次处理函数
# %%
from torch.utils.data import DataLoader
# %%
# DataLoader() #collate_fn: 批次处理函数
# %% [markdown]
# 将dataloder创建的批次交给clloate_fn的函数进行处理
# %%
text_dl = DataLoader(train_data, batch_size=64, shuffle=True)
# %% [markdown]
# offset:偏移值
# tensor(1,2,3,4, 5,6,7,8, 9,0)
# 第一条评论偏移量为0
# 第二条偏移量:4
# 第三条偏移量:8
# offest:偏移量为每条评论的起始位置
# offset = [0, 4, 8]
#
# offset_list = [0, 4, 4, 2] --切掉最后一条--> [0, 4, 4] --做累加cumsum---> [0, 4, 8]
#
#
# %%
def collate_batch(text_label_batch):
label_list, text_list, offset_list = [], [], [0]
for _label, _text in text_label_batch:
label_list.append(lable_pipeline(_label))
precess_text = torch.tensor(text_pipeline(_text), dtype=torch.int64) #【张量】一条评论的文本映射
text_list.append(precess_text)
offset_list.append(precess_text.size(0))
label_list = torch.tensor(label_list) #转换成张量
text_list = torch.cat(text_list) #每个小张量转换成一条张量
offsets = torch.tensor(offset_list[:-1]).cumsum(dim=0)
return label_list.to(device=device), text_list.to(device=device), offsets.to(device=device)
# %% [markdown]
# EmbedingBag:1-所有的文本,合并成一条、2-定义一个偏移
# %% [markdown]
# # 126-文本分类模型初始化
# %%
train_dl = DataLoader(train_data, batch_size=64, shuffle=True, collate_fn=collate_batch)
# %%
test_dl = DataLoader(test_data, batch_size=64, collate_fn=collate_batch)
# %% [markdown]
# ## 创建模型
# %% [markdown]
# 
# %%
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
# %%
vocab_size = len(vocab)
# %%
vocab_size
# %% [markdown]
# embeding_dim:文本映射后的张量长度
# %%
class TextClassificationModel(nn.Module):
def __init__(self, vocab_size, embeding_dim):
super(TextClassificationModel, self).__init__()
self.embedingbag = nn.EmbeddingBag(num_embeddings=vocab_size, embedding_dim=embeding_dim) #初始化EmbeddingBag
self.fc = nn.Linear(embeding_dim, num_class)
def forward(self, text, offset):
embed = self.embedingbag(text, offset)
out = self.fc(embed)
return out
# %% [markdown]
# # 127-模型训练
# %%
embeding_dim = 100
# %%
model = TextClassificationModel(vocab_size=vocab_size, embeding_dim=embeding_dim).to(device=device)
# %%
model
# %% [markdown]
# 损失函数
# %%
loss_fn= nn.CrossEntropyLoss()
# %%
from torch.optim import lr_scheduler
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)
exp_lr_scheduler = lr_scheduler.StepLR(optimizer=optimizer, step_size=20, gamma=0.1)
# %% [markdown]
# 训练函数
# %%
def train(dataloader):
total_acc, total_count, total_loss = 0, 0, 0
model.train()
for label, text, offsets in dataloader:
label, text, offsets = label.to(device), text.to(device), offsets.to(device)
predited_label = model(text, offsets) #【对应forward方法中的定义:forward(self, text, offset)】
loss = loss_fn(predited_label, label)
optimizer.zero_grad()
loss.backward()
optimizer.step()
with torch.no_grad():
total_acc += (predited_label.argmax(1) == label).sum().item()
total_count += label.size(0)
total_loss += loss.item()*label.size(0)
return total_loss/total_count, total_acc/total_count
# %%
def test(dataloader):
total_acc, total_count, total_loss = 0, 0, 0
model.eval()
with torch.no_grad():
for idx, (label, text, offsets) in enumerate(dataloader):
# label, text, offsets = label.to(device), text.to(device), offsets.to(device)
predited_label = model(text, offsets)
loss = loss_fn(predited_label, label)
total_acc += (predited_label.argmax(1) == label).sum().item()
total_count += label.size(0)
total_loss += loss.item()*label.size(0)
return total_loss/total_count, total_acc/total_count
# %%
def fit(epochs, train_dl, test_dl):
train_loss =[]
train_acc = []
test_loss = []
test_acc = []
for epoch in range(epochs):
epoch_loss, epoch_acc = train(train_dl)
epoch_test_loss, epoch_test_acc = test(test_dl)
train_loss.append(epoch_loss)
train_acc.append(epoch_acc)
test_loss.append(epoch_test_loss)
test_acc.append(epoch_test_acc)
exp_lr_scheduler.step()
template = ("epoch: {:2d}, train_loss: {:.5f}, train_acc: {:.1f}%," "test_loss: {:.5f}, test_acc: {:.1f}%")
print(template.format(epoch, epoch_loss, epoch_acc*100, epoch_test_loss, 100*epoch_test_acc))
print("Done!")
return train_loss, train_acc, test_loss, test_acc
# %%
EPOCHS = 30
train_loss, train_acc, test_loss, test_acc = fit(epochs=EPOCHS, train_dl=train_dl, test_dl=test_dl)
# %%
具体问题讨论,主页联系。