經過兩周的摸索和實驗,今天終于把星座運勢推進到最後的測試階段。盡管跑通一個demo會覺得rasa非常簡單,但完成一個function,從分離不同的intent,對話架構設計,設定rasa pipeline,具體月份的讀取,考慮邊界和異常情況,才發現對rasa的了解僅限于冰山一角。
文章目錄
- 借助BotSociety設計story
-
- 圍繞API功能和使用者提問角度來寫story
- rasa部分
-
- 中文分詞問題
- 清空slot的memory
- 日期星座映射
- 關鍵點設為entities
- 合并intent
- 合并path,用slot分流
- Rasa測試
借助BotSociety設計story
圍繞API功能和使用者提問角度來寫story
API功能:
- 查詢總體星座運勢
- 查詢愛情指數
- 查詢工作指數
- 查詢财運指數
- 查詢健康指數
- 查詢幸運顔色
- 查詢比對星座
- 星座映射到日期
- 日期映射到星座
- 安慰等情緒響應
使用者角度:
- 知道星座,明确提問
- 不知道星座,模糊提問,日期映射
- 檢視全部運勢
- 檢視具體運勢
設計story:
rasa部分
中文分詞問題
問題描述:
在rasa train階段就出現了boundary的報錯
理想實體是
摩羯座
但是提取出來的是
摩羯
解決方案:
https://github.com/howl-anderson/rasa_chinese
清空slot的memory
問題描述:
上圖中,bot先詢問了處女座的愛情指數,使starsign_type的槽位被填充入處女座,但是在新的一輪對話中,槽位沒有被清空,影響了接下來的判斷。
解決方案:
首先筆者在action中将槽位設為None,如下所示
class ResetAction(Action):
def name(self) -> Text:
return "action_reset"
def run(self, dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:
return [SlotSet('starsign_type', None)]
然而,并沒有起到清空的效果,而是采用
AllSlotsReset()
才能達到預期效果,使對話重新開始。經過實驗發現
slotset
可以設目前slot為某一個值,但是設為None沒反應。
# 正确方案:
return [AllSlotsReset(), Restarted(), SlotSet('starsign_type', None)]
日期星座映射
問題描述:
由于一些使用者不知道自己的星座,是以有必要在星座運勢查詢中設定星座日期映射的環節。使用者的查詢方式有多種,例如"7月2" “7 2” “七月二号” “7/2”
解決方案一:否定
用entities/slot(type = list)的方式将使用者輸入的資訊中所有的數字提取出來,在action中進行星座比對,再将結果傳回給使用者。
但是在實際操作中,無法将所有的數字提取出來,有些時候隻能取到一個數字,非常不穩定,且中文數字提取不出來。
解決方案二:
猜想可能是entities的部分沒有很好的提取到,
月
和
日
雖然會共享一段數字(1-12,一-十二),但仍需要更明顯的區分,于是用rasa中的role來區分。
我出生于[7]{"entity": "date", "role": "month"}月[2]{"entity": "date", "role": "day"} .
不過,也沒有什麼用。歎氣。
解決方案三:
我願稱之為,沒有python扶不起的牆。
思路是,把使用者這句話讀取,在action這個自由度很高的部分,利用python的正則來處理。題外話: 雖然rasa也有正則,但是總會出些bug,中文不适配等問題,是以選擇熟練度和容易指數高的python好了。
class ReturnStarAction(Action):
def name(self) -> Text:
return "action_return_star"
def checkdate(self, tracker):
inp = tracker.latest_message['text']
print(inp)
number = re.compile(r'([一二三四五六七八九零十]+|[0-9]+)')
pattern = re.compile(number)
all = pattern.findall(inp)
print(all)
date_status = False
date_accurate = False
if len(all) != 2:
return date_status, None, None, None
else:
date_status = True
month = all[0]
day = all[1]
if not month.isdigit():
month = pycnnum.cn2num(month)
if int(month) > 0 and int(month) < 13:
date_accurate = True
if not day.isdigit():
day = pycnnum.cn2num(day)
if int(day) > 0 and int(day) < 32:
date_accurate = True
return date_status, date_accurate, month, day
def findstar(self, month, day):
daylist = [20, 19, 21, 20, 21, 22, 23, 23, 23, 24, 23, 22]
starlist = ['摩羯座', '水瓶座', '雙魚座', '白羊座', '金牛座', '雙子座',
'巨蟹座', '獅子座', '處女座', '天秤座', '天蠍座', '射手座']
if int(day) < daylist[int(month) - 1]:
return starlist[int(month) - 1]
else:
return starlist[int(month)]
def run(self, dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any]) -> List[Dict[Text, Any]]:
date_status, date_accurate, month, day = self.checkdate(tracker)
if not date_status or not date_accurate:
dispatcher.utter_message(text=f'{"我尋思着這日期隻應天上有,要不您說個靠譜的?"}')
return []
else:
star = self.findstar(month, day)
dispatcher.utter_message(text=f"原來你是{star}~!")
return [SlotSet('starsign_type', star)]
解決方案四:
我願稱之為,逃避政策
當識别到intent是使用者在詢問星座日期,就可以直接傳回一張圖,讓使用者自行檢視即可。
關鍵點設為entities
一開始我并沒有把戀愛指數,工作指數,幸運色等作為關鍵詞,而是普通的語料資訊。修改後能夠更好的提取到關鍵資訊。
### 修改前
- intent: ask_horoscope
examples: |
- [白羊座](starsign_type)的愛情運勢
- [金牛座](starsign_type)的愛情
- 我的桃花運咋樣
- 我的戀愛指數
### 修改後
- intent: ask_horoscope
examples: |
- [白羊座](starsign_type)的[愛情運勢](love)
- [金牛座](starsign_type)的[愛情](love)
- 我的[桃花運](love)咋樣
- 我的[戀愛指數](love)
合并intent
合并相似度高的intent,intent變少了,命中正确率也相應提高。反之,細分如果太多,模型不夠那麼好,并不會達到預期效果,容易出現intent識别錯位的情況。
合并path,用slot分流
因為合并了intent,那就意味着出現了公共path,path的一頭是intent,一頭是action,而決定走向哪一個action,就用slot槽位的填補來确定。這裡遵循的原則是盡量走公共path,這也是出于對模型的适應,提高命中率,且用槽位判斷可以更精準一些。
Rasa測試
按照官方文檔https://rasa.com/docs/rasa/testing-your-assistant進行測試。
将nlu按照4:1切分成訓練集和測試集進行交叉驗證。
- 檢視Data and Stories是否有效
- 切分nlu
- test nlu
- 檢視intent/slot混淆矩陣
由于這樣的測試資料集都來自寫好的nlu,是以還是需要在互動頁面中人工測試。