本節是 BrainScript 運算式的規格,雖然我們刻意使用非正式語言來讓它保持可讀性和可存取性。 其對應專案是 BrainScript 函式定義語法的規格, 可在這裡找到。
每個大腦腳本都是一個運算式,接著是由指派給記錄成員變數的運算式所組成。 網路描述的最外層是隱含的記錄運算式。 BrainScript 具有下列類型的運算式:
- 常值,例如數位和字串
- 類似數學的 infix 和一元運算,例如
a + b - 三元條件運算式
- 函式調用
- 記錄、記錄成員存取
- arrays, array-element accesses
- 函式運算式 (Lambda)
- 內建 C++ 物件建構
我們刻意讓每個語言的語法盡可能接近熱門語言,因此您找到的大部分內容會非常熟悉。
概念
在描述個別運算式類型之前,請先瞭解一些基本概念。
立即計算與延後計算
BrainScript 知道兩種值: 立即 和 延遲。 在 BrainScript 處理期間會計算立即值,而延後值則是代表計算網路中節點的物件。 計算網路描述在定型和使用模型期間,CNTK執行引擎所執行的實際計算。
BrainScript 中的立即值是用來將計算參數化。 它們代表張量維度、網路層數目、要從中載入模型的路徑名稱等等。由於 BrainScript 變數不可變,因此立即值一律為常數。
延後值來自大腦腳本的主要用途:描述計算網路。 計算網路可視為傳遞至定型或推斷常式的函式,然後透過CNTK執行引擎執行網路函式。 因此,許多 BrainScript 運算式的結果是計算網路中計算節點,而不是實際值。 從 BrainScript 的觀點來看,延後的值是代表 ComputationNode 網路節點類型的 C++ 物件。 例如,採用兩個網路節點的總和會建立新的網路節點,代表採用兩個節點做為輸入的加總作業。
純量與矩陣與 Tensors
計算網路中的所有值都是我們呼叫張量的數位n維陣列,而 n表示張量順位。 明確指定輸入和模型參數的 Tensor 維度;和 由運算子自動推斷。
計算矩陣最常見的資料類型只是排名 2 的張量。 資料行向量是排名 1 的張量,而資料列向量則是排名 2。 矩陣乘積是類神經網路中的常見作業。
Tensors 一律是延後值,也就是延遲計算圖形中的物件。 涉及矩陣或張量的任何作業都會成為計算圖表的一部分,並在定型和推斷期間進行評估。 不過,系統會在 BS 處理時間預先推斷/檢查 Tensor 維度。
純量可以是立即或延後的值。 參數化計算網路本身的純量,例如張量維度,必須是立即的,也就是在處理 BrainScript 時可計算。 延後純量是維度 [1] 的順位 1 張量。 它們是網路本身的一部分,包括可學習的純量參數,例如自我穩定器,以及 中的 Log (Constant (1) + Exp(x)) 常數。
動態輸入
BrainScript 是一種具有極簡單類型系統的動態類型語言。 使用值時,會在處理 BrainScript 期間檢查類型。
即時值的類型為 number、Boolean、string、record、array、function/lambda 或其中一個CNTK預先定義的 C++ 類別。 其類型會在使用時檢查 (例如, COND 語句的 if 引數會驗證為 Boolean ,而陣列元素存取需要物件成為陣列) 。
所有延後的值都是張量。 Tensor 維度是其類型的一部分,會在處理 BrainScript 期間檢查或推斷。
即時純量與延後張量之間的運算式必須明確地將純量轉換成延遲 Constant() 的 。 例如,Softplus 非線性必須寫入為 Log (Constant(1) + Exp (x)) 。 (計畫在即將推出的 update.) 中移除這項需求
運算式類型
常值
常值是數值、布林值或字串常數,如您所預期。 範例:
13,42,3.1415926538,1e30true,false"my_model.dnn",'single quotes'
數值常值一律為雙精確度浮點數。 BrainScript 中沒有明確的整數類型,雖然陣列索引之類的某些運算式會在呈現不是整數的值時失敗並出現錯誤。
字串常值可以使用單引號或雙引號,但無法逸出引號或包含單引號和雙引號的字串 (內的其他字元,例如 "He'd say " + '"Yes!" in a jiffy.') 。 字串常值可以跨越多行;例如:
I3 = Parameter (3, 3, init='fromLiteral', initFromLiteral = '1 0 0
0 1 0
0 0 1')
Infix 和一元運算
BrainScript 支援下面提供的運算子。 BrainScript 運算子是選擇來表示預期來自熱門語言的專案,除了 .* (元素乘積) 、 * (矩陣產品) ,以及元素明智運算的特殊廣播語意。
數值Infix 運算子+ 、 - 、 * 、 / 、.*
+、-和*適用于純量、矩陣和張量。.*表示元素乘積。 Python 使用者的注意事項:這相當於 numpy 的*。/僅支援純量。 您可以使用內Reciprocal(x)建來撰寫元素相除法,以計算元素的 。1/x
布林值 Infix 運算子 && 、 ||
這些分別代表布林值 AND 和 OR。
字串串連 (+)
字串會與 + 串連。 範例: BS.Networks.Load (dir + "/model.dnn").
比較運算子
六個比較運算子為 < 、 == 、 > 和其負 >= 數 、、 !=<= 。 它們可以如預期般套用至所有立即值;其結果為布林值。
若要將比較運算子套用至張量,則必須改用內建函數,例如 Greater() 。
一元- ,!
這些分別代表否定和邏輯否定。 ! 目前只能用於純量。
元素運算 和 廣播語意
套用至矩陣/張量時, + 會以元素方式套用 、 - 和 .* 。
所有元素的作業都支援 廣播語意。 廣播表示任何指定為 1 的維度都會自動重複,以符合任何維度。
例如,維度的資料 [1 x N] 列向量可以直接加入維度 [M x N] 的矩陣。 維度 1 會自動重複 M 次數。 此外,張量維度會自動填補 1 維度。 例如,允許加入矩陣維度 [M][M x N] 的資料行向量。 在此情況下,資料行向量的維度會自動填補為 [M x 1] ,以符合矩陣的順位。
Python 使用者注意:不同于 numpy,廣播維度會靠左對齊。
Matrix-Product 運算子*
作業 A * B 表示矩陣乘積。 它也可以套用至疏鬆矩陣,以提升處理以單熱向量表示之文字輸入或標籤的效率。 在CNTK中,矩陣產品具有延伸解譯,可讓它與排名 > 2 的張量搭配使用。 例如,可以個別將順位 3 張量中的每個資料行與矩陣相乘。
矩陣乘積及其張量延伸模組詳述 于此處。
注意:若要與純量相乘,請使用元素乘積 .* 。
建議 Python 使用者針對 numpy元素乘積使用 * 運算子,而不是矩陣乘積。 CNTK的 * 運算子對應至 numpy 的 dot() ,而 CNTK 相當於 Python * 的 numpy 陣列運算子為 .* 。
條件運算子 if
BrainScript 中的條件式是運算式,例如 C++ ? 運算子。 BrainScript 語法為 if COND then TVAL else EVAL ,其中 COND 必須是立即布林運算式,而運算式的結果為 TVAL 如果 COND 為 true, EVAL 則為 ,否則為 。 運算式 if 適用于在相同的 BrainScript 中實作多個類似的旗標參數化組態,也適用于遞迴。
(if 運算子僅適用于即時純量值。若要實作延後物件的條件,請使用內建函 BS.Boolean.If() 式 ,其允許根據旗標張量從兩個張量之一中選取值。其格式 If (cond, tval, eval) 為 .)
函式調用
BrainScript 有三種 函式:內建基本類型 (搭配 C++ 實作) 、程式庫函式 (以 BrainScript) 撰寫,以及使用者定義的 (BrainScript) 。 內建函式的範例為 Sigmoid() 和 MaxPooling() 。 程式庫和使用者定義函式的機械方式相同,只是儲存在不同的原始程式檔中。 所有類型都會叫用,類似于數學和通用語言,使用 形式 f (arg1, arg2, ...) 。
某些函式接受選擇性參數。 選擇性參數會傳遞為具名參數,例如 f (arg1, arg2, option1=..., option2=...) 。
函式可以遞迴方式叫用,例如:
DNNLayerStack (x, numLayers) =
if numLayers == 1
then DNNLayer (x, hiddenDim, featDim)
else DNNLayer (DNNLayerStack (x, numLayers-1), # add a layer to a stack of numLayers-1
hiddenDim, hiddenDim)
請注意運算子如何 if 用來結束遞迴。
圖層建立
函式可以建立整個層級或模型,這些是類似函式的函式物件。
依照慣例,建立具有可學習參數之圖層的函式會使用大括弧 { } ,而不是括弧 ( ) 。
您將會遇到如下的運算式:
h = DenseLayer {1024} (v)
在這裡,有兩個叫用現正播放。 第一個是 DenseLayer{1024} 會建立函式物件的函式呼叫,接著會套用至資料 (v) 。
由於 DenseLayer{} 會傳回具有可學習參數的函式物件,因此會使用 { } 來表示這個。
記錄和Record-Member存取
記錄運算式是以大括弧括住的指派。 例如:
{
x = 13
y = x * factorParameter
f (z) = y + z
}
此運算式會定義具有三個成員 、、 y 和 f 的記錄, x 其中 f 是函式。
在記錄內,運算式只要依其名稱參考其他記錄成員,就像 x 在 指派中存取上述一 y 樣。
不過,不同于許多語言,記錄專案可以 依任何順序宣告。 例如 x ,可以在 之後 y 宣告。 這是為了協助定義迴圈網路。 任何記錄成員都可以從任何其他記錄成員的運算式存取。 這與 Python 不同;和 類似 F# 的 let rec 。 迴圈參考是禁止的,但和 FutureValue() 作業的特殊例外狀況 PastValue() 。
當記錄是巢狀 (記錄運算式用於其他記錄) ,則記錄成員會透過整個封入範圍階層查閱。 事實上, 每個 變數指派都是記錄的一部分:BrainScript 的外部層級也是隱含的記錄。 在上述範例中, factorParameter 必須將 指派為封入範圍的記錄成員。
在記錄內指派的函式會擷取他們參考的記錄成員。 例如, f() 會擷取 y ,接著會相 x 依于 和 外部定義的 factorParameter 。 擷取這些表示 f() 可以當做 Lambda 傳遞至不包含 factorParameter 或有權存取其外部範圍。
從外部使用 運算子存取 . 記錄成員。 例如,如果我們已將上述記錄運算式指派給變數 r ,則 r.x 會產生 值 13 。 .運算子不會周遊封入範圍: r.factorParameter 會失敗併發生錯誤。
(請注意,直到 CNTK 1.6,而不是大括弧 { ... } ,記錄會使用括弧 [ ... ] 。這仍允許,但已被取代.)
陣列和陣列存取
BrainScript 具有即時值的一維陣列類型, (不會與張量混淆) 。 陣列會使用 [index] 編制索引。 多維度陣列可以模擬為數組的陣列。
至少有 2 個元素的陣列可以使用 運算子來 : 宣告。 例如,下列宣告名為 imageDims 的 3 維陣列,然後傳遞至 ParameterTensor{} 以宣告 rank-3 參數 tensor:
imageDims = (256 : 256 : 3)
inputFilter = ParameterTensor {imageDims}
您也可以宣告其值彼此參考的陣列。 因此,一個必須使用稍微相關的陣列指派語法:
arr[i:i0..i1] = f(i)
它會建構名為 arr 的陣列,其具有下限和 i0 上限索引系結 i1 , i 表示變數以表示初始化運算式f(i) 中的索引變數,進而表示 的值 arr[i] 。 陣列的值會延遲評估。 這可讓特定索引 i 的初始化運算式存取相同陣列的其他元素 arr[j] ,只要沒有迴圈相依性即可。 例如,這可用來宣告網路層堆疊:
layers[l:1..L] =
if l == 1
then DNNLayer (x, hiddenDim, featDim)
else DNNLayer (layers[l-1], hiddenDim, hiddenDim)
不同于我們稍早引進的遞迴版本,此版本會藉由說 來 layers[i] 保留每個個別層的存取權。
或者,也有運算式語法 array[i0..i1] (i => f(i)) ,較不方便,但有時很有説明。 上述看起來會像這樣:
layers = array[1..L] (l =>
if l == 1
then DNNLayer (x, hiddenDim, featDim)
else DNNLayer (layers[l-1], hiddenDim, hiddenDim)
)
注意:目前沒有任何方法可以宣告 0 個元素的陣列。 這會在未來版本的 CNTK中解決。
函式運算式和 Lambda
在 BrainScript 中,函式是值。 具名函式可以指派給變數,並以引數的形式傳遞,例如:
Layer (x, m, n, f) = f (ParameterTensor {(m:n)} * x + ParameterTensor {n})
h = Layer (x, 512, 40, Sigmoid)
其中 Sigmoid 會傳遞為 函式,其用於 內部 Layer() 。 或者,類似 C# 的 Lambda 語法 (x => f(x)) 允許內嵌建立匿名函式。 例如,這會使用 Softplus 啟用來定義網路層:
h = Layer (x, 512, 40, (x => Log (Constant(1) + Exp (x)))
Lambda 語法目前僅限於具有單一參數的函式。
圖層模式
上述 Layer() 範例結合了參數建立和函式應用程式。
慣用的模式是將這些模式分成兩個步驟:
- 建立參數並傳回保存這些參數的函式物件
- 建立將參數套用至輸入的函式
具體而言,後者也是函式物件的成員。 上述範例可以重寫為:
Layer {m, n, f} = {
W = ParameterTensor {(m:n)} # parameter creation
b = ParameterTensor {n}
apply (x) = f (W * x + b) # the function to apply to data
}.apply
和 會叫用為:
h = Layer {512, 40, Sigmoid} (x)
此模式的原因是一般網路類型是由將一個函式套用到另一個輸入所組成,這可以更輕鬆地使用 函 Sequential() 式來撰寫。
CNTK隨附一組豐富的預先定義層,如這裡所述。
建構內建 C++ CNTK 物件
最後,所有 BrainScript 值都是 C++ 物件。 特殊的 BrainScript 運算子 new 用於與基礎CNTK C++ 物件互動。 它具有表單 new TYPE ARGRECORD ,其中 TYPE 是公開至 BrainScript 之預先定義 C++ 物件的硬式編碼集合之一,而且 ARGRECORD 是傳遞至 C++ 建構函式的記錄運算式。
如果您使用的是 的括弧形式,您可能只會看到此表單 BrainScriptNetworkBuilder ,亦即 BrainScriptNetworkBuilder = (new ComputationNetwork { ... }) , 如這裡所述。
但現在您知道它的意義: new ComputationNetwork 建立類型的 ComputationNetwork 新 C++ 物件,其中 { ... } 只會定義傳遞至內部 ComputationNetwork C++ 物件的 C++ 建構函式的記錄,然後尋找 5 個特定成員 featureNodes 、 labelNodes 、 criterionNodes 、、 evaluationNodes 和 outputNodes , 如這裡所述。
在幕後,所有內建函式都是建 new 構 CNTK C++ 類別 ComputationNode 物件的運算式。 如需圖例,請參閱內建如何 Tanh() 實際定義為建立 C++ 物件:
Tanh (z, tag='') = new ComputationNode { operation = 'Tanh' ; inputs = z /plus 函式 args/ }
運算式評估語意
BrainScript 運算式會在第一次使用時進行評估。 由於 BrainScript 的主要用途是描述網路,因此運算式的值通常是計算圖形中的節點,以供延遲計算。 例如,從 BrainScript 角度, W1 * r + b1 上述範例中的 「評估」物件 ComputationNode 而非數值;而涉及的實際數值將會由圖形執行引擎計算。 只有純量的 BrainScript 運算式 (例如 28*28 ,) 在剖析 BrainScript 時會「計算」。 從未使用過的運算式 (例如,因為條件) 永遠不會評估 (也不會檢查類型錯誤) 。
運算式的常見使用模式
以下是一些搭配 BrainScript 使用的常見模式。
函式的命名空間
藉由將函式指派分組到記錄中,就可以達成名稱步調的形式。 例如:
Layers = {
Affine (x, m, n) = ParameterTensor {(m:n)} * x + ParameterTensor {n}
Sigmoid (x, m, n) = Sigmoid (Affine (x, m, n))
ReLU (x, m, n) = RectifiedLinear (Affine (x, m, n))
}
# 1-hidden layer MLP
ce = CrossEntropyWithSoftmax (Layers.Affine (Layers.Sigmoid (feat, 512, 40), 9000, 512))
本機範圍變數
有時候,對於更複雜的運算式,最好有本機範圍的變數和/或函式。 這可以藉由將整個運算式括在記錄中,並立即存取其結果值來達成。 例如:
{ x = 13 ; y = x * x }.y
將會建立具有立即讀取成員 y 的「暫存」記錄。此記錄是「暫時」,因為它未指派給變數,因此其成員無法存取,但 除外 y 。
此模式通常用於讓具有內建參數的 NN 層更容易閱讀,例如:
SigmoidLayer (m, n, x) = {
W = Parameter (m, n, init='uniform')
b = Parameter (m, 1, init='value', initValue=0)
h = Sigmoid (W * x + b)
}.h
h您可以在這裡考慮此函式的「傳回值」。
下一步:瞭解如何 定義 BrainScript 函式
NDLNetworkBuilder (已被取代)
舊版CNTK現在已淘汰 NDLNetworkBuilder ,而不是 BrainScriptNetworkBuilder 。 NDLNetworkBuilder 實作的 BrainScript 版本大幅減少。 它有下列限制:
- 沒有 infix 語法。 所有運算子都必須透過函式呼叫來叫用。 例如,
Plus (Times (W1, r), b1)而不是W1 * r + b1。 - 沒有巢狀記錄運算式。 只有一個隱含的外部記錄。
- 沒有條件運算式或遞迴函式呼叫。
- 使用者定義函式必須在特殊
load區塊中宣告,而且不能巢狀。 - 最後一筆記錄指派會自動當做函式的值使用。
- 語言
NDLNetworkBuilder版本不是圖文完成。
NDLNetworkBuilder 不應再使用。