標籤: 鏡頭 收購

  • 人機對話技術研究進展與思考

    人機對話技術研究進展與思考

    嘉賓:袁彩霞 博士 北京郵電大學 副教授

    整理:Hoh Xil

    來源:阿里小蜜 & DataFun AI Talk

    出品:DataFun

    注:歡迎轉載,轉載請在留言區內留言。

    導讀:本次分享的主題為人機對話技術研究進展與思考。主要梳理了我們團隊近兩年的工作,渴望可以通過這樣的介紹,能給大家一個關於人機對話 ( 包括它的科學問題和應用技術 ) 方面的啟示,幫助我們進行更深入的研究和討論。主要包括:

    1. Spoken dialogue system:a bird view ( 首先我們來看什麼是人機對話,尤其是 Spoken dialogue。其實說 Spoken 的時候,有兩層含義:第一個 spoken 就是 speech,第二個我們處理的語言本身具有 spoken 的特性。但是,稍後會講的 spoken 是指我們已經進行語音識別之後,轉換為文本的一個特殊的自然語言,後面討論的口語對話不過多地討論它的口語特性,主要是講人和機器之間的自然語言對話。)

    2. X-driven dialogue system:緊接着來講解我們近些年的研究主線 X-driven dialogue syatem,X 指構建一個對話系統時,所採用的數據是什麼,從最早的 dialogue -> FAQ -> KB -> KG -> document 以及我們一直在嘗試的圖文多模態數據。

    3. Concluding remarks ( 結束語 )

    01

    Spoken dialogue system:a bird view

    學術界關於對話系統有着不同的劃分,這種劃分目前看來不是非常準確,也不是特別標準的劃分了。但是,接下來的內容,主要是圍繞着這兩個主線:

    限定領域,專門指任務型對話 ( 圍繞某一特定用戶對話目標而展開的 )。對於任務型對話,對話系統的優化目標就是如何以一個特別高的回報、特別少的對話輪次、特別高的成功率來達成用戶的對話目標。所以即便是限定領域,我們這裏討論的也是特別限定的、專門有明確的用戶對話目標的一種對話。

    開放領域,not purely task-oriented, 已經不再是純粹的對話目標驅動的對話,包括:閑聊、推薦、信息服務等等,後面逐步展開介紹。

    我們在研究一個問題或者做論文答辯和開題報告時,經常討論研究對象的意義在哪裡。圖中,前面講的是應用意義,後面是理論意義。我們實驗室在北京郵電大學叫智能科學與技術實驗室,其實她的前身叫人工智能實驗室。所以從名字來看,我們做了非常多的 AI 基礎理論的研究,我們在研究這些理論的時候,也會講 AI 的終極目的是研製一種能夠從事人類思維活動的計算機系統。人類思維活動建立在獲取到的信號的基礎上。人類獲取信號的方式大體有五類,包括視覺、聽覺、觸覺、味覺、嗅覺等,其中視覺和聽覺是兩個比較高級的傳感器通道,尤其是視覺通道,佔據了人類獲得信息的80%以上。所以我們從這兩個角度,設立了兩個研究對象:第一個是語言,第二個是圖像。而我們在研究語言的時候,發現語言有一個重要的屬性,叫交互性,交互性最典型的一個體現就是對話;同時,語言不是一個獨立的模態,語言的處理離不開跟它相關的另一個通道,就是視覺通道。所以我們早期更多是為了把交互和多模態這樣的屬性納入到語言建模的範圍,以其提升其它自然語言處理系統的性能,這就是我們研究的一個動機。

    1. Block diagram

    上圖為 CMU 等在1997年提出來的人機對話框架,基於這個框架人們開發出了非常多優秀的應用系統,比如應用天氣領域的 “Jupiter”。這個框架從提出到商業化應用,一直到今天,我們都還沿着這樣的一個系統架構在進行開發,尤其是任務驅動的對話。

    這就是具體的對話系統的技術架構。

    1. Specific domain

    這個架構發展到現在,在功能模塊上,已經有了一個很清晰的劃分:

    首先進行語音識別,然後自然語言理解,緊接着做對話管理,將對話管理的輸出交給自然語言生成模塊,最後形成自然語言應答返回給用戶。這就是一個最典型的 specific domain 的架構。早期 task 限定的 dialogue,基本上都是按照這個架構來做的。這個架構雖然是一個 Pipeline,但是從研究的角度來講,每一個模塊和其它模塊之間都會存在依賴關係。因此,我們試圖從研究的角度把不同的功能模塊進行統一建模。在這個建模過程中,又會產生新的學術性問題,我們旨在在這樣的問題上可以產生驅動性的技術。

    1. Open domain

    Open domain,也就是“閑聊”,實現上主要分為途徑:

    第一個是基於匹配/規則的閑聊系統;第二個是基於檢索的閑聊系統;第三個是基於編解碼結構的端到端對話系統。當然,實際情境中,這幾個途徑往往結合在一起使用。

    02

    X-Driven dialogue system

    目前無論是任務型對話還是閑聊式對話,都採用數據驅動的方法,因此依據在構建人機對話系統時所用到的數據不同,建模技術和系統特性也就體現出巨大的不同。我們把使用的數據記為 X,於是就有了不同的 X 驅動的對話。

    1. Our roadmap

    如果想讓機器學會像人一樣對話,我們可以提供的最自然的數據就是 dialogue。我們從2003年開始做對話驅動的對話;2012年開始做 FAQ 驅動的對話;2015年開始做知識庫 ( KB ) 驅動的對話;2016年開始做知識圖譜 ( KG ) 驅動的對話,相比於 KB,KG 中的知識點產生了關聯,有了這種關聯人們就可以在大規模的圖譜上做知識推理;2017年開始做文檔驅動的對話。這就是我們研究的大致脈絡。

    1. Dialogue-driven dialogue

    早期在做 Dialogue driven 的時候,多依賴人工採集數據,但是,從2013年以來,逐步開放了豐富的涵蓋多領域多場景的公開數據集。比如最近的 MultiWOZ,從 task specific 角度講,數據質量足夠好、數據規模足夠大,同時涵蓋的對話情景也非常豐富。但是,目前公開的中文數據集還不是很多。

    這個是和任務型對話無關的數據集,也就是採集的人與人對話的數據集。尤其以 Ubuntu 為例,從15年更新至今,已經積累了非常大規模的數據。

    以 Dialogue 為輸入,我們開展了任務型和非任務型兩個方向的工作。先來看下任務型對話:

    2.1 NLU

    當一個用戶輸入過來,第一個要做的就是自然語言理解 ( NLU ),NLU 要做的三件事為:Domain 識別;Intent 識別;信息槽識別或叫槽填充。這三個任務可以分別獨立地或採用管道式方法做,也可以聯合起來進行建模。在聯合建模以外,我們還做了一些特別的研究。比如我們在槽識別的時候,總是有新槽,再比如有些槽值非常奇怪,例如 “XX手機可以一邊打電話一邊視頻嗎?”,對應着槽值 “視頻電話”,採用序列標註的方式,很難識別它,因為這個槽值非常不規範。用戶輸入可能像這樣語義非常鬆散,不連續,也可能存在非常多噪音,在進行聯合建模時,傳統的序列標註或分類為思想,在實際應用中已經不足以解決問題了。

    我們針對這個問題做了比較多的探討,右圖為我們2015年的一個工作:在這三個任務聯合建模的同時,在槽填充這個任務上將序列標註和分類進行同時建模,來更好地完成 NLU。

    在 NLU 領域還有一個非常重要的問題,隨着開發的業務領域越來越多,我們發現多領域對話產生了諸多非常重要的問題,例如在數據層有些 domain 數據可能很多,有些 domain 數據可能很少,甚至沒有,於是就遇到冷啟動的問題。因此,我們做了非常多的 domain transfer 的工作。上圖為我們2016年的一個工作,我們會把數據比較多的看成源領域,數據比較少的看成目標領域。於是,嘗試了基於多種遷移學習的 NLU,有的是在特徵層進行遷移,有的是在數據層遷移,有的是在模型層進行遷移。圖中是兩個典型的在特徵層進行遷移的例子,不僅關注領域一般特徵,而且關注領域專門特徵,同時採用了對抗網絡來生成一個虛擬的特徵集的模型。

    2.2 NLU+DM

    緊接着,我們研究了 NLU 和對話管理 ( DM ) 進行聯合建模,因為我們發現人人對話的時候,不見得是聽完對方說完話,理解了對方的意圖,然後才形成對話策略,有可能這兩個過程是同時發生的。甚或 DM 還可以反作用於 NLU。早期我們基於的一個假設是, NLU 可能不需要一個顯式的過程,甚至不需要一個顯式的 NLU 的功能,我們認為 NLU 最終是服務於對話管理 ( DM ),甚至就是對話管理 ( DM ) 的一部分。所以,2013年的時候,我們開始了探索,有兩位特別優秀的畢業生在這兩個方面做了特別多的工作。比如,如何更好地聯合建模語言理解的輸出和對話管理的策略優化。這是我們在 NLU 和 DM 聯合建模的工作,同時提升了 NLU 和 DM 的性能。

    在聯合模型中,我們發現,DM 的建模涉及到非常多的 DRL ( 深度強化學習 ) 的工作,訓練起來非常困難,比如如何設計一個好的用戶模擬器,基於規則的,基於統計的,基於語言模型的,基於 RL 的等等我們嘗試了非常多的辦法,也取得了一系列有趣的發現。2018年時我們研究一種不依賴於規則的用戶模擬器,業界管這個問題叫做 “Self”-play,雖然我們和 “Self”-play 在網絡結構上差異挺大的,但是我們還是借鑒了 “Self”-play 訓練的特性,把我們自己的系統叫做 “Self”-play。在這樣的機制引導下,我們研究了不依賴於規則,不依賴於有標記數據的用戶模擬器,使得這個用戶模擬器可以像 Agent 一樣,和我們所構造的對話的 Agent 進行交互,在交互的過程中完成對用戶的模擬。

    在訓練過程中還有一個重要的問題,就是 reward 怎麼來,我們知道在 task oriented 時,reward 通常是人類專家根據業務邏輯/規範制定出來的。事實上,當我們在和環境交互的時候不知道 reward 有多大,但是環境會隱式地告訴我們 reward 是多大,所以我們做了非常多的臨接對和 reward reshaping 的工作。

    2.3 小結

    Dialogue-driven dialogue 這種形式的對話系統,總結來看:

    優點:

    定義非常好,邏輯清晰,每一個模塊的輸入輸出也非常清晰,同時有特別堅實的數學模型可以對它進行建模。

    缺點:

    由於非常依賴數據,同時,不論是在 NLU 還是 NLG 時,我們都是採用有監督的模型來做的,所以它依賴於大量的、精細的標註數據。

    而 DM 往往採用 DRL 來做。NIPS2018 時的一個 talk,直接指出:任何一個 RL 都存在的問題,就是糟糕的重現性、復用性、魯棒性。

    1. FAQ-driven dialogue

    FAQ 是工業界非常常見的一種情景:有大量的標準問,以及這個標準問的答案是什麼。基於這個標準問,一個用戶的問題來了,如何找到和它相似的問題,進而把答案返回給用戶,於是這個 Service 就結束了。

    實際中,我們如何建 FAQ?更多的時候,我會把這個問題和我們庫的標準問題做一個相似度的計算或者做一個分類。

    我們在做這個工作的時候發現一個特別大的問題,就是 Unbalanced Data 問題。比如,我們有5000個問題,每個問題都有標準答案,有些問題可能對應的用戶問題特別多,比如 “屏幕碎了” 可能會有1000多種不同的問法,還有些問題,可能在幾年的時間里都沒有人問到過。所以,面對數據不均衡的問題,我們從2016年開始做了 Data transfer 的工作。

    大致的思路是:我有一個標準問題,但是很糟糕,這個標準問題沒有用戶問題,也就是沒有訓練語料。接下來發現另外一個和這個標準問很相似的其它標準問有很多的訓練語料,於是藉助這個標準問,來生成虛擬樣本,進而削弱了 Unbalance。

    具體的方法:我們把目標領域的標準問看成 Query,把和它相似的標準問題及其對應的用戶問題看成 Context,採用了 MRC 機器閱讀理解的架構來生成一個答案,作為目標問題的虛擬的用戶問題,取得了非常好的效果,並且嘗試了三種不同的生成用戶問題的方法。

    實際項目中,FAQ 中的 Q 可能有非常多的問題,例如3000多個類,需要做極限分類,這就導致性能低下,且非常耗時,不能快速響應用戶的問題。於是我們做了一個匹配和分類進行交互的 model,取得了不錯的效果。

    目前,大部分人都認為 FAQ 驅動的 dialogue 不叫 dialogue,因為我們通常說的 dialogue 輪次是大於兩輪的。而 FAQ 就是一個 QA 系統,沒有交互性。有時候帶來的用戶體驗非常不友好,比如當答案非常長的時候,系統要把長長的答案返回,就會一直講,導致用戶比較差的體驗。於是,我們基於 FAQ 發展出了一個多輪對話的數據,如右圖,這是我們正在開展的一個工作。

    1. KB-driven dialogue

    KB 最早人們認為它就是一個結構化的數據庫,通常存儲在關係型數據庫中。比如要訂一個酒店,這個酒店有各種屬性,如位置、名稱、戶型、價格、面積等等。早期 CMU 的對話系統,所有的模塊都要和 Hub 進行交互,最後 Hub 和後端的數據庫進行交互。數據庫的價值非常大,但是早期人們在建模人機對話的時候,都忽視了數據庫。這裏就會存在一個問題:機器和用戶交互了很久,而在檢索數據庫時發現沒有答案,或者答案非常多,造成用戶體驗非常糟糕。

    從2012年開始,我們開始把 KB 引入我們的對話系統。圖中的對話系統叫做 “teach-and-learn” bot,這是一個多模態的對話,但是每個涉及到的 object,我們都會把它放到 DB 中。和用戶交互過程中,不光看用戶的對話狀態,還要看數據庫狀態。這個想法把工作往前推進了一些。

    直到2016年,MSR 提出的 KB-InfoBot,第一次提出了進行數據庫操作時,要考慮它的可導性,否則,就沒辦法在 RL 網絡中像其它的 Agent action 一樣進行求導。具體的思路:把數據庫的查詢和 Belief State 一起總結起來做同一個 belief,進而在這樣的 belief 基礎上做各種對話策略的優化。

    在上述方法的基礎上,我們做了有效的改良,包括 entropy regularities 工作。是每次和用戶進行交互時,數據庫的 entropy 會發生變化。比如當機器問 “你想訂哪裡的酒店?”,用戶答 “阿里中心附近的。”,於是數據庫立刻進行了一次 entropy 計算進行更新,接着繼續問 “你想訂哪一天的?”,用戶答 “訂7月28號的”,於是又進行了一次 entropy 計算進行更新。這樣在和用戶進行交互的時候,不光看用戶的 dialogue 輸入,還看 DB 的 entropy 輸入,以這兩項共同驅動 Agent action 進行優化。

    這裏我們做了特別多的工作,信息槽從1個到5個,數據庫的規模從大到小,都做了特別多的嘗試,這樣在和用戶交互的時候,agent 可以自主的查詢檢索,甚至可以填充和修改數據庫。

    這是我們2017發布的一個工作,KB driven-dialogue,其優點:

    控制萬能高頻回復 ( 提高答應包含的有用信息 )

    賦予 agent 對話主動性

    1. KG-driven dialogue

    剛剛講的基於 KB 的 dialogue 任務,基本都認為對話任務就是在進行槽填充的任務,如果一個 agent 是主動性的,通過不停的和用戶進行交互來採集槽信息,所以叫槽填充,當槽填完了,就相當於對話任務成功了。但是,當我們在定義槽的時候,我們認為槽是互相獨立的,並且是扁平的。然而,實際中許多任務的槽之間存在相關性,有的是上下位關係,有的是約束關係,有的是遞進關係等等。這樣自然的就引出了知識圖譜,知識圖譜可以較好地描述上述的相關性。於是,產生了兩個新問題:

    知識圖譜驅動的對話理解:實體鏈接

    知識圖譜驅動的對話管理:圖路徑規劃

    這裏主要講下第二個問題。

    舉個例子,我們在辦理電信業務,開通一個家庭寬帶,需要提供相關的證件,是自己去辦,還是委託人去辦,是房東還是租戶等等,需要提供各種不同的材料。於是這個情景就產生了條件的約束,某一個 node 和其它 node 是上下位的關係,比如證件可以是身份證,也可以是護照或者戶口簿等等。所以我們可以通過知識圖譜來進行處理。

    當一個用戶的對話過來,首先會鏈接到不同的 node,再基於 node 和對話歷史構造一個對話的 state,我們會維持一個全局的 state 和一個活躍的 state,同時活躍的 state 會定義三種不同的操作動作,一個是祖先節點,一個是兄弟節點,還有一個是孩子節點。所以,在這樣的知識圖譜上如何尋優,比如當通過某種計算得到,它應該在某個節點上進行交互的時候,我們就應該輸出一個 action,這個 action 要和用戶確認他是一個租戶,還是自有住房等等。所以,這個 action 是有區別於此前所提到的在特定的、扁平的 slot 槽上和用戶進行信息的確認、修改等還是有很大不同的。解決這樣的問題,一個非常巨大的挑戰就是狀態空間非常大。比如圖中的節點大概有120個,每個節點有3個不同的狀態,知識從節點的狀態來看就有3的120次冪種可能。這也是我們正在開展的待解決的一個問題。

    在端到端的對話模型 ( 閑聊 ) 中,也開始逐步地引入知識圖譜。下面介紹兩個比較具有代表性的引入知識圖譜后的人機對話。其中右邊是2018年 IJCAI 的傑出論文,清華大學黃民烈老師團隊的工作,引入了通過 KG 來表示的 Commonsense,同時到底在編碼器端還是在解碼器端引入知識,以及如何排序,排序的時候如何結合對話的 history 做知識的推理等等,都做了特別全面的研究。

    另一個比較有代表性的工作是在 ICLR2019 提出的,在架構中引入了 Local Memory 和 Global Memory 相融合的技術,通過這種融合,在編碼器端和解碼器端同時加入了知識的推理。

    總結下 KB/KG-driven dialogue:

    優點:

    已經有大規模公開的數據 ( e.g.,InCar Assistant,MMD,M2M )。

    訓練過程可控&穩定,因為這裏多數都是有監督學習。

    缺點:

    因為採用有監督的方式進行訓練,所以存在如下問題:

    ① 環境確定性假設
    ② 缺少對動作的建模
    ③ 缺少全局的動作規劃
    Agent 被動,完全依賴於訓練數據,所以模型是不賦予 Agent 主動性的。

    構建 KB 和 KG 成本昂貴!

    1. Document-driven dialogue

    Document 驅動的對話,具有如下優點:

    ① 應用場景真實豐富:

    情景對話 ( conversation ),比如針對某個熱門事件在微博會產生很多對話,這就是一個情景式的對話。

    電信業務辦理 ( service ),比如10086有非常多的套餐,如何從中選擇一個用戶心儀的套餐?每個套餐都有說明書,我們可以圍繞套餐的說明書和用戶進行交互,如 “您希望流量多、還是語音多”,如果用戶回答 “流量多”,就可以基於文本知道給用戶推薦流量多的套餐,如果有三個候選,機器就可以基於這三個候選和用戶繼續進行交互,縮小候選套餐範圍,直到用戶選出心儀的套餐。

    電商產品推薦 ( recommendation ),可以根據商品的描述,進行各種各樣的對話。這裏的輸入不是一個 dialogue,也不是一個 KB,甚至結構化的內容非常少,完全是一個 free document,如何基於這些 document 進行推薦,是一個很好的應用場景。

    交互式信息查詢 ( retrieval ),很多時候,一次查詢的結果可能不能用戶的需求,如何基於非結構化的查詢結果和用戶進行交互,來更好地達成用戶的查詢目的。

    ……

    ② 數據獲取直接便捷:

    相比於 dialogue、FAQ、KB、KQ 等,Document 充斥着互聯網的各種各樣的文本,都可以看成是文本的數據,獲取方便,成本非常低。

    ③ 領域移植性強:

    基於文本,不再基於專家定義的 slot,也不再基於受限的 KB/KG,從技術上講,所構造的 model 本身是和領域無關的,所以它賦予了 model 很強的領域移植性。

    這是我們正在進行的工作,情景對話偏向於閑聊,沒有一個用戶目標。這裏需要解決的問題有兩個:

    如何引入文檔:編碼端引入文檔、解碼端引入文檔

    如何編碼文檔:文檔過長、冗餘信息過多

    數據:

    我們在 DoG 數據的基礎上模擬了一些數據,形成了如上圖所示的數據集,分 Blind 和 Non-blind 兩種情形構造了不同的數據集。

    我們發現,基於文本的端到端的聊天,有些是基於內容的閑聊,有些還需要回答特定的問題。所以,評價的時候,可以直接用 F1 評價回答特定問題,用閑聊通用的評價來評價基於內容的聊天。

    剛剛講的是偏閑聊式的對話,接下來講下任務型對話。

    這裏的動機分為兩種情況:單文檔和多文檔,我們目前在挑戰多文檔的情況,單文檔則採用簡單的多輪閱讀理解來做。

    多文檔要解決的問題:

    如何定義對話動作:因為是基於Document進行交互,而不再是基於slot進行交互,所以需要重新定義對話動作。

    如何建模文檔差異:以剛剛10086的例子為例,總共有10個業務,通過一輪對話,挑選出3個,這3個業務每個業務可能都有10種屬性,那麼其中一些屬性值相同的屬性,沒必要再和用戶進行交互,只需要交互它們之間不同的點,所以交互的角度需要隨對話進行動態地變化。

    數據:

    這裏採用的數據是 WikiMovies 和模擬數據,具體見上圖。

    1. A very simple sketch of dialogue

    上圖為任務型對話和非任務型對話的幾個重要節點,大家可以了解下。

    03

    Concluding remarks

    任務型對話具有最豐富的落地場景。

    純閑聊型對話系統的學術價值尚不清楚。

    任務型和非任務型邊界愈加模糊,一個典型的人人對話既包括閑聊,又包括信息獲取、推薦、服務。

    引入外部知識十分必要,外部知識形態各異,建模方法也因而千差萬別。

    Uncertainty 和 few-shot 問題,是幾乎所有的對話系統最大的 “卡脖子” 問題。

    X-driven dialogue 中的 X 還有哪些可能?剛剛提到的 X 都還是基於文本的。事實上,從2005年開始,我們已經開始做 image 和文本數據融合的對話;從2013年開始做 Visual QA/Dialogue,挑戰了 GuessWhat 和 GuessWhich 任務。

    X=multi media ( MM Dialogue ),X 還可以很寬泛的就是指多媒體,不光有 image 還可能有 text,2018年已經有了相關的任務,並且開源了非常多的電商業務。這是一個非常有挑戰性的工作,也使人機對話本身更加擬人化。

    04

    Reference

    這是部分的參考文獻,有些是我們自己的工作,有些是特別傑出的別人的工作。

    今天的分享就到這裏,感謝大家的聆聽,也感謝我們的團隊。

    歡迎關注DataFunTalk同名公眾號,收看第一手原創技術文章。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 新能源積分交易制度 或替代財政補貼

    新能源積分交易制度 或替代財政補貼

    新能源汽車業發展日益成熟之時,單純的財政補貼帶來的車企騙補等諸多負面效應逐漸顯現。對此,有專家提出建議採用積分交易機制替代財政補貼政策,促進車企在傳統燃油車節能和新能源車技術進步兩方面共同發展。  
      中國汽車技術研究中心新能源汽車積分政策負責人時間表示,相比真金白銀的財政補貼,新能源積分交易制度靈活性更高,是當下國家推動新能源產業發展的一種可行方式。   新能源積分交易制度,即政府將企業年度“零排放”車型的銷售情況記錄成積分,以積分為依據來考核企業在節能減排方面是否達標。若企業積分不達標,可以購買同行業其餘公司的積分,或者向政府繳納高額罰款。   中國若要實施積分交易制度 政府部門職責需合理分配。   為此,積分政策負責人時間給出幾點建議:作為政策的制定者政府,需要部門間進行合理的職責分配,物質保障方面,中國新能源基礎設施建設尚需進一步完善。意識形態方面,當前,消費者對新能源車的認識還不充足,對制度實施造成一定阻礙。企業方面,不同規模的企業須在政策上區別對待,為中小企業的發展提供空間;當企業規模有所變更時,應當提供相應的扶持政策。   文章來源:人民網

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包”嚨底家”

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

  • 特斯拉Gigafactory趕工,可望提前量產

    美國電動車商特斯拉(Tesla Motors Inc.)的平價車款「Model 3」預定2018年問世,為了趕上需求,特斯拉造價50億美元的「Gigafactory」超級電池廠正在加緊趕工,將比原定時程提前數年、明(2017)年初就可以開始量產車用鋰電池。

    華爾街日報、Electrek 24日報導,坐落於內華達州雷諾市的Gigafactory目前占地超過3,000英畝,特斯拉已加倍聘請建築工人,共計1,000名人員一週兩班每天輪流趕工,預計明年初就可大功告成。特斯拉技術長兼共同創辦人JB Straubel表示,在汽車量產前,電池和電池組的組裝廠房一定要事先完工,因此無論是建廠計畫或是電池的擴產時間表都會加快進度。

    特斯拉電池供應夥伴Panasonic Corp.已承諾要為該廠提供16億美元的資金,目前則因為找不到合適的人才而傷透腦筋。特斯拉執行長Elon Musk預估,Gigafactory完工之後,2020年將可年產105GW的電池,足以供應120萬台Model S豪華電動轎車所需,但其中有1/3將用於定置型電池儲存裝置(stationary battery storage product)。

    假如這座廠房能如期完工、擴產,則其產能將是全球現有電池廠的10倍之多,這也使得北美的鋰礦開採活動大增。

    OilPrice.com 21日報導,電池過去幾年來的需求倍數成長,鋰已成為今(2016)年來最夯的金屬、擊敗黃金,雖然最近幾個月的價格漲勢稍緩,但強勁的基本面顯示其長期前景依舊看俏。另外,在戴姆勒(Daimler)、日產汽車等業者的推波助瀾下,預估到了2016年插電式電動車銷售量(plug-in electric vehicle,簡稱PEV)有望年增62%,2017年、2018年更有望成長60%、100%。這相當於2018年會賣出60萬輛PEV。

    (本文內容由授權提供)

     

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 星際爭霸2 AI開發(持續更新)

    星際爭霸2 AI開發(持續更新)

    準備

    我的環境是python3.6,sc2包0.11.1
    機器學習包下載鏈接:
    地圖下載鏈接
    pysc2是DeepMind開發的星際爭霸Ⅱ學習環境。 它是封裝星際爭霸Ⅱ機器學習API,同時也提供Python增強學習環境。
    以神族為例編寫代碼,神族建築科技圖如下:

    採礦

    # -*- encoding: utf-8 -*-
    '''
    @File    :   __init__.py.py    
    @Modify Time      @Author       @Desciption
    ------------      -------       -----------
    2019/11/3 12:32   Jonas           None
    '''
    
    import sc2
    from sc2 import run_game, maps, Race, Difficulty
    from sc2.player import Bot, Computer
    
    
    class SentdeBot(sc2.BotAI):
        async def on_step(self, iteration: int):
            await self.distribute_workers()
    
    
    run_game(maps.get("AcidPlantLE"), [
        Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
    ],realtime = True)

    注意
    game_data.py的assert self.id != 0註釋掉
    pixel_map.py的assert self.bits_per_pixel % 8 == 0, "Unsupported pixel density"註釋掉
    否則會報錯

    運行結果如下,农民開始採礦

    可以正常採礦

    建造农民和水晶塔

    import sc2
    from sc2 import run_game, maps, Race, Difficulty
    from sc2.player import Bot, Computer
    from sc2.constants import *
    
    
    class SentdeBot(sc2.BotAI):
        async def on_step(self, iteration: int):
            await self.distribute_workers()
            await self.build_workers()
            await self.build_pylons()
    
        # 建造农民
        async def build_workers(self):
            # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
            for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                # 是否有50晶體礦
                if self.can_afford(UnitTypeId.PROBE):
                    await self.do(nexus.train(UnitTypeId.PROBE))
    
        ## 建造水晶
        async def build_pylons(self):
            ## 供應人口和現有人口之差小於5且水晶不是正在建造
            if self.supply_left<5 and not self.already_pending(UnitTypeId.PYLON):
                nexuses = self.units(UnitTypeId.NEXUS).ready
                if nexuses.exists:
                    if self.can_afford(UnitTypeId.PYLON):
                        await self.build(UnitTypeId.PYLON,near=nexuses.first)
    
    ## 啟動遊戲
    run_game(maps.get("AcidPlantLE"), [
        Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
    ],realtime = True)
    

    運行結果如下,基地造农民,农民造水晶

    收集氣體和開礦

    代碼如下

    import sc2
    from sc2 import run_game, maps, Race, Difficulty
    from sc2.player import Bot, Computer
    from sc2.constants import *
    
    
    class SentdeBot(sc2.BotAI):
        async def on_step(self, iteration: int):
            await self.distribute_workers()
            await self.build_workers()
            await self.build_pylons()
            await self.build_assimilators()
            await self.expand()
    
        # 建造农民
        async def build_workers(self):
            # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
            for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                # 是否有50晶體礦
                if self.can_afford(UnitTypeId.PROBE):
                    await self.do(nexus.train(UnitTypeId.PROBE))
    
        ## 建造水晶
        async def build_pylons(self):
            ## 供應人口和現有人口之差小於5且建築不是正在建造
            if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
                nexuses = self.units(UnitTypeId.NEXUS).ready
                if nexuses.exists:
                    if self.can_afford(UnitTypeId.PYLON):
                        await self.build(UnitTypeId.PYLON, near=nexuses.first)
    
        ## 建造吸收廠
        async def build_assimilators(self):
            for nexus in self.units(UnitTypeId.NEXUS).ready:
                # 在瓦斯泉上建造吸收廠
                vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
                for vaspene in vaspenes:
                    if not self.can_afford(UnitTypeId.ASSIMILATOR):
                        break
                    worker = self.select_build_worker(vaspene.position)
                    if worker is None:
                        break
                    if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
                        await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene))
    
        ## 開礦
        async def expand(self):
            if self.units(UnitTypeId.NEXUS).amount<3 and self.can_afford(UnitTypeId.NEXUS):
                await self.expand_now()
    
    ## 啟動遊戲
    run_game(maps.get("AcidPlantLE"), [
        Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
    ], realtime=False)
    

    run_game的realtime設置成False,可以在加速模式下運行遊戲。
    運行效果如下:

    可以建造吸收廠和開礦

    建造軍隊

    import sc2
    from sc2 import run_game, maps, Race, Difficulty
    from sc2.player import Bot, Computer
    from sc2.constants import *
    
    
    class SentdeBot(sc2.BotAI):
        async def on_step(self, iteration: int):
            await self.distribute_workers()
            await self.build_workers()
            await self.build_pylons()
            await self.build_assimilators()
            await self.expand()
            await self.offensive_force_buildings()
            await self.build_offensive_force()
    
        # 建造农民
        async def build_workers(self):
            # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
            for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                # 是否有50晶體礦
                if self.can_afford(UnitTypeId.PROBE):
                    await self.do(nexus.train(UnitTypeId.PROBE))
    
        ## 建造水晶
        async def build_pylons(self):
            ## 供應人口和現有人口之差小於5且建築不是正在建造
            if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
                nexuses = self.units(UnitTypeId.NEXUS).ready
                if nexuses.exists:
                    if self.can_afford(UnitTypeId.PYLON):
                        await self.build(UnitTypeId.PYLON, near=nexuses.first)
    
        ## 建造吸收廠
        async def build_assimilators(self):
            for nexus in self.units(UnitTypeId.NEXUS).ready:
                # 在瓦斯泉上建造吸收廠
                vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
                for vaspene in vaspenes:
                    if not self.can_afford(UnitTypeId.ASSIMILATOR):
                        break
                    worker = self.select_build_worker(vaspene.position)
                    if worker is None:
                        break
                    if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
                        await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene))
    
        ## 開礦
        async def expand(self):
            if self.units(UnitTypeId.NEXUS).amount<2 and self.can_afford(UnitTypeId.NEXUS):
                await self.expand_now()
    
        ## 建造進攻性建築
        async def offensive_force_buildings(self):
            if self.units(UnitTypeId.PYLON).ready.exists:
                pylon = self.units(UnitTypeId.PYLON).ready.random
                if self.units(UnitTypeId.PYLON).ready.exists:
                    # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
                    if self.units(UnitTypeId.GATEWAY).ready.exists:
                        if not self.units(UnitTypeId.CYBERNETICSCORE):
                            if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                                await self.build(UnitTypeId.CYBERNETICSCORE,near = pylon)
                    # 否則建造折躍門
                    else:
                        if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                            await self.build(UnitTypeId.GATEWAY,near=pylon)
    
        # 造兵
        async def build_offensive_force(self):
            # 無隊列化建造
            for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
                if self.can_afford(UnitTypeId.STALKER) and self.supply_left>0:
                    await self.do(gw.train(UnitTypeId.STALKER))
    
    
    
    ## 啟動遊戲
    run_game(maps.get("AcidPlantLE"), [
        Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Easy)
    ], realtime=False)
    

    運行結果如下:

    可以看到,我們建造了折躍門和控制核心並訓練了追獵者

    控制部隊進攻

    代碼如下

    
    import sc2
    from sc2 import run_game, maps, Race, Difficulty
    from sc2.player import Bot, Computer
    from sc2.constants import *
    import random
    
    class SentdeBot(sc2.BotAI):
        async def on_step(self, iteration: int):
            await self.distribute_workers()
            await self.build_workers()
            await self.build_pylons()
            await self.build_assimilators()
            await self.expand()
            await self.offensive_force_buildings()
            await self.build_offensive_force()
            await self.attack()
    
        # 建造农民
        async def build_workers(self):
            # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
            for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                # 是否有50晶體礦
                if self.can_afford(UnitTypeId.PROBE):
                    await self.do(nexus.train(UnitTypeId.PROBE))
    
        ## 建造水晶
        async def build_pylons(self):
            ## 供應人口和現有人口之差小於5且建築不是正在建造
            if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
                nexuses = self.units(UnitTypeId.NEXUS).ready
                if nexuses.exists:
                    if self.can_afford(UnitTypeId.PYLON):
                        await self.build(UnitTypeId.PYLON, near=nexuses.first)
    
        ## 建造吸收廠
        async def build_assimilators(self):
            for nexus in self.units(UnitTypeId.NEXUS).ready:
                # 在瓦斯泉上建造吸收廠
                vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
                for vaspene in vaspenes:
                    if not self.can_afford(UnitTypeId.ASSIMILATOR):
                        break
                    worker = self.select_build_worker(vaspene.position)
                    if worker is None:
                        break
                    if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
                        await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene))
    
        ## 開礦
        async def expand(self):
            if self.units(UnitTypeId.NEXUS).amount<3 and self.can_afford(UnitTypeId.NEXUS):
                await self.expand_now()
    
        ## 建造進攻性建築
        async def offensive_force_buildings(self):
            if self.units(UnitTypeId.PYLON).ready.exists:
                pylon = self.units(UnitTypeId.PYLON).ready.random
                # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
                if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                    if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                        await self.build(UnitTypeId.CYBERNETICSCORE,near = pylon)
                # 否則建造折躍門
                elif len(self.units(UnitTypeId.GATEWAY))<=3:
                    if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                        await self.build(UnitTypeId.GATEWAY,near=pylon)
    
        ## 造兵
        async def build_offensive_force(self):
            # 無隊列化建造
            for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
                if self.can_afford(UnitTypeId.STALKER) and self.supply_left>0:
                    await self.do(gw.train(UnitTypeId.STALKER))
    
        ## 尋找目標
        def find_target(self,state):
            if len(self.known_enemy_units)>0:
                # 隨機選取敵方單位
                return random.choice(self.known_enemy_units)
            elif len(self.known_enemy_units)>0:
                # 隨機選取敵方建築
                return random.choice(self.known_enemy_structures)
            else:
                # 返回敵方出生點位
                return self.enemy_start_locations[0]
    
        ## 進攻
        async def attack(self):
            # 追獵者數量超過15個開始進攻
            if self.units(UnitTypeId.STALKER).amount>15:
                for s in self.units(UnitTypeId.STALKER).idle:
                    await self.do(s.attack(self.find_target(self.state)))
    
            # 防衛模式:視野範圍內存在敵人,開始攻擊
            if self.units(UnitTypeId.STALKER).amount>5:
                if len(self.known_enemy_units)>0:
                    for s in self.units(UnitTypeId.STALKER).idle:
                        await self.do(s.attack(random.choice(self.known_enemy_units)))
    
    ## 啟動遊戲
    run_game(maps.get("AcidPlantLE"), [
        Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Medium)
    ], realtime=False)
    

    運行結果如下

    可以看到,4個折躍門訓練追獵者並發動進攻。

    擊敗困難電腦

    我們目前的代碼只能擊敗中等和簡單電腦,那麼如何擊敗困難電腦呢?
    代碼如下

    
    import sc2
    from sc2 import run_game, maps, Race, Difficulty
    from sc2.player import Bot, Computer
    from sc2.constants import *
    import random
    
    
    class SentdeBot(sc2.BotAI):
        def __init__(self):
            # 經過計算,每分鐘大約165迭代次數
            self.ITERATIONS_PER_MINUTE = 165
            # 最大农民數量
            self.MAX_WORKERS = 65
    
        async def on_step(self, iteration: int):
            self.iteration = iteration
            await self.distribute_workers()
            await self.build_workers()
            await self.build_pylons()
            await self.build_assimilators()
            await self.expand()
            await self.offensive_force_buildings()
            await self.build_offensive_force()
            await self.attack()
    
        # 建造农民
        async def build_workers(self):
            # 星靈樞鈕*16(一個基地配備16個农民)大於農民數量並且現有农民數量小於MAX_WORKERS
            if len(self.units(UnitTypeId.NEXUS))*16>len(self.units(UnitTypeId.PROBE)) and len(self.units(UnitTypeId.PROBE))<self.MAX_WORKERS:
                    # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
                    for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                        # 是否有50晶體礦建造农民
                        if self.can_afford(UnitTypeId.PROBE):
                            await self.do(nexus.train(UnitTypeId.PROBE))
    
        ## 建造水晶
        async def build_pylons(self):
            ## 供應人口和現有人口之差小於5且建築不是正在建造
            if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
                nexuses = self.units(UnitTypeId.NEXUS).ready
                if nexuses.exists:
                    if self.can_afford(UnitTypeId.PYLON):
                        await self.build(UnitTypeId.PYLON, near=nexuses.first)
    
        ## 建造吸收廠
        async def build_assimilators(self):
            for nexus in self.units(UnitTypeId.NEXUS).ready:
                # 在瓦斯泉上建造吸收廠
                vaspenes = self.state.vespene_geyser.closer_than(15.0,nexus)
                for vaspene in vaspenes:
                    if not self.can_afford(UnitTypeId.ASSIMILATOR):
                        break
                    worker = self.select_build_worker(vaspene.position)
                    if worker is None:
                        break
                    if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0,vaspene).exists:
                        await self.do(worker.build(UnitTypeId.ASSIMILATOR,vaspene))
    
        ## 開礦
        async def expand(self):
            # (self.iteration / self.ITERATIONS_PER_MINUTE)是一個緩慢遞增的值,動態開礦
            if self.units(UnitTypeId.NEXUS).amount<self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(UnitTypeId.NEXUS):
                await self.expand_now()
    
        ## 建造進攻性建築
        async def offensive_force_buildings(self):
            print(self.iteration / self.ITERATIONS_PER_MINUTE)
            if self.units(UnitTypeId.PYLON).ready.exists:
                pylon = self.units(UnitTypeId.PYLON).ready.random
                # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
                if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                    if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                        await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
                # 否則建造折躍門
                # (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一個緩慢遞增的值
                elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                    if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                        await self.build(UnitTypeId.GATEWAY, near=pylon)
                # 控制核心存在的情況下建造星門
                if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                    if len(self.units(UnitTypeId.STARGATE)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                        if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                            await self.build(UnitTypeId.STARGATE, near=pylon)
    
        ## 造兵
        async def build_offensive_force(self):
            # 無隊列化建造
            for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
                if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount:
    
                    if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
                        await self.do(gw.train(UnitTypeId.STALKER))
    
            for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
                if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                    await self.do(sg.train(UnitTypeId.VOIDRAY))
    
        ## 尋找目標
        def find_target(self,state):
            if len(self.known_enemy_units)>0:
                # 隨機選取敵方單位
                return random.choice(self.known_enemy_units)
            elif len(self.known_enemy_units)>0:
                # 隨機選取敵方建築
                return random.choice(self.known_enemy_structures)
            else:
                # 返回敵方出生點位
                return self.enemy_start_locations[0]
    
        ## 進攻
        async def attack(self):
            # {UNIT: [n to fight, n to defend]}
            aggressive_units = {UnitTypeId.STALKER: [15, 5],
                                UnitTypeId.VOIDRAY: [8, 3]}
    
            for UNIT in aggressive_units:
                # 攻擊模式
                if self.units(UNIT).amount > aggressive_units[UNIT][0] and self.units(UNIT).amount > aggressive_units[UNIT][
                    1]:
                    for s in self.units(UNIT).idle:
                        await self.do(s.attack(self.find_target(self.state)))
                # 防衛模式
                elif self.units(UNIT).amount > aggressive_units[UNIT][1]:
                    if len(self.known_enemy_units) > 0:
                        for s in self.units(UNIT).idle:
                            await self.do(s.attack(random.choice(self.known_enemy_units)))
    ## 啟動遊戲
    run_game(maps.get("AcidPlantLE"), [
        Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Hard)
    ], realtime=False)
    

    運行結果如下

    可以看到,擊敗了困難人族電腦,但是電腦選擇了rush戰術,我們寫得AI腳本會輸掉遊戲。顯然,這不是最佳方案。
    “只有AI才能拯救我的勝率”,請看下文。

    採集地圖數據

    這次我們只造一個折躍門,全力通過星門造虛空光輝艦
    修改offensive_force_buildings(self)方法的判斷

    elif len(self.units(GATEWAY)) < 1:
                    if self.can_afford(GATEWAY) and not self.already_pending(GATEWAY):
                        await self.build(GATEWAY, near=pylon)

    註釋或者刪除build_offensive_force(self)的建造追獵者的代碼

            ## 造兵
        async def build_offensive_force(self):
            # 無隊列化建造
            # for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
            #     if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount:
            #
            #         if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
            #             await self.do(gw.train(UnitTypeId.STALKER))
    
            for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
                if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                    await self.do(sg.train(UnitTypeId.VOIDRAY))

    attack(self)中的aggressive_units註釋掉Stalker
    導入numpy和cv2庫

    game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)

    建立以地圖Heigt為行,Width為列的三維矩陣

    for nexus in self.units(NEXUS):
                nex_pos = nexus.position
                print(nex_pos)
                cv2.circle(game_data, (int(nex_pos[0]), int(nex_pos[1])), 10, (0, 255, 0), -1)  # BGR

    遍歷星靈樞紐,獲取下一個位置,畫圓,circle(承載圓的img, 圓心, 半徑, 顏色, thickness=-1表示填充)
    接下來我們要垂直翻轉三維矩陣,因為我們建立的矩陣左上角是原點(0,0),縱坐標向下延申,橫坐標向右延申。翻轉之後就成了正常的坐標系。

    flipped = cv2.flip(game_data, 0)

    圖像縮放,達到可視化最佳。

            resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
            cv2.imshow('Intel', resized)
            cv2.waitKey(1)

    至此,完整代碼如下

    import sc2
    from sc2 import run_game, maps, Race, Difficulty
    from sc2.player import Bot, Computer
    from sc2.constants import *
    import random
    import numpy as np
    import cv2
    
    
    class SentdeBot(sc2.BotAI):
        def __init__(self):
            # 經過計算,每分鐘大約165迭代次數
            self.ITERATIONS_PER_MINUTE = 165
            # 最大农民數量
            self.MAX_WORKERS = 65
    
        async def on_step(self, iteration: int):
            self.iteration = iteration
            await self.distribute_workers()
            await self.build_workers()
            await self.build_pylons()
            await self.build_assimilators()
            await self.expand()
            await self.offensive_force_buildings()
            await self.build_offensive_force()
            await self.intel()
            await self.attack()
    
        async def intel(self):
            # 根據地圖建立的三維矩陣
            game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)
            for nexus in self.units(UnitTypeId.NEXUS):
                nex_pos = nexus.position
                # circle(承載圓的img, 圓心, 半徑, 顏色, thickness=-1表示填充)
                # 記錄星靈樞紐的位置
                cv2.circle(game_data, (int(nex_pos[0]), int(nex_pos[1])), 10, (0, 255, 0), -1)
            # 圖像翻轉垂直鏡像
            flipped = cv2.flip(game_data, 0)
            # 圖像縮放
            # cv2.resize(原圖像,輸出圖像的大小,width方向的縮放比例,height方向縮放的比例)
            resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
            cv2.imshow('Intel', resized)
    
            # cv2.waitKey(每Xms刷新圖像)
            cv2.waitKey(1)
    
        # 建造农民
        async def build_workers(self):
            # 星靈樞鈕*16(一個基地配備16個农民)大於農民數量並且現有农民數量小於MAX_WORKERS
            if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
                    self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
                # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
                for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                    # 是否有50晶體礦建造农民
                    if self.can_afford(UnitTypeId.PROBE):
                        await self.do(nexus.train(UnitTypeId.PROBE))
    
        ## 建造水晶
        async def build_pylons(self):
            ## 供應人口和現有人口之差小於5且建築不是正在建造
            if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
                nexuses = self.units(UnitTypeId.NEXUS).ready
                if nexuses.exists:
                    if self.can_afford(UnitTypeId.PYLON):
                        await self.build(UnitTypeId.PYLON, near=nexuses.first)
    
        ## 建造吸收廠
        async def build_assimilators(self):
            for nexus in self.units(UnitTypeId.NEXUS).ready:
                # 在瓦斯泉上建造吸收廠
                vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
                for vaspene in vaspenes:
                    if not self.can_afford(UnitTypeId.ASSIMILATOR):
                        break
                    worker = self.select_build_worker(vaspene.position)
                    if worker is None:
                        break
                    if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
                        await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene))
    
        ## 開礦
        async def expand(self):
            # (self.iteration / self.ITERATIONS_PER_MINUTE)是一個緩慢遞增的值,動態開礦
            if self.units(UnitTypeId.NEXUS).amount < self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(
                    UnitTypeId.NEXUS):
                await self.expand_now()
    
        ## 建造進攻性建築
        async def offensive_force_buildings(self):
            print(self.iteration / self.ITERATIONS_PER_MINUTE)
            if self.units(UnitTypeId.PYLON).ready.exists:
                pylon = self.units(UnitTypeId.PYLON).ready.random
                # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
                if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                    if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                        await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
                # 否則建造折躍門
                # (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一個緩慢遞增的值
                # elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                elif len(self.units(UnitTypeId.GATEWAY)) < 1:
                    if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                        await self.build(UnitTypeId.GATEWAY, near=pylon)
                # 控制核心存在的情況下建造星門
                if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                    if len(self.units(UnitTypeId.STARGATE)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                        if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                            await self.build(UnitTypeId.STARGATE, near=pylon)
    
        ## 造兵
        async def build_offensive_force(self):
            # 無隊列化建造
            for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
                if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                    await self.do(sg.train(UnitTypeId.VOIDRAY))
    
        ## 尋找目標
        def find_target(self, state):
            if len(self.known_enemy_units) > 0:
                # 隨機選取敵方單位
                return random.choice(self.known_enemy_units)
            elif len(self.known_enemy_units) > 0:
                # 隨機選取敵方建築
                return random.choice(self.known_enemy_structures)
            else:
                # 返回敵方出生點位
                return self.enemy_start_locations[0]
    
        ## 進攻
        async def attack(self):
            # {UNIT: [n to fight, n to defend]}
            aggressive_units = {UnitTypeId.VOIDRAY: [8, 3]}
    
            for UNIT in aggressive_units:
                # 攻擊模式
                if self.units(UNIT).amount > aggressive_units[UNIT][0] and self.units(UNIT).amount > aggressive_units[UNIT][1]:
                    for s in self.units(UNIT).idle:
                        await self.do(s.attack(self.find_target(self.state)))
                # 防衛模式
                elif self.units(UNIT).amount > aggressive_units[UNIT][1]:
                    if len(self.known_enemy_units) > 0:
                        for s in self.units(UNIT).idle:
                            await self.do(s.attack(random.choice(self.known_enemy_units)))
    
    
    ## 啟動遊戲
    run_game(maps.get("AcidPlantLE"), [
        Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Hard)
    ], realtime=False)
    

    運行結果如下

    採集到了地圖位置。

    偵察

    在intel(self)里創建一個字典draw_dict,UnitTypeId作為key,半徑和顏色是value

    
            draw_dict = {
                UnitTypeId.NEXUS: [15, (0, 255, 0)],
                UnitTypeId.PYLON: [3, (20, 235, 0)],
                UnitTypeId.PROBE: [1, (55, 200, 0)],
                UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
                UnitTypeId.GATEWAY: [3, (200, 100, 0)],
                UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
                UnitTypeId.STARGATE: [5, (255, 0, 0)],
                UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],
    
                UnitTypeId.VOIDRAY: [3, (255, 100, 0)]
            }

    迭代同上

    for unit_type in draw_dict:
                for unit in self.units(unit_type).ready:
                    pos = unit.position
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), draw_dict[unit_type][0], draw_dict[unit_type][1], -1)

    存儲三族的主基地名稱(星靈樞紐,指揮中心,孵化場),刻畫敵方建築。

    # 主基地名稱
            main_base_names = ["nexus", "supplydepot", "hatchery"]
            # 記錄敵方基地位置
            for enemy_building in self.known_enemy_structures:
                pos = enemy_building.position
                if enemy_building.name.lower() not in main_base_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (200, 50, 212), -1)
            for enemy_building in self.known_enemy_structures:
                pos = enemy_building.position
                if enemy_building.name.lower() in main_base_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 15, (0, 0, 255), -1)

    刻畫敵方單位,如果是农民畫得小些,其他單位則畫大些。

            for enemy_unit in self.known_enemy_units:
    
                if not enemy_unit.is_structure:
                    worker_names = ["probe", "scv", "drone"]
                    # if that unit is a PROBE, SCV, or DRONE... it's a worker
                    pos = enemy_unit.position
                    if enemy_unit.name.lower() in worker_names:
                        cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (55, 0, 155), -1)
                    else:
                        cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (50, 0, 215), -1)

    在offensive_force_buildings(self)方法中添加建造机械台

                if self.units(CYBERNETICSCORE).ready.exists:
                    if len(self.units(ROBOTICSFACILITY)) < 1:
                        if self.can_afford(ROBOTICSFACILITY) and not self.already_pending(ROBOTICSFACILITY):
                            await self.build(ROBOTICSFACILITY, near=pylon)

    創建scout(),訓練Observer

    async def scout(self):
            if len(self.units(OBSERVER)) > 0:
                scout = self.units(OBSERVER)[0]
                if scout.is_idle:
                    enemy_location = self.enemy_start_locations[0]
                    move_to = self.random_location_variance(enemy_location)
                    print(move_to)
                    await self.do(scout.move(move_to))
    
            else:
                for rf in self.units(ROBOTICSFACILITY).ready.noqueue:
                    if self.can_afford(OBSERVER) and self.supply_left > 0:
                        await self.do(rf.train(OBSERVER))

    生成隨機位置,很簡單。意思是橫坐標累計遞增-0.2和0.2倍的橫坐標,限制條件為如果x超過橫坐標,那麼就是橫坐標最大值。
    縱坐標同理。

        def random_location_variance(self, enemy_start_location):
            x = enemy_start_location[0]
            y = enemy_start_location[1]
    
            x += ((random.randrange(-20, 20))/100) * enemy_start_location[0]
            y += ((random.randrange(-20, 20))/100) * enemy_start_location[1]
    
            if x < 0:
                x = 0
            if y < 0:
                y = 0
            if x > self.game_info.map_size[0]:
                x = self.game_info.map_size[0]
            if y > self.game_info.map_size[1]:
                y = self.game_info.map_size[1]
    
            go_to = position.Point2(position.Pointlike((x,y)))
            return go_to

    完整代碼如下

    # -*- encoding: utf-8 -*-
    '''
    @File    :   demo.py
    @Modify Time      @Author       @Desciption
    ------------      -------       -----------
    2019/11/3 12:32   Jonas           None
    '''
    
    import sc2
    from sc2 import run_game, maps, Race, Difficulty, position
    from sc2.player import Bot, Computer
    from sc2.constants import *
    import random
    import numpy as np
    import cv2
    
    
    class SentdeBot(sc2.BotAI):
        def __init__(self):
            # 經過計算,每分鐘大約165迭代次數
            self.ITERATIONS_PER_MINUTE = 165
            # 最大农民數量
            self.MAX_WORKERS = 50
    
        async def on_step(self, iteration: int):
            self.iteration = iteration
            await self.scout()
            await self.distribute_workers()
            await self.build_workers()
            await self.build_pylons()
            await self.build_assimilators()
            await self.expand()
            await self.offensive_force_buildings()
            await self.build_offensive_force()
            await self.intel()
            await self.attack()
    
        ## 偵察
        async def scout(self):
            if len(self.units(UnitTypeId.OBSERVER)) > 0:
                scout = self.units(UnitTypeId.OBSERVER)[0]
                if scout.is_idle:
                    enemy_location = self.enemy_start_locations[0]
                    move_to = self.random_location_variance(enemy_location)
                    print(move_to)
                    await self.do(scout.move(move_to))
    
            else:
                for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
                    if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
                        await self.do(rf.train(UnitTypeId.OBSERVER))
    
        async def intel(self):
            game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)
    
            # UnitTypeId作為key,半徑和顏色是value
            draw_dict = {
                UnitTypeId.NEXUS: [15, (0, 255, 0)],
                UnitTypeId.PYLON: [3, (20, 235, 0)],
                UnitTypeId.PROBE: [1, (55, 200, 0)],
                UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
                UnitTypeId.GATEWAY: [3, (200, 100, 0)],
                UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
                UnitTypeId.STARGATE: [5, (255, 0, 0)],
                UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],
    
                UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
                # OBSERVER: [3, (255, 255, 255)],
            }
    
            for unit_type in draw_dict:
                for unit in self.units(unit_type).ready:
                    pos = unit.position
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), draw_dict[unit_type][0], draw_dict[unit_type][1], -1)
    
            # 主基地名稱
            main_base_names = ["nexus", "supplydepot", "hatchery"]
            # 記錄敵方基地位置
            for enemy_building in self.known_enemy_structures:
                pos = enemy_building.position
                # 不是主基地建築,畫小一些
                if enemy_building.name.lower() not in main_base_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (200, 50, 212), -1)
            for enemy_building in self.known_enemy_structures:
                pos = enemy_building.position
                if enemy_building.name.lower() in main_base_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 15, (0, 0, 255), -1)
    
            for enemy_unit in self.known_enemy_units:
    
                if not enemy_unit.is_structure:
                    worker_names = ["probe", "scv", "drone"]
                    # if that unit is a PROBE, SCV, or DRONE... it's a worker
                    pos = enemy_unit.position
                    if enemy_unit.name.lower() in worker_names:
                        cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (55, 0, 155), -1)
                    else:
                        cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (50, 0, 215), -1)
    
            for obs in self.units(UnitTypeId.OBSERVER).ready:
                pos = obs.position
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 255, 255), -1)
    
            # flip horizontally to make our final fix in visual representation:
            flipped = cv2.flip(game_data, 0)
            resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)
    
            cv2.imshow('Intel', resized)
            cv2.waitKey(1)
    
        def random_location_variance(self, enemy_start_location):
            x = enemy_start_location[0]
            y = enemy_start_location[1]
    
            x += ((random.randrange(-20, 20)) / 100) * enemy_start_location[0]
            y += ((random.randrange(-20, 20)) / 100) * enemy_start_location[1]
    
            if x < 0:
                x = 0
            if y < 0:
                y = 0
            if x > self.game_info.map_size[0]:
                x = self.game_info.map_size[0]
            if y > self.game_info.map_size[1]:
                y = self.game_info.map_size[1]
    
            go_to = position.Point2(position.Pointlike((x, y)))
            return go_to
    
        # 建造农民
        async def build_workers(self):
            # 星靈樞鈕*16(一個基地配備16個农民)大於農民數量並且現有农民數量小於MAX_WORKERS
            if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
                    self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
                # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
                for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                    # 是否有50晶體礦建造农民
                    if self.can_afford(UnitTypeId.PROBE):
                        await self.do(nexus.train(UnitTypeId.PROBE))
    
        ## 建造水晶
        async def build_pylons(self):
            ## 供應人口和現有人口之差小於5且建築不是正在建造
            if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
                nexuses = self.units(UnitTypeId.NEXUS).ready
                if nexuses.exists:
                    if self.can_afford(UnitTypeId.PYLON):
                        await self.build(UnitTypeId.PYLON, near=nexuses.first)
    
        ## 建造吸收廠
        async def build_assimilators(self):
            for nexus in self.units(UnitTypeId.NEXUS).ready:
                # 在瓦斯泉上建造吸收廠
                vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
                for vaspene in vaspenes:
                    if not self.can_afford(UnitTypeId.ASSIMILATOR):
                        break
                    worker = self.select_build_worker(vaspene.position)
                    if worker is None:
                        break
                    if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
                        await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene))
    
        ## 開礦
        async def expand(self):
            # (self.iteration / self.ITERATIONS_PER_MINUTE)是一個緩慢遞增的值,動態開礦
            if self.units(UnitTypeId.NEXUS).amount < self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(
                    UnitTypeId.NEXUS):
                await self.expand_now()
    
        ## 建造進攻性建築
        async def offensive_force_buildings(self):
            print(self.iteration / self.ITERATIONS_PER_MINUTE)
            if self.units(UnitTypeId.PYLON).ready.exists:
                pylon = self.units(UnitTypeId.PYLON).ready.random
                # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
                if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                    if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                        await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
                # 否則建造折躍門
                # (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一個緩慢遞增的值
                # elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                elif len(self.units(UnitTypeId.GATEWAY)) < 1:
                    if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                        await self.build(UnitTypeId.GATEWAY, near=pylon)
                # 控制核心存在的情況下建造机械台
                if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                    if len(self.units(UnitTypeId.ROBOTICSFACILITY)) < 1:
                        if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(
                                UnitTypeId.ROBOTICSFACILITY):
                            await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)
    
                # 控制核心存在的情況下建造星門
                if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                    if len(self.units(UnitTypeId.STARGATE)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                        if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                            await self.build(UnitTypeId.STARGATE, near=pylon)
    
        ## 造兵
        async def build_offensive_force(self):
            # 無隊列化建造
            # for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
            #     if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount:
            #
            #         if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
            #             await self.do(gw.train(UnitTypeId.STALKER))
    
            for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
                if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                    await self.do(sg.train(UnitTypeId.VOIDRAY))
    
        ## 尋找目標
        def find_target(self, state):
            if len(self.known_enemy_units) > 0:
                # 隨機選取敵方單位
                return random.choice(self.known_enemy_units)
            elif len(self.known_enemy_units) > 0:
                # 隨機選取敵方建築
                return random.choice(self.known_enemy_structures)
            else:
                # 返回敵方出生點位
                return self.enemy_start_locations[0]
    
        ## 進攻
        async def attack(self):
            # {UNIT: [n to fight, n to defend]}
            aggressive_units = {UnitTypeId.VOIDRAY: [8, 3]}
    
            for UNIT in aggressive_units:
                # 攻擊模式
                if self.units(UNIT).amount > aggressive_units[UNIT][0] and self.units(UNIT).amount > aggressive_units[UNIT][
                    1]:
                    for s in self.units(UNIT).idle:
                        await self.do(s.attack(self.find_target(self.state)))
                # 防衛模式
                elif self.units(UNIT).amount > aggressive_units[UNIT][1]:
                    if len(self.known_enemy_units) > 0:
                        for s in self.units(UNIT).idle:
                            await self.do(s.attack(random.choice(self.known_enemy_units)))
    
    
    ## 啟動遊戲
    run_game(maps.get("AcidPlantLE"), [
        Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Hard)
    ], realtime=False)
    

    運行結果如下,紅色和粉紅色是敵方單位。

    創建訓練數據

    統計資源、人口和軍隊人口比,在intel方法添加如下代碼

            # 追蹤資源、人口和軍隊人口比
            line_max = 50
            mineral_ratio = self.minerals / 1500
            if mineral_ratio > 1.0:
                mineral_ratio = 1.0
    
            vespene_ratio = self.vespene / 1500
            if vespene_ratio > 1.0:
                vespene_ratio = 1.0
    
            population_ratio = self.supply_left / self.supply_cap
            if population_ratio > 1.0:
                population_ratio = 1.0
    
            plausible_supply = self.supply_cap / 200.0
    
            military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
            if military_weight > 1.0:
                military_weight = 1.0
    
            # 农民/人口      worker/supply ratio
            cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
            # 人口/200    plausible supply (supply/200.0)
            cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
            # (人口-現有人口)/人口  population ratio (supply_left/supply)
            cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
            # 氣體/1500   gas/1500
            cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
            # 晶體礦/1500  minerals minerals/1500
            cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)

    運行結果如下,左下角自上而下依次是“农民/人口”,“人口/200”,“(人口-現有人口)/人口”,“氣體/1500”,“晶體礦/1500”

    採集進攻行為數據,在attack方法中加入如下代碼

            if len(self.units(UnitTypeId.VOIDRAY).idle) > 0:
                choice = random.randrange(0, 4)
                target = False
                if self.iteration > self.do_something_after:
                    if choice == 0:
                        # 什麼都不做
                        wait = random.randrange(20, 165)
                        self.do_something_after = self.iteration + wait
    
                    elif choice == 1:
                        # 攻擊離星靈樞紐最近的單位
                        if len(self.known_enemy_units) > 0:
                            target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
    
                    elif choice == 2:
                        # 攻擊敵方建築
                        if len(self.known_enemy_structures) > 0:
                            target = random.choice(self.known_enemy_structures)
    
                    elif choice == 3:
                        # 攻擊敵方出生位置
                        target = self.enemy_start_locations[0]
    
                    if target:
                        for vr in self.units(UnitTypeId.VOIDRAY).idle:
                            await self.do(vr.attack(target))
                    y = np.zeros(4)
                    y[choice] = 1
                    print(y)
                    self.train_data.append([y, self.flipped])

    輸出如下結果

    ···
    [1. 0. 0. 0.]
    [0. 0. 1. 0.]
    [0. 0. 0. 1.]
    [0. 0. 1. 0.]
    [1. 0. 0. 0.]
    ···

    為了使用self.flipped = cv2.flip(game_data, 0),修改

            flipped = cv2.flip(game_data, 0)
            resized = cv2.resize(flipped, dsize=None, fx=2, fy=2)

            self.flipped = cv2.flip(game_data, 0)
            resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)

    init 方法添加do_something_after和train_data

        def __init__(self):
            self.ITERATIONS_PER_MINUTE = 165
            self.MAX_WORKERS = 50
            self.do_something_after = 0
            self.train_data = []

    採集攻擊數據的時候不需要畫圖,我們在類前加HEADLESS = False,intel方法代碼修改如下

            self.flipped = cv2.flip(game_data, 0)
    
            if not HEADLESS:
                resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)
                cv2.imshow('Intel', resized)
                cv2.waitKey(1)

    加入on_end方法,只存儲勝利的數據,在和代碼同級目錄新建train_data文件夾

        def on_end(self, game_result):
            print('--- on_end called ---')
            print(game_result)
    
            if game_result == Result.Victory:
                np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data))

    完整代碼如下

    import os
    import time
    
    import sc2
    from sc2 import run_game, maps, Race, Difficulty, position, Result
    from sc2.player import Bot, Computer
    from sc2.constants import *
    import random
    import numpy as np
    import cv2
    
    HEADLESS = True
    # os.environ["SC2PATH"] = 'F:\StarCraft II'
    
    class SentdeBot(sc2.BotAI):
        def __init__(self):
            # 經過計算,每分鐘大約165迭代次數
            self.ITERATIONS_PER_MINUTE = 165
            # 最大农民數量
            self.MAX_WORKERS = 50
            self.do_something_after = 0
            self.train_data = []
    
        def on_end(self, game_result):
            print('--- on_end called ---')
            print(game_result)
    
            if game_result == Result.Victory:
                np.save("train_data/{}.npy".format(str(int(time.time()))), np.array(self.train_data))
    
        async def on_step(self, iteration: int):
            self.iteration = iteration
            await self.scout()
            await self.distribute_workers()
            await self.build_workers()
            await self.build_pylons()
            await self.build_assimilators()
            await self.expand()
            await self.offensive_force_buildings()
            await self.build_offensive_force()
            await self.intel()
            await self.attack()
    
        ## 偵察
        async def scout(self):
            if len(self.units(UnitTypeId.OBSERVER)) > 0:
                scout = self.units(UnitTypeId.OBSERVER)[0]
                if scout.is_idle:
                    enemy_location = self.enemy_start_locations[0]
                    move_to = self.random_location_variance(enemy_location)
                    print(move_to)
                    await self.do(scout.move(move_to))
    
            else:
                for rf in self.units(UnitTypeId.ROBOTICSFACILITY).ready.noqueue:
                    if self.can_afford(UnitTypeId.OBSERVER) and self.supply_left > 0:
                        await self.do(rf.train(UnitTypeId.OBSERVER))
    
        async def intel(self):
            game_data = np.zeros((self.game_info.map_size[1], self.game_info.map_size[0], 3), np.uint8)
    
            # UnitTypeId作為key,半徑和顏色是value
            draw_dict = {
                UnitTypeId.NEXUS: [15, (0, 255, 0)],
                UnitTypeId.PYLON: [3, (20, 235, 0)],
                UnitTypeId.PROBE: [1, (55, 200, 0)],
                UnitTypeId.ASSIMILATOR: [2, (55, 200, 0)],
                UnitTypeId.GATEWAY: [3, (200, 100, 0)],
                UnitTypeId.CYBERNETICSCORE: [3, (150, 150, 0)],
                UnitTypeId.STARGATE: [5, (255, 0, 0)],
                UnitTypeId.ROBOTICSFACILITY: [5, (215, 155, 0)],
    
                UnitTypeId.VOIDRAY: [3, (255, 100, 0)],
                # OBSERVER: [3, (255, 255, 255)],
            }
    
            for unit_type in draw_dict:
                for unit in self.units(unit_type).ready:
                    pos = unit.position
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), draw_dict[unit_type][0], draw_dict[unit_type][1], -1)
    
            # 主基地名稱
            main_base_names = ["nexus", "supplydepot", "hatchery"]
            # 記錄敵方基地位置
            for enemy_building in self.known_enemy_structures:
                pos = enemy_building.position
                # 不是主基地建築,畫小一些
                if enemy_building.name.lower() not in main_base_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 5, (200, 50, 212), -1)
            for enemy_building in self.known_enemy_structures:
                pos = enemy_building.position
                if enemy_building.name.lower() in main_base_names:
                    cv2.circle(game_data, (int(pos[0]), int(pos[1])), 15, (0, 0, 255), -1)
    
            for enemy_unit in self.known_enemy_units:
    
                if not enemy_unit.is_structure:
                    worker_names = ["probe", "scv", "drone"]
                    # if that unit is a PROBE, SCV, or DRONE... it's a worker
                    pos = enemy_unit.position
                    if enemy_unit.name.lower() in worker_names:
                        cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (55, 0, 155), -1)
                    else:
                        cv2.circle(game_data, (int(pos[0]), int(pos[1])), 3, (50, 0, 215), -1)
    
            for obs in self.units(UnitTypeId.OBSERVER).ready:
                pos = obs.position
                cv2.circle(game_data, (int(pos[0]), int(pos[1])), 1, (255, 255, 255), -1)
    
    
            # 追蹤資源、人口和軍隊人口比
            line_max = 50
            mineral_ratio = self.minerals / 1500
            if mineral_ratio > 1.0:
                mineral_ratio = 1.0
    
            vespene_ratio = self.vespene / 1500
            if vespene_ratio > 1.0:
                vespene_ratio = 1.0
    
            population_ratio = self.supply_left / self.supply_cap
            if population_ratio > 1.0:
                population_ratio = 1.0
    
            plausible_supply = self.supply_cap / 200.0
    
            military_weight = len(self.units(UnitTypeId.VOIDRAY)) / (self.supply_cap - self.supply_left)
            if military_weight > 1.0:
                military_weight = 1.0
    
            # 农民/人口      worker/supply ratio
            cv2.line(game_data, (0, 19), (int(line_max * military_weight), 19), (250, 250, 200), 3)
            # 人口/200    plausible supply (supply/200.0)
            cv2.line(game_data, (0, 15), (int(line_max * plausible_supply), 15), (220, 200, 200), 3)
            # (人口-現有人口)/人口  population ratio (supply_left/supply)
            cv2.line(game_data, (0, 11), (int(line_max * population_ratio), 11), (150, 150, 150), 3)
            # 氣體/1500   gas/1500
            cv2.line(game_data, (0, 7), (int(line_max * vespene_ratio), 7), (210, 200, 0), 3)
            # 晶體礦/1500  minerals minerals/1500
            cv2.line(game_data, (0, 3), (int(line_max * mineral_ratio), 3), (0, 255, 25), 3)
    
    
    
    
            # flip horizontally to make our final fix in visual representation:
            self.flipped = cv2.flip(game_data, 0)
    
            if HEADLESS:
                resized = cv2.resize(self.flipped, dsize=None, fx=2, fy=2)
    
                cv2.imshow('Intel', resized)
                cv2.waitKey(1)
    
        def random_location_variance(self, enemy_start_location):
            x = enemy_start_location[0]
            y = enemy_start_location[1]
    
            x += ((random.randrange(-20, 20)) / 100) * enemy_start_location[0]
            y += ((random.randrange(-20, 20)) / 100) * enemy_start_location[1]
    
            if x < 0:
                x = 0
            if y < 0:
                y = 0
            if x > self.game_info.map_size[0]:
                x = self.game_info.map_size[0]
            if y > self.game_info.map_size[1]:
                y = self.game_info.map_size[1]
    
            go_to = position.Point2(position.Pointlike((x, y)))
            return go_to
    
        # 建造农民
        async def build_workers(self):
            # 星靈樞鈕*16(一個基地配備16個农民)大於農民數量並且現有农民數量小於MAX_WORKERS
            if len(self.units(UnitTypeId.NEXUS)) * 16 > len(self.units(UnitTypeId.PROBE)) and len(
                    self.units(UnitTypeId.PROBE)) < self.MAX_WORKERS:
                # 星靈樞紐(NEXUS)無隊列建造,可以提高晶體礦的利用率,不至於佔用資源
                for nexus in self.units(UnitTypeId.NEXUS).ready.noqueue:
                    # 是否有50晶體礦建造农民
                    if self.can_afford(UnitTypeId.PROBE):
                        await self.do(nexus.train(UnitTypeId.PROBE))
    
        ## 建造水晶
        async def build_pylons(self):
            ## 供應人口和現有人口之差小於5且建築不是正在建造
            if self.supply_left < 5 and not self.already_pending(UnitTypeId.PYLON):
                nexuses = self.units(UnitTypeId.NEXUS).ready
                if nexuses.exists:
                    if self.can_afford(UnitTypeId.PYLON):
                        await self.build(UnitTypeId.PYLON, near=nexuses.first)
    
        ## 建造吸收廠
        async def build_assimilators(self):
            for nexus in self.units(UnitTypeId.NEXUS).ready:
                # 在瓦斯泉上建造吸收廠
                vaspenes = self.state.vespene_geyser.closer_than(15.0, nexus)
                for vaspene in vaspenes:
                    if not self.can_afford(UnitTypeId.ASSIMILATOR):
                        break
                    worker = self.select_build_worker(vaspene.position)
                    if worker is None:
                        break
                    if not self.units(UnitTypeId.ASSIMILATOR).closer_than(1.0, vaspene).exists:
                        await self.do(worker.build(UnitTypeId.ASSIMILATOR, vaspene))
    
        ## 開礦
        async def expand(self):
            # (self.iteration / self.ITERATIONS_PER_MINUTE)是一個緩慢遞增的值,動態開礦
            if self.units(UnitTypeId.NEXUS).amount < self.iteration / self.ITERATIONS_PER_MINUTE and self.can_afford(
                    UnitTypeId.NEXUS):
                await self.expand_now()
    
        ## 建造進攻性建築
        async def offensive_force_buildings(self):
            # print(self.iteration / self.ITERATIONS_PER_MINUTE)
            if self.units(UnitTypeId.PYLON).ready.exists:
                pylon = self.units(UnitTypeId.PYLON).ready.random
                # 根據神族建築科技圖,折躍門建造過後才可以建造控制核心
                if self.units(UnitTypeId.GATEWAY).ready.exists and not self.units(UnitTypeId.CYBERNETICSCORE):
                    if self.can_afford(UnitTypeId.CYBERNETICSCORE) and not self.already_pending(UnitTypeId.CYBERNETICSCORE):
                        await self.build(UnitTypeId.CYBERNETICSCORE, near=pylon)
                # 否則建造折躍門
                # (self.iteration / self.ITERATIONS_PER_MINUTE)/2 是一個緩慢遞增的值
                # elif len(self.units(UnitTypeId.GATEWAY)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                elif len(self.units(UnitTypeId.GATEWAY)) < 1:
                    if self.can_afford(UnitTypeId.GATEWAY) and not self.already_pending(UnitTypeId.GATEWAY):
                        await self.build(UnitTypeId.GATEWAY, near=pylon)
                # 控制核心存在的情況下建造机械台
                if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                    if len(self.units(UnitTypeId.ROBOTICSFACILITY)) < 1:
                        if self.can_afford(UnitTypeId.ROBOTICSFACILITY) and not self.already_pending(
                                UnitTypeId.ROBOTICSFACILITY):
                            await self.build(UnitTypeId.ROBOTICSFACILITY, near=pylon)
    
                # 控制核心存在的情況下建造星門
                if self.units(UnitTypeId.CYBERNETICSCORE).ready.exists:
                    if len(self.units(UnitTypeId.STARGATE)) < ((self.iteration / self.ITERATIONS_PER_MINUTE) / 2):
                        if self.can_afford(UnitTypeId.STARGATE) and not self.already_pending(UnitTypeId.STARGATE):
                            await self.build(UnitTypeId.STARGATE, near=pylon)
    
        ## 造兵
        async def build_offensive_force(self):
            # 無隊列化建造
            # for gw in self.units(UnitTypeId.GATEWAY).ready.noqueue:
            #     if not self.units(UnitTypeId.STALKER).amount > self.units(UnitTypeId.VOIDRAY).amount:
            #
            #         if self.can_afford(UnitTypeId.STALKER) and self.supply_left > 0:
            #             await self.do(gw.train(UnitTypeId.STALKER))
    
            for sg in self.units(UnitTypeId.STARGATE).ready.noqueue:
                if self.can_afford(UnitTypeId.VOIDRAY) and self.supply_left > 0:
                    await self.do(sg.train(UnitTypeId.VOIDRAY))
    
        ## 尋找目標
        def find_target(self, state):
            if len(self.known_enemy_units) > 0:
                # 隨機選取敵方單位
                return random.choice(self.known_enemy_units)
            elif len(self.known_enemy_units) > 0:
                # 隨機選取敵方建築
                return random.choice(self.known_enemy_structures)
            else:
                # 返回敵方出生點位
                return self.enemy_start_locations[0]
    
        ## 進攻
        async def attack(self):
            if len(self.units(UnitTypeId.VOIDRAY).idle) > 0:
                choice = random.randrange(0, 4)
                target = False
                if self.iteration > self.do_something_after:
                    if choice == 0:
                        # 什麼都不做
                        wait = random.randrange(20, 165)
                        self.do_something_after = self.iteration + wait
    
                    elif choice == 1:
                        # 攻擊離星靈樞紐最近的單位
                        if len(self.known_enemy_units) > 0:
                            target = self.known_enemy_units.closest_to(random.choice(self.units(UnitTypeId.NEXUS)))
    
                    elif choice == 2:
                        # 攻擊敵方建築
                        if len(self.known_enemy_structures) > 0:
                            target = random.choice(self.known_enemy_structures)
    
                    elif choice == 3:
                        # 攻擊敵方出生位置
                        target = self.enemy_start_locations[0]
    
                    if target:
                        for vr in self.units(UnitTypeId.VOIDRAY).idle:
                            await self.do(vr.attack(target))
                    y = np.zeros(4)
                    y[choice] = 1
                    print(y)
                    self.train_data.append([y, self.flipped])
    
    
    ## 啟動遊戲
    run_game(maps.get("AcidPlantLE"), [
        Bot(Race.Protoss, SentdeBot()), Computer(Race.Terran, Difficulty.Medium)
    ], realtime=False)
    

    可以看到train_data文件夾下存儲了勝利數據

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

  • java中hashmap容量的初始化

    HashMap使用HashMap(int initialCapacity)對集合進行初始化。

    在默認的情況下,HashMap的容量是16。但是如果用戶通過構造函數指定了一個数字作為容量,那麼Hash會選擇大於該数字的第一個2的冪作為容量。比如如果指定了3,則容量是4;如果指定了7,則容量是8;如果指定了9,則容量是16。

    為什麼要設置HashMap的初始化容量

    在《阿里巴巴Java開發手冊》中,有一條開發建議是建議我們設置HashMap的初始化容量。

    下面我們通過具體的代碼來了解下為什麼會這麼建議。

    我們先來寫一段代碼在JDK1.7的環境下運行,來分別測試下,在不指定初始化容量和指定初始化容量的情況下性能情況的不同。

    public static void main(String[] args) {
        int aHundredMillion = 10000000;
    
        // 未初始化容量
        Map<Integer, Integer> map = new HashMap<>();
        long s1 = System.currentTimeMillis();
        for (int i = 0; i < aHundredMillion; i++) {
            map.put(i, i);
        }
        long s2 = System.currentTimeMillis();
        System.out.println("未初始化容量,耗時: " + (s2 - s1)); // 14322
    
        // 初始化容量為50000000
        Map<Integer, Integer> map1 = new HashMap<>(aHundredMillion / 2);
        long s3 = System.currentTimeMillis();
        for (int i = 0; i < aHundredMillion; i++) {
            map1.put(i, i);
        }
        long s4 = System.currentTimeMillis();
        System.out.println("初始化容量5000000,耗時: " + (s4 - s3)); // 11819
    
        // 初始化容量為100000000
        Map<Integer, Integer> map2 = new HashMap<>(aHundredMillion);
        long s5 = System.currentTimeMillis();
        for (int i = 0; i < aHundredMillion; i++) {
            map2.put(i, i);
        }
        long s6 = System.currentTimeMillis();
        System.out.println("初始化容量為10000000,耗時: " + (s6 - s5)); // 7978
    }

    從以上的代碼不難理解,我們創建了3個HashMap,分別使用默認的容量(16)、使用元素個數的一半(5千萬)作為初始容量和使用元素個數(一億)作為初始容量進行初始化,然後分別向其中put一億個KV。

    從上面的打印結果中可以得到一個初步的結論:在已知HashMap中將要存放的KV個數的時候,設置一個合理的初始化容量可以有效地提高性能。下面我們來簡單分析一下原因。

    我們知道,HashMap是有擴容機制的。所謂的擴容機制,指的是當達到擴容條件的時候,HashMap就會自動進行擴容。而HashMap的擴容條件就是當HashMap中的元素個數(Size)超過臨界值(Threshold)的情況下就會自動擴容。

    threshold = loadFactor * capacity

    在元素個數超過臨界值的情況下,隨着元素的不斷增加,HashMap就會發生擴容,而HashMap中的擴容機制決定了每次擴容都需要重建hash表,這一操作需要消耗大量資源,是非常影響性能的。因此,如果我們沒有設置初始的容量大小,HashMap就可能會不斷髮生擴容,也就使得程序的性能降低了。

    另外,在上面的代碼中我們會發現,同樣是設置了初始化容量,設置的數值不同也會影響性能,那麼當我們已知HashMap中即將存放的KV個數的時候,容量的設置就成了一個問題。

    HashMap中容量的初始化

    開頭提到,在默認的情況下,當我們設置HashMap的初始化容量時,實際上HashMap會採用第一個大於該數值的2的冪作為初始化容量。

    Map<String, String> map = new HashMap<>(1);
    map.put("huangq", "yanggb");
    
    Class<?> mapType = map.getClass();
    Method capacity = mapType.getDeclaredMethod("capacity");
    capacity.setAccessible(true);
    System.out.println("capacity : " + capacity.invoke(map)); // 2

    當初始化的容量設置成1的時候,通過反射取出來的capacity卻是2。在JDK1.8中,如果我們傳入的初始化容量為1,實際上設置的結果也是1。上面的代碼打印的結果為2的原因,是代碼中給map塞入值的操作導致了擴容,容量從1擴容到了2。事實上,在JDK1.7和JDK1.8中,HashMap初始化容量(capacity)的時機不同。在JDK1.8中,調用HashMap的構造函數定義HashMap的時候,就會進行容量的設定。而在JDK1.7中,要等到第一次put操作時才進行這一操作。

    因此,當我們通過HashMap(int initialCapacity)設置初始容量的時候,HashMap並不一定會直接採用我們傳入的數值,而是經過計算,得到一個新值,目的是提高hash的效率。比如1->1、3->4、7->8和9->16。

    HashMap中初始容量的合理值

    通過上面的分析我們可以知道,當我們使用HashMap(int initialCapacity)來初始化容量的時候,JDK會默認幫我們計算一個相對合理的值當做初始容量。那麼,是不是我們只需要把已知的HashMap中即將存放的元素個數直接傳給initialCapacity就可以了呢?

    initialCapacity = (需要存儲的元素個數 / 負載因子) + 1

    這裏的負載因子就是loaderFactor,默認值為0.75。

    initialCapacity = expectedSize / 0.75F + 1.0F

    上面這個公式是《阿里巴巴Java開發手冊》中的一個建議,在Guava中也是提供了相同的算法,更甚之,這個算法實際上是JDK8中putAll()方法的實現。這是公式的得出是因為,當HashMap內部維護的哈希表的容量達到75%時(默認情況下),就會觸發rehash(重建hash表)操作。而rehash的過程是比較耗費時間的。所以初始化容量要設置成expectedSize/0.75 + 1的話,可以有效地減少衝突,也可以減小誤差。

    總結

    當我們想要在代碼中創建一個HashMap的時候,如果我們已知這個Map中即將存放的元素個數,給HashMap設置初始容量可以在一定程度上提升效率。

    但是,JDK並不會直接拿用戶傳進來的数字當做默認容量,而是會進行一番運算,最終得到一個2的冪。而為了最大程度地避免擴容帶來的性能消耗,通常是建議可以把默認容量的数字設置成expectedSize / 0.75F + 1.0F。

    在日常開發中,可以使用Guava提供的一個方法來創建一個HashMap,計算的過程Guava會幫我們完成。

    Map<String, String> map = Maps.newHashMapWithExpectedSize(10);

    最後要說的一點是,這種算法實際上是一種使用內存換取性能的做法,在真正的應用場景中要考慮到內存的影響。

     

    “當你認真喜歡一個人的時候,你的全世界都是她。”

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 【python測試開發棧】帶你徹底搞明白python3編碼原理

    【python測試開發棧】帶你徹底搞明白python3編碼原理

    在之前的文章中,我們介紹過編碼格式的發展史:[文章傳送門-todo]。今天我們通過幾個例子,來徹底搞清楚python3中的編碼格式原理,這樣你之後寫python腳本時碰到編碼問題,才能有章可循。

    我們先搞清楚幾個概念:

    • 系統默認編碼:指python解釋器默認的編碼格式,在python文件頭部沒有聲明其他編碼格式時,python3默認的編碼格式是utf-8。
    • 本地默認編碼:操作系統默認的編碼,常見的Windows的默認編碼是gbk,Linux的默認編碼是UTF-8。
    • python文件頭部聲明編碼格式:修改的是文件的默認編碼格式,只是會影響python解釋器讀取python文件時的編碼格式,並不會改變系統默認編碼和本地默認編碼。

    通過python自帶的庫,可以查看系統默認編碼和本地默認編碼

    Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916 64 bit (AMD64)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import sys
    >>> sys.getdefaultencoding()
    'utf-8'
    >>> import locale
    >>> locale.getdefaultlocale()
    ('zh_CN', 'cp936')
    >>>

    注意,因為我在windows系統的電腦上 進行測試,所以系統默認編碼返回“cp936”, 這是代碼頁(是字符編碼集的別名),而936對應的就是gbk。如果你在linux或者mac上執行上面的代碼,應該會返回utf-8編碼。

    其實總結來看,容易出現亂碼的場景,基本都與讀寫程序有關,比如:讀取/寫入某個文件,或者從網絡流中讀取數據等,因為這個過程中涉及到了編碼解碼的過程,只要編碼和解碼的編碼格式對應不上,就容易出現亂碼。下面我們舉兩個具體的例子,來驗證下python的編碼原理,幫助你理解這個過程。注意:下面的例子都是在pycharm中寫的。

    01默認的編碼格式

    我們新建一個encode_demo.py的文件,其文件默認的編碼格式是UTF-8(可以從pycharm右下角看到編碼格式),代碼如下:

    """
        @author: asus
        @time: 2019/11/21
        @function: 驗證編碼格式
    """
    import sys, locale
    
    
    def write_str_default_encode():
        s = "我是一個str"
        print(s)
        print(type(s))
        print(sys.getdefaultencoding())
        print(locale.getdefaultlocale())
    
        with open("utf_file", "w", encoding="utf-8") as f:
            f.write(s)
        with open("gbk_file", "w", encoding="gbk") as f:
            f.write(s)
        with open("jis_file", "w", encoding="shift-jis") as f:
            f.write(s)
    
    
    if __name__ == '__main__':
        write_str_default_encode()
    

    我們先來猜測下結果,因為我們沒有聲明編碼格式,所以python解釋器默認用UTF-8去解碼文件,因為文件默認編碼格式就是UTF-8,所以字符串s可以正常打印。同時以UTF-8編碼格式寫文件不會出現亂碼,而以gbk和shift-jis(日文編碼)寫文件會出現亂碼(這裏說明一點,我是用pycharm直接打開生成的文件查看的,編輯器默認編碼是UTF-8,如果在windows上用記事本打開則其默認編碼跟隨系統是GBK,gbk_file和utf_file均不會出現亂碼,只有jis_file是亂碼),我們運行看下結果:

    # 運行結果
    我是一個str
    <class 'str'>
    utf-8
    ('zh_CN', 'cp936')
    
    # 寫文件utf_file、gbk_file、jis_file文件內容分別是:
    我是一個str
    ����һ��str
    �䐥�꘢str

    和我們猜測的結果一致,下面我們做個改變,在文件頭部聲明個編碼格式,再來看看效果。

    02 python頭文件聲明編碼格式

    因為上面文件encode_demo.py的格式是UTF-8,那麼我們就將其變為gbk編碼。同樣的我們先來推測下結果,在pycharm中,在python文件頭部聲明編碼為gbk后(頭部加上 # coding=gbk ),文件的編碼格式變成gbk,同時python解釋器會用gbk去解碼encode_demo.py文件,所以運行結果應該和用UTF-8編碼時一樣。運行結果如下:

    # 運行結果
    我是一個str
    <class 'str'>
    utf-8
    ('zh_CN', 'cp936')
    
    # 寫文件utf_file、gbk_file、jis_file文件內容分別是:
    我是一個str
    ����һ��str
    �䐥�꘢str

    結果確實是一樣的,證明我們推論是正確的。接下來我們再做個嘗試,假如我們將(# coding=gbk)去掉(需要注意,在pycharm中將 # coding=gbk去掉,並不會改變文件的編碼格式,也就是說encode_demo.py還是gbk編碼),我們再運行一次看結果:

      File "D:/codespace/python/pythonObject/pythonSample/basic/encodeDemo/encode_demo.py", line 4
    SyntaxError: Non-UTF-8 code starting with '\xd1' in file D:/codespace/python/pythonObject/pythonSample/basic/encodeDemo/encode_demo.py on line 5, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
    

    運行直接報錯了,我們加個斷點,看看具體的異常信息:

    看錯誤提示是UnicodeDecodeError,python解釋器在對encode_demo.py文件解碼時,使用默認的UTF-8編碼,但是文件本身是gbk編碼,所以當碰到有中文沒辦法識別時,就拋出DecodeError。

    03 敲黑板,划重點

    python3中的str和bytes

    python3的重要特性之一就是對字符串和二進制流做了嚴格的區分,我們聲明的字符串都是str類型,不過Str和bytes是可以相互轉換的:

    def str_transfor_bytes():
        s = '我是一個測試Str'
        print(type(s))
        # str 轉bytes
        b = s.encode()
        print(b)
        print(type(b))
        # bytes轉str
        c = b.decode('utf-8')
        print(c)
        print(type(c))
    
    
    if __name__ == '__main__':
        str_transfor_bytes()

    需要注意一點:在調用encode()和decode()方法時,如果不傳參數,則會使用python解釋器默認的編碼格式UTF-8(如果不在python頭文件聲明編碼格式)。但是如果傳參的話,encode和decode使用的編碼格式要能對應上。

    python3默認編碼是UTF-8?還是Unicode?

    經常在很多文章里看到,python3的默認編碼格式是Unicode,但是我在本文中卻一直在說python3的默認編碼格式是UTF-8,那麼哪種說法是正確的呢?其實兩種說法都對,主要得搞清楚Unicode和UTF-8的區別(之前文章有提到):

    • Unicode是一個字符集,說白了就是把各種編碼的映射關係全都整合起來,不過它是不可變長的,全部都以兩個字節或四個字節來表示,佔用的內存空間比較大。
    • UTF-8是Unicode的一種實現方式,主要對 Unicode 碼的數據進行轉換,方便存儲和網絡傳輸 。它是可變長編碼,比如對於英文字母,它使用一個字節就可以表示。

    在python3內存中使用的字符串全都是Unicode碼,當python解釋器解析python文件時,默認使用UTF-8編碼。

    open()方法默認使用本地編碼

    在上面的例子中,我們往磁盤寫入文件時,都指定了編碼格式。如果不指定編碼格式,那麼默認將使用操作系統本地默認的編碼格式,比如:Linux默認是UTF-8,windows默認是GBK。其實這也好理解,因為和磁盤交互,肯定要考慮操作系統的編碼格式。這有區別於encode()和decode()使用的是python解釋器的默認編碼格式,千萬別搞混淆了。

    總結

    不知道你看完上面的例子后,是否已經徹底理解了python3的編碼原理。不過所有的編碼問題,都逃不過“編碼”和“解碼”兩個過程,當你碰到編碼問題時,先確定源文件使用的編碼,再確定目標文件需要的編碼格式,只要能匹配,一般就可以解決編碼的問題。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

  • 面向對象和面向過程詳解

    1.前言

    其實一直對面向過程和面向對象的概念和區別沒有很深入的理解,在自己不斷想完善自己的知識體系中,今天借這個時間,寫一篇博客。來深入的了解面向過程與面向對象!好記性不如爛筆頭!!  

    2.面向對象與面向過程的區別

    面向過程就是分析出解決問題所需要的步驟,然後用函數把這些步驟一步一步實現,使用的時候一個一個依次調用就可以了;面向對象是把構成問題事務分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描敘某個事物在整個解決問題的步驟中的行為。

    舉一個下五子棋通俗例子吧 哈哈哈 我感覺這兒例子很容易讓人理解

    面向過程的設計思路就是首先分析問題的步驟:

    1、開始遊戲,

    2、黑子先走,

    3、繪製畫面,

    4、判斷輸贏,

    5、輪到白子,

    6、繪製畫面,

    7、判斷輸贏,

    8、返回步驟2,

    9、輸出最後結果。

    把上面每個步驟用分別的函數來實現,問題就解決了。

    面向對象的設計則是從另外的思路來解決問題。整個五子棋可以分為

    1、黑白雙方,這兩方的行為是一模一樣的,

    2、棋盤系統,負責繪製畫面,

    3、規則系統,負責判定諸如犯規、輸贏等。第一類對象(玩家對象)負責接受用戶輸入,並告知第二類對象(棋盤對象)棋子布局的變化,

    棋盤對象接收到了棋子的i變化就要負責在屏幕上面显示出這種變化,同時利用第三類對象(規則系統)來對棋局進行判定。

    可以明顯地看出,面向對象是以功能來劃分問題,而不是步驟。同樣是繪製棋局,這樣的行為在面向過程的設計中分散在了總多步驟中,很可能出現不同的繪製版本,因為通常設計人員會考慮到實際情況進行各種各樣的簡化。而面向對象的設計中,繪圖只可能在棋盤對象中出現,從而保證了繪圖的統一。

    功能上的統一保證了面向對象設計的可擴展性。比如要加入悔棋的功能,如果要改動面向過程的設計,那麼從輸入到判斷到显示這一連串的步驟都要改動,甚至步驟之間的循序都要進行大規模調整。如果是面向對象的話,只用改動棋盤對象就行了,棋盤系統保存了黑白雙方的棋譜,簡單回溯就可以了,而显示和規則判斷則不用顧及,同時整個對對象功能的調用順序都沒有變化,改動只是局部的。

    再比如我要把這個五子棋遊戲改為圍棋遊戲,如果你是面向過程設計,那麼五子棋的規則就分佈在了你的程序的每一個角落,要改動還不如重寫。但是如果你當初就是面向對象的設計,那麼你只用改動規則對象就可以了,五子棋和圍棋的區別不就是規則嗎?(當然棋盤大小好像也不一樣,但是你會覺得這是一個難題嗎?直接在棋盤對象中進行一番小改動就可以了。)而下棋的大致步驟從面向對象的角度來看沒有任何變化。

    當然,要達到改動只是局部的需要設計的人有足夠的經驗,使用對象不能保證你的程序就是面向對象,初學者或者很蹩腳的程序員很可能以面向對象之虛而行面向過程之實,這樣設計出來的所謂面向對象的程序很難有良好的可移植性和可擴展性

    三、面向過程與面向對象的優缺點
    很多資料上全都是一群很難理解的理論知識,整的我頭都大了,後來發現了一個比較好的文章,寫的真是太棒了,通俗易懂,想要不明白都難!

    用面向過程的方法寫出來的程序是一份蛋炒飯,而用面向對象寫出來的程序是一份蓋澆飯。所謂蓋澆飯,北京叫蓋飯,東北叫燴飯,廣東叫碟頭飯,就是在一碗白米飯上面澆上一份蓋菜,你喜歡什麼菜,你就澆上什麼菜。我覺得這個比喻還是比較貼切的。

    蛋炒飯製作是把米飯和雞蛋混在一起炒勻。蓋澆飯呢,則是把米飯和蓋菜分別做好,你如果要一份紅燒肉蓋飯呢,就給你澆一份紅燒肉;如果要一份青椒土豆蓋澆飯,就給澆一份青椒土豆絲。

    蛋炒飯的好處就是入味均勻,吃起來香。如果恰巧你不愛吃雞蛋,只愛吃青菜的話,那麼唯一的辦法就是全部倒掉,重新做一份青菜炒飯了。蓋澆飯就沒這麼多麻煩,你只需要把上面的蓋菜撥掉,更換一份蓋菜就可以了。蓋澆飯的缺點是入味不均,可能沒有蛋炒飯那麼香。

    到底是蛋炒飯好還是蓋澆飯好呢?其實這類問題都很難回答,非要比個上下高低的話,就必須設定一個場景,否則只能說是各有所長。如果大家都不是美食家,沒那麼多講究,那麼從飯館角度來講的話,做蓋澆飯顯然比蛋炒飯更有優勢,他可以組合出來任意多的組合,而且不會浪費。

    蓋澆飯的好處就是”菜”“飯”分離,從而提高了製作蓋澆飯的靈活性。飯不滿意就換飯,菜不滿意換菜。用軟件工程的專業術語就是”可維護性“比較好,”飯” 和”菜”的耦合度比較低。蛋炒飯將”蛋”“飯”攪和在一起,想換”蛋”“飯”中任何一種都很困難,耦合度很高,以至於”可維護性”比較差。軟件工程追求的目標之一就是可維護性,可維護性主要表現在3個方面:可理解性、可測試性和可修改性。面向對象的好處之一就是顯著的改善了軟件系統的可維護性。
    看了這篇文章,簡單的總結一下!

    面向過程

    優點:性能比面向對象高,因為類調用時需要實例化,開銷比較大,比較消耗資源;比如嵌入式開發、 Linux/Unix等一般採用面向過程開發,性能是最重要的因素。
    缺點:沒有面向對象易維護、易復用、易擴展
    面向對象

    優點:易維護、易復用、易擴展,由於面向對象有封裝、繼承、多態性的特性,可以設計出低耦合的系統,使系統 更加靈活、更加易於維護

    缺點:性能比面向過程低

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

  • fastjason常用方法

    fastjason常用方法

    什麼是fastjson?

    Fastjson是一個Java語言編寫的高性能功能完善的JSON庫。它採用一種“假定有序快速匹配”的算法,把JSON Parse的性能提升到極致,是目前Java語言中最快的JSON庫。Fastjson接口簡單易用,已經被廣泛使用在緩存序列化、協議交互、Web輸出、Android客戶端等多種應用場景。

    主要特點:

    • 快速FAST (比其它任何基於Java的解析器和生成器更快,包括jackson)
    • 強大(支持普通JDK類包括任意Java Bean Class、Collection、Map、Date或enum)
    • 零依賴(沒有依賴其它任何類庫除了JDK)

    背景

    最近關於fastjson的消息,引起了很多人的關注!

    fastjson爆出重大漏洞,攻擊者可使整個業務癱瘓

    漏洞描述

    常用JSON組件FastJson存在遠程代碼執行漏洞,攻擊者可通過精心構建的json報文對目標服務器執行任意命令,從而獲得服務器權限。此次爆發的漏洞為以往漏洞中autoType的繞過。

    影響範圍

    FastJson < 1.2.48

    很多開發者才猛然發現,fastjson已經深入到我們開發工作的方方面面。那麼除了趕快升級你的json外,我們來挖挖fastjson最常用的用法。

    fastjson常用方式

    1.maven依賴(記得升級到1.2.48以上版本哦)

            <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
            <dependency>
             <groupId>com.alibaba</groupId>
             <artifactId>fastjson</artifactId>
             <version>1.2.62</version>
            </dependency>    

    2.FastJson對於json格式字符串的解析主要用到了一下三個類:

    (1)JSON:fastJson的解析器,用於JSON格式字符串與JSON對象及javaBean之間的轉換。

    (2)JSONObject:fastJson提供的json對象。

    (3)JSONArray:fastJson提供json數組對象。

    3.常用方式

    3.1 string和java對象

     

    實例1:對象轉json字符串

            Map<String,String> map=new HashMap<String,String>();
            map.put("code","0");
            map.put("message","ok");
            String json=JSON.toJSONString(map);
            System.out.println(json);

    輸出結果為:

    {"code":"0","message":"ok"}

    實例2:字符串轉對象

            Map<String,String> map=new HashMap<String,String>();
            map.put("code","0");
            map.put("message","ok");
            String json=JSON.toJSONString(map);
            System.out.println(json);
            
            Map obj=(Map)JSON.parse(json);
            System.out.println("code="+obj.get("code")+",message="+obj.get("message"));

    輸出結果

    {"code":"0","message":"ok"}
    code=0,message=ok

    3.2 工具類JSONObject

        public static void main(String[] args) {
            Map<String,String> map=new HashMap<String,String>();
            map.put("code","0");
            map.put("message","ok");
            String json=JSON.toJSONString(map);
            System.out.println(json);
            
            Map obj=(Map)JSON.parse(json);
            System.out.println("code="+obj.get("code")+",message="+obj.get("message"));        
            
            String code=JSON.parseObject(json).getString("code");
            String message=JSON.parseObject(json).getString("message");
            System.out.println("code="+code+",message="+message);
        }

    輸出結果

    {"code":"0","message":"ok"}
    code=0,message=ok
    code=0,message=ok

    3.3 數組對象

    List<user> list=new ArrayList<user>(JSONArray.parseArray(jsonString,user.class)); 

    Fastjson 與各種JSON庫的性能比較:

     

    json庫 序列化性能 反序列化性能 jar大小
    fastjson 1201 1216 fastjson-1.1.26.jar(356k)
    fastjson-1.1.25-android.jar(226k)
    jackson 1408 1915 jackson-annotations-2.1.1.jar(34k)
    jackson-core-2.1.1.jar(206k)
    jackson-databind-2.1.1.jar(922k)
    總共1162k
    gson 7421 5065 gson-2.2.2.jar(189k)
    json-lib 27555 87292 json-lib-2.4-jdk15.jar(159k)


    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

  • 德國頒佈最新電動汽車補貼計畫 共投入12億歐元

    德國頒佈最新電動汽車補貼計畫 共投入12億歐元

    據報導,自7月1日起,德國頒佈最新的電動車補貼計畫,截止目前已經有近2000位申請者,其中寶馬車主占多數。  
      為了促進電動車等環保車型的普及,德國為每位購買電動車的消費者提供4000歐元的補貼,插電式混合動力車的補貼為3000歐元。在計畫實施後,有1791位插電式混合動力車的車主申請了補貼,其中有581位購買了寶馬的車型。同時還有444位申請者購買了雷諾車型,大眾汽車買主為154位。   據統計,目前德國人汽車擁有量為4500萬輛,而其中僅有5萬輛是純電動或者是混合動力車輛。為改善這一情況,德國此次計畫共投入12億歐元,由政府和汽車製造商平攤,希望能夠在2019年6月底,即計畫截止期前售出40萬輛電動車。   文章來源:環球網

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

  • 續航力600公里的特斯拉 即將問世?

    續航力600公里的特斯拉 即將問世?

    續航力是電動車最受關注的性能,而作為全球電動車龍頭廠商的特斯拉(Tesla),似乎也準備好要推出續航力更久的車款了。跟據了解,新的特斯拉電動車可能搭載100kW的電池,續航力最遠可達611公里。

    《癮科技》中文版指出,德國監管機關的資料中可查到Model S與Model X的100D與P100D型號的相關資訊;而根據特斯拉為車款型號命名的邏輯,這可能暗示特斯拉將推出搭載100kW電池的車款。

    100kW 的電池搭配Model S,預計最高續航里程可來到611公里,比90D的續航里程多了100公里以上。若搭配Model X,續航里程也可來到480 公里之多。這樣的續航力,將能有效減輕美國車主對駕駛特斯拉跨州旅行的疑慮。

    (照片來源:Tesla 臉書專頁)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!