python3.8+tensorflow2.4.0+keras-2.4.3: IMDB电影评论情感分析

参考《python深度学习》第六章第一节

IMDB电影评论二分类

  • 背景知识
    • Embedding
    • IMDB数据集
  • 模型实现
    • 模型1(自训练词嵌入)
    • 模型2(自训练词嵌入)
    • 模型3(预训练词嵌入)
  • 遇到的问题和解决方案

背景知识

Embedding

本节关注:在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数据集

IMDB数据集有5w条评论,其中2w5做训练集,剩下做测试集,每个部分正负评论各占50%。
影评已被预处理为词索引构成的序列。方便起见,单词的索引基于它在数据集中出现的频率,0不映射特定的词,用来表示所有未知单词。


 

模型实现

模型1(自训练词嵌入)

模型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)

 
python3.8+tensorflow2.4.0+keras-2.4.3: IMDB电影评论情感分析_第1张图片
精度在75%左右。
 

模型2(自训练词嵌入)

模型2和模型1的区别:

  • 增加输入序列的长度:查看评论的前100个单词。
  • 减少训练样本:随机选择200条评论。
  • 手动生成输入序列:下载imdb的原始数据并分词,而不是直接使用keras的imdb数据集。
  • 增加一个Dense层,用ReLU做激活函数。
'''加载原始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')

python3.8+tensorflow2.4.0+keras-2.4.3: IMDB电影评论情感分析_第2张图片
精度只有51%左右,主要因为训练样本量少而且是随机选择的。

 

模型3(预训练词嵌入)

模型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

python3.8+tensorflow2.4.0+keras-2.4.3: IMDB电影评论情感分析_第3张图片

因为训练样本少,模型很快过拟合并不奇怪,我们关注的是精度达到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%,对于这么小的样本来说已经不错了。

 

遇到的问题和解决方案

  1. cannot import name ‘preprocessing’ from ‘keras.layers’
    module ‘tensorflow.keras.layers.experimental.preprocessing’ has no attribute ‘sequence’
    from keras import preprocessing

  2. cannot import name ‘Sequential’ from ‘keras.models’
    from keras import Sequential

  3. 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')

  4. 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评估之前的模型。如果之前保存了模型,可以直接加载模型,如果没有保存,就需要重新训练。

 

你可能感兴趣的:(python,NLP,python,深度学习)