SON(JavaScript Object Notation) 是一種輕量級的數據交換格式。在如今的軟件編程中越來越重要,不管你是開發web還是app,其數據格式是一定會使用的。
今天我們就手寫一款JSON格式化工具。
主要是用html,js和css,簡單幾行代碼就寫出一個自己的json格式化工具,顯示樣式可以自己控制,簡潔、方便、好用。而且拿出來還能裝逼。
先看一下完成時的效果:這里樣式我選擇css控制成極簡風格的,只要一個格式控制和顏色控制。畢竟寫代碼的時候不想被視覺污染。
感覺基本的功能都有了,格式化,縮進量,是否顯示引號,單引號雙引號自動變換,分級展示,控制展開和疊起。覺得功能不全的你還可以繼續添加。樣式覺得丑的,你可以自己定義css樣式。
然后讓我們看一下市面上常用的比較成熟的json格式化工具,做下對比。下邊這個是google瀏覽器的插件JSON Edit。
嗯,對比結束,我寫的比他的好,嗯,是這樣,沒毛病。
廢話少說,上代碼:
總共三部分:html、css和js,組合在一起就能用,不過百行代碼。如果你嫌麻煩,在文章底部我有一個做好了的打成壓縮包分享在github了,鏈接已有,自己下。
結構如下
html代碼:
<!DOCTYPE html>
<html >
<head>
<title>原生json格式化及高亮</title>
<meta name="description" content="json,格式化,高亮">
<meta name="keywords" content="json,格式化,高亮">
<script src="c.js" type="text/javascript"></script>
<link href="s.css" type="text/css" rel="stylesheet"></link>
</head>
<body>
<div class="HeadersRow">
<div id="HeaderTitle">
<h3 id="HeaderSubTitle">JSON格式化及高亮:</h3>
<textarea id="RawJson">
</textarea>
</div>
<div id="ControlsRow">
<input type="Button" value="格式化" onclick="Process()"/>
<span id="TabSizeHolder">
縮進量
<select id="TabSize" onchange="TabSizeChanged()">
<option value="1">1</option>
<option value="2" selected="true">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
</select>
</span>
<label for="QuoteKeys">
<input type="checkbox" id="QuoteKeys" onclick="QuoteKeysClicked()" checked="true" />
引號
</label>
<a href="javascript:void(0);" onclick="SelectAllClicked()">全選</a>
<span id="CollapsibleViewHolder" >
<label for="CollapsibleView">
<input type="checkbox" id="CollapsibleView" onclick="CollapsibleViewClicked()" checked="true" />
顯示控制
</label>
</span>
<span id="CollapsibleViewDetail">
<a href="javascript:void(0);" onclick="ExpandAllClicked()">展開</a>
<a href="javascript:void(0);" onclick="CollapseAllClicked()">疊起</a>
<a href="javascript:void(0);" onclick="CollapseLevel(3)">2級</a>
<a href="javascript:void(0);" onclick="CollapseLevel(4)">3級</a>
<a href="javascript:void(0);" onclick="CollapseLevel(5)">4級</a>
<a href="javascript:void(0);" onclick="CollapseLevel(6)">5級</a>
<a href="javascript:void(0);" onclick="CollapseLevel(7)">6級</a>
<a href="javascript:void(0);" onclick="CollapseLevel(8)">7級</a>
<a href="javascript:void(0);" onclick="CollapseLevel(9)">8級</a>
</span>
</div>
<div id="Canvas" class="Canvas"></div>
<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script>
<script type="text/javascript" src="m.js"></script>
</body>
</html>
css代碼:
css代碼是控制樣式的,無操作邏輯,你可以換成你自己喜歡的風格,這里不大費篇章的介紹,簡單截圖帶過。看源碼的可以下我的分享的壓縮包。
js代碼:
直接上github鏈接,我已經上傳壓縮包,解壓可用,源碼也可以查看,自己下載看吧。
https://git.oschina.net/lovepeng/share-for-you.git
者 | 無名之輩FTER
責編 | 夕顏
出品 | 程序人生(ID:coder_life)
本文翻譯自Rasa官方文檔,并融合了自己的理解和項目實戰,同時對文檔中涉及到的技術點進行了一定程度的擴展,目的是為了更好的理解Rasa工作機制。與本文配套的項目GitHub地址:ChitChatAssistant https://github.com/jiangdongguo/ChitChatAssistant,歡迎star和issues,我們共同討論、學習!
對話管理
1.1 多輪對話
多輪對話是相對于單輪對話而言的,單輪對話側重于一問一答,即直接根據用戶的問題給出精準的答案。問答更接近一個信息檢索的過程,雖然也可能涉及簡單的上下文處理,但通常是通過指代消解和 query 補全來完成的,而多輪對話側重于需要維護一個用戶目標狀態的表示和一個決策過程來完成任務,具體來說就是用戶帶著明確的目的而來,希望得到滿足特定限制條件的信息或服務,例如:訂餐,訂票,尋找音樂、電影或某種商品等。因為用戶的需求可以比較復雜,可能需要分多輪進行陳述,用戶也可能在對話過程中不斷修改或完善自己的需求。此外,當用戶的陳述的需求不夠具體或明確的時候,機器也可以通過詢問、澄清或確認來幫助用戶找到滿意的結果。
因此,任務驅動的多輪對話不是一個簡單的自然語言理解加信息檢索的過程,而是一個決策過程,需要機器在對話過程中不斷根據當前的狀態決策下一步應該采取的最優動作(如:提供結果,詢問特定限制條件,澄清或確認需求,等等)從而最有效的輔助用戶完成信息或服務獲取的任務。
注:任務驅動的多輪對話系統通常為封閉域(domain)(閑聊系統為開放域),而特定限制條件對應于槽(Slot),也就是說,用戶在滿足特定限制條件時就是一次槽值填充的過程,如果用戶能夠在一次會話中,滿足全部的限制條件,那么就不必進行多輪對話,即可直接使用戶得到滿意的信息或服務。
1.2 對話管理
對話管理,即Dialog Management(DM),它控制著人機對話的過程,是人機對話系統的重要組成部分。DM會根據NLU模塊輸出的語義表示執行對話狀態的更新和追蹤,并根據一定策略選擇相應的候選動作。簡單來說,就是DM會根據對話歷史信息,決定此刻對用戶的反應,比如在任務驅動的多輪對話系統中,用戶帶著明確的目的如訂餐、訂票等,用戶需求比較復雜,有很多限制條件,可能需要分多輪進行陳述,一方面,用戶在對話過程中可以不斷修改或完善自己的需求,另一方面,當用戶的陳述的需求不夠具體或明確的時候,機器也可以通過詢問、澄清或確認來幫助用戶找到滿意的結果。如下圖所示,DM 的輸入就是用戶輸入的語義表達(或者說是用戶行為,是 NLU 的輸出)和當前對話狀態,輸出就是下一步的系統行為和更新的對話狀態。這是一個循環往復不斷流轉直至完成任務的過程。
從本質上來說,**任務驅動的對話管理實際就是一個決策過程,系統在對話過程中不斷根據當前狀態決定下一步應該采取的最優動作(如:提供結果,詢問特定限制條件,澄清或確認需求等),從而最有效的輔助用戶完成信息或服務獲取的任務。**對話管理的任務大致有:
對話狀態維護(dialog state tracking, DST)
對話狀態是指記錄了哪些槽位已經被填充、下一步該做什么、填充什么槽位,還是進行何種操作。用數學形式表達為,t+1 時刻的對話狀態S(t+1),依賴于之前時刻 t 的狀態St,和之前時刻 t 的系統行為At,以及當前時刻 t+1 對應的用戶行為O(t+1)。可以寫成S(t+1)←St+At+O(t+1)。
生成系統決策(dialog policy)
根據 DST 中的對話狀態(DS),產生系統行為(dialog act),決定下一步做什么 dialog act 可以表示觀測到的用戶輸入(用戶輸入 -> DA,就是 NLU 的過程),以及系統的反饋行為(DA -> 系統反饋,就是 NLG 的過程)。
作為接口與后端/任務模型進行交互
Rasa Core
Rasa Core是Rasa框架提供的對話管理模塊,它類似于聊天機器人的大腦,主要的任務是維護更新對話狀態和動作選擇,然后對用戶的輸入作出響應。所謂對話狀態是一種機器能夠處理的對聊天數據的表征,對話狀態中包含所有可能會影響下一步決策的信息,如自然語言理解模塊的輸出、用戶的特征等;所謂動作選擇,是指基于當前的對話狀態,選擇接下來合適的動作,例如向用戶追問需補充的信息、執行用戶要求的動作等。舉一個具體的例子,用戶說“幫我媽媽預定一束花”,此時對話狀態包括自然語言理解模塊的輸出、用戶的位置、歷史行為等特征。在這個狀態下,系統接下來的動作可能是:
向用戶詢問可接受的價格,如“請問預期價位是多少?”;
向用戶確認可接受的價格,如“像上次一樣買價值200的花可以嗎?”
直接為用戶預訂
2.1 Stories
Rasa的故事是一種訓練數據的形式,用來訓練Rasa的對話管理模型。故事是用戶和人工智能助手之間的對話的表示,轉換為特定的格式,其中用戶輸入表示為相應的意圖(和必要的實體),而助手的響應表示為相應的操作名稱。Rasa核心對話系統的一個訓練示例稱為一個故事。這是一個故事數據格式的指南。兩段對話樣本示例:
<!-- ##表示story的描述,沒有實際作用 -->
## 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格式大致包含三個部分:
1. 用戶輸入(User Messages)
使用*開頭的語句表示用戶的輸入消息,我們無需使用包含某個具體內容的輸入,而是使用NLU管道輸出的intent和entities來表示可能的輸入。需要注意的是,如果用戶的輸入可能包含entities,建議將其包括在內,將有助于policies預測下一步action。這部分大致包含三種形式,示例如下:
(1)* greet 表示用戶輸入沒有entity情況;
(2)* inform{"people": "six"} 表示用戶輸入包含entity情況,響應這類intent為普通action;
(3)* request_weather 表示用戶輸入Message對應的intent為form action情況;
2. 動作(Actions)
使用-開頭的語句表示要執行動作(Action),可分為utterance actions和custom actions,其中,前者在domain.yaml中定義以utter_為前綴,比如名為greet的意圖,它的回復應為utter_greet;后者為自定義動作,具體邏輯由我們自己實現,雖然在定義action名稱的時候沒有限制,但是還是建議以action_為前綴,比如名為inform的意圖fetch_profile的意圖,它的response可為action_fetch_profile。
3. 事件(Events)
Events也是使用-開頭,主要包含槽值設置(SlotSet)和激活/注銷表單(Form),它是是Story的一部分,并且必須顯示的寫出來。Slot Events和Form Events的作用如下:
(1)Slot Events
Slot Events的作用當我們在自定義Action中設置了某個槽值,那么我們就需要在Story中Action執行之后顯著的將這個SlotSet事件標注出來,格式為- slot{"slot_name": "value"}。比如,我們在action_fetch_profile中設置了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中執行action_fetch_profile之后,添加- slot{"account_type" : "premium"}。雖然,這么做看起來有點多余,但是Rasa規定這么做必須的,目的是提高訓練時準確度。
## fetch_profile
* fetch_profile
- action_fetch_profile
- slot{"account_type" : "premium"}
- utter_welcome_premium
當然,如果您的自定義Action中將槽值重置為None,則對應的事件為-slot{"slot_name": }。
(2)Form Events
在Story中主要存在三種形式的表單事件(Form Events),它們可表述為:
Form Action事件
Form Action即表單動作事件,是自定義Action的一種,用于一個表單操作。示例如下:
- restaurant_form
Form activation事件
form activation即激活表單事件,當form action事件執行后,會立馬執行該事件。示例如下:
- form{"name": "restaurant_form"}
Form deactivation事件
form deactivation即注銷表單事件,作用與form activation相反。示例如下:
- form{"name": }
總之,我們在構建Story時,可以說是多種多樣的,因為設計的故事情節是多種多樣的,這就意味著上述三種內容的組合也是非常靈活的。另外,在設計Story時Rasa還提供了Checkpoints 和OR statements兩種功能,來提升構建Story的靈活度,但是需要注意的是,東西雖好,但是不要太貪了,過多的使用不僅增加了復雜度,同時也會拖慢訓練的速度。其中,Checkpoints用于模塊化和簡化訓練數據,示例如下:
## 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主要用于實現某一個action可同時響應多個意圖的情況,比如下面的例子:
## story
* affirm OR thankyou
- action_handle_affirmation
2.2 Domain
Domain,譯為**“領域”**,它描述了對話機器人應知道的所有信息,類似于“人的大腦”,存儲了意圖intents、實體entities、插槽slots以及動作actions等信息,其中,intents、entities在NLU訓練樣本中定義,slots對應于entities類型,只是表現形式不同。domain.yml文件組成結構如下:
具體介紹如下:
1. intents
intents:
- affirm
- deny
- greet
- request_weather
- request_number
- inform
- inform_business
- stop
- chitchat
intents,即意圖,是指我們輸入一段文本,希望Bot能夠明白這段文本是什么意思。在Rasa框架中,意圖的定義是在NLU樣本中實現的,并且在每個意圖下面我們需要枚舉盡可多的樣本用于訓練,以達到Bot能夠準確識別出我們輸入的一句話到底想要干什么。
2. session_config
session_config:
carry_over_slots_to_new_session: true
session_expiration_time: 60
session_config,即會話配置,這部分的作用為配置一次會話(conversation session)是否有超時限制。上例演示的是,每次會話的超時時間為60s,如果用戶開始一段會話后,在60s內沒有輸入任何信息,那么這次會話將被結束,然后Bot又會開啟一次新的會話,并將上一次會話的Slot值拷貝過來。當然,我們希望舍棄上一次會話Slot的值,可以將carry_over_slots_to_new_session設置為false。另外,當session_expiration_time被設置為0時,Bot永遠不會結束當前會話并一直等待用戶輸入(注:執行action_session_start仍然可以開始一次新的會話,在設置為0的情況下)。
3. slots
slots:
date_time:
type: unfeaturized
auto_fill: false
address:
type: unfeaturized
auto_fill: false
Slots,即插槽,它就像對話機器人的內存,它通過鍵值對的形式可用來收集存儲用戶輸入的信息(實體)或者查詢數據庫的數據等。關于Slots的設計與使用,詳情請見本文2.6小節。
4. entities
entities:
- date_time
- address
entities,即實體,類似于輸入文本中的關鍵字,需要在NLU樣本中進行標注,然后Bot進行實體識別,并將其填充到Slot槽中,便于后續進行相關的業務操作。
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
當Rasa NLU識別到用戶輸入Message的意圖后,Rasa Core對話管理模塊就會對其作出回應,而完成這個回應的模塊就是action。Rasa Core支持三種action,即default actions、utter actions以及 custom actions。關于如何實現Actions和處理業務邏輯,我們在一篇文章中詳談,這里僅作簡單了解。
6. forms
forms:
- weather_form
forms,即表單,該部分列舉了在NLU樣本中定義了哪些Form Actions。關于Form Actions的相關知識,請移步至本文的2.7小節。
7. responses
responses:
utter_answer_greet:
- text: "您好!請問我可以幫到您嗎?"
- text: "您好!很高興為您服務。請說出您要查詢的功能?"
utter_ask_date_time:
- text: "請問您要查詢哪一天的天氣?"
utter_ask_address:
- text: "請問您要查下哪里的天氣?"
utter_default:
- text: "沒聽懂,請換種說法吧~"
responses部分就是描述UtterActions具體的回復內容,并且每個UtterAction下可以定義多條信息,當用戶發起一個意圖,比如 “你好!”,就觸發utter_answer_greet操作,Rasa Core會從該action的模板中自動選擇其中的一條信息作為結果反饋給用戶。
2.3 Responses
Responses的作用就是自動響應用戶輸入的信息,因此我們需要管理這些響應(Responses)。Rasa框架提供了三種方式來管理Responses,它們是:
在domain.yaml文件中存儲Responses;
在訓練數據中存儲Responses;
自定義一個NLG服務來生成Responses。
由于第一種我們在本文2.2(7)小節有過介紹,而創建NLG服務是這樣的:
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
當Rasa NLU識別到用戶輸入Message的意圖后,Rasa Core對話管理模塊就會對其作出回應,而完成這個回應的模塊就是action。Rasa Core支持三種action,即default actions、utter actions以及 custom actions。關于如何實現Actions和處理業務邏輯,我們在一篇文章中詳談,這里僅作簡單了解。
1. default actions
DefaultAction是Rasa Core默認的一組actions,我們無需定義它們,直接可以story和domain中使用。包括以下三種action:
action_listen:監聽action,Rasa Core在會話過程中通常會自動調用該action;
action_restart:重置狀態,比初始化Slots(插槽)的值等;
action_default_fallback:當Rasa Core得到的置信度低于設置的閾值時,默認執行該action;
2. utter actions
UtterAction是以utter_為開頭,僅僅用于向用戶發送一條消息作為反饋的一類actions。定義一個UtterAction很簡單,只需要在domain.yml文件中的actions:字段定義以utter_為開頭的action即可,而具體回復內容將被定義在templates:部分,這個我們下面有專門講解。定義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,允許開發者執行任何操作并反饋給用戶,比如簡單的返回一串字符串,或者控制家電、檢查銀行賬戶余額等等。它與DefaultAction不同,自定義action需要我們在domain.yml文件中的actions部分先進行定義,然后在指定的webserver中實現它,其中,這個webserver的url地址在endpoint.yml文件中指定,并且這個webserver可以通過任何語言實現,當然這里首先推薦python來做,畢竟Rasa Core為我們封裝好了一個rasa-core-sdk專門用來處理自定義action。關于action web的搭建和action的具體實現,我們在后面詳細講解,這里我們看下在在Rasa Core項目中需要做什么。假如我們在天氣資訊的人機對話系統需提供查詢天氣和空氣質量兩個業務,那么我們就需要在domain.yml文件中定義查詢天氣和空氣質量的action,即:
actions:
...
- action_search_weather
另外,FormAction也是自定義actions,但是需要在domainl.yaml文件的forms字段聲明。
forms:
- weather_form
2.5 Policies
Policies是Rasa Core中的策略模塊,對應類rasa_core.policies.Policy,它的作用就是使用合適的策略(Policy)來預測一次對話后要執行的行為(Actions)。預測的原理是衡量命中的哪些Policies哪個置信度高,由置信度高的Policy選擇合適的Action執行。假如出現不同的Policy擁有相同的置信度,那么就由它們的優先級決定,即選擇優先級高的Policy。Rasa對提供的Policies進行了優先級排序,具體如下表:
它們的描述與作用如下:
Memoization Policy
MemoizationPolicy只記住(memorizes)訓練數據中的對話。如果訓練數據中存在這樣的對話,那么它將以置信度為1.0預測下一個動作,否則將預測為None,此時置信度為0.0。下面演示了如何在策略配置文件config.yml文件中,配置MemoizationPlicy策略,其中,max_history(超參數)決定了模型查看多少個對話歷史以決定下一個執行的action。
policies:
- name: "MemoizationPolicy"
max_history: 5
注:max_history值越大訓練得到的模型就越大并且訓練時間會變長,關于該值到底該設置多少,我們可以舉這么個例子,比如有這么一個Intent:out_of_scope來描述用戶輸入的消息off-topic(離題),當用戶連續三次觸發out_of_scope意圖,這時候我們就需要主動告知用戶需要向其提供幫助,如果要Rasa Core能夠學習這種模型,max_history應該至少為3。story.md中表現如下:
* out_of_scope
- utter_default
* out_of_scope
- utter_default
* out_of_scope
- utter_help_message
Keras Policy
KerasPolicy策略是Keras框架中實現的神經網絡來預測選擇執行下一個action,它默認的框架使用LSTM(Long Short-Term Memory,長短期記憶網絡)算法,但是我們也可以重寫KerasPolicy.model_architecture函數來實現自己的框架(architecture)。KerasPolicy的模型很簡單,只是單一的LSTM+Dense+softmax,這就需要我們不斷地完善自己的story來把各種情況下的story進行補充。下面演示了如何在策略配置文件config.yml文件中,配置KerasPolicy策略,其中,epochs表示訓練的次數,max_history同上。
policies:
- name: KerasPolicy
epochs: 100
max_history: 5
Embedding Policy
基于機器學習的對話管理能夠學習復雜的行為以完成任務,但是將其功能擴展到新領域并不簡單,尤其是不同策略處理不合作用戶行為的能力,以及在學習新任務(如預訂酒店)時,如何將完成一項任務(如餐廳預訂)重新應用于該任務時的情況。EmbeddingPolicy,即循環嵌入式對話策略(Recurrent Embedding Dialogue Policy,REDP),它通過將actions和對話狀態嵌入到相同的向量空間(vector space)能夠獲得較好的效果,REDP包含一個基于改進的Neural Turing Machine的記憶組件和注意機制,在該任務上顯著優于基線LSTM分類器。
EmbeddingPolicy效果上優于KerasPolicy,但是它有個問題是耗時,因為它沒有使用GPU、沒有充分利用CPU資源。KerasPolicy和EmbeddingPolicy比較示意圖如下:
配置EmbeddingPolicy參數:
policies:
- name: EmbeddingPolicy
epochs: 100
featurizer:
- name: FullDialogueTrackerFeaturizer
state_featurizer:
- name: LabelTokenizerSingleStateFeaturizer
注:新版的Rasa將EmbeddingPolicy重命名為TEDPolicy,但是我在config.yml配置文件中將其替換后,提示無法找到TEDPolicy異常,具體原因不明,暫還未涉及源碼分析。
Form Policy
FormPolicy是MemoizationPolicy的擴展,用于處理(form)表單的填充事項。當一個FormAction被調用時,FormPolicy將持續預測表單動作,直到表單中的所有槽都被填滿,然后再執行對應的FormAction。如果在Bot系統中使用了FormActions,就需要在config.yml配置文件中進行配置。
policies:
- name: FormPolicy
Mapping Policy
MappingPolicy可用于直接將意圖映射到要執行的action,從而實現被映射的action總會被執行,其中,這種映射是通過triggers屬性實現的。舉個栗子(domain.yml文件中):
intents:
- greet: {triggers: utter_goodbye}
其中,greet是意圖;utter_goodbye是action。一個意圖最多只能映射到一個action,我們的機器人一旦收到映射意圖的消息,它將執行對應的action。然后,繼續監聽下一條message。需要注意的是,對于上述映射,我們還需要要在story.md文件中添加如下樣本,否則,任何機器學習策略都可能被預測的action_greet在dialouge歷史中突然出現而混淆。
Fallback Policy
如果意圖識別的置信度低于nlu_threshold,或者沒有任何對話策略預測的action置信度高于core_threshold,FallbackPolicy將執行fallback action。通俗來說,就是我們的對話機器人意圖識別和action預測的置信度沒有滿足對應的閾值,該策略將使機器人執行指定的默認action。configs.yml配置如下:
policies:
- name: "FallbackPolicy"
# 意圖理解置信度閾值
nlu_threshold: 0.3
# action預測置信度閾值
core_threshold: 0.3
# fallback action
fallback_action_name: 'action_default_fallback'
其中,action_default_fallback是Rasa Core中的一個默認操作,它將向用戶發送utter_default模板消息,因此我們需要確保在domain.yml文件中指定此模板。當然,我們也可以在fallback_action_name字段自定義默認回復的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,槽值,相當于機器人的內存(memory),它們以鍵值對的形式存在,用于存儲用戶輸入時消息時比較重要的信息,而這些信息將為Action的執行提供關鍵數據。Slots的定義位于domain.yaml文件中,它們通常與Entities相對應,即Entities有哪些,Slots就有哪些,并且Slots存儲的值就是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時,可以通過initial_value字段為當前slot提供一個初始值,也就是說,當會話開始時,被設定初始值的slot已經被填充好。當然,這個操作不是必須的。
2. Slots Set from NLU
# stories.md
# story_01
* greet{"name": "Ali"}
- slot{"name": "Ali"}
- utter_greet
假如在stories.md文件添加一個包含-slot{}的story,這就意味著當NLU模型提取到一個名為name的實體且這個實體有在domain.yaml中定義,那么NLU模型提取到的實體值會被自動填充到name槽中。實際上,對于Rasa來說,就算你不添加-slot{}字段,這個實體值也會被提取并自動填充到name槽中。當然,如果你希望禁止這種自動填充行為,改為添加-slot{}字段填充,可以在domain.yaml定義slot時,設置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"}'
在點擊Button時填充Slots的值,是指當我們的Bot(Rasa Core)在回復用戶時,可以在回復的消息中附加Button信息,這種Button類似于快捷鍵,用戶獲取到之后,可以直接將其發送給Rasa Core,它會直接進行解析以識別intent和提取entity,并將entity的值填充到slot中。比如你讓用戶通過點擊一個按鈕來選擇一種顏色,那么可以在domain.yaml中utter_ask_color的回復中添加buttons:/choose{"color": "blue"}和/choose{"color": "red"}。注:通常每個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中通過返回事件來填充Slots的值,即調用SlotSet事件函數并將該事件return。需要注意的是,為了達到這個目的,我們在編寫Story時必須包含該Slot,即使用-slot{}實現,只有這樣Rasa Core就會從提供的信息中進行學習,并決定執行正確的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部分,可以通過{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,可理解為跟蹤器,作用是以會話會話的形式維護助手和用戶之間的對話狀態。通過Tracker,能夠輕松獲取整個對話信息,其中就包括Slot的值。
2.7 Form
在Rasa Core中,當我們執行一個action需要同時填充多個slot時,可以使用FormAction來實現,因為FormAction會遍歷監管的所有slot,當發現相關的slot未被填充時,就會向用戶主動發起詢問,直到所有slot被填充完畢,才會執行接下來的業務邏輯。使用步驟如下:
(1)構造story
在story中,不僅需要考慮用戶按照我們的設計準確的提供有效信息,而且還要考慮用戶在中間過程改變要執行的意圖情況或稱輸入無效信息,因為對于FormAction來說,如果無法獲得預期的信息就會報錯,這里我們分別稱這兩種情況為happy path、unhappy path。示例如下:
## happy path
* request_weather
- weather_form
- form{"name": "weather_form"} 激活form
- form{"name": } 使form無效
## 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為默認的action,它的作用是用戶可能在表單操作過程中改變主意,決定不繼續最初的請求,我們使用這個default action來禁止(取消)表單,同時重置要請求的所有slots。
構建stroy最好使用官方提供的Interactive Learning,防止漏掉信息,詳細見本文2.8小節。
(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實現
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
當form action第一被調用時,form就會被激活并進入FormPolicy策略模式。每次執行form action,required_slots會被調用,當發現某個還未被填充時,會主動去調用形式為uter_ask_{slotname}的模板(注:定義在domain.yml的templates字段中);當所有slot被填充完畢,submit方法就會被調用,此時本次form操作完畢被取消激活。
2.8 Interactive Learning
雖然我們可以容易的人工構建story樣本數據,但是往往會出現一些考慮不全,甚至出錯等問題,基于此,Rasa Core框架為我們提供了一種交互式學習(Interactive Learning)來獲得所需的樣本數據。在互動學習模式中,當你與機器人交談時,你會向它提供反饋,這是一種強大的方法來探索您的機器人可以做什么,也是修復它所犯錯誤的最簡單的方法。基于機器學習的對話的一個優點是,當你的機器人還不知道如何做某事時,你可以直接教它。
(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
# 或者(沒有已訓練模型情況)
# rasa會先訓練好模型,再開啟交互式學習會話
python -m rasa interactive --data /data --domain configs/domain.yml --endpoints configs/endpoints.yml --config configs/config.yml
分別執行(1)、(2)命令后,我們可以預設一個交互場景根據終端的提示操作即可。如果一個交互場景所有流程執行完畢,按Ctrl+C結束并選擇Start Fresh進入下一個場景即可。當然Rasa還提供了可視化界面,以幫助你了解每個Story樣本構建的過程,網址:http://localhost:5005/visualization.html。
執行流程大致如下:
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
您要查詢的身份證號碼439912199008071234所屬人為張三,湖南長沙人,現在就職于地球村物業。
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
生成的一個Story示例如下:
## interactive_story_10
# unhappy path:chitchat stop but continue path
* greet
- utter_answer_greet
* request_number{"type": "身份證號碼"}
- number_form
- form{"name": "number_form"}
- slot{"type": "身份證號碼"}
- 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": "身份證號碼"}
- form{"name": }
- slot{"requested_slot": }
* thanks
- utter_noworries
改進ChitChatAssistant項目
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
考慮到目前項目的樣本較少,這里使用MITIE+EmbeddingPolicy組合,雖然訓練時慢了點,但是能夠保證實體提取的準確性,同時又能夠提高意圖識別的命中率。
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
## 天氣 + 時間 + 地點 + 地點
* 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
## 天氣 + 時間 + 地點 + 時間
* 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
## 天氣/時間/地點 + 地點
* 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
## 天氣/時間/地點 + 時間
* 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
## 天氣/時間/地點 + 地點 + 時間
* 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
## 天氣/時間/地點 + 地點 + 地點
* 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
## 天氣/時間/地點 + 時間 + 地點
* 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{"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_5
## 天氣/時間/地點 + 時間 + 時間
* 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
## 天氣/時間 + 地點 + 時間
* 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
## 天氣/地點 + 時間 + 時間
* 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
## 天氣/時間/地點 + chit + chit(restart)+詢問天氣
* 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
在構建Story樣本時,主要是使用Interactive Learning工具實現,以確保枚舉盡可能多的unhappy story,同時又能夠防止在構建樣本時出現信息遺漏的情況。此外,本版本中除了查詢天氣這個案例,還新增了其他案例,并列舉了如何使用同義詞、自定義字典以及正則表達式的使用方法,詳細見最新版項目。
GitHub地址:ChitChatAssistant https://github.com/jiangdongguo/ChitChatAssistant,歡迎star和issues,我們共同討論、學習!
原文鏈接:
https://blog.csdn.net/AndrExpert/article/details/105434136
?沒有監控和日志咋整?老程序員來支招
?朱廣權李佳琦直播掉線,1.2億人在線等
?RPC的超時設置,一不小心就是線上事故!
?拿下Gartner容器產品第一,阿里云打贏云原生關鍵一戰!
?深聊Solidity的測試場景、方法和實踐,太詳細了,必須收藏!
?萬字干貨:一步步教你如何在容器上構建持續部署!
?據說,這是當代極客們的【技術風向標】...
今日福利:評論區留言入選,可獲得價值299元的「2020 AI開發者萬人大會」在線直播門票一張。 快來動動手指,寫下你想說的話吧。
者 | 豬哥
責編 | maozz
JSON的誕生原因是因為XML整合到HTML中各個瀏覽器實現的細節不盡相同,所以道格拉斯·克羅克福特(Douglas Crockford) 和 奇普·莫寧斯達(Chip Morningstar)一起從JS的數據類型中提取了一個子集,作為新的數據交換格式,因為主流的瀏覽器使用了通用的JavaScript引擎組件,所以在解析這種新數據格式時就不存在兼容性問題,于是他們將這種數據格式命名為 “JavaScript Object Notation”,縮寫為 JSON,由此JSON便誕生了!
今天我們來學習一下JSON的結構形式、數據類型、使用場景以及注意事項吧!
JSON格式
上面我們知道JSON是從JavaScript的數據類型中提取出來的子集,那JSON有幾種結構形式呢?又有哪些數據類型呢?他們又分別對應著JavaScript中的哪些數據類型呢?
JSON的2種結構形式,鍵值對形式和數組形式。
舉了一個JSON的實例,就是鍵值對形式的,如下:
{
"person": {
"name": "pig",
"age": "18",
"sex": "man",
"hometown": {
"province": "江西省",
"city": "撫州市",
"county": "崇仁縣"
}
}
}
這種結構的JSON數據規則是:一個無序的“‘名稱/值’對”集合。一個對象以 {左括號 開始, }右括號 結束。每個“名稱”后跟一個 :冒號 ;“‘名稱/值’ 對”之間使用 ,逗號 分隔。
因為大多數的時候大家用的JSON可能都是上面那種key-value形式,所以很多人在講解JSON的時候總是會忽略數組形式,這一點是需要注意的。
那JSON的數組形式是怎么樣的呢?舉一個實例吧!
["pig", 18, "man", "江西省撫州市崇仁縣"]
數組形式的JSON數據就是值(value)的有序集合。一個數組以 [左中括號 開始, ]右中括號 結束。值之間使用 ,逗號 分隔。
JOSN的6種數據類型
上面兩種JSON形式內部都是包含value的,那JSON的value到底有哪些類型,而且上期我們說JSON其實就是從Js數據格式中提取了一個子集,那具體有哪幾種數據類型呢?
string:字符串,必須要用雙引號引起來。
number:數值,與JavaScript的number一致,整數(不使用小數點或指數計數法)最多為 15 位,小數的最大位數是 17。
object:JavaScript的對象形式,{ key:value }表示方式,可嵌套。
array:數組,JavaScript的Array表示方式[ value ],可嵌套。
true/false:布爾類型,JavaScript的boolean類型。
:空值,JavaScript的。
以上數據形式圖片來源JSON官方文檔:http://www.json.org/json-zh.html
JSON使用場景
介紹完JSON的數據格式,那我們來看看JSON在企業中使用的比較多的場景。
接口返回數據和序列化。JSON用的最多的地方莫過于Web了,現在的數據接口基本上都是返回的JSON,具體細化的場景有:
Ajxa異步訪問數據
RPC遠程調用
前后端分離后端返回的數據
開放API,如百度、高德等一些開放接口
企業間合作接口
這種API接口一般都會提供一個接口文檔,說明接口的入參、出參等,
一般的接口返回數據都會封裝成JSON格式,比如類似下面這種
{
"code": 1,
"msg": "success",
"data": {
"name": "pig",
"age": "18",
"sex": "man",
"hometown": {
"province": "江西省",
"city": "撫州市",
"county": "崇仁縣"
}
}
}
程序在運行時所有的變量都是保存在內存當中的,如果出現程序重啟或者機器宕機的情況,那這些數據就丟失了。一般情況運行時變量并不是那么重要丟了就丟了,但有些內存中的數據是需要保存起來供下次程序或者其他程序使用。
保存內存中的數據要么保存在數據庫,要么保存直接到文件中,而將內存中的數據變成可保存或可傳輸的數據的過程叫做序列化,在Python中叫pickling,在其他語言中也被稱之為serialization,marshalling,flattening等等,都是一個意思。
正常的序列化是將編程語言中的對象直接轉成可保存或可傳輸的,這樣會保存對象的類型信息,而JSON序列化則不會保留對象類型!
為了讓大家更直觀的感受區別,豬哥用代碼做一個測試,大家一目了然
Python對象直接序列化會保存class信息,下次使用loads加載到內存時直接變成Python對象。
JSON對象序列化只保存屬性數據,不保留class信息,下次使用loads加載到內存可以直接轉成dict對象,當然也可以轉為Person對象,但是需要寫輔助方法。
對于JSON序列化不能保存class信息的特點,那JSON序列化還有什么用?答案是當然有用,對于不同編程語言序列化讀取有用,比如:我用Python爬取數據然后轉成對象,現在我需要將它序列化磁盤,然后使用Java語言讀取這份數據,這個時候由于跨語言數據類型不同,所以就需要用到JSON序列化。
存在即合理,兩種序列化可根據需求自行選擇!
最后就是生成Token和配置文件
首先聲明Token的形式多種多樣,有JSON、字符串、數字等等,只要能滿足需求即可,沒有規定用哪種形式。
JSON格式的Token最有代表性的莫過于JWT(JSON Web Tokens)。
隨著技術的發展,分布式web應用的普及,通過Session管理用戶登錄狀態成本越來越高,因此慢慢發展成為Token的方式做登錄身份校驗,然后通過Token去取Redis中的緩存的用戶信息,隨著之后JWT的出現,校驗方式更加簡單便捷化,無需通過Redis緩存,而是直接根據Token取出保存的用戶信息,以及對Token可用性校驗,單點登錄更為簡單。
使用JWT做過app的登錄系統,大概的流程就是:
用戶輸入用戶名密碼
app請求登錄中心驗證用戶名密碼
如果驗證通過則生成一個Token,其中Token中包含:
用戶的uid、Token過期時間、過期延期時間等,然后返回給app
app獲得Token,保存在cookie中,下次請求其他服務則帶上
其他服務獲取到Token之后調用登錄中心接口驗證
驗證通過則響應
JWT登錄認證有哪些優勢:
性能好:服務器不需要保存大量的session
單點登錄(登錄一個應用,同一個企業的其他應用都可以訪問):使用JWT做一個登錄中心基本搞定,很容易實現。
兼容性好:支持移動設備,支持跨程序調用,Cookie 是不允許垮域訪問的,而 Token 則不存在這個問題。
安全性好:因為有簽名,所以JWT可以防止被篡改。更多JWT相關知識自行在網上學習,本文不過多介紹!
說實話JSON作為配置文件使用場景并不多,最具代表性的就是npm的package.json包管理配置文件了,下面就是一個npm的package.json配置文件內容。
{
"name": "server", //項目名稱
"version": "0.0.0",
"private": true,
"main": "server.js", //項目入口地址,即執行npm后會執行的項目
"scripts": {
"start": "node ./bin/www" ///scripts指定了運行腳本命令的npm命令行縮寫
},
"dependencies": {
"cookie-parser": "~1.4.3", //指定項目開發所需的模塊
"debug": "~2.6.9",
"express": "~4.16.0",
"http-errors": "~1.6.2",
"jade": "~1.11.0",
"morgan": "~1.9.0"
}
}
但其實JSON并不合適做配置文件,因為它不能寫注釋、作為配置文件的可讀性差等原因。
配置文件的格式有很多種如:toml、yaml、xml、ini等,目前很多地方開始使用yaml作為配置文件格式。
JSON在Python中的使用
最后我們來看看Python中操作JSON的常用方法有哪些,在Python中操作JSON時需要引入json標準庫。
import json
類型轉換
Python類型轉JSON:json.dump
# 1、Python的dict類型轉JSON
person_dict={'name': 'pig', 'age': 18, 'sex': 'man', 'hometown': '江西撫州'}
# indent參數為縮進空格數
person_dict_json=json.dumps(person_dict, indent=4)
print(person_dict_json, '\n')
# 2、Python的列表類型轉JSON
person_list=['pig', 18, 'man', '江西撫州']
person_list_json=json.dumps(person_list)
print(person_list_json, '\n')
# 3、Python的對象類型轉JSON
person_obj=Person('pig', 18, 'man', '江西撫州')
# 中間的匿名函數是獲得對象所有屬性的字典形式
person_obj_json=json.dumps(person_obj, default=lambda obj: obj.__dict__, indent=4)
print(person_obj_json, '\n')
執行結果:
JSON轉Python類型:json.loads
# 4、JSON轉Python的dict類型
person_json='{ "name": "pig","age": 18, "sex": "man", "hometown": "江西撫州"}'
person_json_dict=json.loads(person_json)
print(type(person_json_dict), '\n')
# 5、JSON轉Python的列表類型
person_json2='["pig", 18, "man", "江西撫州"]'
person_json_list=json.loads(person_json2)
print(type(person_json_list), '\n')
# 6、JSON轉Python的自定義對象類型
person_json='{ "name": "pig","age": 18, "sex": "man", "hometown": "江西撫州"}'
# object_hook參數是將dict對象轉成自定義對象
person_json_obj=json.loads(person_json, object_hook=lambda d: Person(d['name'], d['age'], d['sex'], d['hometown']))
print(type(person_json_obj), '\n')
執行結果如下:
對應的數據類型
上面我們演示了Python類型與JSON的相互轉換,最開始的時候我們講過JSON有6種數據類型,那這6種數據類型分別對應Python中的哪些數據類型呢?
需要注意的點
JSON的鍵名和字符串都必須使用雙引號引起來,而Python中單引號也可以表示為字符串,所以這是個比較容易犯的錯誤!
Python類型與JSON相互轉換的時候到底是用load/dump還是用loads\dumps?
他們之間有什么區別?
什么時候該加s什么時候不該加s?
這個我們可以通過查看源碼找到答案:
不加s的方法入參多了一個fp表示filepath,最后多了一個寫入文件的操作。
所以我們在記憶的時候可以這樣記憶:
加s表示轉成字符串(str),不加s表示轉成文件。
Python自定義對象與JSON相互轉換的時候需要輔助方法來指明屬性與鍵名的對應關系,如果不指定一個方法則會拋出異常!
相信有些看的仔細的同學會好奇上面使用json.dumps方法將Python類型轉JSON的時候,如果出現中文,則會出現:
\u6c5f\u897f\u629a\u5dde
這種東西,這是為什么呢?
原因是:Python 3中的json在做dumps操作時,會將中文轉換成unicode編碼,并以16進制方式存儲,而并不是UTF-8格式!
總結
今天我們學習了JSON的2種形式,切記JSON還有[...]這種形式的。
學習了JSON的6種數據類型他們分別對于Python中的哪些類型。
了解了JSON的一些使用場景以及實際的例子。
還學習了在Python中如何使用JSON以及需要注意的事項。
一個JSON知識點卻分兩篇長文(近萬字)來講,其重要性不言而喻。因為不管你是做爬蟲、還是做數據分析、web、甚至前端、測試、運維,JSON都是你必須要掌握的一個知識點
本文為作者投稿,版權歸作者個人所有。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。