前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >观点 | 用于文本的最牛神经网络架构是什么?

观点 | 用于文本的最牛神经网络架构是什么?

作者头像
机器之心
发布2018-05-09 10:49:10
6600
发布2018-05-09 10:49:10
举报
文章被收录于专栏:机器之心

选自GitHub

作者:Nadbor Drozd

机器之心编译

参与:路雪、刘晓坤

用于文本的最牛神经网络架构是什么?数据科学家 Nadbor 在多个文本分类数据集上对大量神经网络架构和 SVM + NB 进行了测试,并展示了测试结果。

去年,我写了一篇关于使用词嵌入如 word2vec 或 GloVe 进行文本分类的文章(http://nadbordrozd.github.io/blog/2016/05/20/text-classification-with-word2vec/)。在我的基准测试中,嵌入的使用比较粗糙,平均文档中所有单词的词向量,然后将结果放进随机森林。不幸的是,最后得出的分类器除了一些特殊情况(极少的训练样本,大量的未标注数据),基本都不如优秀的 SVM,尽管它比较老。

当然有比平均词向量更好的使用词嵌入的方式,上个月我终于着手去做这件事。我对 arXiv 上的论文进行了简单的调查,发现大部分先进的文本分类器使用嵌入作为神经网络的输入。但是哪种神经网络效果最好呢?LSTM、CNN,还是双向长短期记忆(BLSTM)CNN?网上有大量教程展示如何实现神经分类器,并在某个数据集上进行测试。问题在于它们给出的指标通常没有上下文。有人说他们在某个数据集上的准确率达到了 0.85。这就是好吗?它比朴素贝叶斯、SVM 还要好吗?比其他神经架构都好?这是偶然吗?在其他数据集上的效果也会一样好吗?

为了回答这些问题,我在 Keras 中实现了多个神经架构,并创建了一个基准,使这些算法与经典算法,如 SVM、朴素贝叶斯等,进行比较。地址:https://github.com/nadbordrozd/text-top-model。

模型

该 repository 中所有模型都用 .fit(X, y)、.predict(X)、.get_params(recursive) 封装在一个 scikit-learn 相容类中,所有的层大小、dropout 率、n-gram 区间等都被参数化。为清晰起见,下面的代码已经简化。

由于我本来想做一个分类器基准,而不是预处理方法基准,因此所有的数据集都已被符号化,分类器得到一个符号 id 列表,而不是字符串。

朴素贝叶斯

朴素贝叶斯分为两种:伯努利(Bernoulli)和多项式(Multinomial)。我们还可以使用 tf-idf 加权或简单的计数推断出 n-gram。由于 sklearn 的向量器的输入是字符串,并给它一个整数符号 id 列表,因此我们必须重写默认预处理器和分词器。

代码语言:javascript
复制
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.naive_bayes import BernoulliNB, MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.svm import SVC

vectorizer = TfidfVectorizer(
    preprocessor=lambda x: map(str, x),
    tokenizer=lambda x: x,
    ngram_range=(1, 3))

model = Pipeline([('vectorizer', vectorizer), ('model', MultinomialNB())])

SVM

SVM 是所有文本分类任务的强大基线。我们可以对此重用同样的向量器。

代码语言:javascript
复制
from sklearn.svm import SVC

model = Pipeline([('vectorizer', vectorizer), ('model', SVC())])

多层感知器

又叫作 vanilla 前馈神经网络。该模型不使用词嵌入,输入是词袋。

代码语言:javascript
复制
from keras.models import Sequential
from keras.layers import Dense, Dropout, Activation
from keras.preprocessing.text import Tokenizer

vocab_size = 20000
num_classes = 3

model = Sequential()
model.add(Dense(128, input_shape=(vocab_size,)))
model.add(Activation('relu'))
model.add(Dropout(0.2))
model.add(Dense(128, input_shape=(vocab_size,)))
model.add(Activation('relu'))
model.add(Dropout(0.2))
model.add(Dense(num_classes))
model.add(Activation('softmax'))

model.compile(loss='categorical_crossentropy',
              optimizer='adam',
              metrics=['accuracy'])

该模型的输入需要和标签一样进行 One-hot 编码。

代码语言:javascript
复制
import keras
from keras.preprocessing.text import Tokenizer

tokenizer = Tokenizer(num_words=vocab_size)
X = tokenizer.sequences_to_matrix(X, mode='binary')
y = keras.utils.to_categorical(y, num_classes)

(双向)长短期记忆

从这里开始事情就变得有趣了。该模型的输入不是词袋而是一个词 id 序列。首先需要构建一个嵌入层将该序列转换成 d 维向量矩阵。

代码语言:javascript
复制
import numpy as np
from keras.layers import Embedding

max_seq_len = 100
embedding_dim = 37
# we will initialise the embedding layer with random values and set trainable=True
# we could also initialise with GloVe and set trainable=False
embedding_matrix = np.random.normal(size=(vocab_size, embedding_dim))
embedding_layer = Embedding(
    vocab_size,
    embedding_dim,
    weights=[embedding_matrix],
    input_length=max_seq_len,
    trainable=True)

以下适用于该模型:

代码语言:javascript
复制
from keras.layers import Dense, LSTM, Bidirectional
units = 64
sequence_input = Input(shape=(max_seq_len,), dtype='int32')

embedded_sequences = embedding_layer(sequence_input)
layer1 = LSTM(units,
    dropout=0.2,
    recurrent_dropout=0.2,
    return_sequences=True)
# for bidirectional LSTM do:
# layer = Bidirectional(layer)
x = layer1(embedded_sequences)
layer2 = LSTM(units,
    dropout=0.2,
    recurrent_dropout=0.2,
    return_sequences=False)  # last of LSTM layers must have return_sequences=False
x = layer2(x)
final_layer = Dense(class_count, activation='softmax')
predictions = final_layer(x)
model = Model(sequence_input, predictions)

该模型以及其他使用嵌入的模型都需要独热编码的标签,词 id 序列用零填充至固定长度:

代码语言:javascript
复制
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categorical

X = pad_sequences(X, max_seq_len)
y = to_categorical(y, num_classes=class_count)

François Chollet 的 cnn

该架构(稍作修改)来自 Keras 教程(https://blog.keras.io/using-pre-trained-word-embeddings-in-a-keras-model.html),专门为长度为 1000 的文本设计,因此我使用它进行文本分类,而不用于语句分类。

代码语言:javascript
复制
from keras.layers import Conv1D, MaxPooling1D

units = 35
dropout_rate = 0.2

x = Conv1D(units, 5, activation='relu')(embedded_sequences)
x = MaxPooling1D(5)(x)
x = Dropout(dropout_rate)(x)
x = Conv1D(units, 5, activation='relu')(x)
x = MaxPooling1D(5)(x)
x = Dropout(dropout_rate)(x)
x = Conv1D(units, 5, activation='relu')(x)
x = MaxPooling1D(35)(x)
x = Dropout(dropout_rate)(x)
x = Flatten()(x)
x = Dense(units, activation='relu')(x)
preds = Dense(class_count, activation='softmax')(x)
model = Model(sequence_input, predictions)

Yoon Kim 的 CNN

该架构来自 Yoon Kim 的论文(https://arxiv.org/abs/1408.5882v2.pdf),我基于 Alexander Rakhlin 的 GitHub 页面(https://github.com/alexander-rakhlin/CNN-for-Sentence-Classification-in-Keras)实现该架构。这个架构不需要规定文本必须为 1000 词长,更适合语句分类。

代码语言:javascript
复制
from keras.layers import Conv1D, MaxPooling1D, Concatenate

z = Dropout(0.2)(embedded_sequences)
num_filters = 8
filter_sizes=(3, 8),
conv_blocks = []
for sz in filter_sizes:
    conv = Conv1D(
        filters=num_filters,
        kernel_size=sz,
        padding="valid",
        activation="relu",
        strides=1)(z)
    conv = MaxPooling1D(pool_size=2)(conv)
    conv = Flatten()(conv)
    conv_blocks.append(conv)
z = Concatenate()(conv_blocks) if len(conv_blocks) > 1 else conv_blocks[0]

z = Dropout(0.2)(z)
z = Dense(units, activation="relu")(z)
predictions = Dense(class_count, activation="softmax")(z)
model = Model(sequence_input, predictions)

BLSTM2DCNN

论文作者称,结合 BLSTM 和 CNN 将比使用任意一个效果要好(论文地址:https://arxiv.org/abs/1611.06639v1)。但是很奇怪,这个架构与前面两个模型不同,它使用的是 2D 卷积。这意味着神经元的感受野不只覆盖了文本中的近邻词,还覆盖了嵌入向量的近邻坐标。这有些可疑,因为他们使用的嵌入之间(如 GloVe 的连续坐标)并没有关系。如果一个神经元在坐标 5 和 6 学习到了一种模式,那么我们没有理由认为同样的模式会泛化到坐标 22 和 23,这样卷积就失去意义。但是我又知道些什么呢!

代码语言:javascript
复制
from keras.layers import Conv2D, MaxPool2D, Reshape

units = 128
conv_filters = 32
x = Dropout(0.2)(embedded_sequences)
x = Bidirectional(LSTM(
    units,
    dropout=0.2,
    recurrent_dropout=0.2,
    return_sequences=True))(x)
x = Reshape((2 * max_seq_len, units, 1))(x)
x = Conv2D(conv_filters, (3, 3))(x)
x = MaxPool2D(pool_size=(2, 2))(x)
x = Flatten()(x)
preds = Dense(class_count, activation='softmax')(x)
model = Model(sequence_input, predictions)

堆叠

除了那些基础模型外,我还实现了堆叠分类器,来组合不同模型之间的预测。我使用 2 个版本的堆叠。一个是基础模型返回概率,概率由一个简单的 logistic 回归组合;另一个是基础模型返回标签,使用 XGBoost 组合标签。

数据集

对于文档分类基准,我使用的所有数据集均来自:http://www.cs.umb.edu/~smimarog/textmining/datasets/,包括 20 个新闻组、不同版本的 Reuters-21578 和 WebKB 数据集。

对于语句分类基准,我使用的是影评两极化数据集(http://www.cs.cornell.edu/people/pabo/movie-review-data/)和斯坦福情绪树库数据集(http://nlp.stanford.edu/~socherr/stanfordSentimentTreebank.zip)。

结果

一些模型仅用于文档分类或语句分类,因为它们要么在另一个任务中表现太差,要么训练时间太长。神经模型的超参数在基准中测试之前,会在一个数据集上进行调整。训练和测试样本的比例是 0.7 : 0.3。每个数据集上进行 10 次分割,每个模型接受 10 次测试。下表展示了 10 次分割的平均准确率。

文档分类基准

语句分类基准

结论

带嵌入的神经网络没有一个打败朴素贝叶斯和 SVM,至少没有持续打败。只有一层的简单前馈神经网络比任何其他架构效果都好。

我把这归咎于我的超参数,它们没有得到足够的调整,尤其是训练的 epoch 数量。每个模型只训练 1 个 epoch,但是不同的数据集和分割可能需要不同的设置。但是,神经模型显然在做正确的事,因为将它们添加至整体或者堆叠能够大大提高准确率。

原文地址:http://nadbordrozd.github.io/blog/2017/08/12/looking-for-the-text-top-model/

本文为机器之心编译,转载请联系本公众号获得授权。

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

本文分享自 机器之心 微信公众号,前往查看

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

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

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