第57章 词卷积

使用字符卷积对文本分类是可以实现的,但是相对于词来说,字符包含的信息并没有词多,即使卷积神经网络能够较好地对数据信息进行学习,但是由于包含的内容关系不多而导致其最终效果差强人意。

在字符卷积的基础上,研究人员尝试使用以词为基础数据对文本进行处理。比如,下图为使用卷积神经网络CNN做词卷积模型。


图1 使用卷积神经网络CNN做词卷积模型

图2 使用卷积神经网络CNN做词卷积模型

在实际读写中,一般用短文本表达较为集中的思想,文本长度有限、结构紧凑、能够独立表达意思,因此可以使用基于词卷积的神经网络对数据进行处理。

单词的文本处理

使用卷积神经网络对单词进行处理的最基本要求是将文本转换成计算机可以识别的数据。上一章中,使用了卷积神经网络对字符的独热编码矩阵进行了处理,能不能将文本中的单词进行独热编码后再进行处理处理?

图3 单词进行独热编码

图4 单词进行独热编码

使用独热编码表示单词从理论上说是可行的,但是实际上并不可行。对于基于字符的独热编码方案来说,所有的字符会在一个相对合适的字符库(比如26个字母或者一些常用的字符)中选取,那么总量不会太多(一般不出超过128个),因此组成的矩阵也不会太大。

对于单词来说,常用的英文单词或者中文词语一般在5000个左右,因此建立一个稀疏、庞大的独热编码矩阵是不可实际的想法。

目前来说,一般较好的解决方法就是使用word2vec的词嵌入方法,这样可以通过学习将字库中的词转换成维度一定的向量,作为卷积神经网络的计算依据。这里继续使用前几章准备好的AG News新闻数据集,并以标题作为处理的目标。单词的词向量建立步骤如下,

分词处理

首先对数据进行分词处理,与采用独热编码的数据读取类似,首先对文本进行清洗,不使用停用词以及标准化文本。需要注意的是,对于word2vec训练模型来说,需要输入若干词列表,因此要将读取的文本进行分词,转换成数组的形式。 代码如下,


import csv
import re
import jax
import numpy

def setup():
    
    with open("../../Shares/ag_news_csv/train.csv", "r") as handler:
        
        train_labels = []
        train_texts = []
        
        trains = csv.reader(handler)
        trains = list(trains)
        
        for i in range(len(trains)):
            
            line = trains[I]
            
            train_labels.append(jax.numpy.int32(line[0]))
            train_texts.append(purify(line[1], split = True))
            
    return train_labels, train_texts

def purify(string: str, pattern: str = r"[^a-z]", replacement: str = " ", split = False):
    
    string = string.lower()
    
    string = re.sub(pattern = pattern, repl = replacement, string = string)
    # Replace the consucutive spaces with single space
    string = re.sub(pattern = r" +",  repl = replacement, string = string)
    # string = re.sub(pattern = " ", repl = "", string = string)
    
    # Trim the string
    string = string.strip()
    string = string + " eos"
    
    if split:
        
        string = string.split(" ")
    
    return string

模型训练与载入

下一步是对分词模型的训练和载入,基于已有的分词数据,对不同维度的矩阵分别处理。 需要注意的书,对于word2vec词向量来说,简单地将待补全矩阵全部用0矩阵补全是不合适的,一个好的办法就是将0矩阵修改为一个非常小的常数矩阵,代码如下,


def one_hot_numbers(numbers):
    
    array = numpy.array(numbers)
    maximum = numpy.max(array) + 1
    
    eyes = numpy.eye(maximum)[array]
    
    return eyes
    
def make_words_matrix(maximum_length = 12):
    
    train_labels, train_texts = setup()
    
    import gensim.models
        
    model = gensim.models.word2vec.Word2Vec(train_titles, vector_size = 64, min_count = 0, window = 5)
    
    matrixes = []
    
    for line in train_texts:
        
        length = len(line)
        
        if length > maximum_length:
            
            line = line[: maximum_length]
            matrix = model.wv[line]
            
            matrixes.append(matrix)
            
        else:
            
            matrix = model.wv[line]
            padding_length = maximum_length - length
            padding_matrix = numpy.zeros([padding_length, 64]) + 1e-10
            
            matrix = jax.numpy.concatenate([matrix, padding_matrix], axis = 0)
            
            matrixes.append(matrix)
            
    train_texts = numpy.expand_dims(matrixes, axis = 3)
    train_labels = one_hot_numbers(train_labels)
    
    return train_texts, train_labels

def main():
    
    train_texts, train_labels = make_words_matrix()
    
    print(f"train_texts.shape = {train_texts.shape}, train_labels.shape = {train_labels.shape}")
        
if __name__ == "__main__":
    
    main()

运行结果打印输出如下,


train_titles.shape = (120000, 12, 64, 1), train_labels.shape = (120000, 5)

上面代码中,numpy.expand_dims()函数的作用是对生成的数据列表中的数据进行扩展,将原始的三位矩阵扩展成四维,在不改变具体数值大小的前提下扩展了矩阵的维度,这是为下一步使用二维卷积神经网络对文本进行分类任务做数据准备。

卷积神经网络文本分类模型实现

类似于上一章字符卷积的模型设计,使用二维卷积进行文本分类任务。模型的算法很简单,根据输入的已转换成词嵌入形式的词矩阵,通过不同的卷积提取不同的长度进行二维卷积计算,将最终的计算值进行链接,之后经过池化层获取不同矩阵均值,之后通过一个全连接层对其进行分类。

完整代码如下所示,


import csv
import re
import jax
import numpy
import jax.example_libraries.stax
import jax.example_libraries.optimizers

def setup():
    
    with open("../../Shares/ag_news_csv/train.csv", "r") as handler:
        
        train_labels = []
        train_texts = []
        
        trains = csv.reader(handler)
        trains = list(trains)
        
        for i in range(len(trains)):
            
            line = trains[I]
            
            train_labels.append(jax.numpy.int32(line[0]))
            train_texts.append(purify(line[1], split = True))
            
    return train_labels, train_texts

def purify(string: str, pattern: str = r"[^a-z]", replacement: str = " ", split = False):
    
    string = string.lower()
    
    string = re.sub(pattern = pattern, repl = replacement, string = string)
    # Replace the consucutive spaces with single space
    string = re.sub(pattern = r" +",  repl = replacement, string = string)
    # string = re.sub(pattern = " ", repl = "", string = string)
    
    # Trim the string
    string = string.strip()
    string = string + " eos"
    
    if split:
        
        string = string.split(" ")
    
    return string

def one_hot_numbers(numbers):
    
    array = numpy.array(numbers)
    maximum = numpy.max(array) + 1
    
    eyes = numpy.eye(maximum)[array]
    
    return eyes

def make_words_matrix(maximum_length = 12):
    
    train_labels, train_texts = setup()
    
    import gensim.models
        
    model = gensim.models.word2vec.Word2Vec(train_titles, vector_size = 64, min_count = 0, window = 5)
    
    matrixes = []
    
    for line in train_texts:
        
        length = len(line)
        
        if length > maximum_length:
            
            line = line[: maximum_length]
            matrix = model.wv[line]
            
            matrixes.append(matrix)
            
        else:
            
            matrix = model.wv[line]
            padding_length = maximum_length - length
            padding_matrix = numpy.zeros([padding_length, 64]) + 1e-10
            
            matrix = jax.numpy.concatenate([matrix, padding_matrix], axis = 0)
            
            matrixes.append(matrix)
            
    train_texts = numpy.expand_dims(matrixes, axis = 3)
    train_labels = one_hot_numbers(train_labels)
    
    return train_texts, train_labels

def cnn(number_classes):
    
    return jax.example_libraries.stax.serial(
        
        jax.example_libraries.stax.Conv(1, (3, 3)),
        jax.example_libraries.stax.Relu,
        
        jax.example_libraries.stax.Conv(1, (5, 5)),
        jax.example_libraries.stax.Relu,
        
        jax.example_libraries.stax.Flatten,
        
        jax.example_libraries.stax.Dense(32),
        jax.example_libraries.stax.Relu,
        
        jax.example_libraries.stax.Dense(number_classes),
        
        jax.example_libraries.stax.LogSoftmax
        
        )

结论

本章基于前几章介绍过的内容,在学习了字符卷积的前提下,介绍了词卷积,并通过一个例子讲解了使用卷积神经网络进行词卷积的基本操作。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容