目标
- 1.了解
token
和tokenization
。 - 2.知道
N-gram
的概念和作用。 - 3.知道文本向量化表示的方法。
1.文本的tokenization
1.1 基本概念
tokenization
就是通常所说的分词,分出的每一个词语称为token
。
1.2 中英文分词的方法
- 把句子转化为词语
- 把句子转化为单个字
2. N-gram
表示方法
2.1 基本概念
N-gram模型是一种语言模型(Language Model,LM),语言模型是一个基于概率的判别模型,它的输入是一句话(单词的顺序序列),输出是这句话的概率,即这些单词的联合概率(joint probability)。
2.2 N-gram
用途
N-gram
用途有下:
- 1.词性标注
- 2.垃圾短信分类
- 3.分词器
- 4.机器翻译和语音识别
3. 向量化
把文本转化成向量有两种方法:
- 1.转化为one-hot编码
- 2.转化为word embedding
3.1 one-hot编码
one-hot编码使用稀疏的向量表示文本,占用空间多
3.2 word embedding
word embedding使用了浮点型的稠密矩阵来表示token
,根据词典的大小,我们的向量通常使用不同的维度,例如100,256,300等等。其中向量中的每一个值都是超参数,其初始值都是随机生成的,之后会在训练的过程中进行学习而获得。
我们会把所有的文本转化为向量,把句子用向量来表示。
但是在这中间,我们会先把token
使用数字来表示,再把数字使用向量来表示。
3.3 word embedding API
使用torch.nn.Embedding(num_embedding,embedding_dim)
参数介绍:
- 1.
num_embedding:
词典的大小 - 2.
embedding_dim:
embedding的维度
使用方法:
embedding=nn.Embedding(vocab_size,300)#实例化
input_embeded=embedding(input)#进行embedding的操作
形状的变化:从[batch_size,seq_len]
变成[batch_size,seq_len,embedding_dim]
。
4. 文本情感分类
4.1 目标
- 1.知道文本处理的基本方法
- 2.能够使用数据实现情感分类
4.2 思路分析
首先可以把上述问题定义为分类问题,情感评分为1-10。依据之前的学习内容,大致流程如下:
- 1.准备数据集
- 2.构建模型
- 3.模型训练
- 4.模型评估
4.3 准备数据集
在这一部分,我们需要实例化dataset,准备dataloader。其中需要注意:
- 1.如何完成基础的Dataset和Dataloader的构建;
- 2.每个batch中文本的长度不一致的问题如何解决;
- 3.每个batch中文本如何转化为数字序列。
4.4 文本序列化
实现文本序列化考虑的问题:
- 1.如何使用字典把词语和数字进行对应
- 2.不同的词语出现的次数不尽相同,是否需要对高频或者低频词语进行过滤,以及总的词语数量是否需要进行限制
- 3.得到词典之后,如何把数字序列转化为句子
- 4.不同句子长度不同,每个batch的句子如何构造出相同的长度(可以对短句子填充特殊字符)
- 5.对于在测试集中新出现的词语,可以用特殊字符代理
思路分析: - 1.对所有句子进行分词
- 2.词语存入字典,根据次数对词语进行过滤,并统计次数
- 3.实现文本转数字序列的方法
- 4.实现数字序列转文本方法
实现如下:
- 1.dataset.py:
from torch.utils.data import DataLoader,Dataset
from lib import ws,max_len
import torch
import os
import re
#利用正则方法分词,并且去除不必要的符号
def tokenlize(content):
re.sub("<.*?>"," ",content)
fileters=['\.','\t','\n','\x96','\x97']
content=re.sub("|".join(fileters)," ",content)
tokens=[i.strip() for i in content.spilt()]
return tokens
class ImbDataset(Dataset):
def __init__(self,train=True):
self.train_data_path=r"..."#训练集数据读取路径
self.test_data_path = r"..." #测试集数据读取路径
data_path=self.train_data_path if train else self.test_data_path
#把所有文件名放入列表,即pos和neg两个文件夹存入列表
temp_data_path=[os.path.join(data_path,"pos"),os.path.join(data_path,"neg")]
self.total_file_path=[]#所有的评论文件的路径path
for path in temp_data_path:
file_name_list=os.listdir(path)#得到pos或者neg文件夹内部所有文件名
file_path_list=[os.path.join(path,i) for i in file_name_list if i.endswith(".txt")]#过滤不是以.txt结尾的文件
self.total_file_path.extend(file_path_list)
def __getitem__(self, index):
file_path=self.total_file_path[index]
#获取label
label_str=file_path.split("\\")[-2]
label=0 if label_str =="neg" else 1
#获取内容
tokens=tokenlize(open(file_path).read())
return tokens,label
def __len__(self):
return len(self.total_file_path)
def collate_fn(batch):
"""
:param batch: ([tokens,label],[tokens,label],...)
:return:
"""
content,label=list(zip(*batch))
content=[ws.transform(i,max_len=max_len) for i in content]
content=torch.LongTensor(content)#转化成LongTensor,否则在model.py中的embedding()中无法执行,原因是embedding的对象必须是LongTensor
label=torch.LongTensor(label)
return content,label
def get_dataloader(train=True):
imdb_dataset=ImbDataset()
data_loader=DataLoader(imdb_dataset,batch_size=2,shuffle=True,collate_fn=collate_fn)
return data_loader
- 2.word2sequence.py:
import numpy as np
import os
class word2sequence():
UNK_TAG="UNK"
PAD_TAG="PAD"
UNK=0
PAD=1
def __init__(self):
#字典,初始情况下存入两个特殊字符
self.__dict__={
self.UNK_TAG:self.UNK,
self.PAD_TAG:self.PAD
}
self.fited=False
self.count={}#统计词频
def fit(self,sentence):
"""把单个句子保存到dict中
:param sentence: [word1,word2,word3...]
:return:
"""
for word in sentence:
self.count[word]=self.count.get(word,0)+1#统计词频
def bulid_vocab(self,min=0,max=None,max_features=None):
"""
生成词典
:param min:最小出现的次数
:param max:最大的次数
:param max_features:保留的词语数
:return:
"""
# 删除count中词频小于min的单词
if min is not None:
self.count={word:value for word,value in self.count if value>=min}
# 删除count中词频大于max的单词
if max is not None:
self.count={word:value for word,value in self.count if value<max}
#限制保留的词语数
if max_features is not None:
temp=sorted(self.count.items(),key=lambda x:x[-1],reverse=True)[:max_features]#排序,取前max_features个词频的单词
self.count=dict(temp)#sorted之后,结果为列表,需要重新转换成字典
for word in self.count:
self.dict[word]=len(self.dict)#给每个单词进行赋值,由于初始情况已经有两个特殊字符,所以新进入的第一个单词的值为2,此后不断叠加
#得到一个翻转的dict字典(利用键和值的重新匹配)
self.inverse_dict=dict(zip(self.dict.values(),self.dict.keys()))
def transform(self,sentence,max_len=None):
"""
把句子转化成序列
:param sentence:[wword1,word2,word3...]
:param max_len: int 对句子进行填充或者裁剪
:return:
"""
if max_len is not None:
if max_len>len(sentence):#填充
sentence=sentence+[self.PAD_TAG]*(max_len-len(sentence))
if max_len<len(sentence):#裁剪
sentence=sentence[:max_len]
return [self.dict.get(word,self.UNK) for word in sentence]
def inverse_transform(self,indices):
"""
把序列转化成句子
:param indices: [1,2,3...]
:return:
"""
return [self.dinverse_dict.get(idx) for idx in indices
from main import word2sequence
import pickle
from dataset import tokenlize #从一开始定义的数据集中导入tokenlize
from tqdm import tqdm#显示可迭代对象的加载进度
if __name__=='__main__':
ws=word2sequence()
path=r"..."#写入路径
temp_data_path=[os.path.join(path,"pos"),os.path.join(path,"neg")]
for data_path in temp_data_path:
file_paths=[os,path.join(data_path,file_name) for file_name in os.listdir(data_path)]
for file_path in tqdm(file_paths):
sentence = tokenlize(open(file_path).read())
ws.fit(sentence)
ws.bulid_vocab(min=10)
pickle.dump(ws,open("./model/ws.pkl","wb"))#保存文件
- 3.lib.py:
import pickle
ws=pickle.load(open("./model/ws/pkl","rb"))#保存数据
max_len=20
4.5 构建模型
这里我们只联系使用word embedding,所以模型只有一层,即:
- 1.数据经过word embedding
- 2.数据通过全连接层返回结果,计算
log_softmax
为了方便理解,我们只使用了一层模型,所以效果可能并不是特别理想。
model.py代码如下:
import torch
import torch.nn as nn
from lib import ws,max_len
import torch.nn.functional as F
class MyModel(nn.Module):
def __init__(self):
super(MyModel,self).__init__()
self.embedding=nn.Embedding(len(ws),100)#两个参数,前一个是训练的词语的数量,后一个是每一个词语的维度
self.fc=nn.Linear(max_len*100,2)#Linear()函数对象必须是二维,所以在forward里面必须进行view()操作
def forward(self,input):
"""
:param input: [batch_size,max_len]
:return:
"""
x=self.embedding(input)#进行embedding操作,形状成为:[batch_size,max_len,100]
x=x.view([-1,max_len*100])
out=self.fc(x)
return F.log_softmax(out,dim=-1)