NLP初學-簡易聊天機器人

 鏈接:https://pan.baidu.com/s/1ZiMzKulcsEt2xo_a2XK1nw 
提取碼:9umt

import pandas as pd
import fool
import re
import random
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression

# -----------------------------------------------------
# 加載停用詞詞典
stopwords = {}
with open(r'stopword.txt', 'r', encoding='utf-8') as fr:
    for word in fr:
        stopwords[word.strip()] = 0


# -----------------------------------------------------


# 定義類
class clf_model:
    """
    該類將所有模型訓練、預測、數據預處理、意圖識別的函數包括其中
    """

    # 初始化模塊
    def __init__(self):
        self.model = ""  # 成員變量,用於存儲模型
        self.vectorizer = ""  # 成員變量,用於存儲tfidf統計值

    # 訓練模塊
    def train(self):
        """
        訓練結果存儲在成員變量中,沒有return
        """
        # 從excel文件讀取訓練樣本
        d_train = pd.read_excel("data_train.xlsx")
        # 對訓練數據進行預處理
        d_train.sentence_train = d_train.sentence_train.apply(self.fun_clean)
        print("訓練樣本 = %d" % len(d_train))
        # 利用sklearn中的函數進行tifidf訓練
        self.vectorizer = TfidfVectorizer(analyzer="word",
                                          token_pattern=r"(?u)\b\w+\b")  # 注意,這裏自己指定token_pattern,否則sklearn會自動將一個字長度的單詞過濾篩除
        features = self.vectorizer.fit_transform(d_train.sentence_train)
        print("訓練樣本特徵表長度爲 " + str(features.shape))
        # 使用邏輯迴歸進行訓練和預測
        self.model = LogisticRegression(C=10)
        self.model.fit(features, d_train.label)

    # 預測模塊(使用模型預測)
    def predict_model(self, sentence):
        # --------------
        # 對樣本中沒有點特殊情況做特別判斷
        if sentence in ["好的", "需要", "是的", "要的", "好", "要", "是"]:
            return 1, 0.8
        # --------------

        sent_features = self.vectorizer.transform([sentence])
        pre_test = self.model.predict_proba(sent_features).tolist()[0]
        clf_result = pre_test.index(max(pre_test))
        score = max(pre_test)
        return clf_result, score

    # 預測模塊(使用規則)
    def predict_rule(self, sentence):
        """
        如果模型訓練出現異常,可以使用規則進行預測,同時也可以讓學員融合"模型"及"規則"的預測方式
        :param sentence:
        :return 預測結果:
        """
        sentence = sentence.replace(' ', '')
        if re.findall(r'不需要|不要|停止|終止|退出|不買|不定|不訂', sentence):
            return 2, 0.8
        elif re.findall(r'訂|定|預定|買|購', sentence) or sentence in ["好的", "需要", "是的", "要的", "好", "要", "是"]:
            return 1, 0.8
        else:
            return 0, 0.8

    # 預處理函數
    def fun_clean(self, sentence):
        """
        預處理函數
        :輸入 用戶輸入語句:
        :輸出 預處理結果:
        """
        # 使用foolnltk進行實體識別
        words, ners = fool.analysis(sentence)
        # 對識別結果按長度倒序排序
        ners = ners[0].sort(key=lambda x: len(x[-1]), reverse=True)
        # 如果有實體被識別出來,就將實體的字符串替換成實體類別的字符串(目的是看成一類單詞,看成一種共同的特徵)
        if ners:
            for ner in ners:
                sentence = sentence.replace(ner[-1], ' ' + ner[2] + ' ')
        # 分詞,並去除停用詞
        word_lst = [w for w in fool.cut(sentence)[0] if w not in stopwords]
        output_str = ' '.join(word_lst)
        output_str = re.sub(r'\s+', ' ', output_str)
        return output_str.strip()

    # 分類主函數
    def fun_clf(self, sentence):
        """
        意圖識別函數
        :輸入 用戶輸入語句:
        :輸出 意圖類別,分數:
        """
        # 對用戶輸入進行預處理
        sentence = self.fun_clean(sentence)
        # 得到意圖分類結果(0爲“查詢”類別,1爲“訂票”類別,2爲“終止服務”類別)
        clf_result, score = self.predict_model(sentence)  # 使用訓練的模型進行意圖預測
        # clf_result, score = self.predict_rule(sentence)  # 使用規則進行意圖預測(可與用模型進行意圖識別的方法二選一)
        return clf_result, score


def fun_replace_num(sentence):
    """
    替換時間中的數字(目的是便於實體識別包fool對實體的識別)
    :param sentence:
    :return sentence:
    """
    # 定義要替換的數字
    time_num = {"一": "1", "二": "2", "三": "3", "四": "4", "五": "5", "六": "6", "七": "7", "八": "8", "九": "9", "十": "10",
                "十一": "11", "十二": "12"}
    for k, v in time_num.items():
        sentence = sentence.replace(k, v)
    return sentence


def slot_fill(sentence, key=None):
    """
    填槽函數(該函數從sentence中尋找需要的內容,完成填槽工作)
    :param sentence:
    :return slot: (填槽的結果)
    """
    slot = {}
    # 進行實體識別
    words, ners = fool.analysis(sentence)
    to_city_flag = 0  # flag爲1代表找到到達城市(作用:當找到到達城市時,默認句子中另一個城市信息是出發城市)
    for ner in ners[0]:
        # 首先對time類別的實體進行信息抽取填槽工作
        if ner[2] == 'time':
            # --------------------
            # 尋找日期的關鍵詞
            date_content = re.findall(
                r'後天|明天|今天|大後天|週末|週一|週二|週三|週四|週五|週六|週日|本週一|本週二|本週三|本週四|本週五|本週六|本週日|下週一|下週二|下週三|下週四|下週五|下週六|下週日|這週一|這週二|這週三|這週四|這週五|這週六|這週日|\d{,2}月\d{,2}號|\d{,2}月\d{,2}日',
                ner[-1])
            slot["date"] = date_content[0] if date_content else ""
            # 完成日期的填槽
            # --------------------

            # --------------------
            # 尋找具體時間的關鍵詞
            time_content = re.findall(r'\d{,2}點\d{,2}分|\d{,2}點鐘|\d{,2}點', ner[-1])
            # 尋找上午下午的關鍵詞
            pmam_content = re.findall(r'上午|下午|早上|晚上|中午|早晨', ner[-1])
            slot["time"] = pmam_content[0] if pmam_content else "" + time_content[0] if time_content else ""
            # 完成時間的填槽
            # --------------------
        # 對location類別對實體進行信息抽取填槽工作
        if ner[2] == 'location':
            # --------------------
            # 開始對城市填槽
            # 如果沒有指定槽位
            if key is None:
                if re.findall(r'(到|去|回|回去)%s' % (ner[-1]), sentence):
                    to_city_flag = 1
                    slot["to_city"] = ner[-1]
                    continue
                if re.findall(r'從%s|%s出發' % (ner[-1], ner[-1]), sentence):
                    slot["from_city"] = ner[-1]
                elif to_city_flag == 1:
                    slot["from_city"] = ner[-1]
            # 如果指定了槽位
            elif key in ["from_city", "to_city"]:
                slot[key] = ner[-1]
            # 完成出發城市、到達城市的填槽工作
            # --------------------

    return slot


def fun_wait(clf_obj):
    """
    等待詢問函數
    :輸入 None:
    :輸出 用戶意圖類別:
    """
    # 等待用戶輸入
    print("\n\n\n")
    print("-------------------------------------------------------------")
    print("-------------------------------------------------------------")
    print("Starting ...")
    sentence = input("客服:請問需要什麼服務?(時間請用12小時製表示)\n")
    # 對用戶輸入進行意圖識別
    clf_result, score = clf_obj.fun_clf(sentence)
    return clf_result, score, sentence


def fun_search(clf_result, sentence):
    """
    爲用戶查詢餘票
    :param clf_result:
    :param sentence:
    :return: 是否有票
    """
    # 定義槽存儲空間
    name = {"time": "出發時間", "date": "出發日期", "from_city": "出發城市", "to_city": "到達城市"}
    slot = {"time": "", "date": "", "from_city": "", "to_city": ""}
    # 使用用戶第一句話進行填槽
    sentence = fun_replace_num(sentence)
    slot_init = slot_fill(sentence)
    for key in slot_init.keys():
        slot[key] = slot_init[key]
    # 對未填充對槽位,向用戶提問,進行鍼對性填槽
    while "" in slot.values():
        for key in slot.keys():
            if slot[key] == "":
                sentence = input("客服:請問%s是?\n" % (name[key]))
                sentence = fun_replace_num(sentence)
                slot_cur = slot_fill(sentence, key)
                for key in slot_cur.keys():
                    if slot[key] == "":
                        slot[key] = slot_cur[key]

    # 查詢是否有票,並答覆用戶(本次查詢是否有票使用隨機數完成)
    if random.random() > 0.5:
        print("客服:%s%s從%s到%s的票充足" % (slot["date"], slot["time"], slot["from_city"], slot["to_city"]))
        # 返回1表示有票
        return 1
    else:
        print("客服:%s%s從%s到%s無票" % (slot["date"], slot["time"], slot["from_city"], slot["to_city"]))
        print("End !!!")
        print("-------------------------------------------------------------")
        print("-------------------------------------------------------------")
        # 返回0表示無票
        return 0


def fun_book():
    """
    爲用戶訂票
    """
    print("客服:已爲您完成訂票。\n\n\n")
    print("End !!!")
    print("-------------------------------------------------------------")
    print("-------------------------------------------------------------")


if __name__ == "__main__":
    # 實例化對象
    clf_obj = clf_model()
    clf_obj.train()
    threshold = 0.55  # 用戶定義閾值(當分類器分類的分數大於閾值才採納本次意圖分類結果,目的是排除分數過低的意圖分類結果)
    while 1:
        clf_result, score, sentence = fun_wait(clf_obj)
        # -------------------------------------------------------------------------------
        # 狀態轉移條件(等待-->等待):用戶輸入未達到“查詢”、“訂票”類別的閾值 OR 被分類爲“終止服務”
        # -------------------------------------------------------------------------------
        if score < threshold or clf_result == 2:
            continue

        # -------------------------------------------------------------------------------
        # 狀態轉移條件(等待-->查詢):用戶輸入分類爲“查詢” OR “訂票”
        # -------------------------------------------------------------------------------
        else:
            search_result = fun_search(clf_result, sentence)
            if search_result == 0:
                continue
            else:
                # 等待用戶輸入
                sentence = input("客服:需要爲您訂票嗎?\n")
                # 對用戶輸入進行意圖識別
                clf_result, score = clf_obj.fun_clf(sentence)
                # -------------------------------------------------------------------------------
                # 狀態轉移條件(查詢-->訂票):FUN_SEARCH返回有票 AND 用戶輸入分類爲“訂票”
                # -------------------------------------------------------------------------------
                if clf_result == 1:
                    fun_book()
                    continue

運行結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章