昨天,我們解說了對話狀態追蹤,用來紀錄每輪對話提到的重要資訊。
這篇我們來用T5實做一個對話追蹤器。
mT5意即支援多語言的T5,T5是一種Sequence-to-Sequence的預訓練模型,旨在將所有的NLP任務視為Text-To-Text任務,即輸入為一串文字,輸出也是一串文字(像機器翻譯就是個經典的例子)。
在對話狀態追蹤任務中,我們的輸入是當前對話及對話歷史,輸出是對話狀態。
雖然前篇說過對話狀態是字典格式,但我們還是可以將其整理成一字串做輸出
輸入是對話和對話歷史,輸出對話狀態格式為
[對話領域(話題)]([槽][槽值],...)
與BERTNLU相同,我們使用ConvLab裡的T5DST來做舉例。
from convlab.base_models.t5.dst.dst import T5DST
dst = T5DST(
'crosswoz', # 選擇語料庫
speaker='user', # 要辨識使用者/系統狀態
context_window_size=3, # 對話歷史大小
model_name_or_path='ConvLab/mt5-small-dst-crosswoz' # 選擇與語料庫相應的model
)
以下為一個對話歷史串列(contexts)的範例。由於在T5DST類別中我們選擇紀錄使用者的資訊,
所以每輪對話歷史的末尾,我們都會加上使用者的對話當成要辨識的話語。
contexts = [
["我想找一家北京的酒店。",],
["我想找一家北京的酒店。",
"好的,我可以帮您找北京的酒店。您对价格范围或星级有什么要求吗?",
"我想要一家四星级酒店,最好有免费停车。"],
["我想找一家北京的酒店。",
"好的,我可以帮您找北京的酒店。您对价格范围或星级有什么要求吗?",
"我想要一家四星级酒店,最好有免费停车。",
"明白了。我会为您查找北京的四星级酒店,并且有免费停车服务。您还有其他要求吗,比如特定的房间类型或者您感兴趣的设施?",
"暂时没有其他要求了。您能给我一些选择吗?",],
]
接下來,我們使用dst.init_session()
將對話狀態初始化。
然後以for迴圈模擬在對話時,DST更新對話狀態。
dst.init_session()
for context in contexts:
dst.state['history'] = context # 將對話歷史加入DST中
print(dst.update()) # 更新對話狀態
print()
當迴圈跑完,對話狀態即更新至最新的狀態
最後,我們來看看細節的部份,
對話狀態需包含對話歷史、Chatbot和使用者意圖、對話是否中止或對話本體(ontology)
而對話本體是對話中可能提到的資訊,可以看成資料庫有的KEY值
def init_session(self):
self.state = dict() # 初始化對話狀態
self.state['belief_state'] = deepcopy(self.ontology['state']) # 對話中可能提到的資訊
self.state['history'] = [] # 對話歷史
self.state['system_action'] = [] # 紀錄Chatbot行為(意圖)
self.state['user_action'] = [] # 紀錄使用者行為(意圖)
self.state['terminated'] = False # 對話是否結束
update()
部份很單純,只是將對話歷史及當前對話輸入至T5,並預測對話狀態。
def update(self, user_action=None):
if self.state['history'][0][1] == 'null':
# skip first dummy turn
context = self.state['history'][1:]
else:
context = self.state['history']
if len(context) > 0 and type(context[0]) is list and len(context[0]) > 1:
context = [item[1] for item in context]
context = context[-self.context_window_size:]
input_seq = '\n'.join([f"{self.opponent if (i % 2) == (len(context) % 2) else self.speaker}: {utt}" for i, utt in enumerate(context)])
# print(input_seq)
input_seq = self.tokenizer(input_seq, return_tensors="pt").to(self.device)
# print(input_seq)
output_seq = self.model.generate(**input_seq, max_length=256)
# print(output_seq)
output_seq = self.tokenizer.decode(output_seq[0], skip_special_tokens=True)
# print(output_seq)
state = deserialize_dialogue_state(output_seq.strip())
self.state['belief_state'] = state
return self.state
而自然語言理解任務(NLU)也可以用T5做替代
輸入是對話和對話歷史,輸出為當前對話的所有意圖。
相同的,我們也可以將mT5應用於NLU任務上,看起來就像這樣
from convlab.base_models.t5.nlu.nlu import T5NLU
if __name__=="__main__":
texts = [
"我想找一家北京的酒店。",
"我想要一家四星级酒店,最好有免费停车。",
"暂时没有其他要求了。您能给我一些选择吗?"
]
contexts = [
[], # 剛開始對話不會有對話歷史
["我想找一家北京的酒店。",
"好的,我可以帮您找北京的酒店。您对价格范围或星级有什么要求吗?"],
["我想找一家北京的酒店。",
"好的,我可以帮您找北京的酒店。您对价格范围或星级有什么要求吗?",
"我想要一家四星级酒店,最好有免费停车。",
"明白了。我会为您查找北京的四星级酒店,并且有免费停车服务。您还有其他要求吗,比如特定的房间类型或者您感兴趣的设施?"]
]
nlu = T5NLU(
speaker='user',
context_window_size=3,
model_name_or_path='ConvLab/mt5-small-nlu-all-crosswoz'
)
for text, context in zip(texts, contexts):
print(text)
print(nlu.predict(text, context))
print()
好!有了對話意圖、對話狀態,接下來我們就要好好想想Chatbot要怎麼回答?
Reference.
Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer
mT5: A Massively Multilingual Pre-trained Text-to-Text Transformer
Huggingface-ConvLab/mt5-small-dst-crosswoz
Huggingface-ConvLab/mt5-small-nlu-all-crosswoz