Machine Learning Mastery 深度学习 NLP 教程(四)

Machine Learning Mastery 深度学习 NLP 教程(四)原文 MachineLearn 协议 CCBY NC SA4 0 如何开发用于情感分析的 N gram 多通道卷积神经网络原文 machinelearn com develop n gram multichannel convolutiona neural network sentiment analysis 用于文本分类和情感分析的标准深度学习模型使用单词嵌入层和一维卷积神经网络

原文:Machine Learning Mastery

协议:CC BY-NC-SA 4.0

如何开发用于情感分析的 N-gram 多通道卷积神经网络

原文: machinelearningmastery.com/develop-n-gram-multichannel-convolutional-neural-network-sentiment-analysis/

用于文本分类和情感分析的标准深度学习模型使用单词嵌入层和一维卷积神经网络。

可以通过使用多个并行卷积神经网络来扩展模型,该网络使用不同的内核大小读取源文档。实际上,这为文本创建了一个多通道卷积神经网络,用于读取具有不同 n-gram 大小(单词组)的文本。

在本教程中,您将了解如何开发一个多通道卷积神经网络,用于文本电影评论数据的情感预测。

完成本教程后,您将了解:

  • 如何准备电影评论文本数据进行建模。
  • 如何为 Keras 中的文本开发多通道卷积神经网络。
  • 如何评估看不见的电影评论数据的拟合模型。

让我们开始吧。

  • 2018 年 2 月更新:小代码更改以反映 Keras 2.1.3 API 中的更改。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如何开发用于情感分析的 N-gram 多通道卷积神经网络
Ed Dunens 的照片,保留一些权利。

教程概述

本教程分为 4 个部分;他们是:

  1. 电影评论数据集
  2. 数据准备
  3. 开发多渠道模型
  4. 评估模型

Python 环境

本教程假定您已安装 Python 3 SciPy 环境。

您必须安装带有 TensorFlow 或 Theano 后端的 Keras(2.0 或更高版本)。

本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。

如果您需要有关环境的帮助,请参阅此帖子:

  • 如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境

电影评论数据集

电影评论数据是 Bo Pang 和 Lillian Lee 在 21 世纪初从 imdb.com 网站上检索到的电影评论的集合。收集的评论作为他们自然语言处理研究的一部分。

评论最初于 2002 年发布,但更新和清理版本于 2004 年发布,称为“v2.0”。

该数据集包含 1,000 个正面和 1,000 个负面电影评论,这些评论来自 imdb.com 上托管的 rec.arts.movi​​es.reviews 新闻组的存档。作者将此数据集称为“极性数据集”。

我们的数据包含 2000 年之前写的 1000 份正面和 1000 份负面评论,每位作者的评论上限为 20(每位作者共 312 位)。我们将此语料库称为极性数据集。

  • 感伤教育:基于最小削减的主观性总结的情感分析,2004。

数据已经有所清理;例如:

  • 数据集仅包含英语评论。
  • 所有文本都已转换为小写。
  • 标点符号周围有空格,如句号,逗号和括号。
  • 文本每行被分成一个句子。

该数据已用于一些相关的自然语言处理任务。对于分类,机器学习模型(例如支持向量机)对数据的表现在高 70%到低 80%(例如 78%-82%)的范围内。

更复杂的数据准备可以看到高达 86%的结果,交叉验证 10 倍。如果我们想在现代方法的实验中使用这个数据集,这给了我们 80 年代中期的球场。

…根据下游极性分类器的选择,我们可以实现高度统计上的显着改善(从 82.8%到 86.4%)

  • 感伤教育:基于最小削减的主观性总结的情感分析,2004。

您可以从此处下载数据集:

  • 电影评论 Polarity Dataset (review_polarity.tar.gz,3MB)

解压缩文件后,您将有一个名为“txt_sentoken”的目录,其中包含两个子目录,其中包含文本“neg”和“pos”的负数和积极的评论。对于每个 neg 和 pos,每个文件存储一个评论约定cv000cv999

接下来,我们来看看加载和准备文本数据。

数据准备

在本节中,我们将看看 3 件事:

  1. 将数据分成训练和测试集。
  2. 加载和清理数据以删除标点符号和数字。
  3. 准备所有评论并保存到文件。

分为训练和测试装置

我们假装我们正在开发一种系统,可以预测文本电影评论的情感是积极的还是消极的。

这意味着在开发模型之后,我们需要对新的文本评论做出预测。这将要求对这些新评论执行所有相同的数据准备,就像对模型的训练数据执行一样。

我们将通过在任何数据准备之前拆分训练和测试数据集来确保将此约束纳入我们模型的评估中。这意味着测试集中的数据中的任何知识可以帮助我们更好地准备数据(例如,所使用的单词)在用于训练模型的数据的准备中是不可用的。

话虽如此,我们将使用最近 100 次正面评论和最后 100 次负面评论作为测试集(100 条评论),其余 1,800 条评论作为训练数据集。

这是 90%的训练,10%的数据分割。

通过使用评论的文件名可以轻松实现拆分,其中评论为 000 至 899 的评论用于训练数据,而评论为 900 以上的评论用于测试。

装载和清洁评论

文本数据已经非常干净;没有太多准备工作。

不会因细节问题而陷入困境,我们将按以下方式准备数据:

  • 在白色空间的分裂标记。
  • 从单词中删除所有标点符号。
  • 删除所有不完全由字母字符组成的单词。
  • 删除所有已知停用词的单词。
  • 删除长度为< = 1 个字符的所有单词。

我们可以将所有这些步骤放入一个名为clean_doc()的函数中,该函数将从文件加载的原始文本作为参数,并返回已清理的标记列表。我们还可以定义一个函数 load_doc(),它从文件中加载文件,以便与clean_doc()函数一起使用。下面列出了清理第一次正面评价的示例。

from nltk.corpus import stopwords
import string

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, 'r')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

# turn a doc into clean tokens
def clean_doc(doc):
	# split into tokens by white space
	tokens = doc.split()
	# remove punctuation from each token
	table = str.maketrans('', '', string.punctuation)
	tokens = [w.translate(table) for w in tokens]
	# remove remaining tokens that are not alphabetic
	tokens = [word for word in tokens if word.isalpha()]
	# filter out stop words
	stop_words = set(stopwords.words('english'))
	tokens = [w for w in tokens if not w in stop_words]
	# filter out short tokens
	tokens = [word for word in tokens if len(word) > 1]
	return tokens

# load the document
filename = 'txt_sentoken/pos/cv000_29590.txt'
text = load_doc(filename)
tokens = clean_doc(text)
print(tokens)

运行该示例加载并清除一个电影评论。

打印清洁评论中的标记以供审阅。

...
'creepy', 'place', 'even', 'acting', 'hell', 'solid', 'dreamy', 'depp', 'turning', 'typically', 'strong', 'performance', 'deftly', 'handling', 'british', 'accent', 'ians', 'holm', 'joe', 'goulds', 'secret', 'richardson', 'dalmatians', 'log', 'great', 'supporting', 'roles', 'big', 'surprise', 'graham', 'cringed', 'first', 'time', 'opened', 'mouth', 'imagining', 'attempt', 'irish', 'accent', 'actually', 'wasnt', 'half', 'bad', 'film', 'however', 'good', 'strong', 'violencegore', 'sexuality', 'language', 'drug', 'content']

清除所有评论并保存

我们现在可以使用该功能来清理评论并将其应用于所有评论。

为此,我们将在下面开发一个名为process_docs()的新函数,它将遍历目录中的所有评论,清理它们并将它们作为列表返回。

我们还将为函数添加一个参数,以指示函数是处理序列还是测试评论,这样可以过滤文件名(如上所述),并且只清理和返回所请求的那些训练或测试评论。

完整功能如下所列。

# load all docs in a directory
def process_docs(directory, is_trian):
	documents = list()
	# walk through all files in the folder
	for filename in listdir(directory):
		# skip any reviews in the test set
		if is_trian and filename.startswith('cv9'):
			continue
		if not is_trian and not filename.startswith('cv9'):
			continue
		# create the full path of the file to open
		path = directory + '/' + filename
		# load the doc
		doc = load_doc(path)
		# clean doc
		tokens = clean_doc(doc)
		# add to list
		documents.append(tokens)
	return documents

我们可以将此功能称为负面训练评论,如下所示:

negative_docs = process_docs('txt_sentoken/neg', True)

接下来,我们需要训练和测试文件的标签。我们知道我们有 900 份训练文件和 100 份测试文件。我们可以使用 Python 列表推导为训练和测试集的负(0)和正(1)评论创建标签。

trainy = [0 for _ in range(900)] + [1 for _ in range(900)]
testY = [0 for _ in range(100)] + [1 for _ in range(100)]

最后,我们希望将准备好的训练和测试集保存到文件中,以便我们以后可以加载它们进行建模和模型评估。

下面命名为save_dataset()的函数将使用 pickle API 将给定的准备数据集(X 和 y 元素)保存到文件中。

# save a dataset to file
def save_dataset(dataset, filename):
	dump(dataset, open(filename, 'wb'))
	print('Saved: %s' % filename)

完整的例子

我们可以将所有这些数据准备步骤结合在一起。

下面列出了完整的示例。

from string import punctuation
from os import listdir
from nltk.corpus import stopwords
from pickle import dump

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, 'r')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

# turn a doc into clean tokens
def clean_doc(doc):
	# split into tokens by white space
	tokens = doc.split()
	# remove punctuation from each token
	table = str.maketrans('', '', punctuation)
	tokens = [w.translate(table) for w in tokens]
	# remove remaining tokens that are not alphabetic
	tokens = [word for word in tokens if word.isalpha()]
	# filter out stop words
	stop_words = set(stopwords.words('english'))
	tokens = [w for w in tokens if not w in stop_words]
	# filter out short tokens
	tokens = [word for word in tokens if len(word) > 1]
	tokens = ' '.join(tokens)
	return tokens

# load all docs in a directory
def process_docs(directory, is_trian):
	documents = list()
	# walk through all files in the folder
	for filename in listdir(directory):
		# skip any reviews in the test set
		if is_trian and filename.startswith('cv9'):
			continue
		if not is_trian and not filename.startswith('cv9'):
			continue
		# create the full path of the file to open
		path = directory + '/' + filename
		# load the doc
		doc = load_doc(path)
		# clean doc
		tokens = clean_doc(doc)
		# add to list
		documents.append(tokens)
	return documents

# save a dataset to file
def save_dataset(dataset, filename):
	dump(dataset, open(filename, 'wb'))
	print('Saved: %s' % filename)

# load all training reviews
negative_docs = process_docs('txt_sentoken/neg', True)
positive_docs = process_docs('txt_sentoken/pos', True)
trainX = negative_docs + positive_docs
trainy = [0 for _ in range(900)] + [1 for _ in range(900)]
save_dataset([trainX,trainy], 'train.pkl')

# load all test reviews
negative_docs = process_docs('txt_sentoken/neg', False)
positive_docs = process_docs('txt_sentoken/pos', False)
testX = negative_docs + positive_docs
testY = [0 for _ in range(100)] + [1 for _ in range(100)]
save_dataset([testX,testY], 'test.pkl')

运行该示例分别清除文本电影评论文档,创建标签,并分别为train.pkltest.pkl中的训练和测试数据集保存准备好的数据。

现在我们准备开发我们的模型了。

开发多渠道模型

在本节中,我们将开发一个用于情感分析预测问题的多通道卷积神经网络。

本节分为 3 部分:

  1. 编码数据
  2. 定义模型。
  3. 完整的例子。

编码数据

第一步是加载已清理的训练数据集。

可以调用以下名为load_dataset()的函数来加载 pickle 训练数据集。

# load a clean dataset
def load_dataset(filename):
	return load(open(filename, 'rb'))

trainLines, trainLabels = load_dataset('train.pkl')

接下来,我们必须在训练数据集上安装 Keras Tokenizer。我们将使用此标记器来定义嵌入层的词汇表,并将审阅文档编码为整数。

下面的函数create_tokenizer()将创建一个给定文档列表的 Tokenizer。

# fit a tokenizer
def create_tokenizer(lines):
	tokenizer = Tokenizer()
	tokenizer.fit_on_texts(lines)
	return tokenizer

我们还需要知道输入序列的最大长度作为模型的输入并将所有序列填充到固定长度。

下面的函数max_length()将计算训练数据集中所有评论的最大长度(单词数)。

# calculate the maximum document length
def max_length(lines):
	return max([len(s.split()) for s in lines])

我们还需要知道嵌入层的词汇量大小。

这可以从准备好的 Tokenizer 计算,如下:

# calculate vocabulary size
vocab_size = len(tokenizer.word_index) + 1

最后,我们可以整数编码并填充干净的电影评论文本。

名为encode_text()的以下函数将编码和填充文本数据到最大查看长度。

# encode a list of lines
def encode_text(tokenizer, lines, length):
	# integer encode
	encoded = tokenizer.texts_to_sequences(lines)
	# pad encoded sequences
	padded = pad_sequences(encoded, maxlen=length, padding='post')
	return padded

定义模型

文档分类的标准模型是使用嵌入层作为输入,然后是一维卷积神经网络,池化层,然后是预测输出层。

卷积层中的内核大小定义了卷积在输入文本文档中传递时要考虑的单词数,从而提供分组参数。

用于文档分类的多通道卷积神经网络涉及使用具有不同大小的内核的标准模型的多个版本。这允许一次以不同的分辨率或不同的 n-gram(单词组)处理文档,同时模型学习如何最好地整合这些解释。

Yoon Kim 在他的 2014 年题为“用于句子分类的卷积神经网络”的论文中首次描述了这种方法。

在本文中,Kim 尝试了静态和动态(更新)嵌入层,我们可以简化方法,而只关注使用不同的内核大小。

使用 Kim 的论文中的图表可以最好地理解这种方法:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

描述文本的多通道卷积神经网络。
取自“用于句子分类的卷积神经网络”。

在 Keras 中,可以使用功能 API 定义多输入模型。

我们将定义一个带有三个输入通道的模型,用于处理 4 克,6 克和 8 克的电影评论文本。

每个频道由以下元素组成:

  • 输入层,用于定义输入序列的长度。
  • 嵌入层设置为词汇表的大小和 100 维实值表示。
  • 一维卷积层,具有 32 个滤波器,内核大小设置为一次读取的字数。
  • Max Pooling 层用于合并卷积层的输出。
  • 展平层以将三维输出减少为二维以进行连接。

三个通道的输出连接成一个向量,并由 Dense 层和输出层处理。

下面的函数定义并返回模型。作为定义模型的一部分,将打印已定义模型的摘要,并创建模型图的图并将其保存到文件中。

# define the model
def define_model(length, vocab_size):
	# channel 1
	inputs1 = Input(shape=(length,))
	embedding1 = Embedding(vocab_size, 100)(inputs1)
	conv1 = Conv1D(filters=32, kernel_size=4, activation='relu')(embedding1)
	drop1 = Dropout(0.5)(conv1)
	pool1 = MaxPooling1D(pool_size=2)(drop1)
	flat1 = Flatten()(pool1)
	# channel 2
	inputs2 = Input(shape=(length,))
	embedding2 = Embedding(vocab_size, 100)(inputs2)
	conv2 = Conv1D(filters=32, kernel_size=6, activation='relu')(embedding2)
	drop2 = Dropout(0.5)(conv2)
	pool2 = MaxPooling1D(pool_size=2)(drop2)
	flat2 = Flatten()(pool2)
	# channel 3
	inputs3 = Input(shape=(length,))
	embedding3 = Embedding(vocab_size, 100)(inputs3)
	conv3 = Conv1D(filters=32, kernel_size=8, activation='relu')(embedding3)
	drop3 = Dropout(0.5)(conv3)
	pool3 = MaxPooling1D(pool_size=2)(drop3)
	flat3 = Flatten()(pool3)
	# merge
	merged = concatenate([flat1, flat2, flat3])
	# interpretation
	dense1 = Dense(10, activation='relu')(merged)
	outputs = Dense(1, activation='sigmoid')(dense1)
	model = Model(inputs=[inputs1, inputs2, inputs3], outputs=outputs)
	# compile
	model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
	# summarize
	print(model.summary())
	plot_model(model, show_shapes=True, to_file='multichannel.png')
	return model

完整的例子

将所有这些结合在一起,下面列出了完整的示例。

from pickle import load
from numpy import array
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.utils.vis_utils import plot_model
from keras.models import Model
from keras.layers import Input
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import Dropout
from keras.layers import Embedding
from keras.layers.convolutional import Conv1D
from keras.layers.convolutional import MaxPooling1D
from keras.layers.merge import concatenate

# load a clean dataset
def load_dataset(filename):
	return load(open(filename, 'rb'))

# fit a tokenizer
def create_tokenizer(lines):
	tokenizer = Tokenizer()
	tokenizer.fit_on_texts(lines)
	return tokenizer

# calculate the maximum document length
def max_length(lines):
	return max([len(s.split()) for s in lines])

# encode a list of lines
def encode_text(tokenizer, lines, length):
	# integer encode
	encoded = tokenizer.texts_to_sequences(lines)
	# pad encoded sequences
	padded = pad_sequences(encoded, maxlen=length, padding='post')
	return padded

# define the model
def define_model(length, vocab_size):
	# channel 1
	inputs1 = Input(shape=(length,))
	embedding1 = Embedding(vocab_size, 100)(inputs1)
	conv1 = Conv1D(filters=32, kernel_size=4, activation='relu')(embedding1)
	drop1 = Dropout(0.5)(conv1)
	pool1 = MaxPooling1D(pool_size=2)(drop1)
	flat1 = Flatten()(pool1)
	# channel 2
	inputs2 = Input(shape=(length,))
	embedding2 = Embedding(vocab_size, 100)(inputs2)
	conv2 = Conv1D(filters=32, kernel_size=6, activation='relu')(embedding2)
	drop2 = Dropout(0.5)(conv2)
	pool2 = MaxPooling1D(pool_size=2)(drop2)
	flat2 = Flatten()(pool2)
	# channel 3
	inputs3 = Input(shape=(length,))
	embedding3 = Embedding(vocab_size, 100)(inputs3)
	conv3 = Conv1D(filters=32, kernel_size=8, activation='relu')(embedding3)
	drop3 = Dropout(0.5)(conv3)
	pool3 = MaxPooling1D(pool_size=2)(drop3)
	flat3 = Flatten()(pool3)
	# merge
	merged = concatenate([flat1, flat2, flat3])
	# interpretation
	dense1 = Dense(10, activation='relu')(merged)
	outputs = Dense(1, activation='sigmoid')(dense1)
	model = Model(inputs=[inputs1, inputs2, inputs3], outputs=outputs)
	# compile
	model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
	# summarize
	print(model.summary())
	plot_model(model, show_shapes=True, to_file='multichannel.png')
	return model

# load training dataset
trainLines, trainLabels = load_dataset('train.pkl')
# create tokenizer
tokenizer = create_tokenizer(trainLines)
# calculate max document length
length = max_length(trainLines)
# calculate vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Max document length: %d' % length)
print('Vocabulary size: %d' % vocab_size)
# encode data
trainX = encode_text(tokenizer, trainLines, length)
print(trainX.shape)

# define model
model = define_model(length, vocab_size)
# fit model
model.fit([trainX,trainX,trainX], array(trainLabels), epochs=10, batch_size=16)
# save the model
model.save('model.h5')

首先运行该示例将打印准备好的训练数据集的摘要。

Max document length: 1380
Vocabulary size: 44277
(1800, 1380)

接下来,打印已定义模型的摘要。

____________________________________________________________________________________________________
Layer (type)                     Output Shape          Param # Connected to
====================================================================================================
input_1 (InputLayer)             (None, 1380)          0
____________________________________________________________________________________________________
input_2 (InputLayer)             (None, 1380)          0
____________________________________________________________________________________________________
input_3 (InputLayer)             (None, 1380)          0
____________________________________________________________________________________________________
embedding_1 (Embedding)          (None, 1380, 100)     4427700     input_1[0][0]
____________________________________________________________________________________________________
embedding_2 (Embedding)          (None, 1380, 100)     4427700     input_2[0][0]
____________________________________________________________________________________________________
embedding_3 (Embedding)          (None, 1380, 100)     4427700     input_3[0][0]
____________________________________________________________________________________________________
conv1d_1 (Conv1D)                (None, 1377, 32)      12832       embedding_1[0][0]
____________________________________________________________________________________________________
conv1d_2 (Conv1D)                (None, 1375, 32)      19232       embedding_2[0][0]
____________________________________________________________________________________________________
conv1d_3 (Conv1D)                (None, 1373, 32)      25632       embedding_3[0][0]
____________________________________________________________________________________________________
dropout_1 (Dropout)              (None, 1377, 32)      0           conv1d_1[0][0]
____________________________________________________________________________________________________
dropout_2 (Dropout)              (None, 1375, 32)      0           conv1d_2[0][0]
____________________________________________________________________________________________________
dropout_3 (Dropout)              (None, 1373, 32)      0           conv1d_3[0][0]
____________________________________________________________________________________________________
max_pooling1d_1 (MaxPooling1D)   (None, 688, 32)       0           dropout_1[0][0]
____________________________________________________________________________________________________
max_pooling1d_2 (MaxPooling1D)   (None, 687, 32)       0           dropout_2[0][0]
____________________________________________________________________________________________________
max_pooling1d_3 (MaxPooling1D)   (None, 686, 32)       0           dropout_3[0][0]
____________________________________________________________________________________________________
flatten_1 (Flatten)              (None, 22016)         0           max_pooling1d_1[0][0]
____________________________________________________________________________________________________
flatten_2 (Flatten)              (None, 21984)         0           max_pooling1d_2[0][0]
____________________________________________________________________________________________________
flatten_3 (Flatten)              (None, 21952)         0           max_pooling1d_3[0][0]
____________________________________________________________________________________________________
concatenate_1 (Concatenate)      (None, 65952)         0           flatten_1[0][0]
                                                                   flatten_2[0][0]
                                                                   flatten_3[0][0]
____________________________________________________________________________________________________
dense_1 (Dense)                  (None, 10)            659530      concatenate_1[0][0]
____________________________________________________________________________________________________
dense_2 (Dense)                  (None, 1)             11          dense_1[0][0]
====================================================================================================
Total params: 14,000,337
Trainable params: 14,000,337
Non-trainable params: 0
____________________________________________________________________________________________________

该模型相对较快,并且似乎在训练数据集上表现出良好的技能。

...
Epoch 6/10
1800/1800 [==============================] - 30s - loss: 9.9093e-04 - acc: 1.0000
Epoch 7/10
1800/1800 [==============================] - 29s - loss: 5.1899e-04 - acc: 1.0000
Epoch 8/10
1800/1800 [==============================] - 28s - loss: 3.7958e-04 - acc: 1.0000
Epoch 9/10
1800/1800 [==============================] - 29s - loss: 3.0534e-04 - acc: 1.0000
Epoch 10/10
1800/1800 [==============================] - 29s - loss: 2.6234e-04 - acc: 1.0000

定义模型的图表将保存到文件中,清楚地显示模型的三个输入通道。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

文本多通道卷积神经网络图

该模型适用于多个时期并保存到文件model.h5以供以后评估。

评估模型

在本节中,我们可以通过预测未见测试数据集中所有评论的情感来评估拟合模型。

使用上一节中开发的数据加载函数,我们可以加载和编码训练和测试数据集。

# load datasets
trainLines, trainLabels = load_dataset('train.pkl')
testLines, testLabels = load_dataset('test.pkl')

# create tokenizer
tokenizer = create_tokenizer(trainLines)
# calculate max document length
length = max_length(trainLines)
# calculate vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Max document length: %d' % length)
print('Vocabulary size: %d' % vocab_size)
# encode data
trainX = encode_text(tokenizer, trainLines, length)
testX = encode_text(tokenizer, testLines, length)
print(trainX.shape, testX.shape)

我们可以加载保存的模型并在训练和测试数据集上进行评估。

下面列出了完整的示例。

from pickle import load
from numpy import array
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.models import load_model

# load a clean dataset
def load_dataset(filename):
	return load(open(filename, 'rb'))

# fit a tokenizer
def create_tokenizer(lines):
	tokenizer = Tokenizer()
	tokenizer.fit_on_texts(lines)
	return tokenizer

# calculate the maximum document length
def max_length(lines):
	return max([len(s.split()) for s in lines])

# encode a list of lines
def encode_text(tokenizer, lines, length):
	# integer encode
	encoded = tokenizer.texts_to_sequences(lines)
	# pad encoded sequences
	padded = pad_sequences(encoded, maxlen=length, padding='post')
	return padded

# load datasets
trainLines, trainLabels = load_dataset('train.pkl')
testLines, testLabels = load_dataset('test.pkl')

# create tokenizer
tokenizer = create_tokenizer(trainLines)
# calculate max document length
length = max_length(trainLines)
# calculate vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Max document length: %d' % length)
print('Vocabulary size: %d' % vocab_size)
# encode data
trainX = encode_text(tokenizer, trainLines, length)
testX = encode_text(tokenizer, testLines, length)
print(trainX.shape, testX.shape)

# load the model
model = load_model('model.h5')

# evaluate model on training dataset
loss, acc = model.evaluate([trainX,trainX,trainX], array(trainLabels), verbose=0)
print('Train Accuracy: %f' % (acc*100))

# evaluate model on test dataset dataset
loss, acc = model.evaluate([testX,testX,testX],array(testLabels), verbose=0)
print('Test Accuracy: %f' % (acc*100))

运行该示例将在训练和测试数据集上打印模型的技能。

Max document length: 1380
Vocabulary size: 44277
(1800, 1380) (200, 1380)

Train Accuracy: 100.000000
Test Accuracy: 87.500000

我们可以看到,正如预期的那样,训练数据集的技能非常出色,这里的准确率为 100%。

我们还可以看到模型在看不见的测试数据集上的技能也非常令人印象深刻,达到了 87.5%,这高于 2014 年论文中报告的模型的技能(尽管不是直接的苹果对苹果的比较)。

扩展

本节列出了一些扩展您可能希望探索的教程的想法。

  • 不同的 n-gram 。通过更改模型中通道使用的内核大小(n-gram 的数量)来探索模型,以了解它如何影响模型技能。
  • 更多或更少的频道。探索在模型中使用更多或更少的渠道,并了解它如何影响模型技能。
  • 深层网络。卷积神经网络在更深层时在计算机视觉中表现更好。在这里探索使用更深层的模型,看看它如何影响模型技能。

进一步阅读

如果您希望深入了解,本节将提供有关该主题的更多资源。

  • 用于句子分类的卷积神经网络,2014。
  • 用于句子分类的卷积神经网络(代码)。
  • Keras 功能 API

摘要

在本教程中,您了解了如何为文本电影评论数据开发多通道卷积神经网络以进行情感预测。

具体来说,你学到了:

  • 如何准备电影评论文本数据进行建模。
  • 如何为 Keras 中的文本开发多通道卷积神经网络。
  • 如何评估看不见的电影评论数据的拟合模型。

你有任何问题吗?
在下面的评论中提出您的问题,我会尽力回答。

如何从零开始开发神经机器翻译系统

原文: machinelearningmastery.com/develop-neural-machine-translation-system-keras/

自动开发深度学习模型

使用 Keras 逐步将 Python 从德语翻译成英语。

机器翻译是一项具有挑战性的任务,传统上涉及使用高度复杂的语言知识开发的大型统计模型。

神经机器翻译是利用深度神经网络解决机器翻译问题。

在本教程中,您将了解如何开发用于将德语短语翻译成英语的神经机器翻译系统。

完成本教程后,您将了解:

  • 如何清理和准备数据准备训练神经机器翻译系统。
  • 如何开发机器翻译的编解码器模型。
  • 如何使用训练有素的模型推断新的输入短语并评估模型技巧。

让我们开始吧。

:摘录自:“深度学习自然语言处理”。
看一下,如果你想要更多的分步教程,在使用文本数据时充分利用深度学习方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如何在 Keras
中开发神经机器翻译系统BjörnGroß,保留一些权利。

教程概述

本教程分为 4 个部分;他们是:

  1. 德语到英语翻译数据集
  2. 准备文本数据
  3. 训练神经翻译模型
  4. 评估神经翻译模型

Python 环境

本教程假定您已安装 Python 3 SciPy 环境。

您必须安装带有 TensorFlow 或 Theano 后端的 Keras(2.0 或更高版本)。

本教程还假设您已安装 NumPy 和 Matplotlib。

如果您需要有关环境的帮助,请参阅此帖子:

  • 如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境

这样的教程不需要 GPU,但是,您可以在 Amazon Web Services 上廉价地访问 GPU。在本教程中学习如何:

  • 如何设置 Amazon AWS EC2 GPU 以训练 Keras 深度学习模型(循序渐进)

让我们潜入。

德语到英语翻译数据集

在本教程中,我们将使用德语到英语术语的数据集作为语言学习的抽认卡的基础。

该数据集可从 ManyThings.org 网站获得,其中的例子来自 Tatoeba Project 。该数据集由德语短语及其英语对应组成,旨在与 Anki 闪卡软件一起使用。

该页面提供了许多语言对的列表,我建议您探索其他语言:

  • 制表符分隔的双语句子对

我们将在本教程中使用的数据集可在此处下载:

  • 德语 - 英语 deu-eng.zip

将数据集下载到当前工作目录并解压缩;例如:

unzip deu-eng.zip

您将拥有一个名为deu.txt的文件,其中包含 152,820 对英语到德语阶段,每行一对,并带有分隔语言的选项卡。

例如,文件的前 5 行如下所示:

Hi.	Hallo!
Hi.	Grüß Gott!
Run!	Lauf!
Wow!	Potzdonner!
Wow!	Donnerwetter!

我们将预测问题框定为德语中的一系列单词作为输入,翻译或预测英语单词的序列。

我们将开发的模型适用于一些初学德语短语。

准备文本数据

下一步是准备好文本数据以进行建模。

如果您不熟悉清理文本数据,请参阅此帖子:

  • 如何使用 Python 清理机器学习文本

查看原始数据并记下您在数据清理操作中可能需要处理的内容。

例如,以下是我在审核原始数据时注意到的一些观察结果:

  • 有标点符号。
  • 该文本包含大写和小写。
  • 德语中有特殊字符。
  • 英语中有重复的短语,德语有不同的翻译。
  • 文件按句子长度排序,文件末尾有很长的句子。

你有没有注意到其他重要的事情?
请在下面的评论中告诉我。

良好的文本清理程序可以处理这些观察中的一些或全部。

数据准备分为两个小节:

  1. 干净的文字
  2. 拆分文字

1.清洁文字

首先,我们必须以保留 Unicode 德语字符的方式加载数据。下面的函数load_doc()将把文件加载为一团文本。

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, mode='rt', encoding='utf-8')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

每行包含一对短语,首先是英语,然后是德语,由制表符分隔。

我们必须逐行拆分加载的文本,然后按短语拆分。下面的函数to_pairs()将拆分加载的文本。

# split a loaded document into sentences
def to_pairs(doc):
	lines = doc.strip().split('\n')
	pairs = [line.split('\t') for line in  lines]
	return pairs

我们现在准备清理每一句话。我们将执行的具体清洁操作如下:

  • 删除所有不可打印的字符。
  • 删除所有标点字符。
  • 将所有 Unicode 字符规范化为 ASCII(例如拉丁字符)。
  • 将案例规范化为小写。
  • 删除任何非字母的剩余令牌。

我们将对加载的数据集中每对的每个短语执行这些操作。

下面的clean_pairs()函数实现了这些操作。

# clean a list of lines
def clean_pairs(lines):
	cleaned = list()
	# prepare regex for char filtering
	re_print = re.compile('[^%s]' % re.escape(string.printable))
	# prepare translation table for removing punctuation
	table = str.maketrans('', '', string.punctuation)
	for pair in lines:
		clean_pair = list()
		for line in pair:
			# normalize unicode characters
			line = normalize('NFD', line).encode('ascii', 'ignore')
			line = line.decode('UTF-8')
			# tokenize on white space
			line = line.split()
			# convert to lowercase
			line = [word.lower() for word in line]
			# remove punctuation from each token
			line = [word.translate(table) for word in line]
			# remove non-printable chars form each token
			line = [re_print.sub('', w) for w in line]
			# remove tokens with numbers in them
			line = [word for word in line if word.isalpha()]
			# store as string
			clean_pair.append(' '.join(line))
		cleaned.append(clean_pair)
	return array(cleaned)

最后,既然已经清理了数据,我们可以将短语对列表保存到准备使用的文件中。

函数save_clean_data()使用 pickle API 将干净文本列表保存到文件中。

将所有这些结合在一起,下面列出了完整的示例。

import string
import re
from pickle import dump
from unicodedata import normalize
from numpy import array

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, mode='rt', encoding='utf-8')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

# split a loaded document into sentences
def to_pairs(doc):
	lines = doc.strip().split('\n')
	pairs = [line.split('\t') for line in  lines]
	return pairs

# clean a list of lines
def clean_pairs(lines):
	cleaned = list()
	# prepare regex for char filtering
	re_print = re.compile('[^%s]' % re.escape(string.printable))
	# prepare translation table for removing punctuation
	table = str.maketrans('', '', string.punctuation)
	for pair in lines:
		clean_pair = list()
		for line in pair:
			# normalize unicode characters
			line = normalize('NFD', line).encode('ascii', 'ignore')
			line = line.decode('UTF-8')
			# tokenize on white space
			line = line.split()
			# convert to lowercase
			line = [word.lower() for word in line]
			# remove punctuation from each token
			line = [word.translate(table) for word in line]
			# remove non-printable chars form each token
			line = [re_print.sub('', w) for w in line]
			# remove tokens with numbers in them
			line = [word for word in line if word.isalpha()]
			# store as string
			clean_pair.append(' '.join(line))
		cleaned.append(clean_pair)
	return array(cleaned)

# save a list of clean sentences to file
def save_clean_data(sentences, filename):
	dump(sentences, open(filename, 'wb'))
	print('Saved: %s' % filename)

# load dataset
filename = 'deu.txt'
doc = load_doc(filename)
# split into english-german pairs
pairs = to_pairs(doc)
# clean sentences
clean_pairs = clean_pairs(pairs)
# save clean pairs to file
save_clean_data(clean_pairs, 'english-german.pkl')
# spot check
for i in range(100):
	print('[%s] => [%s]' % (clean_pairs[i,0], clean_pairs[i,1]))

运行该示例在当前工作目录中创建一个新文件,其中包含名为 english-german.pkl 的已清理文本。

打印清洁文本的一些示例供我们在运行结束时进行评估,以确认清洁操作是按预期执行的。

[hi] => [hallo]
[hi] => [gru gott]
[run] => [lauf]
[wow] => [potzdonner]
[wow] => [donnerwetter]
[fire] => [feuer]
[help] => [hilfe]
[help] => [zu hulf]
[stop] => [stopp]
[wait] => [warte]
...

2.分割文字

干净的数据包含超过 150,000 个短语对,并且文件末尾的一些对非常长。

这是开发小型翻译模型的大量示例。模型的复杂性随着示例的数量,短语的长度和词汇的大小而增加。

虽然我们有一个很好的数据集用于建模翻译,但我们会稍微简化问题,以大幅减少所需模型的大小,进而缩短适合模型所需的训练时间。

您可以探索在更全面的数据集上开发模型作为扩展;我很想听听你的表现。

我们将通过将数据集减少到文件中的前 10,000 个示例来简化问题;这些将是数据集中最短的短语。

此外,我们将把前 9,000 个作为训练示例,其余 1,000 个例子用于测试拟合模型。

下面是加载干净数据,拆分数据并将数据拆分部分保存到新文件的完整示例。

from pickle import load
from pickle import dump
from numpy.random import rand
from numpy.random import shuffle

# load a clean dataset
def load_clean_sentences(filename):
	return load(open(filename, 'rb'))

# save a list of clean sentences to file
def save_clean_data(sentences, filename):
	dump(sentences, open(filename, 'wb'))
	print('Saved: %s' % filename)

# load dataset
raw_dataset = load_clean_sentences('english-german.pkl')

# reduce dataset size
n_sentences = 10000
dataset = raw_dataset[:n_sentences, :]
# random shuffle
shuffle(dataset)
# split into train/test
train, test = dataset[:9000], dataset[9000:]
# save
save_clean_data(dataset, 'english-german-both.pkl')
save_clean_data(train, 'english-german-train.pkl')
save_clean_data(test, 'english-german-test.pkl')

运行该示例将创建三个新文件: english-german-both.pkl ,其中包含我们可用于定义问题参数的所有训练和测试示例,例如最大短语长度和词汇,以及训练和测试数据集的 english-german-train.pklenglish-german-test.pkl 文件。

我们现在准备开始开发我们的翻译模型。

训练神经翻译模型

在本节中,我们将开发神经翻译模型。

如果您不熟悉神经翻译模型,请参阅帖子:

  • 神经机器翻译的温和介绍

这涉及加载和准备准备好建模的清洁文本数据,以及在准备好的数据上定义和训练模型。

让我们从加载数据集开始,以便我们可以准备数据。以下名为load_clean_sentences()的函数可用于依次加载 train,test 和两个数据集。

# load a clean dataset
def load_clean_sentences(filename):
	return load(open(filename, 'rb'))

# load datasets
dataset = load_clean_sentences('english-german-both.pkl')
train = load_clean_sentences('english-german-train.pkl')
test = load_clean_sentences('english-german-test.pkl')

我们将使用“两者”或训练和测试数据集的组合来定义问题的最大长度和词汇。

这是为了简单起见。或者,我们可以单独从训练数据集定义这些属性,并截断测试集中的例子,这些例子太长或者词汇不在词汇表中。

我们可以根据建模需要使用 KerasTokenize类将单词映射到整数。我们将为英语序列和德语序列使用单独的分词器。下面命名为create_tokenizer()的函数将在短语列表上训练一个分词器。

# fit a tokenizer
def create_tokenizer(lines):
	tokenizer = Tokenizer()
	tokenizer.fit_on_texts(lines)
	return tokenizer

类似地,下面名为max_length()的函数将找到短语列表中最长序列的长度。

# max sentence length
def max_length(lines):
	return max(len(line.split()) for line in lines)

我们可以使用组合数据集调用这些函数来为英语和德语短语准备标记符,词汇表大小和最大长度。

# prepare english tokenizer
eng_tokenizer = create_tokenizer(dataset[:, 0])
eng_vocab_size = len(eng_tokenizer.word_index) + 1
eng_length = max_length(dataset[:, 0])
print('English Vocabulary Size: %d' % eng_vocab_size)
print('English Max Length: %d' % (eng_length))
# prepare german tokenizer
ger_tokenizer = create_tokenizer(dataset[:, 1])
ger_vocab_size = len(ger_tokenizer.word_index) + 1
ger_length = max_length(dataset[:, 1])
print('German Vocabulary Size: %d' % ger_vocab_size)
print('German Max Length: %d' % (ger_length))

我们现在准备准备训练数据集。

每个输入和输出序列必须编码为整数并填充到最大短语长度。这是因为我们将对输入序列使用字嵌入,并对输出序列进行热编码。以下名为encode_sequences()的函数将执行这些操作并返回结果。

# encode and pad sequences
def encode_sequences(tokenizer, length, lines):
	# integer encode sequences
	X = tokenizer.texts_to_sequences(lines)
	# pad sequences with 0 values
	X = pad_sequences(X, maxlen=length, padding='post')
	return X

输出序列需要进行单热编码。这是因为模型将预测词汇表中每个单词作为输出的概率。

下面的函数encode_output()将对英文输出序列进行单热编码。

# one hot encode target sequence
def encode_output(sequences, vocab_size):
	ylist = list()
	for sequence in sequences:
		encoded = to_categorical(sequence, num_classes=vocab_size)
		ylist.append(encoded)
	y = array(ylist)
	y = y.reshape(sequences.shape[0], sequences.shape[1], vocab_size)
	return y

我们可以利用这两个函数并准备训练模型的训练和测试数据集。

# prepare training data
trainX = encode_sequences(ger_tokenizer, ger_length, train[:, 1])
trainY = encode_sequences(eng_tokenizer, eng_length, train[:, 0])
trainY = encode_output(trainY, eng_vocab_size)
# prepare validation data
testX = encode_sequences(ger_tokenizer, ger_length, test[:, 1])
testY = encode_sequences(eng_tokenizer, eng_length, test[:, 0])
testY = encode_output(testY, eng_vocab_size)

我们现在准备定义模型。

我们将在这个问题上使用编解码器 LSTM 模型。在这种架构中,输入序列由称为编码器的前端模型编码,然后由称为解码器的后端模型逐字解码。

下面的函数define_model()定义了模型,并采用了许多用于配置模型的参数,例如输入和输出词汇的大小,输入和输出短语的最大长度以及数字用于配置模型的内存单元。

该模型使用有效的 Adam 方法训练随机梯度下降并最小化分类损失函数,因为我们将预测问题框定为多分类。

模型配置未针对此问题进行优化,这意味着您有足够的机会对其进行调整并提升翻译技能。我很想看看你能想出什么。

有关配置神经机器翻译模型的更多建议,请参阅帖子:

  • 如何为神经机器翻译配置编解码器模型
# define NMT model
def define_model(src_vocab, tar_vocab, src_timesteps, tar_timesteps, n_units):
	model = Sequential()
	model.add(Embedding(src_vocab, n_units, input_length=src_timesteps, mask_zero=True))
	model.add(LSTM(n_units))
	model.add(RepeatVector(tar_timesteps))
	model.add(LSTM(n_units, return_sequences=True))
	model.add(TimeDistributed(Dense(tar_vocab, activation='softmax')))
	return model

# define model
model = define_model(ger_vocab_size, eng_vocab_size, ger_length, eng_length, 256)
model.compile(optimizer='adam', loss='categorical_crossentropy')
# summarize defined model
print(model.summary())
plot_model(model, to_file='model.png', show_shapes=True)

最后,我们可以训练模型。

我们训练了 30 个时期的模型和 64 个样本的批量大小。

我们使用检查点来确保每次测试集上的模型技能得到改进时,模型都会保存到文件中。

# fit model
filename = 'model.h5'
checkpoint = ModelCheckpoint(filename, monitor='val_loss', verbose=1, save_best_only=True, mode='min')
model.fit(trainX, trainY, epochs=30, batch_size=64, validation_data=(testX, testY), callbacks=[checkpoint], verbose=2)

我们可以将所有这些结合在一起并适合神经翻译模型。

完整的工作示例如下所示。

from pickle import load
from numpy import array
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categorical
from keras.utils.vis_utils import plot_model
from keras.models import Sequential
from keras.layers import LSTM
from keras.layers import Dense
from keras.layers import Embedding
from keras.layers import RepeatVector
from keras.layers import TimeDistributed
from keras.callbacks import ModelCheckpoint

# load a clean dataset
def load_clean_sentences(filename):
	return load(open(filename, 'rb'))

# fit a tokenizer
def create_tokenizer(lines):
	tokenizer = Tokenizer()
	tokenizer.fit_on_texts(lines)
	return tokenizer

# max sentence length
def max_length(lines):
	return max(len(line.split()) for line in lines)

# encode and pad sequences
def encode_sequences(tokenizer, length, lines):
	# integer encode sequences
	X = tokenizer.texts_to_sequences(lines)
	# pad sequences with 0 values
	X = pad_sequences(X, maxlen=length, padding='post')
	return X

# one hot encode target sequence
def encode_output(sequences, vocab_size):
	ylist = list()
	for sequence in sequences:
		encoded = to_categorical(sequence, num_classes=vocab_size)
		ylist.append(encoded)
	y = array(ylist)
	y = y.reshape(sequences.shape[0], sequences.shape[1], vocab_size)
	return y

# define NMT model
def define_model(src_vocab, tar_vocab, src_timesteps, tar_timesteps, n_units):
	model = Sequential()
	model.add(Embedding(src_vocab, n_units, input_length=src_timesteps, mask_zero=True))
	model.add(LSTM(n_units))
	model.add(RepeatVector(tar_timesteps))
	model.add(LSTM(n_units, return_sequences=True))
	model.add(TimeDistributed(Dense(tar_vocab, activation='softmax')))
	return model

# load datasets
dataset = load_clean_sentences('english-german-both.pkl')
train = load_clean_sentences('english-german-train.pkl')
test = load_clean_sentences('english-german-test.pkl')

# prepare english tokenizer
eng_tokenizer = create_tokenizer(dataset[:, 0])
eng_vocab_size = len(eng_tokenizer.word_index) + 1
eng_length = max_length(dataset[:, 0])
print('English Vocabulary Size: %d' % eng_vocab_size)
print('English Max Length: %d' % (eng_length))
# prepare german tokenizer
ger_tokenizer = create_tokenizer(dataset[:, 1])
ger_vocab_size = len(ger_tokenizer.word_index) + 1
ger_length = max_length(dataset[:, 1])
print('German Vocabulary Size: %d' % ger_vocab_size)
print('German Max Length: %d' % (ger_length))

# prepare training data
trainX = encode_sequences(ger_tokenizer, ger_length, train[:, 1])
trainY = encode_sequences(eng_tokenizer, eng_length, train[:, 0])
trainY = encode_output(trainY, eng_vocab_size)
# prepare validation data
testX = encode_sequences(ger_tokenizer, ger_length, test[:, 1])
testY = encode_sequences(eng_tokenizer, eng_length, test[:, 0])
testY = encode_output(testY, eng_vocab_size)

# define model
model = define_model(ger_vocab_size, eng_vocab_size, ger_length, eng_length, 256)
model.compile(optimizer='adam', loss='categorical_crossentropy')
# summarize defined model
print(model.summary())
plot_model(model, to_file='model.png', show_shapes=True)
# fit model
filename = 'model.h5'
checkpoint = ModelCheckpoint(filename, monitor='val_loss', verbose=1, save_best_only=True, mode='min')
model.fit(trainX, trainY, epochs=30, batch_size=64, validation_data=(testX, testY), callbacks=[checkpoint], verbose=2)

首先运行该示例将打印数据集参数的摘要,例如词汇表大小和最大短语长度。

English Vocabulary Size: 2404
English Max Length: 5
German Vocabulary Size: 3856
German Max Length: 10

接下来,打印已定义模型的摘要,允许我们确认模型配置。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
embedding_1 (Embedding)      (None, 10, 256)           987136
_________________________________________________________________
lstm_1 (LSTM)                (None, 256)               525312
_________________________________________________________________
repeat_vector_1 (RepeatVecto (None, 5, 256)            0
_________________________________________________________________
lstm_2 (LSTM)                (None, 5, 256)            525312
_________________________________________________________________
time_distributed_1 (TimeDist (None, 5, 2404)           617828
=================================================================
Total params: 2,655,588
Trainable params: 2,655,588
Non-trainable params: 0
_________________________________________________________________

还创建了模型图,提供了模型配置的另一个视角。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

NMT 模型图的图

接下来,训练模型。

在现代 CPU 硬件上,每个时期大约需要 30 秒;不需要 GPU。

在运行期间,模型将保存到文件model.h5,准备在下一步中进行推理。

...
Epoch 26/30
Epoch 00025: val_loss improved from 2.20048 to 2.19976, saving model to model.h5
17s - loss: 0.7114 - val_loss: 2.1998
Epoch 27/30
Epoch 00026: val_loss improved from 2.19976 to 2.18255, saving model to model.h5
17s - loss: 0.6532 - val_loss: 2.1826
Epoch 28/30
Epoch 00027: val_loss did not improve
17s - loss: 0.5970 - val_loss: 2.1970
Epoch 29/30
Epoch 00028: val_loss improved from 2.18255 to 2.17872, saving model to model.h5
17s - loss: 0.5474 - val_loss: 2.1787
Epoch 30/30
Epoch 00029: val_loss did not improve
17s - loss: 0.5023 - val_loss: 2.1823

评估神经翻译模型

我们将评估训练上的模型和测试数据集。

该模型应该在训练数据集上表现很好,并且理想情况下已被推广以在测试数据集上表现良好。

理想情况下,我们将使用单独的验证数据集来帮助在训练期间选择模型而不是测试集。您可以尝试将其作为扩展名。

必须像以前一样加载和准备干净的数据集。

...
# load datasets
dataset = load_clean_sentences('english-german-both.pkl')
train = load_clean_sentences('english-german-train.pkl')
test = load_clean_sentences('english-german-test.pkl')
# prepare english tokenizer
eng_tokenizer = create_tokenizer(dataset[:, 0])
eng_vocab_size = len(eng_tokenizer.word_index) + 1
eng_length = max_length(dataset[:, 0])
# prepare german tokenizer
ger_tokenizer = create_tokenizer(dataset[:, 1])
ger_vocab_size = len(ger_tokenizer.word_index) + 1
ger_length = max_length(dataset[:, 1])
# prepare data
trainX = encode_sequences(ger_tokenizer, ger_length, train[:, 1])
testX = encode_sequences(ger_tokenizer, ger_length, test[:, 1])

接下来,必须加载训练期间保存的最佳模型。

# load model
model = load_model('model.h5')

评估涉及两个步骤:首先生成翻译的输出序列,然后针对许多输入示例重复此过程,并在多个案例中总结模型的技能。

从推理开始,模型可以以一次性方式预测整个输出序列。

translation = model.predict(source, verbose=0)

这将是一个整数序列,我们可以在 tokenizer 中枚举和查找以映射回单词。

以下函数名为 word_for_id(),将执行此反向映射。

# map an integer to a word
def word_for_id(integer, tokenizer):
	for word, index in tokenizer.word_index.items():
		if index == integer:
			return word
	return None

我们可以为转换中的每个整数执行此映射,并将结果作为一个单词串返回。

下面的函数predict_sequence()对单个编码的源短语执行此操作。

# generate target given source sequence
def predict_sequence(model, tokenizer, source):
	prediction = model.predict(source, verbose=0)[0]
	integers = [argmax(vector) for vector in prediction]
	target = list()
	for i in integers:
		word = word_for_id(i, tokenizer)
		if word is None:
			break
		target.append(word)
	return ' '.join(target)

接下来,我们可以对数据集中的每个源短语重复此操作,并将预测结果与英语中的预期目标短语进行比较。

我们可以将这些比较中的一些打印到屏幕上,以了解模型在实践中的表现。

我们还将计算 BLEU 分数,以获得模型表现良好的定量概念。

您可以在此处了解有关 BLEU 分数的更多信息:

  • 计算 Python 中文本的 BLEU 分数的温和介绍

下面的evaluate_model()函数实现了这一点,为提供的数据集中的每个短语调用上述predict_sequence()函数。

# evaluate the skill of the model
def evaluate_model(model, tokenizer, sources, raw_dataset):
	actual, predicted = list(), list()
	for i, source in enumerate(sources):
		# translate encoded source text
		source = source.reshape((1, source.shape[0]))
		translation = predict_sequence(model, eng_tokenizer, source)
		raw_target, raw_src = raw_dataset[i]
		if i < 10:
			print('src=[%s], target=[%s], predicted=[%s]' % (raw_src, raw_target, translation))
		actual.append(raw_target.split())
		predicted.append(translation.split())
	# calculate BLEU score
	print('BLEU-1: %f' % corpus_bleu(actual, predicted, weights=(1.0, 0, 0, 0)))
	print('BLEU-2: %f' % corpus_bleu(actual, predicted, weights=(0.5, 0.5, 0, 0)))
	print('BLEU-3: %f' % corpus_bleu(actual, predicted, weights=(0.3, 0.3, 0.3, 0)))
	print('BLEU-4: %f' % corpus_bleu(actual, predicted, weights=(0.25, 0.25, 0.25, 0.25)))

我们可以将所有这些结合在一起,并在训练和测试数据集上评估加载的模型。

完整的代码清单如下。

from pickle import load
from numpy import array
from numpy import argmax
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.models import load_model
from nltk.translate.bleu_score import corpus_bleu

# load a clean dataset
def load_clean_sentences(filename):
	return load(open(filename, 'rb'))

# fit a tokenizer
def create_tokenizer(lines):
	tokenizer = Tokenizer()
	tokenizer.fit_on_texts(lines)
	return tokenizer

# max sentence length
def max_length(lines):
	return max(len(line.split()) for line in lines)

# encode and pad sequences
def encode_sequences(tokenizer, length, lines):
	# integer encode sequences
	X = tokenizer.texts_to_sequences(lines)
	# pad sequences with 0 values
	X = pad_sequences(X, maxlen=length, padding='post')
	return X

# map an integer to a word
def word_for_id(integer, tokenizer):
	for word, index in tokenizer.word_index.items():
		if index == integer:
			return word
	return None

# generate target given source sequence
def predict_sequence(model, tokenizer, source):
	prediction = model.predict(source, verbose=0)[0]
	integers = [argmax(vector) for vector in prediction]
	target = list()
	for i in integers:
		word = word_for_id(i, tokenizer)
		if word is None:
			break
		target.append(word)
	return ' '.join(target)

# evaluate the skill of the model
def evaluate_model(model, tokenizer, sources, raw_dataset):
	actual, predicted = list(), list()
	for i, source in enumerate(sources):
		# translate encoded source text
		source = source.reshape((1, source.shape[0]))
		translation = predict_sequence(model, eng_tokenizer, source)
		raw_target, raw_src = raw_dataset[i]
		if i < 10:
			print('src=[%s], target=[%s], predicted=[%s]' % (raw_src, raw_target, translation))
		actual.append(raw_target.split())
		predicted.append(translation.split())
	# calculate BLEU score
	print('BLEU-1: %f' % corpus_bleu(actual, predicted, weights=(1.0, 0, 0, 0)))
	print('BLEU-2: %f' % corpus_bleu(actual, predicted, weights=(0.5, 0.5, 0, 0)))
	print('BLEU-3: %f' % corpus_bleu(actual, predicted, weights=(0.3, 0.3, 0.3, 0)))
	print('BLEU-4: %f' % corpus_bleu(actual, predicted, weights=(0.25, 0.25, 0.25, 0.25)))

# load datasets
dataset = load_clean_sentences('english-german-both.pkl')
train = load_clean_sentences('english-german-train.pkl')
test = load_clean_sentences('english-german-test.pkl')
# prepare english tokenizer
eng_tokenizer = create_tokenizer(dataset[:, 0])
eng_vocab_size = len(eng_tokenizer.word_index) + 1
eng_length = max_length(dataset[:, 0])
# prepare german tokenizer
ger_tokenizer = create_tokenizer(dataset[:, 1])
ger_vocab_size = len(ger_tokenizer.word_index) + 1
ger_length = max_length(dataset[:, 1])
# prepare data
trainX = encode_sequences(ger_tokenizer, ger_length, train[:, 1])
testX = encode_sequences(ger_tokenizer, ger_length, test[:, 1])

# load model
model = load_model('model.h5')
# test on some training sequences
print('train')
evaluate_model(model, eng_tokenizer, trainX, train)
# test on some test sequences
print('test')
evaluate_model(model, eng_tokenizer, testX, test)

首先运行示例打印源文本,预期和预测翻译的示例,以及训练数据集的分数,然后是测试数据集。

鉴于数据集的随机改组和神经网络的随机性,您的具体结果会有所不同。

首先查看测试数据集的结果,我们可以看到翻译是可读的并且大部分都是正确的。

例如:“ ich liebe dich ”被正确翻译为“_ 我爱你 _”。

我们还可以看到翻译并不完美,“ ich konnte nicht gehen ”翻译为“_ 我不能 ”而不是预期的“ 我无法行走 _ ]“。

我们还可以看到 BLEU-4 得分为 0.51,它提供了我们对此模型的预期上限。

src=[ich liebe dich], target=[i love you], predicted=[i love you]
src=[ich sagte du sollst den mund halten], target=[i said shut up], predicted=[i said stop up]
src=[wie geht es eurem vater], target=[hows your dad], predicted=[hows your dad]
src=[das gefallt mir], target=[i like that], predicted=[i like that]
src=[ich gehe immer zu fu], target=[i always walk], predicted=[i will to]
src=[ich konnte nicht gehen], target=[i couldnt walk], predicted=[i cant go]
src=[er ist sehr jung], target=[he is very young], predicted=[he is very young]
src=[versucht es doch einfach], target=[just try it], predicted=[just try it]
src=[sie sind jung], target=[youre young], predicted=[youre young]
src=[er ging surfen], target=[he went surfing], predicted=[he went surfing]

BLEU-1: 0.085682
BLEU-2: 0.284191
BLEU-3: 0.459090
BLEU-4: 0.517571

查看测试集上的结果,确实看到可读的翻译,这不是一件容易的事。

例如,我们看到“ ich mag dich nicht ”正确翻译为“_ 我不喜欢你 _”。

我们还看到一些不良的翻译以及该模型可能受到进一步调整的好例子,例如“ ich bin etwas beschwipst ”翻译为“ ia bit bit ”而不是预期“_ 我有点醉了 _”

BLEU-4 得分为 0.076238,提供了基线技能,可以进一步改进模型。

src=[tom erblasste], target=[tom turned pale], predicted=[tom went pale]
src=[bring mich nach hause], target=[take me home], predicted=[let us at]
src=[ich bin etwas beschwipst], target=[im a bit tipsy], predicted=[i a bit bit]
src=[das ist eine frucht], target=[its a fruit], predicted=[thats a a]
src=[ich bin pazifist], target=[im a pacifist], predicted=[im am]
src=[unser plan ist aufgegangen], target=[our plan worked], predicted=[who is a man]
src=[hallo tom], target=[hi tom], predicted=[hello tom]
src=[sei nicht nervos], target=[dont be nervous], predicted=[dont be crazy]
src=[ich mag dich nicht], target=[i dont like you], predicted=[i dont like you]
src=[tom stellte eine falle], target=[tom set a trap], predicted=[tom has a cough]

BLEU-1: 0.082088
BLEU-2: 0.006182
BLEU-3: 0.046129
BLEU-4: 0.076238

扩展

本节列出了一些扩展您可能希望探索的教程的想法。

  • 数据清理。可以对数据执行不同的数据清理操作,例如不删除标点符号或标准化案例,或者可能删除重复的英语短语。
  • 词汇。可以改进词汇表,可能删除在数据集中使用少于 5 或 10 次的单词并替换为“unk”。
  • 更多数据。用于拟合模型的数据集可以扩展到 50,000,1000 个短语或更多。
  • 输入订单。输入短语的顺序可以颠倒,据报道可提升技能,或者可以使用双向输入层。
  • 。编码器和/或解码器模型可以通过附加层进行扩展,并针对更多时期进行训练,从而为模型提供更多的代表表现力。
  • 单位。可以增加编码器和解码器中的存储器单元的数量,从而为模型提供更多的代表性容量。
  • 正规化。该模型可以使用正则化,例如权重或激活正则化,或在 LSTM 层上使用压差。
  • 预训练的单词向量。可以在模型中使用预训练的单词向量。
  • 递归模型。可以使用模型的递归公式,其中输出序列中的下一个字可以以输入序列和到目前为止生成的输出序列为条件。

进一步阅读

如果您希望深入了解,本节将提供有关该主题的更多资源。

  • 制表符分隔的双语句子对
  • 德语 - 英语 deu-eng.zip
  • 编解码器长短期记忆网络

摘要

在本教程中,您了解了如何开发用于将德语短语翻译成英语的神经机器翻译系统。

具体来说,你学到了:

  • 如何清理和准备数据准备训练神经机器翻译系统。
  • 如何开发机器翻译的编解码器模型。
  • 如何使用训练有素的模型推断新的输入短语并评估模型技巧。

你有任何问题吗?
在下面的评论中提出您的问题,我会尽力回答。

:这篇文章摘录自:“深度学习自然语言处理”。看一下,如果您想要在使用文本数据时获得有关深入学习方法的更多分步教程。

如何用 Python 和 Keras 开发基于单词的神经语言模型

原文: machinelearningmastery.com/develop-word-based-neural-language-models-python-keras/

语言建模涉及在已经存在的单词序列的情况下预测序列中的下一个单词。

语言模型是许多自然语言处理模型中的关键元素,例如机器翻译和语音识别。语言模型的框架选择必须与语言模型的使用方式相匹配。

在本教程中,您将了解在从童谣中生成短序列时,语言模型的框架如何影响模型的技能。

完成本教程后,您将了解:

  • 为给定的应用程序开发基于单词的语言模型的良好框架的挑战。
  • 如何为基于单词的语言模型开发单字,双字和基于行的框架。
  • 如何使用拟合语言模型生成序列。

让我们开始吧。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如何使用 Keras 在 Python 中开发基于 Word 的神经语言模型
照片由 Stephanie Chapman 保留,保留一些权利。

教程概述

本教程分为 5 个部分;他们是:

  1. 框架语言建模
  2. 杰克和吉尔童谣
  3. 模型 1:单字输入,单字输出序列
  4. 模型 2:逐行序列
  5. 模型 3:双字输入,单字输出序列

框架语言建模

从原始文本中学习统计语言模型,并且在给定已经存在于序列中的单词的情况下预测序列中下一个单词的概率。

语言模型是大型模型中的关键组件,用于挑战自然语言处理问题,如机器翻译和语音识别。它们也可以作为独立模型开发,并用于生成与源文本具有相同统计属性的新序列。

语言模型一次学习和预测一个单词。网络的训练涉及提供单词序列作为输入,每次处理一个单词,其中可以为每个输入序列做出预测和学习。

类似地,在做出预测时,可以用一个或几个单词播种该过程,然后可以收集预测的单词并将其作为后续预测的输入呈现,以便建立生成的输出序列

因此,每个模型将涉及将源文本分成输入和输出序列,使得模型可以学习预测单词。

有许多方法可以从源文本中构建序列以进行语言建模。

在本教程中,我们将探讨在 Keras 深度学习库中开发基于单词的语言模型的 3 种不同方法。

没有单一的最佳方法,只是可能适合不同应用的不同框架。

杰克和吉尔童谣

杰克和吉尔是一个简单的童谣。

它由 4 行组成,如下所示:

杰克和吉尔上山
去取一桶水
杰克摔倒了,打破了他的王冠
吉尔跌倒了之后

我们将使用它作为我们的源文本来探索基于单词的语言模型的不同框架。

我们可以在 Python 中定义这个文本如下:

# source text
data = """ Jack and Jill went up the hill\n To fetch a pail of water\n Jack fell down and broke his crown\n And Jill came tumbling after\n """

模型 1:单字输入,单字输出序列

我们可以从一个非常简单的模型开始。

给定一个单词作为输入,模型将学习预测序列中的下一个单词。

例如:

X,		y
Jack, 	and
and,	Jill
Jill,	went
...

第一步是将文本编码为整数。

源文本中的每个小写字都被赋予一个唯一的整数,我们可以将单词序列转换为整数序列。

Keras 提供了 Tokenizer 类,可用于执行此编码。首先,Tokenizer 适合源文本,以开发从单词到唯一整数的映射。然后通过调用texts_to_sequences()函数将文本序列转换为整数序列。

# integer encode text
tokenizer = Tokenizer()
tokenizer.fit_on_texts([data])
encoded = tokenizer.texts_to_sequences([data])[0]

我们稍后需要知道词汇表的大小,以便在模型中定义单词嵌入层,以及使用单热编码对输出单词进行编码。

通过访问word_index属性,可以从训练好的 Tokenizer 中检索词汇表的大小。

# determine the vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary Size: %d' % vocab_size)

运行这个例子,我们可以看到词汇量的大小是 21 个单词。

我们添加一个,因为我们需要将最大编码字的整数指定为数组索引,例如单词编码 1 到 21,数组指示 0 到 21 或 22 个位置。

接下来,我们需要创建单词序列以适合模型,其中一个单词作为输入,一个单词作为输出。

# create word -> word sequences
sequences = list()
for i in range(1, len(encoded)):
	sequence = encoded[i-1:i+1]
	sequences.append(sequence)
print('Total Sequences: %d' % len(sequences))

运行这一部分表明我们总共有 24 个输入输出对来训练网络。

Total Sequences: 24

然后我们可以将序列分成输入(X)和输出元素(y)。这很简单,因为我们在数据中只有两列。

# split into X and y elements
sequences = array(sequences)
X, y = sequences[:,0],sequences[:,1]

我们将使用我们的模型来预测词汇表中所有单词的概率分布。这意味着我们需要将输出元素从单个整数转换为单热编码,对于词汇表中的每个单词都为 0,对于值的实际单词为 1。这为网络提供了一个基本事实,我们可以从中计算错误并更新模型。

Keras 提供to_categorical()函数,我们可以使用它将整数转换为单热编码,同时指定类的数量作为词汇表大小。

# one hot encode outputs
y = to_categorical(y, num_classes=vocab_size)

我们现在准备定义神经网络模型。

该模型使用嵌入在输入层中的学习单词。这对于词汇表中的每个单词具有一个实值向量,其中每个单词向量具有指定的长度。在这种情况下,我们将使用 10 维投影。输入序列包含单个字,因此 input_length = 1

该模型具有单个隐藏的 LSTM 层,具有 50 个单元。这远远超过了需要。输出层由词汇表中每个单词的一个神经元组成,并使用 softmax 激活函数来确保输出被标准化为看起来像概率。

# define model
model = Sequential()
model.add(Embedding(vocab_size, 10, input_length=1))
model.add(LSTM(50))
model.add(Dense(vocab_size, activation='softmax'))
print(model.summary())

网络结构可归纳如下:

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
embedding_1 (Embedding)      (None, 1, 10)             220
_________________________________________________________________
lstm_1 (LSTM)                (None, 50)                12200
_________________________________________________________________
dense_1 (Dense)              (None, 22)                1122
=================================================================
Total params: 13,542
Trainable params: 13,542
Non-trainable params: 0
_________________________________________________________________

对于本教程中的每个示例,我们将使用相同的通用网络结构,对学习的嵌入层进行微小更改。

接下来,我们可以在编码的文本数据上编译和拟合网络。从技术上讲,我们正在建模一个多分类问题(预测词汇表中的单词),因此使用分类交叉熵损失函数。我们在每个时代结束时使用有效的 Adam 实现梯度下降和跟踪精度。该模型适用于 500 个训练时期,也许比需要更多。

网络配置没有针对此和后续实验进行调整;选择了一个过度规定的配置,以确保我们可以专注于语言模型的框架。

# compile network
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(X, y, epochs=500, verbose=2)

在模型拟合之后,我们通过从词汇表中传递给定的单词并让模型预测下一个单词来测试它。在这里我们通过编码传递’Jack’并调用model.predict_classes()来获得预测单词的整数输出。然后在词汇表映射中查找,以提供相关的单词。

# evaluate
in_text = 'Jack'
print(in_text)
encoded = tokenizer.texts_to_sequences([in_text])[0]
encoded = array(encoded)
yhat = model.predict_classes(encoded, verbose=0)
for word, index in tokenizer.word_index.items():
	if index == yhat:
		print(word)

然后可以重复该过程几次以建立生成的单词序列。

为了使这更容易,我们将函数包含在一个函数中,我们可以通过传入模型和种子字来调用它。

# generate a sequence from the model
def generate_seq(model, tokenizer, seed_text, n_words):
	in_text, result = seed_text, seed_text
	# generate a fixed number of words
	for _ in range(n_words):
		# encode the text as integer
		encoded = tokenizer.texts_to_sequences([in_text])[0]
		encoded = array(encoded)
		# predict a word in the vocabulary
		yhat = model.predict_classes(encoded, verbose=0)
		# map predicted word index to word
		out_word = ''
		for word, index in tokenizer.word_index.items():
			if index == yhat:
				out_word = word
				break
		# append to input
		in_text, result = out_word, result + ' ' + out_word
	return result

我们可以把所有这些放在一起。完整的代码清单如下。

from numpy import array
from keras.preprocessing.text import Tokenizer
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.layers import Embedding

# generate a sequence from the model
def generate_seq(model, tokenizer, seed_text, n_words):
	in_text, result = seed_text, seed_text
	# generate a fixed number of words
	for _ in range(n_words):
		# encode the text as integer
		encoded = tokenizer.texts_to_sequences([in_text])[0]
		encoded = array(encoded)
		# predict a word in the vocabulary
		yhat = model.predict_classes(encoded, verbose=0)
		# map predicted word index to word
		out_word = ''
		for word, index in tokenizer.word_index.items():
			if index == yhat:
				out_word = word
				break
		# append to input
		in_text, result = out_word, result + ' ' + out_word
	return result

# source text
data = """ Jack and Jill went up the hill\n To fetch a pail of water\n Jack fell down and broke his crown\n And Jill came tumbling after\n """
# integer encode text
tokenizer = Tokenizer()
tokenizer.fit_on_texts([data])
encoded = tokenizer.texts_to_sequences([data])[0]
# determine the vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary Size: %d' % vocab_size)
# create word -> word sequences
sequences = list()
for i in range(1, len(encoded)):
	sequence = encoded[i-1:i+1]
	sequences.append(sequence)
print('Total Sequences: %d' % len(sequences))
# split into X and y elements
sequences = array(sequences)
X, y = sequences[:,0],sequences[:,1]
# one hot encode outputs
y = to_categorical(y, num_classes=vocab_size)
# define model
model = Sequential()
model.add(Embedding(vocab_size, 10, input_length=1))
model.add(LSTM(50))
model.add(Dense(vocab_size, activation='softmax'))
print(model.summary())
# compile network
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(X, y, epochs=500, verbose=2)
# evaluate
print(generate_seq(model, tokenizer, 'Jack', 6))

运行该示例打印每个训练时期的损失和准确率。

...
Epoch 496/500
0s - loss: 0.2358 - acc: 0.8750
Epoch 497/500
0s - loss: 0.2355 - acc: 0.8750
Epoch 498/500
0s - loss: 0.2352 - acc: 0.8750
Epoch 499/500
0s - loss: 0.2349 - acc: 0.8750
Epoch 500/500
0s - loss: 0.2346 - acc: 0.8750

我们可以看到模型没有记住源序列,可能是因为输入序列中存在一些模糊性,例如:

jack => and
jack => fell

等等。

在运行结束时,传入’Jack’并生成预测或新序列。

我们得到一个合理的序列作为输出,它有一些源的元素。

Jack and jill came tumbling after down

这是一个很好的第一个切割语言模型,但没有充分利用 LSTM 处理输入序列的能力,并通过使用更广泛的上下文消除一些模糊的成对序列的歧义。

模型 2:逐行序列

另一种方法是逐行分割源文本,然后将每一行分解为一系列构建的单词。

例如:

X,									y
_, _, _, _, _, Jack, 				and
_, _, _, _, Jack, and 				Jill
_, _, _, Jack, and, Jill,			went
_, _, Jack, and, Jill, went,		up
_, Jack, and, Jill, went, up,		the
Jack, and, Jill, went, up, the,		hill

这种方法可以允许模型在一个简单的单字输入和输出模型产生歧义的情况下使用每一行的上下文来帮助模型。

在这种情况下,这是以跨行预测单词为代价的,如果我们只对建模和生成文本行感兴趣,那么现在可能没问题。

请注意,在此表示中,我们将需要填充序列以确保它们满足固定长度输入。这是使用 Keras 时的要求。

首先,我们可以使用已经适合源文本的 Tokenizer 逐行创建整数序列。

# create line-based sequences
sequences = list()
for line in data.split('\n'):
	encoded = tokenizer.texts_to_sequences([line])[0]
	for i in range(1, len(encoded)):
		sequence = encoded[:i+1]
		sequences.append(sequence)
print('Total Sequences: %d' % len(sequences))

接下来,我们可以填充准备好的序列。我们可以使用 Keras 中提供的 pad_sequences()函数来完成此操作。这首先涉及找到最长的序列,然后使用它作为填充所有其他序列的长度。

# pad input sequences
max_length = max([len(seq) for seq in sequences])
sequences = pad_sequences(sequences, maxlen=max_length, padding='pre')
print('Max Sequence Length: %d' % max_length)

接下来,我们可以将序列拆分为输入和输出元素,就像之前一样。

# split into input and output elements
sequences = array(sequences)
X, y = sequences[:,:-1],sequences[:,-1]
y = to_categorical(y, num_classes=vocab_size)

然后可以像之前一样定义模型,除了输入序列现在比单个字长。具体来说,它们的长度为 max_length-1 ,-1 因为当我们计算序列的最大长度时,它们包括输入和输出元素。

# define model
model = Sequential()
model.add(Embedding(vocab_size, 10, input_length=max_length-1))
model.add(LSTM(50))
model.add(Dense(vocab_size, activation='softmax'))
print(model.summary())
# compile network
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(X, y, epochs=500, verbose=2)

我们可以像以前一样使用该模型生成新序列。通过在每次迭代中将预测添加到输入词列表中,可以更新generate_seq()函数以建立输入序列。

# generate a sequence from a language model
def generate_seq(model, tokenizer, max_length, seed_text, n_words):
	in_text = seed_text
	# generate a fixed number of words
	for _ in range(n_words):
		# encode the text as integer
		encoded = tokenizer.texts_to_sequences([in_text])[0]
		# pre-pad sequences to a fixed length
		encoded = pad_sequences([encoded], maxlen=max_length, padding='pre')
		# predict probabilities for each word
		yhat = model.predict_classes(encoded, verbose=0)
		# map predicted word index to word
		out_word = ''
		for word, index in tokenizer.word_index.items():
			if index == yhat:
				out_word = word
				break
		# append to input
		in_text += ' ' + out_word
	return in_text

将所有这些结合在一起,下面提供了完整的代码示例。

from numpy import array
from keras.preprocessing.text import Tokenizer
from keras.utils import to_categorical
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.layers import Embedding

# generate a sequence from a language model
def generate_seq(model, tokenizer, max_length, seed_text, n_words):
	in_text = seed_text
	# generate a fixed number of words
	for _ in range(n_words):
		# encode the text as integer
		encoded = tokenizer.texts_to_sequences([in_text])[0]
		# pre-pad sequences to a fixed length
		encoded = pad_sequences([encoded], maxlen=max_length, padding='pre')
		# predict probabilities for each word
		yhat = model.predict_classes(encoded, verbose=0)
		# map predicted word index to word
		out_word = ''
		for word, index in tokenizer.word_index.items():
			if index == yhat:
				out_word = word
				break
		# append to input
		in_text += ' ' + out_word
	return in_text

# source text
data = """ Jack and Jill went up the hill\n To fetch a pail of water\n Jack fell down and broke his crown\n And Jill came tumbling after\n """
# prepare the tokenizer on the source text
tokenizer = Tokenizer()
tokenizer.fit_on_texts([data])
# determine the vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary Size: %d' % vocab_size)
# create line-based sequences
sequences = list()
for line in data.split('\n'):
	encoded = tokenizer.texts_to_sequences([line])[0]
	for i in range(1, len(encoded)):
		sequence = encoded[:i+1]
		sequences.append(sequence)
print('Total Sequences: %d' % len(sequences))
# pad input sequences
max_length = max([len(seq) for seq in sequences])
sequences = pad_sequences(sequences, maxlen=max_length, padding='pre')
print('Max Sequence Length: %d' % max_length)
# split into input and output elements
sequences = array(sequences)
X, y = sequences[:,:-1],sequences[:,-1]
y = to_categorical(y, num_classes=vocab_size)
# define model
model = Sequential()
model.add(Embedding(vocab_size, 10, input_length=max_length-1))
model.add(LSTM(50))
model.add(Dense(vocab_size, activation='softmax'))
print(model.summary())
# compile network
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(X, y, epochs=500, verbose=2)
# evaluate model
print(generate_seq(model, tokenizer, max_length-1, 'Jack', 4))
print(generate_seq(model, tokenizer, max_length-1, 'Jill', 4))

运行该示例可以更好地适应源数据。添加的上下文允许模型消除一些示例的歧义。

仍有两行文字以“Jack”开头,可能仍然是网络的问题。

...
Epoch 496/500
0s - loss: 0.1039 - acc: 0.9524
Epoch 497/500
0s - loss: 0.1037 - acc: 0.9524
Epoch 498/500
0s - loss: 0.1035 - acc: 0.9524
Epoch 499/500
0s - loss: 0.1033 - acc: 0.9524
Epoch 500/500
0s - loss: 0.1032 - acc: 0.9524

在运行结束时,我们生成两个具有不同种子词的序列:‘Jack’和’Jill’。

第一个生成的行看起来很好,直接匹配源文本。第二个有点奇怪。这是有道理的,因为网络只在输入序列中看到’Jill’,而不是在序列的开头,所以它强制输出使用’Jill这个词’,即押韵的最后一行。

Jack fell down and broke
Jill jill came tumbling after

这是一个很好的例子,说明框架可能如何产生更好的新线条,但不是良好的部分输入线条。

模型 3:双字输入,单字输出序列

我们可以使用单词输入和全句子方法之间的中间,并传入单词的子序列作为输入。

这将在两个框架之间进行权衡,允许生成新线并在中线拾取生成。

我们将使用 3 个单词作为输入来预测一个单词作为输出。序列的准备与第一个示例非常相似,只是源序列数组中的偏移量不同,如下所示:

# encode 2 words -> 1 word
sequences = list()
for i in range(2, len(encoded)):
	sequence = encoded[i-2:i+1]
	sequences.append(sequence)

下面列出了完整的示例

from numpy import array
from keras.preprocessing.text import Tokenizer
from keras.utils import to_categorical
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
from keras.layers import Embedding

# generate a sequence from a language model
def generate_seq(model, tokenizer, max_length, seed_text, n_words):
	in_text = seed_text
	# generate a fixed number of words
	for _ in range(n_words):
		# encode the text as integer
		encoded = tokenizer.texts_to_sequences([in_text])[0]
		# pre-pad sequences to a fixed length
		encoded = pad_sequences([encoded], maxlen=max_length, padding='pre')
		# predict probabilities for each word
		yhat = model.predict_classes(encoded, verbose=0)
		# map predicted word index to word
		out_word = ''
		for word, index in tokenizer.word_index.items():
			if index == yhat:
				out_word = word
				break
		# append to input
		in_text += ' ' + out_word
	return in_text

# source text
data = """ Jack and Jill went up the hill\n To fetch a pail of water\n Jack fell down and broke his crown\n And Jill came tumbling after\n """
# integer encode sequences of words
tokenizer = Tokenizer()
tokenizer.fit_on_texts([data])
encoded = tokenizer.texts_to_sequences([data])[0]
# retrieve vocabulary size
vocab_size = len(tokenizer.word_index) + 1
print('Vocabulary Size: %d' % vocab_size)
# encode 2 words -> 1 word
sequences = list()
for i in range(2, len(encoded)):
	sequence = encoded[i-2:i+1]
	sequences.append(sequence)
print('Total Sequences: %d' % len(sequences))
# pad sequences
max_length = max([len(seq) for seq in sequences])
sequences = pad_sequences(sequences, maxlen=max_length, padding='pre')
print('Max Sequence Length: %d' % max_length)
# split into input and output elements
sequences = array(sequences)
X, y = sequences[:,:-1],sequences[:,-1]
y = to_categorical(y, num_classes=vocab_size)
# define model
model = Sequential()
model.add(Embedding(vocab_size, 10, input_length=max_length-1))
model.add(LSTM(50))
model.add(Dense(vocab_size, activation='softmax'))
print(model.summary())
# compile network
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(X, y, epochs=500, verbose=2)
# evaluate model
print(generate_seq(model, tokenizer, max_length-1, 'Jack and', 5))
print(generate_seq(model, tokenizer, max_length-1, 'And Jill', 3))
print(generate_seq(model, tokenizer, max_length-1, 'fell down', 5))
print(generate_seq(model, tokenizer, max_length-1, 'pail of', 5))

再次运行示例可以很好地适应源文本,准确度大约为 95%。

...
Epoch 496/500
0s - loss: 0.0685 - acc: 0.9565
Epoch 497/500
0s - loss: 0.0685 - acc: 0.9565
Epoch 498/500
0s - loss: 0.0684 - acc: 0.9565
Epoch 499/500
0s - loss: 0.0684 - acc: 0.9565
Epoch 500/500
0s - loss: 0.0684 - acc: 0.9565

我们看一下 4 代示例,两个线路起始线和两个起始中线。

Jack and jill went up the hill
And Jill went up the
fell down and broke his crown and
pail of water jack fell down and

第一次启动行案例正确生成,但第二次没有生成。第二种情况是第 4 行的一个例子,它与第一行的内容含糊不清。也许进一步扩展到 3 个输入单词会更好。

正确生成了两个中线生成示例,与源文本匹配。

我们可以看到,语言模型的框架选择以及模型的使用要求必须兼容。一般情况下使用语言模型时需要仔细设计,或许通过序列生成进行现场测试,以确认模型要求已得到满足。

扩展

本节列出了一些扩展您可能希望探索的教程的想法。

  • 全韵序列。考虑更新上述示例中的一个以构建整个押韵作为输入序列。该模型应该能够在给定第一个单词的种子的情况下生成整个事物,并证明这一点。
  • 预训练嵌入。在嵌入中使用预先训练的单词向量进行探索,而不是将嵌入作为模型的一部分进行学习。这样一个小的源文本不需要这样做,但可能是一个好习惯。
  • 角色模型。探索使用基于字符的语言模型来源文本而不是本教程中演示的基于单词的方法。

进一步阅读

如果您要深入了解,本节将提供有关该主题的更多资源。

  • 杰克和吉尔在维基百科
  • 维基百科上的语言模型
  • Keras 嵌入层 API
  • Keras 文本处理 API
  • Keras 序列处理 API
  • Keras Utils API

摘要

在本教程中,您了解了如何为简单的童谣开发不同的基于单词的语言模型。

具体来说,你学到了:

  • 为给定的应用程序开发基于单词的语言模型的良好框架的挑战。
  • 如何为基于单词的语言模型开发单字,双字和基于行的框架。
  • 如何使用拟合语言模型生成序列。

你有任何问题吗?
在下面的评论中提出您的问题,我会尽力回答。

如何开发一种预测电影评论情感的词嵌入模型

原文: machinelearningmastery.com/develop-word-embedding-model-predicting-movie-review-sentiment/

开发一个深度学习模型,使用 Keras 自动将电影评论

分类为 Python 中的正面或负面,一步一步。

单词嵌入是用于表示文本的技术,其中具有相似含义的不同单词具有类似的实值向量表示。

它们是一项重大突破,它使神经网络模型在一系列具有挑战性的自然语言处理问题上表现出色。

在本教程中,您将了解如何为神经网络开发单词嵌入模型以对电影评论进行分类。

完成本教程后,您将了解:

  • 如何使用深度学习方法准备电影评论文本数据进行分类。
  • 如何学习嵌入词作为拟合深度学习模型的一部分。
  • 如何学习独立的单词嵌入以及如何在神经网络模型中使用预先训练的嵌入。

让我们开始吧。

:摘录自:“深度学习自然语言处理”。
看一下,如果你想要更多的分步教程,在使用文本数据时充分利用深度学习方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如何开发用于预测电影评论情感的词嵌入模型
照片由 Katrina Br ?#!@ nd ,保留一些权利。

教程概述

本教程分为 5 个部分;他们是:

  1. 电影评论数据集
  2. 数据准备
  3. 训练嵌入层
  4. 训练 word2vec 嵌入
  5. 使用预先训练的嵌入

Python 环境

本教程假设您安装了 Python SciPy 环境,理想情况下使用 Python 3。

您必须安装带有 TensorFlow 或 Theano 后端的 Keras(2.2 或更高版本)。

本教程还假设您安装了 scikit-learn,Pandas,NumPy 和 Matplotlib。

如果您需要有关环境的帮助,请参阅本教程:

  • 如何使用 Anaconda 设置用于机器学习和深度学习的 Python 环境

本教程不需要 GPU,但您可以在 Amazon Web Services 上以低成本方式访问 GPU。在本教程中学习如何:

  • 如何设置 Amazon AWS EC2 GPU 以训练 Keras 深度学习模型(循序渐进)

让我们潜入。

1.电影评论数据集

电影评论数据是 Bo Pang 和 Lillian Lee 在 21 世纪初从 imdb.com 网站上检索到的电影评论的集合。收集的评论作为他们自然语言处理研究的一部分。

评论最初于 2002 年发布,但更新和清理版本于 2004 年发布,称为“v2.0”。

该数据集包含 1,000 个正面和 1,000 个负面电影评论,这些评论来自 imdb.com 上托管的 rec.arts.movi​​es.reviews 新闻组的存档。作者将此数据集称为“极性数据集”。

我们的数据包含 2000 年之前写的 1000 份正面和 1000 份负面评论,每位作者的评论上限为 20(每位作者共 312 位)。我们将此语料库称为极性数据集。

  • 感伤教育:基于最小削减的主观性总结的情感分析,2004。

数据已经有所清理,例如:

  • 数据集仅包含英语评论。
  • 所有文本都已转换为小写。
  • 标点符号周围有空格,如句号,逗号和括号。
  • 文本每行被分成一个句子。

该数据已用于一些相关的自然语言处理任务。对于分类,机器学习模型(例如支持向量机)对数据的表现在高 70%到低 80%(例如 78%-82%)的范围内。

更复杂的数据准备可以看到高达 86%的结果,交叉验证 10 倍。如果我们想在现代方法的实验中使用这个数据集,这给了我们 80 年代中期的球场。

…根据下游极性分类器的选择,我们可以实现高度统计上的显着改善(从 82.8%到 86.4%)

  • 感伤教育:基于最小削减的主观性总结的情感分析,2004。

您可以从此处下载数据集:

  • 电影评论 Polarity Dataset (review_polarity.tar.gz,3MB)

解压缩文件后,您将有一个名为“txt_sentoken”的目录,其中包含两个子目录,其中包含文本“neg”和“pos”的负数和积极的评论。对于每个 neg 和 pos,每个文件存储一个评论,命名约定为 cv000 到 cv999。

接下来,我们来看看加载和准备文本数据。

2.数据准备

在本节中,我们将看看 3 件事:

  1. 将数据分成训练和测试集。
  2. 加载和清理数据以删除标点符号和数字。
  3. 定义首选词汇的词汇。

分为训练和测试装置

我们假装我们正在开发一种系统,可以预测文本电影评论的情感是积极的还是消极的。

这意味着在开发模型之后,我们需要对新的文本评论做出预测。这将要求对这些新评论执行所有相同的数据准备,就像对模型的训练数据执行一样。

我们将通过在任何数据准备之前拆分训练和测试数据集来确保将此约束纳入我们模型的评估中。这意味着在准备用于训练模型的数据时,测试集中的数据中的任何知识可以帮助我们更好地准备数据(例如,所使用的单词)。

话虽如此,我们将使用最近 100 次正面评论和最后 100 次负面评论作为测试集(100 条评论),其余 1,800 条评论作为训练数据集。

这是 90%的训练,10%的数据分割。

通过使用评论的文件名可以轻松实现拆分,其中评论为 000 至 899 的评论用于训练数据,而评论为 900 以上的评论用于测试。

装载和清洁评论

文本数据已经非常干净;没有太多准备工作。

如果您不熟悉清理文本数据,请参阅此帖子:

  • 如何使用 Python 清理机器学习文本

如果没有太多陷入细节,我们将使用以下方式准备数据:

  • 在白色空间的分裂标记。
  • 从单词中删除所有标点符号。
  • 删除所有不完全由字母字符组成的单词。
  • 删除所有已知停用词的单词。
  • 删除长度为&lt; = 1 个字符的所有单词。

我们可以将所有这些步骤放入一个名为clean_doc()的函数中,该函数将从文件加载的原始文本作为参数,并返回已清理的标记列表。我们还可以定义一个函数 load_doc(),它从文件中加载文件,以便与clean_doc()函数一起使用。

下面列出了清理第一次正面评价的示例。

from nltk.corpus import stopwords
import string

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, 'r')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

# turn a doc into clean tokens
def clean_doc(doc):
	# split into tokens by white space
	tokens = doc.split()
	# remove punctuation from each token
	table = str.maketrans('', '', string.punctuation)
	tokens = [w.translate(table) for w in tokens]
	# remove remaining tokens that are not alphabetic
	tokens = [word for word in tokens if word.isalpha()]
	# filter out stop words
	stop_words = set(stopwords.words('english'))
	tokens = [w for w in tokens if not w in stop_words]
	# filter out short tokens
	tokens = [word for word in tokens if len(word) > 1]
	return tokens

# load the document
filename = 'txt_sentoken/pos/cv000_29590.txt'
text = load_doc(filename)
tokens = clean_doc(text)
print(tokens)

运行该示例会打印一长串清洁令牌。

我们可能想要探索更多清洁步骤,并将其留作进一步练习。

我很想知道你能想出什么。
最后在评论中发布您的方法和结果。

...
'creepy', 'place', 'even', 'acting', 'hell', 'solid', 'dreamy', 'depp', 'turning', 'typically', 'strong', 'performance', 'deftly', 'handling', 'british', 'accent', 'ians', 'holm', 'joe', 'goulds', 'secret', 'richardson', 'dalmatians', 'log', 'great', 'supporting', 'roles', 'big', 'surprise', 'graham', 'cringed', 'first', 'time', 'opened', 'mouth', 'imagining', 'attempt', 'irish', 'accent', 'actually', 'wasnt', 'half', 'bad', 'film', 'however', 'good', 'strong', 'violencegore', 'sexuality', 'language', 'drug', 'content']

定义词汇表

在使用词袋或嵌入模型时,定义已知单词的词汇表很重要。

单词越多,文档的表示越大,因此将单词限制为仅被认为具有预测性的单词是很重要的。这很难事先知道,并且通常重要的是测试关于如何构建有用词汇的不同假设。

我们已经看到了如何从上一节中的词汇表中删除标点符号和数字。我们可以对所有文档重复此操作,并构建一组所有已知单词。

我们可以开发一个词汇表作为计数器,这是一个单词及其计数的字典映射,允许我们轻松更新和查询。

每个文档都可以添加到计数器(一个名为add_doc_to_vocab()的新函数),我们可以跳过负目录中的所有评论,然后是肯定目录(一个名为 process_docs 的新函数) ())。

下面列出了完整的示例。

from string import punctuation
from os import listdir
from collections import Counter
from nltk.corpus import stopwords

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, 'r')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

# turn a doc into clean tokens
def clean_doc(doc):
	# split into tokens by white space
	tokens = doc.split()
	# remove punctuation from each token
	table = str.maketrans('', '', punctuation)
	tokens = [w.translate(table) for w in tokens]
	# remove remaining tokens that are not alphabetic
	tokens = [word for word in tokens if word.isalpha()]
	# filter out stop words
	stop_words = set(stopwords.words('english'))
	tokens = [w for w in tokens if not w in stop_words]
	# filter out short tokens
	tokens = [word for word in tokens if len(word) > 1]
	return tokens

# load doc and add to vocab
def add_doc_to_vocab(filename, vocab):
	# load doc
	doc = load_doc(filename)
	# clean doc
	tokens = clean_doc(doc)
	# update counts
	vocab.update(tokens)

# load all docs in a directory
def process_docs(directory, vocab, is_trian):
	# walk through all files in the folder
	for filename in listdir(directory):
		# skip any reviews in the test set
		if is_trian and filename.startswith('cv9'):
			continue
		if not is_trian and not filename.startswith('cv9'):
			continue
		# create the full path of the file to open
		path = directory + '/' + filename
		# add doc to vocab
		add_doc_to_vocab(path, vocab)

# define vocab
vocab = Counter()
# add all docs to vocab
process_docs('txt_sentoken/neg', vocab, True)
process_docs('txt_sentoken/pos', vocab, True)
# print the size of the vocab
print(len(vocab))
# print the top words in the vocab
print(vocab.most_common(50))

运行该示例表明我们的词汇量为 43,476 个单词。

我们还可以看到电影评论中前 50 个最常用单词的样本。

请注意,此词汇表仅基于训练数据集中的那些评论构建。

44276
[('film', 7983), ('one', 4946), ('movie', 4826), ('like', 3201), ('even', 2262), ('good', 2080), ('time', 2041), ('story', 1907), ('films', 1873), ('would', 1844), ('much', 1824), ('also', 1757), ('characters', 1735), ('get', 1724), ('character', 1703), ('two', 1643), ('first', 1588), ('see', 1557), ('way', 1515), ('well', 1511), ('make', 1418), ('really', 1407), ('little', 1351), ('life', 1334), ('plot', 1288), ('people', 1269), ('could', 1248), ('bad', 1248), ('scene', 1241), ('movies', 1238), ('never', 1201), ('best', 1179), ('new', 1140), ('scenes', 1135), ('man', 1131), ('many', 1130), ('doesnt', 1118), ('know', 1092), ('dont', 1086), ('hes', 1024), ('great', 1014), ('another', 992), ('action', 985), ('love', 977), ('us', 967), ('go', 952), ('director', 948), ('end', 946), ('something', 945), ('still', 936)]

我们可以逐步浏览词汇表并删除所有发生率较低的单词,例如仅在所有评论中使用一次或两次。

例如,以下代码段将仅检索在所有评论中出现 2 次或更多次的令牌。

# keep tokens with a min occurrence
min_occurane = 2
tokens = [k for k,c in vocab.items() if c >= min_occurane]
print(len(tokens))

使用此添加运行上面的示例表明,词汇量大小略大于其大小的一半(从 43,476 到 25,767 个单词)。

25767

最后,词汇表可以保存到一个名为vocab.txt的新文件中,以后我们可以加载并使用它来过滤电影评论,然后再编码进行建模。我们定义了一个名为save_list()的新函数,它将词汇表保存到文件中,每个文件只有一个单词。

例如:

# save list to file
def save_list(lines, filename):
	# convert lines to a single blob of text
	data = '\n'.join(lines)
	# open file
	file = open(filename, 'w')
	# write text
	file.write(data)
	# close file
	file.close()

# save tokens to a vocabulary file
save_list(tokens, 'vocab.txt')

在词汇表上运行最小出现过滤器并将其保存到文件,您现在应该有一个名为vocab.txt的新文件,其中只包含我们感兴趣的词。

文件中的单词顺序会有所不同,但应如下所示:

aberdeen
dupe
burt
libido
hamlet
arlene
available
corners
web
columbia
...

我们现在准备从评论中查看学习功能。

3.训练嵌入层

在本节中,我们将在分类问题上训练神经网络时学习嵌入一词。

单词嵌入是表示文本的一种方式,其中词汇表中的每个单词由高维空间中的实值向量表示。以这样的方式学习向量:具有相似含义的单词在向量空间中具有相似的表示(在向量空间中接近)。对于文本而言,这是一种更具表现力的表达,而不是像词袋这样的经典方法,其中单词或标记之间的关系被忽略,或者在 bigram 和 trigram 方法中被强制使用。

在训练神经网络时可以学习单词的实值向量表示。我们可以使用嵌入层在 Keras 深度学习库中完成此操作。

如果您不熟悉单词嵌入,请参阅帖子:

  • 什么是词嵌入文本?

如果您不熟悉 Keras 中的字嵌入层,请参阅帖子:

  • 如何使用 Keras 深入学习使用词嵌入层

第一步是加载词汇表。我们将用它来过滤我们不感兴趣的电影评论中的单词。

如果你已完成上一节,你应该有一个名为’vocab.txt’的本地文件,每行一个单词。我们可以加载该文件并构建一个词汇表作为检查令牌有效性的集合。

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, 'r')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

# load the vocabulary
vocab_filename = 'vocab.txt'
vocab = load_doc(vocab_filename)
vocab = vocab.split()
vocab = set(vocab)

接下来,我们需要加载所有训练数据电影评论。为此,我们可以调整上一节中的process_docs()来加载文档,清理它们,并将它们作为字符串列表返回,每个字符串有一个文档。我们希望每个文档都是一个字符串,以便以后简单编码为整数序列。

清理文档涉及根据空白区域拆分每个评论,删除标点符号,然后过滤掉不在词汇表中的所有标记。

更新的clean_doc()功能如下所示。

# turn a doc into clean tokens
def clean_doc(doc, vocab):
	# split into tokens by white space
	tokens = doc.split()
	# remove punctuation from each token
	table = str.maketrans('', '', punctuation)
	tokens = [w.translate(table) for w in tokens]
	# filter out tokens not in vocab
	tokens = [w for w in tokens if w in vocab]
	tokens = ' '.join(tokens)
	return tokens

更新的process_docs()然后可以为’pos’和’neg’目录中的每个文档调用clean_doc()在我们的训练数据集中。

# load all docs in a directory
def process_docs(directory, vocab, is_trian):
	documents = list()
	# walk through all files in the folder
	for filename in listdir(directory):
		# skip any reviews in the test set
		if is_trian and filename.startswith('cv9'):
			continue
		if not is_trian and not filename.startswith('cv9'):
			continue
		# create the full path of the file to open
		path = directory + '/' + filename
		# load the doc
		doc = load_doc(path)
		# clean doc
		tokens = clean_doc(doc, vocab)
		# add to list
		documents.append(tokens)
	return documents

# load all training reviews
positive_docs = process_docs('txt_sentoken/pos', vocab, True)
negative_docs = process_docs('txt_sentoken/neg', vocab, True)
train_docs = negative_docs + positive_docs

下一步是将每个文档编码为整数序列。

Keras 嵌入层需要整数输入,其中每个整数映射到单个标记,该标记在嵌入中具有特定的实值向量表示。这些向量在训练开始时是随机的,但在训练期间对网络有意义。

我们可以使用 Keras API 中的 Tokenizer 类将训练文档编码为整数序列。

首先,我们必须构造一个类的实例,然后在训练数据集中的所有文档上训练它。在这种情况下,它开发了训练数据集中所有标记的词汇表,并开发了从词汇表中的单词到唯一整数的一致映射。我们可以使用我们的词汇表文件轻松地开发此映射。

# create the tokenizer
tokenizer = Tokenizer()
# fit the tokenizer on the documents
tokenizer.fit_on_texts(train_docs)

现在已经准备好了单词到整数的映射,我们可以使用它来对训练数据集中的评论进行编码。我们可以通过调用 Tokenizer 上的texts_to_sequences()函数来实现。

# sequence encode
encoded_docs = tokenizer.texts_to_sequences(train_docs)

我们还需要确保所有文档具有相同的长度。

这是 Keras 对高效计算的要求。我们可以将评论截断为最小尺寸或零填充(具有值’0’的填充)评论到最大长度,或者某些混合。在这种情况下,我们将所有评论填充到训练数据集中最长评论的长度。

首先,我们可以使用训练数据集上的max()函数找到最长的评论并获取其长度。然后,我们可以调用 Keras 函数 pad_sequences(),通过在末尾添加 0 值将序列填充到最大长度。

# pad sequences
max_length = max([len(s.split()) for s in train_docs])
Xtrain = pad_sequences(encoded_docs, maxlen=max_length, padding='post')

最后,我们可以定义训练数据集的类标签,以适应监督的神经网络模型来预测评论的情感。

# define training labels
ytrain = array([0 for _ in range(900)] + [1 for _ in range(900)])

然后我们可以对测试数据集进行编码和填充,稍后需要在我们训练之后评估模型。

# load all test reviews
positive_docs = process_docs('txt_sentoken/pos', vocab, False)
negative_docs = process_docs('txt_sentoken/neg', vocab, False)
test_docs = negative_docs + positive_docs
# sequence encode
encoded_docs = tokenizer.texts_to_sequences(test_docs)
# pad sequences
Xtest = pad_sequences(encoded_docs, maxlen=max_length, padding='post')
# define test labels
ytest = array([0 for _ in range(100)] + [1 for _ in range(100)])

我们现在准备定义我们的神经网络模型。

该模型将使用嵌入层作为第一个隐藏层。嵌入需要规范词汇量大小,实值向量空间的大小以及输入文档的最大长度。

词汇量大小是我们词汇表中的单词总数,加上一个未知单词。这可以是用于对文档进行整数编码的标记器内的词汇集长度或词汇大小,例如:

# define vocabulary size (largest integer value)
vocab_size = len(tokenizer.word_index) + 1

我们将使用 100 维向量空间,但您可以尝试其他值,例如 50 或 150.最后,最大文档长度在填充期间使用的max_length变量中计算。

下面列出了完整的模型定义,包括嵌入层。

我们使用卷积神经网络(CNN),因为它们已经证明在文档分类问题上是成功的。保守的 CNN 配置与 32 个滤波器(用于处理字的并行字段)和具有整流线性(‘relu’)激活功能的 8 的内核大小一起使用。接下来是一个池化层,它将卷积层的输出减少一半。

接下来,将来自模型的 CNN 部分的 2D 输出展平为一个长 2D 向量,以表示由 CNN 提取的“特征”。模型的后端是标准的多层感知机层,用于解释 CNN 功能。输出层使用 sigmoid 激活函数为评论中的消极和积极情感输出介于 0 和 1 之间的值。

有关文本分类的有效深度学习模型配置的更多建议,请参阅帖子:

  • 深度学习文档分类的最佳实践
# define model
model = Sequential()
model.add(Embedding(vocab_size, 100, input_length=max_length))
model.add(Conv1D(filters=32, kernel_size=8, activation='relu'))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(10, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
print(model.summary())

仅运行此片段可提供已定义网络的摘要。

我们可以看到嵌入层需要长度为 442 个单词的文档作为输入,并将文档中的每个单词编码为 100 个元素向量。

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
embedding_1 (Embedding)      (None, 442, 100)          2576800
_________________________________________________________________
conv1d_1 (Conv1D)            (None, 435, 32)           25632
_________________________________________________________________
max_pooling1d_1 (MaxPooling1 (None, 217, 32)           0
_________________________________________________________________
flatten_1 (Flatten)          (None, 6944)              0
_________________________________________________________________
dense_1 (Dense)              (None, 10)                69450
_________________________________________________________________
dense_2 (Dense)              (None, 1)                 11
=================================================================
Total params: 2,671,893
Trainable params: 2,671,893
Non-trainable params: 0
_________________________________________________________________

接下来,我们使网络适应训练数据。

我们使用二元交叉熵损失函数,因为我们正在学习的问题是二分类问题。使用随机梯度下降的高效 Adam 实现,除了训练期间的损失之外,我们还跟踪准确率。该模型训练 10 个时期,或 10 次通过训练数据。

通过一些试验和错误找到了网络配置和训练计划,但对于此问题并不是最佳选择。如果您可以使用其他配置获得更好的结果,请告诉我们。

# compile network
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(Xtrain, ytrain, epochs=10, verbose=2)

在拟合模型之后,在测试数据集上对其进行评估。此数据集包含我们以前从未见过的单词和在训练期间未看到的评论。

# evaluate
loss, acc = model.evaluate(Xtest, ytest, verbose=0)
print('Test Accuracy: %f' % (acc*100))

我们可以将所有这些结合在一起。

完整的代码清单如下。

from string import punctuation
from os import listdir
from numpy import array
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import Embedding
from keras.layers.convolutional import Conv1D
from keras.layers.convolutional import MaxPooling1D

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, 'r')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

# turn a doc into clean tokens
def clean_doc(doc, vocab):
	# split into tokens by white space
	tokens = doc.split()
	# remove punctuation from each token
	table = str.maketrans('', '', punctuation)
	tokens = [w.translate(table) for w in tokens]
	# filter out tokens not in vocab
	tokens = [w for w in tokens if w in vocab]
	tokens = ' '.join(tokens)
	return tokens

# load all docs in a directory
def process_docs(directory, vocab, is_trian):
	documents = list()
	# walk through all files in the folder
	for filename in listdir(directory):
		# skip any reviews in the test set
		if is_trian and filename.startswith('cv9'):
			continue
		if not is_trian and not filename.startswith('cv9'):
			continue
		# create the full path of the file to open
		path = directory + '/' + filename
		# load the doc
		doc = load_doc(path)
		# clean doc
		tokens = clean_doc(doc, vocab)
		# add to list
		documents.append(tokens)
	return documents

# load the vocabulary
vocab_filename = 'vocab.txt'
vocab = load_doc(vocab_filename)
vocab = vocab.split()
vocab = set(vocab)

# load all training reviews
positive_docs = process_docs('txt_sentoken/pos', vocab, True)
negative_docs = process_docs('txt_sentoken/neg', vocab, True)
train_docs = negative_docs + positive_docs

# create the tokenizer
tokenizer = Tokenizer()
# fit the tokenizer on the documents
tokenizer.fit_on_texts(train_docs)

# sequence encode
encoded_docs = tokenizer.texts_to_sequences(train_docs)
# pad sequences
max_length = max([len(s.split()) for s in train_docs])
Xtrain = pad_sequences(encoded_docs, maxlen=max_length, padding='post')
# define training labels
ytrain = array([0 for _ in range(900)] + [1 for _ in range(900)])

# load all test reviews
positive_docs = process_docs('txt_sentoken/pos', vocab, False)
negative_docs = process_docs('txt_sentoken/neg', vocab, False)
test_docs = negative_docs + positive_docs
# sequence encode
encoded_docs = tokenizer.texts_to_sequences(test_docs)
# pad sequences
Xtest = pad_sequences(encoded_docs, maxlen=max_length, padding='post')
# define test labels
ytest = array([0 for _ in range(100)] + [1 for _ in range(100)])

# define vocabulary size (largest integer value)
vocab_size = len(tokenizer.word_index) + 1

# define model
model = Sequential()
model.add(Embedding(vocab_size, 100, input_length=max_length))
model.add(Conv1D(filters=32, kernel_size=8, activation='relu'))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(10, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
print(model.summary())
# compile network
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(Xtrain, ytrain, epochs=10, verbose=2)
# evaluate
loss, acc = model.evaluate(Xtest, ytest, verbose=0)
print('Test Accuracy: %f' % (acc*100))

运行该示例在每个训练时期结束时打印损失和准确率。我们可以看到该模型很快就能在训练数据集上实现 100%的准确率。

在运行结束时,模型在测试数据集上达到了 84.5%的准确度,这是一个很好的分数。

鉴于神经网络的随机性,您的具体结果会有所不同。考虑运行几次示例并将平均分数作为模型的技能。

...
Epoch 6/10
2s - loss: 0.0013 - acc: 1.0000
Epoch 7/10
2s - loss: 8.4573e-04 - acc: 1.0000
Epoch 8/10
2s - loss: 5.8323e-04 - acc: 1.0000
Epoch 9/10
2s - loss: 4.3155e-04 - acc: 1.0000
Epoch 10/10
2s - loss: 3.3083e-04 - acc: 1.0000
Test Accuracy: 84.500000

我们刚刚看到一个例子,说明我们如何学习嵌入字作为拟合神经网络模型的一部分。

接下来,让我们看看如何有效地学习我们以后可以在神经网络中使用的独立嵌入。

4.训练 word2vec 嵌入

在本节中,我们将了解如何使用名为 word2vec 的高效算法学习独立的单词嵌入。

学习单词嵌入作为网络一部分的缺点是它可能非常慢,特别是对于非常大的文本数据集。

word2vec 算法是一种以独立方式从文本语料库中学习单词嵌入的方法。该方法的好处是它可以在空间和时间复杂性方面非常有效地产生高质量的字嵌入。

第一步是准备好文档以便学习嵌入。

这涉及与前一节相同的数据清理步骤,即通过空白区域分割文档,删除标点符号,以及过滤掉不在词汇表中的标记。

word2vec 算法逐句处理文档。这意味着我们将在清洁期间保留基于句子的结构。

我们开始像以前一样加载词汇表。

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, 'r')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

# load the vocabulary
vocab_filename = 'vocab.txt'
vocab = load_doc(vocab_filename)
vocab = vocab.split()
vocab = set(vocab)

接下来,我们定义一个名为doc_to_clean_lines()的函数来逐行清理已加载的文档并返回已清理行的列表。

# turn a doc into clean tokens
def doc_to_clean_lines(doc, vocab):
	clean_lines = list()
	lines = doc.splitlines()
	for line in lines:
		# split into tokens by white space
		tokens = line.split()
		# remove punctuation from each token
		table = str.maketrans('', '', punctuation)
		tokens = [w.translate(table) for w in tokens]
		# filter out tokens not in vocab
		tokens = [w for w in tokens if w in vocab]
		clean_lines.append(tokens)
	return clean_lines

接下来,我们调整 process_docs()函数来加载和清理文件夹中的所有文档,并返回所有文档行的列表。

该函数的结果将是 word2vec 模型的训练数据。

# load all docs in a directory
def process_docs(directory, vocab, is_trian):
	lines = list()
	# walk through all files in the folder
	for filename in listdir(directory):
		# skip any reviews in the test set
		if is_trian and filename.startswith('cv9'):
			continue
		if not is_trian and not filename.startswith('cv9'):
			continue
		# create the full path of the file to open
		path = directory + '/' + filename
		# load and clean the doc
		doc = load_doc(path)
		doc_lines = doc_to_clean_lines(doc, vocab)
		# add lines to list
		lines += doc_lines
	return lines

然后我们可以加载所有训练数据并将其转换为一长串的“句子”(令牌列表),以便为 word2vec 模型拟合。

# load training data
positive_lines = process_docs('txt_sentoken/pos', vocab, True)
negative_lines = process_docs('txt_sentoken/neg', vocab, True)
sentences = negative_docs + positive_docs
print('Total training sentences: %d' % len(sentences))

我们将使用 Gensim Python 库中提供的 word2vec 实现。具体是 Word2Vec 类。

有关使用 Gensim 训练独立单词的更多信息,请参阅帖子:

  • 如何使用 Gensim 在 Python 中开发词嵌入

在构造类时,该模型是合适的。我们从训练数据中传入干净的句子列表,然后指定嵌入向量空间的大小(我们再次使用 100),在学习如何在训练句子中嵌入每个单词时要查看的相邻单词的数量(我们使用 5 个邻居),在拟合模型时使用的线程数(我们使用 8,但是如果你有更多或更少的 CPU 核心则更改它),以及词汇表中要考虑的单词的最小出现次数(我们将其设置为 1 因为我们已经准备好了词汇表)。

在模型拟合之后,我们打印学习词汇的大小,这应该与我们在 25,767 个令牌的 vocab.txt 中的词汇量相匹配。

# train word2vec model
model = Word2Vec(sentences, size=100, window=5, workers=8, min_count=1)
# summarize vocabulary size in model
words = list(model.wv.vocab)
print('Vocabulary size: %d' % len(words))

最后,我们使用模型的’wv’(字向量)属性上的 save_word2vec_format()将学习的嵌入向量保存到文件中。嵌入以 ASCII 格式保存,每行一个字和向量。

下面列出了完整的示例。

from string import punctuation
from os import listdir
from gensim.models import Word2Vec

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, 'r')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

# turn a doc into clean tokens
def doc_to_clean_lines(doc, vocab):
	clean_lines = list()
	lines = doc.splitlines()
	for line in lines:
		# split into tokens by white space
		tokens = line.split()
		# remove punctuation from each token
		table = str.maketrans('', '', punctuation)
		tokens = [w.translate(table) for w in tokens]
		# filter out tokens not in vocab
		tokens = [w for w in tokens if w in vocab]
		clean_lines.append(tokens)
	return clean_lines

# load all docs in a directory
def process_docs(directory, vocab, is_trian):
	lines = list()
	# walk through all files in the folder
	for filename in listdir(directory):
		# skip any reviews in the test set
		if is_trian and filename.startswith('cv9'):
			continue
		if not is_trian and not filename.startswith('cv9'):
			continue
		# create the full path of the file to open
		path = directory + '/' + filename
		# load and clean the doc
		doc = load_doc(path)
		doc_lines = doc_to_clean_lines(doc, vocab)
		# add lines to list
		lines += doc_lines
	return lines

# load the vocabulary
vocab_filename = 'vocab.txt'
vocab = load_doc(vocab_filename)
vocab = vocab.split()
vocab = set(vocab)

# load training data
positive_lines = process_docs('txt_sentoken/pos', vocab, True)
negative_lines = process_docs('txt_sentoken/neg', vocab, True)
sentences = negative_docs + positive_docs
print('Total training sentences: %d' % len(sentences))

# train word2vec model
model = Word2Vec(sentences, size=100, window=5, workers=8, min_count=1)
# summarize vocabulary size in model
words = list(model.wv.vocab)
print('Vocabulary size: %d' % len(words))

# save model in ASCII (word2vec) format
filename = 'embedding_word2vec.txt'
model.wv.save_word2vec_format(filename, binary=False)

运行该示例从训练数据中加载 58,109 个句子,并为 25,767 个单词的词汇表创建嵌入。

您现在应该在当前工作目录中有一个带有学习向量的文件’embedding_word2vec.txt’。

Total training sentences: 58109
Vocabulary size: 25767

接下来,让我们看看在我们的模型中使用这些学习过的向量。

5.使用预先训练的嵌入

在本节中,我们将使用在非常大的文本语料库上准备的预训练的单词嵌入。

我们可以使用前一节中开发的预训练单词嵌入和之前部分开发的 CNN 模型。

第一步是将单词嵌入作为单词目录加载到向量。单词嵌入保存在包含标题行的所谓’word2vec’格式中。加载嵌入时我们将跳过此标题行。

下面名为load_embedding()的函数加载嵌入并返回映射到 NumPy 格式的向量的单词目录。

# load embedding as a dict
def load_embedding(filename):
	# load embedding into memory, skip first line
	file = open(filename,'r')
	lines = file.readlines()[1:]
	file.close()
	# create a map of words to vectors
	embedding = dict()
	for line in lines:
		parts = line.split()
		# key is string word, value is numpy array for vector
		embedding[parts[0]] = asarray(parts[1:], dtype='float32')
	return embedding

现在我们已经在内存中拥有了所有向量,我们可以按照匹配 Keras Tokenizer 准备的整数编码的方式对它们进行排序。

回想一下,我们在将审阅文档传递给嵌入层之前对它们进行整数编码。整数映射到嵌入层中特定向量的索引。因此,重要的是我们将向量放置在嵌入层中,使得编码的单词映射到正确的向量。

下面定义了一个函数 get_weight_matrix(),它将加载的嵌入和 tokenizer.word_index 词汇表作为参数,并返回一个矩阵,其中的单词 vector 位于正确的位置。

# create a weight matrix for the Embedding layer from a loaded embedding
def get_weight_matrix(embedding, vocab):
	# total vocabulary size plus 0 for unknown words
	vocab_size = len(vocab) + 1
	# define weight matrix dimensions with all 0
	weight_matrix = zeros((vocab_size, 100))
	# step vocab, store vectors using the Tokenizer's integer mapping
	for word, i in vocab.items():
		weight_matrix[i] = embedding.get(word)
	return weight_matrix

现在我们可以使用这些函数为我们的模型创建新的嵌入层。

...
# load embedding from file
raw_embedding = load_embedding('embedding_word2vec.txt')
# get vectors in the right order
embedding_vectors = get_weight_matrix(raw_embedding, tokenizer.word_index)
# create the embedding layer
embedding_layer = Embedding(vocab_size, 100, weights=[embedding_vectors], input_length=max_length, trainable=False)

请注意,准备好的权重矩阵embedding_vectors作为参数传递给新的嵌入层,并且我们将’_ 可训练 _'参数设置为’False’以确保网络不会尝试将预先学习的向量作为训练网络的一部分。

我们现在可以将此层添加到我们的模型中。我们还有一个稍微不同的模型配置,在 CNN 模型中有更多的过滤器(128),以及在开发 word2vec 嵌入时匹配用作邻居的 5 个单词的内核。最后,简化了模型的后端。

# define model
model = Sequential()
model.add(embedding_layer)
model.add(Conv1D(filters=128, kernel_size=5, activation='relu'))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
print(model.summary())

通过一些试验和错误发现了这些变化。

完整的代码清单如下。

from string import punctuation
from os import listdir
from numpy import array
from numpy import asarray
from numpy import zeros
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import Embedding
from keras.layers.convolutional import Conv1D
from keras.layers.convolutional import MaxPooling1D

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, 'r')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

# turn a doc into clean tokens
def clean_doc(doc, vocab):
	# split into tokens by white space
	tokens = doc.split()
	# remove punctuation from each token
	table = str.maketrans('', '', punctuation)
	tokens = [w.translate(table) for w in tokens]
	# filter out tokens not in vocab
	tokens = [w for w in tokens if w in vocab]
	tokens = ' '.join(tokens)
	return tokens

# load all docs in a directory
def process_docs(directory, vocab, is_trian):
	documents = list()
	# walk through all files in the folder
	for filename in listdir(directory):
		# skip any reviews in the test set
		if is_trian and filename.startswith('cv9'):
			continue
		if not is_trian and not filename.startswith('cv9'):
			continue
		# create the full path of the file to open
		path = directory + '/' + filename
		# load the doc
		doc = load_doc(path)
		# clean doc
		tokens = clean_doc(doc, vocab)
		# add to list
		documents.append(tokens)
	return documents

# load embedding as a dict
def load_embedding(filename):
	# load embedding into memory, skip first line
	file = open(filename,'r')
	lines = file.readlines()[1:]
	file.close()
	# create a map of words to vectors
	embedding = dict()
	for line in lines:
		parts = line.split()
		# key is string word, value is numpy array for vector
		embedding[parts[0]] = asarray(parts[1:], dtype='float32')
	return embedding

# create a weight matrix for the Embedding layer from a loaded embedding
def get_weight_matrix(embedding, vocab):
	# total vocabulary size plus 0 for unknown words
	vocab_size = len(vocab) + 1
	# define weight matrix dimensions with all 0
	weight_matrix = zeros((vocab_size, 100))
	# step vocab, store vectors using the Tokenizer's integer mapping
	for word, i in vocab.items():
		weight_matrix[i] = embedding.get(word)
	return weight_matrix

# load the vocabulary
vocab_filename = 'vocab.txt'
vocab = load_doc(vocab_filename)
vocab = vocab.split()
vocab = set(vocab)

# load all training reviews
positive_docs = process_docs('txt_sentoken/pos', vocab, True)
negative_docs = process_docs('txt_sentoken/neg', vocab, True)
train_docs = negative_docs + positive_docs

# create the tokenizer
tokenizer = Tokenizer()
# fit the tokenizer on the documents
tokenizer.fit_on_texts(train_docs)

# sequence encode
encoded_docs = tokenizer.texts_to_sequences(train_docs)
# pad sequences
max_length = max([len(s.split()) for s in train_docs])
Xtrain = pad_sequences(encoded_docs, maxlen=max_length, padding='post')
# define training labels
ytrain = array([0 for _ in range(900)] + [1 for _ in range(900)])

# load all test reviews
positive_docs = process_docs('txt_sentoken/pos', vocab, False)
negative_docs = process_docs('txt_sentoken/neg', vocab, False)
test_docs = negative_docs + positive_docs
# sequence encode
encoded_docs = tokenizer.texts_to_sequences(test_docs)
# pad sequences
Xtest = pad_sequences(encoded_docs, maxlen=max_length, padding='post')
# define test labels
ytest = array([0 for _ in range(100)] + [1 for _ in range(100)])

# define vocabulary size (largest integer value)
vocab_size = len(tokenizer.word_index) + 1

# load embedding from file
raw_embedding = load_embedding('embedding_word2vec.txt')
# get vectors in the right order
embedding_vectors = get_weight_matrix(raw_embedding, tokenizer.word_index)
# create the embedding layer
embedding_layer = Embedding(vocab_size, 100, weights=[embedding_vectors], input_length=max_length, trainable=False)

# define model
model = Sequential()
model.add(embedding_layer)
model.add(Conv1D(filters=128, kernel_size=5, activation='relu'))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
print(model.summary())
# compile network
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(Xtrain, ytrain, epochs=10, verbose=2)
# evaluate
loss, acc = model.evaluate(Xtest, ytest, verbose=0)
print('Test Accuracy: %f' % (acc*100))

运行该示例显示表现未得到改善。

事实上,表现差得多。结果表明训练数据集是成功学习的,但对测试数据集的评估非常差,准确度仅略高于 50%。

测试表现差的原因可能是因为选择了 word2vec 配置或选择的神经网络配置。

...
Epoch 6/10
2s - loss: 0.3306 - acc: 0.8778
Epoch 7/10
2s - loss: 0.2888 - acc: 0.8917
Epoch 8/10
2s - loss: 0.1878 - acc: 0.9439
Epoch 9/10
2s - loss: 0.1255 - acc: 0.9750
Epoch 10/10
2s - loss: 0.0812 - acc: 0.9928
Test Accuracy: 53.000000

嵌入层中的权重可以用作网络的起始点,并且在网络训练期间进行调整。我们可以通过在创建嵌入层时设置’ trainable = True '(默认值)来实现。

使用此更改重复实验显示略微更好的结果,但仍然很差。

我鼓励您探索嵌入和网络的备用配置,看看您是否可以做得更好。让我知道你是怎么做的。

...
Epoch 6/10
4s - loss: 0.0950 - acc: 0.9917
Epoch 7/10
4s - loss: 0.0355 - acc: 0.9983
Epoch 8/10
4s - loss: 0.0158 - acc: 1.0000
Epoch 9/10
4s - loss: 0.0080 - acc: 1.0000
Epoch 10/10
4s - loss: 0.0050 - acc: 1.0000
Test Accuracy: 57.500000

可以使用在非常大的文本数据集上准备的预训练的单词向量。

例如,Google 和 Stanford 都提供了可以下载的预训练单词向量,分别使用高效的 word2vec 和 GloVe 方法进行训练。

让我们尝试在我们的模型中使用预先训练的向量。

您可以从斯坦福网页下载预训练的 GloVe 载体。具体来说,训练维基百科数据的向量:

  • 手套.6B.zip (822 兆字节下载)

解压缩文件,您将找到各种不同尺寸的预先训练嵌入。我们将在文件’glove.6B.100d.txt’中加载 100 维版本

Glove 文件不包含头文件,因此在将嵌入加载到内存时我们不需要跳过第一行。下面列出了更新的load_embedding()功能。

# load embedding as a dict
def load_embedding(filename):
	# load embedding into memory, skip first line
	file = open(filename,'r')
	lines = file.readlines()
	file.close()
	# create a map of words to vectors
	embedding = dict()
	for line in lines:
		parts = line.split()
		# key is string word, value is numpy array for vector
		embedding[parts[0]] = asarray(parts[1:], dtype='float32')
	return embedding

加载的嵌入可能不包含我们选择的词汇表中的所有单词。因此,在创建嵌入权重矩阵时,我们需要跳过在加载的 GloVe 数据中没有相应向量的单词。以下是get_weight_matrix()功能的更新,更具防御性的版本。

# create a weight matrix for the Embedding layer from a loaded embedding
def get_weight_matrix(embedding, vocab):
	# total vocabulary size plus 0 for unknown words
	vocab_size = len(vocab) + 1
	# define weight matrix dimensions with all 0
	weight_matrix = zeros((vocab_size, 100))
	# step vocab, store vectors using the Tokenizer's integer mapping
	for word, i in vocab.items():
		vector = embedding.get(word)
		if vector is not None:
			weight_matrix[i] = vector
	return weight_matrix

我们现在可以像以前一样加载 GloVe 嵌入并创建嵌入层。

# load embedding from file
raw_embedding = load_embedding('glove.6B.100d.txt')
# get vectors in the right order
embedding_vectors = get_weight_matrix(raw_embedding, tokenizer.word_index)
# create the embedding layer
embedding_layer = Embedding(vocab_size, 100, weights=[embedding_vectors], input_length=max_length, trainable=False)

我们将使用与以前相同的模型。

下面列出了完整的示例。

from string import punctuation
from os import listdir
from numpy import array
from numpy import asarray
from numpy import zeros
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import Flatten
from keras.layers import Embedding
from keras.layers.convolutional import Conv1D
from keras.layers.convolutional import MaxPooling1D

# load doc into memory
def load_doc(filename):
	# open the file as read only
	file = open(filename, 'r')
	# read all text
	text = file.read()
	# close the file
	file.close()
	return text

# turn a doc into clean tokens
def clean_doc(doc, vocab):
	# split into tokens by white space
	tokens = doc.split()
	# remove punctuation from each token
	table = str.maketrans('', '', punctuation)
	tokens = [w.translate(table) for w in tokens]
	# filter out tokens not in vocab
	tokens = [w for w in tokens if w in vocab]
	tokens = ' '.join(tokens)
	return tokens

# load all docs in a directory
def process_docs(directory, vocab, is_trian):
	documents = list()
	# walk through all files in the folder
	for filename in listdir(directory):
		# skip any reviews in the test set
		if is_trian and filename.startswith('cv9'):
			continue
		if not is_trian and not filename.startswith('cv9'):
			continue
		# create the full path of the file to open
		path = directory + '/' + filename
		# load the doc
		doc = load_doc(path)
		# clean doc
		tokens = clean_doc(doc, vocab)
		# add to list
		documents.append(tokens)
	return documents

# load embedding as a dict
def load_embedding(filename):
	# load embedding into memory, skip first line
	file = open(filename,'r')
	lines = file.readlines()
	file.close()
	# create a map of words to vectors
	embedding = dict()
	for line in lines:
		parts = line.split()
		# key is string word, value is numpy array for vector
		embedding[parts[0]] = asarray(parts[1:], dtype='float32')
	return embedding

# create a weight matrix for the Embedding layer from a loaded embedding
def get_weight_matrix(embedding, vocab):
	# total vocabulary size plus 0 for unknown words
	vocab_size = len(vocab) + 1
	# define weight matrix dimensions with all 0
	weight_matrix = zeros((vocab_size, 100))
	# step vocab, store vectors using the Tokenizer's integer mapping
	for word, i in vocab.items():
		vector = embedding.get(word)
		if vector is not None:
			weight_matrix[i] = vector
	return weight_matrix

# load the vocabulary
vocab_filename = 'vocab.txt'
vocab = load_doc(vocab_filename)
vocab = vocab.split()
vocab = set(vocab)

# load all training reviews
positive_docs = process_docs('txt_sentoken/pos', vocab, True)
negative_docs = process_docs('txt_sentoken/neg', vocab, True)
train_docs = negative_docs + positive_docs

# create the tokenizer
tokenizer = Tokenizer()
# fit the tokenizer on the documents
tokenizer.fit_on_texts(train_docs)

# sequence encode
encoded_docs = tokenizer.texts_to_sequences(train_docs)
# pad sequences
max_length = max([len(s.split()) for s in train_docs])
Xtrain = pad_sequences(encoded_docs, maxlen=max_length, padding='post')
# define training labels
ytrain = array([0 for _ in range(900)] + [1 for _ in range(900)])

# load all test reviews
positive_docs = process_docs('txt_sentoken/pos', vocab, False)
negative_docs = process_docs('txt_sentoken/neg', vocab, False)
test_docs = negative_docs + positive_docs
# sequence encode
encoded_docs = tokenizer.texts_to_sequences(test_docs)
# pad sequences
Xtest = pad_sequences(encoded_docs, maxlen=max_length, padding='post')
# define test labels
ytest = array([0 for _ in range(100)] + [1 for _ in range(100)])

# define vocabulary size (largest integer value)
vocab_size = len(tokenizer.word_index) + 1

# load embedding from file
raw_embedding = load_embedding('glove.6B.100d.txt')
# get vectors in the right order
embedding_vectors = get_weight_matrix(raw_embedding, tokenizer.word_index)
# create the embedding layer
embedding_layer = Embedding(vocab_size, 100, weights=[embedding_vectors], input_length=max_length, trainable=False)

# define model
model = Sequential()
model.add(embedding_layer)
model.add(Conv1D(filters=128, kernel_size=5, activation='relu'))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(1, activation='sigmoid'))
print(model.summary())
# compile network
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
# fit network
model.fit(Xtrain, ytrain, epochs=10, verbose=2)
# evaluate
loss, acc = model.evaluate(Xtest, ytest, verbose=0)
print('Test Accuracy: %f' % (acc*100))

运行该示例显示了更好的表现。

同样,训练数据集很容易学习,模型在测试数据集上达到 76%的准确度。这很好,但不如使用学习的嵌入层。

这可能是由于在更多数据上训练的更高质量的向量和/或使用稍微不同的训练过程的原因。

鉴于神经网络的随机性,您的具体结果可能会有所不同。尝试运行几次示例。

...
Epoch 6/10
2s - loss: 0.0278 - acc: 1.0000
Epoch 7/10
2s - loss: 0.0174 - acc: 1.0000
Epoch 8/10
2s - loss: 0.0117 - acc: 1.0000
Epoch 9/10
2s - loss: 0.0086 - acc: 1.0000
Epoch 10/10
2s - loss: 0.0068 - acc: 1.0000
Test Accuracy: 76.000000

在这种情况下,似乎学习嵌入作为学习任务的一部分可能是比使用专门训练的嵌入或更一般的预训练嵌入更好的方向。

进一步阅读

如果您要深入了解,本节将提供有关该主题的更多资源。

数据集

  • 电影评论数据
  • 一种感伤教育:基于最小削减的主观性总结的情感分析,2004。
  • 电影评论 Polarity Dataset (。tgz)
  • 数据集自述文件 v2.0 和 v1.1 。

蜜蜂

  • 集合 API - 容器数据类型
  • Tokenizer Keras API
  • 嵌入 Keras API
  • Gensim Word2Vec API
  • Gensim WordVector API

嵌入方法

  • Google 代码上的 word2vec
  • 斯坦福大学

相关文章

  • 在 Keras 模型中使用预训练的字嵌入,2016。
  • 在 TensorFlow ,2015 年实现 CNN 进行文本分类。

摘要

在本教程中,您了解了如何为电影评论的分类开发单词嵌入。

具体来说,你学到了:

  • 如何使用深度学习方法准备电影评论文本数据进行分类。
  • 如何学习嵌入词作为拟合深度学习模型的一部分。
  • 如何学习独立的单词嵌入以及如何在神经网络模型中使用预先训练的嵌入。

你有任何问题吗?
在下面的评论中提出您的问题,我会尽力回答。

:这篇文章摘录自:“深度学习自然语言处理”。看一下,如果您想要在使用文本数据时获得有关深入学习方法的更多分步教程。

编程小号
上一篇 2025-01-11 17:17
下一篇 2025-01-11 17:06

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ji-chu/108175.html