Fasttext之Pytorch实现_理论部分

目录

1.fasttext引入

2.应用一:文档分类

3.应用二:词嵌入

4.层序softmax

5.总结


 

 

1.fasttext引入

之前学习fasttext时候,网上看了很多关于fasttext介绍,结果看的云里雾里,一头雾水,尤其是对于fasttext的n-gram输入更是感到困惑,在查阅了很多资料,知乎等,自我感觉算是对fasttext有个基础的认识。fasttext可以看做是word2vec的变种,具体来说其实有两种应用:一种是网上吵来吵去的分类模型,其出自于文章《Bag of Tricks for Efficient Text Classification》;另一种也是大家想知道的fasttext在词嵌入的应用,其文章出自《Enriching Word Vectors with Subword Information》。下面将具体介绍

2.应用一:文档分类

fasttext采用了cbow的模型,即如下所示:

cbow,英文为(continuous bag of word),后面是bag of word,我们知道bow模型是根据单词中char级字符的频数最后得出词向量向量,即将出现的所有字符放入一个袋子里面,考虑这个单词每个char出现的频数而做出的向量。而cbow所做的也是防着类似,将所有的背景词放入一个袋子里面(求和),然后选一个向量作为背景词代表(平均)。fasttext有什么不同?如大多数网上说的那样,主要的不同就是cbow是预测中心词,而fasttext则是预测类别标签。

具体来说:即一句话中有T个词,那么文档的向量表示即为这T个词经过词嵌入得到的词向量的平均,得到文档向量经过过一个线性层得到不同类别的分数,并经过输出层的softmax计算概率。此时,肯定有一个疑惑,就是:图上n-gram特征呢?

这里要强调4点(基于原文)

(1)在fasttext分类任务中,最小的粒度就是word,而非char(更专业的说法是subword,子词),就算是n-gram特征也是基于word的n-gram特征。

(2)word级别n-gram特征对于fasttext来说,只是锦上添花的东西(可以看如下图),添加了bigram特征后准确率添加了1%~3%;其次分类fasttext的输出可以是层序softamx(不懂第4节会细说层序softmax),也可以是普通的softma,视分类规模而定。

(3)在分类任务中的fasttext词向量也不是word2vec训练好的,就是普通随机初始化的词向量矩阵,因为fasttext的模型本来就是借鉴word2vec的,所以一切也都仿着他。

(4)cbow和fasttext在代码实现中求和平均而非求和,二者效果差距挺大的。

此时我们在说说一下n-gram特征:

举一个例子来说:

I love NJ university

那么bigram的特征为:I/love love/NJ NJ/university

作者认为添加额外的n-gram特征(记住分类级别就是word),可以弥补bag-of-word存在的忽略词序的弊端,实时上,引入了这些特征(同词嵌入一起求和平均)以后,确实可以给模型在分类的效果上带来一些提高。

那么上面一句话的向量表示为:

s=mean(v(I)+...++(university)+v(I/love)+..._+v(NJ/university))

3.应用二:词嵌入

fasttext在词嵌入的应用上依然沿用的还是word2vec模型,任务也没有变化,依旧是根据中心词预测上下文或者根据上下文预测中心词,这里我们放上word2vec的两种模型:

要清楚fasttext这两篇文章的通讯作者都是Tomas Mikolov,那在词嵌入中,fasttext与word2vec有什么本质上不同?粒度不同!在词嵌入任务中,fasttext的最小粒度是char,具体如何说呢?这里要先介绍char级的n-gram。

对于where一词,trigram的特征为:

whe  her  ere

此时我们会发现her不就是一个单词吗?正是为了区分到底是word嵌入还是char级的n-gram特征嵌入,fasttext会给所有的单词都加上<>,即<where>,此时我们在求得<where>的n-gram即为:

<wh whe  her  ere re>,

这时也会有her,但是我们一定知道其实n-gram特征,因为word的her表示为<her>

那有了char级的n-gram特征有什么用呢?要知道word2vec是基于word级别,因此它有一个弊端就是无法解决OOV的问题(未登陆词),只能用一个<unk>向量来代替所有这种未登陆词。而fasttext则不一定了,我们因为有了很多种char级的组合,对于未登陆词,我可以通过这些n-gram特征求和来得到为登陆词。比如:company单词是训练语料库中未出现的,在word中只能给这个单词一个<unk>的词嵌入,而在fasttext中我们可以对其n-gram求和得到:

v(study)=v(<stu)+v(stu)+v(tud)+v(udy)+v(dy>)

当然若出现没有搭配的char级字符,那就只能<unk>了,不过相较于word2vec的束手无策,fasttext至少尽最大的努力去尝试了。不过这种n-gram特征一定要对于那种组合起来有意义的单词效果更佳,如英语,其实中文的部首偏旁也是可以的,但是在实现上难度较大。

基本概念说完,下面我们开始正式关注模型的训练部分:

我们在word2vec中,每个词只有一种表示方法,即词嵌入。但是在fasttext中,我们的每个词由好几种表示:

(1)n-gram特征嵌入求和平均表示

(2)词嵌入表示

(3)词嵌入与n-gram特征嵌入求和平均表示

有了这个表示,在对应到word2vec模型中,就是把这表示放到两个位置上,允许word2vec模型即可,哪两个位置呢?

(a)中心词

(b)背景词

这样我们就能有6种组合方式:如a1b1  a1b2  a1b3  a2b1  a2b2 a2b3。

其中a2b2的组合就是我们的word2vec,那么fasttext采用哪种组合呢?a3b2(别问为什么其他不选,因为这个效果最好!!!)

即中心词我们通过词嵌入与n-gram特征嵌入的求和平均表示,而背景词就是单纯的词向量。最终模型进行预测,即采用cbow的上下文预测中心词或者skip-gram的中心词预测上下文,优化方式也和word2vec一样,有两种,即分别是负采样和层序softmax。最终我们会得到词嵌入矩阵和n-gram嵌入矩阵,如下图所示:

由于n-gram特征矩阵数量远远大于词的数量,因此作者建议放在hash桶里面(就是python里面的字典,java的hashmap)。我在word2vec并未细说层序softmax,这里一并补上。

4.层序softmax

大多数应对多分类问题都是最终一个softmax,而softmanx的公分母是所有类别的概率求和,类别很多的时候(比如word2vec),其计算量将会非常巨大。而层序softmax其实也可以看做是针对softmax的优化。其根据频数建立一颗哈夫曼树,其叶子节点保留类别信息,其他节点保存参数。如下所示:(哈夫曼编码这里就不细说了)

那么原来的计算某一类别的概率为:

p(y=j|x)=\frac{\theta_j^Tx}{\sum_{i\in Y}exp\{\theta_i^Tx\}}

要遍历所有类别,因此复杂度为:O(|Y|)。而哈夫曼编码计算某一类别概率变为:(也就是类别节点的祖先节点概率连乘),

p(y=j|x)=\prod_{l=1}^{Lay(y=j)-1}p

这里的p简单表示为类别节点(也就是叶子节点)之前的祖先节点(唯一)。若上图则是:

p(y=2|x)=p(n(y_2,1))\cdot p(n(y_2,2))\cdot p(n(y_2,3))

这里用n(y_i,l)表示某节点,此时我们发现,我们计算的复杂度不在与多少类别有关了,而与此类别所在树的高度h有关,即复杂度为h=log|Y|。类别概率计算更专业的写法为:

p(y=i|x)=\prod_{l=1}^{L(y_i)}\sigma(f(n(y_i,l+1)=LC(n(y_i,l)))\theta_{n(y_i,l)}X)

其中LC(n(y_i,l))表示节点n(y_i,l)的左孩子,\theta_{n(y_i,l)为节点n(y_i,l)参数,L(y_i)表示路径长度,X为其句向量,f(\cdot)是一个特殊函数,即为:

f(x)=\begin{cases} +1,&\text{x=true},\\ -1,& \text{x=false}. \end{cases}

我们隐隐约约觉得f(\cdot)具有控制方向的功能,为什么需要这样写呢?

在模型的训练过程中,通过Huffman编码,构造了一颗庞大的Huffman树,同时会给非叶子结点赋予向量。我们要计算的是类别y的概率,这个概率的具体含义,是指从root结点开始随机走,走到类别y的概率。因此在途中路过非叶子结点(包括root)时,需要分别知道往左走和往右走的概率。到达非叶子节点n的时候往左边走和往右边走的概率分别是:

p(n,left)=\sigma(w_{n}X)\\ p(n,right)=1-\sigma(w_{n}X)=\sigma(-w_{n}X)

因此,可以通过x的正负号来判断往左往右。

比如,给出下面一颗哈夫曼树,非叶子节点存储当前的权重:

比如在训练的时候,我们的类别为y_4,那么我们的编码为“01”,此时其实编码“01"已经告知的路径(唯一):

因此从根节点出发,编码为0,说明往右,则:

p_1=\sigma(-w_1s)

到达根节点的右孩子以后,编码为1,说明往左,则:

p_2=\sigma(w_2s)

那么最终,类别为y_4的概率为:

p(y_4|s)=p_1p_2=\sigma(-w_1s)\sigma(w_2s)

而我们要做的就是最大化p(y_4|s),也就在不断努力最大化p_1,p_2,使得往对应方向的概率大于另一个方向概率。当然在实现的时候要原本的概率连乘求最大等价转换成的负号对数概率求和取最小,这里不再赘述。

我们在测试阶段没有方向指引的时候,我们就计算当前阶段的往左的概率p(n,left)=\sigma(\theta_{n}X),若其大于0.5则往左边,若其小于0.5,则往右边,直至到叶节点取出类别信息。

5.总结

现在在细看看fasttext,其实发现其对word2vec模型根本没有进行任何变化,真正变化的就是输入输出做的调整。真正的亮点就在于:n-gram特征的引入。在分类中,引入word的n-gram特征与词嵌入一起求和平均得到文档向量经过隐层(线性分类器)进行分类。在词嵌入中,通过引入char级的n-gram特征来减缓word2vec的oov问题(glove关于oov的问题也没有应对措施)。

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FastText是Facebook开发的一种文本分类算法,它通过将文本分解成n-gram特征来表示文本,并基于这些特征训练模型。PyTorch是一个流行的深度学习框架,可以用于实现FastText文本分类算法。 以下是使用PyTorch实现FastText文本分类的基本步骤: 1. 数据预处理:将文本数据分成训练集和测试集,并进行预处理,如分词、去除停用词、构建词典等。 2. 构建数据集:将预处理后的文本数据转换成PyTorch中的数据集格式,如torchtext中的Dataset。 3. 定义模型:使用PyTorch定义FastText模型,模型包括嵌入层、平均池化层和全连接层。 4. 训练模型:使用训练集训练FastText模型,并在验证集上进行验证调整超参数。 5. 测试模型:使用测试集评估训练好的FastText模型的性能。 以下是一个简单的PyTorch实现FastText文本分类的示例代码: ```python import torch import torch.nn as nn import torch.optim as optim from torchtext.legacy.data import Field, TabularDataset, BucketIterator # 数据预处理 TEXT = Field(tokenize='spacy', tokenizer_language='en_core_web_sm', include_lengths=True) LABEL = Field(sequential=False, dtype=torch.float) train_data, test_data = TabularDataset.splits( path='data', train='train.csv', test='test.csv', format='csv', fields=[('text', TEXT), ('label', LABEL)] ) TEXT.build_vocab(train_data, max_size=25000, vectors="glove.6B.100d") LABEL.build_vocab(train_data) # 定义模型 class FastText(nn.Module): def __init__(self, vocab_size, embedding_dim, output_dim): super().__init__() self.embedding = nn.Embedding(vocab_size, embedding_dim) self.fc = nn.Linear(embedding_dim, output_dim) def forward(self, x): embedded = self.embedding(x) pooled = embedded.mean(0) output = self.fc(pooled) return output # 训练模型 BATCH_SIZE = 64 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') train_iterator, test_iterator = BucketIterator.splits( (train_data, test_data), batch_size=BATCH_SIZE, sort_within_batch=True, device=device ) model = FastText(len(TEXT.vocab), 100, 1).to(device) optimizer = optim.Adam(model.parameters()) criterion = nn.BCEWithLogitsLoss().to(device) for epoch in range(10): for batch in train_iterator: text, text_lengths = batch.text labels = batch.label optimizer.zero_grad() output = model(text).squeeze(1) loss = criterion(output, labels) loss.backward() optimizer.step() with torch.no_grad(): total_loss = 0 total_correct = 0 for batch in test_iterator: text, text_lengths = batch.text labels = batch.label output = model(text).squeeze(1) loss = criterion(output, labels) total_loss += loss.item() predictions = torch.round(torch.sigmoid(output)) total_correct += (predictions == labels).sum().item() acc = total_correct / len(test_data) print('Epoch:', epoch+1, 'Test Loss:', total_loss / len(test_iterator), 'Test Acc:', acc) ``` 这个示例代码使用了torchtext库来处理数据集,并定义了一个FastText模型,模型包括一个嵌入层、一个平均池化层和一个全连接层。模型在训练集上训练,并在测试集上进行测试,并输出测试集的损失和准确率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值