1. fastText模型原理
fastText大约是NLP文本分类任务中最简单最直观的模型架构之一了,其原始文献详见参考链接2,facebook也提供了相应的开源工具,可以相当便利地用来训练一些nlp分类模型。网上其实已经有了不少有关fasttext模型的介绍,比如下述参考链接3和4。
但这里,出于内容的完整性考虑,我们还是首先来介绍一下fasttext模型的结构,后面再给出基于fasttext开源工具、tensorflow以及pytorch的代码实现。
fasttext模型的最核心的结构可以由下图进行表示:
首先,句子中的每一个token的id经过一个embedding层转换为词向量,而后将句子中所有的词向量求一个平均即可得到整个句子的向量表达,最后通过一个全连接层转换为最后的输出,其中全连接层的激活函数直接采用softmax函数。
用公式表达如下:
y = s o f t m a x ( W ⋅ m e a n ( x ) b ) y = softmax(W cdot mean(x) b) y=softmax(W⋅mean(x) b)
下面,我们使用imdb的电影评论分类数据来实际考察一下fasttext的应用。
2. facebook的fastText模块使用
facebook的fasttext开源项目的GitHub仓库链接详见下述参考链接1。
fasttext工具的训练数据格式如下:
代码语言:javascript复制__label__正向 今 天 天 气 真 好 !
其数据开头为一个以__label__
为开头的标签,后面为以空格字符隔开的文本的tokens。
处理数据后训练模型的方法如下:
代码语言:javascript复制model = fasttext.train_supervised('data/fasttext/train.txt', epoch=50, lr=0.05, dim=300)
训练完成之后调用模型的方法为:
代码语言:javascript复制data = ["今 天 天 气 真 好 !"]
ret = model.predict(data)
# ret = (["__label__正向"], [1.0])
模型的输入为训练数据中除标签外的文本,即将tokens使用空格字符进行分隔,而输出为一个二元元组,其中第一个元素为label的list,第二个元素为各个预测结果对应的预测概率。
我们在imdb数据中进行分类模型训练,训练得到模型结果如下:
代码语言:javascript复制 precision recall f1-score support
__label__1 0.48 0.85 0.61 5022
__label__10 0.45 0.77 0.57 4999
__label__2 0.00 0.00 0.00 2302
__label__3 0.67 0.00 0.00 2541
__label__4 0.28 0.39 0.33 2635
__label__7 0.29 0.14 0.19 2307
__label__8 0.27 0.26 0.26 2850
__label__9 0.00 0.00 0.00 2344
accuracy 0.41 25000
macro avg 0.30 0.30 0.24 25000
weighted avg 0.34 0.41 0.32 25000
3. 使用tensorflow构建fastText模型
现在,我们来使用tensorflow来构建一个简单的fasttext模型,并在imdb数据集中进行测试。
给出模型代码如下:
代码语言:javascript复制class MyLoss(tf.keras.losses.Loss):
def __init__(self, label_num):
super().__init__()
self.label_num = label_num
self.cross_entropy = tf.keras.losses.CategoricalCrossentropy()
def call(self, y_true, y_pred):
y_true = tf.one_hot(tf.squeeze(y_true, axis=1), self.label_num)
return self.cross_entropy(y_true, y_pred)
class FasttextModel(tf.keras.Model):
def __init__(self, vocab_size, label_num, dim=100):
super().__init__()
self.embedding_layer = tf.keras.layers.Embedding(vocab_size, dim)
self.dense_layer = tf.keras.layers.Dense(label_num, activation=tf.nn.softmax)
def call(self, inputs, training=False):
m = self.embedding_layer(inputs)
m = tf.math.reduce_mean(m, axis=1)
logits = self.dense_layer(m)
if training:
return logits
else:
return {
"tag": tf.math.argmax(logits, axis=-1),
"prob": tf.math.reduce_max(logits, axis=-1)
}
model = FasttextModel(vocab_size=vocab_size, label_num=label_num, dim=300)
model.compile(optimizer=tf.keras.optimizers.Adam(), loss=MyLoss(label_num))
model.fit(x=train_src, y=train_tgt, batch_size=1024, epochs=40)
测试得到结果如下:
代码语言:javascript复制 precision recall f1-score support
0 0.52 0.67 0.59 5022
1 0.22 0.11 0.15 2302
2 0.22 0.16 0.18 2541
3 0.25 0.27 0.26 2635
4 0.22 0.21 0.22 2307
5 0.24 0.24 0.24 2850
6 0.22 0.11 0.15 2344
7 0.48 0.62 0.54 4999
accuracy 0.37 25000
macro avg 0.30 0.30 0.29 25000
weighted avg 0.34 0.37 0.35 25000
和facebook的结果略有差别,但是整体上觉得差异不大。
4. 使用torch构建fastText模型
同样的,我们使用torch来进行fasttext模型的训练。
给出python代码如下:
代码语言:javascript复制class MyDense:
def __init__(self, input_dim, output_dim):
self.linear = torch.nn.Linear(input_dim, output_dim)
def __call__(self, x):
return torch.nn.functional.softmax(self.linear(x), dim=-1)
class FasttextModel(torch.nn.Module):
def __init__(self, vocab_size, label_num, dim=100):
super().__init__()
self.embedding_layer = torch.nn.Embedding(vocab_size, dim)
self.dense_layer = MyDense(dim, label_num)
def forward(self, inputs):
m = self.embedding_layer(inputs)
m = torch.mean(m, axis=1)
logits = self.dense_layer(m)
return logits
def train_model(x, y, vocab_size, label_num, epochs=20, dim=300, batch_size=1024):
model = FasttextModel(vocab_size, label_num, dim)
optimizer = torch.optim.Adam(model.parameters())
loss_fn = torch.nn.CrossEntropyLoss()
for epoch in range(epochs):
with tqdm(range((len(x)-1) // batch_size 1), ncols=100) as t:
for i in t:
optimizer.zero_grad()
y_pred = model(torch.tensor(x[i*batch_size:(i 1)*batch_size]))
y_true = torch.tensor(y[i*batch_size:(i 1)*batch_size])
loss = loss_fn(y_pred, y_true)
loss.backward()
optimizer.step()
print("train {} epochs, loss = {}".format(epoch 1, loss.item()))
return model
评测得到结果如下:
代码语言:javascript复制 precision recall f1-score support
0 0.34 0.84 0.49 5022
1 0.00 0.00 0.00 2302
2 0.00 0.00 0.00 2541
3 0.00 0.00 0.00 2635
4 0.00 0.00 0.00 2307
5 0.00 0.00 0.00 2850
6 0.00 0.00 0.00 2344
7 0.31 0.80 0.45 4999
accuracy 0.33 25000
macro avg 0.08 0.20 0.12 25000
weighted avg 0.13 0.33 0.19 25000
结果蛮怪的,看loss也下降的极其缓慢,但是代码也没检查出来哪里有问题,感觉后面还需要仔细考察一下,当然如果有大神比较了解的话希望可以直接帮忙看一下。
5. 总结
在上述三种fasttext模型的模型训练结果当中,结果多少有点层次不齐,但是整体效果都卡在了0.4附近,而网上查到的imdb数据的sota结果事实上已经轻松达到了0.968,和我们的结果天差地别。
归根结底,这个任务使用fasttext模型就太过暴力了,因为fasttext模型本质上来说就是基于文本中典型的代表词汇来表征整个句子,因此针对这个任务本来就不是一个很好的实现手段,相较之下,哪怕只是用LSTM做一个分类模型估计都会比这个效果好很多,因为会联合去考虑上下文的表达。
因此,这里,更多的我们只是用这个例子来介绍一下fasttext模型,并借以介绍一下fasttext模块的用法以及如何基于tensorflow以及pytorch来实现fasttext模型。其相应的代码均已放置到我的GitHub当中,其仓库链接如下:
- https://github.com/CodenameCYS/fasttext_exp
6. 参考链接
- https://github.com/facebookresearch/fastText
- https://arxiv.org/abs/1607.01759
- fastText原理和文本分类实战,看这一篇就够了
- NLP︱高级词向量表达(二)——FastText(简述、学习笔记)