前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用TensorFlow 2.0的LSTM进行多类文本分类

使用TensorFlow 2.0的LSTM进行多类文本分类

作者头像
代码医生工作室
发布2019-12-19 11:53:41
4.2K0
发布2019-12-19 11:53:41
举报
文章被收录于专栏:相约机器人

作者 | Susan Li

来源 | Medium

编辑 | 代码医生团队

关于NLP的许多创新都是如何将上下文添加到单词向量中。常用的方法之一是使用递归神经网络。以下是递归神经网络的概念:

  • 它们利用顺序信息。
  • 他们有一个记忆,可以捕捉到到目前为止已经计算过的内容,即我=最后讲的内容将影响我=接下来要讲的内容。
  • RNN是文本和语音分析的理想选择。
  • 最常用的RNN是LSTM。

以上是递归神经网络的体系结构。

  • “ A”是前馈神经网络的一层。
  • 如果只看右侧,则会经常通过每个序列的元素。
  • 如果解开左侧,它将看起来完全像右侧。

假设正在解决新闻文章数据集的文档分类问题。

  • 输入每个单词,单词以某种方式彼此关联。
  • 当看到文章中的所有单词时,就会在文章结尾进行预测。
  • RNN通过传递来自最后一个输出的输入,能够保留信息,并能够在最后利用所有信息进行预测。
  • 这对于短句子非常有效,当处理长篇文章时,将存在长期依赖问题。

因此,通常不使用普通RNN,而使用长短期记忆。LSTM是一种RNN,可以解决此长期依赖问题。

在新闻文章示例的文件分类中,具有这种多对一的关系。输入是单词序列,输出是单个类或标签。

现在,将使用TensorFlow 2.0和Keras使用LSTM解决BBC新闻文档分类问题。数据集可以在这里找到。

https://raw.githubusercontent.com/susanli2016/PyCon-Canada-2019-NLP-Tutorial/master/bbc-text.csv

首先导入库,并确保TensorFlow是正确的版本。

代码语言:javascript
复制
import csv
import tensorflow as tf
import numpy as np
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
from nltk.corpus import stopwords
STOPWORDS = set(stopwords.words('english'))
 
print(tf.__version__)
  • 像这样将超参数放在顶部,以便更轻松地进行更改和编辑。
  • 当到达那里时,将解释每个超参数如何工作。
代码语言:javascript
复制
vocab_size = 5000
embedding_dim = 64
max_length = 200
trunc_type = 'post'
padding_type = 'post'
oov_tok = '<OOV>'
training_portion = .8
  • 定义两个包含文章和标签的列表。同时删除停用词。
代码语言:javascript
复制
articles = []
labels = []
 
with open("bbc-text.csv", 'r') as csvfile:
    reader = csv.reader(csvfile, delimiter=',')
    next(reader)
    for row in reader:
        labels.append(row[0])
        article = row[1]
        for word in STOPWORDS:
            token = ' ' + word + ' '
            article = article.replace(token, ' ')
            article = article.replace(' ', ' ')
        articles.append(article)
print(len(labels))
print(len(articles))

数据中有2,225条新闻文章,根据之前设置的参数将它们分为训练集和验证集,其中80%用于训练,20%用于验证。

代码语言:javascript
复制
train_size = int(len(articles) * training_portion)
 
train_articles = articles[0: train_size]
train_labels = labels[0: train_size]
 
validation_articles = articles[train_size:]
validation_labels = labels[train_size:]
 
print(train_size)
print(len(train_articles))
print(len(train_labels))
print(len(validation_articles))
print(len(validation_labels))

Tokenizer完成了所有繁重的工作。在标记化文章中,将使用5,000个最常用的词。oov_token当遇到看不见的单词时,要赋予特殊的值。这意味着要<OOV>用于不在中的单词word_index。fit_on_text将遍历所有文本并创建像这样的字典:

代码语言:javascript
复制
tokenizer = Tokenizer(num_words = vocab_size, oov_token=oov_tok)
tokenizer.fit_on_texts(train_articles)
word_index = tokenizer.word_index
dict(list(word_index.items())[0:10])

可以看到“ <OOV>”是语料库中最常见的标记,其次是“ said”,其次是“ mr”,依此类推。

标记化后,下一步是将这些标记转换为序列列表。以下是训练数据中已转为序列的第11条。

代码语言:javascript
复制
train_sequences = tokenizer.texts_to_sequences(train_articles)
print(train_sequences[10])

图1

当为NLP训练神经网络时,需要序列大小相同,这就是为什么要使用填充的原因。如果向上看,max_length长度是200,因此pad_sequences将所有文章的长度都设为200。结果会看到第一篇文章的长度为426,变成200,第二篇文章的长度为192。长度变为200,依此类推。

代码语言:javascript
复制
train_padded = pad_sequences(train_sequences, maxlen=max_length, padding=padding_type, truncating=trunc_type)
print(len(train_sequences[0]))
print(len(train_padded[0]))
 
print(len(train_sequences[1]))
print(len(train_padded[1]))
 
print(len(train_sequences[10]))
print(len(train_padded[10]))

另外,还有padding_type和truncating_type,都有post,例如,对于第11条,长度为186,填充为200,最后填充,即添加14个零。

代码语言:javascript
复制
print(train_padded[10])

图2

对于第一篇文章,长度为426,将其截短为200,最后也将其截断。

然后,对验证序列执行相同的操作。

代码语言:javascript
复制
validation_sequences = tokenizer.texts_to_sequences(validation_articles)
validation_padded = pad_sequences(validation_sequences, maxlen=max_length, padding=padding_type, truncating=trunc_type)
 
print(len(validation_sequences))
print(validation_padded.shape)

现在来看一下标签。因为标签是文本,所以将标记它们,在训练时,标签应该是numpy数组。因此,将标签列表变成numpy数组,如下所示:

代码语言:javascript
复制
label_tokenizer = Tokenizer()
label_tokenizer.fit_on_texts(labels)
 
training_label_seq = np.array(label_tokenizer.texts_to_sequences(train_labels))
validation_label_seq = np.array(label_tokenizer.texts_to_sequences(validation_labels))
print(training_label_seq[0])
print(training_label_seq[1])
print(training_label_seq[2])
print(training_label_seq.shape)
 
print(validation_label_seq[0])
print(validation_label_seq[1])
print(validation_label_seq[2])
print(validation_label_seq.shape)

在训练深度神经网络之前,应该探索原始文章和填充后文章的外观。运行以下代码,探索第11条文章,可以看到有些单词变成了“ <OOV>”,因为它们没有进入前5,000个排名。

代码语言:javascript
复制
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
 
def decode_article(text):
    return ' '.join([reverse_word_index.get(i, '?') for i in text])
print(decode_article(train_padded[10]))
print('---')
print(train_articles[10])

图3

现在是实施LSTM的时候了。

  • 建立tf.keras.Sequential模型并从嵌入层开始。嵌入层每个单词存储一个向量。调用时,它将单词索引序列转换为向量序列。经过训练,具有相似含义的单词通常具有相似的向量。
  • 双向包装器与LSTM层一起使用,它通过LSTM层向前和向后传播输入,然后连接输出。这有助于LSTM学习长期依赖关系。然后将其拟合到密集的神经网络中进行分类。
  • 用它们relu代替tahn功能,因为它们是彼此很好的替代品。
  • 添加了一个包含6个单位并softmax激活的密集层。当有多个输出时,softmax将输出层转换为概率分布。
代码语言:javascript
复制
model = tf.keras.Sequential([
    # Add an Embedding layer expecting input vocab of size 5000, and output embedding dimension of size 64 we set at the top
    tf.keras.layers.Embedding(vocab_size, embedding_dim),
    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(embedding_dim)),
#    tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(32)),
    # use ReLU in place of tanh function since they are very good alternatives of each other.
    tf.keras.layers.Dense(embedding_dim, activation='relu'),
    # Add a Dense layer with 6 units and softmax activation.
    # When we have multiple outputs, softmax convert outputs layers into a probability distribution.
    tf.keras.layers.Dense(6, activation='softmax')
])
model.summary()

图4

在模型摘要中,有嵌入的内容,双向包含LSTM,后跟两个密集层。双向的输出为128,因为它在LSTM中的输出增加了一倍。也可以堆叠LSTM层,但是发现结果更糟。

代码语言:javascript
复制
print(set(labels))

总共有5个标签,但是由于没有对标签进行单一编码,因此必须将其sparse_categorical_crossentropy用作损失函数,似乎认为0也是可能的标签,而令牌化程序对象则以整数1开头进行令牌化结果是,最后一个密集层需要输出标签0、1、2、3、4、5,尽管从未使用过0。

如果希望最后一个密集层为5,则需要从训练和验证标签中减去1。决定保留原样。

决定训练10个时期,正如将看到的,这是很多时期。

代码语言:javascript
复制
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
num_epochs = 10
history = model.fit(train_padded, training_label_seq, epochs=num_epochs, validation_data=(validation_padded, validation_label_seq), verbose=2)

图5

代码语言:javascript
复制
def plot_graphs(history, string):
  plt.plot(history.history[string])
  plt.plot(history.history['val_'+string])
  plt.xlabel("Epochs")
  plt.ylabel(string)
  plt.legend([string, 'val_'+string])
  plt.show()
  
plot_graphs(history, "accuracy")
plot_graphs(history, "loss")

图6

可能只需要3或4个时期。在训练结束时,可以看到有点过拟合。

Jupyter笔记本可以在Github上找到。

https://github.com/susanli2016/PyCon-Canada-2019-NLP-Tutorial/blob/master/BBC%20News_LSTM.ipynb

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 相约机器人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档