高分方案代码解析
Post training(预训练的后操作)
首先需要提取出一个新的词表
这里面提取词表采用的是最小熵原理
具体的操作步骤可以参考苏神的博客
最小熵原理
调用的时候很简单
train_data = load_data('train')
test_data = load_data('test')
获得train_data和test_data的字符串数组
[......
......
'可以贷公积金吗纯公积金吗可以的可以留个电话吗?我们录入,我不给你打电话的我们联系还是用这个不太大了,已经很便宜了',
'您好,我正在看尚林家园的房子看房吗有啊我带你看看',
'今天可以安排看房子吗?我约下房东,稍后回你可以看,你几点有时间过来呢?好的,那咱们在一号门口这碰头?']
接下来直接调用库生成new_words
from nlp_zero import *
f = Word_Finder(min_proba=1e-5)
f.train(G())
f.find(G())
# 长度为2~5 且不在jieba 词典内的词
new_words = [w for w, _ in f.words.items() if len(w) > 2 and len(w) < 5 and len(jieba.lcut(w, HMM=False)) > 1]
with open('new_dict.txt', 'w') as f:
f.write('\n'.join(new_words))
进行point-post-training-wwm-sop.py和pair-post-training-wwm-sop.py的训练
python point-post-training-wwm-sop.py
python pair-post-training-wwm-sop.py
1.分析point-post-training-wwm-sop.py文件
这里load_data之后
train_data = load_data('train')
test_data = load_data('test')
data = train_data+test_data
构成的list的内容为
d =
[['采荷一小是分校吧', '杭州市采荷第一小学钱江苑校区,杭州市钱江新城实验学校。', '是的', '这是5楼'],
['毛坯吗?', '因为公积金贷款贷的少', '是呢', '这套一楼带院的,您看看', '房本都是五年外的', '好的??,您先看下'],
['你们的佣金费大约是多少和契税是多少。', '您是首套还是二套呢?', '所有费用下来654万', '包含着税费和我们的服务费和房款', '好的', '链家天鸿美域店NAME,电话是PHONE(同微信号),随时联系?'],
......]
接下来采用jieba切割词语的方法
因为nezha采用的是jieba分割词语的方法,所以这里也采用分割词语的方法
words_data = [[jieba.lcut(line) for line in sen] for sen in data]
获得切分之后的words_data
[
[['采荷', '一小', '是', '分校', '吧'], ['杭州市', '采荷', '第一', '小学', '钱江苑', '校区', ',', '杭州市', '钱江', '新城', '实验学校', '。'], ['是', '的'], ['这是', '5', '楼']],
[['毛坯吗', '?'], ['因为', '公积金', '贷款', '贷', '的', '少'], ['是', '呢'], ['这套', '一楼', '带院的', ',', '您看看'], ['房本', '都', '是', '五年', '外', '的'], ['好', '的', '?', '?', ',', '您先看下']],
[['你们', '的', '佣金', '费', '大约', '是多少', '和', '契税', '是多少', '。'], ['您', '是首套', '还是二套', '呢', '?'], ['所有', '费用', '下来', '654', '万'], ['包含', '着', '税费', '和', '我们', '的', '服务费', '和', '房款'], ['好', '的'], ['链家', '天鸿美域', '店', 'NAME', ',', '电话', '是', 'PHONE', '(', '同微信', '号', ')', ',', '随时联系', '?']]
......
]
接下来不管中间的各种函数,直接进入正文模型以及损失函数的构建
bert,train_model,loss = build_transformer_model_with_mlm()
进入到build_transformer_model_with_mlm函数之中查看模型的构建
bert = build_transformer_model(
config_path,
with_mlm='linear',
with_nsp=True,
model='nezha',
return_keras_model=False
)
首先我们仔细分析NEZHA模型
# add External knowledge
if self.external_embedding_size:
external_embedding = self.apply(x,
Embedding,
name='Embedding-External',
input_dim=self.vocab_size,
output_dim=self.external_embedding_size,
weights=[self.external_embedding_weights]
)
if self.external_embedding_size != self.embedding_size:
external_embedding = self.apply(external_embedding,
Dense,
name='External-Embedding-Mapping',
units=self.embedding_size,
kernel_initializer=self.initializer)
x = self.apply([token_with_segment, external_embedding], Add, name='Embedding-Token-Segment-External')
else:
x = token_with_segment
这里面引入了外部的embedding参数的内容
查看作者的原版的论文引入外部embedding的说明
这里调用模型的输出分为两部分
bert = build_transformer_model(
config_path,
with_mlm='linear',
with_nsp=True,
model='nezha',
return_keras_model=False
#return_keras_model=True
)
proba = bert.model.output
输出的proba的内容为
proba =
[<tf.Tensor 'NSP-Proba/Softmax:0' shape=(?, 2) dtype=float32>,
<tf.Tensor 'MLM-Activation/MLM-Activation/Identity:0' shape=(?, ?, 21128) dtype=float32>]
然后这里面封装的是几种损失函数
def mlm_loss(inputs):
"""计算loss的函数,需要封装为一个层
"""
y_true, y_pred, mask = inputs
_, y_pred = y_pred
loss = K.sparse_categorical_crossentropy(
y_true, y_pred, from_logits=True
)
loss = K.sum(loss * mask) / (K.sum(mask) + K.epsilon())
return loss
def nsp_loss(inputs):
"""计算nsp loss的函数,需要封装为一个层
"""
y_true, y_pred = inputs
y_pred, _ = y_pred
loss = K.sparse_categorical_crossentropy(
y_true, y_pred
)
loss = K.mean(loss)
return loss
def mlm_acc(inputs):
"""计算准确率的函数,需要封装为一个层
"""
y_true, y_pred, mask = inputs
_, y_pred = y_pred
y_true = K.cast(y_true, K.floatx())
acc = keras.metrics.sparse_categorical_accuracy(y_true, y_pred)
acc = K.sum(acc * mask) / (K.sum(mask) + K.epsilon())
return acc
def nsp_acc(inputs):
"""计算准确率的函数,需要封装为一个层
"""
y_true, y_pred = inputs
y_pred, _ = y_pred
y_true = K.cast(y_true, K.floatx())
acc = keras.metrics.sparse_categorical_accuracy(y_true, y_pred)
acc = K.mean(acc)
return acc
接下来主要看sentence loss是如何调用的
batch_token_ids =
[[101, 6435, 7309, 3300, 2791, 1377, 809, 4692, 4692, 1408, 102, 2644, 4924, 5023, 102, 2644, 1962, 102, 7987, 1814, 103, 103, 3300, 697, 1947, 1377, 809, 4692, 102]]
batch_segment_ids =
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]
batch_target_ids =
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1724, 2108, 0, 0, 0, 0, 0, 0, 0]]
batch_is_masked =
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0]]
batch_nsp =
[[0]]
这里的遮盖掩码是103,这个103会放入batch_token_ids的内容之中,batch_segment_ids正常的区分问题和答案,batch_target_ids除了带标记的为需要预测的之外,其他的为0,batch_is_masked标记遮盖了的内容为1,其他部分的内容为0,batch_nsp标记是否为下一个句子。
这里重点看一下nsp(下一个句子损失)的训练
label = 1
p = np.random.random()
if p < 0.5:
label = 0
item = shuffle_reply(item)
注意这里的shuffle_reply只打乱回答部分,不打乱第一句的问句
接下来由于是按照切词构成的list数组,所以随机掩码的时候random_masking即可
source_tokens,target_tokens,segment_ids = random_masking(item)
next_sentence_prediction struct
if self.with_pool:
# pooler 提取cls向量
x = self.apply(x, layer=Lambda, name='Pooler', function=lambda x: x[:, 0])
pool_activation = 'tanh' if self.with_pool is True else self.with_pool
x = self.apply(x,
layer=Dense,
name='Pooler-Dense',
units=self.hidden_size,
activation=pool_activation,
kernel_initializer=self.initializer)
if self.with_nsp:
# Next sentence prediction
x = self.apply(x,
layer=Dense,
name='NSP-Proba',
units=2,
activation='softmax',
kernel_initializer=self.initializer)
outputs.append(x)
mlm struct
if self.with_mlm:
# Mask language model, Dense --> Norm --> Embedding --> Biasadd --> softmax
x = outputs[0]
x = self.apply(x,
layer=Dense,
name='MLM-Dense',
units=self.embedding_size,
activation=self.hidden_act,
kernel_initializer=self.initializer)
x = self.apply(inputs=self.simplify([x, z]),
layer=LayerNormalization,
conditional=(z is not None),
condition_hidden_units=self.layer_norm_conds[1],
condition_hidden_activation=self.layer_norm_conds[2],
condition_hidden_initializer=self.initializer,
name='MLM-Norm',
)
# 重用embedding-token layer
x = self.apply(x, Embedding, 'Embedding-Token', arguments={
'mode': 'dense'})
x = self.apply(x, BiasAdd, 'MLM-Bias')
mlm_activation = 'softmax' if self.with_mlm is True else self.with_mlm
x = self.apply(x, Activation, 'MLM-Activation', activation=mlm_activation)
outputs.append(x)
pair-post-training-wwm-sop.py
读取代码的部分
train_data = load_data('train')
test_data = load_data('test')
获得的train_data和test_data的结果
[['采荷一小是分校吧', '杭州市采荷第一小学钱江苑校区,杭州市钱江新城实验学校。', '是的', '这是5楼'],
['毛坯吗?', '因为公积金贷款贷的少', '是呢', '这套一楼带院的,您看看', '房本都是五年外的', '好的??,您先看下']
......
]
这里的item和label的关键在于句子的顺序换不换,我们通过循环来进行具体的查看
for is_end,item in self.get_sample(shuffle):
label = 1
p = np.random.random()
if p < 0.5:
label = 0
item = item[::-1]
这里如果p < 0.5的时候,原文内容
item =
[['哪里呢'], ['您', '这边', '考虑', '的', '是', '总价', '在', '70', '.', '80万', '左右', '么']]
经过变换之后的内容
[['您', '这边', '考虑', '的', '是', '总价', '在', '70', '.', '80万', '左右', '么'], ['哪里呢']]
每一个问句匹配一个答句,当p<0.5的时候句子交换顺序,当p>0.5的时候句子不交换顺序
注意next_sentence_loss中的标签内容一定要注意到
正常情况下句子的标签内容为1,如果调换顺序之后句子的标签内容为0
后续的操作大同小异,这里就不赘述了
pet post-training后续的操作
三种sentence-loss的总结
1.第一种sentence-loss:将同一对话作为同一篇文档顺序排列
比如如下对话:
采荷一小是分校吧。是的。
还有可能更换顺序:
是的。采荷一小是分校吧。
2.第二种sentence-loss:将问题作为单独文档,同一问题的所有回答作为单独文档。
比如如下的整个对话:
采荷一小是分校吧。
是的 杭州市采荷第一小学钱江苑校区,杭州市钱江新城实验学校。 这是五楼
还有可能更换顺序:
采荷一小是分校吧。
杭州市采荷第一小学钱江苑校区,杭州市钱江新城实验学校。 是的 这是五楼
3.将问题和回答同时拆分为左右两个部分做nsp任务
这一部分跟作者交流了一下,属于作者的创意部分,也是使用pair的方法
采荷一小
是分校吧。
是
的
杭州市采荷第一小学钱江苑校区,
杭州市钱江新城实验学校。
得到的pair部分(举例子)
是分校吧。采荷一小[sep]的是[sep]