tensorboard是让torch可以使用tensorflow的web可视化工具,之前叫tensorboardX。
至于其他的介绍以及为什么需要,可自行百度。
简单的完整代码
1 | # -*- coding: utf8 -*- |
终端跑下面这条命令:
1 | tensorboard --logdir=./runs |
更完整的demo
其中数据集加载没有添加进来,引用代码可参考lstm_sent_polarity。
1 | # Defined in Section 4.6.7 |
tensorboard是让torch可以使用tensorflow的web可视化工具,之前叫tensorboardX。
至于其他的介绍以及为什么需要,可自行百度。
1 | # -*- coding: utf8 -*- |
终端跑下面这条命令:
1 | tensorboard --logdir=./runs |
其中数据集加载没有添加进来,引用代码可参考lstm_sent_polarity。
1 | # Defined in Section 4.6.7 |
本篇文章主要讲基于bert预训练模型的一些例子,希望可以从不同角度理解与应用基于bert的一些应用。
nlp发展了这么多年,经历了规则,统计,模型等阶段,目前预训练模型基本算是一统天下了。
大公司有更多资源,可以联合一些科研机构与组织搞一些事情,比如微软和nvidia利用更多的资源来探测模型的边界。
这个就很有意思,思考一个问题,什么叫意识?什么情况下产生了意识?
什么是意识,这个可以尝试用两个词来理解,计算
和算计
。计算很好理解,比如计算机,计算器,本质来讲是人输入指令,获得一个预期结果,不会产生任何歧义。而算计呢,它是一个综合体,它的输出是多样的,有可能不可预知的。它具有自己的思考。
那什么情况下产生了意识?这个问题就是这些大佬们所想要尝试认知的东西。比如人类有30亿碱基对,蚊子才几千万,单细胞生物可能更少可能就被认为不具备意识?如此看来的话,那我可以尝试扩充网络容量,更多的训练数据集,等到了某个程度下,突然机器就具备了某种智能。
这是个有意思的研究!
不过扯了这么多,小公司的话,更多是基于预训练模型的微调。
为什么要基于预训练模型微调呢,严格意义来讲就是预训练模型已经学习到了语义,一个认知大脑。基于此,给定一个具体任务,来对其进行微调,使其具备更快的收敛能力和更好的泛化能力。
所以掌握下还是很重要滴😂😂😂。
下面介绍四个应用示例,每一种大致介绍下,更具体的实现可以自行实现。
不过作者在这里使用了Trainer,这是transformers出的一套工具,可以让你更快的训练,但是封装太高,懒得折腾了,不如自己从头写😂😂😂。
代码.
这个是个ner任务,不知道ner的自行百度,模型后面接了一个linear,如果想要更深入,可以看看crf。
不过这个任务如果用于分词,词性标注还是很nice的。
代码.
这个任务还是蛮有意思的,之前也有类似的比赛。比如天池 疫情相似句对判定大赛 线上第一名方案。
整体实现思路基本一致,不过作者加入了对抗训练这些东西,感兴趣可以看看。
代码.
这个就是多分类任务,也是比较常见的场景。
代码.
这个我没看😂😂😂。
好吧,这本书快让我水了一波,感兴趣的需要基础的老铁们可以多翻翻。
对transformers库不常用记录,方便回溯。
比如BertTokenizerFast
,use_fast
, 示例如下:
1 | AutoTokenizer.from_pretrained('hfl/chinese-electra-180g-small-discriminator', use_fast=True) |
它的含义是使用rust加速速度。
嘿嘿,rust现在要进入linux内核了,恭喜恭喜。
比如常见的convert_ids_to_tokens
,encode
, encode_plus
等等,下面记录一种对句子对的使用方式.
完整例子可参考ne_bert_mrc.py。
1 | # -*- coding: utf8 -*- |
可以自行改动这个例子,其中stride默认注释掉了,默认为0。
elmo是用于解决静态词向量无法一词多义的模型。
在介绍如何实现elmo模型的时候,此处穿插进来Conv1d layer(一维卷积层)
。
本文代码以plm-nlp-code chp6为准,可直接参考。
卷积有Conv1d(序列),Conv2d(图像),Conv3d(立体数据),主要区别在于不同方向上进行卷积。因为文字是一维结构的,从而在nlp领域使用Conv1d。
一维卷积适合在句子对于时序结构上体现不重要的方面有更加的优势。比如一句话中关键词位置的变动不影响句子的语义。
但是对时序结构通常效果并不好,因为时间序列通常不满足平移不变的假设。
此处不过多介绍关于Conv1d的原理,感兴趣可看一维卷积tensorflow2版本的Conv1D以及Pytroch的nn.Conv1d用法。
此处只关心input和output,卷积核和padding等。
假设o
为上一层的hidden_size输出维度,kernel_size
为卷积核大小,padding
为使用边界填充,stride
为步长,那么:
卷积后的维度: (o
- kernel_size
+ 2 * padding
) / stride
+ 1
池化后的维度: (o
- kernel_size
) / stride
+ 1
torch中的输入为(batch_size, hidden_size, seq_length)。
1 | m = nn.Conv1d(16, 33, 3, stride=1) |
1 | m = nn.Conv1d(16, 33, 3, stride=2) |
1 | m = nn.Conv1d(16, 33, 3, stride=1,padding=True) |
1 | m = nn.Conv1d(16, 33, 3, stride=2, padding=True) |
这地方主要分成两部分:
1 | def load_corpus(path, max_tok_len=None, max_seq_len=None): |
1 | class BiLMDataset(Dataset): |
1 | BiLM( |
这个网络结构和ELMoForManyLangs基本一样,除了没有使用lstm。
1 | Model( |
这个讲解可以看Highway net。但是这个给我的直观感觉有点类似lstm的门控制机制,用于遗忘与记忆。
1 | class ConvTokenEmbedder(nn.Module): |
这个地方比较有意思,使用不同大小的kernel_size来捕捉上下文信息,这地方没啥可说的。
主要在concat后的操作,比如说不同的kernel_size后的concat到一起后,它到底代表什么含义呢?比如我可以降维到2,表示分类。
后面它用了一个linear以及view重新获得了seq_len每一个token的hidden_size。这地方骚气。
太懒了,目前先跳过。。。
简单来说就是使用不同大小的kernel_size的卷积核来捕捉上下文信息,注意是char level的。随后经过一个前向的lstm和一个后向的lstm捕捉不同方向的语义信息。那么从整体网络看来,不同层代表的含义也就比较清晰明了,token embedding更倾向是词法等信息,往后就更倾向深层次的信息,比如语义。那么从使用角度上一是可以按需使用自己的层,二是可以给予不同层不同的权重。
感觉看完了没有很清晰明了的感觉,有可能是中间涉及到太多的转换,elmo解决一词多义的原理可能更多是使用lstm这种以及用char level token来拟合词。
另外elmo不同layer代表的含义不一样,这个在SIFRank中有不同的用法,后续可以关注。
在之前讲解获取静态词向量的方法中,都是在context_size下用到了word和context的共现关系。要么word预测context words,要么context words预测word。本质上都是利用文本中词与词在局部上下文中的共现信息作为自监督学习信息。
还有一种是通过矩阵分解的方式,比如LSA,然后使用SVD进行奇异值分解,对该矩阵进行降维,获得词的低维表示。
这部分可以参考【词的分布式表示】点互信息PMI和基于SVD的潜在语义分析。
那glove的提出就是就是结合了这两者的特点。
其loss函数如下所示:
嘿嘿,hexo貌似公式还要额外折腾,懒的搞了~
这个公式分成两部分:
同样加载reuters数据集,但是不同之处在Dataset那里。代码如下:
1 | # 非必要的忽略掉。 |
在共现次数随距离衰减那里,glove考虑了w与c的距离,即词w和上下文c在受限窗口大小内的共现次数与距离,越远的词的贡献程度越低。
下面在计算loss时有多个地方引用这个共现矩阵,所以注意。
1 | class GloveModel(nn.Module): |
整体模型和其他求静态词向量的模型基本一致,只是多了求偏置部分。
模型的输出就代表了下面这部分。
具体的实现代码如下所示。
1 | # 提取batch内词、上下文的向量表示及偏置 |
剩下log那项就是对共现矩阵求log。完整代码如下所示:
1 | words, contexts, counts = [x.to(device) for x in batch] |
剩下这项,即是求样本权重,完整代码如下:
1 | weight_factor = torch.clamp(torch.pow(counts / m_max, alpha), max=1.0) |
乍一看其实和求log(count)那部分没有本质区别。只不过是对其进行了分段加权处理。
本质是共现次数越少那么含有的有用信息越少,因此给予较低的权重,相反,对于高频出现的样本,那么也要避免给予过高的权重。
关于静态词向量使用上可以有两个方向。一是计算词语之间的相似度(similar),二是根据一组词来类推相似的词(analogy)。
比如和哥哥相似的词是兄长,这个叫做相似度。
根据国王和皇后,来类推和男人相似的是女人。
总之这两者本质上来讲都是计算空间距离,具有相同语义的词的空间距离会更近。
完整测试代码如下:
1 | # Defined in Section 5.4.1 and 5.4.2 |
1 | # Defined in Section 5.3.4 |
前文介绍了许多方法来获取静态词向量,本文介绍使用lstm来训练词向量。
1 | class RNNLM(nn.Module): |
1 | # 读取文本数据,构建FFNNLM训练数据集(n-grams) |
数据来自reuters,在dataset那里是将前面词预测后面一个词,可以具体看dataset的处理方式。
不过有一个点需要注意,我用了1080Ti竟然跑不起来,参数量太大了,所以我改动了load_reuters代码,将词出现频次低于5的就忽略掉。代码如下。
1 | def load_reuters(): |
1 | # Defined in Section 5.1.3.3 |
如果你看懂了skipgram和cbow的区别,那么实现上面就很简单了。skipgram是中心词预测周围词,cbow是周围词预测中心词,即dataset那里更换下input和target即可。
具体就不细讲了,大家看源码吧~。
1 |
|
本文分享几个好玩的知识点:
什么叫前馈神经网络呢,emmm,自个去看百度百科定义前馈神经网络。简单来说,就是两个linear加一个激活函数,简单结构如下:
1 | class FFNN(nn.Module): |
其中大名鼎鼎的transformer中也用到了FFNN,所以要认真对待每一种结构哦。
啥叫词袋呢,emmmm,这个咋解释呢?就是从一堆词取context_size大小的词回来。它没有顺序,所以叫做词袋。比如unigram, bigram, trigram,ngram,都是属于词袋。
而大名鼎鼎的word2vec也是属于词袋这种的哦!这里画重点。
这里就不难理解了,就是换一种方式来实现词向量的获取方式。我在这两采用了两种方式,第一种是以前面两个词为准,获取当前词,这叫做用过去的词来预测未来的词。嘿嘿,如果脑洞大开点的话,是不是有种transformer encoder的感觉😂😂😂。
1 | # Defined in Section 5.3.1.2 |
是不是像cbow~
1 |
|
这两者之间就以下几点不同:
其余都一样哦,可以自己跑一跑呢。
当当当,欢迎来学习word2vec skipgram,关于word2vec,网上介绍的例子一大堆,这里就简单说明下。
最开始进行tokenizer的时候,是使用onehot编码,缺点就是矩阵太大,另外太稀疏,而且词和词之前是不具备语义信息的。
你说什么叫语义?语义没有官方定义,可以简单理解成更符合人类认知的,我觉得就可以理解成语义。
而word2vec带来了稠密向量,并且词和词之间有了语义关联,可以用于计算词和词之间的空间距离,也可以叫做相似度。
实现word2vec的方式有两种,一个是Hierarchical Softmax(也是softmax),另外一种是negative sampling。
下面将分别介绍这两种方式的实现思路。
偷懒群众可以直接看:
啥叫Hierarchical Softmax,嘿嘿,这个我没看😂😂😂。总而言之也是softmax,不过应用场景主要在对大规模语料进行softmax的时候加速计算结果,比如softmax(100w个),那么他的优势就体现出来了。
如果感兴趣其实现原理的可以自行百度,我就葛优躺了😂😂😂。
1 | def load_reuters(): |
其中corpus
长下面这个样子:
1 | corpus[:2] |
构建训练数据集的步骤如下:
1 | class SkipGramDataset(Dataset): |
看这段代码,其核心在于,取当前词的index,然后以当前词取左右大小为context_size
的词出来来作为他的中心词,然后在collate_fn那里就转换成当前词的id,和周围词的id作为其预测目标。
是不是瞬间明白他想干什么事儿了吧!!
没明白是吧,继续往下看。
1 | class SkipGramModel(nn.Module): |
这个结构是不是相当简单,从今天来看,这个结构很简单,另外pytorch已经帮忙做了很多,但是放到实现那年,不得不佩服这些研究者。
训练部分就很直白了,每次取batch_size=1024个样本,然后输入模型,获得log_probs=(1024, 31081)结果,其中31081是指vocab_size,然后交叉熵算loss。
到这是不是明白了Hierarchical Softmax而不是softmax的意义了吧。
当面对百万级的文本,就算是隐藏层是检索功能,其计算量也是相当大,而且还会造成冗余计算,这时候对高频词抽样以及负采样就应运而生了。
他的做法是对文档中出现的每个词计算其出现频率,然后以当前词周围context_size大小的作为postive sampling,和选中的词无关的并且以词出现频率高为negative sampling。分别计算当前词和positive以及negative的相关度,就是直接计算其点积,将其loss最低即完成训练。
1 |
|
其中get_unigram_distribution
是获取每个词的出现概率。每次记不住这个unigram,bigram,还有trigram。。。
1 | class SGNSDataset(Dataset): |
这地方有两个地方需要注意的:
第一是获取positive sampling,也就是以当前词周围context_size大小的作为postive sampling,这个在__init__
函数中完成。
第二是获取negative sampling,这个是在collate_fn中完成,其中涉及到一个采样函数multinomial
,这个大家可以参考这篇文章。
1 | class SGNSModel(nn.Module): |
这部分可以直接看源代码了,其中在计算负样本的分类(对数)似然地方,我改了下,更方便理解,其实本质没啥~
1 | # 负样本的分类(对数)似然 |
这部分属于个人猜测部分,不保证其准确性。
抛开所有的理论,单纯看代码思考一个问题,源码这里使用了两个embedding,分别是词嵌入
和上下文嵌入
,那我们能不能只用一个embedding呢?
那既然如此,我们需要修改下源码,有两个地方需要修改:
关于第一点,为什么要在负样本中去掉当前词?
不可能让当前词和当前词在计算相似度时出现悖论吧😂😂😂。
关于第二点,这个没啥好解释的了。
修改后的完整代码如下:
1 | # Defined in Section 5.2.3.3 |
在训练的过程中,发现其loss是往下不断的降低,那么我们可以认为是有用的,但是具体效果要再自行实验下。
缺失模块。
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