AlphaGo的制勝祕訣:蒙特卡洛樹搜尋初學者指南
編譯 | reason_W
出品 | AI科技大本營(公眾號ID:rgznai100)
長久以來,計算機在圍棋領域不可能達到人類專家的水平一直是學術界的主流觀點。圍棋,被認為是人工智慧的“聖盃”——一個我們原本希望在未來十年努力攻克的里程碑。
二十年前,“深藍”就已經在國際象棋上超越了人類,二十年過去了,計算機卻依然無法在圍棋這一專案上戰勝人類。圍棋的運算的複雜性一度讓人們將其稱為“數值混沌”。甚至有人據此創作了一部科幻驚悚電影《圓周率》。
然而,出乎很多人意料的是,AlphaGo ——一個由谷歌 Deepmind 發明的圍棋 AI 於 2016 年以 4:1 的成績擊敗了韓國圍棋冠軍李世石。AlphaGo 的出現結束了圍棋不可戰勝的局面。一年之後,Alpha Go Zero 又以 100:0 的成績擊敗了 Alpha Go Lee(擊敗李世石的那個)。我們不禁懷疑,人類還能追上 AI 嗎?
作為人類工程學上的傑作,Alpha Go Zero 將多種方法集於一體,其核心元件包括:
蒙特卡洛樹搜尋 ——包含了用於樹遍歷的 PUCT 函式的某些變體
殘差卷積網路 ——其中的策略和價值網路在遊戲中被用於棋局評估以及落子位置的先驗概率估計
強化學習——通過自我博弈來訓練網路
在本文中,我們將就 AlphaGo 中的蒙特卡洛樹搜尋(MCTS/Monte Carlo Tree Search)進行專門介紹 ,這也是所有現代圍棋程式的最核心演算法。
本文內容目錄如下:
1 介紹
1.1 有限雙人零和回合制博弈
1.2 如何表示博弈?
1.3 如何選擇最優勝率下法? 極小化極大演算法(Minimax)和剪枝演算法(alpha-beta)
2 蒙特卡洛樹搜尋的基本概念
2.1 模擬
2.1.1 Alpha Go 和Alpha Zero 中的模擬
2.2 博弈樹節點展開——完全展開節點和訪問節點
2.3 反向傳播——將模擬結果傳回去
2.4 節點的統計資料
2.5 博弈樹遍歷
2.6 樹的上限置信區間
2.6.1 Alpha Go 和Alpha Zero 中的 UCT 演算法
2.6.2 Alpha Go 和Alpha Zero 的策略網路培訓
2.7終止蒙特卡洛樹搜尋
3 總結
01 介紹
2006 年,法國的一名研究人員RémiCoulom 首次將蒙特卡洛樹搜尋引入到圍棋程式 Crazy Stone 中,這算得上是當時計算機圍棋程式的一次突破。
巨集觀來看,蒙特卡羅樹搜尋的主要目的是:給出一個博弈(即遊戲)狀態以選擇勝率最高的下一步走法。在本文中,我們將盡量對蒙特卡羅樹搜尋的那些細節進行解釋,方便讀者瞭解其原理。同時,我們也會結合 Alpha Go / Zero,以解釋 MCTS 變體。
▌1.1 有限雙人零和回合制博弈
“博弈”——蒙特卡洛樹搜尋執行的框架或者說環境,其本身就是一個非常抽象的廣義術語 ,因此本文會將討論範圍限定在一種博弈型別:有限雙人零和回合制博弈 —— 乍一聽這個詞好像很複雜,但其實很簡單,讓我們把它分解成幾個部分:
博弈意味著這是一個互動的情形,互動就意味著會有(一個或多個)玩家參與。
有限表示在任何時間點,玩家之間都只存在有限的互動方式。
雙人有限博弈意味著博弈中只有兩名玩家。
回合制,表示玩家要輪流進行行動。
最後,零和博弈——意味著博弈雙方被賦予對抗性目標,換句話說:在博弈的任何終結狀態中,所有玩家的收益總和等於零。有時候這樣的博弈也被稱為嚴格競爭。
這樣分析下來,我們很容易就可以發現圍棋、國際象棋和井字棋都是有限雙人零和回合制博弈。它們都是隻有兩名玩家,每個人可選的執行動作有限,且該博弈是嚴格競爭的——兩名玩家擁有對抗性目標——博弈最終收益總和為零。
注意:蒙特卡洛搜尋是一個應用相當廣泛的工具,不僅僅限於本文討論的有限雙人零和博弈。 更為全面的概述可以參閱:http://mcts.ai/pubs/mcts-survey-master.pdf 。為了簡化本教程,我們將主要討論可能場景的某些子集。
▌1.2 如何表示博弈?
形式上,博弈由一些基本的數學實體表示。
如果不瞭解博弈論可以先看下這本書:
http://gametheory.tau.ac.il/arielDocs/
在一本博士水平的博弈理論書籍中,我們可以找到以下定義:
定義1. 一個廣義的博弈可以由一個元組定義:
這個定義相當冗長,其中的樂趣估計也只有數學家才能體會到。
從計算機程式設計師的角度來看,這個正式的定義也確實可能不太容易看懂。 幸運的是,我們可以用一個眾所周知的資料結構來簡化表示一個博弈:博弈樹(move) 。
博弈樹是一種樹結構,其中每個節點都代表博弈的一種確定狀態。 從一個節點到它的一個子節點(如果存在的話)的過程被視為一個行動(move)。節點的子節點數目稱為分支因子(branching factor) 。樹的根節點表示博弈的初始狀態 。我們還區分了博弈樹的末端節點 (terminal nodes)——沒有子節點的節點,表示此處博弈無法繼續進行。末端節點的狀態可以被評估並用以總結博弈結果。
為限制博弈樹大小,只有訪問過的狀態被展開 ,未展開的節點被標記為灰色。
在圖的頂部,我們可以看到樹的根節點,它代表井字棋博弈的初始狀態,即空白棋盤(我們將其標記為綠色)
任何從一個節點到另一個節點的過程都是一個行動
井字棋博弈的分支因子各不相同——這取決於樹的深度
博弈在末端節點 結束(我們將其標記為紅色)
從根節點到末端節點的樹遍歷代表了單次博弈的全部過程
博弈樹是一種遞迴資料結構。每次進行最優行動併到達一個子節點後,該子節點都會被視為其子樹的根節點。因此,我們可以將博弈看做一個用一系列博弈樹所表示的“下一步最優行動”組成的過程式列,當然每次的根節點都是不同的。在實踐中,我們不必記住到達當前狀態的路徑,因為那並不是當前博弈狀態中的關注點。
▌1.3 如何選擇最優勝率下法? 極小化極大演算法(Minimax)和剪枝演算法(alpha-beta)
不要忘了,我們的最終目標是在給定博弈狀態的情況下,利用博弈樹找到最優勝率下法。 但究竟如何實現呢?
這個問題沒有直接的答案。首先,你不可能提前知道對手的策略——對手可能是個圍棋國手,也可能只是一個剛學會圍棋的菜鳥。就拿國際象棋來說,如果事先知道了對手是一個業餘玩家(在數學中,這被稱為已知對手的“混合策略”),我們只要選擇簡單的策略來欺騙他就可以迅速獲勝。但很明顯,當對手換成一名高手時,同樣的策略就會適得其反。
在完全不瞭解對手的情況下,我們可以使用一種非常激進的策略——極小化極大算(Minimax)。在假設對手會做出最優決策的情況下,該策略可以最大化己方收益。 在 A 和 B 之間的雙人有限零和回合制博弈中(其中 A 會不斷最大化他的收益,而 B 則會不斷最小化 A 的收益),極小化極大演算法可以用下面的遞迴公式來描述:
其中:
VA 和 VB 分別是玩家 A 和 B 的效用函式(效用 = 收益)
move 是一個函式,在給定當前狀態和該狀態的行動的情況下,它可以生成下一個博弈狀態(即選擇當前節點的其中一個子節點)
eval 是一個函式,用於(在末端節點處)評估最終博弈狀態
s_hat 表示任意最終博弈狀態(末端節點)
右下角公式中的負號表示該博弈為零和博弈。
簡單來說,給定狀態,並假設對手試圖最小化我們的收益,該演算法希望找到一個可以最大化己方收益的行動。很自然這也是該演算法得名極小化極大演算法的原因 。現在我們需要做的就是展開整個博弈樹,並反向傳播根據遞迴公式給出的規則得到的值。
上面的博弈樹說明了如何在極小化極大演算法中選擇最優行動。白皇后要吃掉所有的黑棋,就要選擇顏色儘可能深(儘可能黑)的博弈結果(獎勵值 = 畫素強度),而黑皇后則相反,要選擇顏色最淺的路徑。每一層上的每個選擇都是極小化極大演算法判斷的最優結果。
我們可以從底部的末端節點開始看,其選擇顯而易見。黑皇后會顏色最淺的路徑,然後白皇后為了得到最大獎勵,將並選擇顏色最深(最黑)的路徑,依此類推......直到路徑到達代表當前博弈狀態的節點。這就是極小化極大演算法的基本工作原理。
極小化極大演算法的最大弱點是它需要展開整個博弈樹。在面對分支因子高的博弈(如圍棋或國際象棋)時,該演算法生成的博弈樹就會十分巨大,難以計算。
那麼有沒有補救辦法呢?
一種方法是僅在特定的閾值深度 d 內展開我們的博弈樹。但是,這種做法不能保證閾值深度 d 內的所有節點都是末端節點,因此我們需要一個評估非最終博弈狀態的函式。這對於人類來說非常自然:即使博弈仍在繼續,我們也可以通過檢視國際象棋或圍棋的棋盤預測贏家。比如下面這種棋局我們就可以很容易猜到結局。
另一種克服博弈樹過大問題的方法是通過 alpha-beta 剪枝演算法修剪博弈樹。alpha-beta 剪枝演算法可以看作升級版的極小化極大演算法。它以極小化極大的方式遍歷博弈樹,同時避免某些分支的展開。其結果在最好的情況下與極小化極大演算法結果相同,但優勢在於 alpha-beta 剪枝演算法通過減少搜尋空間提高了搜尋效率。
關於極小化極大演算法和 alpha-beta 剪枝演算法的更多介紹讀者可以參考這裡(https://www.youtube.com/watch?v=STjW3eH0Cik)。
總之 Minimax / Alpha-beta 剪枝演算法已經是非常成熟的解決方案,現在已經被成功用在了各種的博弈引擎中,比如 Stockfish —— Alpha Zero 的主要競爭對手之一。
02 蒙特卡洛樹搜尋的基本概念
上面我們介紹了兩種基本的搜尋策略。但在蒙特卡洛樹搜尋演算法中,最優行動卻是以一種非常不同的方式計算出來的。顧名思義,蒙特卡洛樹搜尋會進行多次模擬博弈,並根據模擬結果嘗試預測最優行動。
蒙特卡洛樹搜尋的主要概念是搜尋。搜尋是一組沿著博弈樹向下的遍歷過程。單次遍歷的路徑會從根節點(當前博弈狀態)開始,一直到達未完全展開的節點。未完全展開的節點意味著其子節點至少有一個未被訪問。
當遇到未完全展開的節點時,其未訪問子節點中的一個會被選為單次模擬的根節點。然後模擬結果會被反向傳播會當前的根節點,並更新博弈樹節點的統計資訊 。 一旦搜尋(受時間或計算能力限制)終止,演算法就會根據收集的統計資訊選擇行動策略。
還有很多地方不清楚?我們可以試著先思考一下這一過程中的幾個關鍵問題:
什麼是展開或未完全展開的博弈樹節點?
在搜尋過程中,“向下遍歷”意味著什麼? 下一個(子)節點如何選擇 ?
什麼是模擬 ?
什麼是反向傳播 ?
什麼是在展開的博弈樹節點中被反向傳播和更新的統計資料?
最後的行動策略如何選擇?
下面,我們將一步步解決這些問題,幫助大家理解蒙特卡洛樹搜尋的過程。
▌2.1 模擬
首先我們來看模擬。這一過程的解釋不太依賴其他術語的定義。模擬是一種單一的博弈行為——一系列從當前節點(表示博弈狀態)開始並終止於可計算博弈結果的末端節點的動作序列。模擬是對博弈樹節點評估的近似計算,這一過程會從該節點處開始以某種隨機博弈的方式進行。
那麼,在模擬過程中我們如何選擇動作呢?答案就是 rollout 策略函式:
該函式會根據輸入的博弈狀態產生下一個“移動/動作”。在實踐中,這一函式的計算速度很快,從而可以進行很多次的模擬——預設的 rollout 策略函式會使用服從均勻分佈的隨機取樣 。
2.1.1 Alpha Go 和 Alpha Zero 中的模擬
在 Alpha Go Lee 中,葉子 S_L 的評估是以下兩部分的加權和:
標準 rollout 評估 z_L ,它採用了自定義快速 rollout 策略,這是一個具有人工特徵的淺層 softmax 神經網路
價值網路,它會從 Alpha Go 的自我博弈中抽取 30 萬個不同的位置進行訓練,通過 13 層卷積神經網路 v_0 給出位置評估
在 Alpha Zero 中, Deepmind 的工程師更進一步,他們根本不執行 playout ,而是直接用19 層 CNN 殘留網路評估當前節點。在 Alpha Zero 中,他們有一個網路 f_0,可以馬上輸出位置評估和移動概率向量。
模擬 / rollout 的最簡單形式只是一系列隨機的從給定的博弈狀態開始並終止的行動。模擬總會產生一個評估,就圍棋而言,這個評估結果就是勝利,失敗或平局,但通常模擬結果出現值都是可以的。
在蒙特卡羅樹搜尋模擬中,我們始終都是從先前沒有被訪問的節點開始。現在我們就來了解一下訪問節點的含義。
▌2.2 博弈樹節點展開——完全展開節點和訪問節點
我們可以想一下人類是如何思考圍棋或國際象棋博弈的。
給定一個根節點並加上博弈規則,博弈樹的其餘部分其實就已經隱含地表示出來了。我們不需要將整個樹儲存在記憶體中就可以實現對它的遍歷。在初始形式中,博弈樹是沒有展開的。在最初的博弈狀態中,我們處於博弈樹的根部,其餘節點都還沒有被訪問。一旦需要選擇一個行動,我們就會想象這個行動會帶來的結果(即到達哪個節點位置),並分析(評估)這個行動會到達的節點位置。而那些我們從未考慮過的博弈狀態則會繼續等待我們去發現。
蒙特卡洛樹搜尋博弈樹也具有同樣的特性。節點會被分為已訪問節點或未訪問節點。那麼節點被訪問代表什麼呢?這代表著該節點該節點已經至少進行了一次評估 。如果一個節點的所有子節點都被訪問了,則該節點即會認為是完全展開的 ,否則它就是未完全展開節點,並且有可能進一步展開。
在搜尋開始時,所有根節點的子節點都是未被訪問的。演算法會選中一個節點 ,然後開始第一次模擬(評估)。
請注意, 模擬期間由 rollout 策略函式選中的節點仍會被標記為未訪問 。即使它們通過了 rollout ,也仍然是未訪問的,只有模擬開始的那個節點會被標記為已訪問。
▌2.3 反向傳播——將模擬結果傳回去
一旦完成了一次新訪問節點(有時稱為葉節點)的模擬,其結果就將反向傳播回當前的博弈樹根節點。模擬開始的節點就會被標記為已訪問 。
反向傳播是從葉節點(模擬開始的節點)到根節點的遍歷。模擬結果會被傳送到根節點,並且反向傳播路徑上的每個節點的統計資料都會被計算/更新。反向傳播了保證每個節點的統計資料將會反映在其所有後代節點所開始的模擬結果中(因為模擬結果會被傳送到博弈樹根節點)
▌2.4 節點的統計資料
反向傳播模擬結果的目的是更新反向傳播路徑上的所有節點 v(包括模擬開始的節點)的總模擬獎勵 Q(v) 和總訪問次數 N(v) 。
Q(v) - 總模擬獎勵是節點v 的一個屬性,最簡單的形式是通過考慮的節點的模擬結果的總和。
N(v) - 總訪問次數是節點v 的另一個屬性,表示一個節點在反向傳播路徑上的次數(同時是它對總模擬獎勵貢獻的次數)
每個已訪問節點都會保留這兩個值,一旦完成了特定次數的模擬,已訪問節點就會將這些代表它們如何被展開/探索的資訊儲存下來。
換句話說,隨便檢視一個節點的統計資料,這兩個值都可以反映該節點的潛在價值(總模擬獎勵)以及被探索的程度(總訪問次數)。總模擬獎勵較高的節點會是很好的候選節點,但訪問量較低的節點也可能很值得訪問(因為它們還沒有被很好地探索 )。
現在我們已經知道了訪問節點的含義,但還剩一條線沒有完成。如何才能從根節點到達未訪問節點,然後開始模擬呢?
▌2.5 博弈樹遍歷
在搜尋的一開始,由於我們還沒有進行過任何模擬,所以首先要選擇未訪問的節點。單個模擬會從這些節點開始,結果會被反向傳播到根節點,然後根節點就會被認為完全展開 。
但接下來我們該怎麼做? 如何才能從一個完全展開的節點到達未訪問的節點呢? 我們必須遍歷已訪問節點的層,但目前還沒有很好的方式。
為了在路徑上選擇下一個節點,以通過完全展開節點 v 開始下一次模擬,我們需要考慮節點 v 本身的資訊及其所有子節點 v :v_1,v_2,…,v_k 的資訊。 現在讓我們來看一下有哪些資訊可以用吧。
當前節點(藍色)是完全展開的,因此它肯定已經被訪問了,並且儲存了節點統計資訊:總模擬獎勵和總訪問次數。其子節點同樣也是已訪問的,並且儲存了節點統計資訊。 這些值就可以用於樹的上限置信區間( UCT )計算。
▌2.6 樹的上限置信區間
UCT 函式可以讓我們在訪問節點中選擇下一個要遍歷的節點,這也是蒙特卡羅樹搜尋的核心函式:
可以最大化 UCT 函式值的節點就是在蒙特卡洛樹搜尋樹遍歷中要選擇的節點。讓我們來看看這個函式:
首先,該函式是為節點 v 的子節點 v_i 定義的,它同樣是兩部分的和。
第一部分是
也被稱為 exploitation 分量 ,可以被看作是勝率——用總模擬獎勵除以總訪問次數,就可以得到在節點v_i中的獲勝比例。 這一部分看起來很靠譜——因為我們會願意遍歷具有高勝率的節點。
為什麼我們不能只使用 exploitation 分量呢? 因為這樣演算法很快就會以對那些在搜尋開始就帶來一個獲勝模擬的節點進行貪婪探索告終 。
比方說:假設我們只使用 exploitation 分量開始蒙特卡羅樹搜尋。 從一個根節點開始,我們會為所有子節點執行一次模擬,然後下一步就會只訪問那些模擬結果至少有一次勝利的節點。 第一次模擬失敗的節點則會被立即放棄 ,而沒有任何改進的機會。這樣是非常短視的做法,難以實現全域性最優。
因此我們有 UCT 的第二部分,探索分量(exploration component) 。探索分量偏向於那些尚未探索過的節點,即那些相對訪問次數較少的節點(即 N(vi) 較低的節點)。我們來看看 UCT 函式的探索分量 ——它會隨著節點訪問次數的增加而減小,並且將為訪問次數較少的節點提供更高的選擇可能性,從而引導搜尋進行探索。
UCT 公式中的引數 c 是用來在蒙特卡洛樹搜尋中的 expolitation 與 exploration 之間進行平衡的。
2.6.1 Alpha Go 和Alpha Zero 中的 UCT 演算法
在 Alpha Go Lee 和 Alpha Zero 中 ,博弈樹遍歷是通過最大化下面這個 UCT 變體進行的:
其中 P(v_i, v) 是行動(從 v 到 v_i )的先驗概率,其值來自被稱為策略網路的深度神經網路的輸出。策略網路是一種函式,它會根據博弈狀態產生可能移動的概率分佈(請注意,這一點與快速 rollout 策略不同)。 這裡的目的是將搜尋空間縮小到合理的移動範圍內——將其新增到 exploration 分量將有助於 exploration 保持合理的移動範圍。
2.6.2 Alpha Go 和 Alpha Zero 的策略網路訓練
Alpha Go 有兩個策略網路:
SL策略網路 ,基於人類博弈資料集以有監督的方式進行訓練。
RL策略網路,增強版SL策略網路 。它們有相同的架構,但RL策略網路會通過強化學習(自我博弈)進一步訓練,
有趣的是,Deepmind 的蒙特卡洛樹搜尋變體選擇了 SL 策略網路輸出來進行先前的移動概率估計(P(v,v_i)),因為這一策略的實際效果更好(作者認為基於人類的資料更適合探索性移動)。那麼 RL策略網路的目的是什麼呢? 更強大的RL策略網路被用來生成價值網路訓練(用於博弈狀態評估的那個)需要的 30 萬個位置資料集,
而在 Alpha Zero 中 ,則只有一個網路 f_0 ,它既是價值網路,也是策略網路。它是完全從隨機初始化開始通過自我博弈進行訓練的。它會並行訓練很多網路,並且在針對當前最佳神經網路進行評估後會選擇最好的網路進行訓練資料生成。
關於 UCT 函式的一個重要說明:在競爭博弈中,其 expolitation 分量 Q_i 的計算總是與在節點i 處移動的玩家有關 。這意味著在遍歷博弈樹時,這個值會根據正在遍歷的節點變化 :對於任何兩個連續的節點,這個值是相反的。
▌2.7 終止蒙特卡洛樹搜尋
我們現在差不多已經知道了成功實施蒙特卡羅樹搜尋所需的所有部分,但還有幾個問題需要解決。 首先,什麼時候才能真正結束 MCTS ? 這個答案是:看情況。在構建一個博弈引擎時,你的“思考時間”可能是有限的,再加上你的計算能力也有其限制。因此,最穩妥的選擇是隻要在資源允許範圍內,就可以執行 MCTS 。
一旦完成 MCTS ,最優的一步通常是總訪問次數 N(v_i) 最高的節點,因為它的值是被估計的最好的(節點的自身估計值一定是很高的,並且同是也是被探索次數最多的節點)
在使用蒙特卡洛樹搜尋選擇了下一步之後,我們選擇的節點就會成為對手下一步的博弈初始狀態。 一旦他走出了他那一步,我們就可以從表示對手所選擇的博弈狀態的節點開始,再次開始蒙特卡羅樹搜尋。而之前 MCTS 輪次的一些統計資料仍有可能存在於我們正在考慮的新分支中。 這就可以重複使用統計資料,而不需要從頭開始構建新的樹——事實上,這也是 Alpha Go / Alpha Zero 的創造者做的工作。
03 總結
現在,我們終於可以把所有的東西放在一起了。回顧一下蒙特卡洛樹搜尋的第一個定義 ,我們可以用虛擬碼(pseudo-code)把它表示出來:
def monte_carlo_tree_search(root):
while resources_left(time, computational power):
leaf = traverse(root) # leaf = unvisited node
simulation_result = rollout(leaf)
backpropagate(leaf, simulation_result)
return best_child(root)
def traverse(node):
while fully_expanded(node):
node = best_uct(node)
return pick_univisted(node.children) or node # in case no children are present / node is terminal
def rollout(node):
while non_terminal(node):
node = rollout_policy(node)
return result(node)
def rollout_policy(node):
return pick_random(node.children)
def backpropagate(node, result):
if is_root(node) return
node.stats = update_stats(node, result)
backpropagate(node.parent)
def best_child(node):
pick child with highest number of visits
可以發現,這個定義可以用很少的函式實現。這個函式可以用於任何博弈,不僅是圍棋或國際象棋。這裡還有一個用蒙特卡洛樹搜尋玩井字棋的示例:https://github.com/int8/monte-carlo-tree-search 。
希望大家喜歡這篇文章,並且能夠對蒙特卡洛樹搜尋有一個基本的瞭解。
原連結:https://int8.io/monte-carlo-tree-search-beginners-guide/
AI科技大本營現招聘AI記者和資深編譯,有意者請將簡歷投至:gulei@csdn.net,期待你的加入!
如果你暫時不能加入營長的隊伍,也歡迎與營長分享你的精彩文章,投稿郵箱:suiling@csdn.net
AI科技大本營讀者群(計算機視覺、機器學習、深度學習、NLP、Python、AI硬體、AI+金融、AI+PM方向)正在招募中,關注AI科技大本營微信公眾號,後臺回覆:讀者群,聯絡營長,新增營長請備註姓名,研究方向。
☟☟☟點選 | 閱讀原文 | 檢視更多精彩內容
相關文章
- 【AlphaGo】AlphaGo背後的力量:蒙特卡洛樹搜尋入門指南Go
- Kotlin初學者指南Kotlin
- Nginx初學者指南Nginx
- Groovy初學者指南
- 【譯】GraphQL 初學者指南
- OAuth 2.0初學者指南OAuth
- Apache Hudi初學者指南Apache
- 給初學者的Web安全指南Web
- Electron 的初學者詳細指南
- Java初學者入門指南Java
- React Redux 的初學者詳盡指南ReactRedux
- 強化學習(十八) 基於模擬的搜尋與蒙特卡羅樹搜尋(MCTS)強化學習
- 96不同的二查搜尋樹
- 二叉搜尋樹
- Leetcode 700. 二叉搜尋樹中的搜尋(DAY 2)LeetCode
- 圖資料庫初學者指南資料庫
- 5S管理--初學者指南
- 從二分搜尋到二叉搜尋樹
- dart系列之:dart優秀的祕訣-隔離機制Dart
- leetcode 700. 二叉搜尋樹中的搜尋 思考分析LeetCode
- 二叉搜尋樹的操作集
- 二叉搜尋樹的結構
- 二分搜尋樹元素的插入
- Avalonia下拉可搜尋樹(TreeComboBox)
- 【資料結構】搜尋樹資料結構
- 給初學者的以太坊路線圖指南
- 一份送給Java初學者的指南Java
- [譯] 網站優化初學者指南網站優化
- 模糊測試: 初學者入門指南
- 價值流圖 (VSM) 初學者指南
- FGO保持制霸手遊界的祕訣:活動案例分析Go
- 初識搜尋:百度搜尋產品經理的第一課
- 二叉搜尋樹的python實現Python
- 96. 不同的二叉搜尋樹
- Day20 | 654.最大二叉樹 、 617.合併二叉樹 、 700.二叉搜尋樹中的搜尋 98.驗證二叉搜尋樹二叉樹
- Elasticsearch核心技術(五):搜尋API和搜尋執行機制ElasticsearchAPI
- 自動機器學習和AI初學者指南機器學習AI
- 57_初識搜尋引擎_分散式搜尋引擎核心解密之query phase分散式解密