观点 | 用于文本的最牛神经网络架构是什么?
选自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、朴素贝叶斯等,进行比较。地址:http://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 列表,因此我们必须重写默认预处理器和分词器。
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 是所有文本分类任务的强大基线。我们可以对此重用同样的向量器。
from sklearn.svm import SVC
model = Pipeline([("vectorizer", vectorizer), ("model", SVC())])
多层感知器
又叫作 vanilla 前馈神经网络。该模型不使用词嵌入,输入是词袋。
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 编码。
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 维向量矩阵。
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)
以下适用于该模型:
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 序列用零填充至固定长度:
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 教程(http://blog.keras.io/using-pre-trained-word-embeddings-in-a-keras-model.html),专门为长度为 1000 的文本设计,因此我使用它进行文本分类,而不用于语句分类。
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 的论文(http://arxiv.org/abs/1408.5882v2.pdf),我基于 Alexander Rakhlin 的 GitHub 页面(http://github.com/alexander-rakhlin/CNN-for-Sentence-Classification-in-Keras)实现该架构。这个架构不需要规定文本必须为 1000 词长,更适合语句分类。
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 将比使用任意一个效果要好(论文地址:http://arxiv.org/abs/1611.06639v1)。但是很奇怪,这个架构与前面两个模型不同,它使用的是 2D 卷积。这意味着神经元的感受野不只覆盖了文本中的近邻词,还覆盖了嵌入向量的近邻坐标。这有些可疑,因为他们使用的嵌入之间(如 GloVe 的连续坐标)并没有关系。如果一个神经元在坐标 5 和 6 学习到了一种模式,那么我们没有理由认为同样的模式会泛化到坐标 22 和 23,这样卷积就失去意义。但是我又知道些什么呢!
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/
本文为机器之心编译,转载请联系本公众号获得授权。
?------------------------------------------------
加入机器之心(全职记者/实习生):hr@jiqizhixin.com
投稿或寻求报道:content@jiqizhixin.com
广告&商务合作:bd@jiqizhixin.com
- 观点丨补齐半导体产业链硅片短板刻不容缓
- 好项目:一种用于监测心力衰竭的感官植入体
- 广东专列100万元用于奖励举报人
- Suplemon:带有多光标支持的现代 CLI 文本编辑器
- 明日将会如何,观点速看!
- 中医观点:肾病,伤精,白血病和肿瘤如何生成和消失
- 黄金12月13号开始布局小波段多单--个人交易观点
- 禁止摸鱼!字幕组成员盗用他人文本,被组长罚抄100遍“我错了
- 收入增长为什么跟不上房价上涨?丨观点
- 用比特币也能用于房产交易了!