1. SWA(随机权重平均)
简单来说就是将指定epochs范围内训练出来的模型,将这些模型的weight取平均赋值给这个swa_model。
- Pytorch内置的swa,但是引入了一个SWALR。
- 一个项目用到的。
2. attack training(对抗训练)
1 | import torch |
1 | # 初始化 |
简单来说就是将指定epochs范围内训练出来的模型,将这些模型的weight取平均赋值给这个swa_model。
1 | import torch |
1 | # 初始化 |
讯飞之前发布了一个事件抽取挑战赛,这个比赛分成两个任务:
1、任务一:事件触发词及论元抽取
2、任务二:事件属性抽取
搜了下github,发现第一名的解决方案。
看readme介绍主要思路,作者将任务分割为触发词抽取,论元抽取,属性抽取
。
那本篇即讲述触发词(trigger提取器)的实现方式。
修改train.py,增加下面这些:
1 | args = TrainArgs().get_parser() |
主要代码看这里。
[BLANK]
,没有tokenize的换成了[INV]
。虽然他这种做法我勉强能接受,但是还是理解不了。。。因为tokenizer该做的都做了,做这一步不就多此一举么。。。(除非有其他理由)1 |
|
看到有意思的地方了么,以char level进行分割raw_text,然后找到公布
对应的start_index和end_index置为1。
1 | TriggerExtractor( |
输入input_ids, attention_mask, token_type_ids获取bert output,然后mid_linear,最后classifier,shape变成比如32,128,2
,核心地方在计算了loss和解码部分。
举个例子计算loss:
1 | # -*- coding: utf8 -*- |
代码。
1 | 缺点:存在5%的句子会出现解码为空的现象,导致误差传播极大; |
这个在作者的ppt里面提到了。
作者舍弃CRF结构,采用指针式解码
的方案。这个地方是我觉得最有意思的地方。
我为什么任务这个地方是优点呢,初看网络结构,我以为的网络结构类似下面这样:
1 | bert |
也就是说bert后面有两个linear,一个是start_index的,一个是end_index的,然后分别预测开始和结束index。
但是这么做有几个坏处:
1、start_index和end_index没有任何交互
2、解码的时候,会解码出很多的结果
那作者的优势就很明显了,start_index和end_index用一个linear表示。
但是即使这样,都绕不开一个地方,就是解码出多个结果
和长度
的问题。
解码的长度是不确定的,只要start_logits > 0.5,end_logits>0.5,就认为是合理的。那么这两者之间组合,就会有许多的结果。这个是绕不开的。
作者这里做了一个限制,看代码:
1 | candidate_entities = [] |
即限制了触发词
的长度
。
第二,在这个比赛里,绝大多数的样本的触发词就为1个。
如果解码为空的话,作者采用start_logits和end_logits最大的作为输出。代码。
本质来讲,我没太看得出来和crf相对比的优劣。作者也没有公开每个任务分别的score。
但是从模型结构上来讲,这种方式有个好处,就是可以解决嵌套
的问题。第二就是速度的好处。
1、没有考虑数据增强
2、没有use_distant_trigger,即将构造的trigger输入ebedding作为extra feature(即将句子中所有可能的触发词都列出来然后输入到embeddings和bert output concat到一起)。
1 | tokenizer = BertTokenizer.from_pretrained(bert_dir) |
最近在做句子结构分析的模型,具体样例如下图左图所示。
因为之前看过constituency parser任务,故尝试将这棵树转成满足consituency parser满足的那种格式,转换后的样例如下图右图所示。
![]() |
---|
找个简单的例子如下所示。
![]() |
---|
所以隆重推荐这款转换工具,constituency-tree-labeling-tool,目前这个工具已经在我司使用以及在hanlp和yuzhangcs/parser中被提及。
因为有这个工具的加持,我们就可以尝试使用consitutuency parser的方式来做这个任务。目前模型也已经被业务使用。
我发现在真实使用的时候,会将这棵树按照一层为单位进行拆开,即:
基于上面的使用方式,可以进行以下方面的尝试:
句子:['商务部', '开展', '首批步行街改造提升试点工作', '。']
。
比如上面这句话,他首先构造一个9*9的临界矩阵(按照分词个数),因为这句话只能拆出来一个完整子句,故只有第一行有label,剩下的8行全是空的。
即:
1 | [['B-主', 'B-谓', 'B-宾', 'I-宾', 'I-宾', 'I-宾', 'I-宾', 'I-宾', 'B-符号'], |
那这里变成了两个问题:
args-level。
输入一句话,预测有几个子句。
这里就一个需要注意的地方,就是设置一个层数上限,比如10层。那么bert输出比如(32, 128, 768),将其expand至(32, 10, 128, 768),然后接一个768 * 768
的linear,将bert的语义表示用这个linear对每一层重新获取对应的语义表示。
args-level-crf-parser。
就是将上面两个模型集成到一个任务里,两个loss分配不同的weight。
1.实验
训练层数预测模型时,发现在验证集上F1一直只到0.7~0.8
之间,和我最初预想能达到0.95以上有很大的差距,虽然训练集上基本达到100%准确。
2.思考
后来我想了下,我觉得有两部分原因:
为什么我会有第二个见解?我在训练中途看了transformers库集成的多分类代码,发现和我的没什么区别,这让我很纠结,难道是梯度裁剪、dropout、optimizer各方面没有设置好??还是模型我不应该用electra,以及不应该使用交叉熵作为损失函数,而是可以尝试下FocalLoss?
结果在种种测试下,都没有达到一个有效的改善。
后来我觉得可能就是我对这个任务的准确度抱有的期望过高,为什么呢?
因为从人角度来看,都未必能准确知道一个句子应该分成几个子句。
最终结果是没有达到预期的,一开始我先写的联合模型,但是发现在验证集上的层数预测的f1最多到0.8,label预测准确率更低。
后来我对这两个模型进行拆分成两个任务来训练,以为可以改进层数预测的结果,但是分开后在验证集上层数预测的F1仍旧只能到0.8左右~,所以联合模型没有大的问题。
对于CRF预测,label有40个左右,加之label的数量分布严重不均衡,那么即使假设这个任务的f1可以达到0.8,那两个任务的最终结果也就在0.6以上。
那么由此可证,这种方式可能走不通。
args0complement。(忽略名字啦,本想留给主语补齐的。。。)
这种方式是使用嵌套ner的方式来表示这些数据。
对于上面这句话,将其转成一个9*9临接矩阵(按照分词进行构成)。
那他将会落在位置(0, 0), (1, 1), (2, 7), (8, 8)上,并且其标签分别是:主语
,谓语
,宾语
,符号
。
他将面临的最主要的问题:
虽然位置和标签都可以表示了,但是其之间的关联关系并没有。
那么训练时没有问题,但是在解码的时候就会出现多种组合,比如:
1 | (1, 2, 3, 4) |
为什么会有这么多种组合呢?
因为一棵树是由多层组成的,那么其相互之间是可以在不同层进行组合。
转成一种方便理解的方式,即:
1 | 主谓宾符号 |
那么难点就是从这一堆的路径里面选取合适的路径出来。
1.实验
首先我没想到模型结果会出乎意料好许多,不是从这个嵌套ner任务本身的准确率,而是从这一套的实现方式上,效果得到了一个很大的提升。
这个给了极大的信心去写后面多条路径解码的代码😅😅😅。
做法上我是先按照dependency-parser的方式来做,分成两个loss,一个是arc loss,还有一个rel loss。其中arc loss表示在这个临接矩阵中存在对应关系,那么就用1表示,否则0,即位置。rel_loss表示label在这个临接矩阵中的位置以及对应的label。
dependency-parser在位置解码的时候是以arc-loss为准,因为基本毫无疑问arc相对是更容易的,我也按照这个思路进行的,不过最终发现直接用rel loss就已经足够了。。
![]() |
![]() |
---|
从上图可以看到:
1 | eval[2]: 109it [00:25, 4.25it/s] |
这地方有两个需要学习的:
1 | # -*- coding: utf8 -*- |
1 | from unittest import TestCase |
书接上文,上文在计算loss时一共分为两个。
例如:
训练数据集 | 样本 |
---|---|
original_text | 我和你在一其 |
correct_text | 我和你在一起 |
det_labels | 0 0 0 0 0 1 |
我和你在一器
。此为mlm loss。那这篇文章改动了什么?
det_labels的获取方式更改了,怎么更改的?以mlm_result和correct_text进行对比进行获取。
如上例:即mlm_result输出我和你在一器
,和correct_text我和你在一起
进行对比。
即用GAN的思想来进行corrector!
此处我将mlm预测称为generator
(G
),二分类判别称为discriminator
(D
)。
分配G
和D
两者loss不同的weight。
1
和50
![]() |
![]() |
---|
可以看到一个现象,不管dev_metric还是train_metric,在epoch2和epoch3的时候,它的准确度很突然下降,对应的loss也会比刚开始的大的许多。随后效果才会慢慢的提升。
0.9
和0.1
![]() |
![]() |
---|
如果我把上图dev_metric展开下再和train_metric放到一起对比:
![]() |
![]() |
---|
可以看到效果:
0.1
和0.9
![]() |
![]() |
---|
0.1
和0.9
,并且判别器添加GELU![]() |
![]() |
---|
上面这些实验例子都有一个共同的现象:
1
和50
首先解释为什么选择1和50,这个地方来自electra地方的思想,可看上文。
0.9
和0.1
整体很平滑,仿佛判别器没有起到作用似的或者作用很小。
0.1
和0.9
没什么大的意外。
但是让我意外的是下面4情况。
0.1
和0.9
,并且判别器添加GELU我在看《机器学习实战:基于Scikit-Learn和TensorFlow》第二版 时里面讲到训练GAN有些小trick,比如可以给判别器添加一个激活函数。
但是当添加后,发现其抖动的更为严重。。。
这个是个实验值,没有具体理论依据哈,本身就是超参。看下图:
![]() |
![]() |
---|
d_loss和g_loss分别是判别器和生成器各自的loss。
从上图可以看出,d_loss比g_loss小大概9~10倍,所以依据来源在此。
当然还有一些其他的小技巧,就不做实验了,gan本来就难搞。。。
比如:
啥是模式崩溃?
就是当生成器学会苹果后,那判别器就会判别苹果。当生成器转头学会橘子后,那判别器会对橘子判别更准。
而对苹果和橘子共同存在的情况下效果不好。而达到一个纳什均衡。
此篇文章是对macbert4csc模型的一次尝试与思考,废话不多说,看内容。
[x180/macbert4csc-scalarmix-base-chinese]https://huggingface.co/x180/macbert4csc-scalarmix-base-chinese)。
这个模型分成两部分:
举例:
比如错句为我和你在一其
,正确的句子为我和你在一起
,错句输入MLM,得到的结果假设为我和你在一器
。然后输入linear判别器,判断和正确的句子做二分类。最后两个loss进行相加。
首先看看什么是macbert,全称是(MLM as corrector),它是哈工大和讯飞一起训练的模型。主要做的内容有两点:
第一条的做法好处是下游任务一般不会有[mask],那么就不会带来预训练模型和下游任务不匹配的问题。
第二条的做法好处是简单来讲就是更能理解句子的语义性(合理以及通顺)。
这个问题就是为什么选择macbert做微调,因为macbert对mlm中做mask替代的地方用了同义词替换。
这个问题是我自己强行加进来的,哈哈。。
先来看下electra的结构图。
electra模型分成两部分:
the
和painting
做[MASK]
,然后使用生成器进行训练获取结果, 其中the
对了,painting
对应了car
。painting
生成错了。其loss计算方式如下所示:
有木有发现,两者其实还蛮像的,哈哈。
下面是正经思考:
回答1:我觉得阔以,甚者我觉得结果要比macbert4csc更好,为什么?
回答2:我觉得阔以,因为对于多loss,一般没有好的解决方法,明明超参数,只能实验呗。作者在这里选择了50,因为他认为
二分类相比mlm的交叉熵更容易一些,所以给予了更大的权重。
但是我觉得还可以有另外一种思路来参考,即灾难性遗忘
,它是说下游任务对预训练模型进行fine-tune,会对预训练模型造成干扰,那么loss计算时可以分成两部分,pretrained loss和fine-tune loss,一般按经验fine-tune loss权重为0.5。
作者这里使用0.7/0.3的权重分配给MLM和sigmoid二分类,这里我对其做了如下调整:
ID | MLM weight | sigmoid二分类 weight | dev metric |
---|---|---|---|
1 | 1 | 0.5 | epoch26:0.9168 |
2 | 1 | 5 | epoch35:0.914 |
3 | 1 | 50 |
以macbert eval.py为准进行测试,记得把macbert_model_dir改成自己的。
以epoch 26的模型为准:
1 | Sentence Level: acc:0.7040, precision:0.8545, recall:0.6087, f1:0.7109, cost time:5.35 s |
和”shibing624/macbert4csc-base-chinese”进行对比,发现recall要高了不少,结果效果更好了些~。
dev metric 在epoch 35的时候为0.914,相比ID(1)在epoch 26 0.9168的结果,发现收敛速度变的慢了。
故这个忽略。
dev metric 在epoch 35的时候为0.9112,虽说还有小量上涨可能,但是train metric已经过拟合了,这个结果让我觉得…,emmmm,这些超参没有达到想象中的区别哇。
作者在进行二分类的时候使用了最后一层的hidden_states,那么是否可以对所有的hiddeen_states分配不同的权重。为什么?前面的layer更偏向浅层语义信息,比如词法,句法等,越往后代表的含义可能更深层次,本来就是预测词对不对,搞那么深不一定适合。
所以下面对其进行尝试。
添加scalarmix层,代码如下:
1 | class ScalarMix(nn.Module): |
结果如下:
ID | MLM weight | sigmoid二分类 weight | dev metric |
---|---|---|---|
1 | 1 | 0.5 | epoch12:0.9118 |
为啥epoch 12就停了,因为train metric基本已经达到1了。。。
由此可见,整个准确率的提升,更多是在mlm这个任务上,加了二分类判断其对不对,整体影响并没那个大。。。
后来我发现了一个地方,mlm weight和二分类weight我给的是1和0.5,压缩到1之内就分别是0.6666和0.3333,我嘞个去,这不基本和作者的0.7和0.3很相近了么。。下次需要提前注意下。
当我对mlm loss weight分配了更高的权重,结果发现效果有提升。
备忘一下~
之前在看腾讯开源的词向量时,Tencent AI Lab Embedding Corpus for Chinese Words and Phrases,在看到Simple Cases那里,瞬间感到震撼!!果然大公司就是大公司,有钱有地位。。。
跟着公司做了一些技术研究和项目后,发现目前nlp之所以发展没到位,最主要原因就是:算法和数据分家
。算法层面,目前整个学术界没有大的进步,另外像GPT3这种,一般玩不起。数据层面,没人开源数据,即使开源了,标注质量参差不齐,标注标准也是如此。那大家比什么,比的只有算法喽,结果最后就是华而不实(工业上应用)~。
那回到开始,觉得震撼之余,就在想那腾讯肯定也有相应的分词,结果找了半天,没找到。今天突然发现了,窃喜之余,记录一下。
关于它的介绍,TexSmart: 文本理解工具与服务,以及它的Demo,整体效果看下来,要好于目前很多开源的(当然,你懂我意思),大家算法可能都差不多。
但是分为离线版和http api版,差距有多大,这个木有尝试,反正官方说有差距。
以后尝试尝试。
参考地址:
这篇文章主要对pycorrector默认使用规则的代码进行debug和理解,不论怎样,应先读下作者的readme,有个充分的理解先。
初始化主要做了三件事:
1 | check_corrector_initialized() |
1 | # 编码统一,utf-8 to unicode |
额外插句:单从re_han
这里就可以看出,作者至少对jieba很熟悉。
1 | def split_2_short_text(text, include_symbol=False): |
关于kenlm,网上搜了下,除了纠错基本很少有人用到(而且还是针对pycorrector~),只有这篇文章说的还有点意思,而且看Github kenlm介绍,作者也是十分任性,只强调速度,没有强调用处。。。
简单来讲,kenlm是基于n-gram训练出来的一个预训练模型,它的更多用法可看Example。
加载词频
(这个我看了下,和jieba自带的那个dict.txt基本没关系,相当于作者自己训练了一个词频词典)
~~ * 自定义混淆集(空的,所以忽略这步)~~
自定义切词词典
(默认是空,个人感觉可以把jieba那个dict.txt加进去,哈哈哈)
一些特定词典
人名词典词频、place词典词频、停用词词典词频、将这些词典词频合并到一起
对于人名和place这种词典,不如使用现成了命名实体模型,这种词典的方式总之是无法完全枚举的。
1 | # 词、频数dict, TODO: 这里虽然有,但是貌似没有用到 |
这部分使用jieba的search模式进行分词。
它的实现原理是:先使用hmm进行分词,比如少先队员因该为老人让坐
,它的分词结果是["少先队员", "因该", "为", "老人", "让", "坐"]
,然后对每个词再用2阶gram和3阶gram进行切分,在self.FREQ
中进行查找是否存在,得到的结果如下:
1 | ('队员', 2, 4) |
分完词后,按词粒度判断是否在词典里,符号,英文则跳过,否则则认为是可能错的。
到这里识别出因该
是可能错误的。
取bigram和trigram,通过kenlm获取对应的score,然后求平均获取和句子长度一致的score。
比如:
1 | sent_scores = [-5.629326581954956, -6.566553155581156, -6.908517241477966, -7.255491574605306, -7.401519060134888, -7.489806890487671, -7.1438290278116865, -6.559153278668722, -6.858733296394348, -7.7903218269348145, -8.28114366531372] |
然后通过这个sent_scores
取判断哪些index是错的。
那作者是怎么判断的呢?
1 | def _get_maybe_error_index(scores, ratio=0.6745, threshold=2): |
平均绝对离差定义为各数据与平均值的离差的绝对值的平均数
,那作者这里的计算方式貌似就不一样了。至此获取到的可能错误列表是:
1 | [['因该', 4, 6, 'word'], ['坐', 10, 11, 'char']] |
假设当前输入word是因该
:
_confusion_word_set
_confusion_custom_set
他这个获取相同拼音的写法就让我觉得emo,直接在self.known(自定义词典)里找长度相同,然后判断拼音一样不就得了~
自定义混淆集就是自定义一些经验进行。比如{“因该”: “应该”}这种,增大候选集。
这地方分成三部分:
same pinyin 加载同音的列表
,以及加载形似字same stroke 加载形似字
。因
,然后获取相同拼音的same pinyin 加载同音的列表
,以及加载形似字same stroke 加载形似字
,然后和该
进行拼接,获取新的候选集。第二个字该
执行相同操作。这个地方就有意思了,如何获取最正确的那个呢?看下面代码。
1 | def get_lm_correct_item(self, cur_item, candidates, before_sent, after_sent, threshold=57, cut_type='char'): |
核心的地方在self.ppl_score
那里,代码如下:
1 |
|
看作者注释,说的很明白了,如果这个句子越是流畅的,那么他的score就会更高。
1 | pprint(sorted_ppl_scores) |
最后一步,作者以score最高的那个加了一个threshold,如果得分在这个阈值内的,添加到候选的top_items里面。
如果当前的cur_item,即因该
不在这个候选集里,那么取第一个top_items
,如果在,那么就返回当前的cur_item。
这步的目的在于防止误判。
因该
也是有可能作为一个单独的词,只是出现的可能性较小。本文尝试从几个方面来介绍提取关键词所知的技术,以及关键词提取所遇到的问题,接着介绍SIFRank-zh算法,最后穿插下个人的理解与总结。
刚开始接触这个概念的时候,网上一大堆介绍TF-IDF和TextRank算法,这俩简直已经称为了关键词提取的baseline。
关于TF-IDF,的确在许多文档中已经作为了baseline来和其他技术相对比,是一种简单易行并且效果不差的无监督技术。
TextRank具体我没看,此处略过。
那么,此处引入一个问题,什么叫关键词?换句话说,什么样的词我们认为是关键词?
比如一句话:从2021年11月1日起,南京各个社区将尝试采取网格化管理,增强人民群众安全。
,不同分词器的结果如下:
lac的分词结果:
1 | [('从', 'p'), |
ltpV3版本的分词结果:
1 | ['从', '2021年', '11月', '1日', '起', ',', |
这里我想突出一个问题,目前市面所见的所有分词器都是采用细粒度分词
,那这就导致一个最直接的问题,就是关键信息词被拆开了。
这句话本质想突出网格化管理
这个词,但是在两个分词器中都将其分成了两个词。
你可以说NER可以解决这个问题哇,从技术角度本身来讲,这的确是可行的。比如时间短语,机构短语,地址短语。
你也可以说数据标注标准不同,比如msra或者ptb数据集。
你也可以说粗粒度分词会更容易产生歧义。比如白天鹅在湖中游
等等。这条不太认同。
你也可以说粗粒度分词下如果想获取更细粒度分词则无法获得。这条我认为可有可无。
但是我想说的是,那能不能有一个分词器,从使用效果上来讲更贴切人直观感受,不需要关注底层分词和NER这两种技术的。
可惜没有!
之前我看腾讯开源了一个训练好的word2vec模型,在Simple Cases那里,简直看到了希望!!
当时就在想,怎么根据word2vec反推出一个分词?
过程就是分词和word2vec训练放到一个任务中,但是只保存分词的模型。
然后在脑里想分词实现技术有哪些,首先肯定排除掉最长距离或者最短距离分词,那么能想到的只有HMM和CRF。
不管HMM还是CRF,本质都是计算状态概率转移矩阵和发射概率矩阵,HMM多了个初始概率矩阵,这个还好。
但是想了半天,貌似都不太可行,为什么?
商品和服务
,有['商品', '和', '服务']
以及['商品', '和服', '务']
,仅仅这个例子,就有两种可能性,如果一个句子过长的话甚者有很多中语料的话,就无法估算那个语料才是真正正确的那个。那么在无法估计正确语料这条路来讲,crf就不可行。emmmmm,但是我觉得可以换条思路来实现,那就是用于辅助标注人员的方式来重标数据集,或者对标注人员说,我更倾向于使用粗粒度分词的。
额,说了这么些,感觉貌似又回到了起点。
但是是一劳永逸的一种方式。
上面介绍了这么多,本质来讲是想提取更粗粒度的,那么我们基于现有的条件下,可以借鉴的实现方式:
排序的本质是正确代表语句或文本的真实意图或者需要涵盖的方面点。从无监督方式出发的话,我们能够利用的特征:
SIFRank的实现主要分为以下步骤:
接下来会对其实现做更具体说明。
ELMoForManyLangs,SIFRank_zh作者指出的关于:
1 | 哈工大的elmoformanylangs 0.0.3中有个较为明显的问题,当返回所有层Embeddings的时候代码写错了,当output_layer=-2时并不是返回所有层的向量,只是返回了倒数第二层的。问题讨论在这里#31 |
已经解决,故可以忽略。
测试代码以官方test.py为准。
1 | #download from https://github.com/HIT-SCIR/ELMoForManyLangs |
1 | class SentEmbeddings(): |
1 | def get_word_weight(weightfile="", weightpara=2.7e-4): |
这地方主要干了初始化elmo和以jieba分词统计的词频为主进行获取词的权重。
- 分词使用thunlp,但是以jieba同级的词频为主,这地方不合适。
- 这样获取词的权重,也不太合适。是否可以使用tf-idf呢。
1 | zh_model = thulac.thulac(model_path=r'../auxiliary_data/thulac.models/',user_dict=r'../auxiliary_data/user_dict.txt') |
1 | class InputTextObj: |
1 | GRAMMAR_zh = """ NP: |
到这里位置作者完成了对候选词的处理,还是回到最上面讲候选词获取那里的问题,如果有一个更合适的分词器。
- 那么这里我们就可以重新获取dict.txt。
- 不需要这么复杂的正则表达式来获取候选词。
- 接下来的获取候选词词向量那里也会更适合。
另外一个方面,关于正则匹配那:
- 我们可以不局限于词性,也可以根据词的信息来合并,比如’和’,‘的’等字。
- 针对特定词进行对分词器做调整。
1 | def get_sent_segmented(tokens): |
但是想不明白的是,干嘛不直接用一个更成熟的分句工具呢,比如ltp和hanlp中都有现成的。
这一步和获取elmo每层输出作者将其称为文档分割(document segmentation,DS)
,其作用如下:
1 | DS:通过将文档分为较短且完整的句子(如16个词左右),并行计算来加速ELMo; |
注意,下面将不区分elmo和ELMoForManyLangs,如果不做说明,则统一为ELMoForManyLangs。
1 | def get_tokenized_words_embeddings(self, sents_tokened): |
elmo输出为三层结构,这个可以在ELMoForManyLangs中看到,
1 | output_layer: the target layer to output. |
比如sents_tokened长度为[44, 110],输出结果elmo_embedding为torch.Size([2, 3, 110, 1024])
。
1 | def context_embeddings_alignment(elmo_embeddings, tokens_segmented): |
这地方做了三件事情:
- 获取每个词对应的词向量,将相同词的向量append到一起。
- 求每个词的平均词向量
- 替换掉elmo最后一层的词对应的词向量
这一步作者叫做词向量对齐(embeddings alignment,EA)
,其作用如下:
1 | EA:同时利用锚点词向量对不同句子中的相同词的词向量进行对齐,来稳定同一词在相同语境下的词向量表示。 |
1 | def splice_embeddings(elmo_embeddings,tokens_segmented): |
比如tokens_segmented
长度为[44, 110]
,elmo_embeddings
shape为:torch.Size([2, 3, 110, 1024])
。
那么new_elmo_embeddings
的shape为:torch.Size([1, 3, 154, 1024])
。
1 | def get_weight_list(word2weight_pretrain, word2weight_finetune, tokenized_sents, lamda, database=""): |
1 | def get_oov_weight(tokenized_sents,word2weight,word,method="max_weight"): |
就是根据dict.txt计算出每个词的权重,所获取到的.
1 | def get_weighted_average(tokenized_sents, sents_tokened_tagged,weight_list, embeddings_list, embeddings_type="elmo"): |
只计算考虑的词性
。比如(n,np,ns)等。注意这里哦,这里有两点需要注意的:
- elmo第三层不是原来第三层的结果了,是作者称为
词向量对齐
的给替代了。- 他计算的是只考虑的词性,而不是所有词的平均向量。
1 | def get_candidate_weighted_average(tokenized_sents, weight_list, embeddings_list, start,end,embeddings_type="elmo"): |
整个计算过程和上一步基本一致。
到这一步作者完成了对文档向量的计算,以及候选词向量的计算。
不难看出,第一还是词权重的问题。第二是文档向量只考虑了目标词性。第三是候选词一般是复合词,那么在计算复合词权重的时候是词权重 * 词向量求平均的方式。
那如果我们有一个更合适的分词器,训练一个自己的词权重文件,以及训练一个更适合自己的elmo模型,那是否可以讲整套技术上是更为完整的。
1 |
|
计算每个候选词与整段话的余弦距离,但是这里默认每层的权重是[0.0,1.0,0.0]
,那这就导致作者声称的词向量对齐(embeddings alignment,EA)
技术并没有用到~
1 |
|
产生的结果比如:{“技术”: [0.8432613915568461, 0.8243992563094531, 0.8120348604031394]},那么又做了一次平均。
词向量对齐(embeddings alignment,EA)
技术默认没有用到。到这里作者完成了SIFRank_zh算法,的确是受限于目前的成果,整体流程也是具备参考价值的。
突出的问题,到这里为止,并没有利用到位置信息特征,比如行首或者行尾可能更具备代表含义。那么作者在此基础上,提供了SIFRank+算法。
1 |
|
我原本以为作者会用到词在原文中的索引位置,结果是候选词之间的索引位置。emmmm,但反过来想这其实也不正是原文中的相对索引位置嘛。
作者这里用到了两个技术点让我觉得很nice,一个是wnl.lemmatize
,其作用是词性还原的,对于英文来说有用。还有一个是softmax
,是用于归一化的。都能看出作者的扎实底子。
从作者这地方可以看出,整套流程是提取复合词的候选词,然后使用elmo计算候选词和以只考虑的目标词性所组成的句向量进行余弦计算其排序。
在不改变整体流程和技术的方式上
缺失模块。
1、请确保node版本大于6.2
2、在博客根目录(注意不是yilia根目录)执行以下命令:
npm i hexo-generator-json-content --save
3、在根目录_config.yml里添加配置:
jsonContent: meta: false pages: false posts: title: true date: true path: true text: false raw: false content: false slug: false updated: false comments: false link: false permalink: false excerpt: false categories: false tags: true