다음을 통해 공유


BrainScript 식

이 섹션은 의도적으로 비공식 언어를 사용하여 읽을 수 있고 액세스할 수 있도록 하는 BrainScript 식의 사양입니다. 해당 항목은 여기에서 찾을 수 있는 BrainScript 함수 정의 구문의 사양입니다.

모든 브레인 스크립트는 식이며, 이 식은 멤버 변수를 기록하기 위해 할당된 식으로 구성됩니다. 네트워크 설명의 가장 바깥쪽 수준은 암시적 레코드 식입니다. BrainScript에는 다음과 같은 종류의 식이 있습니다.

  • 숫자 및 문자열과 같은 리터럴
  • 수학과 유사한 접두사 및 단항 연산(예: a + b
  • 3항 조건식
  • 함수 호출
  • 레코드, 레코드 멤버 액세스
  • arrays, array-element accesses
  • 함수 식(람다)
  • 기본 제공 C++ 개체 생성

우리는 의도적으로 대중 언어에 가능한 한 가까운 이들의 구문을 유지, 그래서 당신이 아래 찾을 무엇의 대부분은 매우 익숙한 보일 것이다.

개념

개별 식 종류를 설명하기 전에 먼저 몇 가지 기본 개념을 설명합니다.

즉시 계산과 지연된 계산 비교

BrainScript는 즉각적인 값과 지연이라는 두 가지 종류의 값을 알고 있습니다. 직접 실행 값은 BrainScript를 처리하는 동안 계산되지만 지연 값은 계산 네트워크의 노드를 나타내는 개체입니다. 계산 네트워크는 모델을 학습하고 사용하는 동안 CNTK 실행 엔진에서 수행하는 실제 계산을 설명합니다.

BrainScript의 직접 값은 계산을 매개 변수화하기 위한 것입니다. 텐서 차원, 네트워크 계층 수, 모델을 로드할 경로 이름 등을 나타냅니다. BrainScript 변수는 변경할 수 없으므로 직접 실행 값은 항상 상수입니다.

지연된 값은 컴퓨팅 네트워크를 설명하는 브레인 스크립트의 기본 목적에서 발생합니다. 계산 네트워크는 학습 또는 유추 루틴에 전달되는 함수로 볼 수 있으며, 이 함수는 CNTK 실행 엔진을 통해 네트워크 함수를 실행합니다. 따라서 많은 BrainScript 식의 결과는 실제 값이 아닌 계산 네트워크의 계산 노드입니다. BrainScript의 관점에서 지연된 값은 네트워크 노드를 나타내는 형식 ComputationNode 의 C++ 개체입니다. 예를 들어 두 네트워크 노드의 합을 사용하면 두 노드를 입력으로 사용하는 합계 작업을 나타내는 새 네트워크 노드가 만들어집니다.

스칼라와 행렬 대 텐서

계산 네트워크의 모든 값은 텐서라고 하는 숫자 n차원 배열이며 n텐서 순위를 나타냅니다. 텐서 차원은 입력 및 모델 매개 변수에 대해 명시적으로 지정됩니다. 연산자가 자동으로 유추합니다.

계산에 가장 일반적인 데이터 형식인 행렬은 순위 2의 텐서일 뿐입니다. 열 벡터는 순위 1의 텐서이고 행 벡터는 순위 2입니다. 행렬 제품은 신경망에서 일반적인 작업입니다.

텐서는 항상 지연된 값, 즉 지연된 계산 그래프의 개체입니다. 행렬 또는 텐서를 포함하는 모든 작업은 계산 그래프의 일부가 되며 학습 및 유추 중에 평가됩니다. 그러나 텐서 차원은 BS 처리 시간에 미리 유추/확인됩니다.

스칼라는 즉시 또는 지연된 값일 수 있습니다. 텐서 차원과 같이 계산 네트워크 자체를 매개 변수화하는 스칼라는 즉시 수행해야 합니다. 즉, BrainScript를 처리할 때 계산할 수 있어야 합니다. 지연된 스칼라는 차원 [1]의 순위 1 텐서입니다. 자체 안정기와 같은 학습 가능한 스칼라 매개 변수 및 다음과 같은 상수를 포함하여 네트워크 자체의 Log (Constant (1) + Exp(x))일부입니다.

동적 입력

BrainScript는 매우 간단한 형식 시스템을 사용하는 동적 형식의 언어입니다. 값을 사용할 때 BrainScript를 처리하는 동안 형식을 확인합니다.

직접 값은 숫자, 부울, 문자열, 레코드, 배열, 함수/람다 또는 CNTK 미리 정의된 C++ 클래스 중 하나입니다. 해당 형식은 사용 시 검사됩니다(예 COND : 문에 if 대한 인수가 a Boolean로 확인되고 배열 요소 액세스 시 개체가 배열이어야 함).

지연된 모든 값은 텐서입니다. Tensor 차원은 BrainScript를 처리하는 동안 확인되거나 유추되는 형식의 일부입니다.

직접 스칼라와 지연된 텐서 사이의 식은 스칼라를 지연된 Constant()스칼라로 명시적으로 변환해야 합니다. 예를 들어 Softplus 비선형성을 로 작성 Log (Constant(1) + Exp (x))해야 합니다. (향후 업데이트에서 이 요구 사항을 제거할 예정입니다.)

식 형식

리터럴

리터럴은 예상대로 숫자, 부울 또는 문자열 상수입니다. 예:

  • 13, 42, 3.1415926538, 1e30
  • true, 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')

접두사 및 단항 작업

BrainScript는 아래에 제공된 연산자를 지원합니다. BrainScript 연산자는 (요소별 제품), (행렬 제품) * 및 요소별 연산의 .* 특수 브로드캐스팅 의미 체계를 제외하고 인기 있는 언어에서 무엇을 기대하는지 의미하도록 선택됩니다.

숫자 접두사 연산자+, -, *, /.*

  • +스칼라-*, 행렬 및 텐서에 적용합니다.
  • .* 는 요소별 제품을 나타냅니다. Python 사용자에 대한 참고 사항: numpy와 *동일합니다.
  • / 는 스칼라에 대해서만 지원됩니다. 요소별 구분은 기본 제공 Reciprocal(x) 컴퓨팅을 사용하여 요소 단위 1/x로 작성할 수 있습니다.

부울 접두사 연산자 &&, ||

각각 부울 AND 및 OR을 나타냅니다.

문자열 연결(+)

문자열은 .와 +연결됩니다. 예: BS.Networks.Load (dir + "/model.dnn").

비교 연산자

6개의 비교 연산자는 <, >및 해당 부정 , >=!=<=. == 모든 즉시 값에 예상대로 적용할 수 있습니다. 결과는 부울입니다.

텐서에 비교 연산자를 적용하려면 텐서와 같은 Greater()기본 제공 함수를 대신 사용해야 합니다.

단항-, !

이들은 각각 부정과 논리적 부정을 나타냅니다. ! 는 현재 스칼라에만 사용할 수 있습니다.

요소별 작업브로드캐스트 의미 체계

행렬/텐서에 +-적용되고 .* 요소 단위로 적용됩니다.

모든 요소별 작업은 브로드캐스트 의미 체계를 지원합니다. 브로드캐스팅은 1로 지정된 모든 차원이 모든 차원과 일치하도록 자동으로 반복됨을 의미합니다.

예를 들어 차원의 행 벡터를 차원 [1 x N][M x N]행렬에 직접 추가할 수 있습니다. 1 차원이 자동으로 반복 M 됩니다. 또한 텐서 차원은 자동으로 차원으로 1 패딩됩니다. 예를 들어 행렬 차원 [M][M x N] 의 열 벡터를 추가할 수 있습니다. 이 경우 열 벡터의 차원은 행렬의 순위와 일치하도록 [M x 1] 자동으로 패딩됩니다.

Python 사용자 참고 사항: numpy와 달리 브로드캐스트 차원은 왼쪽 맞춤입니다.

행렬-제품 연산자*

연산은 A * B 행렬 제품을 표시합니다. 스파스 행렬에도 적용할 수 있으므로 텍스트 입력 또는 원 핫 벡터로 표시되는 레이블을 처리하는 효율성이 향상됩니다. CNTK 행렬 제품에는 2계급 > 텐서와 함께 사용할 수 있는 확장 해석이 있습니다. 예를 들어 순위 3 텐서의 모든 열을 행렬과 개별적으로 곱할 수 있습니다.

행렬 제품 및 해당 텐서 확장은 여기에 자세히 설명되어 있습니다.

참고: 스칼라를 곱하려면 요소별 제품을 .*사용합니다.

Python 사용자는 행렬 제품이 아닌 요소별 제품에 연산자를 사용하는 * 것이 좋습니다numpy. * CNTK 연산자는 numpy'sdot()'에 해당하지만 CNTK 배열에 대한 numpy Python 연 * 산자에 해당합니다.*.

조건부 연산자 if

BrainScript의 조건은 C++ ? 연산자처럼 식입니다. BrainScript 구문은 if COND then TVAL else EVAL바로 부울 식이어야 하는 구 COND 문이며, 식의 결과는 true이면 true이고 EVAL 그렇지 않은 경우 COND 입니다TVAL. 이 if 식은 동일한 BrainScript에서 유사한 플래그 매개 변수가 있는 여러 구성을 구현하는 데 유용하며 재귀에도 유용합니다.

(연산자는 if 즉시 스칼라 값에 대해서만 작동합니다. 지연된 개체에 대한 조건을 구현하려면 플래그 텐서에 따라 두 텐서 중 하나에서 값을 선택할 수 있는 기본 제공 함수 BS.Boolean.If()를 사용합니다. 형식 If (cond, tval, eval)이 있습니다.)

함수 호출

BrainScript에는 기본 제공 기본 형식(C++ 구현 포함), 라이브러리 함수(BrainScript로 작성됨) 및 사용자 정의(BrainScript)의 세 가지 종류의 함수가 있습니다. 기본 제공 함수의 예는 다음과 MaxPooling()같습니다Sigmoid(). 라이브러리 및 사용자 정의 함수는 기계적으로 동일하며 다른 소스 파일에 저장됩니다. 형식을 사용하여 수학 및 공용 언어와 마찬가지로 모든 종류가 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
}

이 식은 함수가 있는 3개의 멤버xy, 및 f위치에 f 있는 레코드를 정의합니다. 레코드 내에서 식은 위의 할당y에서 액세스하는 것처럼 x 이름으로만 다른 레코드 멤버를 참조할 수 있습니다.

그러나 많은 언어와 달리 레코드 항목은 순서대로 선언할 수 있습니다. 예를 들어 다음을 y선언할 수 있습니다. x 이는 되풀이 네트워크의 정의를 용이하게 하기 위한 것입니다. 모든 레코드 멤버는 다른 레코드 멤버의 식에서 액세스할 수 있습니다. Python과 다릅니다. F#과 let rec유사합니다. 순환 참조는 금지되며, 특수한 예외는 PastValue() 작업입니다 FutureValue() .

레코드가 중첩되면(다른 레코드 내에서 사용되는 레코드 식) 레코드 멤버는 바깥쪽 범위의 전체 계층 구조를 통해 조회됩니다. 실제로 모든 변수 할당은 레코드의 일부입니다. BrainScript의 외부 수준도 암시된 레코드입니다. 위의 예제 factorParameter 에서는 바깥쪽 범위의 레코드 멤버로 할당되어야 합니다.

레코드 내에 할당된 함수는 참조하는 레코드 멤버를 캡처합니다. 예를 들어 f() 캡처됩니다. 이 캡처 y는 다음에 따라 달라 x 지고 외부에서 정의 factorParameter됩니다. 이러한 캡처는 f() 람다를 포함 factorParameter 하거나 액세스할 수 없는 외부 범위로 전달할 수 있음을 의미합니다.

외부에서 레코드 멤버는 연산자를 . 사용하여 액세스됩니다. 예를 들어 위의 레코드 식을 변수 rr.x 에 할당한 경우 값을 13생성합니다. 연산자는 . 바깥쪽 범위를 트래버스하지 않습니다. r.factorParameter 오류가 발생하면 실패합니다.

(CNTK 1.6까지 중괄호 대신 레코드는 대괄호{ ... }를 사용했습니다[ ... ]. 여전히 허용되지만 더 이상 사용되지 않습니다.)

배열 및 배열 액세스

BrainScript에는 직접 값에 대한 1차원 배열 형식이 있습니다(텐서와 혼동하지 않음). 배열은 .를 사용하여 [index]인덱싱됩니다. 다차원 배열은 배열 배열로 에뮬레이트할 수 있습니다.

연산자를 사용하여 2개 이상의 요소 배열을 : 선언할 수 있습니다. 예를 들어 다음에서는 rank-3 매개 변수 텐서 선언을 위해 ParameterTensor{} 전달되는 명명 imageDims 된 3차원 배열을 선언합니다.

imageDims = (256 : 256 : 3)
inputFilter = ParameterTensor {imageDims}

값이 서로를 참조하는 배열을 선언할 수도 있습니다. 이를 위해 좀 더 많이 관련된 배열 할당 구문을 사용해야 합니다.

arr[i:i0..i1] = f(i)

는 인덱스 바인딩 i0 이 낮고 인덱 i1i 스 상한이 있는 배열 arr 을 생성합니다. 이 변수는 이니셜라이저 식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 해결될 예정입니다.

함수 식 및 람다

BrainScript에서 함수는 값입니다. 명명된 함수를 변수에 할당하고 인수로 전달할 수 있습니다. 예:

Layer (x, m, n, f) = f (ParameterTensor {(m:n)} * x + ParameterTensor {n})
h = Layer (x, 512, 40, Sigmoid)

여기서 Sigmoid 는 내부에서 Layer()사용되는 함수로 전달됩니다. 또는 C#과 유사한 람다 구문을 (x => f(x)) 사용하여 익명 함수를 인라인으로 만들 수 있습니다. 예를 들어 Softplus 활성화를 사용하여 네트워크 계층을 정의합니다.

h = Layer (x, 512, 40, (x => Log (Constant(1) + Exp (x)))

람다 구문은 현재 단일 매개 변수가 있는 함수로 제한됩니다.

레이어 패턴

위의 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 ARGRECORDTYPE 은 BrainScript ARGRECORD 에 노출된 미리 정의된 C++ 개체의 하드 코딩된 집합 중 하나이며 C++ 생성자에 전달되는 레코드 식입니다.

여기에 설명된 대로 괄호 형식을 BrainScriptNetworkBuilder = (new ComputationNetwork { ... })사용하는 경우에만 이 양식을 BrainScriptNetworkBuilder볼 수 있습니다. 그러나 이제는 new ComputationNetwork 형식의 새 C++ 개체를 만듭니다. 여기서 { ... } 는 내부 ComputationNetwork C++ 개체의 ComputationNetworkC++ 생성자에 전달되는 레코드를 정의합니다. 그러면 여기에 설명된 대로 5개의 특정 멤버, labelNodes, criterionNodesevaluationNodesoutputNodes5개의 특정 멤버featureNodes를 찾습니다.

내부적으로 모든 기본 제공 함수는 실제로 new CNTK C++ 클래스ComputationNode의 개체를 생성하는 식입니다. 자세한 내용은 기본 제공이 실제로 C++ 개체를 만드는 것으로 정의되는 방법을 Tanh() 참조하세요.

Tanh (z, tag='') = new ComputationNode { operation = 'Tanh' ; inputs = z /plus the function args/ }

식 계산 의미 체계

BrainScript 식은 처음 사용할 때 평가됩니다. BrainScript의 주요 목적은 네트워크를 설명하는 것이므로 식의 값은 지연된 계산을 위한 계산 그래프의 노드인 경우가 많습니다. 예를 들어, 브레인스크립트 각도 W1 * r + b1 에서 위의 예제에서는 숫자 값이 아닌 개체로 'evaluates' ComputationNode 를 실행합니다. 반면, 관련된 실제 숫자 값은 그래프 실행 엔진에 의해 계산됩니다. 브레인스크립트가 구문 분석될 때 스칼라의 BrainScript 식(예: 28*28)만 '계산'됩니다. 사용되지 않는 식(예: 조건)은 평가되지 않으며 형식 오류도 검사되지 않습니다.

식의 일반적인 사용 패턴

다음은 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 대신 현재 사용되지 않는 버전을 사용했습니다 NDLNetworkBuilderBrainScriptNetworkBuilder. NDLNetworkBuilder 는 훨씬 축소된 버전의 BrainScript를 구현했습니다. 다음과 같은 제한 사항이 있었습니다.

  • 접두사 구문이 없습니다. 모든 연산자는 함수 호출을 통해 호출되어야 합니다. 예: Plus (Times (W1, r), b1) 대신 .W1 * r + b1
  • 중첩된 레코드 식이 없습니다. 암시된 외부 레코드는 하나뿐입니다.
  • 조건식 또는 재귀 함수 호출이 없습니다.
  • 사용자 정의 함수는 특수 load 블록에서 선언되어야 하며 중첩할 수 없습니다.
  • 마지막 레코드 할당은 함수의 값으로 자동으로 사용됩니다.
  • NDLNetworkBuilder 언어 버전은 Turing-complete가 아닙니다.

NDLNetworkBuilder 더 이상 사용하지 않아야 합니다.