本文将以IMDB电影评论数据集为范例,介绍Keras对文本数据预处理并喂入神经网络模型的方法。
IMDB数据集的目标是根据电影评论的文本内容预测评论的情感标签。训练集有20000条电影评论文本,测试集有5000条电影评论文本,其中正面评论和负面评论都各占一半。
文本数据预处理主要包括中文切词(本示例不涉及),构建词典,序列填充,定义数据管道等步骤。让我们出发吧!
一,准备数据
1,获取数据
在公众号后台回复关键字:imdb,可以获取IMDB数据集的下载链接。数据大小约为13M,解压后约为31M。
数据集结构如下所示。
直观感受一下文本内容。
2,构建词典
为了能够将文本数据喂入模型,我们一般要构建词典,以便将词转换成对应的token(即数字编码)。
from keras.preprocessing.text import Tokenizer
from tqdm import tqdm
# 数据集路径
train_data_path = 'imdb\_datasets/xx\_train\_imdb'
test_data_path = 'imdb\_datasets/xx\_test\_imdb'
train_samples = 20000 #训练集样本数量
test_samples = 5000 #测试集样本数量
max_words = 10000 # 保留词频最高的前10000个词
maxlen = 500 # 每个样本文本内容最多保留500个词
# 构建训练集文本生成器
def texts\_gen():
with open(train_data_path,'r',encoding = 'utf-8') as f,\
tqdm(total = train_samples) as pbar:
while True:
text = (f.readline().rstrip('\n').split('\t')[-1])
if not text:
break
if len(text) > maxlen:
text = text[0:maxlen]
pbar.update(1)
yield text
texts = texts_gen()
tokenizer = Tokenizer(num_words=max_words)
tokenizer.fit_on_texts(texts)
看一下我们生成的词典。
3,分割样本
为了能够像ImageDataGenerator那样用数据管道多进程并行地读取数据,我们需要将数据集按样本分割成多个文件。
import os
scatter_train_data_path = 'imdb\_datasets/train/'
scatter_test_data_path = 'imdb\_datasets/test/'
# 将数据按样本打散到多个文件
def scatter\_data(data\_file, scatter\_data\_path):
if not os.path.exists(scatter_data_path):
os.makedirs(scatter_data_path)
for idx,line in tqdm(enumerate(open(data_file,'r',encoding = 'utf-8'))):
with open(scatter_data_path + str(idx) + '.txt','w',
encoding = 'utf-8') as f:
f.write(line)
scatter_data(train_data_path,scatter_train_data_path)
scatter_data(test_data_path,scatter_test_data_path)
4,定义管道
通过继承keras.utils.Sequence类,我们可以构建像ImageDataGenerator那样能够并行读取数据的生成器管道。尽管下面的代码看起来有些长,但通常只有__data_generation方法需要被修改。
# 定义Sequence数据管道, 可以多线程读数据
import keras
import numpy as np
from keras.preprocessing.sequence import pad_sequences
batch_size = 64
class DataGenerator(keras.utils.Sequence):
def \_\_init\_\_(self,n\_samples,data\_path,batch\_size=batch\_size,shuffle=True):
self.data_path = data_path
self.n_samples = n_samples
self.batch_size = batch_size
self.shuffle = shuffle
self.on_epoch_end()
def \_\_len\_\_(self):
return int(np.ceil(self.n_samples/self.batch_size))
def \_\_getitem\_\_(self, index):
# Generate indexes of the batch
batch_indexes = self.indexes[index*self.batch_size:(index+1)*self.batch_size]
# Generate data
datas, labels = self.__data_generation(batch_indexes)
return datas, labels
def on\_epoch\_end(self):
self.indexes = np.arange(self.n_samples)
if self.shuffle == True:
np.random.shuffle(self.indexes)
def \_\_read\_file(self,file\_name):
with open(file_name,encoding = 'utf-8') as f:
line = f.readline()
return line
def \_\_data\_generation(self, batch\_indexes):
lines = [self.__read_file(self.data_path + str(i) + '.txt') for i in batch_indexes]
labels = np.array([int(line.strip().split('\t')[0]) for line in lines])
texts = [line.strip().split('\t')[-1] for line in lines]
sequences = tokenizer.texts_to_sequences(texts)
datas = pad_sequences(sequences,maxlen)
return datas,labels
train_gen = DataGenerator(train_samples,scatter_train_data_path)
test_gen = DataGenerator(test_samples,scatter_test_data_path)
二,构建模型
为了将文本token后的整数序列用神经网络进行处理,我们在第一层使用了Embedding层,Embedding层从数学上等效为将输入数据进行onehot编码后的一个全连接层,在形式上以查表方式实现以提升效率。
from keras import models,layers
from keras import backend as K
K.clear_session()
embedding_dim = 4
model = models.Sequential()
model.add(layers.Embedding(max_words, embedding_dim, input_length=maxlen))
model.add(layers.Flatten())
model.add(layers.Dense(32,activation = 'relu'))
model.add(layers.Dense(1, activation = 'sigmoid'))
model.summary()
model.compile(optimizer='adam',
loss='binary\_crossentropy',
metrics=['acc'])
三,训练模型
epoch_num = 5
steps_per_epoch = int(np.ceil(train_samples/batch_size))
validation_steps = int(np.ceil(test_samples/batch_size))
history = model.fit_generator(train_gen,
steps_per_epoch = steps_per_epoch,
epochs = epoch_num,
validation_data= test_gen,
validation_steps = validation_steps,
workers=1,
use_multiprocessing=False #linux上可使用多进程读取数据
)
四,评估模型
import os
import pandas as pd
# 保存得分
acc = history.history['acc']
val_acc = history.history['val\_acc']
loss = history.history['loss']
val_loss = history.history['val\_loss']
epochs = range(1, len(acc) + 1)
dfhistory = pd.DataFrame({'epoch':epochs,'train\_loss':loss,'test\_loss':val_loss,
'train\_acc':acc,'test\_acc':val_acc})
print(dfhistory)
五,使用模型
六,保存模型
model.save('imdb\_model.h5')
推荐阅读: