NLP:训练一个中文问答模型Ⅰ
训练一个中文问答模型I-Step by Step
本文基于经典的NMT架构(Seq2Seq+Attention),训练了一个中文问答模型,把问题到答案之间的映射看作是问题到答案的翻译。基于Tensorflow 2.x实现,分词采用了jieba,在中文词汇粒度上训练模型。
模型架构如下图所示:
目录:
- 数据集介绍
- 数据预处理
- 文本的向量化
- 编码器
- 交叉注意力
- 解码器
- 组装模型
- 训练模型
- 测试模型
(由于显卡显存有限,所以模型的机构比较简单,在显存资源比较充足的情况下可以增加模型的复杂度,获得更好的表现)
数据集
cMedQA2:中文医学问答的数据集的2.0版本,数据是匿名的,不包括任何个人信息。
DataSet | #Ques | #Ans | Ave. #words per Question | Ave. #words per Answer | Ave. #characters per Question | Ave. #characters per Answer |
---|---|---|---|---|---|---|
Train | 100,000 | 188,490 | - | - | 48 | 101 |
Dev | 4,000 | 7,527 | - | - | 49 | 101 |
Test | 4,000 | 7,552 | - | - | 49 | 100 |
Total | 108,000 | 203,569 | - | - | 49 | 101 |
- questions.csv :全部的问题数据
- answers.csv :全部的答案数据
- train_candidates.txt 、 test_candidates.txt :划分好的训练测试集
import os
import jieba
import numpy as np
import pandas as pd
import tensorflow as tf
import matplotlib.pyplot as plt
import matplotlib.ticker as tickeros.environ['TF_CPP_MIN_LOG_LEVEL']='2'
question_df = pd.read_csv('~/DataSet/cMedQA2-master/question.csv')answer_df = pd.read_csv('~/DataSet/cMedQA2-master/answer.csv')
question_df.head()
question_id | content | |
---|---|---|
0 | 65102009 | 头痛恶心肌肉痛关节痛颈部淋巴结疼痛怎么回事啊 |
1 | 44275784 | 我怀孕37周,肠子一直呼噜呼噜叫感觉像是在里面灌水,上厕所拉稀和喷水一样,一天上厕所5次,对... |
2 | 42163349 | 男,67岁,2天前突然出现右小腿(类似抽筋症状),现出现右小腿消肿,有指压痕,无,请问可能是... |
3 | 67935540 | 怀孕前两个月照了CT和X光,来两次月经后怀孕了,怀孕期间有轻微盆腔积水吃了两瓶杏香兔耳片。请... |
4 | 33429289 | 阴囊湿冷阳痿早泻请问用点什么药 |
answer_df.head()
ans_id | question_id | content | |
---|---|---|---|
0 | 0 | 45619783 | 月经延迟十四天而且伴随恶心,头痛,乏力的现象,那么考虑怀孕的概率是非常大的,建议你去医院检查... |
1 | 1 | 45619783 | 如果你的月经周期规律,有正常的性生活,未采取任何有效的避孕措施,此时的症状考虑有怀孕的可能。... |
2 | 2 | 45619783 | 建议在性生活过后14天左右可以用怀孕试纸自我检测一下,一般怀孕试纸显示2条线的话是怀孕了的,... |
3 | 3 | 26616465 | 头痛是临床上最为常见的临床症状之一,是人体对各种致痛因素所产生的主观感觉,属于疼痛的范畴。建... |
4 | 4 | 26616465 | 头痛主要是由于头部的血管、神经、脑膜等对疼痛敏感的组织受到刺激引起的。由紧张、疲劳、饮酒等原... |
数据预处理
- 对文本进行分词
- 添加开始和结束的token:
[START],[END]
- 创建词到索引的的映射关系
word->id->word
- 如果是不同语言之间的翻译要创建两套不同的映射关系
- 同一种语言的问答模型一组映射关系即可,但也可以使用两组
- 填充文本到固定的最大长度
question_df['wordslist'] = question_df.content.apply(lambda x: list(jieba.cut(x)))question_df['wordlist'] = question_df.content.apply(lambda x: list(x))
Building prefix dict from the default dictionary ...
Loading model from cache /tmp/jieba.cache
Loading model cost 0.810 seconds.
Prefix dict has been built successfully.
answer_df['wordslist'] = answer_df.content.apply(lambda x: list(jieba.cut(x)))answer_df['wordlist'] = answer_df.content.apply(lambda x: list(x))
question_df.head(3)
question_id | content | wordslist | wordlist | |
---|---|---|---|---|
0 | 65102009 | 头痛恶心肌肉痛关节痛颈部淋巴结疼痛怎么回事啊 | [头痛, 恶心, 肌肉, 痛, 关节痛, 颈部, 淋巴结, 疼痛, 怎么回事, 啊] | [头, 痛, 恶, 心, 肌, 肉, 痛, 关, 节, 痛, 颈, 部, 淋, 巴, 结, ... |
1 | 44275784 | 我怀孕37周,肠子一直呼噜呼噜叫感觉像是在里面灌水,上厕所拉稀和喷水一样,一天上厕所5次,对... | [我, 怀孕, 37, 周, ,, 肠子, 一直, 呼噜, 呼噜, 叫, 感觉, 像是, 在... | [我, 怀, 孕, 3, 7, 周, ,, 肠, 子, 一, 直, 呼, 噜, 呼, 噜, ... |
2 | 42163349 | 男,67岁,2天前突然出现右小腿(类似抽筋症状),现出现右小腿消肿,有指压痕,无,请问可能是... | [男, ,, 67, 岁, ,, 2, 天前, 突然, 出现, 右小腿, (, 类似, 抽筋... | [男, ,, 6, 7, 岁, ,, 2, 天, 前, 突, 然, 出, 现, 右, 小, ... |
answer_df.head(3)
ans_id | question_id | content | wordslist | wordlist | |
---|---|---|---|---|---|
0 | 0 | 45619783 | 月经延迟十四天而且伴随恶心,头痛,乏力的现象,那么考虑怀孕的概率是非常大的,建议你去医院检查... | [月经, 延迟, 十四天, 而且, 伴随, 恶心, ,, 头痛, ,, 乏力, 的, 现象,... | [月, 经, 延, 迟, 十, 四, 天, 而, 且, 伴, 随, 恶, 心, ,, 头, ... |
1 | 1 | 45619783 | 如果你的月经周期规律,有正常的性生活,未采取任何有效的避孕措施,此时的症状考虑有怀孕的可能。... | [如果, 你, 的, 月经周期, 规律, ,, 有, 正常, 的, 性生活, ,, 未, 采... | [如, 果, 你, 的, 月, 经, 周, 期, 规, 律, ,, 有, 正, 常, 的, ... |
2 | 2 | 45619783 | 建议在性生活过后14天左右可以用怀孕试纸自我检测一下,一般怀孕试纸显示2条线的话是怀孕了的,... | [建议, 在, 性生活, 过后, 14, 天, 左右, 可以, 用, 怀孕, 试纸, 自我,... | [建, 议, 在, 性, 生, 活, 过, 后, 1, 4, 天, 左, 右, 可, 以, ... |
问题词频统计:
qwords_counts = {}
for q in question_df.wordslist:for words in q:if words in qwords_counts:qwords_counts[words] += 1else:qwords_counts[words] = 0
awords_counts = {}
for a in answer_df.wordslist:for words in a:if words in awords_counts:awords_counts[words] += 1else:awords_counts[words] = 0
qwords_set = set([words for words in qwords_counts.keys()])
awords_set = set([words for words in awords_counts.keys()])
len(qwords_counts), len(awords_counts): (60436, 82837)
len(qwords_set.intersection(awords_set)) : 32068
划分训练和测试数据(因为每个问题对应多个回答,有消极和积极):
train_ids = pd.read_csv('~/DataSet/cMedQA2-master/train_candidates.txt')test_ids = pd.read_csv('~/DataSet/cMedQA2-master/test_candidates.txt')test_ids = test_ids.drop_duplicates('question_id')train_ids = train_ids.drop_duplicates('question_id')
train_data = train_ids.merge(question_df[['question_id','wordslist']], on='question_id', how='left')train_data = train_data.merge(answer_df[['ans_id','wordslist']], left_on='pos_ans_id', right_on='ans_id')test_data = test_ids.merge(question_df[['question_id','wordslist']], on='question_id', how='left')test_data = test_data.merge(answer_df[['ans_id','wordslist']], on='ans_id', how='left')
test_data.head(3)
question_id | ans_id | cnt | label | wordslist_x | wordslist_y | |
---|---|---|---|---|---|---|
0 | 23423734 | 137315 | 0 | 1 | [我, 的, 右脚, 外, 踝骨, 折, 一年, 多, ・, 平时, 有脚, 走路, 不敢,... | [你, 的, 情况, 考虑, 局部, 有, 炎症, 的, 可能性, 大, 一些, 。, 建议... |
1 | 6469692 | 153600 | 0 | 1 | [全部, 症状, :, 手指, 关节, 不, 小心, 韧带, 扭伤, 现在, 关节, 肿大,... | [首先, 建议, 拍片, 看看, 是否是, 有, 骨折, 啊, 。, 如果, 没有, 骨折,... |
2 | 4833968 | 51452 | 0 | 1 | [请问, 一下, 脑袋, 疼, 的, 厉害, ,, 基本, 整个, 脑袋, 都, 疼, ,,... | [如果, 你, 有, 这方面, 的, 烦恼, ,, 请, 先到, 正规, 的, 医院, 诊断... |
train_qs = np.array([' '.join(wordslist) for wordslist in train_data.wordslist_x])train_as = np.array([' '.join(wordslist) for wordslist in train_data.wordslist_y])test_qs = np.array([' '.join(wordslist) for wordslist in test_data.wordslist_x])test_as = np.array([' '.join(wordslist) for wordslist in test_data.wordslist_y])
test_as[:3]
array(['你 的 情况 考虑 局部 有 炎症 的 可能性 大 一些 。 建议 服用 红 药片 , 乙酰螺旋霉素 片 等 治疗 观察 。 必要 时 拍 X 线 明确 诊断 。','首先 建议 拍片 看看 是否是 有 骨折 啊 。 如果 没有 骨折 可能 是 软组织 伤 啊 。 建议 前 3 天 冷敷 。 3 天后 热敷 试试 。','如果 你 有 这方面 的 烦恼 , 请 先到 正规 的 医院 诊断 , 再 根据 医生 的 指导 进行 治疗 , 切勿 自行 用药 。'],dtype='<U402')
问题和答案长度的分布:
创建tf.DataSet:
BUFFER_SIZE = len(train_qs)
BATCH_SIZE = 64train_ds = (tf.data.Dataset.from_tensor_slices((train_qs, train_as)).shuffle(BUFFER_SIZE).batch(BATCH_SIZE))
test_ds = (tf.data.Dataset.from_tensor_slices((test_qs, test_as)).shuffle(BUFFER_SIZE).batch(BATCH_SIZE))
创建文本向量化层
- 构建词到索引的映射关系
- 添加开始和结束符
def add_start_end_token(text):# Strip whitespace.text = tf.strings.strip(text)text = tf.strings.join(['[START]', text, '[END]'], separator=' ')return text
for example_question_strings, example_answer_strings in train_ds.take(1):break
example_text = example_answer_strings[0]print(example_text.numpy().decode())
print(add_start_end_token(example_text).numpy().decode())
女性 腹部 包块 主要 考虑 生殖系统 的 问题 ( 如 附件炎 、 卵巢囊肿 等 ) , 首选 留尿后 B超 检查 , 另外 还 应该 超声 检查 肝胆 、 双肾 等 。 总之 首选 B超 检查 , 根据 医师 的 再进一步 做 其他 相关 的 辅助 检查 。 建议 及时 到 医院 就医 , B超 后 遵 医嘱 做 相关 进一步 检查 。
[START] 女性 腹部 包块 主要 考虑 生殖系统 的 问题 ( 如 附件炎 、 卵巢囊肿 等 ) , 首选 留尿后 B超 检查 , 另外 还 应该 超声 检查 肝胆 、 双肾 等 。 总之 首选 B超 检查 , 根据 医师 的 再进一步 做 其他 相关 的 辅助 检查 。 建议 及时 到 医院 就医 , B超 后 遵 医嘱 做 相关 进一步 检查 。 [END]
问题文本向量化:
question_vocab_size = 60436
question_max_length = 75question_vectorization_layer = tf.keras.layers.TextVectorization(standardize = add_start_end_token,max_tokens = question_vocab_size,output_mode = 'int',output_sequence_length = question_max_length)
question_vectorization_layer.adapt(train_ds.map(lambda question, answer: question))# the first 10 words from the question vocabulary:
question_vectorization_layer.get_vocabulary()[:10]
['', '[UNK]', ',', '[START]', '[END]', '了', '的', '。', '?', '我']
答案文本向量化:
answer_vocab_size = 82837
answer_max_length = 125answer_vectorization_layer = tf.keras.layers.TextVectorization(standardize = add_start_end_token,max_tokens = answer_vocab_size,output_mode = 'int',output_sequence_length = answer_max_length)
answer_vectorization_layer.adapt(train_ds.map(lambda question, answer: answer))# the first 10 words from the question vocabulary:
answer_vectorization_layer.get_vocabulary()[:10]
['', '[UNK]', ',', '的', '。', '是', '[START]', '[END]', '、', '治疗']
文本的向量化层可以把文本从字符串,转化为对应的索引序列:
- text → \to → tokens,填充字符的索引为:0,很容易转换为掩码。
example_tokens = question_vectorization_layer(example_question_strings)
example_tokens[:1, :]
<tf.Tensor: shape=(1, 75), dtype=int64, numpy=
array([[ 3, 6675, 11, 36101, 55, 24, 290, 18, 4,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, 0, 0,0, 0, 0]])>
tokens → \to → text
question_vocab = np.array(question_vectorization_layer.get_vocabulary())
tokens = question_vocab[example_tokens[0].numpy()]
' '.join(tokens)
'[START] 腹中 有 痞块 要 做 哪些 检查 [END] '
可视化Token
创建训练数据集
创建喂给模型的数据格式,当前的数据格式为(question,answer)对,要转为((question, answer_in),answer_out)对。(question, answer_in)作为模型的输入,answer_out为模型的输出,也就是标签。answer_in 和 answer_out 之间的区别在于它们相对于彼此移动一个位置的索引,因此answer_out在每个位置的token索引都是answer_in的下一个token的索引。
例如:
- answer_in : 1, 2, 3, 4, 5
- answer_out: 2, 3, 4, 5, 6
def process_ds(question, answer):question = question_vectorization_layer(question)answer = answer_vectorization_layer(answer)answer_in = answer[:,:-1]answer_out = answer[:,1:]return (question, answer_in), answer_out
train_ds = train_ds.map(process_ds, tf.data.AUTOTUNE)
test_ds = test_ds.map(process_ds, tf.data.AUTOTUNE)
for (question_tok, answer_in), answer_out in train_ds.take(1):print("Question :", question_tok[0, :10].numpy()) print()print("Answer_in:",answer_in[0, :10].numpy()) print("Answer_out:",answer_out[0, :10].numpy())
Question : [ 3 136 195 40116 391 6069 10 445 42 8]Answer_in: [ 6 247 10 274 3 15 114 945 916 20158]
Answer_out: [ 247 10 274 3 15 114 945 916 20158 1406]
编码器
BRNN : bidirectional RNN
- 通过文本向量化层,获取token(词)的索引序列。
- 通过嵌入层查找每个token的嵌入向量。
- 将转换后的词向量序列输入到BRNN中。
- 返回处理后的序列,传递给注意力层。
编码器的目标是将上下文序列处理为对解码器有用的向量序列,由于上下文序列是恒定的,因此对信息在编码器中的流动方式没有限制,因此使用双向RNN进行处理:
class Encoder(tf.keras.layers.Layer):def __init__(self, text_processor, units=256):super(Encoder, self).__init__()self.text_processor = text_processorself.vocab_size = text_processor.vocabulary_size()self.units = units# The embedding layer converts tokens to vectorsself.embedding = tf.keras.layers.Embedding(self.vocab_size, units,mask_zero=True)# The RNN layer processes those vectors sequentially.self.rnn = tf.keras.layers.Bidirectional(merge_mode='sum',layer=tf.keras.layers.GRU(units,# Return the sequence and statereturn_sequences=True,recurrent_initializer='glorot_uniform'))def call(self, x):# looks up the embedding vector for each token.x = self.embedding(x)# GRU processes the sequence of embeddings.x = self.rnn(x)# Returns the new sequence of embeddings.return xdef convert_input(self, texts):texts = tf.convert_to_tensor(texts)if len(texts.shape) == 0:texts = tf.convert_to_tensor(texts)[tf.newaxis]tokens = self.text_processor(texts)x = self(tokens)return x
# Encode the input sequence.
encoder = Encoder(question_vectorization_layer)
ex_question = encoder(question_tok)print(f'question tokens, shape (batch, s):{question_tok.shape}')
print(f'Encoder output, shape (batch, s, units):{ex_question.shape}')
question tokens, shape (batch, s): (64, 75)
Encoder output, shape (batch, s, units): (64, 75, 256)
注意力层
注意层允许解码器访问编码器提取的信息。它从整个上下文序列中计算得到一个向量,并将其添加到解码器的输出中。把整个序列中压缩为单个向量的最简单方法是取整个序列(层)的平均值。GlobalAveragePooling1D)。注意力层类似,但计算上下文序列中的加权平均值。其中权重是根据上下文和“查询”向量的组合计算的。
class CrossAttention(tf.keras.layers.Layer):def __init__(self, units=256, **kwargs):super().__init__()self.mha = tf.keras.layers.MultiHeadAttention(key_dim=units, num_heads=2, **kwargs)self.layernorm = tf.keras.layers.LayerNormalization()self.add = tf.keras.layers.Add()def call(self, x, context):attn_output, attn_scores = self.mha(query=x,value=context,return_attention_scores=True)# Cache the attention scores for plotting later.attn_scores = tf.reduce_mean(attn_scores, axis=1)self.last_attention_weights = attn_scoresx = self.add([x, attn_output])x = self.layernorm(x)return x
attention_layer = CrossAttention()# Attend to the encoded tokens
embed = tf.keras.layers.Embedding(answer_vectorization_layer.vocabulary_size(),output_dim=256, mask_zero=True)
answer_embed = embed(answer_in)result = attention_layer(answer_embed, ex_question)print(f'question sequence, shape (batch, s, units):{ex_question.shape}')
print(f'answer_in sequence, shape (batch, t, units):{answer_embed.shape}')
print(f'Attention result, shape (batch, t, units):{result.shape}')
print(f'Attention weights, shape (batch, t, s):{attention_layer.last_attention_weights.shape}')
question sequence, shape (batch, s, units): (64, 75, 256)
answer_in sequence, shape (batch, t, units): (64, 124, 256)
Attention result, shape (batch, t, units): (64, 124, 256)
Attention weights, shape (batch, t, s): (64, 124, 75)
可视化注意力矩阵
attention_weights = attention_layer.last_attention_weights
mask=(question_tok != 0).numpy()plt.subplot(1, 2, 1)
plt.pcolormesh(attention_weights[:, 0, :])
plt.title('Attention weights')plt.subplot(1, 2, 2)
plt.pcolormesh(mask)
plt.title('Mask');
由于小随机初始化,注意力权重最初都接近 1/(sequence_length)。
解码器
解码器预测answer_in序列中每个位置的下一个token。
- 通过嵌入层查询输入序列每个token的词嵌入向量。
- 使用RNN来处理词向量序列。
- 把RNN 输出作为注意力层的“查询”输入。
- 输出预测目标序列每一个位置下一个token的概率分布。
class Decoder(tf.keras.layers.Layer):@classmethoddef add_method(cls, fun):setattr(cls, fun.__name__, fun)return fundef __init__(self, text_processor, units=256):super(Decoder, self).__init__()self.text_processor = text_processorself.vocab_size = text_processor.vocabulary_size()self.word_to_id = tf.keras.layers.StringLookup(vocabulary=text_processor.get_vocabulary(),mask_token='', oov_token='[UNK]')self.id_to_word = tf.keras.layers.StringLookup(vocabulary=text_processor.get_vocabulary(),mask_token='', oov_token='[UNK]',invert=True)self.start_token = self.word_to_id(np.array('[START]',dtype=np.str_))self.end_token = self.word_to_id(np.array('[END]',dtype=np.str_))self.units = units# embedding layer converts token IDs to vectorsself.embedding = tf.keras.layers.Embedding(self.vocab_size,units, mask_zero=True)# RNN keeps track of what's been generated so far.self.rnn = tf.keras.layers.GRU(units,return_sequences=True,return_state=True,recurrent_initializer='glorot_uniform')# RNN output will be the query for the attention layer.self.attention = CrossAttention(units)# This fully connected layer produces the logits for each# output token.self.output_layer = tf.keras.layers.Dense(self.vocab_size)
- context : question经过编码器后的输出结果
- x : answer_in
@Decoder.add_method
def call(self,context, x, state=None, return_state=False): # Lookup the embeddingsx = self.embedding(x)# Process the target sequence.x, state = self.rnn(x, initial_state=state)# Use the RNN output as the query for the attention over the context.x = self.attention(x, context)self.last_attention_weights = self.attention.last_attention_weights# Generate logit predictions for the next token.logits = self.output_layer(x)if return_state:return logits, stateelse:return logits
decoder = Decoder(answer_vectorization_layer, units=256)logits = decoder(ex_question, answer_in)print(f'encoder output shape: (batch, s, units){ex_question.shape}')
print(f'input answer_in tokens shape: (batch, t){answer_in.shape}')
print(f'logits shape shape: (batch, target_vocabulary_size){logits.shape}')
encoder output shape: (batch, s, units) (64, 75, 256)
input answer_in tokens shape: (batch, t) (64, 124)
logits shape shape: (batch, target_vocabulary_size) (64, 124, 59768)
推理
用start_toekn:[START]
作为开始:
@Decoder.add_method
def get_initial_state(self, context):batch_size = tf.shape(context)[0]start_tokens = tf.fill([batch_size, 1], self.start_token)done = tf.zeros([batch_size, 1], dtype=tf.bool)embedded = self.embedding(start_tokens)return start_tokens, done, self.rnn.get_initial_state(embedded)[0]
过滤填充字符:
@Decoder.add_method
def tokens_to_text(self, tokens):words = self.id_to_word(tokens)result = tf.strings.reduce_join(words, axis=-1, separator=' ')result = tf.strings.regex_replace(result, '\[START\]', '')result = tf.strings.regex_replace(result, '\[END\]', '')result = tf.strings.regex_replace(result, '\[UNK\]','')return result
通过采样得到下一个token:
@Decoder.add_method
def get_next_token(self, context, next_token, done, state, temperature = 0.0):logits, state = self(context, next_token, state = state, return_state=True) if temperature == 0.0:next_token = tf.argmax(logits, axis=-1)else:logits = logits[:, -1, :]/temperaturenext_token = tf.random.categorical(logits, num_samples=1)# If a sequence produces an `end_token`, set it `done`done = done | (next_token == self.end_token)# Once a sequence is done it only produces 0-padding.next_token = tf.where(done, tf.constant(0, dtype=tf.int64), next_token)return next_token, done, state
通过自回归的方式生成文本序列:
# Setup the loop variables.
next_token, done, state = decoder.get_initial_state(ex_question)tokens = []for i in range(1, 11):# Run one step.next_token, done, state = decoder.get_next_token(ex_question, next_token, done, state, temperature=1.0)# Add the token to the output.tokens.append(next_token)
# Stack all the tokens together.
tokens = tf.concat(tokens, axis=-1) # (batch, t)
# Convert the tokens back to a a string
result = decoder.tokens_to_text(tokens)
for _ in result.numpy()[:5]:print(_.decode())
STII 缝里 需不需要 中如 葛根芩 眼下 主因 抗老 连子心 制
主任 甲灭 力所不及 四分之三 四物汤 不齐 维酸钾 社会保障 木糖醇 食生
深加工 股管 试服 袭击 妊高症 性寒味 今古 血要 泡发 中下旬
先验 渠道 15mg 穹隆 骨密度 绿茶 些 唾腺 螃蟹 承受不住
维一素 还会压 宗合 盘腿 归纳 鞭毛虫 心神 上移 平性 240
构建模型
- 编码器 + 注意力 + 解码器 → \to → 模型
class Translator(tf.keras.Model):@classmethoddef add_method(cls, fun):setattr(cls, fun.__name__, fun)return fundef __init__(self, units, question_text_processor,answer_text_processor):super().__init__()# Build the encoder and decoderencoder = Encoder(question_text_processor, units)decoder = Decoder(answer_text_processor, units)self.encoder = encoderself.decoder = decoderdef call(self, inputs):question, answer_in = inputsquestion = self.encoder(question)logits = self.decoder(question, answer_in)#TODO(b/250038731): remove thistry:# Delete the keras mask, so keras doesn't scale the loss+accuracy.del logits._keras_maskexcept AttributeError:passreturn logits
model = Translator(256, question_vectorization_layer, answer_vectorization_layer)logits = model((question_tok, answer_in))print(f'Question tokens, shape: (batch, s, units){question_tok.shape}')
print(f'Answer_in tokens, shape: (batch, t){answer_in.shape}')
print(f'logits, shape: (batch, t, target_vocabulary_size){logits.shape}')
Question tokens, shape: (batch, s, units) (64, 75)
Answer_in tokens, shape: (batch, t) (64, 124)
logits, shape: (batch, t, target_vocabulary_size) (64, 124, 59768)
损失函数和目标函数
def masked_loss(y_true, y_pred):# Calculate the loss for each item in the batch.loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True, reduction='none')loss = loss_fn(y_true, y_pred)# Mask off the losses on padding.mask = tf.cast(y_true != 0, loss.dtype)loss *= mask# Return the total.return tf.reduce_sum(loss)/tf.reduce_sum(mask)
def masked_acc(y_true, y_pred):# Calculate the loss for each item in the batch.y_pred = tf.argmax(y_pred, axis=-1)y_pred = tf.cast(y_pred, y_true.dtype)match = tf.cast(y_true == y_pred, tf.float32)mask = tf.cast(y_true != 0, tf.float32)return tf.reduce_sum(match)/tf.reduce_sum(mask)
model.compile(optimizer='adam',loss=masked_loss, metrics=[masked_acc])
当前模型是随机初始化的,会给出大致一致的输出概率。因此,Loss和Accuracy的初始值是:
vocab_size = 1.0 * answer_vectorization_layer.vocabulary_size(){"expected_loss": tf.math.log(vocab_size).numpy(),"expected_acc": 1/vocab_size}
{'expected_loss': 10.998226, 'expected_acc': 1.67313612635524e-05}
model.summary()
Model: "translator_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
encoder_1 (Encoder) multiple 14869760
_________________________________________________________________
decoder_1 (Decoder) multiple 33159800
=================================================================
Total params: 48,029,560
Trainable params: 48,029,560
Non-trainable params: 0
_________________________________________________________________
训练模型
history = model.fit(train_ds.repeat(), epochs=100,steps_per_epoch = 1000,validation_data = test_ds,validation_steps = 20,callbacks=[tf.keras.callbacks.EarlyStopping(patience=3)])
Epoch 1/100
1000/1000 [==============================] - 317s 305ms/step - loss: 5.5534 - masked_acc: 0.1849 - val_loss: 4.3384 - val_masked_acc: 0.2744
Epoch 2/100
1000/1000 [==============================] - 302s 302ms/step - loss: 3.9044 - masked_acc: 0.3086 - val_loss: 3.7822 - val_masked_acc: 0.3286
Epoch 3/100
1000/1000 [==============================] - 303s 303ms/step - loss: 3.5184 - masked_acc: 0.3494 - val_loss: 3.4996 - val_masked_acc: 0.3641
Epoch 4/100
1000/1000 [==============================] - 302s 302ms/step - loss: 3.1584 - masked_acc: 0.3866 - val_loss: 3.4521 - val_masked_acc: 0.3790
Epoch 5/100
1000/1000 [==============================] - 303s 303ms/step - loss: 3.0122 - masked_acc: 0.4073 - val_loss: 3.3358 - val_masked_acc: 0.3981
Epoch 6/100
1000/1000 [==============================] - 302s 302ms/step - loss: 2.8398 - masked_acc: 0.4290 - val_loss: 3.3117 - val_masked_acc: 0.4101
Epoch 7/100
1000/1000 [==============================] - 303s 303ms/step - loss: 2.6473 - masked_acc: 0.4566 - val_loss: 3.4230 - val_masked_acc: 0.4038
Epoch 8/100
1000/1000 [==============================] - 303s 303ms/step - loss: 2.5828 - masked_acc: 0.4671 - val_loss: 3.3233 - val_masked_acc: 0.4208
Epoch 9/100
1000/1000 [==============================] - 301s 301ms/step - loss: 2.4191 - masked_acc: 0.4907 - val_loss: 3.3951 - val_masked_acc: 0.4155
学习曲线:
模型保存和加载
下面的方法使用了一个张量流循环,它看起来像一个python循环,但是当你使用张量作为循环的输入(或循环的条件)时,tf.function使用tf.while_loop等操作将其转换为动态循环。max_length
只是以防万一模型卡住生成一个循环,例如:1, 2, 1, 2, 1 ...
同时通过tf.TensorArray保存生成的token。
import einops
@Translator.add_method
def translate(self, texts, *, max_length=125,temperature=tf.constant(0.0)):context = self.encoder.convert_input(texts)batch_size = tf.shape(context)[0]next_token, done, state = self.decoder.get_initial_state(context)# initialize the accumulatortokens = tf.TensorArray(tf.int64, size=1, dynamic_size=True)for t in tf.range(max_length):# Generate the next tokennext_token, done, state = self.decoder.get_next_token(context, next_token, done, state, temperature)# Collect the generated tokenstokens = tokens.write(t, next_token)# if all the sequences are done, breakif tf.reduce_all(done):break# Convert the list of generated token ids to a list of strings.tokens = tokens.stack()tokens = einops.rearrange(tokens, 't batch 1 -> batch t')text = self.decoder.tokens_to_text(tokens)return text
%%time
result = model.translate(inputs)print(result[0].numpy().decode())
print(result[1].numpy().decode())
print(result[2].numpy().decode())
print()
这个 情况 要 考虑 手术 的 , 建议 你 可以 到 医院 骨科 就诊 , 做 核磁共振 检查
这个 情况 要 考虑 手术 的 , 建议 你 可以 到 医院 骨科 就诊 , 做 核磁共振 检查
这个 情况 要 考虑 手术 的 , 建议 你 可以 到 医院 骨科 就诊 , 做 核磁共振 检查 CPU times: user 1.93 s, sys: 38 ms, total: 1.97 s
Wall time: 1.94 s
class Export(tf.Module):def __init__(self, model):self.model = model@tf.function(input_signature=[tf.TensorSpec(dtype=tf.string, shape=[None])])def translate(self, inputs):return self.model.translate(inputs)
export = Export(model)
%%time
_ = export.translate(inputs)
CPU times: user 3.96 s, sys: 117 ms, total: 4.07 s
Wall time: 4.03 s
%%time
inputs = [question_text, question_text, question_text]
result = export.translate(inputs)print(result[0].numpy().decode())
print(result[1].numpy().decode())
print(result[2].numpy().decode())
print()
这个 情况 要 考虑 手术 的 , 建议 你 可以 到 医院 骨科 就诊 , 做 核磁共振 检查
这个 情况 要 考虑 手术 的 , 建议 你 可以 到 医院 骨科 就诊 , 做 核磁共振 检查
这个 情况 要 考虑 手术 的 , 建议 你 可以 到 医院 骨科 就诊 , 做 核磁共振 检查 CPU times: user 132 ms, sys: 10.2 ms, total: 142 ms
Wall time: 94.2 ms
保存和加载
%%time
tf.saved_model.save(export, 'dynamic_translator',signatures={'serving_default': export.translate})
WARNING:absl:Found untraced functions such as embedding_6_layer_call_and_return_conditional_losses, embedding_6_layer_call_fn, embedding_7_layer_call_and_return_conditional_losses, embedding_7_layer_call_fn, cross_attention_4_layer_call_and_return_conditional_losses while saving (showing 5 of 80). These functions will not be directly callable after loading.INFO:tensorflow:Assets written to: dynamic_translator/assetsINFO:tensorflow:Assets written to: dynamic_translator/assetsCPU times: user 45 s, sys: 2.17 s, total: 47.1 s
Wall time: 50.2 s
%%time
reloaded = tf.saved_model.load('dynamic_translator')
_ = reloaded.translate(tf.constant(inputs)) #warmup
CPU times: user 19.6 s, sys: 486 ms, total: 20.1 s
Wall time: 20 s
%%time
result = reloaded.translate(tf.constant(inputs))print(result[0].numpy().decode())
print(result[1].numpy().decode())
print(result[2].numpy().decode())
print()
这个 情况 要 考虑 手术 的 , 建议 你 可以 到 医院 骨科 就诊 , 做 核磁共振 检查
这个 情况 要 考虑 手术 的 , 建议 你 可以 到 医院 骨科 就诊 , 做 核磁共振 检查
这个 情况 要 考虑 手术 的 , 建议 你 可以 到 医院 骨科 就诊 , 做 核磁共振 检查 CPU times: user 125 ms, sys: 21.7 ms, total: 147 ms
Wall time: 105 ms
def answer_postprocess(answer):words = answer.split(' ')return ''.join(words)
问答测试
问: 盆腔积液会引起腿麻么?最近两个月总是右腿麻走路都不敢使劲还右侧痛。因为例假不正常,所以看了妇科检盆腔积液会引起腿麻么?最近两个月总右腿麻走路都不敢使劲还右侧腰痛。因为例假不正常,所以看了妇科检查是盆积液子宫内膜厚。这些会引起腿麻么?还是别的问题?
答: 盆腔炎的治疗主要以抗炎为主,治疗主要是对症治疗,如盆腔炎、附件
、盆腔炎等,建议你到正规医院妇科检查,查明病因,对症治疗。
问: 排卵试纸检测线深什么意思中午1点排卵试纸检测线很深,对照线很模
,请问现在同房还是晚上同房易怀孕,谢谢
答: 排卵日期一般在下次月经来潮前的14天左右,下次月经来潮的第1天
起,倒数14天或减去14天就是排卵日,排卵日及其前5天和后4天加在一
称为排卵期。你的情况建议你最好是在排卵期同房,怀孕的可能性比较大。
问: 晚上睡觉鼻子凉,早上起来又觉得热,请问是啥原因
答: 你的这种情况可能是由于受凉引起的鼻腔粘膜充血,或者是由于受凉引
的,建议你可以服用一些风寒感冒冲剂,可以服用感康,元胡止痛片等进行治
,注意休息,注意保暖,避免着凉,注意保暖,避免着凉,注意保暖,避免着
问: :我的月经是昨天干净的我明天能做性激素六项抽血检查吗?
答: 月经推迟一周是正常的,月经推迟一周是正常现象,一般月经推迟一周
正常现象。你可以到药店买到医院看看,让医生看看,吃一点消炎药。
NLP:训练一个中文问答模型Ⅰ相关推荐
- NLP:训练一个中文问答模型Ⅱ
训练一个中文问答模型Ⅱ-Step by Step 接上一篇 中文问答模型Ⅰ基于这次仍是基于NMT架构训练,但是把Seq2Seq替换为Transformer架构,还有一点不同是,本次没有采用分词训练 ...
- 如何从头训练一个一键抠图模型
如何从头训练一个一键抠图模型 1. 前言 抠图是图像编辑的基础功能之一,在抠图的基础上可以发展出很多有意思的玩法和特效.比如一键更换背景.一键任务卡通化.一键人物素描化等.正是因为这些有意思的玩法,C ...
- pascal行人voc_在一个很小的Pascal VOC数据集上训练一个实例分割模型
只使用1349张图像训练Mask-RCNN,有代码. 代码:https://github.com/kayoyin/tiny-inst-segmentation 介绍 计算机视觉的进步带来了许多有前途的 ...
- 论文浅尝 | 面向单关系事实问题的中文问答模型
来源:NLPCC 2017 论文下载地址:http://tcci.ccf.org.cn/conference/2017/papers/2003.pdf 动机 开放领域的QA问题是一个被广泛研究的问题, ...
- 利用GPT2训练中文闲聊模型
利用GPT2模型训练中文闲聊模型 最近看了一下GPT2模型,看到很多博主都用来写诗歌,做问答等,小编突然萌生一个想法,利用GPT2来训练一个闲聊模型!!(小说生成器模型已经破产,写出来的东西狗屁不通, ...
- 深度学习将会变革NLP中的中文分词
深度学习将会变革NLP中的中文分词 2016-08-08 19:03 转载 陈圳 0条评论 雷锋网按:本文转自ResysChina高翔,文章主要介绍了1)区分中文分词的方法:2)用深度学习的方法来解决 ...
- LEBERT:基于词汇增强的中文NER模型
01 任务概述 命名实体识别(简称NER)是NLP中的经典任务,即给定一个输入文本,让模型识别出文本中的实体信息. 在中文NER任务中,可以分为 Character-based (字符粒度) 和 Wo ...
- 用训练好的paddlepaddle模型继续训练模型和验证数据ckpt
# 模型加载 model = hub.Module(name='ernie', task='seq-cls', num_classes=14) tokenizer = model.get_tokeni ...
- 【经验帖】深度学习如何训练出好的模型
深度学习在近年来得到了广泛的应用,从图像识别.语音识别到自然语言处理等领域都有了卓越的表现.但是,要训练出一个高效准确的深度学习模型并不容易.不仅需要有高质量的数据.合适的模型和足够的计算资源,还需要 ...
最新文章
- “神经网络”的逆袭:80年AI斗争史
- C# 读取CSV和EXCEL文件示例
- java——File类常用方法
- java 有穷自动机_Java实现雪花算法(snowflake)
- ibm+x3650+m4+linux+raid驱动,IBM X3650M4阵列卡驱动下载
- spring三种注入方式
- 呼叫中心职场EQ与情绪压力管控(时刻提醒自己!)
- Microsoft Enterprise Library 5.0 系列(二) Cryptography Application Block (高级)
- python3.5.4安装_linux-centos系统下安装python3.5.4步骤
- c语言文件指针重新定向,C语言rewind()函数:将文件指针重新指向文件开头
- python之 ffmpeg合并ts视频为mp4视频
- AWS学习(一)——AWS云技术基础
- 网站优化 SEO概念
- 互联网金融指导意见落地 行业发展开始步入正轨
- 星淘惠:现在做跨境电商还有优势吗?跨境电商发展怎么样
- MUX VLAN详解与配置实例
- excel表格打开灰色,没有内容
- 手机投屏索尼电视显示无法访问服务器,钉钉视频会议投屏不成功是什么原因 钉钉视频会议如何屏幕共享...
- 国标GB28181协议国标设备是否可以同时接入多个国标GB28181平台进行视频直播、录像检索、回看
- android华为账号登陆,华为手机怎么找回华为账号密码?华为账号密码两种找回方法...