听它爹说他孩儿:Keras 学习笔记 6.1

本书第六章:文本和序列的深度学习

本章内容包括:
  • 将文本数据转化成可用的表达方式
  • 运用循环神经网络
  • 运用一维卷积神经网络

本章探索处理文本、时间等序列数据的深度学习模型。文本,可以理解为单词或字符的序列。深度学习有两个处理序列的基础算法,一是循环神经网络,二是一维卷积神经网络。一维卷积神经网络是二维卷积神经网络的变种。我们将在本章讨论这两种方法。

 两个算法的应用如下:

  • 文档分类和时间序列数据分类,例如,认定文章主题或书的作者
  • 时间序列数据的比较,例如,两个文档或两只股票的相关度
  • 序列的转变,例如,英语句子译成法语
  • 情感分析,例如,把电影评论或推特上的文章分类成积极或消极情绪的
  • 序列数据的广播,例如,预报某地未来天气情况

本章的范例集中在两个方面:因特网电影数据库( IMDB )的数据集的情感分析和气温预报。但是,其中使用的技术适用于上述和其他各种类似问题。

应用于文本数据

文本是序列数据最广泛的形式之一。文本可视为字符序列或单词序列,通常是作为单词序列处理。以下各节引进的深度学习序列处理模块,可以从文本产生自然语言理解的基本方式,充分用于文档分类、情感分析、作者识别、某些问题的提问-解答(QA)等方面。当然,要记住本章每个模型并非像人那样真正理解文本含意。这些模型只是可以映射书面语言的统计结构,有效解决简单的文本问题。自然语言的深度学习,是用于单词、语句、段落的模式识别,就如同计算机视觉是像素的模式识别。

如同其他各种神经网络,深度学习模型并不输入原始文本,而是仅输入数字张量。文本向量化,是把文本转化成数字张量的过程。转化有多种方式:

  • 把文本分割成单词后转化成向量。
  • 把文本分割成字符后转化成向量。
  • 从文本中提取单词或字符的语料单位(n-grams)后转化成向量。

n-grams 是由多相连续单词或字符构成的重叠的组。

你从文本分割出的单词、字符或语料单元(n-grams)统称标识(tokens)。把文本分割成标识叫做标识化。文本向量化过程包括应用标识化方案,以及把生成的标识与数字向量关联。这些向量包装成序列化张量后,输入深度神经网络。向量与标识关联的方法有多种。在本节,我将介绍两种主要的关联方法:标识的单1编码(one-hot),和标识嵌入(典型地专用于单词,叫做词向量)。本节的其余部分,解释如何把原始文本转化成 Numpy 张量,并将其输入 Keras 网络的技术。

理解语料单元(n-grams)和词袋(bag-of-words)

n-grams 表示一组数目不超过 N 个的对象。对象是你可以从语句中提取出的连续的单词或者字符。

下面是个简单的例子“The cat sat on the mat.”分解成 2 单元(2-grams)的集合:
{"The", "The cat", "cat", "cat sat", "sat",

"sat on", "on", "on the", "the", "the mat", "mat"}

也可以分解成 3 单元的集合:
{"The", "The cat", "cat", "cat sat", "The cat sat",
"sat", "sat on", "on", "cat sat on", "on the", "the",

"sat on the", "the mat", "mat", "on the mat"}

这两个集合叫做 2 单元词袋和 3 单元词袋。这里“袋”的意思是指你处理的是标识的集合,不是列表或序列。

袋子里的标识没有特定顺序。标识化方法的产物叫做词袋。

词袋不是有序的标识化方法(生成的标识是集合不是序列,并且语句的一般结构不再存在),它主要用于浅层语言处理模型,并不用于深度学习模型。提取语料单元属于特征工程的方法,深度学习并不常用这种生硬而脆弱的方法,而是以分层特征学习取而代之。本章后面介绍的一维卷积神经网络和递归神经网络,能学习表示单词和字符的分组,不必查看字词的连续序列,不必显式告之分组的存在。因此,本书不再进一步涉及语料单元。但要记住,在使用轻型的、浅层的文本处理模型,如逻辑回归和随机森林时,它们是强有力的、不可忽视的特征工程工具。

单词和字符的单1编码(one-hot)

把标识转化成向量最常见最基本的方法是单1编码。方法是这样的:文本每个单词关联着唯一的整数索引 i,这个索引置入长度为 N (也是词汇表的长度)的二值向量。这个向量的元素值只有一个为 1 ,其余全部为零。值为 1 的元素位置索引值是 i。

当然,单1编码也适用于字符。以下是几个例子。

 单词的单1编码(玩具代码) 

import numpy as np
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
# 数据初始化: 每个样本是一项数据。本例样本是一条语句,但可以是整个文档
token_index = {}
# 对数据的全部标识建立索引
for sample in samples:
    for word in sample.split():
    # 用函数 split 把样本标识化。在实际应用中,还需去除标点等特殊字符
        if word not in token_index:
            token_index[word] = len(token_index) + 1
            # 把唯一的索引赋予每个唯一的单词。注意,不能使用索引值0
max_length = 10 
# 把样本向量化。你只需考虑样本中单词数的第一个最大值
results = np.zeros(shape=(len(samples),
                   max_length,
                   max(token_index.values()) + 1)) # 你在此保存结果 results
for i, sample in enumerate(samples):
    for j, word in list(enumerate(sample.split()))[:max_length]:
        index = token_index.get(word)
        results[i, j, index] = 1.

 字符的单1编码(玩具代码) 

import string
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
characters = string.printable # 全部 ASCII 字符
token_index = dict(zip(range(1, len(characters) + 1), characters))
max_length = 50
results = np.zeros((len(samples), max_length, max(token_index.keys()) + 1))
for i, sample in enumerate(samples):
    for j, character in enumerate(sample):
        index = token_index.get(character)
        results[i, j, index] = 1.
注意,Keras 有内建工具对原始文本数据的单词或字符进行单1编码。你应当使用这些工具,因为它们有些重要功能,如去除特殊字符、从你的数据集中确定N个最常见的单词,以防止处理非常大的输入向量。

 用 Keras 对单词作单1编码 

from keras.preprocessing.text import Tokenizer
samples = ['The cat sat on the mat.', 'The dog ate my homework.']
tokenizer = Tokenizer(num_words=1000) # 限定标识化的常用单词不超过1000个
tokenizer.fit_on_texts(samples) # 建立单词索引
sequences = tokenizer.texts_to_sequences(samples) # 字符串转化成整数索引列表
one_hot_results = tokenizer.texts_to_matrix(samples, mode='binary') 
    # 你可以直接得到单1编码的二值表示。这个 tokenizer 支持向量模式而非单1编码
word_index = tokenizer.word_index # 恢复计算出的单词索引
print('Found %s unique tokens.' % len(word_index))

单1编码的另一种形式是所谓的哈希单1编码。你的词汇中,如果单一标识太多难以处理,可以使用哈希单1编码。你可以把单词哈希化成固定长度的向量,而不是给每个单词加上索引,并用字典保持这些索引。这一般要用非常轻的哈希函数。这样做的主要好处,是不再需要单词索引,节省内存;并且,允许随时进行数据编码(在你见到全部可用数据前,可以立刻生成标识向量)。这一方法的缺点是容易受到哈希冲突的影响:两个不同的单词可能有相同的哈希值,并且,机器学习模型无法区分这种单词。

 对单词作哈希单1编码(玩具代码) 

samples = ['The cat sat on the mat.', 'The dog ate my homework.']
dimensionality = 1000
    # 保存单词的向量长度为 1,000。你会看到许多哈希冲突,降低了这种编码的精度
max_length = 10
results = np.zeros((len(samples), max_length, dimensionality))
for i, sample in enumerate(samples):
for j, word in list(enumerate(sample.split()))[:max_length]:
    index = abs(hash(word)) % dimensionality # 给单词随机的哈希索引,值为 0 - 1,000
    results[i, j, index] = 1.
使用词向量(word embeddings)
单词与向量相关联的另一种办法,是使用密集单词向量,叫做词向量。单1编码是二值稀疏、多数值为零、高维度(维度与词汇数目相同)。词向量是低维度浮点数向量,密集而不稀疏。词向量是从数据学习而来,不是由单1编码形成的向量。常见的词向量是256维、512维或者1024维向量,可以处理非常多的词汇。另一方面,单1编码的单词,需要20000维甚至更大的向量,以容纳相同数目的词汇。所以,词向量可以更小的维度容纳更多的信息。

有两个办法实现词向量:
  • 把词向量的学习与你的主要任务,如文档分类、语句预测等结合起来。开始是随机单词向量,接着像通过学习得到神经网络权重那样,得到学习后的单词向量。
  • 先用专门的机器学习算法得到词向量,再把这一向量输入你的模型。这叫做预先训练的词向量。

看一下这两种词向量。

  Embedding 层实例化  

from keras.layers import Embedding
embedding_layer = Embedding(1000, 64)
# Embedding 层至少有2个参数:标识数目(1000,索引从1起算)和词向量的维度(64)

最好把 Embedding 层理解成字典,它把表示特定单词的整数索引映射成密集向量。

Embedding 层的输入是2维整数张量(样本集,序列长度),每个样本都是整数序列。样本的序列长度必须相同。

3维浮点型张量(样本集,序列长度,词向量维度),可由 RNN 层或者 1维卷积层处理。这些将在下面几节介绍。

在你实例化 Embedding 时,它的权重(它的标识向量的内部词典)随机初始化,如同其他网络层的初始化。在训练过程中,这些单词向量通过反向传播逐渐调整。在完成训练后,词向量空间形成许多结构,一种结构对应模型处理的一个问题。

让我们把这些应用到 IMDB 影评情绪预测。首先,你要快速准备数据,把影评用词限制在10000个常用词内,并且影评字数不超过20个。网络模型从10000单词中学习8维度词向量,把输入的2维整数张量序列转化成3维浮点张量序列,把张量扁平化为2维,并以单一 Dense 层为顶层,进行分类训练。

  把 IMDB 数据装入 Embedding 层 

from keras.datasets import imdb
from keras import preprocessing
max_features = 10000 # 可用作描述特征的词汇数
maxlen = 20 # 特征描述的字数限制
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
    # 载入整数列表数据
x_train = preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
    # 把整数列表数据转化成2维整数张量(样本集,最大长度)
x_test = preprocessing.sequence.pad_sequences(x_test, maxlen=maxlen)

  用 Embedding 层对 IMDB 数据分类 

from keras.models import Sequential
from keras.layers import Flatten, Dense
model = Sequential()
model.add(Embedding(10000, 8, input_length=maxlen))
model.add(Flatten()) # 把嵌入的3维张量扁平化为2维张量(样本集,maxlen * 8)
model.add(Dense(1, activation='sigmoid')) # 在网络顶层加入分类器
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
model.summary()
history = model.fit(x_train, y_train,
                    epochs=10,
                    batch_size=32,
                    validation_split=0.2)

你得到的验证精度约为76%。因为你仅用到每一影评的开头20个词,所以这个结果很好。但要注意,仅仅扁平化了词向量序列,训练了单一 Dense 层,把输入序列看作多个离散的词,没有考虑词的内部联系和语句的结构。更好的办法,是在词向量序列的顶层,增加循环层或1维卷积层,以从特征中学习把序列看作一个整体。下面的几节,专门讲讲这个问题。

使用预先训练好的词向量

看一下你可如何在Keras模型中使用GloVe词向量。同样的办法可用于Word2vec词向量和其他词向量数据库。这一范例将刷新你学习过的文本标识化技术。

汇总:从纯文本到词向量

你将使用的模型与以前学过的相似:把语句向量装入向量序列,将其扁平化,在网络顶层训练 Dense 层。但是,你将用到预先训练的词向量,不用 Keras 自带的预先标识化的 IMDB 数据集。你需要从头开始做,先下载原始文本数据。

下载 IMDB 纯文本数据

下载地址是 http://mng.bz/0tIo 。下载后解压缩,把单独训练的影评收集成字符串列表,一个字符串是一个影评。还要收集影评的标签(正面的/负面的)放入标签列表。

  处理 IMDB 数据的标签  

import os
imdb_dir = '/Users/fchollet/Downloads/aclImdb'
train_dir = os.path.join(imdb_dir, 'train')
labels = []
texts = []
for label_type in ['neg', 'pos']:
dir_name = os.path.join(train_dir, label_type)
for fname in os.listdir(dir_name):
    if fname[-4:] == '.txt':
        f = open(os.path.join(dir_name, fname))
        texts.append(f.read())
        f.close()
        if label_type == 'neg':
            labels.append(0)
        else:
            labels.append(1)
数据标识化

把文本转成向量,分成训练和验证两部分。因为预先训练的词向量特别有用,我们只对影评的前200个样本学习分类。

  IMDB 纯文本数据转化成标识  

from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
maxlen = 100 # 影评限定前100字
training_samples = 200 # 训练样本 200个
validation_samples = 10000 # 验证样本 10000个
max_words = 10000 # 词汇上限 10000
tokenizer = Tokenizer(num_words=max_words)
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)
word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))
data = pad_sequences(sequences, maxlen=maxlen)
labels = np.asarray(labels)
print('Shape of data tensor:', data.shape)
print('Shape of label tensor:', labels.shape)
indices = np.arange(data.shape[0])
    # 把数据分成训练和验证两部分。在分割前先洗牌,因为样本排序消极评价的在前,积极评价的在后
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]
x_train = data[:training_samples]
y_train = labels[:training_samples]
x_val = data[training_samples: training_samples + validation_samples]
y_val = labels[training_samples: training_samples + validation_samples]
下载GloVe词向量
地址是 https://nlp.stanford.edu/projects/glove。下载预先计算好的词向量,2014年英文版维基百科。下载的文件名是

 glove.6B.zip, 大小为822MB,词汇量 400000(包括非文字标识),词向量 100维度。下载后解压缩。

词向量的预处理

让我们分析解压缩后的.txt文件,设置索引把单词(字符串)映射到它们的向量表示(数字向量)。

  分析 GloVe 词向量文件  

glove_dir = '/Users/fchollet/Downloads/glove.6B'
embeddings_index = {}
f = open(os.path.join(glove_dir, 'glove.6B.100d.txt'))
for line in f:
    values = line.split()
    word = values[0]
    coefs = np.asarray(values[1:], dtype='float32')
    embeddings_index[word] = coefs
f.close()
print('Found %s word vectors.' % len(embeddings_index))
下一步,你将设置词向量矩阵,以装入 Embedding 层。这个矩阵的形状是(最多单词数,词向量维度),每一矩阵单元

的索引位置 i 都包含词向量,i 就是标识化单词的索引。注意,索引 0 不代表任何单词或标识,它只是个占位符。

  准备 GloVe 词向量矩阵  

embedding_dim = 100
embedding_matrix = np.zeros((max_words, embedding_dim))
for word, i in word_index.items():
    if i < max_words:
        embedding_vector = embeddings_index.get(word)
        if embedding_vector is not None:
            embedding_matrix[i] = embedding_vector # 在词向量索引处没有单词的,值为0

定义模型

你用的模型架构与前相同。

from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense
model = Sequential()
model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
model.add(Flatten())
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()
把 GloVe 词向量装入模型

Embedding 层有个单权重矩阵:2维浮点矩阵的索引位置 i 是词向量。把你预备好的 GloVe 矩阵装进Embedding 层,模型的第一层。很简单。

model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False

另外,你冻结了 Embedding 层(设置其属性 attribute 值为 False),你熟悉了预先训练的卷积网特征的语境:如果模型的一部分是预先训练的,如你的 Embedding 层;另一部分是随机初始化,如你的分类器,那么,预先训练的部分在训练过程中不应更新,以防止遗忘这部分特征知识。引发大规模梯度更新的随机初始化的层,其原有特征知识将被破坏。

训练评估模型

编译训练模型

model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])
history = model.fit(x_train, y_train,
                    epochs=10,
                    batch_size=32,
                    validation_data=(x_val, y_val))
model.save_weights('pre_trained_glove_model.h5')

画出模型训练中的性能表现

import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()

  没有预先训练的词向量,训练相同的模型  

from keras.models import Sequential
from keras.layers import Embedding, Flatten, Dense
model = Sequential()
model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
model.add(Flatten())
model.add(Dense(32, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.summary()
model.compile(optimizer='rmsprop',
              loss='binary_crossentropy',
              metrics=['acc'])
history = model.fit(x_train, y_train,
                    epochs=10,
                    batch_size=32,
                    validation_data=(x_val, y_val))

最后,我们用测试数据评估模型。首先,你需要把测试数据转化为标识。

test_dir = os.path.join(imdb_dir, 'test')
labels = []
texts = []
for label_type in ['neg', 'pos']:
    dir_name = os.path.join(test_dir, label_type)
    for fname in sorted(os.listdir(dir_name)):
        if fname[-4:] == '.txt':
            f = open(os.path.join(dir_name, fname))
            texts.append(f.read())
            f.close()
            if label_type == 'neg':
                labels.append(0)
            else:
                labels.append(1)
sequences = tokenizer.texts_to_sequences(texts)
x_test = pad_sequences(sequences, maxlen=maxlen)
y_test = np.asarray(labels)

接着,装入并评估第一个模型。

model.load_weights('pre_trained_glove_model.h5')
model.evaluate(x_test, y_test)

你的测试精度为 56%,令人震惊。太少的样本训练数据,很难得到好的学习效果。

小结

现在,你学会了做以下事情:

  • 把纯文本转化为神经网络可以处理的数据
  • 在Keras模型中使用词向量,学习特定用途的标识向量
  • 使用预先训练的词向量,在自然语言处理的小型问题时,得到额外帮助




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值