├── README.md ├── backend └── README.md ├── imgs └── gelu_erf.jpg ├── layers └── README.md ├── models └── README.md ├── optimizers └── README.md ├── snippets └── README.md └── tokenizers └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Sniper 2 | 3 | ### 写在前面 4 | 5 | 由于苏神开发的[bert4keras](https://github.com/bojone/bert4keras )并没有什么文档,苏神本人的[文档](https://bert4keras.spaces.ac.cn/ )写了个头就没动过了,取而代之的是很多例子。 6 | 7 | 这对框架的使用者造成了一定的困扰,事实上我在初识[bert4keras](https://github.com/bojone/bert4keras )时,因为没文档,一度选择了学习[transformers](https://github.com/huggingface/transformers ),当然[transformers](https://github.com/huggingface/transformers )是一个很好的框架,但是对于学习的人来说,并没有[bert4keras](https://github.com/bojone/bert4keras )来的实在,毕竟有苏神在带着。 8 | 9 | 本文主要为我在阅读和使用[bert4keras](https://github.com/bojone/bert4keras )源码时的思考,和使用方法一并写下(试图)作为一版文档吧。 10 | 11 | 因此,其实本仓库并不能称作完整的文档,其实是一个笔记,争取把用法和代码讲明白(我也能力有限,只能把我用过的和我学过的部分进行说明)。 12 | 13 | 已经做了[transformers](https://github.com/huggingface/transformers )的contributor努力做[bert4keras](https://github.com/bojone/bert4keras )的哈哈哈哈。 14 | 15 | **本文可能会照搬很多苏神的文字和代码,如果苏神介意我就取消开源** 16 | 17 | ### 快速开始 18 | 19 | 下面是一个调用bert base模型来编码句子的简单例子: 20 | 21 | from bert4keras.models import build_transformer_model 22 | from bert4keras.tokenizers import Tokenizer 23 | import numpy as np 24 | 25 | config_path = '/root/kg/bert/chinese_L-12_H-768_A-12/bert_config.json' 26 | checkpoint_path = '/root/kg/bert/chinese_L-12_H-768_A-12/bert_model.ckpt' 27 | dict_path = '/root/kg/bert/chinese_L-12_H-768_A-12/vocab.txt' 28 | 29 | tokenizer = Tokenizer(dict_path, do_lower_case=True) # 建立分词器 30 | model = build_transformer_model(config_path, checkpoint_path) # 建立模型,加载权重 31 | 32 | # 编码测试 33 | token_ids, segment_ids = tokenizer.encode(u'语言模型') 34 | 35 | print('\n ===== predicting =====\n') 36 | print(model.predict([np.array([token_ids]), np.array([segment_ids])])) 37 | 38 | 39 | ### 其他库 40 | 41 | 其他库详见文件列表。分别对应了[bert4keras](https://github.com/bojone/bert4keras )的几大文件。 42 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Sniper 2 | 3 | 这个文件主要是小工具(我也不知道为啥在这里不在snippets里)和适配tf的各个版本。 4 | 5 | 6 | ### def gelu_erf() 7 | 8 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/backend.py#L28 ) 9 | 10 | def gelu_erf(x) 11 | 12 | 基于Erf直接计算的gelu函数 13 | 14 | 根据公式 15 | ![img](../imgs/gelu_erf.jpg) 16 | 计算gelu。 17 | 18 | 19 | ### def gelu_tanh() 20 | 21 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/backend.py#L34 ) 22 | 23 | def gelu_tanh(x) 24 | 25 | 基于Tanh近似计算的gelu函数 26 | 27 | ### def set_gelu() 28 | 29 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/backend.py#L43 ) 30 | 31 | def set_gelu(version) 32 | 33 | 设置gelu版本(由于gelu没有精确数学表达,只能近似拟合,gelu有两个拟合版本),根据传入的version,动态的选择`gelu_tanh()`和`gelu_erf()` 34 | 35 | 其中: 36 | 37 | |参数| 说明| 38 | |:----- |-----| 39 | |version|版本:str| 40 | 41 | 其中 `assert version in ['erf', 'tanh']` 42 | 43 | ### def piecewise_linear() 44 | 45 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/backend.py#L54 ) 46 | 47 | def piecewise_linear(t, schedule) 48 | 49 | 分段线性函数。 50 | 51 | 用来给optimizers中的分断线性学习率优化器[def extend_with_piecewise_linear_lr_v2()](https://github.com/Sniper970119/bert4keras_document/tree/master/optimizers#def-extend_with_piecewise_linear_lr_v2() )提供基础方法。 52 | 53 | 54 | ### def search_layer() 55 | 56 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/backend.py#L82 ) 57 | 58 | def search_layer(inputs, name, exclude_from=None) 59 | 60 | 根据inputs和name来搜索层 61 | 62 | 根据inputs一直往上递归搜索,直到发现名字为name的层为止(所以输入应该是output);如果找不到,那就返回None。 63 | 64 | 其中: 65 | 66 | |参数| 说明| 67 | |:----- |-----| 68 | |inputs|某个层或某个层的输出:Tensor or EagerTensor| 69 | |name|目标层的名字:str| 70 | |exclude_from|排除的名单:list| 71 | 72 | 73 | ### def sequence_masking() 74 | 75 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/backend.py#L117 ) 76 | 77 | def sequence_masking(x, mask, value=0.0, axis=None) 78 | 79 | 80 | 序列条件mask的函数 81 | 82 | 83 | 其中: 84 | 85 | |参数| 说明| 86 | |:----- |-----| 87 | |x|输入数据 | 88 | |mask|形如(batch_size, seq_len)的0-1矩阵| 89 | |value| mask部分要被替换成的值,可以是'-inf'或'inf';:str or float| 90 | |axis|序列所在轴,默认为1:int| 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /imgs/gelu_erf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sniper970119/bert4keras_document/021bbc7786a1bf7e1b05ad437b07c16413241e97/imgs/gelu_erf.jpg -------------------------------------------------------------------------------- /layers/README.md: -------------------------------------------------------------------------------- 1 | # Sniper 2 | 3 | 这个文件主要为定义自定义层,在`model.py`中使用。 4 | * * * 5 | ## class Layer() 6 | 7 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L31 ) 8 | 9 | class Layer(keras.layers.Layer) 10 | 11 | 这里苏神做了版本统一与框架自定义。 12 | 13 | 对低于keras2.3(tf1)的版本,添加层中层的概念(也就是一层中可以有多层)。 14 | 15 | > 相比原生keras,通过在对象中[添加_layers列表](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L43 ),将“子层”添加到列表中从而做到支持层中层。 16 | 17 | 同时对于keras版本低于2.2.5重新定义Model,整合fit和git_generator() 18 | 19 | 另外单独一说,keras的supports_masking默认为False,bert4keras全部为True,即支持单独mask。 20 | 21 | [keras中supports_masking为False](https://github.com/keras-team/keras/blob/keras-2/keras/engine/topology.py#L249 ) 22 | 23 | [bert4keras中supports_masking默认为True](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L39 ) 24 | * * * 25 | ## class GlobalAveragePooling1D() 26 | 27 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L122 ) 28 | 29 | class GlobalAveragePooling1D(keras.layers.GlobalAveragePooling1D) 30 | 31 | 重新定义GlobalAveragePooling1D,支持序列长度为None。 32 | * * * 33 | ## class GlobalMaxPooling1D() 34 | 35 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L136 ) 36 | 37 | class GlobalMaxPooling1D(keras.layers.GlobalMaxPooling1D) 38 | 39 | 重新定义GlobalMaxPooling1D,支持mask。 40 | 41 | 通过 backend的[def sequence_masking()](https://github.com/Sniper970119/bert4keras_document/tree/master/backend#def-sequence_masking ) 来进行mask。 42 | * * * 43 | ## class Embedding() 44 | 45 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L156 ) 46 | 47 | class Embedding(keras.layers.Embedding) 48 | 49 | 拓展Embedding层 50 | 51 | 相比原生的Embedding,主要适配了T5以及其类似模型,保证第一个token不能被mask。 52 | * * * 53 | ## class BiasAdd() 54 | 55 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L192 ) 56 | 57 | class BiasAdd(Layer) 58 | 59 | 在这一层加上一个可训练的偏置,用于`model.py`中的模型构建。 60 | * * * 61 | ## class Concatenate1D() 62 | 63 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L210 ) 64 | 65 | class Concatenate1D(Layer) 66 | 67 | 1维序列拼接层 68 | 69 | 说明:本来该功能可以直接通过Concatenate层来实现,无奈Keras自带的Concatenate层的compute_mask写得不合理,导致一个 70 | mask的序列与一个不带mask的序列拼接会报错,因此干脆自己重写一个好了。 71 | 72 | 对比tensorflow中的[代码](https://github.com/tensorflow/tensorflow/blob/r2.4/tensorflow/python/keras/layers/merge.py#L542 ), 73 | 可以发现bert4keras在compute_mask这里并不是像tensorflow中最直接append到masks中,而是如果mask为空,则初始化一个一样的mask矩阵。 74 | 75 | tensorflow: 76 | 77 | if mask_i is None: 78 | # Input is unmasked. Append all 1s to masks, 79 | masks.append(array_ops.ones_like(input_i, dtype='bool')) 80 | elif K.ndim(mask_i) < K.ndim(input_i): 81 | # Mask is smaller than the input, expand it 82 | masks.append(array_ops.expand_dims(mask_i, axis=-1)) 83 | else: 84 | masks.append(mask_i) 85 | 86 | bert4keras: 87 | 88 | if mask is not None: 89 | masks = [] 90 | for i, m in enumerate(mask): 91 | if m is None: 92 | m = K.ones_like(inputs[i][..., 0], dtype='bool') 93 | masks.append(m) 94 | return K.concatenate(masks, axis=1) 95 | 96 | * * * 97 | ## class MultiHeadAttention() 98 | 99 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L234 ) 100 | 101 | class MultiHeadAttention(Layer) 102 | 103 | 多头注意力机制 104 | 105 | 其中: 106 | |参数| 说明| 107 | |:----- |-----| 108 | |heads |头的个数:int| 109 | |head_size |头的大小:int| 110 | |out_dim |输出维度:int| 111 | |key_size |论文中的alpha:int| 112 | |use_bias |是否使用偏置:bool| 113 | |attention_scale |是否使用小规模参数:bool| 114 | |return_attention_scores |是否返回注意力分数:bool| 115 | |kernel_initializer |初始化方式:str| 116 | 117 | 118 | 其中, 119 | 120 | `heads` 就是多头注意力的头个数,比如bert-base为12。 121 | 122 | `head_size`就是每个头的大小,比如bert-base为64。 123 | 124 | `out_dim` 默认为None。 为None时就是heads * head_size ,不为None则后面跟一个dense层处理到out_dim这个维度。 125 | 126 | `key_size`默认为None。为None时就是head_size,不一样时,Q和K会被初始化为key_size*heads。至于这个参数非默认情况的用途。。我没看出来。。等见过之后回来补充。 127 | 128 | `use_bias`是否使用偏置(传入的QKV首先会经过一次线性变换后然后计算attention,这里的偏置指的是这个线性变化的,默认为True)。 129 | 130 | `attention_scale` 返回的注意力参数规模,如果为True则开方(sqrt)后返回。 131 | 132 | `attention_dropout`,2021.09.13更新,使能够对attention进行dropout,输入dropout rate 133 | 134 | `return_attention_scores`是否返回注意力分数。 135 | 136 | `kernel_initializer` 参数初始化方式,默认为`glorot_uniform`。 137 | 138 | 最终的mask是通过计算完attention通过v_mask的值mask掉attention的输出。 139 | 140 | 141 | 对于attention的高端操作(比如,attention bias、position embedding[NEZHA将相对位置编码直接加到了attention中])[NEZHA](https://github.com/Sniper970119/bert4keras_document/tree/master/models#class-NEZHA ) 142 | 143 | 将这些 144 | 145 | 这里通过一行代码 146 | 147 | qkv_inputs = [qw, kw, vw] + inputs[3:] 148 | 149 | 将bias添加到了inputs后,然后在`def pay_attention_to`中进行了注意力的计算。 150 | 151 | 这里有一个规约:inputs[4]为attention bias,而inputs[5]则为position bias(如果不需要bias[a_bias=None],则inputs[4]为position bias). 152 | 153 | 这里单独一提,对于语言模型的mask,是通过 154 | [LM_MASK](https://github.com/Sniper970119/bert4keras_document/tree/master/models#class-LM_Mask )以及 155 | [UniLM_Mask](https://github.com/Sniper970119/bert4keras_document/tree/master/models#class-UniLM_Mask) 156 | 157 | 这类方法计算一个偏置 bias,通过keras的support_mask=True在各个层之间传递,最终通过在原结果中减去一个极大值,从而将attention的结果变为一个绝对值很大的负数,这样这个位置的值在softmax就会无限接近于0,因此就被mask掉了。 158 | 159 | * * * 160 | ## class GatedAttentionUnit() 161 | 162 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L612 ) 163 | 164 | class GatedAttentionUnit(Layer) 165 | 166 | 门控注意力单元 167 | [链接](https://arxiv.org/abs/2202.10447) 168 | [苏神介绍](https://kexue.fm/archives/8934) 169 | 170 | 171 | 其中: 172 | |参数| 说明| 173 | |:----- |-----| 174 | |units |dense的unit的个数:int| 175 | |key_size |attention隐藏层个数:int| 176 | |activation |激活方式,默认seish| 177 | |use_bias |dense是否使用偏置| 178 | |normalization |标准化方式,默认relu的平方:int| 179 | |attention_scale |注意力是否使用scale(开立方),默认为True:bool| 180 | |attention_dropout |注意力是否使用dropout,论文没有使用dropout,但是其实使用也无妨:float| 181 | |kernel_initializer |初始化方式:str| 182 | 183 | 184 | 185 | 186 | * * * 187 | ## class LayerNormalization() 188 | 189 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L399 ) 190 | 191 | class LayerNormalization(Layer) 192 | 193 | (Conditional) Layer Normalization 194 | 195 | hidden_*系列参数仅为有条件输入时(conditional=True)使用 196 | 197 | 其中: 198 | |参数| 说明| 199 | |:----- |-----| 200 | |center |是否使用beta:bool| 201 | |scale |是否使用gamma:bool| 202 | |epsilon |一个极小值:float| 203 | |conditional |是否为Conditional Layer Normalization:bool| 204 | |hidden_units |隐藏层个数:int| 205 | |hidden_activation |隐藏层激活方法:str| 206 | |hidden_initializer |初始化方式:str| 207 | 208 | 其中, 209 | 210 | `center` 是否使用beta。 211 | 212 | `scale` 是否使用gamma。 213 | 214 | `epsilon` 默认为1e-12。 用于计算标准差时的极小值。 215 | 216 | `conditional`False。为True时为使用Conditional Layer Normalization,该方法通过在LN层加入一个方向的扰动,从而可以在一个模型中完成多个类似的任务, 217 | 比如在一个模型中生成积极的文本和消极的文本、在一个模型中进行短短文本匹配,短长文本匹配等。详见[苏神博客](https://kexue.fm/archives/7124 ) 218 | 219 | `hidden_units`隐藏层个数,用于控制在Conditional Layer Normalization的embedding大小。 220 | 221 | `hidden_activation` 隐藏层激活方法,默认为`linear`。 222 | 223 | `hidden_initializer` 参数初始化方式,默认为`glorot_uniform`。 224 | 225 | * * * 226 | ## class PositionEmbedding() 227 | 228 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L524 ) 229 | 230 | class PositionEmbedding(Layer) 231 | 232 | 定义可训练的位置Embedding(比如Bert的position embedding) 233 | 234 | def __init__( 235 | self, 236 | input_dim, 237 | output_dim, 238 | merge_mode='add', 239 | hierarchical=None, 240 | embeddings_initializer='zeros', 241 | custom_position_ids=False, 242 | **kwargs 243 | ) 244 | 245 | `hierarchical`默认为None,为True时为使用超长编码(利用层次分解,将bert的最长512的序列长度扩充为512*512,会损失一定精度, 246 | 但是微调后可以使用很小的代价恢复性能) [苏神博客](https://kexue.fm/archives/7947 ) 247 | 248 | 249 | 250 | * * * 251 | 252 | ## class SinusoidalPositionEmbedding() 253 | 254 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L612 ) 255 | 256 | class SinusoidalPositionEmbedding(Layer) 257 | 258 | 定义Sin-Cos位置Embedding(比如transformer的position embedding) 259 | * * * 260 | 261 | ## class RelativePositionEmbedding() 262 | 263 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L672 ) 264 | 265 | class RelativePositionEmbedding(Layer) 266 | 267 | 计算相对位置编码。(比如NEZHA的position embedding)[代码](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1085), 268 | [文档](https://github.com/Sniper970119/bert4keras_document/tree/master/models#class-NEZHA ) 269 | * * * 270 | 271 | ## class RelativePositionEmbeddingT5() 272 | 273 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L727 ) 274 | 275 | class RelativePositionEmbeddingT5(Layer) 276 | 277 | Google T5的相对位置编码 https://arxiv.org/abs/1910.10683 278 | 279 | * * * 280 | ## class FeedForward() 281 | 282 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L786 ) 283 | 284 | class FeedForward(Layer): 285 | 286 | FeedForward层 287 | 288 | def __init__( 289 | self, 290 | units, 291 | activation='relu', 292 | use_bias=True, 293 | kernel_initializer='glorot_uniform', 294 | **kwargs 295 | ) 296 | 297 | 298 | 如果activation不是一个list,那么它就是两个Dense层的叠加; 299 | 300 | 如果activation是一个list,那么第一个Dense层将会被替换成门控线性单元(Gated Linear Unit)。 301 | 302 | [参考论文(T5.1.1的论文,通过使用GLU来增强FFN的效果。)](https://arxiv.org/abs/2002.05202 ) 303 | [苏神博客](https://kexue.fm/archives/7867#T5.1.1 ) 304 | 305 | * * * 306 | ## class ConditionalRandomField() 307 | 308 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L850 ) 309 | 310 | class ConditionalRandomField(Layer) 311 | 312 | 纯Keras实现CRF层,CRF层本质上是一个带训练参数的loss计算层。 313 | 314 | 条件随机场,整个框架内暂时没有被(调)用过。 315 | 316 | * * * 317 | ## class MaximumEntropyMarkovModel() 318 | 319 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L971 ) 320 | 321 | class MaximumEntropyMarkovModel(Layer): 322 | 323 | 324 | (双向)最大熵隐马尔可夫模型,作用和用法都类似CRF,但是比CRF更快更简单。 325 | 326 | 同样,整个框架内暂时没有被(调)用过。 327 | 328 | * * * 329 | ## class Loss() 330 | 331 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/layers.py#L1158 ) 332 | 333 | class Loss(Layer) 334 | 335 | 特殊的层,用来定义复杂loss。 336 | 337 | 通过引入一个层,来实现复杂的loss计算。在层内部通过将计算后的loss添加到self.add_loss从而定义这个层的loss。 338 | 339 | def call(self, inputs, mask=None): 340 | loss = self.compute_loss(inputs, mask) 341 | self.add_loss(loss, inputs=inputs) 342 | if self.output_axis is None: 343 | return inputs 344 | elif isinstance(self.output_axis, list): 345 | return [inputs[i] for i in self.output_axis] 346 | else: 347 | return inputs[self.output_axis] 348 | 349 | def compute_loss(self, inputs, mask=None): 350 | raise NotImplementedError 351 | 352 | 通过`self.add_loss(loss, inputs=inputs)`定义改层loss,并且返回`inputs`,因此并不改变整个网络的输出。 353 | 354 | 而add_loss 则是`class Layer(object):`中的一个方法,定义如下: 355 | 356 | def add_loss(self, losses, inputs=None): 357 | """Adds losses to the layer. 358 | 359 | The loss may potentially be conditional on some inputs tensors, 360 | for instance activity losses are conditional on the layer's inputs. 361 | 362 | # Arguments 363 | losses: loss tensor or list of loss tensors 364 | to add to the layer. 365 | inputs: input tensor or list of inputs tensors to mark 366 | the losses as conditional on these inputs. 367 | If None is passed, the loss is assumed unconditional 368 | (e.g. L2 weight regularization, which only depends 369 | on the layer's weights variables, not on any inputs tensors). 370 | """ 371 | 372 | 这样添加了一个loss层来计算loss作为整个模型的loss。实在是。。太妙了,牛b。 373 | 374 | example: 375 | 376 | class CrossEntropy(Loss): 377 | """交叉熵作为loss,并mask掉padding部分 378 | """ 379 | 380 | def compute_loss(self, inputs, mask=None): 381 | y_true, y_pred = inputs 382 | if mask[1] is None: 383 | y_mask = 1.0 384 | else: 385 | y_mask = K.cast(mask[1], K.floatx())[:, 1:] 386 | y_true = y_true[:, 1:] # 目标token_ids 387 | y_pred = y_pred[:, :-1] # 预测序列,错开一位 388 | loss = K.sparse_categorical_crossentropy(y_true, y_pred) 389 | loss = K.sum(loss * y_mask) / K.sum(y_mask) 390 | return loss 391 | 392 | model = build_transformer_model( 393 | config_path, 394 | checkpoint_path, 395 | application='lm', 396 | ) 397 | output = CrossEntropy(1)([model.inputs[0], model.outputs[0]]) 398 | 399 | 这里自定义了一个可以mask的损失函数(MLM的),然后下面通过将Bert的输入和输出一同送入计算loss,并返回 `model.outputs[0]` 也就是索引1. 400 | 401 | 这样就可以“透明”的添加一个自定义loss,而不改变模型输出。 402 | -------------------------------------------------------------------------------- /models/README.md: -------------------------------------------------------------------------------- 1 | # Sniper 2 | 3 | Model主要存放一些模型。比如Trm、Bert、T5等。 4 | * * * 5 | ## class Transformer() 6 | 7 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L13) 8 | 9 | class Transformer(object) 10 | 11 | 模型基类。所有Transformer based(Bert以及各种变种、T5等)的模型的基类。 12 | 13 | def __init__( 14 | self, 15 | vocab_size, # 词表大小 16 | hidden_size, # 编码维度 17 | num_hidden_layers, # Transformer总层数 18 | num_attention_heads, # Attention的头数 19 | intermediate_size, # FeedForward的隐层维度 20 | hidden_act, # FeedForward隐层的激活函数 21 | dropout_rate=None, # Dropout比例 22 | attention_dropout_rate=None, # Attention矩阵的Dropout比例(2021.09.13更新) 23 | embedding_size=None, # 是否指定embedding_size 24 | attention_head_size=None, # Attention中V的head_size 25 | attention_key_size=None, # Attention中Q,K的head_size 26 | sequence_length=None, # 是否固定序列长度 27 | keep_tokens=None, # 要保留的词ID列表 28 | compound_tokens=None, # 扩展Embedding 29 | residual_attention_scores=False, # Attention矩阵加残差 30 | ignore_invalid_weights=False, # 允许跳过不存在的权重 31 | layers=None, # 外部传入的Keras层 32 | prefix=None, # 层名前缀 33 | name=None, # 模型名称 34 | **kwargs 35 | ) 36 | 37 | 大部分参数代码注释比较完善,需要格外说明的: 38 | 39 | `hierarchical`默认为None,为True时为使用超长编码(利用层次分解,将bert(Transformer)的最长512的序列长度扩充为512*512,会损失一定精度, 40 | 但是微调后可以使用很小的代价恢复性能) [苏神博客](https://kexue.fm/archives/7947 ) 41 | 42 | `residual_attention_scores`是否使用残差Attention矩阵。残差Attention矩阵,给每个Attention矩阵加上前上一层的Attention矩阵, 43 | 来源RealFormer[论文](https://arxiv.org/abs/2012.11747 ),目前的实现可能还相对粗糙,欠缺通用性。 44 | 45 | `ignore_invalid_weights` 为是否允许跳过名字不匹配的权重。默认为False,为True时,遇到名字不匹配的层名字时, 会输出一个报错信息,但是程序并不会终止,改层的权重会随机初始化。 46 | 47 | 48 | 2021.09.13更新:新增支持attention 的dropout。PS:由于更新,下面的源码行数(&SOURCE)可能有一定的偏移,但是由于更新较小,偏移不大,基本就在行数下面几行的位置,因此不做更改。[github地址](https://github.com/bojone/bert4keras/commit/48626f05ace6559c5c318a2f453c2296ababa8cd#diff-4a84fd2720a02ea8e9b5b7028daa441d2c02f5b1e9b1dcb4d561a0c8175e6ff9) 49 | 50 | 51 | ### def build(self): 52 | 53 | def build( 54 | self, 55 | attention_caches=None, 56 | layer_norm_cond=None, 57 | layer_norm_cond_hidden_size=None, 58 | layer_norm_cond_hidden_act=None, 59 | additional_input_layers=None, 60 | **kwargs 61 | ): 62 | 63 | `attention_caches` 为Attention的K,V的缓存序列字典,格式为{Attention层名: [K缓存, V缓存]}; 64 | 65 | `layer_norm_*`系列参数:实现`Conditional Layer Normalization` 66 | 时使用,用来实现以“固定长度向量”为条件的条件Bert。该方法通过在LN层加入一个方向的扰动,从而可以在一个模型中完成多个类似的任务, 67 | 比如在一个模型中生成积极的文本和消极的文本、在一个模型中进行短短文本匹配,短长文本匹配等。详见[苏神博客](https://kexue.fm/archives/7124 ) 68 | 69 | `additional_input_layers`为除Bert原生输入外其余的输入项。通过`self.set_inputs()`来添加到模型中。 70 | 71 | 72 | ### def call(self): 73 | 74 | def call(self, inputs): 75 | """定义模型的执行流程 76 | """ 77 | # Embedding 78 | outputs = self.apply_embeddings(inputs) 79 | # Main 80 | for i in range(self.num_hidden_layers): 81 | outputs = self.apply_main_layers(outputs, i) 82 | # Final 83 | outputs = self.apply_final_layers(outputs) 84 | return outputs 85 | 86 | call方法可以看出来,整体来说,是embedding、main layers(Transformer)、final layers(dense)。 87 | 88 | ### def set_inputs(self): 89 | 90 | def set_inputs(self, inputs, additional_input_layers=None): 91 | """设置input和inputs属性 92 | """ 93 | if inputs is None: 94 | inputs = [] 95 | elif not isinstance(inputs, list): 96 | inputs = [inputs] 97 | 98 | inputs = inputs[:] 99 | if additional_input_layers is not None: 100 | if not isinstance(additional_input_layers, list): 101 | additional_input_layers = [additional_input_layers] 102 | inputs.extend(additional_input_layers) 103 | 104 | self.inputs = inputs 105 | if len(inputs) > 1: 106 | self.input = inputs 107 | else: 108 | self.input = inputs[0] 109 | 110 | set_inputs方法可以看出来如何添加的`additional_input_layers`,同时处理input参数。 (input/inputs区分一下,我研究半天这是干嘛的,后来发现不一样,如果你观察过`bert4keras` 111 | 的模型你就会发现有input和inputs两个变量)。 112 | 113 | ### def load_embeddings(self): 114 | 115 | def load_embeddings(self, embeddings): 116 | """处理Embedding层权重 117 | """ 118 | embeddings = embeddings.astype(K.floatx()) # 防止np.average报错 119 | 120 | if self.keep_tokens is not None: 121 | embeddings = embeddings[self.keep_tokens] 122 | 123 | if self.compound_tokens is not None: 124 | ext_embeddings = [] 125 | for item in self.compound_tokens: 126 | if isinstance(item, list): 127 | item = (item, [1] * len(item)) 128 | ext_embeddings.append( 129 | np.average(embeddings[item[0]], 0, item[1]) 130 | ) 131 | embeddings = np.concatenate([embeddings, ext_embeddings], 0) 132 | 133 | return embeddings 134 | 135 | load_embedding分别对应的缩小embedding(keep_token)和扩大embedding(compound_token)两种情况。 136 | 137 | 前者用于不需要这么多token(比如bert4keras默认的精简方式详见[参数simplified](https://github.com/Sniper970119/bert4keras_document/tree/master/tokenizers#def-load_vocab ) 138 | ) ,只需要将embedding对应部分截取出来就行。 后者对应需要更多的token,直接在embedding中添加新的行(axis=0)就行了。 139 | 140 | 141 | ### def compute_attention_bias(self) 142 | 143 | def compute_attention_bias(self, inputs=None): 144 | """定义每一层的Attention Bias 145 | """ 146 | return self.attention_bias 147 | 148 | 这个方法主要是计算attention的mask(或者bias)比如在[LM_MASK](https://github.com/Sniper970119/bert4keras_document/tree/master/models#class-LM_Mask )以及[UniLM_Mask](https://github.com/Sniper970119/bert4keras_document/tree/master/models#class-UniLM_Mask ) 149 | 中复写的`compute_attention_bias`,用于相关用途(在attention阶段添加mask[比如LM中的随机Mask]或bias[比如NEZHA在attention中添加相对位置编码])。 150 | 151 | 152 | 153 | * * * 154 | ## class LM_Mask() 155 | 156 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L349) 157 | 158 | class LM_Mask(object) 159 | 160 | 定义下三角Attention Mask(语言模型用) 161 | 162 | def compute_attention_bias(self, inputs=None): 163 | """通过idxs序列的比较来得到对应的mask 164 | """ 165 | if self.attention_bias is None: 166 | 167 | def lm_mask(s): 168 | seq_len = K.shape(s)[1] 169 | idxs = K.arange(0, seq_len) 170 | mask = idxs[None, :] <= idxs[:, None] 171 | mask = K.cast(mask, K.floatx()) 172 | return -(1 - mask[None, None]) * 1e12 173 | 174 | self.attention_bias = self.apply( 175 | inputs=self.inputs[0], 176 | layer=Lambda, 177 | function=lm_mask, 178 | name='Attention-LM-Mask' 179 | ) 180 | 181 | return self.attention_bias 182 | 183 | 这里就是计算一个下三角矩阵,通过s(s -> [batch_size,token_ids])计算mask矩阵。用于进行语言模型的训练(其实就是GPT-2的思路)。 184 | 185 | 使用只需要在`build_transformer_model`中添加`application='lm'`即可。 186 | 187 | 这里`mask = idxs[None, :] <= idxs[:, None]`添加两个None维度是为了便于idx的错位比较 188 | 189 | 最后输出[1,1,token_len,token_len],最后两个token_len为mask矩阵。用于拼接在MultiHeadAttention的输入中。 190 | 191 | 详见[multi-head-attention](https://github.com/Sniper970119/bert4keras_document/tree/master/layers#class-multiheadattention ) 192 | 193 | example: 194 | 195 | model = build_transformer_model( 196 | config_path = config_path, 197 | checkpoint_path = checkpoint_path, 198 | model='bert', 199 | application='lm', 200 | ) 201 | * * * 202 | ### class UniLM_Mask() 203 | 204 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L374) 205 | 206 | class UniLM_Mask(object) 207 | 208 | 定义UniLM的Attention Mask(Seq2Seq模型用) 209 | [UniLM](https://arxiv.org/abs/1905.03197 )[苏神博客](https://kexue.fm/archives/6933 ) 210 | 211 | def compute_attention_bias(self, inputs=None): 212 | """通过idxs序列的比较来得到对应的mask 213 | """ 214 | if self.attention_bias is None: 215 | 216 | def lm_mask(s): 217 | seq_len = K.shape(s)[1] 218 | idxs = K.arange(0, seq_len) 219 | mask = idxs[None, :] <= idxs[:, None] 220 | mask = K.cast(mask, K.floatx()) 221 | return -(1 - mask[None, None]) * 1e12 222 | 223 | self.attention_bias = self.apply( 224 | inputs=self.inputs[0], 225 | layer=Lambda, 226 | function=lm_mask, 227 | name='Attention-LM-Mask' 228 | ) 229 | 230 | return self.attention_bias 231 | 232 | 这里就是通过s(s -> [batch_size,segment_ids])的segment_ids为1的地方进行下三角矩阵mask,用以完成UniLM的Seq2Seq任务。 233 | 234 | 使用只需要在`build_transformer_model`中添加`application='unilm'`即可。 235 | 236 | `idxs = K.cumsum(s, axis=1)` 对列进行求和(eg:[0,0,1,1,1]则返回[0,0,1,2,3])。 237 | 238 | 这里`idxs[:, None, :] <= idxs[:, :, None]`添加两个None维度是为了便于idx的错位比较 239 | 240 | 最后输出[batch size,1,token_len,token_len],最后两个token_len为mask矩阵。用于拼接在MultiHeadAttention的输入中。 241 | 242 | 详见[multi-head-attention](https://github.com/Sniper970119/bert4keras_document/tree/master/layers#class-multiheadattention ) 243 | 244 | example: 245 | 246 | model = build_transformer_model( 247 | config_path = config_path, 248 | checkpoint_path = checkpoint_path, 249 | model='bert', 250 | application='unilm', 251 | ) 252 | 253 | * * * 254 | 255 | ## class BERT() 256 | 257 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#400) 258 | 259 | class BERT(Transformer) 260 | 261 | Bert类,继承了`Transformer`类 262 | 263 | def __init__( 264 | self, 265 | max_position, # 序列最大长度 266 | segment_vocab_size=2, # segment总数目 267 | with_pool=False, # 是否包含Pool部分 268 | with_nsp=False, # 是否包含NSP部分 269 | with_mlm=False, # 是否包含MLM部分 270 | hierarchical_position=None, # 是否层次分解位置编码 271 | custom_position_ids=False, # 是否自行传入位置id 272 | shared_segment_embeddings=False, # 若True,则segment跟token共用embedding 273 | **kwargs # 其余参数 274 | ) 275 | 276 | 我们可以发现,苏神在这里还支持了多segment_idx(原生bert仅支持两句话,也就是segment_vocab_size=2)。 277 | 278 | 当然多segment是有代价的,就是原bert的segment需要被弃用,需要在代码的`def load_weights_from_checkpoint`(Transformer类的类方法)中将`Embedding-Segment`移除`mapping`(`mapping.pop('Embedding-Segment')`)从而不再加载这一部分的预训练权重。 279 | 280 | `with_pool`就是最后CLS的768维、`with_nsp`就是是否进行NSP任务(当进行NSP任务时,`with_pool`必须为True。因为nsp需要CLS向量。当然,这一步代码可以自动处理), 281 | 282 | if self.with_nsp and not self.with_pool: 283 | self.with_pool = True 284 | 285 | `with_mlm`也是是否进行MLM任务。 286 | 287 | `hierarchical_position`对应的层次编码,以让bert可以处理512*512长度的文本[苏神博客](https://kexue.fm/archives/7947 )。 288 | 289 | ### def apply_embeddings(self): 290 | 291 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L454 ) 292 | 293 | 这个方法为BERT的embedding,它是token、position、segment三者embedding之和 294 | 295 | 从这里我们可以看到bert的embedding过程,同时还适配处理了`Conditional Layer Normalization`,[苏神博客](https://kexue.fm/archives/7124 )。 296 | 297 | 这里提一嘴,为什么三者相加呢?不怕信息混乱吗? 298 | 299 | 苏神在这里给的解释是: 300 | 301 | Embedding的数学本质,就是以one hot为输入的单层全连接。 302 | 也就是说,世界上本没什么Embedding,有的只是one hot。 ” 303 | 304 | 所以你给三个拼接再送去下级网络,和加起来,并没有什么实质性区别。 305 | 306 | ### def apply_main_layers(self): 307 | 308 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L529 ) 309 | 310 | BERT的主体是基于Self-Attention的模块。顺序:Att --> Add --> LN --> FFN --> Add --> LN 311 | 312 | 这里是Bert的Transformer的最基本层(也就是Bert由12个这种层组成),由基类Transformer的[call](https://github.com/Sniper970119/bert4keras_document/tree/master/models#def-call ) 313 | 进行循环调用 314 | 315 | 这里的LN依然适配了`Conditional Layer Normalization`,[苏神博客](https://kexue.fm/archives/7124 )。 316 | 317 | * * * 318 | 319 | ## class ALBERT() 320 | 321 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#776) 322 | 323 | class ALBERT(BERT) 324 | 325 | ALBERT模型,继承Bert。 326 | 327 | 重新定义了`apply_main_layers`(核心层)和层名称映射(因为相比Bert,公共层参数了,所以映射也会发生变化。可以看到Albert的映射中并没有循环)。 328 | 329 | ### def apply_main_layers(self): 330 | 331 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L779 ) 332 | 333 | ALBERT的主体是基于Self-Attention的模块。顺序:Att --> Add --> LN --> FFN --> Add --> LN 334 | 335 | 其实这里除了命名(Bert的12层分别命名)之外,相比Bert没有什么变化。 336 | 337 | 由于Bert的[apply_embeddings](https://github.com/Sniper970119/bert4keras_document/tree/master/models#def-apply_embeddings ) 338 | 已经处理了embedding和hidden size不符合的问题,因此Albert这里对嵌入压缩并不需要格外适配。 339 | 340 | * * * 341 | 342 | ## class ALBERT_Unshared() 343 | 344 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#893) 345 | 346 | class ALBERT_Unshared(BERT) 347 | 348 | 解开ALBERT共享约束,当成BERT用。 349 | 350 | 这个就可以只修改权重名映射了,因为“不共享”就和Bert一样了。embedding压缩Bert的基类也已经处理了。 351 | 352 | * * * 353 | 354 | ## class NEZHA() 355 | 356 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#933) 357 | 358 | class NEZHA(BERT) 359 | 360 | 华为推出的NAZHA模型。[论文链接](https://arxiv.org/abs/1909.00204 ) 361 | 362 | 全称为“ **NE**ural contextuali**Z**ed representation for C**H**inese l**A**nguage understanding ”。 363 | 364 | 主要改进如下: 365 | 366 | 1.增加相对位置编码函数 367 | 368 | - Bert中学习了绝对位置编码,Transformer中也是用了函数式编码。 NEZHA通过在注意力机制中引入相对位置的概念,提升了在NER等任务中的效果。 369 | 370 | 2.全词掩码 371 | 372 | - 他减轻了预训练过程中掩码不分word pirce的弊端。 比如:playing在token部分会被分为play和##ing,而原生bert会随机的mask play或者##ing或者两者全部mask,而wwm则只会mask两者 373 | 374 | 3.混合精度训练 375 | 376 | - 在训练过程中的每一个step,为模型的所有weight维护一个FP32的copy,称为Master Weights;在做前向和后向传播过程中,Master 377 | Weights会转换成FP16(半精度浮点数)格式,其中权重、激活函数和梯度都是用FP16进行表示,最后梯度会转换成FP32格式去更新Master Weights。 378 | 由于float16的运算速度大于float32,因此能够显著提升训练速度。 379 | 380 | 4.优化器改进 381 | 382 | - NEZHA使用了《Large Batch Optimization for Deep Learning:Training BERT in 76 minutes》 383 | ([def extend_with_layer_adaptation](https://github.com/Sniper970119/bert4keras_document/tree/master/optimizers#def-extend_with_layer_adaptation_v2 ) 384 | ) 的一个优化器,它可以将预训练bert时间从三天降到76分钟。 385 | 386 | 从上面的改进就可以发现,模型端的改进主要就是位置编码。 387 | 388 | 因此NEZHA的embedding是token、segment两者embedding之和 389 | 390 | ### def apply_embeddings(self): 391 | 392 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L937 ) 393 | 394 | 可以看到,并没有Position Embedding。同时依然适配了embedding压缩。 395 | 396 | ### def compute_position_bias(self): 397 | 398 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1077) 399 | 400 | 这里就是计算相对位置编码的地方。可以看到最后输出的维度为attention_key_size(bert base为64)。 401 | 402 | 调用[class RelativePositionEmbedding](https://github.com/Sniper970119/bert4keras_document/tree/master/layers#class-relativepositionembedding) 403 | 404 | ### def apply_main_layers(self): 405 | 406 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L997) 407 | 408 | 和其他的并没有什么太大差距,不同的是这里将position_bias(def compute_position_bias的返回)送入attention中。 409 | 410 | 这里送入Multi-Head-attention中的数据从3维上升到4维(或更高)。 411 | 412 | 详情查看[Multi-Head-attention](https://github.com/Sniper970119/bert4keras_document/tree/master/layers#class-MultiHeadAttention) 413 | 414 | 415 | * * * 416 | 417 | ## class RoFormer() 418 | 419 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#1096) 420 | 421 | class RoFormer(NEZHA) 422 | 423 | 旋转式位置编码的BERT模型。[苏神博客](https://kexue.fm/archives/8265 ) 424 | 425 | 一个苏神(追一科技)自研的模型。 426 | 427 | 既然是“旋转式位置编码的BERT模型”,为什么继承NEZHA不继承BERT呢? 428 | 429 | 因为既然采用了“旋转式位置编码”,也就意味着同样是“相对位置编码”。 430 | 431 | 实际上,旋转式位置编码(Rotary Position Embedding,RoPE),是一种配合Attention机制能达到“绝对位置编码的方式实现相对位置编码”的设计。 432 | 433 | 因此,这种方式依然需要将绝对位置编码送入attention中。因此需要“借用”NEZHA中已经写好的位置编码(因为都没有进行position embedding)。 434 | 435 | * * * 436 | 437 | ## class ELECTRA() 438 | 439 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#1197) 440 | 441 | class ELECTRA(BERT) 442 | 443 | Google推出的ELECTRA模型[论文](https://arxiv.org/abs/2003.10555 ) 444 | 445 | 相比Bert,主要是将结构更改称为类强化学习的思路(但是不是),通过生成器和判别器来训练。[我的笔记](http://www.sniper97.cn/index.php/note/deep-learning/3842/ ) 446 | 447 | 但是苏神这里的ELECTRA并不是完整模型,而只是判别器(只有在预训练过程中需要生成器)。 448 | 449 | 而原文的判别器也是bert base的模型,因此这里苏神只对模型特有的最后一层进行了一定的改变(`def apply_final_layers`)。 450 | 451 | * * * 452 | 453 | ## class GPT() 454 | 455 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1267) 456 | 457 | class GPT(LM_Mask, BERT) 458 | 459 | GPT([GPT-1](https://github.com/openai/finetune-transformer-lm)) 460 | 461 | 可以看到,由于继承了`LM_Mask`,而`LM_Mask`复写了`compute_attention_bias`方法,更换为下三角矩阵,以达到Mask的效果。 462 | 463 | ### def apply_embedding() 464 | 465 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1276) 466 | 467 | def apply_embeddings(self, inputs): 468 | 469 | GPT的embedding是token、position、segment三者embedding之和。 470 | 跟BERT的主要区别是三者相加之后没有加LayerNormalization层。 471 | 472 | * * * 473 | 474 | ## class GPT2() 475 | 476 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1386) 477 | 478 | class GPT2(GPT) 479 | 480 | 构建GPT2模型,[GPT-2](https://github.com/openai/gpt-2) 481 | 482 | ### def get_inputs() 483 | 484 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1390) 485 | 486 | def get_inputs(self): 487 | 488 | GPT-2的输入。GPT2的输入是token_ids。 489 | 490 | ### def apply_embeddings() 491 | 492 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1398) 493 | 494 | def apply_embeddings(self, inputs): 495 | 496 | GPT2的embedding是token、position两者embedding之和。 497 | 498 | ### def apply_main_layers() 499 | 500 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1433) 501 | 502 | def apply_main_layers(self, inputs, index): 503 | 504 | GPT2的主体是基于Self-Attention的模块。 505 | 506 | 顺序:LN --> Att --> Add --> LN --> FFN --> Add 507 | 508 | 作为对比,这里贴出来Bert的: 509 | 510 | 顺序:Att --> Add --> LN --> FFN --> Add --> LN 511 | 512 | ### def apply_final_layers() 513 | 514 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1508) 515 | 516 | def apply_final_layers(self, inputs): 517 | 518 | GPT-2的剩余部分。 519 | 520 | 相比GPT,对了一个LN(和dropout),因此整体结构变为: 521 | 522 | (LN --> Att --> Add --> LN --> FFN --> Add )*n --> LN --> Embedding 523 | 524 | * * * 525 | 526 | ## class GPT2_ML() 527 | 528 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1550) 529 | 530 | class GPT2_ML(GPT) 531 | 532 | 533 | 534 | 构建[GPT2_ML](https://github.com/imcaspar/gpt2-ml) 模型 535 | GPT2_ML虽然号称GPT2,但是它的结构其实更接近GPT,它自称GPT2的原因大概是因为它开源的版本参数量达到了GPT2的15亿参数。 536 | 537 | GPT2_ML的主体是基于Self-Attention的模块 538 | 539 | 顺序:Att --> LN --> FFN --> Add --> LN 540 | 541 | (GPT-2: LN --> Att --> Add --> LN --> FFN --> Add 542 | 543 | Bert: Att --> Add --> LN --> FFN --> Add --> LN 544 | ) 545 | 546 | * * * 547 | 548 | ## class T5_Base() 549 | 550 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1740) 551 | 552 | class T5_Base(Transformer): 553 | 554 | 555 | Google的T5模型(基类) 556 | 557 | 注意T5有两个版本,一开始放出来的版本称为t5.1.0,而后来放出了一个升级,版本称为t5.1.1。 558 | 两者结构略有不同,包括后来放出来的多国语言版T5也采用了t5.1.1的结构。 559 | 560 | [t5.1.0](https://github.com/google-research/text-to-text-transfer-transformer) 561 | 562 | [t5.1.1](https://github.com/google-research/text-to-text-transfer-transformer/blob/master/released_checkpoints.md#t511) 563 | 564 | [multilingual-t5](https://github.com/google-research/multilingual-t5) 565 | 566 | * * * 567 | 568 | ## class T5_Encoder() 569 | 570 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1861) 571 | 572 | class T5_Encoder(T5_Base): 573 | 574 | Google的T5模型(Encoder) 575 | 576 | ### def apply_embeddings() 577 | 578 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1874) 579 | 580 | def apply_embeddings(self, inputs): 581 | 582 | T5的embedding只有token embedding,并把relative position embedding准备好,待attention使用。 583 | 584 | ### def apply_main_layers() 585 | 586 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1906) 587 | 588 | def apply_main_layers(self, inputs, index): 589 | 590 | T5的Encoder的主体是基于Self-Attention的模块 591 | 592 | 顺序:LN --> Att --> Add --> LN --> FFN --> Add 593 | 594 | ### def compute_position_bias() 595 | 596 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L1906) 597 | 598 | def compute_position_bias(self, inputs=None): 599 | 600 | T5相对位置编码。调用[def RelativePositionEmbeddingT5](Todo) 来计算相对位置编码。 601 | * * * 602 | 603 | ## class T5_Decoder() 604 | 605 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L2034) 606 | 607 | class T5_Decoder(LM_Mask, T5_Base): 608 | 609 | Google的T5模型(Decoder) 610 | 611 | ### def apply_embeddings() 612 | 613 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L2044) 614 | 615 | def apply_embeddings(self, inputs): 616 | 617 | T5的embedding只有token embedding,并把relative position embedding准备好,待attention使用。 618 | 619 | ### def apply_main_layers() 620 | 621 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L2091) 622 | 623 | def apply_main_layers(self, inputs, index): 624 | 625 | T5的Decoder主体是基于Self-Attention、Cross-Attention的模块 626 | 627 | 顺序:LN --> Att1 --> Add --> LN --> Att2 --> Add --> LN --> FFN --> Add 628 | 629 | ### def compute_position_bias() 630 | 631 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L2293) 632 | 633 | def compute_position_bias(self, inputs=None): 634 | 635 | T5相对位置编码。调用[def RelativePositionEmbeddingT5](Todo) 来计算相对位置编码。 636 | * * * 637 | 638 | ## class T5() 639 | 640 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L2322) 641 | 642 | class T5(T5_Base): 643 | 644 | Google的T5模型(Encoder-Decoder) 645 | 646 | * * * 647 | 648 | 分别调用[T5-Encoder](Todo) 和 [T5-Decoder](Todo),构建一个完整的T5模型。 649 | 650 | ### def extend_with_language_model() 651 | 652 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L2350) 653 | 654 | def extend_with_language_model(BaseModel): 655 | 656 | * * * 657 | 658 | ### def extend_with_unified_language_model() 659 | 660 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L2363) 661 | 662 | def extend_with_unified_language_model(BaseModel): 663 | 664 | * * * 665 | 666 | ### def build_transformer_model() 667 | 668 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/models.py#L2377) 669 | 670 | def build_transformer_model( 671 | config_path=None, 672 | checkpoint_path=None, 673 | model='bert', 674 | application='encoder', 675 | return_keras_model=True, 676 | **kwargs 677 | ): 678 | 679 | * * * 680 | -------------------------------------------------------------------------------- /optimizers/README.md: -------------------------------------------------------------------------------- 1 | # Sniper 2 | 3 | `optimizers`为各种优化器所在的文件 4 | 5 | 代码中所有的v1 v2为苏神自己的迭代版本号,实际上调用调用没有v的就行,有变量指向这些方法。 6 | 7 | 由于本文是根据源代码写的,因此保留这些版本号。 8 | 9 | ## class Adam() 10 | 11 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L13) 12 | 13 | class Adam(keras.optimizers.Optimizer) 14 | 15 | 重新定义Adam优化器,便于派生出新的优化器(tensorflow的optimizer_v2类) 16 | 17 | 检查了一下tensorflow中对于Adam的[源码](https://github.com/tensorflow/tensorflow/blob/r2.4/tensorflow/python/keras/optimizer_v2/adam.py#L107 ), 18 | 没发现什么不同却又处处不同。读了一下代码,大概猜测就如苏神说的一样,“重新定义Adam优化器,便于派生出新的优化器”。 19 | 将tensorflow的代码提取,并去掉AmsGrad的支持,简化了一下。 20 | 21 | 2021.09.25更:这里苏神实现的Adam还采用了误差修正(bias_correction,默认为True),在[论文](https://arxiv.org/abs/2006.05987)中发现,使用偏移修正的adam能够极大提升模型在测试集上的效果 22 | 23 | ## class AdaFactorBase 24 | 25 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L92) 26 | 27 | 28 | class AdaFactorBase(keras.optimizers.Optimizer) 29 | 30 | AdaFactor优化器(基类) 31 | 32 | [论文链接](https://arxiv.org/abs/1804.04235 )、[参考实现](https://github.com/tensorflow/mesh/blob/master/mesh_tensorflow/optimize.py ) 33 | 34 | 但是这个优化器本身是Google提出来的,它具有自适应学习率,但是比RMSProp还要省显存,并针对性解决了一些Adam的缺陷。 35 | 36 | 这么好,又省显存又解决缺陷,那为啥不用呢? 37 | 38 | > 需要提醒的是,用AdaFactor的时候,batch_size最好大一些,因为本身低秩分解会带来误差,而如果batch_size过小, 39 | 那么梯度估算本身也带来较大的误差,两者叠加优化过程可能还不收敛。对于预训练模型来说,batch_size通常还是很大的, 40 | 所以现在不少预训练模型开始用AdaFactor优化器了;对于普通的下游任务来说,AdaFactor也可以尝试,但可能需要多炼炼丹, 41 | 才能搞出优于无脑Adam的效果。对了,还要提醒一下,用AdaFactor的时候,学习率要设大一点,大概是10的-3级别为好, 42 | 哪怕是finetune阶段也是如此。 43 | 44 | [详见苏神博客](https://kexue.fm/archives/7302 ) 45 | 46 | 47 | ## class AdaFactorV1() 48 | 49 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L171) 50 | 51 | 52 | class AdaFactorV1(AdaFactorBase) 53 | 54 | 55 | AdaFactor优化器(纯Keras版) 56 | 57 | [论文链接](https://arxiv.org/abs/1804.04235 )、[参考实现](https://github.com/tensorflow/mesh/blob/master/mesh_tensorflow/optimize.py ) 58 | 59 | tensorflow1.x 版本的AdaFactor(keras) 60 | 61 | 详见 [ class AdaFactorBase](https://github.com/Sniper970119/bert4keras_document/tree/master/optimizers#class-AdaFactorV2 ) 62 | 63 | ## class AdaFactorV2() 64 | 65 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L236) 66 | 67 | 68 | class AdaFactorV1(AdaFactorBase) 69 | 70 | 71 | AdaFactor优化器(tf.keras版) 72 | 73 | [论文链接](https://arxiv.org/abs/1804.04235 )、[参考实现](https://github.com/tensorflow/mesh/blob/master/mesh_tensorflow/optimize.py ) 74 | 75 | tensorflow2.x 版本的AdaFactor(tf.keras) 76 | 77 | 详见 [ class AdaFactorBase](https://github.com/Sniper970119/bert4keras_document/tree/master/optimizers#class-AdaFactorV2 ) 78 | 79 | ### def extend_with_weight_decay() 80 | 81 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L325) 82 | 83 | def extend_with_weight_decay(BaseOptimizer) 84 | 85 | 返回加入权重衰减的新优化器,已弃用 86 | 87 | 88 | ### def extend_with_weight_decay_v2() 89 | 90 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L367) 91 | 92 | def extend_with_weight_decay_v2(BaseOptimizer) 93 | 94 | 返回加入权重衰减的新优化器。 95 | 96 | L2正则化的目的就是为了让权重衰减到更小的值,在一定程度上减少模型过拟合的问题,所以权重衰减也叫L2正则化。 97 | 98 | 更小的权值w,从某种意义上说,表示网络的复杂度更低,对数据的拟合更好(这个法则也叫做奥卡姆剃刀),而在实际应用中,也验证了这一点,L2正则化的效果往往好于未经正则化的效果。 99 | 100 | 101 | ### def extend_with_layer_adaptation() 102 | 103 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L407) 104 | 105 | def extend_with_layer_adaptation(BaseOptimizer) 106 | 107 | 返回加入层自适应学习率的新优化器,已弃用 108 | 109 | 110 | ### def extend_with_layer_adaptation_v2() 111 | 112 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L462) 113 | 114 | def extend_with_layer_adaptation_v2(BaseOptimizer) 115 | 116 | 返回加入层自适应学习率的新优化器。 117 | 118 | 119 | 带有层自适应学习率的优化器,用每一层参数的模长来校正当前参数的学习率。 120 | 121 | LAMB(Layer-wise Adaptive Moments optimizer for Batching training)优化器,将Bert的训练时间从3天减少到了76分钟。 122 | 123 | > 为了训练BERT, Devlin等人首先使用序列长度为128的900k迭代训练模型,然后在最后的100k迭代中转换为512的序列长度。这导致了在16个TPUv3芯片上大约需要3天的训练时间。(baseline) 124 | > 125 | >将训练优化器更改为LAMB之后,保持与基线相同的训练过程,使用与基线相同数量的epochs运行, 126 | >但批大小(batch size)从512扩展到32K(选择32K大小(序列长度512)主要是由于TPU Pod的内存限制)。 127 | >通过使用LAMB优化器,能够在批大小(batch size)为32k的15625次迭代(序列长度为128的14063次迭代和序列长度为512的1562次迭代) 128 | >中获得91.460的F1分数。 129 | >对于批大小(batch size)为32K,本文将BERT训练时间从3天缩短到约100分钟。 130 | 131 | 该论文来自Google,被收录进ICLR2020。[论文](https://arxiv.org/abs/1904.00962 ) 132 | 133 | 134 | ### def extend_with_piecewise_linear_lr() 135 | 136 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L515) 137 | 138 | def extend_with_piecewise_linear_lr(BaseOptimizer) 139 | 140 | 141 | 带有分段线性学习率的优化器,其中schedule是形如{1000: 1, 2000: 0.1}的字典,,表示0~1000步内学习率线性地从零增加到100%,然后 142 | 1000~2000步内线性地降到10%,2000步以后保持10%。 143 | 144 | 已弃用 145 | 146 | 147 | ### def extend_with_piecewise_linear_lr_v2() 148 | 149 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L557) 150 | 151 | def extend_with_piecewise_linear_lr_v2(BaseOptimizer) 152 | 153 | 带有分段线性学习率的优化器,其中schedule是形如{1000: 1, 2000: 0.1}的字典,,表示0~1000步内学习率线性地从零增加到100%,然后 154 | 1000~2000步内线性地降到10%,2000步以后保持10%。 155 | 156 | 可以用来模拟退火,warm up等 157 | 158 | 调用了backend中的[def piecewise_linear()](https://github.com/Sniper970119/bert4keras_document/tree/master/backend#def-piecewise_linear() )方法。 159 | 160 | 161 | ### def extend_with_gradient_accumulation() 162 | 163 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L587) 164 | 165 | def extend_with_gradient_accumulation(BaseOptimizer) 166 | 167 | 168 | 带有梯度累积的优化器。 169 | 170 | 已弃用 171 | 172 | 173 | ### def extend_with_gradient_accumulation_v2() 174 | 175 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L648) 176 | 177 | def extend_with_gradient_accumulation_v2(BaseOptimizer) 178 | 179 | 带有梯度累积的优化器。 180 | 181 | 梯度累计:就是类似于torch不清空梯度,累计几次再进行一次反向传播、清空梯度。从而“放大”batch size。同时学习率也要对应增大。 182 | 183 | PS:这个优化器实际使用来看并没有明显的效果,并且需要大范围的缩小batch size,感觉并不实用。当然也可能是任务的原因,在其他的提督累积实现方法下效果依然不明显。 184 | 185 | 186 | ### def extend_with_lookahead() 187 | 188 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L702) 189 | 190 | def extend_with_lookahead(BaseOptimizer) 191 | 192 | 193 | 带有look ahead的优化器。 194 | 195 | 其中: 196 | |参数| 说明| 197 | |:----- |-----| 198 | |steps_per_slow_update |论文中的k:int| 199 | |slow_step_size |论文中的alpha:float| 200 | 201 | 已弃用 202 | 203 | 204 | ### def extend_with_lookahead_v2() 205 | 206 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L752) 207 | 208 | def extend_with_lookahead_v2(BaseOptimizer) 209 | 210 | 带有look ahead的优化器。 211 | 212 | 其中: 213 | |参数| 说明| 214 | |:----- |-----| 215 | |steps_per_slow_update |论文中的k:int| 216 | |slow_step_size |论文中的alpha:float| 217 | 218 | look ahead:通过一个内循环优化器(它可以是任何优化器,Adam、SGD)提前循环k次权重,称作Fast Weights。 219 | 然后由外层循环将内层计算的k次权重通过权重滑动平均(EMA)计算新的方向,得到Slow Weights。 220 | 221 | 最终模型使用的参数是Slow Weights,Fast Weights相当于做了一系列实验“look ahead”。 222 | 223 | [论文](https://arxiv.org/abs/1907.08610 ) 224 | 225 | 226 | ### def extend_with_lazy_optimization() 227 | 228 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L799) 229 | 230 | def extend_with_lazy_optimization(BaseOptimizer) 231 | 232 | 233 | 带有懒惰更新的优化器。使得部分权重(尤其是embedding)只有在梯度不等于0时更新。 234 | 235 | 已弃用 236 | 237 | 238 | ### def extend_with_lazy_optimization_v2() 239 | 240 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L853) 241 | 242 | def extend_with_lazy_optimization_v2(BaseOptimizer) 243 | 244 | 带有懒惰更新的优化器。使得部分权重(尤其是embedding)只有在梯度不等于0时更新。 245 | 246 | 247 | ### def extend_with_exponential_moving_average() 248 | 249 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L903) 250 | 251 | def extend_with_exponential_moving_average(BaseOptimizer) 252 | 253 | 254 | 带EMA(权重滑动平均,Exponential Moving Average)的优化器。 255 | 256 | 已弃用 257 | 258 | 259 | ### def extend_with_exponential_moving_average_v2() 260 | 261 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L956) 262 | 263 | def extend_with_exponential_moving_average_v2(BaseOptimizer) 264 | 265 | 带EMA(权重滑动平均,Exponential Moving Average)的优化器。 266 | 267 | 可以用来估计变量的局部值,使得变量的更新与一段时间内的历史值有关。 268 | 269 | 维护一个shadow weight,这个shadow weight为前n次计算的weight的平均值。 270 | 271 | 只能在测试阶段使用,训练阶段依然要使用真实的weight。 272 | 273 | PS:这个确实有效,而且效果不小,苏神的博客中也多次提到ema,从本人的实际测试中,ema会提高0.5左右,算得上一个十分好用的trick了。 274 | 275 | [苏神博客](https://kexue.fm/archives/6575#%E6%9D%83%E9%87%8D%E6%BB%91%E5%8A%A8%E5%B9%B3%E5%9D%87 ) 276 | 277 | example: 278 | 279 | from bert4keras.optimizers import Adam, extend_with_exponential_moving_average 280 | AdamEMA = extend_with_exponential_moving_average(Adam, name='AdamEMA') 281 | optimizer = AdamEMA(learing_rate, ema_momentum=0.9999) 282 | 283 | ### def extend_with_parameter_wise_lr() 284 | 285 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L1021) 286 | 287 | def extend_with_parameter_wise_lr(BaseOptimizer) 288 | 289 | 290 | 带分参数学习率的优化器,主要场景就是给每层甚至每个参数设置不同的学习率。 291 | 292 | 已弃用 293 | 294 | 295 | ### def extend_with_parameter_wise_lr_v2() 296 | 297 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/optimizers.py#L1067) 298 | 299 | def extend_with_parameter_wise_lr_v2(BaseOptimizer) 300 | 301 | 带分参数学习率的优化器,主要场景就是给每层甚至每个参数设置不同的学习率。 302 | 303 | 其中schedule是形如{name1: 2, name2: 0.1}的字典,其实name1、name2是字符串,表示变量名包含name1的 304 | 参数学习率乘以2,变量名包含name2的参数学习率要乘以0.1。 305 | 306 | -------------------------------------------------------------------------------- /snippets/README.md: -------------------------------------------------------------------------------- 1 | # Sniper 2 | 3 | `snippets`为一些小工具,该文件内置了数据处理时可能使用到的工具。 4 | 5 | ### def strQ2B() 6 | 7 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L27) 8 | 9 | def strQ2B(ustring) 10 | 11 | 其中: 12 | 13 | |参数| 说明| 14 | |:----- |-----| 15 | |ustring|全角字符串:str| 16 | 17 | 返回: 18 | 19 | |参数| 说明| 20 | |:----- |-----| 21 | |rstring |半角字符串:str| 22 | 23 | 这个方法负责将全角字符串转换为半角字符串。 24 | 25 | ### def string_matching() 26 | 27 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L49) 28 | 29 | string_matching(s, keywords) 30 | 31 | 其中: 32 | 33 | |参数| 说明| 34 | |:----- |-----| 35 | |s|字符串:str| 36 | |keywords|关键字:list| 37 | 38 | 返回: 39 | 40 | |参数| 说明| 41 | |:----- |-----| 42 | |flag |是否包含关键字:bool| 43 | 44 | 判断s中是否包含关键字。 45 | 46 | 47 | ### def convert_to_unicode() 48 | 49 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L58) 50 | 51 | def convert_to_unicode(text, encoding='utf-8', errors='ignore') 52 | 53 | 其中: 54 | 55 | |参数| 说明| 56 | |:----- |-----| 57 | |text|字符串:str| 58 | |encoding|输入字符串编码方式:str| 59 | |errors|忽略警告:str| 60 | 61 | 返回: 62 | 63 | |参数| 说明| 64 | |:----- |-----| 65 | |text |解码后的文本:str| 66 | 67 | 字符串unicode解码。 68 | 69 | ### def convert_to_str() 70 | 71 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L70) 72 | 73 | def convert_to_str(text, encoding='utf-8', errors='ignore') 74 | 75 | 其中: 76 | 77 | |参数| 说明| 78 | |:----- |-----| 79 | |text|字符串:str| 80 | |encoding|输出字符串编码方式:str| 81 | |errors|忽略警告:str| 82 | 83 | 返回: 84 | 85 | |参数| 说明| 86 | |:----- |-----| 87 | |text |编码后的文本:str| 88 | 89 | 字符串编码。 90 | 91 | ### class open() 92 | 93 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L82) 94 | 95 | 这个方法主要是兼容py2和py3,引入索引,方便大文件读取。 96 | 97 | 但是我并没有看见过苏神使用过这个方法(使用索引),本人代码复现的时候也是直接不用这个open。 98 | 99 | 因此暂无。 100 | 101 | ### def parallel_apply() 102 | 103 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L189) 104 | 105 | def parallel_apply( 106 | func, 107 | iterable, 108 | workers, 109 | max_queue_size, 110 | callback=None, 111 | dummy=False, 112 | random_seeds=True 113 | ) 114 | 115 | 其中: 116 | 117 | |参数| 说明| 118 | |:----- |-----| 119 | |func|单个数据处理的方法:function| 120 | |iterable|数据:list| 121 | |workers|线程 or 进程 数:int| 122 | |max_queue_size|最大队列长度:int| 123 | |callback|回调函数:function| 124 | |dummy|是否为多进程:bool| 125 | |random_seeds|随机种子:bool| 126 | 127 | 返回: 128 | 129 | |参数| 说明| 130 | |:----- |-----| 131 | |res |多线程处理后的结果:list| 132 | 133 | 多线程(进程)处理框架,可以用于多线程(进程)数据处理。 134 | 135 | windows系统中仅支持多线程;Linux系统中支持多线程和多进程。 136 | 137 | `func`为每条的数据处理方法。 138 | 139 | `iterable`为一个可迭代结果,事实上,它不仅可以是list,任何迭代结构(可以用于enumerate中的)都可以,列表、元组、字符串等,当然,tqdm也可以。 140 | 141 | `workers`线程(进程)数。 142 | 143 | `max_queue_size`最大队列长度,每个线程(进程)队列长度。 144 | 145 | `callback`回调函数,每个数据处理完成后的回调函数 146 | 147 | `dummy`为True为多进程,False为多线程(Windows仅支持False,原因是python在windows中使用多进程必须在main结构下)。 148 | 149 | `random_seeds`每个线程的随机种子。 150 | 151 | example: 152 | 153 | # input: 154 | from tqdm import tqdm 155 | from bert4keras.snippets import parallel_apply 156 | 157 | def fun(a): 158 | print(a) 159 | return a 160 | 161 | def fun1(a): 162 | print('callback', a) 163 | 164 | # call with return 165 | res = parallel_apply( 166 | func=fun, 167 | iterable=tqdm(range(5), desc=u'转换数据'), 168 | workers=2, 169 | max_queue_size=2, 170 | dummy=True, 171 | ) 172 | print(res) 173 | 174 | # output: 175 | 转换数据: 100%|██████████| 5/5 [00:00<00:00, 208.29it/s] 176 | 0 177 | 1 178 | 2 179 | 3 180 | 4 181 | [0, 1, 2, 3, 4] 182 | 183 | # call with callback 184 | res = parallel_apply( 185 | func=fun, 186 | iterable=tqdm(range(5), desc=u'转换数据'), 187 | workers=2, 188 | max_queue_size=2, 189 | dummy=True, 190 | callback=fun1 191 | ) 192 | print(res) 193 | 194 | # output: 195 | 转换数据: 100%|██████████| 5/5 [00:00<00:00, 185.14it/s] 196 | 0 197 | 1 198 | 2 199 | 3 200 | callback 0 201 | 4 202 | callback 1 203 | callback 2 204 | callback 3 205 | callback 4 206 | None 207 | 208 | 209 | ### def sequence_padding() 210 | 211 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L270) 212 | 213 | def sequence_padding(inputs, length=None, padding=0, mode='post') 214 | 215 | 其中: 216 | 217 | |参数| 说明| 218 | |:----- |-----| 219 | |inputs|输入序列:list| 220 | |length|填充长度:int| 221 | |padding|填充值:int| 222 | |mode|填充模式:str| 223 | 224 | 225 | 返回: 226 | 227 | |参数| 说明| 228 | |:----- |-----| 229 | |res |填充后的结果:ndarry| 230 | 231 | 将序列padding到同一长度 232 | 233 | `inputs`输入数据。 234 | 235 | `length`将所有的列表填充到该长度,为None则取整个列表中最大长度 `max([len(x) for x in inputs])`。 236 | 237 | `padding`填充值,填充部分的值,默认0。 238 | 239 | `mode`填充模式,post 和pre两种,分别为向后填充和向前填充,默认为post。 240 | 241 | 返回填充后的ndarry。 242 | 243 | 实际上这个方法苏神还当作np.array()用了,对于不需要填充只需要转化为ndarry的变量他也调用的`sequence_padding`。 244 | 也确实有这个功能,毕竟最后返回的是ndarry。 245 | 246 | 247 | ### def truncate_sequences() 248 | 249 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L292) 250 | 251 | def truncate_sequences(maxlen, index, *sequences) 252 | 253 | 其中: 254 | 255 | |参数| 说明| 256 | |:----- |-----| 257 | |maxlen|输入序列:list| 258 | |index|填充长度:int| 259 | |*sequences|填充值:int| 260 | 261 | 262 | 返回: 263 | 264 | |参数| 说明| 265 | |:----- |-----| 266 | |sequences |填充后的结果:ndarry| 267 | 268 | 这个函数是用来做序列截断的。 269 | 270 | `maxlen`为截断的最大长度(所有句子长度和)。 271 | 272 | `index` index为删除的位置(对每个句子,删除同一个位置,从而做到句子长度缩小) 273 | 274 | `*sequences`多参,句子对。 275 | 276 | 这个函数主要是在[encode](https://github.com/Sniper970119/bert4keras_document/tree/master/tokenizers#def-encode )中使用的。 277 | 278 | 279 | ### def text_segmentate() 280 | 281 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L305) 282 | 283 | def text_segmentate(text, maxlen, seps='\n', strips=None) 284 | 285 | 其中: 286 | 287 | |参数| 说明| 288 | |:----- |-----| 289 | |text|输入序列:str| 290 | |maxlen|填充长度:int| 291 | |seps|填充值:str| 292 | |strips|填充值:str| 293 | 294 | 295 | 返回: 296 | 297 | |参数| 说明| 298 | |:----- |-----| 299 | |text |切割后的结果:list| 300 | 301 | 递归拆分文本,相比通过逗号和句号分词提供更多的拆分可能。 302 | 303 | `text`为需要被拆分的文本。 304 | 305 | `maxlen`为拆分的最大长度,当文本长度不足`maxlen`时不做拆分。 306 | 307 | `seps`拆分字符,为字符串类型,如`?!`则代表分别依据?和!进行拆分。比如“你好?你好!”则会被拆成两个你好。 308 | **其实这里感觉替换成列表更好,并不需要更改逻辑代码, 309 | 还能适配多字符的拆分(现在只支持单字符,比如想根据'...'进行拆分是不行的,只能对'.'进行拆分)** 310 | 311 | `strips`需要被去掉的字符,为字符串类型,拆分后的结果如果首位有这些字符则会被去掉。 312 | 313 | 而该方法的返回值可能是具有多个长度的列表(因为可能进行了拆分,也可能不进行拆分) 314 | 315 | example: 316 | 317 | text = '贝贝好爱干净!每天出门都要洗澡。还喜欢喝蒙牛!不喜欢蹲地方~喜欢坐凳子上还喜欢和我坐在一起~' 318 | texts = text_segmentate(text, 1, u'\n。;:,!~', u'。') 319 | # output: 320 | ['贝贝好爱干净!', 321 | '每天出门都要洗澡', 322 | '还喜欢喝蒙牛!', 323 | '不喜欢蹲地方~', 324 | '喜欢坐凳子上还喜欢和我坐在一起~'] 325 | 326 | 327 | ### def is_one_of() 328 | 329 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L327) 330 | 331 | def is_one_of(x, ys) 332 | 333 | 其中: 334 | 335 | |参数| 说明| 336 | |:----- |-----| 337 | |x|keyword:any| 338 | |ys|列表:list| 339 | 340 | 341 | 342 | 返回: 343 | 344 | |参数| 说明| 345 | |:----- |-----| 346 | |res |x是否在ys中:bool| 347 | 348 | 判断x是否在ys之中,等价于x in ys,但有些情况下x in ys会报错 349 | 350 | 351 | ## class DataGenerator(object) 352 | 353 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L337) 354 | 355 | def __init__(self, data, batch_size=32, buffer_size=None) 356 | 357 | 其中: 358 | 359 | |参数| 说明| 360 | |:----- |-----| 361 | |data|数据:list| 362 | |batch_size|批大小:int| 363 | |buffer_size|缓存大小:int| 364 | 365 | 366 | `data`为所有的数据,一般为从文件读取后的数据列表。 367 | 368 | `batch_size`数据批大小。 369 | 370 | `buffer_size`缓存大小,默认为批大小*1000。 371 | 372 | 该类为数据生成器,用来将数据分批。通常在该类中将数据tokenizer然后分批次返回。 373 | 374 | ### def sample() 375 | 376 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L354) 377 | 378 | def sample(self, random=False) 379 | 380 | 其中: 381 | 382 | |参数| 说明| 383 | |:----- |-----| 384 | |random|是否随机:bool| 385 | 386 | 387 | 返回: 388 | 389 | |参数| 说明| 390 | |:----- |-----| 391 | |flag |是否为最后一个数据:bool| 392 | |data |数据:bool| 393 | 394 | 这个方法就是(随机)取出一条数据,同时返回这条数据是否为最后一条。 395 | 396 | 这里虽然默认random为False,但是有意思的是:其他地方(def forfit())调用该方法时,默认为True 397 | (也就是这里的random=False实际上是random=True),不知道是苏神有意为之还是一个小彩蛋。 398 | 399 | 当然啦,你直接调用这个方法当然还是False。也不是bug,只是好奇,在这里提一嘴。 400 | 401 | 这个就是(随机)遍历数据,如果到了batch size大小,就yield等待返回。 402 | 403 | ### def \_\_iter__() 404 | 405 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L390) 406 | 407 | def __iter__(self, random=False) 408 | 409 | 其中: 410 | 411 | |参数| 说明| 412 | |:----- |-----| 413 | |random|是否随机:bool| 414 | 415 | 这个方法是需要子类实现的,用来定义每一批次数据的具体实现。 416 | 417 | 通常为 读取数据->tokenize->封装batch->返回数据->读取数据 418 | 419 | example: 420 | 421 | from bert4keras.snippets import sequence_padding, DataGenerator 422 | class data_generator(DataGenerator): 423 | def __iter__(self, random=False): 424 | batch_token_ids, batch_segment_ids, batch_labels = [], [], [] 425 | # 读取数据 426 | for is_end, (source, target, label) in self.sample(random): 427 | # tokenize 428 | token_ids, segment_ids = tokenizer.encode( 429 | source, target, maxlen=maxlen 430 | ) 431 | # 封装batch 432 | batch_token_ids.append(token_ids) 433 | batch_segment_ids.append(segment_ids) 434 | batch_labels.append([label]) 435 | # 返回数据 436 | if len(batch_token_ids) == self.batch_size or is_end: 437 | # 序列填充(这里填充长度为每一批最大长度,因为没指定maxlen) 438 | batch_token_ids = sequence_padding(batch_token_ids) 439 | batch_segment_ids = sequence_padding(batch_segment_ids) 440 | # yield返回 441 | yield [batch_token_ids, batch_segment_ids], batch_labels 442 | batch_token_ids, batch_segment_ids, batch_labels = [], [], [] 443 | 444 | 445 | ### def forfit() 446 | 447 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L393) 448 | 449 | def forfit(self, random=True) 450 | 451 | 其中: 452 | 453 | |参数| 说明| 454 | |:----- |-----| 455 | |random|是否随机:bool| 456 | 457 | 这个方法就是用来返回数据的,model.fit中,可以传入一个迭代器,巧了,这个就是。 458 | 459 | 我们可以看下这个的源码,反正也不长,这个random默认为True。 460 | 461 | def forfit(self, random=True): 462 | while True: 463 | for d in self.__iter__(random): 464 | yield d 465 | 466 | 我们可以看到,实际上从 forfit -> \_\_iter__ -> sample ,random为True,但是\_\_iter__ 和 sample 中random默认 467 | 都是False。咱也不晓得为啥。 468 | 469 | ## class ViterbiDecoder(object) 470 | 471 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L438) 472 | 473 | 我没用过,也没看苏神用过。学习到之后再来补充。 474 | 475 | ## class AutoRegressiveDecoder(object) 476 | 477 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L484) 478 | 479 | def __init__(self, start_id, end_id, maxlen, minlen=1) 480 | 481 | 其中: 482 | 483 | |参数| 说明| 484 | |:----- |-----| 485 | |start_id|开始id:int| 486 | |end_id|结束id:int| 487 | |maxlen|最大长度:int| 488 | |minlen|最小长度:int| 489 | 490 | 该类为一个自回归生成模型解码的基类(类似于seq2seq)。 491 | 492 | `start_id` 为解码的起始id,一般为CLS。 493 | 494 | `end_id` 为解码的结束id,一般为SEP。 495 | 496 | `maxlen` 文本最大长度。 497 | 498 | `minlen` 文本最小长度。 499 | 500 | ### def last_token() 501 | 502 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L539) 503 | 504 | def last_token(self, model) 505 | 506 | 507 | 其中: 508 | 509 | |参数| 说明| 510 | |:----- |-----| 511 | |model|模型:tf.keras.model.Model| 512 | 513 | 创建一个只返回最后一个token输出的新Model。 514 | 515 | ### def wraps() 516 | 517 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L500) 518 | 519 | @staticmethod 520 | def wraps(default_rtype='probas', use_states=False) 521 | 522 | 其中: 523 | 524 | |参数| 说明| 525 | |:----- |-----| 526 | |default_rtype|模型:tf.keras.model.Model 或 未知| 527 | |use_states|模型:tf.keras.model.Model 或 未知| 528 | 529 | 530 | 返回: 531 | 532 | |参数| 说明| 533 | |:----- |-----| 534 | |model|模型:tf.keras.model.Model| 535 | 536 | 一个静态方法,用来完善predict函数。 537 | 538 | `default_rtype`为`probas`时为随机采样调用,返回归一化的概率。`logits`为`beam_search`时调用,返回softmax前的结果或者概率对数; 539 | 540 | 该方法实际上主要就是model.predict()然后完善了一下输出。 541 | 542 | 543 | ### def predict() 544 | 545 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L551) 546 | 547 | def predict(self, inputs, output_ids, states=None) 548 | 549 | 其中: 550 | 551 | |参数| 说明| 552 | |:----- |-----| 553 | |inputs|输入:list 或 tensor 或 ndarry| 554 | |output_ids|输出id:list| 555 | |states|携带的状态:list| 556 | 557 | 558 | 返回: 559 | 560 | |参数| 说明| 561 | |:----- |-----| 562 | |res |结果: tuple| 563 | 564 | 这个函数需要用户需自定义实现。 565 | 566 | `inputs`模型的输入(一般为CLS,也就是解析的起点)。 567 | 568 | `output_ids`输出的id,就是模型当前解析的输出(通过[beam search](https://github.com/Sniper970119/bert4keras_document/tree/master/snippets#def-beam_search ) 569 | 或者 [random_sample](https://github.com/Sniper970119/bert4keras_document/tree/master/snippets#def-random_sample ) ) 570 | 571 | `states`用来携带解码状态的变量。有些情况可能不仅仅需要进行seq2seq式的生成,还需要一些token状态,比如[UNILM+BIO标记的文本生成](https://kexue.fm/archives/8046) 每一个token需要携带一个BIO标记,而这个BIO标记就需要这个states进行携带。 572 | 573 | 返回二元组 (得分或概率, states)。 574 | 575 | example: 576 | 577 | @AutoRegressiveDecoder.wraps(default_rtype='probas') 578 | def predict(self, inputs, output_ids, states): 579 | token_ids = output_ids 580 | segment_ids = np.zeros_like(token_ids) 581 | return self.last_token(model).predict([ 582 | token_ids, segment_ids, inputs[0] 583 | ]) 584 | 585 | ### def last_token() 586 | 587 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L539) 588 | 589 | def last_token(self, model) 590 | 591 | 其中: 592 | 593 | |参数| 说明| 594 | |:----- |-----| 595 | |model|模型:tf.keras.model.Model 或 未知| 596 | 597 | 598 | 返回: 599 | 600 | |参数| 说明| 601 | |:----- |-----| 602 | |model|模型:tf.keras.model.Model| 603 | 604 | 创建一个只返回最后一个token输出的新Model。 605 | 606 | 比如我们使用UNILM,那模型实际上会输出多个token,而我们只需要最后一个token(也就是当前轮次预测值),因此这个方法会自动的将模型的输出改为最后一个token,而不用我们手动取最后一个token。 607 | 608 | ### def beam_search() 609 | 610 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L560) 611 | 612 | def beam_search(self, inputs, topk, states=None, temperature=1, min_ends=1) 613 | 614 | 其中: 615 | 616 | |参数| 说明| 617 | |:----- |-----| 618 | |inputs|输入:list| 619 | |topk|beam size:int| 620 | |states|未知:None| 621 | |temperature|未知:float| 622 | |min_ends|出现结束标志的次数(猜的) : int| 623 | 624 | 625 | 返回: 626 | 627 | |参数| 说明| 628 | |:----- |-----| 629 | |res|n个解码序列组成的列表:list| 630 | 631 | 632 | beam search解码。 633 | 634 | `inputs`输入的序列,如果没有输入则为空列表 635 | 636 | `topk`topk即beam size 637 | 638 | `states`用来携带解码状态的变量。有些情况可能不仅仅需要进行seq2seq式的生成,还需要一些token状态,比如[UNILM+BIO标记的文本生成](https://kexue.fm/archives/8046) 每一个token需要携带一个BIO标记,而这个BIO标记就需要这个states进行携带。 639 | 640 | `temperature` 默认为1,是[predict](https://github.com/Sniper970119/bert4keras_document/tree/master/snippets#def-predict )中的一个参数,用来控制结果的softmax比例。 641 | 642 | `min_ends`从代码阅读结果来看,应该是最小的结束标记次数,默认为1(比如生成nsp那种句子,则为2)。 643 | 644 | ### def random_sample() 645 | 646 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L598) 647 | 648 | def random_sample( 649 | self, 650 | inputs, 651 | n, 652 | topk=None, 653 | topp=None, 654 | states=None, 655 | temperature=1, 656 | min_ends=1 657 | ) 658 | 659 | 其中: 660 | 661 | |参数| 说明| 662 | |:----- |-----| 663 | |inputs|输入:list| 664 | |n|输出个数:int| 665 | |topk|beam size:int| 666 | |topp|随机概率:int| 667 | |states|未知:None| 668 | |temperature|未知:float| 669 | |min_ends|出现结束标志的次数(猜的): int| 670 | 671 | 672 | 返回: 673 | 674 | |参数| 说明| 675 | |:----- |-----| 676 | |res|n个解码序列组成的列表:list| 677 | 678 | 随机采样解码。 679 | 680 | `inputs`输入的序列,如果没有输入则为空列表 681 | `n` 输出个数 682 | `topk`非None的topk表示每一步只从概率最高的topk个中采样 683 | `topp`非None的topp表示每一步只从概率最高的且概率之和刚好达到topp的若干个token中采样 684 | `states`用来携带解码状态的变量。有些情况可能不仅仅需要进行seq2seq式的生成,还需要一些token状态,比如[UNILM+BIO标记的文本生成](https://kexue.fm/archives/8046) 每一个token需要携带一个BIO标记,而这个BIO标记就需要这个states进行携带。 685 | `temperature`默认为1,是[predict](https://github.com/Sniper970119/bert4keras_document/tree/master/snippets#def-predict )中的一个参数,用来控制结果的softmax比例。 686 | `inputs`非None的topp表示每一步只从概率最高的且概率之和刚好达到topp的若干个token中采样 687 | 688 | 689 | ### def longest_common_substring() 690 | 691 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L705) 692 | 693 | def longest_common_substring(source, target) 694 | 695 | 最长公共子串(source和target的最长公共切片区间) 696 | 697 | 698 | 其中: 699 | 700 | |参数| 说明| 701 | |:----- |-----| 702 | |source|文本1:str| 703 | |target|文本2:str| 704 | 705 | 706 | 返回: 707 | 708 | |参数| 说明| 709 | |:----- |-----| 710 | |l |最大公共子串长度:int| 711 | |子串位置 |数据:list| 712 | 713 | 查找最长公共子串。返回:子串长度, 所在区间(四元组) 714 | 715 | 注意:最长公共子串可能不止一个,所返回的区间只代表其中一个。 716 | 717 | `source`这里有一点出入吧,其实不应该命名为source和target,就是s1和s2的关系,找子串嘛。 718 | 719 | `target`:s2。 720 | 721 | 最终返回一个最长子串长度和一个具有四个元素的列表,分别代表子串在s1和s2的start和end。 722 | 723 | 这个算法可能还算是个暴力算法,并没有用例如KMP这类优化后的子串算法。 724 | 725 | example: 726 | 727 | s1 = [1, 2, 3, 4, 5] 728 | s2 = [3, 1, 2, 4, 1] 729 | print(longest_common_substring(s1, s2)) 730 | 731 | # output 732 | (2, (0, 2, 1, 3)) 733 | # 最长公共子串长度为2,分别为s1的[0:2] 与 s2的[1:3] 734 | 735 | 736 | ### def longest_common_subsequence() 737 | 738 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/snippets.py#L721) 739 | 740 | def longest_common_subsequence(source, target) 741 | 742 | 其中: 743 | 744 | |参数| 说明| 745 | |:----- |-----| 746 | |source|文本1:str| 747 | |target|文本2:str| 748 | 749 | 750 | 返回: 751 | 752 | |参数| 说明| 753 | |:----- |-----| 754 | |l |最大公共子串长度:int| 755 | |映射关系(映射对组成的列表) |数据:list| 756 | 757 | 最长公共子序列 758 | 759 | 这个算法如果没有bug的话,只能说这个算法比较鸡肋(或者我还是没想到这个的应用场景)。 760 | 761 | 我刚开始想的是最长公共子串的序列版本,结果并不是。 762 | 763 | 比如: 764 | 765 | s1 = [1, 2, 3, 4, 5] 766 | s2 = [3, 1, 2, 4, 1] 767 | longest_common_subsequence(s1, s2) 768 | 769 | # output: 770 | [(0, 1), (1, 2), (3, 3)] 771 | 772 | 我以为会输出[1,2]这个公共子串,结果只是输出了一样的位置(s1: idx0 == s2: idx1; s1: idx1 == s2: idx2) 773 | 774 | 那问题来了,s1: idx0 == s2: idx5 为啥没有呢? 775 | 776 | 不是很想找这个问题,等遇到苏神使用的时候再具体学习吧。 777 | -------------------------------------------------------------------------------- /tokenizers/README.md: -------------------------------------------------------------------------------- 1 | # Sniper 2 | 3 | `tokenizers` 为tokenizer相关工具,该文件内置了所有在生成token时可能使用到的工具。 4 | 5 | ### def load_vocab() 6 | 7 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/tokenizers.py#L11) 8 | 9 | load_vocab(dict_path, encoding='utf-8', simplified=False, startswith=None) 10 | 11 | 其中: 12 | 13 | |参数| 说明| 14 | |:----- |-----| 15 | |dict_path|词汇表路径:str| 16 | |encoding|编码方式:str| 17 | |simplified|是否简化 :bool| 18 | |startswith| 开始标记:list| 19 | 20 | 返回: 21 | 22 | |参数| 说明| 23 | |:----- |-----| 24 | |token_dict|格式为 {keyword:idx,keyword:idx} 的字典:dict| 25 | |keep_tokens(optional)|列表,精简后的词汇表在源词汇表中的映射:list| 26 | 27 | 这个方法就是读取bert中的`vovab.txt`文件,返回一个字典,格式为`{keyword:idx,keyword:idx}`。 28 | 29 | `dict_path` 为`vovab.txt`文件的路径。 30 | 31 | `encoding` 为`vovab.txt`的编码方式。 32 | 33 | `simplified`为True则开启精简词汇表模型,通过去除CJK类字符和标点符号,将之前的21128(bert-chinese)个词精简到13584个, 34 | 从而将词汇表变小(随之而来的就是embedding变小,通过将21128\*768切片成13584\*768),同时,返回`token_dict`和`keep_tokens`, 35 | `token_dict`负责生成token,`keep_tokens`则传入模型构建中,来对embedding进行切片。 36 | 37 | `startswith`则为一个列表,在`simplified`为True时使用,用来保留一些特殊字符(比如[PAD], [UNK], [CLS], [SEP])。 38 | 39 | example: 40 | 41 | token_dict, keep_tokens = load_vocab( 42 | dict_path=dict_path, 43 | simplified=True, 44 | startswith=['[PAD]', '[UNK]', '[CLS]', '[SEP]'], 45 | ) 46 | 47 | ### def save_vocab() 48 | 49 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/tokenizers.py#L48) 50 | 51 | save_vocab(dict_path, token_dict, encoding='utf-8') 52 | 53 | 其中: 54 | 55 | |参数| 说明| 56 | |:----- |-----| 57 | |dict_path|词汇表保存路径:str| 58 | |token_dict|需要被保存的词汇表:dict | 59 | |encoding|编码方式:str| 60 | 61 | 62 | 保存词汇表(比如精简后的)。 63 | 64 | ## class Tokenizer(TokenizerBase) 65 | 66 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/tokenizers.py#L181) 67 | 68 | def __init__( 69 | self, 70 | token_dict, 71 | do_lower_case=False, 72 | word_maxlen=200 73 | token_start='[CLS]', 74 | token_end='[SEP]', 75 | pre_tokenize=None, 76 | token_translate=None, 77 | ) 78 | 79 | 用于类Bert模型的tokenizer。 80 | 81 | 其中: 82 | 83 | |参数| 说明| 84 | |:----- |-----| 85 | |token_dict|vocab.txt的路径:str| 86 | |do_lower_case|是否转化为小写:bool| 87 | |word_maxlen|单词最大长度:int| 88 | |token_start|token开始标记(CLS):str| 89 | |token_end|token结束标记(SEP) :str| 90 | |pre_tokenize|预分词,在预分词的基础上进行token:function| 91 | |token_translate|token转换:dict| 92 | 93 | `token_dict`为vacab.txt的路径,方法内调用[load_vocab()](https://github.com/Sniper970119/bert4keras_document/tree/master/tokenizers#def-load_vocab )获得词汇表。 94 | 95 | `do_lower_case`是否全部转化为小写(bert case和uncase)。 96 | 97 | `word_maxlen`单词最大长度,由于使用了细粒度拆次因此,词中词,比如北京大学中有北京 和 大学,定义最大长度,比如word_maxlen=3,则不对北京大学进行拆分。 98 | 99 | `token_start`token开始标记(默认CLS)。 100 | 101 | `token_end`token结束标记(默认SEP)。 102 | 103 | `pre_tokenize`为一个预分词方法,可以是一个jieba分词等方法,用来先将句子分词然后做token。 104 | 105 | `token_translate`为token替换字典,比如需要将所有的CLS替换为SEP({101:102})。 106 | 107 | example: 108 | 109 | tokenizer = Tokenizer(token_dict, do_lower_case=True) 110 | 111 | tokenizer = Tokenizer( 112 | dict_path, 113 | do_lower_case=True, 114 | pre_tokenize=lambda s: jieba.lcut(s, HMM=False) 115 | ) 116 | 117 | ### def encode() 118 | 119 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/tokenizers.py#L113) 120 | 121 | def encode( 122 | self, 123 | first_text, 124 | second_text=None, 125 | maxlen=None, 126 | pattern='S*E*E', 127 | truncate_from='right' 128 | ) 129 | 130 | 该方法为`Tokenizer`的父类`TokenizerBase`中的方法。 131 | 132 | 其中: 133 | 134 | |参数| 说明| 135 | |:----- |-----| 136 | |first_text|第一个文本:str| 137 | |second_text|第二个文本:str| 138 | |maxlen|最大长度:int| 139 | |pattern|规则:str| 140 | |truncate_from|截断方向 :str| 141 | 142 | 返回: 143 | 144 | |参数| 说明| 145 | |:----- |-----| 146 | |token_ids |token ids:list| 147 | |segment_ids |segment_ids:list| 148 | 149 | 对文本进行编码(变成token)。 150 | 151 | `first_text` 第一个文本(对应bert中nsp任务的两个句子,虽然并不一定是nsp任务)。 152 | 153 | `second_text`第二个文本。 154 | 155 | `maxlen`返回数据的最大长度,超过截断(比如bert base的512)。 156 | 157 | `pattern`对于first text 和second text的拼接规则,S代表CLS,E代表SEP,代表两句话的拼接规则,比如默认的S\*E\*E则为CLS sentence 1 SEP sentence 2 SEP,而S\*ES\*E 则为 CLS sentence 1 SEP CLS sentence 2 SEP。 158 | 159 | `truncate_from`截断方向,对于超过最大长度的数据,截头部(left)还是截尾部(right)当然还支持通过一直删某个中间位置来截取(某一int值), 160 | 截取方法详见[truncate_sequences](https://github.com/Sniper970119/bert4keras_document/tree/master/snippets#def-truncate_sequences )。 161 | 162 | **注意,由于bert4keras只提供了两局的分割,既[CLS]sentence1[SEP]sentence2[SEP],如果用户有多句输入的需求,比如[CLS]sentence1[SEP]sentence2[SEP]sentence3[SEP],原生bert4keras无法做到[因为encode的实现逻辑将“[SEP]"视为了多个字符],需要复写encode[支持更个性化的tokenize方式],或者只需要简单的复写tokenize即可** 163 | 164 | example: 165 | 166 | class CustomTokenizer(Tokenizer): 167 | def __init__(self, *args, **kwargs): 168 | super().__init__(*args, **kwargs) 169 | 170 | def tokenize(self, text, maxlen=None): 171 | """分词函数 172 | """ 173 | tokens = [ 174 | self._token_translate.get(token) or token 175 | for token in self._tokenize(text) 176 | ] 177 | for i in range(len(tokens)): 178 | # 将#转换为SEP,只有这里的SEP会被识别为分隔符 179 | if tokens[i] == '#': 180 | tokens[i] = '[SEP]' 181 | if self._token_start is not None: 182 | tokens.insert(0, self._token_start) 183 | if self._token_end is not None: 184 | tokens.append(self._token_end) 185 | 186 | if maxlen is not None: 187 | index = int(self._token_end is not None) + 1 188 | truncate_sequences(maxlen, -index, tokens) 189 | 190 | return tokens 191 | 192 | 上述代码就可以通过输入tokenizer sentence1#sentence2#sentence3 来实现[CLS]sentence1[SEP]sentence2[SEP]sentence3[SEP] 193 | 194 | ### def decode() 195 | 196 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/tokenizers.py#L215) 197 | 198 | def decode(self, ids, tokens=None) 199 | 200 | 解码器,将token变为可读的文本。 201 | 202 | 其中: 203 | 204 | |参数| 说明| 205 | |:----- |-----| 206 | |ids|token ids:list| 207 | |tokens|映射后的文本:list| 208 | 209 | 返回: 210 | 211 | |参数| 说明| 212 | |:----- |-----| 213 | |text |可读文本:str| 214 | 215 | `ids`:encode之后(或模型输出)的token ids列表,例如`[101,770,102]`,返回‘你’。 216 | 217 | `tokens`:这个比较简单,当tokens不为None时,ids无效。方法变为将tokens的字符拼接、处理,作为可读文本返回。 218 | 例如`['[CLS]','你','[SEP]']` 则会返回‘你’。 219 | 220 | ## class SpTokenizer(TokenizerBase) 221 | 222 | [&SOURCE](https://github.com/bojone/bert4keras/blob/master/bert4keras/tokenizers.py#L404) 223 | 224 | 用于类T5模型的tokenizer。 225 | 226 | ### def encode() 227 | 228 | 详见Tokenizer的[encode](https://github.com/Sniper970119/bert4keras_document/tree/master/tokneizers#def-encode ) 229 | 230 | 这两个共同继承自TokenizerBase,并在TokenizerBase中完成实现。 231 | 232 | 这个之所以单独写出来主要是因为T5的tokenizer使用了sentencepiece,与bert有区别。这个sentencepiece我现在还没有研究,回头研究了回来补 233 | --------------------------------------------------------------------------------