参考书目:陈允杰.TensorFlow与Keras——Python深度学习应用实战.北京:中国水利水电出版社,2021
本系列基本不讲数学原理,只从代码角度去让读者们利用最简洁的Python代码实现深度学习方法。
虽然很多教材或很多视频都展示了怎么构建循环神经网络,怎么训练文本类型的时序数据,但是它们都没教我们怎么把文字变成可以运算的数据,本章就是在教大家怎么处理文本数据。采用的是IMDB网络电影情感分析的数据集。
首先我们得知道Keras里面的一些基础功能。
分割文字数据——断词
下面演示Keras将英文句子切割:
from keras.preprocessing.text import text_to_word_sequence
# 定义文件
doc = "Keras is an API designed for human beings, not machines."
# 将文件分割成单字
words = text_to_word_sequence(doc)
print(words)
#计算单词个数
vocab_size = len(words)
print(vocab_size)
可以看出这句话切成了10个词,并且过滤了标点符号,text_to_word_sequence默认是用空白去分割句子的,英语句子单词之间都是有空白的。也可以使用逗号分割:
doc = "Pregnancies,Glucose,BloodPressure,SkinThickness,Insulin,Outcome"
# 将文件分割成单字
words = text_to_word_sequence(doc, lower=False, split=",")
print(words)
对于中文,没有空白分割,可以试一试逗号切割:
jay='从前从前有个人爱你很久,但偏偏风渐渐把距离吹得好远,好不容易又能再多爱一天,但故事的最后你好像还是说了拜拜。'
words = text_to_word_sequence(jay, lower=False, split=",")
print(words)
好像切不出来,也不知道是不是不支持中文的逗号的原因。中文分词一般都采用——jieba库:
import jieba
jay='从前从前有个人爱你很久,但偏偏风渐渐把距离吹得好远,好不容易又能再多爱一天,但故事的最后你好像还是说了拜拜。'
sentence_depart = jieba.lcut(jay.strip())
print(sentence_depart)
可以看到分割效果还是不错的,就是没过滤掉标点符号,可以后面手动循环去除一下标点符号
切割完后,就是对单词进行索引化,变为数字。使用tokenizer对象去实现
from keras.preprocessing.text import Tokenizer
# 定义3 份文件
docs = ["Keras is an API designed for human beings, not machines.",
"Easy to learn and easy to use." ,
"Keras makes it easy to turn models into products."]
# 建立 Tokenizer
tok = Tokenizer()
# 执行文字数据预处理
tok.fit_on_texts(docs)
# 显示摘要信息
print(tok.document_count)#显示文件个数
print(tok.word_counts)#显示每个单词的个数
print(tok.word_index)#显示单词的索引
print(tok.word_docs)#显示单词在文件中出现的次数
将文字都变为数字
# 建立序列数据
words = tok.texts_to_sequences(docs)
print(words)
可以看到三个文本变成了3个列表,然后使用numpy变为数组,嵌入神经网络里面就可以进行运算了。
IMDB网络电影数据预处理
虽然这个数据以及内置在Kears里面,但是生活处理实际问题时我们都是从文本自己变为数字的,下面演示不使用内置数据集接口,从网站下载文字,然后把这个文本变成可以预算的数组。
下载地址为:https://ai.stanford.edu/~amaas/data/sentiment/
下载这个,然后解压
解压出来为一个aclImdb的文件夹,里面目录是这样的:
训练集和测试集里面都有正面和负面两个文件夹,里面都是相对应的评论文本。每个目录下都是12500个,即训练集有12500个正类,12500个负类。测试集也一样。下面开始扫描目录,逐一读取。(由于文本爬虫下来的里面还有一个网页文件的符号,要定义一个函数去删除这些符号,然后手工构建标签y的列表)
import re
from os import listdir
from keras.preprocessing import sequence
from keras.preprocessing.text import Tokenizer
#IMDb数据所在目录
path = "aclImdb/"
#建立档案清单
fList = [path + "train/pos/" + x for x in listdir(path + "train/pos")] + \
[path + "train/neg/" + x for x in listdir(path + "train/neg")] + \
[path + "test/pos/" + x for x in listdir(path + "test/pos")] + \
[path + "test/neg/" + x for x in listdir(path + "test/neg")]
#删除HTML标签的符号
def remove_tags(text):
TAG = re.compile(r'<[^>]+>')
return TAG.sub('', text)
# 读取文字档案的数据
input_label = ([1] * 12500 + [0] * 12500) * 2
input_text = []
下面开始读取5w个文件,并把文件写入imput_text这个列表中,展示一下第5个评论的内容和标签。
# 读取档案内容
for fname in fList:
with open(fname, encoding="utf8") as ff:
input_text += [remove_tags(" ".join(ff.readlines()))]
print(input_text[5])
print(input_label[5])
开始用训练集的数据构建索引字典,查看前10个索引是什么单词
# 将文件分割成单字, 建立词索引字典
tok = Tokenizer(num_words=2000)
tok.fit_on_texts(input_text[:25000])
print("文件数 : ", tok.document_count)
print({k: tok.word_index[k] for k in list(tok.word_index)[:10]})
将每个文本都变为数字列表:
# 建立训练和测试数据集
X_train = tok.texts_to_sequences(input_text[:25000])
X_test = tok.texts_to_sequences(input_text[25000:])
Y_train = input_label[:25000]
Y_test = input_label[25000:]
由于每个评论的单词数量都不一样,变为数字的长度也不一样,使用运算时要弄成一样长的,然后将y也变为数组
# 将序列数据填充成相同长度
X_train = sequence.pad_sequences(X_train, maxlen=100)
X_test = sequence.pad_sequences(X_test, maxlen=100)
print("X_train.shape: ", X_train.shape)
print("X_test.shape: ", X_test.shape)
Y_train=np.array(Y_train)
Y_test=np.array(Y_test)
print(Y_train.shape)
print(Y_test.shape)
到这里,文本数据预处理就完了,变为了数值,就可以进行神经网络的运算了。
IMDB网络电影数据分类
我们采用六种神经网络进行分类,分别是MLP,一维CNN,RNN,LSTM,GRU,1DCNN+LSTM,首先导入包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from keras.preprocessing import sequence
from keras.models import Sequential
from keras.layers import Dense, Dropout, Embedding, Flatten,MaxPooling1D,Conv1D,SimpleRNN,LSTM,GRU
seed = 10
np.random.seed(seed) # 指定乱数种子
#单词索引的最大个数2000,单句话最大长度100
top_words=2000 #tok = Tokenizer(num_words=2000)和这里这个2000是对应的
max_words=100 #sequence.pad_sequences(X_train, maxlen=100)和这里的100对应
构建模型(这里是六种模型一起构建的,方便代码的重复使用,但是对新手可能不好理解,新手想看一个一个的模型分别构建的可以看我前面的文章)
def build_model(top_words=top_words,max_words=max_words,mode='LSTM',hidden_dim=[32]):
model = Sequential()
model.add(Embedding(top_words, 32, input_length=max_words))
model.add(Dropout(0.25))
if mode=='RNN':
#RNN
model.add(SimpleRNN(32))
elif mode=='MLP':
model.add(Flatten())
model.add(Dense(256, activation="relu"))
elif mode=='LSTM':
# LSTM
model.add(LSTM(32))
elif mode=='GRU':
#GRU
model.add(GRU(32))
elif mode=='CNN':
#一维卷积
model.add(Conv1D(filters=32, kernel_size=3, padding="same",activation="relu"))
model.add(MaxPooling1D(pool_size=2))
model.add(Flatten())
model.add(Dense(256, activation="relu"))
elif mode=='CNN+LSTM':
model.add(Conv1D(filters=32, kernel_size=3, padding="same",activation="relu"))
model.add(MaxPooling1D(pool_size=2))
model.add(LSTM(100))
model.add(Dropout(0.25))
model.add(Dense(1, activation="sigmoid"))
model.compile(loss="binary_crossentropy", optimizer="adam", metrics=["accuracy"])
return model
每种神经网络的神经元个数,还有网络的层数在我模型里面都是固定的,当然也可以自己去改,看实际情况需求,下面定义画损失和精度随着训练周期变化的图的函数:
#定义损失和精度的图
def plot_loss(history):
# 显示训练和验证损失图表
loss = history.history["loss"]
epochs = range(1, len(loss)+1)
val_loss = history.history["val_loss"]
plt.plot(epochs, loss, "bo", label="Training Loss")
plt.plot(epochs, val_loss, "r", label="Validation Loss")
plt.title("Training and Validation Loss")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.legend()
plt.show()
# 显示训练和验证准确度
def plot_acc(history):
acc = history.history["accuracy"]
epochs = range(1, len(acc)+1)
val_acc = history.history["val_accuracy"]
plt.plot(epochs, acc, "b-", label="Training Acc")
plt.plot(epochs, val_acc, "r--", label="Validation Acc")
plt.title("Training and Validation Accuracy")
plt.xlabel("Epochs")
plt.ylabel("Accuracy")
plt.legend()
plt.show()
定义训练函数
#定义训练函数
def train_fuc(max_words=max_words,mode='LSTM',batch_size=32,epochs=10,hidden_dim=[32],show_acc=True,show_loss=True):
#构建模型
model=build_model(max_words=max_words,mode=mode)
history=model.fit(X_train, Y_train,batch_size=batch_size,epochs=epochs,validation_split=0.2, verbose=1)
print('——————训练完毕——————')
# 评估模型
loss, accuracy = model.evaluate(X_test, Y_test)
print("测试数据集的准确度 = {:.4f}".format(accuracy))
if show_loss:
plot_loss(history)
if show_acc:
plot_acc(history)
设置参数,
max_words=100
batch_size=32
epochs=10
show_acc=True
show_loss=True
mode='MLP'
top_words=2000
开始用不同的模型去训练了,由于已经定义好了,只需要把训练函数的mode参数改一下就是不同的模型,评估测试集精度,损失画图都一起出来,很方便
MLP训练
train_fuc(mode='MLP',batch_size=batch_size,epochs=epochs)
上面是训练10轮的过程,中间是评估,下面是损失图和精度图。其他模型一样
CNN训练
#下面模型都是接受三维数据输入,先把X变个形状
X_train= X_train.reshape((X_train.shape[0], X_train.shape[1], 1))
X_test= X_test.reshape((X_test.shape[0], X_test.shape[1], 1))
train_fuc(mode='CNN',batch_size=batch_size,epochs=epochs)
RNN(参数都是一样的,训练轮数和批量大小都没改,就不写到训练函数里面去了,只需要改一下mode 的名称就行)
mode='RNN'
train_fuc(mode='RNN')
LSTM(一样,改一下mode参数就行,很方便简洁)
train_fuc(mode='LSTM')
LSTM精度还是很高的,比前面的模型都高
GRU
train_fuc(mode='GRU')
训练结果也差不多,不展示了。精度是0.8345,略低于LSTM
CNN+LSTM
train_fuc(mode='CNN+LSTM')
精度最高,为 0.8478。组合模型还是有效的。
大家可以自己去搭建不同的模型,可以修改神经元个数,神经网络层数,使用不同网络层的组合,然后比较一下测试集精度大小,找到最优的模型。