参考书籍:Deep Learning with Python
视频:Deep Learning with Python 46 Deep Learning for Text and Sequences-13 用卷積神經網路處理序列_哔哩哔哩_bilibili
本章主要围绕深度学习模型处理文本数据、时间序列数据进行展开,介绍了两种新的模型,即循环神经网络(RNN)和一维卷积神经网络(1D Convet),并和第五章的模型结合进行训练。
6.1 处理文本数据
由于模型自身特性,将原始的文本进行输入不能处理,需要转换为张量的形式。有以下三种方法进行处理:
1.将文本分割为单词,并将每个单词转换为一个向量。
2.将文本分割为字符,并将每个字符转换为一个向量。
3.提取单词或字符的n-gram,并将每个n-gram转换为一个向量。n-gram是多个连续单词或字符的集合。(由于该方法本身特点,深度学习中不采用该方法进行特征处理,但是常用于轻量级的文本处理模型中)
上述的三种方法均可以统称为文本向量化,但是如何执行文本向量化呢,书中介绍了两种主要的方法,一个是one-hot编码,另一个是word embedding。
6.1.1 单词和字符的one-hot编码
I.单词级的One-hot编码
import numpy as np
samples = ['The cat sat on the mat.','The dog ate my homework.']
# build the index for all token
token_index = {}
for sample in samples:
for word in sample.split():
if word not in token_index:
token_index[word] = len(token_index) + 1 #index 从1开始
max_length = 10 #对样本进行分词
results = np.zeros(shape=(len(samples),max_length,max(token_index.values())+1)) # 开辟一块len(samples)*10的零矩阵
for i,sample in enumerate(samples):
for j,word in list(enumerate(sample.split()))[:max_length]:
index = token_index.get(word)
results[i,j,index] = 1
代码中很好的体现了one-hot编码的特征,将某个索引值的位置设为1。在这个代码中,需要注意的是python中的数组是从0开始,而我们所设置的index是从1开始,所以所输出的矩阵虽然存在第0列,但是全部为空。
II.字符级的one-hot编码
import string
samples = ['The cat sat on the mat.','The dog ate my homework.']
characters = string.printable # 所有可以打印的ASCII码
token_index = dict(zip(range(1,len(characters) + 1),characters))
max_length = 50
results = np.zeros((len(samples),max_length,max(token_index.keys())+1))
for i,sample in enumerate(samples):
for j,character in enumerate(sample):
index = token_index.get(character)
results[i,j,index] = 1
可以看出以字符进行划分,通过ASCII码进行构建,同时shape中的一部分是由ASCII码来决定的。思路和以单词划分的相同。
III.Keras自带的分词
from keras.preprocessing.text import Tokenizer
samples = ['The cat sat on the mat.','The dog ate my homework']
tokenizer = Tokenizer(num_words=1000) # 只考虑前1000个最常见的词
tokenizer.fit_on_texts(samples) # 构建单词索引
sequences = tokenizer.texts_to_sequences(samples) #将字符串转换为整数所以组成的列表
one_hot_results = tokenizer.texts_to_matrix(samples,mode='binary')
word_index = tokenizer.word_index #找回单词索引
可以看出keras不区分大小写。同时,只考虑数据集中最常见的N个单词。
6.1.2 使用词嵌入
词嵌入(Embedding):可以理解为词与词之间的关系。映射到几何空间中可以表示这些词之间的距离和方向。
在进行Embedding层实例化时,他的权重最开始是随机的,在训练过程中通过反向传播逐渐调节词向量,解决问题。
在Embedding层使用文档中指出“This layer can only be used as the first layer in a model.”
from keras.models import Sequential
from keras.layers import Flatten,Dense,Embedding
model = Sequential()
model.add(Embedding(10000,8,input_length=maxlen))
model.add(Flatten())
model.add(Dense(1,activation='sigmoid'))
model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])
model.summary()
history = model.fit(x_train,y_train,epochs=10,batch_size=32,validation_split=0.2)
构建模型,首先是进行Embedding层嵌入,其次针对于Embedding层进行展开,最后使用全连接层针对于每个单词进行处理。这种处理方式只考虑单词本身而没有从句子层面看,准确率会有所下降。下图为model结构:
6.1.3 完整流程 -- IMDB
下载2014年英文维基百科的预计算嵌入。预训练的词嵌入对于训练数据很少的问题很有效,所以在一开始便设定了训练数据的限制。
I.对于原始数据进行分词
# 文本分词
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
maxlen = 100 #在100个单词后截断评论
training_samples = 200 #在200个样本上训练
validation_samples = 10000 #在10000个样本上验证
max_words = 10000 #只考虑数据集中前10000个最常见的单词
tokenizer = Tokenizer(num_words=max_words)
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)
word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))
data = pad_sequences(sequences,maxlen=maxlen)
labels = np.asarray(labels)
print('Shape of data tensor:',data.shape)
print('Shape of label tensor',labels.shape)
indices = np.arange(data.shape[0])
np.random.shuffle(indices)
data = data[indices]
labels = labels[indices]
x_train = data[:training_samples]
y_train = labels[:training_samples]
x_val = data[training_samples:training_samples + validation_samples]
y_val = labels[training_samples:training_samples + validation_samples]
II.对于Glove进行预处理和嵌入
glove_dir = r'D:\XJTLU\Deep Learning with Python\glove.6B'
embeddings_index = {}
with open(os.path.join(glove_dir,'glove.6B.100d.txt'),'r',encoding='utf-8') as f:
for line in f:
values = line.split()
word = values[0]
coefs = np.asarray(values[1:],dtype='float32')
embeddings_index[word] = coefs
f.close()
#print(len(embeddings_index))
# 准备GloVe词嵌入矩阵
embedding_dim = 100
embedding_matrix = np.zeros((max_words,embedding_dim))
for word,i in word_index.items():
if i < max_words:
embedding_vector = embeddings_index.get(word)
if embedding_vector is not None:
embedding_matrix[i] = embedding_vector
III.定义模型并加载Glove嵌入
from keras.models import Sequential
from keras.layers import Flatten,Dense,Embedding
model = Sequential()
model.add(Embedding(max_words,embedding_dim,input_length=maxlen))
model.add(Flatten())
model.add(Dense(32,activation='relu'))
model.add(Dense(1,activation='sigmoid'))
model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False
model.summary()
model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])
history = model.fit(x_train,y_train,epochs=10,batch_size=32,validation_data=(x_val,y_val))
#model.save_weights('pre_trained_glove_model.h5')
这段代码中和第五章卷积神经网络冻结有点相似,也是针对于模型中的指定位置进行操作,在这段代码中通过指定Embedding层位置进行Glove嵌入。
IV.训练模型并进行评估和测试
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(1, len(acc) + 1)
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
# 对测试集数据进行分词
test_dir = os.path.join(imdb_dir,'test')
labels = []
texts = []
for label_type in ['neg','pos']:
dir_name = os.path.join(test_dir,label_type)
for fname in sorted(os.listdir(dir_name)):
if fname[-4:] == '.txt':
with open(os.path.join(dir_name, fname), 'r', encoding='utf-8') as f:
texts.append(f.read())
f.close()
if label_type == 'neg':
labels.append(0)
else:
labels.append(1)
sequences = tokenizer.texts_to_sequences(texts)
x_test = pad_sequences(sequences,maxlen=maxlen)
y_test = np.asarray(labels)
model.evaluate(x_test,y_test)
模型最终达到57%,在200个小样本中,表现还是比较不错的。
6.2 理解循环神经网络
循环神经网络(RNN)是一类具有内部环结构的神经网络,利用之前的数据对于模型进行训练,通过新信息对于模型进行更新。在这一节,将介绍SimpleRNN,LSTM和GRU三类。
6.2.1 SimpleRNN
I.基本介绍
该层可以在两种不同的模式下运行,该模型的输入为(btach_size,timesteps,input_features),输出为(batch_size,timesteps,output_features)或(batch_size,output_features)两种类型。
from keras.models import Sequential
from keras.layers import Embedding, SimpleRNN
model = Sequential()
model.add(Embedding(10000, 32))
model.add(SimpleRNN(32))
model.summary()
和之前的神经网络对比,RNN有一个V,上面所展示的模型的2080 = (32+1)* 32 + 32*32
II.应用
1.准备数据
数据处理过程:文字-整数-矩阵。由于IMDB所提供的数据已经是整数的形式,我们再次基础上直接将它变为矩阵的形式。
from keras.datasets import imdb
from keras.preprocessing import sequence
max_features = 10000 # number of words to consider as features
maxlen = 500 # cut texts after this number of words (among top max_features most common words)
batch_size = 32
print('Loading data...')
(input_train, y_train), (input_test, y_test) = imdb.load_data(num_words=max_features)
print(len(input_train), 'train sequences')
print(len(input_test), 'test sequences')
print('Pad sequences (samples x time)')
input_train = sequence.pad_sequences(input_train, maxlen=maxlen)
input_test = sequence.pad_sequences(input_test, maxlen=maxlen)
print('input_train shape:', input_train.shape)
print('input_test shape:', input_test.shape)
2.用Embedding层和SimpleRNN层来训练模型
由于循环神经网络对于之前的结果有一定的参考,所以在选择优化器时选择较为稳定的rmsprop,减少动量的波动。
from keras.layers import Dense,Embedding,SimpleRNN
from keras.models import Sequential
import tensorflow as tf
model = Sequential()
model.add(Embedding(max_features, 32))
model.add(SimpleRNN(32))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(input_train, y_train,
epochs=10,
batch_size=128,
validation_split=0.2)
3.绘制结果
import matplotlib.pyplot as plt
acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']
epochs = range(len(acc))
plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs, loss, 'bo', label='Training loss')
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
6.2.2 LSTM
LSTM可以理解为四个SimpleRNN的结合,在一定程度上解决了梯度消失问题。包含遗忘门,输入门,输出门和隐藏层。遗忘门是通过所分配的比例进行携带的分配,进入下一个状态。
6.2.3 GRU
GRU可以看是LSTM的变种,比LSTM更为简单,由3个SimpleRNN组成。
6.3 循环神经网络的高级用法
import os
import numpy as np
f = open(r'/content/jena_climate_2009_2016.csv')
data = f.read()
f.close()
lines = data.split('\n')
header = lines[0].split(',')
lines = lines[1:]
float_data = np.zeros((len(lines), len(header) - 1))
for i, line in enumerate(lines):
values = [float(x) for x in line.split(',')[1:]]
float_data[i, :] = values
mean = float_data[:200000].mean(axis=0)
float_data -= mean
std = float_data[:200000].std(axis=0)
float_data /= std
def generator(data, lookback, delay, min_index, max_index,
shuffle=False, batch_size=128, step=6):
if max_index is None:
max_index = len(data) - delay - 1
i = min_index + lookback
while 1:
if shuffle:
rows = np.random.randint(
min_index + lookback, max_index, size=batch_size)
else:
if i + batch_size >= max_index:
i = min_index + lookback
rows = np.arange(i, min(i + batch_size, max_index))
i += len(rows)
samples = np.zeros((len(rows),
lookback // step,
data.shape[-1]))
targets = np.zeros((len(rows),))
for j, row in enumerate(rows):
indices = range(rows[j] - lookback, rows[j], step)
samples[j] = data[indices]
targets[j] = data[rows[j] + delay][1]
yield samples, targets
lookback = 1440
step = 6
delay = 144
batch_size = 128
train_gen = generator(float_data,
lookback=lookback,
delay=delay,
min_index=0,
max_index=200000,
shuffle=True,
step=step,
batch_size=batch_size)
val_gen = generator(float_data,
lookback=lookback,
delay=delay,
min_index=200001,
max_index=300000,
step=step,
batch_size=batch_size)
test_gen = generator(float_data,
lookback=lookback,
delay=delay,
min_index=300001,
max_index=None,
step=step,
batch_size=batch_size)
# This is how many steps to draw from `val_gen`
# in order to see the whole validation set:
val_steps = (300000 - 200001 - lookback) // batch_size
# This is how many steps to draw from `test_gen`
# in order to see the whole test set:
test_steps = (len(float_data) - 300001 - lookback) // batch_size
数据处理:主要是针对于原始数据进行标准化操作,针对于每个时间序列减去其平均值,除以标准差。
构建python生成器:生成器可以理解为一种迭代操作的工具,与之前的一次性元素添加到内存中不同,根据需要生成每个数据。下面我将根据实例化进行绘图解释作者进行的相关工作。
剩余工作就是针对于模型进行组合,作者给出了三种优化方式:
1 循环dropout,在循环层中使用dropout来降低过拟合,打破该层训练数据中的偶然相关性。
2 堆叠循环层,提高网络的表示能力,构建更加强大的循环网络。
3 双向循环层,将相同的信息以不同的方式(正向和逆向)呈现给循环网络,可以提高精度并缓解遗忘问题。
6.4 用卷积神经网络处理序列
第五章介绍了利用卷积神经网络处理计算机视觉问题,在本节,卷积神经网络也可以用来处理序列,用到的是一维卷积神经网络(Conv1D)。一维卷积神经网络是从输入中提取一段子序列,输出某个值(通常为最大值或者平均值)。
from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop
model = Sequential()
model.add(layers.Embedding(max_features, 128, input_length=max_len))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.MaxPooling1D(5))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(1))
model.summary()
model.compile(optimizer=RMSprop(lr=1e-4),
loss='binary_crossentropy',
metrics=['acc'])
history = model.fit(x_train, y_train,
epochs=10,
batch_size=128,
validation_split=0.2)
模型结构如上图所示,参数计算如下:
后续作者给出了多种模型相结合的方式,并用MAE进行评估。提到了结合一维CNN和RNN来处理长序列,结合卷积神经网络的速度和RNN的顺序敏感性,优化模型效果。