Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
提交按鈕用來(lái)將輸入的信息提交到服務(wù)器。代碼格式如下:
<input type="submit" name="..." value="...">
其中type="submit"定義提交按鈕;name屬性定義提交按鈕的名稱;value屬性定義按鈕的顯示文字。通過(guò)提交按鈕,可以將表單的信息提交給表單里action所指向的文件。
(1)編寫代碼如下圖所示,在<body>標(biāo)簽中加入以下代碼。
(2)在瀏覽器中打開文件,預(yù)覽效果圖如下所示,輸入內(nèi)容按【提交】按鈕,即可將表單中的數(shù)據(jù)發(fā)送到指定的文件。
復(fù)位按鈕用來(lái)重置表單中輸入的信息。代碼格式如下:
<input type="reset" name="..." value="..." >
其中type="reset"定義復(fù)位按鈕;name屬性定義復(fù)位按鈕的名稱;value屬性定義按鈕的顯示文字。
(1)編寫代碼如下圖所示,在<body>標(biāo)簽中加入以下代碼。
(2)在瀏覽器中打開文件,預(yù)覽效果圖如下所示,輸入內(nèi)容后單機(jī)【重置】按鈕,即可將表單中的數(shù)據(jù)清空。
者 | 無(wú)名之輩FTER
責(zé)編 | 夕顏
出品 | 程序人生(ID:coder_life)
本文翻譯自Rasa官方文檔,并融合了自己的理解和項(xiàng)目實(shí)戰(zhàn),同時(shí)對(duì)文檔中涉及到的技術(shù)點(diǎn)進(jìn)行了一定程度的擴(kuò)展,目的是為了更好的理解Rasa工作機(jī)制。與本文配套的項(xiàng)目GitHub地址:ChitChatAssistant https://github.com/jiangdongguo/ChitChatAssistant,歡迎star和issues,我們共同討論、學(xué)習(xí)!
對(duì)話管理
1.1 多輪對(duì)話
多輪對(duì)話是相對(duì)于單輪對(duì)話而言的,單輪對(duì)話側(cè)重于一問(wèn)一答,即直接根據(jù)用戶的問(wèn)題給出精準(zhǔn)的答案。問(wèn)答更接近一個(gè)信息檢索的過(guò)程,雖然也可能涉及簡(jiǎn)單的上下文處理,但通常是通過(guò)指代消解和 query 補(bǔ)全來(lái)完成的,而多輪對(duì)話側(cè)重于需要維護(hù)一個(gè)用戶目標(biāo)狀態(tài)的表示和一個(gè)決策過(guò)程來(lái)完成任務(wù),具體來(lái)說(shuō)就是用戶帶著明確的目的而來(lái),希望得到滿足特定限制條件的信息或服務(wù),例如:訂餐,訂票,尋找音樂(lè)、電影或某種商品等。因?yàn)橛脩舻男枨罂梢员容^復(fù)雜,可能需要分多輪進(jìn)行陳述,用戶也可能在對(duì)話過(guò)程中不斷修改或完善自己的需求。此外,當(dāng)用戶的陳述的需求不夠具體或明確的時(shí)候,機(jī)器也可以通過(guò)詢問(wèn)、澄清或確認(rèn)來(lái)幫助用戶找到滿意的結(jié)果。
因此,任務(wù)驅(qū)動(dòng)的多輪對(duì)話不是一個(gè)簡(jiǎn)單的自然語(yǔ)言理解加信息檢索的過(guò)程,而是一個(gè)決策過(guò)程,需要機(jī)器在對(duì)話過(guò)程中不斷根據(jù)當(dāng)前的狀態(tài)決策下一步應(yīng)該采取的最優(yōu)動(dòng)作(如:提供結(jié)果,詢問(wèn)特定限制條件,澄清或確認(rèn)需求,等等)從而最有效的輔助用戶完成信息或服務(wù)獲取的任務(wù)。
注:任務(wù)驅(qū)動(dòng)的多輪對(duì)話系統(tǒng)通常為封閉域(domain)(閑聊系統(tǒng)為開放域),而特定限制條件對(duì)應(yīng)于槽(Slot),也就是說(shuō),用戶在滿足特定限制條件時(shí)就是一次槽值填充的過(guò)程,如果用戶能夠在一次會(huì)話中,滿足全部的限制條件,那么就不必進(jìn)行多輪對(duì)話,即可直接使用戶得到滿意的信息或服務(wù)。
1.2 對(duì)話管理
對(duì)話管理,即Dialog Management(DM),它控制著人機(jī)對(duì)話的過(guò)程,是人機(jī)對(duì)話系統(tǒng)的重要組成部分。DM會(huì)根據(jù)NLU模塊輸出的語(yǔ)義表示執(zhí)行對(duì)話狀態(tài)的更新和追蹤,并根據(jù)一定策略選擇相應(yīng)的候選動(dòng)作。簡(jiǎn)單來(lái)說(shuō),就是DM會(huì)根據(jù)對(duì)話歷史信息,決定此刻對(duì)用戶的反應(yīng),比如在任務(wù)驅(qū)動(dòng)的多輪對(duì)話系統(tǒng)中,用戶帶著明確的目的如訂餐、訂票等,用戶需求比較復(fù)雜,有很多限制條件,可能需要分多輪進(jìn)行陳述,一方面,用戶在對(duì)話過(guò)程中可以不斷修改或完善自己的需求,另一方面,當(dāng)用戶的陳述的需求不夠具體或明確的時(shí)候,機(jī)器也可以通過(guò)詢問(wèn)、澄清或確認(rèn)來(lái)幫助用戶找到滿意的結(jié)果。如下圖所示,DM 的輸入就是用戶輸入的語(yǔ)義表達(dá)(或者說(shuō)是用戶行為,是 NLU 的輸出)和當(dāng)前對(duì)話狀態(tài),輸出就是下一步的系統(tǒng)行為和更新的對(duì)話狀態(tài)。這是一個(gè)循環(huán)往復(fù)不斷流轉(zhuǎn)直至完成任務(wù)的過(guò)程。
從本質(zhì)上來(lái)說(shuō),**任務(wù)驅(qū)動(dòng)的對(duì)話管理實(shí)際就是一個(gè)決策過(guò)程,系統(tǒng)在對(duì)話過(guò)程中不斷根據(jù)當(dāng)前狀態(tài)決定下一步應(yīng)該采取的最優(yōu)動(dòng)作(如:提供結(jié)果,詢問(wèn)特定限制條件,澄清或確認(rèn)需求等),從而最有效的輔助用戶完成信息或服務(wù)獲取的任務(wù)。**對(duì)話管理的任務(wù)大致有:
對(duì)話狀態(tài)維護(hù)(dialog state tracking, DST)
對(duì)話狀態(tài)是指記錄了哪些槽位已經(jīng)被填充、下一步該做什么、填充什么槽位,還是進(jìn)行何種操作。用數(shù)學(xué)形式表達(dá)為,t+1 時(shí)刻的對(duì)話狀態(tài)S(t+1),依賴于之前時(shí)刻 t 的狀態(tài)St,和之前時(shí)刻 t 的系統(tǒng)行為At,以及當(dāng)前時(shí)刻 t+1 對(duì)應(yīng)的用戶行為O(t+1)??梢詫懗蒘(t+1)←St+At+O(t+1)。
生成系統(tǒng)決策(dialog policy)
根據(jù) DST 中的對(duì)話狀態(tài)(DS),產(chǎn)生系統(tǒng)行為(dialog act),決定下一步做什么 dialog act 可以表示觀測(cè)到的用戶輸入(用戶輸入 -> DA,就是 NLU 的過(guò)程),以及系統(tǒng)的反饋行為(DA -> 系統(tǒng)反饋,就是 NLG 的過(guò)程)。
作為接口與后端/任務(wù)模型進(jìn)行交互
Rasa Core
Rasa Core是Rasa框架提供的對(duì)話管理模塊,它類似于聊天機(jī)器人的大腦,主要的任務(wù)是維護(hù)更新對(duì)話狀態(tài)和動(dòng)作選擇,然后對(duì)用戶的輸入作出響應(yīng)。所謂對(duì)話狀態(tài)是一種機(jī)器能夠處理的對(duì)聊天數(shù)據(jù)的表征,對(duì)話狀態(tài)中包含所有可能會(huì)影響下一步?jīng)Q策的信息,如自然語(yǔ)言理解模塊的輸出、用戶的特征等;所謂動(dòng)作選擇,是指基于當(dāng)前的對(duì)話狀態(tài),選擇接下來(lái)合適的動(dòng)作,例如向用戶追問(wèn)需補(bǔ)充的信息、執(zhí)行用戶要求的動(dòng)作等。舉一個(gè)具體的例子,用戶說(shuō)“幫我媽媽預(yù)定一束花”,此時(shí)對(duì)話狀態(tài)包括自然語(yǔ)言理解模塊的輸出、用戶的位置、歷史行為等特征。在這個(gè)狀態(tài)下,系統(tǒng)接下來(lái)的動(dòng)作可能是:
向用戶詢問(wèn)可接受的價(jià)格,如“請(qǐng)問(wèn)預(yù)期價(jià)位是多少?”;
向用戶確認(rèn)可接受的價(jià)格,如“像上次一樣買價(jià)值200的花可以嗎?”
直接為用戶預(yù)訂
2.1 Stories
Rasa的故事是一種訓(xùn)練數(shù)據(jù)的形式,用來(lái)訓(xùn)練Rasa的對(duì)話管理模型。故事是用戶和人工智能助手之間的對(duì)話的表示,轉(zhuǎn)換為特定的格式,其中用戶輸入表示為相應(yīng)的意圖(和必要的實(shí)體),而助手的響應(yīng)表示為相應(yīng)的操作名稱。Rasa核心對(duì)話系統(tǒng)的一個(gè)訓(xùn)練示例稱為一個(gè)故事。這是一個(gè)故事數(shù)據(jù)格式的指南。兩段對(duì)話樣本示例:
<!-- ##表示story的描述,沒(méi)有實(shí)際作用 -->
## greet + location/price + cuisine + num people
* greet
- utter_greet
* inform{"location": "rome", "price": "cheap"}
- action_on_it
- action_ask_cuisine
* inform{"cuisine": "spanish"}
- action_ask_numpeople
* inform{"people": "six"}
- action_ack_dosearch
<!-- Form Action-->
## happy path
* request_weather
- weather_form
- form{"name": "weather_form"}
- form{"name": }
Story格式大致包含三個(gè)部分:
1. 用戶輸入(User Messages)
使用*開頭的語(yǔ)句表示用戶的輸入消息,我們無(wú)需使用包含某個(gè)具體內(nèi)容的輸入,而是使用NLU管道輸出的intent和entities來(lái)表示可能的輸入。需要注意的是,如果用戶的輸入可能包含entities,建議將其包括在內(nèi),將有助于policies預(yù)測(cè)下一步action。這部分大致包含三種形式,示例如下:
(1)* greet 表示用戶輸入沒(méi)有entity情況;
(2)* inform{"people": "six"} 表示用戶輸入包含entity情況,響應(yīng)這類intent為普通action;
(3)* request_weather 表示用戶輸入Message對(duì)應(yīng)的intent為form action情況;
2. 動(dòng)作(Actions)
使用-開頭的語(yǔ)句表示要執(zhí)行動(dòng)作(Action),可分為utterance actions和custom actions,其中,前者在domain.yaml中定義以u(píng)tter_為前綴,比如名為greet的意圖,它的回復(fù)應(yīng)為utter_greet;后者為自定義動(dòng)作,具體邏輯由我們自己實(shí)現(xiàn),雖然在定義action名稱的時(shí)候沒(méi)有限制,但是還是建議以action_為前綴,比如名為inform的意圖fetch_profile的意圖,它的response可為action_fetch_profile。
3. 事件(Events)
Events也是使用-開頭,主要包含槽值設(shè)置(SlotSet)和激活/注銷表單(Form),它是是Story的一部分,并且必須顯示的寫出來(lái)。Slot Events和Form Events的作用如下:
(1)Slot Events
Slot Events的作用當(dāng)我們?cè)谧远xAction中設(shè)置了某個(gè)槽值,那么我們就需要在Story中Action執(zhí)行之后顯著的將這個(gè)SlotSet事件標(biāo)注出來(lái),格式為- slot{"slot_name": "value"}。比如,我們?cè)赼ction_fetch_profile中設(shè)置了Slot名為account_type的值,代碼如下:
from rasa_sdk.actions import Action
from rasa_sdk.events import SlotSet
import requests
class FetchProfileAction(Action):
def name(self):
return "fetch_profile"
def run(self, dispatcher, tracker, domain):
url = "http://myprofileurl.com"
data = requests.get(url).json
return [SlotSet("account_type", data["account_type"])]
那么,就需要在Story中執(zhí)行action_fetch_profile之后,添加- slot{"account_type" : "premium"}。雖然,這么做看起來(lái)有點(diǎn)多余,但是Rasa規(guī)定這么做必須的,目的是提高訓(xùn)練時(shí)準(zhǔn)確度。
## fetch_profile
* fetch_profile
- action_fetch_profile
- slot{"account_type" : "premium"}
- utter_welcome_premium
當(dāng)然,如果您的自定義Action中將槽值重置為None,則對(duì)應(yīng)的事件為-slot{"slot_name": }。
(2)Form Events
在Story中主要存在三種形式的表單事件(Form Events),它們可表述為:
Form Action事件
Form Action即表單動(dòng)作事件,是自定義Action的一種,用于一個(gè)表單操作。示例如下:
- restaurant_form
Form activation事件
form activation即激活表單事件,當(dāng)form action事件執(zhí)行后,會(huì)立馬執(zhí)行該事件。示例如下:
- form{"name": "restaurant_form"}
Form deactivation事件
form deactivation即注銷表單事件,作用與form activation相反。示例如下:
- form{"name": }
總之,我們?cè)跇?gòu)建Story時(shí),可以說(shuō)是多種多樣的,因?yàn)樵O(shè)計(jì)的故事情節(jié)是多種多樣的,這就意味著上述三種內(nèi)容的組合也是非常靈活的。另外,在設(shè)計(jì)Story時(shí)Rasa還提供了Checkpoints 和OR statements兩種功能,來(lái)提升構(gòu)建Story的靈活度,但是需要注意的是,東西雖好,但是不要太貪了,過(guò)多的使用不僅增加了復(fù)雜度,同時(shí)也會(huì)拖慢訓(xùn)練的速度。其中,Checkpoints用于模塊化和簡(jiǎn)化訓(xùn)練數(shù)據(jù),示例如下:
## first story
* greet
- action_ask_user_question
> check_asked_question
## user affirms question
> check_asked_question
* affirm
- action_handle_affirmation
> check_handled_affirmation
## user denies question
> check_asked_question
* deny
- action_handle_denial
> check_handled_denial
## user leaves
> check_handled_denial
> check_handled_affirmation
* goodbye
- utter_goodbye
在上面的例子中,可以使用> check_asked_question表示first story,這樣在其他story中,如果有相同的first story部分,可以直接用> check_asked_question代替。而OR Statements主要用于實(shí)現(xiàn)某一個(gè)action可同時(shí)響應(yīng)多個(gè)意圖的情況,比如下面的例子:
## story
* affirm OR thankyou
- action_handle_affirmation
2.2 Domain
Domain,譯為**“領(lǐng)域”**,它描述了對(duì)話機(jī)器人應(yīng)知道的所有信息,類似于“人的大腦”,存儲(chǔ)了意圖intents、實(shí)體entities、插槽slots以及動(dòng)作actions等信息,其中,intents、entities在NLU訓(xùn)練樣本中定義,slots對(duì)應(yīng)于entities類型,只是表現(xiàn)形式不同。domain.yml文件組成結(jié)構(gòu)如下:
具體介紹如下:
1. intents
intents:
- affirm
- deny
- greet
- request_weather
- request_number
- inform
- inform_business
- stop
- chitchat
intents,即意圖,是指我們輸入一段文本,希望Bot能夠明白這段文本是什么意思。在Rasa框架中,意圖的定義是在NLU樣本中實(shí)現(xiàn)的,并且在每個(gè)意圖下面我們需要枚舉盡可多的樣本用于訓(xùn)練,以達(dá)到Bot能夠準(zhǔn)確識(shí)別出我們輸入的一句話到底想要干什么。
2. session_config
session_config:
carry_over_slots_to_new_session: true
session_expiration_time: 60
session_config,即會(huì)話配置,這部分的作用為配置一次會(huì)話(conversation session)是否有超時(shí)限制。上例演示的是,每次會(huì)話的超時(shí)時(shí)間為60s,如果用戶開始一段會(huì)話后,在60s內(nèi)沒(méi)有輸入任何信息,那么這次會(huì)話將被結(jié)束,然后Bot又會(huì)開啟一次新的會(huì)話,并將上一次會(huì)話的Slot值拷貝過(guò)來(lái)。當(dāng)然,我們希望舍棄上一次會(huì)話Slot的值,可以將carry_over_slots_to_new_session設(shè)置為false。另外,當(dāng)session_expiration_time被設(shè)置為0時(shí),Bot永遠(yuǎn)不會(huì)結(jié)束當(dāng)前會(huì)話并一直等待用戶輸入(注:執(zhí)行action_session_start仍然可以開始一次新的會(huì)話,在設(shè)置為0的情況下)。
3. slots
slots:
date_time:
type: unfeaturized
auto_fill: false
address:
type: unfeaturized
auto_fill: false
Slots,即插槽,它就像對(duì)話機(jī)器人的內(nèi)存,它通過(guò)鍵值對(duì)的形式可用來(lái)收集存儲(chǔ)用戶輸入的信息(實(shí)體)或者查詢數(shù)據(jù)庫(kù)的數(shù)據(jù)等。關(guān)于Slots的設(shè)計(jì)與使用,詳情請(qǐng)見(jiàn)本文2.6小節(jié)。
4. entities
entities:
- date_time
- address
entities,即實(shí)體,類似于輸入文本中的關(guān)鍵字,需要在NLU樣本中進(jìn)行標(biāo)注,然后Bot進(jìn)行實(shí)體識(shí)別,并將其填充到Slot槽中,便于后續(xù)進(jìn)行相關(guān)的業(yè)務(wù)操作。
5. actions
actions:
- utter_answer_affirm # utter_開頭的均為utter actions
- utter_answer_deny
- utter_answer_greet
- utter_answer_goodbye
- utter_answer_thanks
- utter_answer_whoareyou
- utter_answer_whattodo
- utter_ask_date_time
- utter_ask_address
- utter_ask_number
- utter_ask_business
- utter_ask_type
- action_default_fallback # default actions
當(dāng)Rasa NLU識(shí)別到用戶輸入Message的意圖后,Rasa Core對(duì)話管理模塊就會(huì)對(duì)其作出回應(yīng),而完成這個(gè)回應(yīng)的模塊就是action。Rasa Core支持三種action,即default actions、utter actions以及 custom actions。關(guān)于如何實(shí)現(xiàn)Actions和處理業(yè)務(wù)邏輯,我們?cè)谝黄恼轮性斦劊@里僅作簡(jiǎn)單了解。
6. forms
forms:
- weather_form
forms,即表單,該部分列舉了在NLU樣本中定義了哪些Form Actions。關(guān)于Form Actions的相關(guān)知識(shí),請(qǐng)移步至本文的2.7小節(jié)。
7. responses
responses:
utter_answer_greet:
- text: "您好!請(qǐng)問(wèn)我可以幫到您嗎?"
- text: "您好!很高興為您服務(wù)。請(qǐng)說(shuō)出您要查詢的功能?"
utter_ask_date_time:
- text: "請(qǐng)問(wèn)您要查詢哪一天的天氣?"
utter_ask_address:
- text: "請(qǐng)問(wèn)您要查下哪里的天氣?"
utter_default:
- text: "沒(méi)聽懂,請(qǐng)換種說(shuō)法吧~"
responses部分就是描述UtterActions具體的回復(fù)內(nèi)容,并且每個(gè)UtterAction下可以定義多條信息,當(dāng)用戶發(fā)起一個(gè)意圖,比如 “你好!”,就觸發(fā)utter_answer_greet操作,Rasa Core會(huì)從該action的模板中自動(dòng)選擇其中的一條信息作為結(jié)果反饋給用戶。
2.3 Responses
Responses的作用就是自動(dòng)響應(yīng)用戶輸入的信息,因此我們需要管理這些響應(yīng)(Responses)。Rasa框架提供了三種方式來(lái)管理Responses,它們是:
在domain.yaml文件中存儲(chǔ)Responses;
在訓(xùn)練數(shù)據(jù)中存儲(chǔ)Responses;
自定義一個(gè)NLG服務(wù)來(lái)生成Responses。
由于第一種我們?cè)诒疚?.2(7)小節(jié)有過(guò)介紹,而創(chuàng)建NLG服務(wù)是這樣的:
nlg:
url: http://localhost:5055/nlg # url of the nlg endpoint
# you can also specify additional parameters, if you need them:
# headers:
# my-custom-header: value
# token: "my_authentication_token" # will be passed as a get parameter
# basic_auth:
# username: user
# password: pass
# example of redis external tracker store config
tracker_store:
type: redis
url: localhost
port: 6379
db: 0
password: password
record_exp: 30000
# example of mongoDB external tracker store config
#tracker_store:
#type: mongod
#url: mongodb://localhost:27017
#db: rasa
#user: username
#password: password
2.4 Actions
當(dāng)Rasa NLU識(shí)別到用戶輸入Message的意圖后,Rasa Core對(duì)話管理模塊就會(huì)對(duì)其作出回應(yīng),而完成這個(gè)回應(yīng)的模塊就是action。Rasa Core支持三種action,即default actions、utter actions以及 custom actions。關(guān)于如何實(shí)現(xiàn)Actions和處理業(yè)務(wù)邏輯,我們?cè)谝黄恼轮性斦?,這里僅作簡(jiǎn)單了解。
1. default actions
DefaultAction是Rasa Core默認(rèn)的一組actions,我們無(wú)需定義它們,直接可以story和domain中使用。包括以下三種action:
action_listen:監(jiān)聽action,Rasa Core在會(huì)話過(guò)程中通常會(huì)自動(dòng)調(diào)用該action;
action_restart:重置狀態(tài),比初始化Slots(插槽)的值等;
action_default_fallback:當(dāng)Rasa Core得到的置信度低于設(shè)置的閾值時(shí),默認(rèn)執(zhí)行該action;
2. utter actions
UtterAction是以u(píng)tter_為開頭,僅僅用于向用戶發(fā)送一條消息作為反饋的一類actions。定義一個(gè)UtterAction很簡(jiǎn)單,只需要在domain.yml文件中的actions:字段定義以u(píng)tter_為開頭的action即可,而具體回復(fù)內(nèi)容將被定義在templates:部分,這個(gè)我們下面有專門講解。定義utter actions示例如下:
actions:
- utter_answer_greet
- utter_answer_goodbye
- utter_answer_thanks
- utter_introduce_self
- utter_introduce_selfcando
- utter_introduce_selffrom
3. custom actions
CustomAction,即自定義action,允許開發(fā)者執(zhí)行任何操作并反饋給用戶,比如簡(jiǎn)單的返回一串字符串,或者控制家電、檢查銀行賬戶余額等等。它與DefaultAction不同,自定義action需要我們?cè)赿omain.yml文件中的actions部分先進(jìn)行定義,然后在指定的webserver中實(shí)現(xiàn)它,其中,這個(gè)webserver的url地址在endpoint.yml文件中指定,并且這個(gè)webserver可以通過(guò)任何語(yǔ)言實(shí)現(xiàn),當(dāng)然這里首先推薦python來(lái)做,畢竟Rasa Core為我們封裝好了一個(gè)rasa-core-sdk專門用來(lái)處理自定義action。關(guān)于action web的搭建和action的具體實(shí)現(xiàn),我們?cè)诤竺嬖敿?xì)講解,這里我們看下在在Rasa Core項(xiàng)目中需要做什么。假如我們?cè)谔鞖赓Y訊的人機(jī)對(duì)話系統(tǒng)需提供查詢天氣和空氣質(zhì)量?jī)蓚€(gè)業(yè)務(wù),那么我們就需要在domain.yml文件中定義查詢天氣和空氣質(zhì)量的action,即:
actions:
...
- action_search_weather
另外,F(xiàn)ormAction也是自定義actions,但是需要在domainl.yaml文件的forms字段聲明。
forms:
- weather_form
2.5 Policies
Policies是Rasa Core中的策略模塊,對(duì)應(yīng)類rasa_core.policies.Policy,它的作用就是使用合適的策略(Policy)來(lái)預(yù)測(cè)一次對(duì)話后要執(zhí)行的行為(Actions)。預(yù)測(cè)的原理是衡量命中的哪些Policies哪個(gè)置信度高,由置信度高的Policy選擇合適的Action執(zhí)行。假如出現(xiàn)不同的Policy擁有相同的置信度,那么就由它們的優(yōu)先級(jí)決定,即選擇優(yōu)先級(jí)高的Policy。Rasa對(duì)提供的Policies進(jìn)行了優(yōu)先級(jí)排序,具體如下表:
它們的描述與作用如下:
Memoization Policy
MemoizationPolicy只記住(memorizes)訓(xùn)練數(shù)據(jù)中的對(duì)話。如果訓(xùn)練數(shù)據(jù)中存在這樣的對(duì)話,那么它將以置信度為1.0預(yù)測(cè)下一個(gè)動(dòng)作,否則將預(yù)測(cè)為None,此時(shí)置信度為0.0。下面演示了如何在策略配置文件config.yml文件中,配置MemoizationPlicy策略,其中,max_history(超參數(shù))決定了模型查看多少個(gè)對(duì)話歷史以決定下一個(gè)執(zhí)行的action。
policies:
- name: "MemoizationPolicy"
max_history: 5
注:max_history值越大訓(xùn)練得到的模型就越大并且訓(xùn)練時(shí)間會(huì)變長(zhǎng),關(guān)于該值到底該設(shè)置多少,我們可以舉這么個(gè)例子,比如有這么一個(gè)Intent:out_of_scope來(lái)描述用戶輸入的消息off-topic(離題),當(dāng)用戶連續(xù)三次觸發(fā)out_of_scope意圖,這時(shí)候我們就需要主動(dòng)告知用戶需要向其提供幫助,如果要Rasa Core能夠?qū)W習(xí)這種模型,max_history應(yīng)該至少為3。story.md中表現(xiàn)如下:
* out_of_scope
- utter_default
* out_of_scope
- utter_default
* out_of_scope
- utter_help_message
Keras Policy
KerasPolicy策略是Keras框架中實(shí)現(xiàn)的神經(jīng)網(wǎng)絡(luò)來(lái)預(yù)測(cè)選擇執(zhí)行下一個(gè)action,它默認(rèn)的框架使用LSTM(Long Short-Term Memory,長(zhǎng)短期記憶網(wǎng)絡(luò))算法,但是我們也可以重寫KerasPolicy.model_architecture函數(shù)來(lái)實(shí)現(xiàn)自己的框架(architecture)。KerasPolicy的模型很簡(jiǎn)單,只是單一的LSTM+Dense+softmax,這就需要我們不斷地完善自己的story來(lái)把各種情況下的story進(jìn)行補(bǔ)充。下面演示了如何在策略配置文件config.yml文件中,配置KerasPolicy策略,其中,epochs表示訓(xùn)練的次數(shù),max_history同上。
policies:
- name: KerasPolicy
epochs: 100
max_history: 5
Embedding Policy
基于機(jī)器學(xué)習(xí)的對(duì)話管理能夠?qū)W習(xí)復(fù)雜的行為以完成任務(wù),但是將其功能擴(kuò)展到新領(lǐng)域并不簡(jiǎn)單,尤其是不同策略處理不合作用戶行為的能力,以及在學(xué)習(xí)新任務(wù)(如預(yù)訂酒店)時(shí),如何將完成一項(xiàng)任務(wù)(如餐廳預(yù)訂)重新應(yīng)用于該任務(wù)時(shí)的情況。EmbeddingPolicy,即循環(huán)嵌入式對(duì)話策略(Recurrent Embedding Dialogue Policy,REDP),它通過(guò)將actions和對(duì)話狀態(tài)嵌入到相同的向量空間(vector space)能夠獲得較好的效果,REDP包含一個(gè)基于改進(jìn)的Neural Turing Machine的記憶組件和注意機(jī)制,在該任務(wù)上顯著優(yōu)于基線LSTM分類器。
EmbeddingPolicy效果上優(yōu)于KerasPolicy,但是它有個(gè)問(wèn)題是耗時(shí),因?yàn)樗鼪](méi)有使用GPU、沒(méi)有充分利用CPU資源。KerasPolicy和EmbeddingPolicy比較示意圖如下:
配置EmbeddingPolicy參數(shù):
policies:
- name: EmbeddingPolicy
epochs: 100
featurizer:
- name: FullDialogueTrackerFeaturizer
state_featurizer:
- name: LabelTokenizerSingleStateFeaturizer
注:新版的Rasa將EmbeddingPolicy重命名為TEDPolicy,但是我在config.yml配置文件中將其替換后,提示無(wú)法找到TEDPolicy異常,具體原因不明,暫還未涉及源碼分析。
Form Policy
FormPolicy是MemoizationPolicy的擴(kuò)展,用于處理(form)表單的填充事項(xiàng)。當(dāng)一個(gè)FormAction被調(diào)用時(shí),F(xiàn)ormPolicy將持續(xù)預(yù)測(cè)表單動(dòng)作,直到表單中的所有槽都被填滿,然后再執(zhí)行對(duì)應(yīng)的FormAction。如果在Bot系統(tǒng)中使用了FormActions,就需要在config.yml配置文件中進(jìn)行配置。
policies:
- name: FormPolicy
Mapping Policy
MappingPolicy可用于直接將意圖映射到要執(zhí)行的action,從而實(shí)現(xiàn)被映射的action總會(huì)被執(zhí)行,其中,這種映射是通過(guò)triggers屬性實(shí)現(xiàn)的。舉個(gè)栗子(domain.yml文件中):
intents:
- greet: {triggers: utter_goodbye}
其中,greet是意圖;utter_goodbye是action。一個(gè)意圖最多只能映射到一個(gè)action,我們的機(jī)器人一旦收到映射意圖的消息,它將執(zhí)行對(duì)應(yīng)的action。然后,繼續(xù)監(jiān)聽下一條message。需要注意的是,對(duì)于上述映射,我們還需要要在story.md文件中添加如下樣本,否則,任何機(jī)器學(xué)習(xí)策略都可能被預(yù)測(cè)的action_greet在dialouge歷史中突然出現(xiàn)而混淆。
Fallback Policy
如果意圖識(shí)別的置信度低于nlu_threshold,或者沒(méi)有任何對(duì)話策略預(yù)測(cè)的action置信度高于core_threshold,F(xiàn)allbackPolicy將執(zhí)行fallback action。通俗來(lái)說(shuō),就是我們的對(duì)話機(jī)器人意圖識(shí)別和action預(yù)測(cè)的置信度沒(méi)有滿足對(duì)應(yīng)的閾值,該策略將使機(jī)器人執(zhí)行指定的默認(rèn)action。configs.yml配置如下:
policies:
- name: "FallbackPolicy"
# 意圖理解置信度閾值
nlu_threshold: 0.3
# action預(yù)測(cè)置信度閾值
core_threshold: 0.3
# fallback action
fallback_action_name: 'action_default_fallback'
其中,action_default_fallback是Rasa Core中的一個(gè)默認(rèn)操作,它將向用戶發(fā)送utter_default模板消息,因此我們需要確保在domain.yml文件中指定此模板。當(dāng)然,我們也可以在fallback_action_name字段自定義默認(rèn)回復(fù)的action,比如my_fallback_cation,就可以這么改:
policies:
- name: "FallbackPolicy"
nlu_threshold: 0.4
core_threshold: 0.3
fallback_action_name: "my_fallback_action"
2.6 Slots
Slots,槽值,相當(dāng)于機(jī)器人的內(nèi)存(memory),它們以鍵值對(duì)的形式存在,用于存儲(chǔ)用戶輸入時(shí)消息時(shí)比較重要的信息,而這些信息將為Action的執(zhí)行提供關(guān)鍵數(shù)據(jù)。Slots的定義位于domain.yaml文件中,它們通常與Entities相對(duì)應(yīng),即Entities有哪些,Slots就有哪些,并且Slots存儲(chǔ)的值就是NLU模型提取的Entities的值。
2.6.1 Slots Type
1. Text類型
示例:
# domain.yaml
slots:
cuisine:
type: text
2. Boolean類型
示例:
slots:
is_authenticated:
type: bool
3. categorical類型
示例:
slots:
risk_level:
type: categorical
values:
- low
- medium
- high
4. Float類型
示例:
slots:
temperature:
type: float
min_value: -100.0
max_value: 100.0
5. List類型
示例:
slots:
shopping_items:
type: list
6. Unfeaturized 類型
示例:
slots:
internal_user_id:
type: unfeaturized
2.6.2 Slots Set
Slots值填充有多種方式,它們的操作方式如下:
1. Slots Initial
# domain.yaml
slots:
name:
type: text
initial_value: "human"
在domain.yaml文件中聲明slots時(shí),可以通過(guò)initial_value字段為當(dāng)前slot提供一個(gè)初始值,也就是說(shuō),當(dāng)會(huì)話開始時(shí),被設(shè)定初始值的slot已經(jīng)被填充好。當(dāng)然,這個(gè)操作不是必須的。
2. Slots Set from NLU
# stories.md
# story_01
* greet{"name": "Ali"}
- slot{"name": "Ali"}
- utter_greet
假如在stories.md文件添加一個(gè)包含-slot{}的story,這就意味著當(dāng)NLU模型提取到一個(gè)名為name的實(shí)體且這個(gè)實(shí)體有在domain.yaml中定義,那么NLU模型提取到的實(shí)體值會(huì)被自動(dòng)填充到name槽中。實(shí)際上,對(duì)于Rasa來(lái)說(shuō),就算你不添加-slot{}字段,這個(gè)實(shí)體值也會(huì)被提取并自動(dòng)填充到name槽中。當(dāng)然,如果你希望禁止這種自動(dòng)填充行為,改為添加-slot{}字段填充,可以在domain.yaml定義slot時(shí),設(shè)置auto_fill的值為False,即:
# domain.yaml
slots:
name:
type: text
auto_fill: False
3. Slots Set By Clicking Buttons
# domain.yaml
utter_ask_color:
- text: "what color would you like?"
buttons:
- title: "blue"
payload: '/choose{"color": "blue"}' # 格式 '/intent{"entity":"value",...}'
- title: "red"
payload: '/choose{"color": "red"}'
在點(diǎn)擊Button時(shí)填充Slots的值,是指當(dāng)我們的Bot(Rasa Core)在回復(fù)用戶時(shí),可以在回復(fù)的消息中附加Button信息,這種Button類似于快捷鍵,用戶獲取到之后,可以直接將其發(fā)送給Rasa Core,它會(huì)直接進(jìn)行解析以識(shí)別intent和提取entity,并將entity的值填充到slot中。比如你讓用戶通過(guò)點(diǎn)擊一個(gè)按鈕來(lái)選擇一種顏色,那么可以在domain.yaml中utter_ask_color的回復(fù)中添加buttons:/choose{"color": "blue"}和/choose{"color": "red"}。注:通常每個(gè)button由title和payload字段組成。
4. Slots Set by Actions
from rasa_sdk.actions import Action
from rasa_sdk.events import SlotSet
import requests
class FetchProfileAction(Action):
def name(self):
return "fetch_profile"
def run(self, dispatcher, tracker, domain):
url = "http://myprofileurl.com"
data = requests.get(url).json
return [SlotSet("account_type", data["account_type"])]
該示例演示了如何在Custom Action中通過(guò)返回事件來(lái)填充Slots的值,即調(diào)用SlotSet事件函數(shù)并將該事件return。需要注意的是,為了達(dá)到這個(gè)目的,我們?cè)诰帉慡tory時(shí)必須包含該Slot,即使用-slot{}實(shí)現(xiàn),只有這樣Rasa Core就會(huì)從提供的信息中進(jìn)行學(xué)習(xí),并決定執(zhí)行正確的action。Story.md示例如下:
# story_01
* greet
- action_fetch_profile
- slot{"account_type" : "premium"}
- utter_welcome_premium
# story_02
* greet
- action_fetch_profile
- slot{"account_type" : "basic"}
- utter_welcome_basic
其中,account_type在domain.yaml中定義如下:
slots:
account_type:
type: categorical
values:
- premium
- basic
2.6.3 Slots Get
目前主要有兩種獲取Slots值方式:
1. Get Slot in responses
responses:
utter_greet:
- text: "Hey, {name}. How are you?"
在domain.yaml的responses部分,可以通過(guò){slotname}的形式獲取槽值。
2. Get Slot in Custom Action
from rasa_sdk.actions import Action
from rasa_sdk.events import SlotSet
import requests
class FetchProfileAction(Action):
def name(self):
return "fetch_profile"
def run(self, dispatcher, tracker, domain):
# 獲取slot account_type的值
account_type = tracker.get_slot('account_type')
return
Tracker,可理解為跟蹤器,作用是以會(huì)話會(huì)話的形式維護(hù)助手和用戶之間的對(duì)話狀態(tài)。通過(guò)Tracker,能夠輕松獲取整個(gè)對(duì)話信息,其中就包括Slot的值。
2.7 Form
在Rasa Core中,當(dāng)我們執(zhí)行一個(gè)action需要同時(shí)填充多個(gè)slot時(shí),可以使用FormAction來(lái)實(shí)現(xiàn),因?yàn)镕ormAction會(huì)遍歷監(jiān)管的所有slot,當(dāng)發(fā)現(xiàn)相關(guān)的slot未被填充時(shí),就會(huì)向用戶主動(dòng)發(fā)起詢問(wèn),直到所有slot被填充完畢,才會(huì)執(zhí)行接下來(lái)的業(yè)務(wù)邏輯。使用步驟如下:
(1)構(gòu)造story
在story中,不僅需要考慮用戶按照我們的設(shè)計(jì)準(zhǔn)確的提供有效信息,而且還要考慮用戶在中間過(guò)程改變要執(zhí)行的意圖情況或稱輸入無(wú)效信息,因?yàn)閷?duì)于FormAction來(lái)說(shuō),如果無(wú)法獲得預(yù)期的信息就會(huì)報(bào)錯(cuò),這里我們分別稱這兩種情況為happy path、unhappy path。示例如下:
## happy path
* request_weather
- weather_form
- form{"name": "weather_form"} 激活form
- form{"name": } 使form無(wú)效
## unhappy path
* request_weather
- weather_form
- form{"name": "weather_form"}
* stop
- utter_ask_continue
* deny
- action_deactivate_form
- form{"name": }
注:* request_restaurant為意圖;- restaurant_form為form action;- form{"name": "restaurant_form"}為激活form;- form{"name": }為注銷form;- action_deactivate_form為默認(rèn)的action,它的作用是用戶可能在表單操作過(guò)程中改變主意,決定不繼續(xù)最初的請(qǐng)求,我們使用這個(gè)default action來(lái)禁止(取消)表單,同時(shí)重置要請(qǐng)求的所有slots。
構(gòu)建stroy最好使用官方提供的Interactive Learning,防止漏掉信息,詳細(xì)見(jiàn)本文2.8小節(jié)。
(2)添加form字段到Domain
在doamin文件下新增forms:部分,并將所有用到的form名稱添加到該字段下:
intents:
- request_weather
forms:
- weather_form
(3)配置FormPolicy
在工程的配置文件configs.yml中,新增FormPolicy策略:
policies:
- name: EmbeddingPolicy
epochs: 100
max_history: 5
- name: FallbackPolicy
fallback_action_name: 'action_default_fallback'
- name: MemoizationPolicy
max_history: 5
- name: FormPolicy
(4)Form Action實(shí)現(xiàn)
class WeatherForm(FormAction):
def name(self) -> Text:
"""Unique identifier of the form"""
return "weather_form"
@staticmethod
def required_slots(tracker: Tracker) -> List[Text]:
"""A list of required slots that the form has to fill"""
return ["date_time", "address"]
def submit(
self,
dispatcher: CollectingDispatcher,
tracker: Tracker,
domain: Dict[Text, Any],
) -> List[Dict]:
"""Define what the form has to do
after all required slots are filled"""
address = tracker.get_slot('address')
date_time = tracker.get_slot('date_time')
return
當(dāng)form action第一被調(diào)用時(shí),form就會(huì)被激活并進(jìn)入FormPolicy策略模式。每次執(zhí)行form action,required_slots會(huì)被調(diào)用,當(dāng)發(fā)現(xiàn)某個(gè)還未被填充時(shí),會(huì)主動(dòng)去調(diào)用形式為uter_ask_{slotname}的模板(注:定義在domain.yml的templates字段中);當(dāng)所有slot被填充完畢,submit方法就會(huì)被調(diào)用,此時(shí)本次form操作完畢被取消激活。
2.8 Interactive Learning
雖然我們可以容易的人工構(gòu)建story樣本數(shù)據(jù),但是往往會(huì)出現(xiàn)一些考慮不全,甚至出錯(cuò)等問(wèn)題,基于此,Rasa Core框架為我們提供了一種交互式學(xué)習(xí)(Interactive Learning)來(lái)獲得所需的樣本數(shù)據(jù)。在互動(dòng)學(xué)習(xí)模式中,當(dāng)你與機(jī)器人交談時(shí),你會(huì)向它提供反饋,這是一種強(qiáng)大的方法來(lái)探索您的機(jī)器人可以做什么,也是修復(fù)它所犯錯(cuò)誤的最簡(jiǎn)單的方法?;跈C(jī)器學(xué)習(xí)的對(duì)話的一個(gè)優(yōu)點(diǎn)是,當(dāng)你的機(jī)器人還不知道如何做某事時(shí),你可以直接教它。
(1)開啟Action Server
python -m rasa run actions --port 5055 --actions actions --debug
(2)開啟Interactive Learning
python -m rasa interactive -m models/20200313-101055.tar.gz --endpoints configs/endpoints.yml --config configs/config.yml
# 或者(沒(méi)有已訓(xùn)練模型情況)
# rasa會(huì)先訓(xùn)練好模型,再開啟交互式學(xué)習(xí)會(huì)話
python -m rasa interactive --data /data --domain configs/domain.yml --endpoints configs/endpoints.yml --config configs/config.yml
分別執(zhí)行(1)、(2)命令后,我們可以預(yù)設(shè)一個(gè)交互場(chǎng)景根據(jù)終端的提示操作即可。如果一個(gè)交互場(chǎng)景所有流程執(zhí)行完畢,按Ctrl+C結(jié)束并選擇Start Fresh進(jìn)入下一個(gè)場(chǎng)景即可。當(dāng)然Rasa還提供了可視化界面,以幫助你了解每個(gè)Story樣本構(gòu)建的過(guò)程,網(wǎng)址:http://localhost:5005/visualization.html。
執(zhí)行流程大致如下:
Bot loaded. Visualisation at http://localhost:5006/visualization.html .
Type a message and press enter (press 'Ctr-c' to exit).
? Your input -> 查詢身份證439912199008071234
? Is the intent 'request_idcard' correct for '查詢身份證[439912199008071234](id_number)' and are all entities labeled correctly? Yes
------
Chat History
# Bot You
───────────────────────────────────────────────────────────────────
1 action_listen
───────────────────────────────────────────────────────────────────
2 查詢身份證[439912199008071234](id_number)
intent: request_idcard 1.00
Current slots:
address: None, business: None, date-time: None, id_number: None, requested_slot: None
------
? The bot wants to run 'number_form', correct? Yes
Chat History
# Bot You
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 action_listen
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
2 查詢身份證[439912199008071234](id_number)
intent: request_idcard 1.00
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
3 number_form 1.00
您要查詢的身份證號(hào)碼439912199008071234所屬人為張三,湖南長(zhǎng)沙人,現(xiàn)在就職于地球村物業(yè)。
form{"name": "number_form"}
slot{"id_number": "439912199008071234"}
form{"name": }
slot{"requested_slot": }
Current slots:
address: None, business: None, date-time: None, id_number: 439912199008071234, requested_slot: None
------
? The bot wants to run 'action_listen', correct? Yes
生成的一個(gè)Story示例如下:
## interactive_story_10
# unhappy path:chitchat stop but continue path
* greet
- utter_answer_greet
* request_number{"type": "身份證號(hào)碼"}
- number_form
- form{"name": "number_form"}
- slot{"type": "身份證號(hào)碼"}
- slot{"number": }
- slot{"business": }
- slot{"requested_slot": "number"}
* chitchat
- utter_chitchat
- number_form
- slot{"requested_slot": "number"}
* stop
- utter_ask_continue
* affirm
- number_form
- slot{"requested_slot": "number"}
* form: request_number{"number": "440123199087233467"}
- form: number_form
- slot{"number": "440123199087233467"}
- slot{"type": "身份證號(hào)碼"}
- form{"name": }
- slot{"requested_slot": }
* thanks
- utter_noworries
改進(jìn)ChitChatAssistant項(xiàng)目
3.1 config.yml
# zh_jieba_mitie_embeddings_config.yml
language: "zh"
pipeline:
- name: "MitieNLP"
model: "data/total_word_feature_extractor_zh.dat"
- name: "JiebaTokenizer"
dictionary_path: "data/dict"
- name: "MitieEntityExtractor"
- name: "EntitySynonymMapper"
- name: "RegexFeaturizer"
- name: "MitieFeaturizer"
- name: "EmbeddingIntentClassifier"
policies:
- name: FallbackPolicy
nlu_threshold: 0.5
ambiguity_threshold: 0.1
core_threshold: 0.5
fallback_action_name: 'action_default_fallback'
- name: MemoizationPolicy
max_history: 5
- name: FormPolicy
- name: MappingPolicy
- name: EmbeddingPolicy
epochs: 500
考慮到目前項(xiàng)目的樣本較少,這里使用MITIE+EmbeddingPolicy組合,雖然訓(xùn)練時(shí)慢了點(diǎn),但是能夠保證實(shí)體提取的準(zhǔn)確性,同時(shí)又能夠提高意圖識(shí)別的命中率。
3.2 weather_stories.md
## happy path
* request_weather
- weather_form
- form{"name": "weather_form"}
- form{"name": }
## happy path
* greet
- utter_answer_greet
* request_weather
- weather_form
- form{"name": "weather_form"}
- form{"name": }
* thanks
- utter_noworries
## unhappy path
* greet
- utter_answer_greet
* request_weather
- weather_form
- form{"name": "weather_form"}
* chitchat
- utter_chitchat
- weather_form
- form{"name": }
* thanks
- utter_noworries
## very unhappy path
* greet
- utter_answer_greet
* request_weather
- weather_form
- form{"name": "weather_form"}
* chitchat
- utter_chitchat
- weather_form
* chitchat
- utter_chitchat
- weather_form
* chitchat
- utter_chitchat
- weather_form
- form{"name": }
* thanks
- utter_noworries
## stop but continue path
* greet
- utter_answer_greet
* request_weather
- weather_form
- form{"name": "weather_form"}
* stop
- utter_ask_continue
* affirm
- weather_form
- form{"name": }
* thanks
- utter_noworries
## stop and really stop path
* greet
- utter_answer_greet
* request_weather
- weather_form
- form{"name": "weather_form"}
* stop
- utter_ask_continue
* deny
- action_deactivate_form
- form{"name": }
## chitchat stop but continue path
* request_weather
- weather_form
- form{"name": "weather_form"}
* chitchat
- utter_chitchat
- weather_form
* stop
- utter_ask_continue
* affirm
- weather_form
- form{"name": }
* thanks
- utter_noworries
## stop but continue and chitchat path
* greet
- utter_answer_greet
* request_weather
- weather_form
- form{"name": "weather_form"}
* stop
- utter_ask_continue
* affirm
- weather_form
* chitchat
- utter_chitchat
- weather_form
- form{"name": }
* thanks
- utter_noworries
## chitchat stop but continue and chitchat path
* greet
- utter_answer_greet
* request_weather
- weather_form
- form{"name": "weather_form"}
* chitchat
- utter_chitchat
- weather_form
* stop
- utter_ask_continue
* affirm
- weather_form
* chitchat
- utter_chitchat
- weather_form
- form{"name": }
* thanks
- utter_noworries
## chitchat, stop and really stop path
* greet
- utter_answer_greet
* request_weather
- weather_form
- form{"name": "weather_form"}
* chitchat
- utter_chitchat
- weather_form
* stop
- utter_ask_continue
* deny
- action_deactivate_form
- form{"name": }
## interactive_story_1
## 天氣 + 時(shí)間 + 地點(diǎn) + 地點(diǎn)
* request_weather
- weather_form
- form{"name": "weather_form"}
- slot{"requested_slot": "date_time"}
* form: inform{"date_time": "明天"}
- form: weather_form
- slot{"date_time": "明天"}
- slot{"requested_slot": "address"}
* form: inform{"address": "廣州"}
- form: weather_form
- slot{"address": "廣州"}
- form{"name": }
- slot{"requested_slot": }
* inform{"date_time": "后天"} OR request_weather{"date_time": "后天"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"address": "廣州"}
- slot{"date_time": "后天"}
- form{"name": }
- slot{"requested_slot": }
* thanks
- utter_answer_thanks
## interactive_story_1
## 天氣 + 時(shí)間 + 地點(diǎn) + 時(shí)間
* request_weather
- weather_form
- form{"name": "weather_form"}
- slot{"requested_slot": "date_time"}
* form: inform{"date_time": "明天"}
- form: weather_form
- slot{"date_time": "明天"}
- slot{"requested_slot": "address"}
* form: inform{"address": "廣州"}
- form: weather_form
- slot{"address": "廣州"}
- form{"name": }
- slot{"requested_slot": }
* inform{"address": "上海"} OR request_weather{"address": "深圳"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"address": "廣州"}
- slot{"address": "上海"}
- form{"name": }
- slot{"requested_slot": }
* affirm
- utter_answer_affirm
## interactive_story_2
## 天氣/時(shí)間/地點(diǎn) + 地點(diǎn)
* request_weather{"date_time": "明天", "address": "上海"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"address": "上海"}
- form{"name": }
- slot{"requested_slot": }
* inform{"address": "廣州"} OR request_weather{"address": "廣州"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"address": "上海"}
- slot{"address": "廣州"}
- form{"name": }
- slot{"requested_slot": }
* thanks
- utter_answer_thanks
## interactive_story_3
## 天氣/時(shí)間/地點(diǎn) + 時(shí)間
* request_weather{"address": "深圳", "date_time": "后天"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "后天"}
- slot{"address": "深圳"}
- form{"name": }
- slot{"requested_slot": }
* inform{"date_time": "大后天"} OR request_weather{"date_time": "大后天"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "后天"}
- slot{"address": "深圳"}
- slot{"date_time": "大后天"}
- form{"name": }
- slot{"requested_slot": }
* thanks
- utter_answer_thanks
## interactive_story_2
## 天氣/時(shí)間/地點(diǎn) + 地點(diǎn) + 時(shí)間
* request_weather{"date_time": "明天", "address": "上海"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"address": "上海"}
- form{"name": }
- slot{"requested_slot": }
* inform{"address": "北京"} OR request_weather{"address": "北京"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"address": "上海"}
- slot{"address": "北京"}
- form{"name": }
- slot{"requested_slot": }
* inform{"date_time": "后天"} OR request_weather{"date_time": "后天"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"address": "北京"}
- slot{"date_time": "后天"}
- form{"name": }
- slot{"requested_slot": }
* affirm
- utter_answer_affirm
## interactive_story_3
## 天氣/時(shí)間/地點(diǎn) + 地點(diǎn) + 地點(diǎn)
* request_weather{"date_time": "后天", "address": "北京"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "后天"}
- slot{"address": "北京"}
- form{"name": }
- slot{"requested_slot": }
* inform{"address": "深圳"} OR request_weather{"address": "深圳"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "后天"}
- slot{"address": "北京"}
- slot{"address": "深圳"}
- form{"name": }
- slot{"requested_slot": }
* inform{"address": "南京"} OR request_weather{"address": "南京"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "后天"}
- slot{"address": "深圳"}
- slot{"address": "南京"}
- form{"name": }
- slot{"requested_slot": }
* thanks
- utter_answer_thanks
## interactive_story_4
## 天氣/時(shí)間/地點(diǎn) + 時(shí)間 + 地點(diǎn)
* request_weather{"date_time": "明天", "address": "長(zhǎng)沙"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"address": "長(zhǎng)沙"}
- form{"name": }
- slot{"requested_slot": }
* inform{"date_time": "后天"} OR request_weather{"date_time": "后天"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"address": "長(zhǎng)沙"}
- slot{"date_time": "后天"}
- form{"name": }
- slot{"requested_slot": }
* inform{"date_time": "大后天"} OR request_weather{"date_time": "大后天"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "后天"}
- slot{"address": "長(zhǎng)沙"}
- slot{"date_time": "大后天"}
- form{"name": }
- slot{"requested_slot": }
* affirm
- utter_answer_affirm
## interactive_story_5
## 天氣/時(shí)間/地點(diǎn) + 時(shí)間 + 時(shí)間
* request_weather{"date_time": "后天", "address": "深圳"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "后天"}
- slot{"address": "深圳"}
- form{"name": }
- slot{"requested_slot": }
* inform{"date_time": "明天"} OR request_weather{"date_time": "明天"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "后天"}
- slot{"address": "深圳"}
- slot{"date_time": "明天"}
- form{"name": }
- slot{"requested_slot": }
* inform{"address": "廣州"} OR request_weather{"address": "廣州"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"address": "深圳"}
- slot{"address": "廣州"}
- form{"name": }
- slot{"requested_slot": }
* thanks
- utter_answer_thanks
## interactive_story_4
## 天氣/時(shí)間 + 地點(diǎn) + 時(shí)間
* request_weather{"date_time": "明天"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"requested_slot": "address"}
* form: inform{"address": "廣州"}
- form: weather_form
- slot{"address": "廣州"}
- form{"name": }
- slot{"requested_slot": }
* inform{"date_time": "后天"} OR request_weather{"date_time": "后天"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "明天"}
- slot{"address": "廣州"}
- slot{"date_time": "后天"}
- form{"name": }
- slot{"requested_slot": }
* thanks
- utter_answer_thanks
## interactive_story_5
## 天氣/地點(diǎn) + 時(shí)間 + 時(shí)間
* request_weather{"address": "廣州"}
- weather_form
- form{"name": "weather_form"}
- slot{"address": "廣州"}
- slot{"requested_slot": "date_time"}
* form: inform{"date_time": "后天"}
- form: weather_form
- slot{"date_time": "后天"}
- form{"name": }
- slot{"requested_slot": }
* inform{"date_time": "明天"} OR request_weather{"date_time": "明天"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "后天"}
- slot{"address": "廣州"}
- slot{"date_time": "明天"}
- form{"name": }
- slot{"requested_slot": }
* thanks
- utter_answer_thanks
## interactive_story_1
## 天氣/時(shí)間/地點(diǎn) + chit + chit(restart)+詢問(wèn)天氣
* request_weather{"date_time": "今天", "address": "廣州"}
- weather_form
- form{"name": "weather_form"}
- slot{"date_time": "今天"}
- slot{"address": "廣州"}
- form{"name": }
- slot{"requested_slot": }
* chitchat
- utter_chitchat
* chitchat
- utter_chitchat
- action_restart
* request_weather
- weather_form
- form{"name": "weather_form"}
- slot{"requested_slot": "date_time"}
* form: inform{"date_time": "今天"}
- form: weather_form
- slot{"date_time": "今天"}
- slot{"requested_slot": "address"}
* form: inform{"address": "廣州"}
- form: weather_form
- slot{"address": "廣州"}
- form{"name": }
- slot{"requested_slot": }
* thanks
- utter_answer_thanks
在構(gòu)建Story樣本時(shí),主要是使用Interactive Learning工具實(shí)現(xiàn),以確保枚舉盡可能多的unhappy story,同時(shí)又能夠防止在構(gòu)建樣本時(shí)出現(xiàn)信息遺漏的情況。此外,本版本中除了查詢天氣這個(gè)案例,還新增了其他案例,并列舉了如何使用同義詞、自定義字典以及正則表達(dá)式的使用方法,詳細(xì)見(jiàn)最新版項(xiàng)目。
GitHub地址:ChitChatAssistant https://github.com/jiangdongguo/ChitChatAssistant,歡迎star和issues,我們共同討論、學(xué)習(xí)!
原文鏈接:
https://blog.csdn.net/AndrExpert/article/details/105434136
?沒(méi)有監(jiān)控和日志咋整?老程序員來(lái)支招
?朱廣權(quán)李佳琦直播掉線,1.2億人在線等
?RPC的超時(shí)設(shè)置,一不小心就是線上事故!
?拿下Gartner容器產(chǎn)品第一,阿里云打贏云原生關(guān)鍵一戰(zhàn)!
?深聊Solidity的測(cè)試場(chǎng)景、方法和實(shí)踐,太詳細(xì)了,必須收藏!
?萬(wàn)字干貨:一步步教你如何在容器上構(gòu)建持續(xù)部署!
?據(jù)說(shuō),這是當(dāng)代極客們的【技術(shù)風(fēng)向標(biāo)】...
今日福利:評(píng)論區(qū)留言入選,可獲得價(jià)值299元的「2020 AI開發(fā)者萬(wàn)人大會(huì)」在線直播門票一張。 快來(lái)動(dòng)動(dòng)手指,寫下你想說(shuō)的話吧。
文檔對(duì)象模型 (DOM) 是HTML和XML文檔的編程接口。它提供了對(duì)文檔的結(jié)構(gòu)化的表述,并定義了一種方式可以使從程序中對(duì)該結(jié)構(gòu)進(jìn)行訪問(wèn),從而改變文檔的結(jié)構(gòu),樣式和內(nèi)容。文檔對(duì)象模型 (DOM) 是對(duì)HTML文件的另一種展示,通俗地說(shuō),一個(gè)HTML 文件,我們可以用編輯器以代碼的形式展示它,也可以用瀏覽器以頁(yè)面的形式展示它,同一份文件通過(guò)不同的展示方式,就有了不一樣的表現(xiàn)形式。而DOM 將文檔解析為一個(gè)由節(jié)點(diǎn)和對(duì)象(包含屬性和方法的對(duì)象)組成的結(jié)構(gòu)集合。簡(jiǎn)言之,它會(huì)將web頁(yè)面和腳本或程序語(yǔ)言連接起來(lái),我們可以使用腳本或者程序語(yǔ)言通過(guò)DOM 來(lái)改變或者控制web頁(yè)面。
我們可以通過(guò)JavaScript 來(lái)調(diào)用document和window元素的API來(lái)操作文檔或者獲取文檔的信息。
Node 是一個(gè)接口,有許多接口都從Node 繼承方法和屬性:Document, Element, CharacterData (which Text, Comment, and CDATASection inherit), ProcessingInstruction, DocumentFragment, DocumentType, Notation, Entity, EntityReference。Node 有一個(gè)nodeType的屬性表示Node 的類型,是一個(gè)整數(shù),不同的值代表不同的節(jié)點(diǎn)類型。具體如下表所示:
節(jié)點(diǎn)類型常量
已棄用的節(jié)點(diǎn)類型常量
假設(shè)我們要判斷一個(gè)Node 是不是一個(gè)元素,通過(guò)查表可知元素的nodeType屬性值為1,代碼可以這么寫:
復(fù)制代碼if(X.nodeType === 1){
console.log('X 是一個(gè)元素');
}
在Node 類型中,比較常用的就是element,text,comment,document,document_fragment這幾種類型。
Element提供了對(duì)元素標(biāo)簽名,子節(jié)點(diǎn)和特性的訪問(wèn),我們常用HTML元素比如div,span,a等標(biāo)簽就是element中的一種。Element有下面幾條特性:(1)nodeType為1(2)nodeName為元素標(biāo)簽名,tagName也是返回標(biāo)簽名(3)nodeValue為null(4)parentNode可能是Document或Element(5)子節(jié)點(diǎn)可能是Element,Text,Comment,Processing_Instruction,CDATASection或EntityReference
Text表示文本節(jié)點(diǎn),它包含的是純文本內(nèi)容,不能包含html代碼,但可以包含轉(zhuǎn)義后的html代碼。Text有下面的特性:(1)nodeType為3(2)nodeName為#text(3)nodeValue為文本內(nèi)容(4)parentNode是一個(gè)Element(5)沒(méi)有子節(jié)點(diǎn)
Comment表示HTML文檔中的注釋,它有下面的幾種特征:(1)nodeType為8(2)nodeName為#comment(3)nodeValue為注釋的內(nèi)容(4)parentNode可能是Document或Element(5)沒(méi)有子節(jié)點(diǎn)
Document表示文檔,在瀏覽器中,document對(duì)象是HTMLDocument的一個(gè)實(shí)例,表示整個(gè)頁(yè)面,它同時(shí)也是window對(duì)象的一個(gè)屬性。Document有下面的特性:(1)nodeType為9(2)nodeName為#document(3)nodeValue為null(4)parentNode為null(5)子節(jié)點(diǎn)可能是一個(gè)DocumentType或Element
DocumentFragment是所有節(jié)點(diǎn)中唯一一個(gè)沒(méi)有對(duì)應(yīng)標(biāo)記的類型,它表示一種輕量級(jí)的文檔,可能當(dāng)作一個(gè)臨時(shí)的倉(cāng)庫(kù)用來(lái)保存可能會(huì)添加到文檔中的節(jié)點(diǎn)。DocumentFragment有下面的特性:(1)nodeType為11(2)nodeName為#document-fragment(3)nodeValue為null(4)parentNode為null
用如其名,這類API是用來(lái)創(chuàng)建節(jié)點(diǎn)的
createElement通過(guò)傳入指定的一個(gè)標(biāo)簽名來(lái)創(chuàng)建一個(gè)元素,如果傳入的標(biāo)簽名是一個(gè)未知的,則會(huì)創(chuàng)建一個(gè)自定義的標(biāo)簽,注意:IE8以下瀏覽器不支持自定義標(biāo)簽。
語(yǔ)法
復(fù)制代碼 let element = document.createElement(tagName);
使用createElement要注意:通過(guò)createElement創(chuàng)建的元素并不屬于HTML文檔,它只是創(chuàng)建出來(lái),并未添加到HTML文檔中,要調(diào)用appendChild或insertBefore等方法將其添加到HTML文檔樹中。
例子:
復(fù)制代碼 let elem = document.createElement("div");
elem.id = 'test';
elem.style = 'color: red';
elem.innerHTML = '我是新創(chuàng)建的節(jié)點(diǎn)';
document.body.appendChild(elem);
運(yùn)行結(jié)果為:
createTextNode用來(lái)創(chuàng)建一個(gè)文本節(jié)點(diǎn)
語(yǔ)法
復(fù)制代碼 var text = document.createTextNode(data);
createTextNode接收一個(gè)參數(shù),這個(gè)參數(shù)就是文本節(jié)點(diǎn)中的文本,和createElement一樣,創(chuàng)建后的文本節(jié)點(diǎn)也只是獨(dú)立的一個(gè)節(jié)點(diǎn),同樣需要appendChild將其添加到HTML文檔樹中
例子:
復(fù)制代碼 var node = document.createTextNode("我是文本節(jié)點(diǎn)");
document.body.appendChild(node);
運(yùn)行結(jié)果為:
cloneNode返回調(diào)用該方法的節(jié)點(diǎn)的一個(gè)副本
語(yǔ)法
復(fù)制代碼 var dupNode = node.cloneNode(deep);
node 將要被克隆的節(jié)點(diǎn)dupNode 克隆生成的副本節(jié)點(diǎn)deep(可選)是否采用深度克隆,如果為true,則該節(jié)點(diǎn)的所有后代節(jié)點(diǎn)也都會(huì)被克隆,如果為false,則只克隆該節(jié)點(diǎn)本身.
這里有幾點(diǎn)要注意:(1)和createElement一樣,cloneNode創(chuàng)建的節(jié)點(diǎn)只是游離有HTML文檔外的節(jié)點(diǎn),要調(diào)用appendChild方法才能添加到文檔樹中(2)如果復(fù)制的元素有id,則其副本同樣會(huì)包含該id,由于id具有唯一性,所以在復(fù)制節(jié)點(diǎn)后必須要修改其id(3)調(diào)用接收的deep參數(shù)最好傳入,如果不傳入該參數(shù),不同瀏覽器對(duì)其默認(rèn)值的處理可能不同
注意如果被復(fù)制的節(jié)點(diǎn)綁定了事件,則副本也會(huì)跟著綁定該事件嗎?這里要分情況討論:(1)如果是通過(guò)addEventListener或者比如onclick進(jìn)行綁定事件,則副本節(jié)點(diǎn)不會(huì)綁定該事件(2)如果是內(nèi)聯(lián)方式綁定比如:<div onclick="showParent()"></div>,這樣的話,副本節(jié)點(diǎn)同樣會(huì)觸發(fā)事件。
例子:
復(fù)制代碼<body>
<div id="parent">
我是父元素的文本
<br/>
<span>
我是子元素
</span>
</div>
<button id="btnCopy">復(fù)制</button>
</body>
<script>
var parent = document.getElementById("parent");
document.getElementById("btnCopy").onclick = function(){
var parent2 = parent.cloneNode(true);
parent2.id = "parent2";
document.body.appendChild(parent2);
}
</script>
運(yùn)行結(jié)果為:
DocumentFragments 是DOM節(jié)點(diǎn)。它們不是主DOM樹的一部分。通常的用例是創(chuàng)建文檔片段,將元素附加到文檔片段,然后將文檔片段附加到DOM樹。在DOM樹中,文檔片段被其所有的子元素所代替。因?yàn)槲臋n片段存在于內(nèi)存中,并不在DOM樹中,所以將子元素插入到文檔片段時(shí)不會(huì)引起頁(yè)面回流(reflow)(對(duì)元素位置和幾何上的計(jì)算)。因此,使用文檔片段document fragments 通常會(huì)起到優(yōu)化性能的作用。
語(yǔ)法
復(fù)制代碼 let fragment = document.createDocumentFragment();
例子:
復(fù)制代碼<body>
<ul id="ul"></ul>
</body>
<script>
(function()
{
var start = Date.now();
var str = '', li;
var ul = document.getElementById('ul');
var fragment = document.createDocumentFragment();
for(var i=0; i<1000; i++)
{
li = document.createElement('li');
li.textContent = '第'+(i+1)+'個(gè)子節(jié)點(diǎn)';
fragment.appendChild(li);
}
ul.appendChild(fragment);
})();
</script>
運(yùn)行結(jié)果為:
節(jié)點(diǎn)創(chuàng)建型API主要包括createElement,createTextNode,cloneNode和createDocumentFragment四個(gè)方法,需要注意下面幾點(diǎn):(1)它們創(chuàng)建的節(jié)點(diǎn)只是一個(gè)孤立的節(jié)點(diǎn),要通過(guò)appendChild添加到文檔中(2)cloneNode要注意如果被復(fù)制的節(jié)點(diǎn)是否包含子節(jié)點(diǎn)以及事件綁定等問(wèn)題(3)使用createDocumentFragment來(lái)解決添加大量節(jié)點(diǎn)時(shí)的性能問(wèn)題
前面我們提到節(jié)點(diǎn)創(chuàng)建型API,它們只是創(chuàng)建節(jié)點(diǎn),并沒(méi)有真正修改到頁(yè)面內(nèi)容,而是要調(diào)用·appendChild·來(lái)將其添加到文檔樹中。我在這里將這類會(huì)修改到頁(yè)面內(nèi)容歸為一類。修改頁(yè)面內(nèi)容的api主要包括:appendChild,insertBefore,removeChild,replaceChild。
appendChild我們?cè)谇懊嬉呀?jīng)用到多次,就是將指定的節(jié)點(diǎn)添加到調(diào)用該方法的節(jié)點(diǎn)的子元素的末尾。
語(yǔ)法
復(fù)制代碼 parent.appendChild(child);
child節(jié)點(diǎn)將會(huì)作為parent節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn)。appendChild這個(gè)方法很簡(jiǎn)單,但是還有有一點(diǎn)需要注意:如果被添加的節(jié)點(diǎn)是一個(gè)頁(yè)面中存在的節(jié)點(diǎn),則執(zhí)行后這個(gè)節(jié)點(diǎn)將會(huì)添加到指定位置,其原本所在的位置將移除該節(jié)點(diǎn),也就是說(shuō)不會(huì)同時(shí)存在兩個(gè)該節(jié)點(diǎn)在頁(yè)面上,相當(dāng)于把這個(gè)節(jié)點(diǎn)移動(dòng)到另一個(gè)地方。如果child綁定了事件,被移動(dòng)時(shí),它依然綁定著該事件。
例子:
復(fù)制代碼<body>
<div id="child">
要被添加的節(jié)點(diǎn)
</div>
<br/>
<br/>
<br/>
<div id="parent">
要移動(dòng)的位置
</div>
<input id="btnMove" type="button" value="移動(dòng)節(jié)點(diǎn)" />
</body>
<script>
document.getElementById("btnMove").onclick = function(){
var child = document.getElementById("child");
document.getElementById("parent").appendChild(child);
}
</script>
運(yùn)行結(jié)果:
insertBefore用來(lái)添加一個(gè)節(jié)點(diǎn)到一個(gè)參照節(jié)點(diǎn)之前
語(yǔ)法
復(fù)制代碼 parentNode.insertBefore(newNode,refNode);
parentNode表示新節(jié)點(diǎn)被添加后的父節(jié)點(diǎn)newNode表示要添加的節(jié)點(diǎn)refNode表示參照節(jié)點(diǎn),新節(jié)點(diǎn)會(huì)添加到這個(gè)節(jié)點(diǎn)之前
例子:
復(fù)制代碼<body>
<div id="parent">
父節(jié)點(diǎn)
<div id="child">
子元素
</div>
</div>
<input type="button" id="insertNode" value="插入節(jié)點(diǎn)" />
</body>
<script>
var parent = document.getElementById("parent");
var child = document.getElementById("child");
document.getElementById("insertNode").onclick = function(){
var newNode = document.createElement("div");
newNode.textContent = "新節(jié)點(diǎn)"
parent.insertBefore(newNode,child);
}
</script>
運(yùn)行結(jié)果:
關(guān)于第二個(gè)參數(shù)參照節(jié)點(diǎn)還有幾個(gè)注意的地方:(1)refNode是必傳的,如果不傳該參數(shù)會(huì)報(bào)錯(cuò)(2)如果refNode是undefined或null,則insertBefore會(huì)將節(jié)點(diǎn)添加到子元素的末尾
刪除指定的子節(jié)點(diǎn)并返回
語(yǔ)法
復(fù)制代碼 var deletedChild = parent.removeChild(node);
deletedChild指向被刪除節(jié)點(diǎn)的引用,它等于node,被刪除的節(jié)點(diǎn)仍然存在于內(nèi)存中,可以對(duì)其進(jìn)行下一步操作。注意:如果被刪除的節(jié)點(diǎn)不是其子節(jié)點(diǎn),則程序?qū)?huì)報(bào)錯(cuò)。我們可以通過(guò)下面的方式來(lái)確??梢詣h除:
復(fù)制代碼if(node.parentNode){
node.parentNode.removeChild(node);
}
運(yùn)行結(jié)果:
通過(guò)節(jié)點(diǎn)自己獲取節(jié)點(diǎn)的父節(jié)點(diǎn),然后將自身刪除
replaceChild用于使用一個(gè)節(jié)點(diǎn)替換另一個(gè)節(jié)點(diǎn)
語(yǔ)法
復(fù)制代碼 parent.replaceChild(newChild,oldChild);
newChild是替換的節(jié)點(diǎn),可以是新的節(jié)點(diǎn),也可以是頁(yè)面上的節(jié)點(diǎn),如果是頁(yè)面上的節(jié)點(diǎn),則其將被轉(zhuǎn)移到新的位置oldChild是被替換的節(jié)點(diǎn)
例子:
復(fù)制代碼<body>
<div id="parent">
父節(jié)點(diǎn)
<div id="child">
子元素
</div>
</div>
<input type="button" id="insertNode" value="替換節(jié)點(diǎn)" />
</body>
<script>
var parent = document.getElementById("parent");
var child = document.getElementById("child");
document.getElementById("insertNode").onclick = function(){
var newNode = document.createElement("div");
newNode.textContent = "新節(jié)點(diǎn)"
parent.replaceChild(newNode,child)
}
運(yùn)行結(jié)果:
頁(yè)面修改型API主要是這四個(gè)接口,要注意幾個(gè)特點(diǎn):(1)不管是新增還是替換節(jié)點(diǎn),如果新增或替換的節(jié)點(diǎn)是原本存在頁(yè)面上的,則其原來(lái)位置的節(jié)點(diǎn)將被移除,也就是說(shuō)同一個(gè)節(jié)點(diǎn)不能存在于頁(yè)面的多個(gè)位置(2)節(jié)點(diǎn)本身綁定的事件會(huì)不會(huì)消失,會(huì)一直保留著。
這個(gè)接口很簡(jiǎn)單,根據(jù)元素id返回元素,返回值是Element類型,如果不存在該元素,則返回null
語(yǔ)法
復(fù)制代碼 var element = document.getElementById(id);
使用這個(gè)接口有幾點(diǎn)要注意:(1)元素的Id是大小寫敏感的,一定要寫對(duì)元素的id(2)HTML文檔中可能存在多個(gè)id相同的元素,則返回第一個(gè)元素(3)只從文檔中進(jìn)行搜索元素,如果創(chuàng)建了一個(gè)元素并指定id,但并沒(méi)有添加到文檔中,則這個(gè)元素是不會(huì)被查找到的
例子:
復(fù)制代碼<body>
<p id="para1">Some text here</p>
<button onclick="changeColor('blue');">blue</button>
<button onclick="changeColor('red');">red</button>
</body>
<script>
function changeColor(newColor) {
var elem = document.getElementById("para1");
elem.style.color = newColor;
}
</script>
運(yùn)行結(jié)果:
返回一個(gè)包括所有給定標(biāo)簽名稱的元素的HTML集合HTMLCollection。 整個(gè)文件結(jié)構(gòu)都會(huì)被搜索,包括根節(jié)點(diǎn)。返回的 HTML集合是動(dòng)態(tài)的, 意味著它可以自動(dòng)更新自己來(lái)保持和 DOM 樹的同步而不用再次調(diào)用document.getElementsByTagName()
語(yǔ)法
復(fù)制代碼 var elements = document.getElementsByTagName(name);
(1)如果要對(duì)HTMLCollection集合進(jìn)行循環(huán)操作,最好將其長(zhǎng)度緩存起來(lái),因?yàn)槊看窝h(huán)都會(huì)去計(jì)算長(zhǎng)度,暫時(shí)緩存起來(lái)可以提高效率(2)如果沒(méi)有存在指定的標(biāo)簽,該接口返回的不是null,而是一個(gè)空的HTMLCollection(3)name是一個(gè)代表元素的名稱的字符串。特殊字符 "*" 代表了所有元素。
例子:
復(fù)制代碼<body>
<div>div1</div>
<div>div2</div>
<input type="button" value="顯示數(shù)量" id="btnShowCount"/>
<input type="button" value="新增div" id="btnAddDiv"/>
</body>
<script>
var divList = document.getElementsByTagName("div");
document.getElementById("btnAddDiv").onclick = function(){
var div = document.createElement("div");
div.textContent ="div" + (divList.length+1);
document.body.appendChild(div);
}
document.getElementById("btnShowCount").onclick = function(){
alert(divList.length);
}
</script>
這段代碼中有兩個(gè)按鈕,一個(gè)按鈕是顯示HTMLCollection元素的個(gè)數(shù),另一個(gè)按鈕可以新增一個(gè)div標(biāo)簽到文檔中。前面提到HTMLCollcetion元素是即時(shí)的表示該集合是隨時(shí)變化的,也就是是文檔中有幾個(gè)div,它會(huì)隨時(shí)進(jìn)行變化,當(dāng)我們新增一個(gè)div后,再訪問(wèn)HTMLCollection時(shí),就會(huì)包含這個(gè)新增的div。
運(yùn)行結(jié)果:
getElementsByName主要是通過(guò)指定的name屬性來(lái)獲取元素,它返回一個(gè)即時(shí)的NodeList對(duì)象
語(yǔ)法
復(fù)制代碼 var elements = document.getElementsByName(name)
使用這個(gè)接口主要要注意幾點(diǎn):(1)返回對(duì)象是一個(gè)即時(shí)的NodeList,它是隨時(shí)變化的(2)在HTML元素中,并不是所有元素都有name屬性,比如div是沒(méi)有name屬性的,但是如果強(qiáng)制設(shè)置div的name屬性,它也是可以被查找到的(3)在IE中,如果id設(shè)置成某個(gè)值,然后傳入getElementsByName的參數(shù)值和id值一樣,則這個(gè)元素是會(huì)被找到的,所以最好不好設(shè)置同樣的值給id和name
例子:
復(fù)制代碼<script type="text/javascript">
function getElements()
{
var x=document.getElementsByName("myInput");
alert(x.length);
}
</script>
<body>
<input name="myInput" type="text" size="20" /><br />
<input name="myInput" type="text" size="20" /><br />
<input name="myInput" type="text" size="20" /><br />
<br />
<input type="button" onclick="getElements()" value="How many elements named 'myInput'?" />
</body>
運(yùn)行結(jié)果:
這個(gè)API是根據(jù)元素的class返回一個(gè)即時(shí)的HTMLCollection
語(yǔ)法
復(fù)制代碼 var elements = document.getElementsByClassName(names); // or:
var elements = rootElement.getElementsByClassName(names);
這個(gè)接口有下面幾點(diǎn)要注意:(1)返回結(jié)果是一個(gè)即時(shí)的HTMLCollection,會(huì)隨時(shí)根據(jù)文檔結(jié)構(gòu)變化(2)IE9以下瀏覽器不支持(3)如果要獲取2個(gè)以上classname,可傳入多個(gè)classname,每個(gè)用空格相隔,例如
復(fù)制代碼 var elements = document.getElementsByClassName("test1 test2");
例子:
復(fù)制代碼 var elements = document.getElementsByClassName('test');
復(fù)制代碼 var elements = document.getElementsByClassName('red test');
復(fù)制代碼 var elements = document.getElementById('main').getElementsByClassName('test');
復(fù)制代碼 var testElements = document.getElementsByClassName('test');
var testDivs = Array.prototype.filter.call(testElements, function(testElement){
return testElement.nodeName === 'DIV';;
});
這兩個(gè)API很相似,通過(guò)css選擇器來(lái)查找元素,注意選擇器要符合CSS選擇器的規(guī)則
document.querySelector返回第一個(gè)匹配的元素,如果沒(méi)有匹配的元素,則返回null
語(yǔ)法
復(fù)制代碼 var element = document.querySelector(selectors);
注意,由于返回的是第一個(gè)匹配的元素,這個(gè)api使用的深度優(yōu)先搜索來(lái)獲取元素。
例子:
復(fù)制代碼<body>
<div>
<div>
<span class="test">第三級(jí)的span</span>
</div>
</div>
<div class="test">
同級(jí)的第二個(gè)div
</div>
<input type="button" id="btnGet" value="獲取test元素" />
</body>
<script>
document.getElementById("btnGet").addEventListener("click",function(){
var element = document.querySelector(".test");
alert(element.textContent);
})
</script>
兩個(gè)class都包含“test”的元素,一個(gè)在文檔樹的前面,但是它在第三級(jí),另一個(gè)在文檔樹的后面,但它在第一級(jí),通過(guò)querySelector獲取元素時(shí),它通過(guò)深度優(yōu)先搜索,拿到文檔樹前面的第三級(jí)的元素。運(yùn)行結(jié)果:
語(yǔ)法
復(fù)制代碼 var elementList = document.querySelectorAll(selectors);
例子:
復(fù)制代碼 var matches = document.querySelectorAll("div.note, div.alert");
返回一個(gè)文檔中所有的class為"note"或者"alert"的div元素
復(fù)制代碼<body>
<div class="test">
class為test
</div>
<div id="test">
id為test
</div>
<input id="btnShow" type="button" value="顯示內(nèi)容" />
</body>
<script>
document.getElementById("btnShow").addEventListener("click",function(){
var elements = document.querySelectorAll("#test,.test");
for(var i = 0,length = elements.length;i<length;i++){
alert(elements[i].textContent);
}
})
</script>
這段代碼通過(guò)querySelectorAll,使用id選擇器和class選擇器選擇了兩個(gè)元素,并依次輸出其內(nèi)容。要注意兩點(diǎn):(1)querySelectorAll也是通過(guò)深度優(yōu)先搜索,搜索的元素順序和選擇器的順序無(wú)關(guān)(2)返回的是一個(gè)非即時(shí)的NodeList,也就是說(shuō)結(jié)果不會(huì)隨著文檔樹的變化而變化兼容性問(wèn)題:querySelector和querySelectorAll在ie8以下的瀏覽器不支持。
運(yùn)行結(jié)果:
在html文檔中的每個(gè)節(jié)點(diǎn)之間的關(guān)系都可以看成是家譜關(guān)系,包含父子關(guān)系,兄弟關(guān)系等等
每個(gè)節(jié)點(diǎn)都有一個(gè)parentNode屬性,它表示元素的父節(jié)點(diǎn)。Element的父節(jié)點(diǎn)可能是Element,Document或DocumentFragment
返回元素的父元素節(jié)點(diǎn),與parentNode的區(qū)別在于,其父節(jié)點(diǎn)必須是一個(gè)Element,如果不是,則返回null
返回一個(gè)即時(shí)的NodeList,表示元素的子節(jié)點(diǎn)列表,子節(jié)點(diǎn)可能會(huì)包含文本節(jié)點(diǎn),注釋節(jié)點(diǎn)等
一個(gè)即時(shí)的HTMLCollection,子節(jié)點(diǎn)都是Element,IE9以下瀏覽器不支持children屬性為只讀屬性,對(duì)象類型為HTMLCollection,你可以使用elementNodeReference.children[1].nodeName來(lái)獲取某個(gè)子元素的標(biāo)簽名稱
只讀屬性返回樹中節(jié)點(diǎn)的第一個(gè)子節(jié)點(diǎn),如果節(jié)點(diǎn)是無(wú)子節(jié)點(diǎn),則返回 null
返回當(dāng)前節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn)。如果父節(jié)點(diǎn)為一個(gè)元素節(jié)點(diǎn),則子節(jié)點(diǎn)通常為一個(gè)元素節(jié)點(diǎn),或一個(gè)文本節(jié)點(diǎn),或一個(gè)注釋節(jié)點(diǎn)。如果沒(méi)有子節(jié)點(diǎn),則返回null
返回一個(gè)布爾值,表明當(dāng)前節(jié)點(diǎn)是否包含有子節(jié)點(diǎn).
返回當(dāng)前節(jié)點(diǎn)的前一個(gè)兄弟節(jié)點(diǎn),沒(méi)有則返回nullGecko內(nèi)核的瀏覽器會(huì)在源代碼中標(biāo)簽內(nèi)部有空白符的地方插入一個(gè)文本結(jié)點(diǎn)到文檔中.因此,使用諸如Node.firstChild和Node.previousSibling之類的方法可能會(huì)引用到一個(gè)空白符文本節(jié)點(diǎn), 而不是使用者所預(yù)期得到的節(jié)點(diǎn)
previousElementSibling返回當(dāng)前元素在其父元素的子元素節(jié)點(diǎn)中的前一個(gè)元素節(jié)點(diǎn),如果該元素已經(jīng)是第一個(gè)元素節(jié)點(diǎn),則返回null,該屬性是只讀的。注意IE9以下瀏覽器不支持
Node.nextSibling是一個(gè)只讀屬性,返回其父節(jié)點(diǎn)的childNodes列表中緊跟在其后面的節(jié)點(diǎn),如果指定的節(jié)點(diǎn)為最后一個(gè)節(jié)點(diǎn),則返回nullGecko內(nèi)核的瀏覽器會(huì)在源代碼中標(biāo)簽內(nèi)部有空白符的地方插入一個(gè)文本結(jié)點(diǎn)到文檔中.因此,使用諸如Node.firstChild和Node.previousSibling之類的方法可能會(huì)引用到一個(gè)空白符文本節(jié)點(diǎn), 而不是使用者所預(yù)期得到的節(jié)點(diǎn)
nextElementSibling返回當(dāng)前元素在其父元素的子元素節(jié)點(diǎn)中的后一個(gè)元素節(jié)點(diǎn),如果該元素已經(jīng)是最后一個(gè)元素節(jié)點(diǎn),則返回null,該屬性是只讀的。注意IE9以下瀏覽器不支持
設(shè)置指定元素上的一個(gè)屬性值。如果屬性已經(jīng)存在,則更新該值; 否則將添加一個(gè)新的屬性用指定的名稱和值
語(yǔ)法
復(fù)制代碼 element.setAttribute(name, value);
其中name是特性名,value是特性值。如果元素不包含該特性,則會(huì)創(chuàng)建該特性并賦值。
例子:
復(fù)制代碼<body>
<div id="div1">ABC</div>
</body>
<script>
let div1 = document.getElementById("div1");
div1.setAttribute("align", "center");
</script>
運(yùn)行結(jié)果:
如果元素本身包含指定的特性名為屬性,則可以世界訪問(wèn)屬性進(jìn)行賦值,比如下面兩條代碼是等價(jià)的:
復(fù)制代碼 element.setAttribute("id","test");
element.id = "test";
getAttribute()返回元素上一個(gè)指定的屬性值。如果指定的屬性不存在,則返回null或""(空字符串)
語(yǔ)法
復(fù)制代碼 let attribute = element.getAttribute(attributeName);
attribute是一個(gè)包含attributeName屬性值的字符串。attributeName是你想要獲取的屬性值的屬性名稱
例子:
復(fù)制代碼<body>
<div id="div1">ABC</div>
</body>
<script>
let div1 = document.getElementById("div1");
let align = div1.getAttribute("align");
alert(align);
</script>
運(yùn)行結(jié)果:
removeAttribute()從指定的元素中刪除一個(gè)屬性
語(yǔ)法
復(fù)制代碼 element.removeAttribute(attrName)
attrName是一個(gè)字符串,將要從元素中刪除的屬性名
例子:
復(fù)制代碼<body>
<div id="div1" style="color:red" width="200px">ABC
</div>
</body>
<script>
let div = document.getElementById("div1")
div.removeAttribute("style");
</script>
在運(yùn)行之前div有個(gè)style="color:red"的屬性,在運(yùn)行之后這個(gè)屬性就被刪除了
運(yùn)行結(jié)果:
Window.getComputedStyle()方法給出應(yīng)用活動(dòng)樣式表后的元素的所有CSS屬性的值,并解析這些值可能包含的任何基本計(jì)算假設(shè)某個(gè)元素并未設(shè)置高度而是通過(guò)其內(nèi)容將其高度撐開,這時(shí)候要獲取它的高度就要用到getComputedStyle
語(yǔ)法
復(fù)制代碼 var style = window.getComputedStyle(element[, pseudoElt]);
element是要獲取的元素,pseudoElt指定一個(gè)偽元素進(jìn)行匹配。返回的style是一個(gè)CSSStyleDeclaration對(duì)象。通過(guò)style可以訪問(wèn)到元素計(jì)算后的樣式
getBoundingClientRect用來(lái)返回元素的大小以及相對(duì)于瀏覽器可視窗口的位置
語(yǔ)法
復(fù)制代碼 var clientRect = element.getBoundingClientRect();
clientRect是一個(gè)DOMRect對(duì)象,包含left,top,right,bottom,它是相對(duì)于可視窗口的距離,滾動(dòng)位置發(fā)生改變時(shí),它們的值是會(huì)發(fā)生變化的。除了IE9以下瀏覽器,還包含元素的height和width等數(shù)據(jù)
例子:
復(fù)制代碼 elem.style.color = 'red';
elem.style.setProperty('font-size', '16px');
elem.style.removeProperty('color');
例子:
復(fù)制代碼 var style = document.createElement('style');
style.innerHTML = 'body{color:red} #top:hover{background-color: red;color: white;}';
document.head.appendChild(style););
JavaScript中的API太多了,將這些API記住并熟練使用對(duì)JavaScript的學(xué)習(xí)是有很大的幫助
作者:yyzclyang
鏈接:https://juejin.cn/post/6844903604445249543
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。