从jieba分词到BERT-wwm——中文自然语言处理(NLP)基础分享系列(7)

从『词袋』到『词序列』

我们之前对于文档的数字化表示,采用的是向量空间模型(例如TF-IDF),又被形象地称为『词袋』模型(Bag-of-words model)。就像是把文档里的词汇,放入到以词典作为标签的袋子里。
我们可以看到,基于词袋模型的文档表示方法,虽然考虑了词的重要程度,但它只是根据词的统计特性表示一个文档,而没有考虑到词在文中的次序。
比方说有这样两句话:
•“熊二/的/哥哥/是/熊大。”
•“熊二/是/熊大/的/哥哥。”
这两句话的TF-IDF数据表示形式是一样的,但它们的语义是截然相反的。

究其原因,是因为词袋模型丢失了文档中的上下文信息。

这样就有了一个新的思路:将文档表示成词编码的一个序列,这样词在文档上下文关系信息就能够保留下来。

建立词典并将文本转成数字序列

接下来我们尝试将文档转换成一个数字序列,方便电脑处理。
首先我们要根据语料库中出现的词建立一个词典,建立数字索引(Index),分别对应到特定的词汇。实际上,这个建立词典的步骤在之前的TF-IDF模型训练函数TfidfVectorizer中已经隐含地做过。但现在我们不再使用TF-IDF,因此需要把这一过程单独拿出来显性实现一下。

在李孟博士的原博客里,后续演示使用的是TensorFlow.Keras相关包,我们改为全部使用Pytorch演示相关功能的实现。

首先,把Jieba分词结果和语料库调入内存。

import pandas as pd 
import pickle 
 
pkl_file_rb = open(r'./save_file', 'rb') 
train =pickle.load(pkl_file_rb) 
 
corpus = pd.concat([train . title1_tokenized, train . title2_tokenized]) 
corpus = [c for c in corpus] 
type(train[0:1])
pandas.core.frame.DataFrame
corpus[16].strip().split() 
['男人', '在', '机舱', '口', '跪下', '原来', '一切', '都', '只', '因为', '爱']

定义一个迭代器对语料库中的分词数据进行预处理:

def yield_tokens(corpus): 
    for i in range(len(corpus)): 
        yield corpus[i].strip().split() 

使用 torchtextbuild_vocab_from_iterator 函数建立词典。

from torchtext.vocab import build_vocab_from_iterator 
 
vocab = build_vocab_from_iterator(yield_tokens(corpus), min_freq=2, specials=[""]) 
vocab.set_default_index(vocab['']) #在词典里设置一个默认词,用来对于OOV的生词做默认编码 
vocab['熊二'], vocab['爱'],vocab[''] #'熊二'在语料库里没有出现过 
(0, 326, 0)
vocab.__len__()
61841

经过这一步骤,我们就可以将一个新闻标题文档表示为一个词典序编码的数字序列,如下所示:

vocab(['男人', '在', '机舱', '口', '跪下', '原来', '一切', '都', '只', '因为', '爱']) 
[306, 16, 5905, 1724, 5474, 181, 1843, 14, 99, 487, 326]

用PyTorch的DataLoader 预处理数据

在后续建立深度学习模型之前,我们将采用PyTorch建议使用的 torch.utils.data.DataLoader 方法对数据进行预处理。

首先我们定义2个匿名函数,用来对训练数据中的label和titleX_tokenized字段做个简单的处理。

label_to_index = { 'unrelated' : 0 , 'agreed' : 1 , 'disagreed' : 2 } 
label_pipeline = lambda x : label_to_index [ x ] 
text_pipeline = lambda x: vocab(x.strip().split()) 
import torch 
import numpy as np 
from torch.utils.data import Dataset 
from torch.utils.data import DataLoader,TensorDataset 

MAX_LEN = 30 #定义一个最大长度,用于对过长的标题进行截断 

下面我们定义一个myDataSet 类,用于对之前的DataFrame train 进行预处理。
这里稍微有点复杂,对于程序比较生疏的我来说有些许困难,花了半天时间学习和试验才调通。

主要的逻辑是:
从train中提取title1_tokenized和title2_tokenized字段,转为词典编码序列,并根据最大长度MAX_LEN进行截断,作为将来模型输入的tensor;
提取label字段转换为0、1、2作为预测目标,作为将来模型输出tensor。

# 自定义一个myDataSet类 对输入数据进行预处理 
class myDataset(Dataset): 
    def __init__(self, train_data, max_len=20, transform=None): 
        super().__init__() 
        x1_train = train_data.title1_tokenized.apply(text_pipeline) 
        x2_train = train_data.title2_tokenized.apply(text_pipeline) 
        y_train = train.label.apply(label_pipeline) 
        
        # 以下根据max_len参数,对长短不一的数字化标题序列进行截断和补齐 
        tmp = [] 
        for x in (x1_train.values,x2_train.values): 
            tmp_x = [] 
            for i in range(len(x)): 
                temp_x1 = [] 
                for j in range(max_len): 
                    temp_x1.append(x[i][j] if j < len(x[i]) else 0) 
                tmp_x.append(temp_x1) 
            tmp.append(tmp_x) 
            
        self.x = torch.from_numpy(np.transpose(np.asarray(tmp),[1,0,2])) 
        self.y = torch.from_numpy(np.asarray(y_train.values)) 
        self.transform = transform 

    def __len__(self): 
        return len(self.y)  # 数据集长度 
    
    def __getitem__(self, index): 
        x = self.x[index]  # tensor类型 
        y = self.y[index] 
        if self.transform is not None: 
            x = self.transform(x)  # 对输入进行某些变换 
        
        return x, y 

实例化一个MyDataset 类的对象train_data,作为参数传入DataLoader 进行数据预处理。

device = torch.device("cuda" if torch.cuda.is_available() else "cpu") 
train_data = myDataset(train,max_len=MAX_LEN) 
dataloader = DataLoader(train_data, batch_size=8, shuffle=False) 

看一下处理后的数据样例和维度形式是否如我们所愿:

for x,y in dataloader: 
    print(x,y) 
    print("*"*80) 
    print(x.shape,y.shape) 
    break 
tensor([[[  218,  1269,    32,  1178,  5971,    24,   488,  2875,   116,  5568,
              4,  1847,     2,    13,     0,     0,     0,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0],
         [  150,     8, 12895,  6168,  6345,   529,    44,    64,   740,    12,
            488,   286, 13213,     0,     0,     0,     0,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0]],

        [[    4,    10,    47,   677,  2561,     4,   165,    34,    17,    47,
           5153,    62,    15,   677,  4509,  3208,    23,   284,  1185,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0],
         [  677,  3208,  1141,   284,  1185,   677, 23975,     8,   784,  4515,
             16, 12858,     0,     0,     0,     0,     0,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0]],

        [[    4,    10,    47,   677,  2561,     4,   165,    34,    17,    47,
           5153,    62,    15,   677,  4509,  3208,    23,   284,  1185,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0],
         [ 3208,  1141,   284,  1185,   677,   390,    22,   953,  9816,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0]],

        [[    4,    10,    47,   677,  2561,     4,   165,    34,    17,    47,
           5153,    62,    15,   677,  4509,  3208,    23,   284,  1185,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0],
         [ 3299,   677,  3208,  1141,   284,  1185,   677, 23975,     8,    22,
            953, 45212,   120,     0,     0,     0,     0,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0]],

        [[   31,   319,  3373,  3057,     1,    94,    97,  3373,  3057,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0],
         [    7,     2,   208,    15, 14291,   174,    50,  1627,   319,   122,
           3373,  3057,     0,     0,     0,     0,     0,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0]],

        [[    4,    10,    47,   677,  2561,     4,   165,    34,    17,    47,
           5153,    62,    15,   677,  4509,  3208,    23,   284,  1185,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0],
         [  677,  3208,  1141,   284,  1185, 23975,     8,   381,   284,   242,
           4515,  1670, 12858,     0,     0,     0,     0,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0]],

        [[    7,  2220,     1,  2071,     7, 18165,  2188,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0],
         [ 2220,    78,    20,   101,   125,     7,    46,  1552,   641,     7,
           3055,  4705,     0,     0,     0,     0,     0,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0]],

        [[    4,    10,    47,   677,  2561,     4,   165,    34,    17,    47,
           5153,    62,    15,   677,  4509,  3208,    23,   284,  1185,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0],
         [  677,  3208,  1141,   284,  1185,     8,   381,   284,   242,  4515,
           1670, 12858,     0,     0,     0,     0,     0,     0,     0,     0,
              0,     0,     0,     0,     0,     0,     0,     0,     0,     0]]],
       dtype=torch.int32) tensor([0, 0, 0, 0, 1, 0, 0, 0])
********************************************************************************
torch.Size([8, 2, 30]) torch.Size([8])

好了,就到这儿吧。

本系列共12期,将分6天发布。相关代码已全部分享在我的github项目(moronism189/chinese-nlp-stepbystep)下,急的可以先去那里下载。

你可能感兴趣的:(自然语言处理,深度学习,pytorch)