Python+TensorFlow实现长文本分类——基于词嵌入和卷积神经网络的模型
作者:禅与计算机程序设计艺术
1.简介
1、本文将详细介绍如何使用Python+TensorFlow进行长文本分类。所用到的数据集是IMDB影评数据集,但文章中会对其进行扩充,用更具代表性的英文短文进行分类。
2、文章将会从词嵌入、卷积神经网络、序列模型三个方面介绍如何利用这些模型解决长文本分类的问题。
3、文章主要适合熟悉机器学习基础知识,以及具有一定编程能力的人士阅读。
2.基本概念
2.1 词嵌入 Word Embedding
词嵌入(word embedding)是一个对文本信息进行特征表示的高效方式。它把不同单词映射到一个低维空间中的向量表示,通过该向量表示可以很方便地计算相似度和距离等关系。词嵌入的目的是为了能够将文本转化成计算机可处理的形式,从而在自然语言处理任务中发挥作用。如图2所示,图中展示了“embedding”这一词在不同维度下的分布。
对不同维度上的词向量进行聚类可视化后得到如下图所示的词向量表示。每个单词都由一组浮点数构成的向量表示,其中每一维对应一种不同意义的特征。
词嵌入通过训练算法得到,并且可以通过多种方式进行表示。Word2vec、GloVe等都是常用的词嵌入方法。
2.2 卷积神经网络 Convolutional Neural Network (CNN)
卷积神经网络(Convolutional Neural Networks,CNNs)是20世纪90年代末提出的一种深度学习技术。它是一类用于处理图像或者时序数据的神经网络模型。卷积神经网络结构中包含多个卷积层、池化层、全连接层,使得神经网络可以自动提取出图像或时序数据的特征。 CNN 是一种特别有效的深度学习技术,它能够有效的处理图像、视频、语音、文本等多种输入数据。
对于图片类数据,典型的 CNN 模型一般包括以下几个模块:
- 卷积层(Convolution Layer):卷积层首先使用卷积操作对输入图像进行特征提取,然后应用非线性激活函数进行特征整合。
- 池化层(Pooling Layer):池化层用来降低卷积后的特征维度,即减少参数个数并保持重要特征。
- 全连接层(Fully Connected Layer):全连接层作为输出层,使用softmax函数进行分类预测。
对于文本类数据,典型的 CNN 模型一般包括以下几个模块:
- Embedding Layer:词嵌入层用来将文本转换为固定长度的向量表示。
- 卷积层(Convolution Layer):卷积层使用多种尺寸的卷积核对文本进行卷积操作,提取出不同长度范围内的特征。
- 池化层(Pooling Layer):池化层用来降低卷积后的特征维度,减少参数个数并保持重要特征。
- 全连接层(Fully Connected Layer):全连接层作为输出层,使用softmax函数进行分类预测。
从上述结构中可以看到,卷积神经网络(CNN)是一种强大的特征提取器,它可以自动捕获到文本中不同区域之间的差异,从而做出比较好的分类预测。
3.核心算法原理和具体操作步骤
3.1 数据准备
3.1.1 IMDB影评数据集
IMDB影评数据集是一个经典的英文短文文本分类数据集,它包含25,000条训练样本和25,000条测试样本,分为pos(正面)和neg(负面)两类。原始数据集可以在Kaggle网站免费下载。但是为了增加文章的专业性,我们需要扩展这个数据集,让数据更加具有代表性。因此,我们选择英文短文中包含一些感兴趣的主题,如科技、艺术、政治、旅游、体育等,再收集大量相关的英文短文进行分类。例如,我们可以选取以下四类主题的英文短文:电子游戏、健康保健、航空航天、汽车,并收集含有以上主题的英文短文。这样的扩展数据集共包含约20万条英文短文。
3.1.2 数据扩充
在实际应用场景中,由于标注数据的稀缺性,我们往往希望更多的数据来训练我们的模型,而不是依赖于少量的训练数据。所以,对于我们的扩展数据集来说,我们可以对原始数据集进行数据扩充的方法。数据扩充的过程就是在已有数据上加入新的、与原有数据不同的样本。比如,对于电子游戏这个主题,我们可以收集一些没有过多讨论的游戏评论,这些评论可能是其他主题的评论的一个很好的替代品;对于健康保健这个主题,我们可以收集一些老年人的健康咨询,这些评论也许会增强模型的泛化性能。通过数据扩充的方法,我们可以扩充训练集的规模,提升模型的鲁棒性和效果。
3.1.3 词表构建
我们还可以使用词嵌入的方法对英文短文进行特征提取,词嵌入需要事先知道所有出现的词的词频统计情况才能得到精准的向量表示。所以,我们需要先对英文短文进行预处理,去除停用词、特殊符号、标点符号等无效字符。然后,我们可以使用集合求并集的方式得到所有单词的集合,并按照词频排序,选择频率最高的n个单词作为特征词,从而得到词表。
3.2 数据预处理
3.2.1 分词、编码
在数据预处理过程中,第一步就是分词。分词的目的是把句子变成由单词组成的词序列。例如,"How are you today?"可能被分词为[“how”, “are”, “you”, “today”]。对于中文文本来说,通常采用分字的方式进行分词。如果是英文文本,则直接以空格隔开即可。在分词完成之后,我们需要给每个词赋予一个唯一的编号,通常是根据词的词频排序,编号从0开始。这种编码方式称为One-Hot Encoding,即每个单词对应一个向量,向量的第i位的值为1表示对应的单词出现,否则为0。
3.2.2 填充与截断
有些样本的长度比最大的样本长度要长,这时我们就需要对样本进行填充或者截断。填充的方法是在样本的尾部添加一些特殊符号,直到样本长度达到最大长度。截断的方法是丢弃掉超出最大长度的部分,只保留前面的部分。
3.2.3 序列化
为了能够充分利用GPU资源,我们应该尽量将数据存入连续内存中,这要求我们对数据进行序列化。常见的序列化方案有序列文件(SequenceFile),它存储了元组的键值对,并且元组中的元素是按照相同顺序排列的。另一种常见的序列化方案是tfrecord文件,它是tensorflow定义的一种二进制文件格式,用来保存tensorflow模型的参数和中间结果。
3.3 词嵌入
3.3.1 Word2Vec
word2vec是一种文本表示学习的经典模型,它通过上下文相邻词的关系来学习词的向量表示。它属于无监督学习,不需要任何标记信息。它的工作原理是通过统计相近词之间的共现关系来训练词向量。
3.3.2 GloVe
GloVe是Global Vectors for Word Representation(全局向量代表词)的缩写,它也是一种文本表示学习的模型。它与word2vec相比,不仅考虑了相邻词的关系,而且考虑了整个句子的信息。
3.4 卷积神经网络模型设计
3.4.1 RNN模型设计
使用RNN(递归神经网络)模型来解决序列分类问题是一种常见的做法。RNN模型是指递归神经网络,它能够记录之前出现的某些信息,并根据当前的输入信息进行输出预测。RNN模型可以学习到输入序列中时间依赖的信息,同时它也能够解决梯度消失和梯度爆炸的问题。
3.4.2 CNN模型设计
使用CNN(卷积神经网络)模型来解决序列分类问题也是一种常见的做法。CNN模型是指卷积神经网络,它能够对输入序列进行局部连接,从而能够同时识别出序列中的相关特征。CNN模型通过多层卷积核的叠加以及最大池化层的使用,能够从局部到全局的捕获到序列的全局特征。
4.具体代码实例和解释说明
下面是具体的代码实例,为了使文章更加易懂,我们将每一步的代码解释清楚。
4.1 数据准备
4.1.1 导入库
import tensorflow as tf
from sklearn.model_selection import train_test_split
import numpy as np
import os
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import matplotlib.pyplot as plt
%matplotlib inline
4.1.2 加载数据集
data = []
labels = []
with open('/home/aistudio/data/imdb.csv', 'r') as f:lines = f.readlines()
for line in lines[1:]:label, text = line.strip().split(',', maxsplit=1)if int(label)<2:continueelse:labels.append(int(label)-2)data.append(' '.join(jieba.lcut(text)))
通过读取数据集,我们可以获得两个列表,第一个列表是所有的文本,第二个列表是对应的标签。我们这里只取2分类,所以标签只有两种,1或者0。
4.1.3 数据扩充
为了进一步提升模型的泛化能力,我们可以通过对原始数据集进行数据扩充的方法来扩展数据集。我们可以使用同义词替换的方法,将负面的评论里面的词语替换为正面评论里面的词语,这样就相当于弥补了原始数据集的不足。
def augmentation(data):new_data=[]for i in range(len(data)):words=jieba.lcut(data[i])if random.random()>0.5:pos_words=[w for w in words if w in ['非常','好','给力']]if len(pos_words)>0:rep_word=random.choice(pos_words)neg_words=[w for w in words if w not in ['非常','好','给力'] and len(w)==2]if len(neg_words)>0:neg_word=random.choice(neg_words)new_words=words[:words.index(rep_word)]+['{}{}'.format(rep_word[-1],neg_word[-1])]else:new_words=wordselse:new_words=words else:new_words=wordsnew_data.append(' '.join(new_words))return new_data
上述代码定义了一个函数augmentation,它会对数据进行随机替换,从而扩充数据集。
4.1.4 词表构建
接下来我们需要构建词表。首先,我们先对所有文本进行分词,然后将每个词转换为编号。
tokenizer = Tokenizer(num_words=max_features, split=' ')
tokenizer.fit_on_texts(all_data)
all_data = tokenizer.texts_to_sequences(all_data)
代码中tokenizer是keras提供的工具,它可以帮助我们生成词表。它接受一个参数num_words,它指定了词表的大小,我们这里设置为max_features,这是通过词频排序选择的20000个特征词。我们通过fit_on_texts方法训练tokenizer,它会将所有文本转化为数字序列。最后,我们调用texts_to_sequences方法对所有文本进行编码,得到的结果是一个列表。每个元素是一个整数序列,表示该样本中出现的特征词。
x_train, x_valid, y_train, y_valid = train_test_split(all_data, labels, test_size=0.2, random_state=42)
print("Training set size:", len(x_train))
print("Validation set size:", len(x_valid))
将所有数据划分为训练集和验证集。
4.2 数据预处理
4.2.1 One-Hot Encoding
根据词表的不同,我们可以设置一个最大长度max_seq_length。超过这个长度的文本将会被截断或者填充。
x_train = pad_sequences(x_train, maxlen=max_seq_length)
x_valid = pad_sequences(x_valid, maxlen=max_seq_length)
通过pad_sequences方法,我们会将序列的长度统一为max_seq_length。
4.3 词嵌入
4.3.1 GloVe词向量下载
glove_file='/home/aistudio/glove.6B.zip'
!wget http://nlp.stanford.edu/data/glove.6B.zip --no-check-certificate
!unzip glove.6B.zip
glove_dir='/home/aistudio/.keras/datasets/'
glove_embeddings_dict={}
embeddings_dim=100
file_path=os.path.join(glove_dir,'glove.6B.{}d.txt'.format(embeddings_dim))
with open(file_path,'r',encoding="utf8") as file:for line in file.readlines():values=line.split()word=values[0]vector=np.asarray(values[1:],dtype='float32')glove_embeddings_dict[word]=vector
下载并解压GloVe词向量文件。
vocab_size = max_features + 1
embedding_matrix = np.zeros((vocab_size, embeddings_dim))
num_tokens=0
oov_token=-1
for word, index in tokenizer.word_index.items():if index > max_features:breakelif index == 0:oov_token=index try:embedding_vector = glove_embeddings_dict[word]embedding_matrix[index] = embedding_vectornum_tokens+=1except KeyError:pass
创建一个词嵌入矩阵。遍历词表,查找对应的GloVe词向量,若不存在则跳过。
num_tokens += 1
oov_token = num_tokens - 1
glove_embeddings_dict[str(oov_token)]=np.random.normal(scale=0.6, size=(embeddings_dim,))
embedding_matrix[oov_token]=glove_embeddings_dict[str(oov_token)]
为这个词增加一个随机初始化的向量。
4.3.2 Word2Vec词向量训练
如果下载较慢或磁盘空间不够,也可以使用Word2Vec训练词向量。
model = gensim.models.Word2Vec([data[i].split(' ') for i in range(len(labels))], min_count=5, size=embeddings_dim)
word_vectors = {key: val.tolist() for key, val in zip(model.wv.index2word, model.wv.syn0)}
用Gensim训练Word2Vec模型,并得到词向量字典。
vocabulary=set()
for sentence in data:vocabulary.update(sentence.split())
获取全部词汇表。
embeddings_matrix = np.zeros((len(vocabulary)+1, embeddings_dim), dtype=np.float32)
for i in range(len(vocabulary)):embeddings_vector = Noneword = vocabulary[i]if word in word_vectors:embeddings_vector = word_vectors[word]elif word.lower() in word_vectors:embeddings_vector = word_vectors[word.lower()] elif re.sub('\d','',word.lower()) in word_vectors:embeddings_vector = word_vectors[re.sub('\d','',word.lower())] if embeddings_vector is not None: embeddings_matrix[i+1] = embeddings_vector
遍历词汇表,找到词向量,若没有则跳过。若存在多个词,则优先选用小写字母、数字去除数字的版本。将找到的词向量赋值到embeddings_matrix相应行中。
del model
gc.collect()
删除模型,释放内存。
4.3.3 词嵌入矩阵保存
保存词嵌入矩阵,便于后续直接加载使用。
np.savez('./embedding_{}.npz'.format(embeddings_dim), embeddings_matrix=embeddings_matrix)
将词嵌入矩阵保存为npz格式。
4.4 卷积神经网络模型设计
4.4.1 LSTM模型设计
inputs = Input(shape=(max_seq_length,), name='input')
embedding = Embedding(output_dim=embeddings_dim, input_dim=vocab_size, weights=[embedding_matrix], input_length=max_seq_length)(inputs)
lstm_out = Bidirectional(LSTM(units=128, activation='tanh', dropout=0.2, recurrent_dropout=0.2))(embedding)
predictions = Dense(units=2, activation='softmax')(lstm_out)
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
history = model.fit(x_train, to_categorical(y_train), batch_size=batch_size, epochs=epochs, validation_data=(x_valid, to_categorical(y_valid)), verbose=1)
用BiLSTM层设计模型,输入层通过词嵌入层获取特征向量。BiLSTM层提取到序列中的全局特征,并用Softmax层做出分类预测。
4.4.2 CNN模型设计
inputs = Input(shape=(max_seq_length,), name='input')
embedding = Embedding(output_dim=embeddings_dim, input_dim=vocab_size, weights=[embedding_matrix], input_length=max_seq_length)(inputs)
conv_filters = [32, 64, 128]
convs = []
for filter_size in conv_filters:conv = Conv1D(filters=filter_size, kernel_size=kernel_size, padding='same')(embedding)pool = MaxPooling1D(pool_size=pool_size)(conv)flatten = Flatten()(pool)convs.append(flatten)
concatenated = Concatenate()(convs)
dropout = Dropout(0.5)(concatenated)
output = Dense(units=2, activation='softmax')(dropout)
model = Model(inputs=inputs, outputs=output)
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(x_train, to_categorical(y_train), batch_size=batch_size, epochs=epochs, validation_data=(x_valid, to_categorical(y_valid)), verbose=1)
用多个1D卷积层和池化层来提取局部特征,然后用Concatenate层合并特征。Dropout层用来防止过拟合。最后通过Dense层输出分类结果。
4.5 模型训练结果
通过训练模型,我们可以得到各项指标,包括精确度、召回率、F1值等。下面,我们可以画出模型的训练曲线。
plt.plot(history.history['accuracy'], color='green', label='training accuracy')
plt.plot(history.history['val_accuracy'], color='blue', label='validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.title('Model Accuracy')
plt.legend()
plt.show()
上图显示了模型的训练和验证集上的准确度。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
