- gitalk
- hexo-tag-aplayer
- 百度统计
- 不蒜子
- Figma各类icon制作
Reference:
Hexo NexT主题配置文档
Hexo: 给博客添加百度统计
Hexo NexT主题中添加网页音乐播放器功能
Hexo NexT 打造APlayer HTML5 音乐播放器
Hexo中Gitalk配置使用教程-可能是目前最详细的教程
hexo博客解决不蒜子统计无法显示问题
工人有力量,硅器有生命
从11月初开始,google-research就陆续开源了bert的各个版本。google此次开源的bert是通过tensorflow高级API—— tf.estimator
进行封装(wrapper)的。因此对于不同数据集的适配,只需要修改代码中的processor部分,就能进行代码的训练、交叉验证和测试。
bert的代码同论文里描述的一致,主要分为两个部分。一个是训练语言模型(language model)的预训练(pretrain)部分。另一个是训练具体任务(task)的fine-tune部分。在开源的代码中,预训练的入口是在run_pretraining.py
而fine-tune的入口针对不同的任务分别在run_classifier.py
和run_squad.py
。其中run_classifier.py
适用的任务为分类任务。如CoLA、MRPC、MultiNLI这些数据集。而run_squad.py
适用的是阅读理解(MRC)任务,如squad2.0和squad1.1。预训练是bert很重要的一个部分,与此同时,预训练需要巨大的运算资源。按照论文里描述的参数,其Base的设定在消费级的显卡Titan x 或Titan 1080ti(12GB RAM)上,甚至需要近几个月的时间进行预训练,同时还会面临显存不足的问题。不过所幸的是谷歌满足了issues#2里各国开发者的请求,针对大部分语言都公布了bert的预训练模型。因此在我们可以比较方便得在自己的数据集上进行fine-tune。
对于中文而言,google公布了一个参数较小的bert预训练模型。具体参数数值如下所示:
Chinese Simplified and Traditional, 12-layer, 768-hidden, 12-heads, 110M parameters
模型的下载链接可以在github上google的开源代码里找到。对下载的压缩文件进行解压,可以看到文件里有五个文件,其中bert_model.ckpt开头的文件是负责模型变量载入的,而vocab.txt是训练时中文文本采用的字典,最后bert_config.json是bert在训练时,可选调整的一些参数。
任何模型的训练、预测都是需要有一个明确的输入,而bert代码中processor就是负责对模型的输入进行处理。我们以分类任务的为例,介绍如何修改processor来运行自己数据集上的fine-tune。在run_classsifier.py
文件中我们可以看到,google对于一些公开数据集已经写了一些processor,如XnliProcessor
,MnliProcessor
,MrpcProcessor
和ColaProcessor
。这给我们提供了一个很好的示例,指导我们如何针对自己的数据集来写processor。
对于一个需要执行训练、交叉验证和测试完整过程的模型而言,自定义的processor里需要继承DataProcessor,并重载获取label的get_labels
和获取单个输入的get_train_examples
,get_dev_examples
和get_test_examples
函数。其分别会在main
函数的FLAGS.do_train
、FLAGS.do_eval
和FLAGS.do_predict
阶段被调用。
这三个函数的内容是相差无几的,区别只在于需要指定各自读入文件的地址。以get_train_examples
为例,函数需要返回一个由InputExample
类组成的list
。InputExample
类是一个很简单的类,只有初始化函数,需要传入的参数中guid是用来区分每个example的,可以按照train-%d'%(i)
的方式进行定义。text_a是一串字符串,text_b则是另一串字符串。在进行后续输入处理后(bert代码中已包含,不需要自己完成) text_a和text_b将组合成[CLS] text_a [SEP] text_b [SEP]
的形式传入模型。最后一个参数label也是字符串的形式,label的内容需要保证出现在get_labels
函数返回的list
里。
举一个例子,假设我们想要处理一个能够判断句子相似度的模型,现在在data_dir
的路径下有一个名为train.csv
的输入文件,如果我们现在输入文件的格式如下csv形式:
1 | 1,你好,您好 |
那么我们可以写一个如下的get_train_examples
的函数。当然对于csv的处理,可以使用诸如csv.reader
的形式进行读入。
1 | def get_train_examples(self, data_dir): |
同时对应判断句子相似度这个二分类任务,get_labels
函数可以写成如下的形式:
1 | def get_labels(self): |
在对get_dev_examples
和get_test_examples
函数做类似get_train_examples
的操作后,便完成了对processor的修改。其中get_test_examples
可以传入一个随意的label数值,因为在模型的预测(prediction)中label将不会参与计算。
修改完成processor后,需要在在原本main
函数的processor字典里,加入修改后的processor类,即可在运行参数里指定调用该processor。
1 | processors = { |
之后就可以直接运行run_classsifier.py
进行模型的训练。在运行时需要制定一些参数,一个较为完整的运行参数如下所示:
1 | export BERT_BASE_DIR=/path/to/bert/chinese_L-12_H-768_A-12 #全局变量 下载的预训练bert地址 |
在开始训练我们自己fine-tune的bert后,我们可以再来看看bert代码里除了processor之外的一些部分。
我们可以发现,process在得到字符串形式的输入后,在file_based_convert_examples_to_features
里先是对字符串长度,加入[CLS]和[SEP]等一些处理后,将其写入成TFrecord的形式。这是为了能在estimator里有一个更为高效和简易的读入。
我们还可以发现,在create_model
的函数里,除了从modeling.py
获取模型主干输出之外,还有进行fine-tune时候的loss计算。因此,如果对于fine-tune的结构有自定义的要求,可以在这部分对代码进行修改。如进行NER任务的时候,可以按照bert论文里的方式,不只读第一位的logits,而是将每一位logits进行读取。
bert这次开源的代码,由于是考虑在google自己的TPU上高效地运行,因此采用的estimator是tf.contrib.tpu.TPUEstimator
,虽然tpu的estimator同样可以在gpu和cpu上运行,但若想在gpu上更高效得做一些提升,可以考虑将其换成tf.estimator.Estimator
,于此同时model_fn里一些tf.contrib.tpu.TPUEstimatorSpec
也需要修改成tf.estimator.EstimatorSpec
的形式,以及相关调用参数也需要做一些调整。在转换成较普通的estimator后便可以使用常用的方式对estimator进行处理,如生成用于部署的.pb
文件等。
从google对bert进行开源开始,issues里的讨论便异常活跃,bert论文第一作者javob devlin也积极地在issues里进行回应,在交流讨论中,产生了一些很有趣的内容。
在#95中大家讨论了bert模型在今年ai-challenger比赛上的应用。我们也同样尝试了bert在ai-challenger的mrc赛道的表现。如果简单得地将mrc的文本连接成一个长字符串的形式,可以在dev集上得到79.1%的准确率。如果参考openAI的GPT论文里multi-choice的形式对bert的输入输出代码进行修改则可以将准确率提高到79.3%。采用的参数都是bert默认的参数,而单一模型成绩在赛道的test a排名中已经能超过榜单上的第一名。因此,在相关中文的任务中,bert能有很大的想象空间。
在#123中,@hanxiao给出了一个采用ZeroMQ便捷部署bert的service,可以直接调用训练好的模型作为应用的接口。同时他将bert改为一个大的encode模型,将文本通过bert进行encode,来实现句子级的encode。此外,他对比了多GPU上的性能,发现bert在多GPU并行上的出色表现。
总得来说,google此次开源的bert和其预训练模型是非常有价值的,可探索和改进的内容也很多。相关数据集上已经出现了对bert进行修改后的复合模型,如squad2.0上哈工大(HIT)的AoA + DA + BERT
以及西湖大学(DAMO)的SLQA + BERT
。
在感谢google这份付出的同时,我们也可以借此站在巨人的肩膀上,尝试将其运用在自然语言处理领域的方方面面,让人工智能的梦想更近一步。
一、在执行代码时使用 tf.enable_eager_execution()
开启eager模式
二、正向传播支持自定义class类型
二、反向传播的使用
先用tf的api定义loss函数
用tfe的api调用loss得到梯度grads
1 | tfe.gradients_function(loss,x) |
或者也可以采用GradientTape(y,x)来进行计算可以根据函数y计算变量x的梯
1 | with tf.GradientTape() as grad_tape: |
在tf的诸多eager execution样例中里用第二种方法较多
用tf定义的optimizer优化梯度更新参数
1 | optimizer.apply_gradients(grad) |
三、Eager的输入使用tf.data.Dataset
但不支持placeholder
和string_input_producer
这类在graph模式中使用的输入
四、使用tf.train.Checkpoint()
保存模型的checkpoint
五、可以使用tf.contrib.eager.defun
对python的函数进行封装转换成图的形式进行运算。用eager的写法可以实现graph的运算速度。
使用了defun的forward propagation例子如下:
1 | model.call = tf.contrib.eager.defun(model.call) |
一个使用了defun的back propagation例子如下
1 | optimizer = tf.train.GradientDescentOptimizer() |
Refer:Code with Eager Execution, Run with Graphs: Optimizing Your Code with RevNet as an Example
checkpoint的基础使用在官方的手册里描述地比较清楚了。但在进行迁移学习时,需要对一些预训练的权重进行读取,因此如果能可阅读得打印一些变量,可以使得读取过程变得简捷便利。
1 | from tensorflow.python import pywrap_tensorflow |
在ckpt文件里的变量有两类,一类是进行前馈的权重,另一类是在后馈时的梯度。同时有一些变量并不是此前在构建模型时声明的,而是在实现各类模型api时自动产生的,通常这类变量会根据参数产生W和bias。对于包含多步线性计算的cell即各类RNN的cell而言,W会被整合成一个名为kernel的变量,其tensor大小将根据具体的计算方式生成,如lstm的kernel变量大小为(input_dim+lstm_dim,4*lstm_dim)。
1 | sess.run(tf.global_variables_initializer()) |
因为ckpt读取变量需要新变量和ckpt里的变量名字完全一致,所以可以通过上述代码查看变量名是否满足条件。
1 | variables_to_restore = [var for var in tf.global_variables() |
通过在saver初始化时传入需要读取的参数,可以控制restore哪些变量。
estimator的官方使用方式介绍了使用自定义的estimator的model,没有涉及到从keras的model来使用estimator。
主要的使用方式来自这篇notebook在使用的时候没有遇上太多障碍。
但有一些细节花了一点时间去调试。
比如estimator能按照dataset重复次数dataset.repeat(n)
作为epoch,因此如果直接使用dataset.repeat()
会在训练时陷入死循环。
1 | def model_fn(features, labels, mode): |
通过传递参数是无法打印更多的训练结果,但是可以通过创建一个logging hook来让estimator运行。
In the body of model_fn function for your estimator:
1 | logging_hook = tf.train.LoggingTensorHook({"loss" : loss, |
除了self.estimator.train()
以外,可以使用tf.estimator.train_and_evaluate()
对train
和evaluate
进行更精细地操作。
此外add_metrics(estimator,my_auc)
只是把metrics加入到最终结果的输出里,而不是每一次step,对于每一次step需要在EstimatorSpec(training_hook=[logging_hook])
里添加logging_hook
多gpu出现的
All hooks must be SessionRunHook instances问题在#issues21444 里解决,等待tf-1.11版本。
tf.dataset的API支持keras,是Tensorflow-1.9.0的一个新特性。使用tf.dataset作为输入的pipeline可以减少系统内存和显存的占用率,使得在训练大规模数据时,避免内存容量不够的问题出现。
在tensorflow的官方文档中,有简单的使用说明,但仍有一些bug在tensorflow-1.10.0中才将被修复。针对这个版本尚存在的一些问题,以下介绍一些可以规避这些问题的使用办法。
将tf.dataset作为输入传入可以在fit()函数中,也可以在Input()层和compiler()函数中分别传入。
1
2
3
4
5
6
7#在fit()函数传入
model.fit(x=iter_x.get_next(),y=iter_y.get_next(),
epochs=epochs,steps_per_epoch=steps_per_epoch)
# 在Input()层和compiler()函数传入
inputs = Input(tensor=iter_x.get_next())
model.compile(loss=loss,optimizer=optimizer,
target_tensors=[iter_y.get_next()])
传入时需要将dataset类转换为tensor类。这一步涉及两个步骤,首先需要生成dataset的iterator,对于不同的dataset有不同生成方法,常用的有make_one_shot_iterator和make_initializable_iterator两种。之后通过iterator的get_next()函数迭代获取tensor类数据。
1 | iterator = dataset.make_initializable_iterator() |
keras对于传入的tensor有tf.dtype的要求,x需要是tf.float32类型。如果类型不符,可以通过tf.cast()进行类型转换
1 | inputs = Input(tensor=tf.cast(iter_x.get_next(),tf.float32)) |
面对一系列初始化的需要时,可以先获取kera的session,并在keras的session中对table和iterator进行初始化
1 | from keras import backend as K |
训练时如果采用在Input()层和compiler()函数中传入x和y的方法,后续进行交叉验证、测试集测试以及结果生成时需要构建新的模型。因为这种方式相当于将模型的输入输出固定,将使模型不受输入输出的影响。新构建的模型需要符合原来模型的结构,但在Input()层和compiler()函数中的相应参数可以改为新的x和y。再传入训练时模型的权重并调用evaluate或者predict进行验证和预测。一个kears官方的示例
如果在notebook中使用这种方法需要注意在内存中清除原来的会话。
1 | K.clear_session() |
tensorflow 有许多种不同的模型存储格式,不同的存储方式调用的存储函数是不一样的,使用tensorflow作为后端的keras也同样支持这些模型保存的方法。这里介绍一种能够方便部署在服务器端和移动端的protobuf格式。
建议采用from tensorflow import keras
以及from tensorflow.python.keras.models import...
来使用keras。
在构建好keras的网络结构后就可以开始对模型进行保存。为了使之后能便捷找到模型的结点,可以构建模型时对每一层的name参数进行赋值,对相关层命名。
1 | context_input = Input(shape=(10,),name='context_input') |
构建完模型后可以获得模型在tensorflow下的graph,查看网络结构是否如愿搭建以及此前命名是否成功。
1 | graph = K.get_session().graph |
模型在保存前,可以通过K.set_learning_phase(0)
将模型的参数设为不可变化的非训练模式。
模型的保存可以采用SaveModel的API进行保存,保存的结果将得到一个目录,目录里包含模型结构的pb文件以及包含参数名称和值的另一个目录。
1 | from tensorflow.python.keras.models import Model |
其中add_meta_graph_and_variables的第二个参数是可选字符。
虽然保存之后目录里有.pb文件,但这个.pb文件的格式并不能通过graph_def.ParseFromString(f.read())
的方式进行读入,因为tensorflow的文件读写需要完全使用成对的api进行完成。对应于f.saved_model.builder.SavedModelBuilder()
的是tf.saved_model.loader.load()
具体的使用方式如下:
1 |
|
这样ret
得到的值便是对应输入data1
的模型前馈的结果。
这部分有两个比较耗时的地方,首先是在load部分,其次是在get_tensor_by_name部分。因此在实际部署时,可以保持sess处于常开状态来减小i/o开销。此外,每个sess第一次run的时间将为之后run时间的数十倍。