参考《python深度学习》第六章第一节
本节关注:在Embedding层,使用预训练和从零开始训练的词嵌入对模型性能的影响。
Embedding层相当于一个字典,负责将词索引映射到固定尺寸的稠密词向量。它接受整数序列作为输入,返回对应的词向量。它只能作为模型的第一层。
输入是二维整数张量,形状是 samples * sequence_length,每个元素是一个整数序列。
输出是三维浮点数张量,形状是 samples * sequence_length * output_dim。
Embedding层的权重描述字典的准确度,就是使近义词映射到相似词向量的能力。
keras.layers.Embedding()的主要参数:
input_dim/batch_size/samples
字典的大小/词索引的数量
output_dim
词向量的维度
input_length/sequence_lenth
输入序列的长度,可选。如果你需要连接 Flatten 和 Dense 层,则这个参数是必须的(没有它,dense 层的输出尺寸就无法计算)。
IMDB数据集有5w条评论,其中2w5做训练集,剩下做测试集,每个部分正负评论各占50%。
影评已被预处理为词索引构成的序列。方便起见,单词的索引基于它在数据集中出现的频率,0不映射特定的词,用来表示所有未知单词。
模型1只查看每条评论的前20个单词,embedding层的字典大小是10000,生成8维词向量。在embedding层之后,叠加一个flatten层展开二维张量,最后叠加Dense层进行分类。这里直接展开词嵌入,会导致同一评论中的每个单词被单独处理,丢弃了句子结构和单词组合的信息。更好的做法是embedding层之后训练一个循环层或者一维卷积层。
import keras
from keras.datasets import imdb
max_features = 10000 # 字典的大小
maxlen = 20 # 截取数据的长度
# 加载数据集
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
# 对齐序列,将整数列表转换为形状为(samples, maxlen)的二维整数张量
x_train = keras.preprocessing.sequence.pad_sequences(x_train, maxlen=maxlen)
x_test = keras.preprocessing.sequence.pad_sequences(x_test, maxlen=maxlen)
# 模型初始化
from keras.layers import Flatten, Dense, Embedding
model = keras.Sequential() # 堆叠模型:堆叠层来构建网络
model.add(Embedding(10000, 8, input_length=maxlen)) # Embedding层:生成词嵌入
model.add(Flatten()) # Flatten层:二维化输出张量
model.add(Dense(1, activation='sigmoid')) # Dense层:分类
model.compile(optimizer='rmsprop',
loss='binary_crossentropy',
metrics=['acc']) # loss对应sigmoid,评估指标是精度
model.summary() # 输出参数
# 模型训练
history = model.fit(x_train, y_train, epochs=10, batch_size=32, validation_split=0.2)
模型2和模型1的区别:
'''加载原始IMDB数据并转换为列表'''
import os
imdb_dir = 'G:/chrome/aclImdb/aclImdb'
train_dir = os.path.join(imdb_dir,'train')
labels = []
texts = []
for label_type in ['neg', 'pos']:
dir_name = os.path.join(train_dir, label_type)
for fname in os.listdir(dir_name):
if fname[-4:] == '.txt':
f = open(os.path.join(dir_name, fname), encoding='UTF-8')
texts.append(f.read()) # 评论转换为texts列表
f.close()
# 标签转换为labels列表
if label_type == 'neg':
labels.append(0)
else:
labels.append(1)
'''对文本进行分词'''
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import numpy as np
train_samples = 200 # 训练200样本
val_samples = 10000 # 验证10000样本
maxlen = 100 # 输入序列长度
max_words = 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[:train_samples]
y_train = labels[:train_samples]
x_val = data[train_samples: train_samples + val_samples]
y_val = labels[train_samples: train_samples + val_samples]
# 模型初始化
import keras
from keras.layers import Flatten, Dense, Embedding
model = keras.Sequential()
model.add(Embedding(max_words, embedding_dim, input_length=maxlen))
model.add(Flatten())
model.add(Dense(32, activation='relu')) # 比之前增加一层
model.add(Dense(1, activation='sigmoid'))
model.summary()
# 训练和评估模型
model.compile(optimizer='rmsprop',loss='binary_crossentropy',metrics=['acc'])
history = model.fit(x_train, y_train,
epochs=10, batch_size=32,
validation_data=(x_val, y_val))
model.save_weights('pre_trained_glove_model.h5')
模型3在模型2的基础上,在Embedding层加载额外下载的预训练词嵌入GloVe。
'''准备预训练的嵌入GloVe'''
glove_dir = 'G:/chrome/glove.6B' # 包含400,000个单词的100维向量
# 用embeddings_index保存单词与其向量的映射,其中单词为索引
embeddings_index = {}
f = open(os.path.join(glove_dir, 'glove.6B.100d.txt'), encoding='UTF-8')
for line in f:
values = line.split()
word = values[0]
coefs = np.asarray(values[1:], dtype='float32')
embeddings_index[word] = coefs
f.close()
# 构建本次任务的嵌入矩阵
embedding_dim = 100
embedding_matrix = np.zeros((max_words,embedding_dim)) # 在GloVe中找不到的单词,嵌入向量全为0
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
# 加载GloVe
model.layers[0].set_weights([embedding_matrix])
model.layers[0].trainable = False
因为训练样本少,模型很快过拟合并不奇怪,我们关注的是精度达到56%左右,验证了在样本数少的情况下,预训练词嵌入比自训练词嵌入的性能好。
'''在测试集上评估使用GloVe的模型'''
# 获取测试集并转换为列表
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 os.listdir(dir_name):
if fname[-4:] == '.txt':
f = open(os.path.join(dir_name, fname), encoding='UTF-8')
texts.append(f.read())
f.close()
if label_type == 'neg':
labels.append(0)
else:
labels.append(1)
# 处理数据,注意这里不用打乱数据
sequences = tokenizer.texts_to_sequences(texts) # 分词
x_test = pad_sequences(sequences, maxlen=maxlen) # 序列预处理:统一长度
y_test = np.asarray(labels) # 矩阵化标签
model.load_weights('pre_trained_glove_model.h5')
model.evaluate(x_test, y_test)
测试集的验证精度达到55.6%,对于这么小的样本来说已经不错了。
cannot import name ‘preprocessing’ from ‘keras.layers’
module ‘tensorflow.keras.layers.experimental.preprocessing’ has no attribute ‘sequence’
from keras import preprocessing
cannot import name ‘Sequential’ from ‘keras.models’
from keras import Sequential
UnicodeDecodeError: ‘gbk’ codec can’t decode byte 0x93 in position 130: illegal multibyte sequence
打开文件的时候指定编码
f = open(os.path.join(dir_name, fname), encoding='UTF-8')
Unable to load weights saved in HDF5 format into a subclassed Model which has not created its variables yet. Call the Model first, then load the weights
因为训练了新的模型,所以就无法使用load_weights评估之前的模型。如果之前保存了模型,可以直接加载模型,如果没有保存,就需要重新训练。