Translation of Caffe2 documents in Korean, especially tutorials. The second tutorial is about ‘Intro Tutorial’. Here is original Documentation about Intro Tutorial.
시작 튜토리얼
Caffe2의 기본 개념들
아래에서 Caffe2 모델을 개발하고 이해하는 데에 필수적인 Caffe2의 주요 개념에 대해 학습할 수 있습니다.
Blobs, Workspace, Tensors
Caffe2의 데이터는 blob으로 구성되어있습니다. Blob은 메모리에 있는 명명된(named) 데이터 덩어리입니다. 대부분의 blob들은 tensor를 포함하고, 파이썬에서는 numpy 배열로 변환됩니다. numpy는 파이썬을위한 대중적인 numerical 라이브러리이며 이미 설치되어있습니다.
Workspace는 모든 blob을 저장합니다. 다음 예제는 blob을 workspace
로 보내고 가져 오는 방법을 보여줍니다. 작업 공간은 시작 시, 자동으로 초기화됩니다.
from caffe2.python import workspace, model_helper
import numpy as np
# 3차원 랜덤 tensor 생성
x = np.random.rand(4, 3, 2)
print(x)
print(x.shape)
workspace.FeedBlob("my_x", x)
x2 = workspace.FetchBlob("my_x")
print(x2)
Nets 와 연산자(Operators)
Caffe2의 기본 객체는 net(network의 약자)입니다. Net은 연산자의 그래프이며, 각 연산자는 입력(input) blob 집합을 가져와 하나 이상의 출력(output) blob집합을 생성합니다.
아래의 코드 블록에서 아주 간단한 모델을 만들어보겠습니다. 모델은 다음의 요소들을 포함하고 있습니다. 요소들로 구성되어있습니다.
- 하나의 fully-connected layer(FC)
- a Sigmoid activation with a Softmax
- a CrossEntropy loss
net을 직접 작성하는 것은 매우 지루하므로, net을 만드는 데 도움을 주는 파이썬 클래스 model helpers를 사용하는 것이 좋습니다. 우리가 그것을 호출하고 “my first net”이라는 하나의 이름으로 전달하더라도 ModelHelper
는 두 개의 상호 연결된 net을 만듭니다: 파라미터를 초기화 하는 net (ref. init_net), 실제로 작동에 쓰이는 net (ref. exec_net)
# input data 생성
data = np.random.rand(16, 100).astype(np.float32)
# 정수 [0, 9]로 데이터에 라벨 생성
label = (np.random.rand(16) * 10).astype(np.int32)
workspace.FeedBlob("data", data)
workspace.FeedBlob("label", label)
임의로(randomly) 데이터와 레이블을 작성하고 workspace에 blob을 보냈습니다.
# model helper를 이용해 모델 만들기
m = model_helper.ModelHelper(name="my first net")
방금, model_helper
를 사용하여 앞서 언급 한 두 개의 net(init_net, exec_net)을 만들었습니다. 이 모델에서 FC 연산자를 사용하여 완전히 연결된(fully-connected) 레이어를 추가 할 계획이지만, 먼저 FC 연산자가 필요로 하는 random fills를 만들어 사전 준비 작업을 수행해야합니다. 다음으로 연산자를 추가하고 우리가 만든 weights 및 bias blob을 사용하면, FC 연산자를 호출 할 때 이름으로 호출 할 수 있습니다.
weight = m.param_init_net.XavierFill([], 'fc_w', shape=[10, 100])
bias = m.param_init_net.ConstantFill([], 'fc_b', shape=[10, ])
Caffe2에서 FC 연산자는 input blob(우리의 데이터), weights, bias를 받습니다. XavierFill
이나 ConstantFill
를 사용하는 weights와 bias는 둘 다 빈 배열(empty array), 이름, 그리고 형태를 받습니다.(예. shape=[output, input]
)
fc_1 = m.net.FC(["data", "fc_w", "fc_b"], "fc1")
pred = m.net.Sigmoid(fc_1, "pred")
[softmax, loss] = m.net.SoftmaxWithLoss([pred, "label"], ["softmax", "loss"])
위의 코드블럭을 분석 해봅시다.
먼저, 우리는 input data와 레이블 blob을 메모리에 만들었습니다. (더 나중에 다룰 연습에서는 database와 같은 데이터 소스로부터 data를 가져올 수도 있습니다.) 데이터와 label blob들이 1차원 값 ‘16’을 갖는다는 것에 유의하세요; 모델로 들어가는 input(입력)이 한 번에 16개 샘플로 이루어진 mini-batch이기 때문입니다. 대부분의 Caffe2 연산자들이 ModelHelper
를 통해 직접 접근이 가능하고, input의 mini-batch를 다룰 수 있습니다. 자세한 사항은 ModelHelper‘s Operator List를 참조 해주세요.
두 번째로, 우리는 몇 개의 연산자를 정의함으로써 모델을 만들었습니다: FC, Sigmoid, 그리고 SoftmaxWithLoss. 주의: 여기서 우리는 모델의 정의를 만들었을 뿐, 연산자들이 실행되는 것은 아닙니다.
Model helper는 2개의 net을 만들어냅니다: m.param_init_net
은 딱 한번만 실행되는 net입니다. 이것은 (FC layer의 weight와 같은) 모든 parameter blob들을 초기화시킵니다. 실제로 training은 m.net
을 실행시켜 하게 됩니다. 이것은 사용자에게 느껴지지 않으며 자동으로 일어납니다.
net의 정의는 protobuf 구조상에 저장됩니다.(더 많은 정보를 위해 Google의 Protobuffer문서를 참조하세요. protobuffer는 Thrift 구조와 같은 말입니다.) net.Proto()
를 호출함으로써 쉽게 검사해 볼 수 있습니다.
print(str(m.net.Proto()))
output은 다음과 같습니다.
name: "my first net"
op {
input: "data"
input: "fc_w"
input: "fc_b"
output: "fc1"
name: ""
type: "FC"
}
op {
input: "fc1"
output: "pred"
name: ""
type: "Sigmoid"
}
op {
input: "pred"
input: "label"
output: "softmax"
output: "loss"
name: ""
type: "SoftmaxWithLoss"
}
external_input: "data"
external_input: "fc_w"
external_input: "fc_b"
external_input: "label"
파라미터를 초기화하는 net도 살펴볼 필요가 있습니다.
print(str(m.param_init_net.Proto()))
FC연산자의 weight과 bias blob의 random fill을 생성하는 두 연산자가 존재하는 방식을 알게 될 것입니다.
여기까지가 Caffe2 API의 기본 아이디어입니다: net을 만들어 모델을 학습시키는 데에 Python을 이용하세요. 그 net들을 직렬화(serialized)된 protobuffer로써 C++코드에 전달되도록 하고, C++코드가 완전한 성능으로 net을 실행하도록 만듭니다.
실행
이제 모델을 학습시킬 연산자들이 정의되었으니, 그것을 실행시켜 모델을 학습시켜봅시다.
먼저, parameter 초기화를 한 번만 실행시킵니다:
workspace.RunNetOnce(m.param_init_net)
참고로, 이것을 실행하기 위해서는 평소처럼 param_init_net
의 protobuffer를 C++런타임에 전달합니다.
다음으로, 실제 training Net을 만듭니다:
workspace.CreateNet(m.net)
한번 만들어두면 효율적으로 여러 번 실행시킬 수 있습니다.
# 100 x 10 반복(iterations)을 실행
for j in range(0, 100):
data = np.random.rand(16, 100).astype(np.float32)
label = (np.random.rand(16) * 10).astype(np.int32)
workspace.FeedBlob("data", data)
workspace.FeedBlob("label", label)
workspace.RunNet(m.name, 10) # 10번 실행
RunNet()
에서 네트워크 이름을 참조하는 방법에 유의해주세요. net이 workspace안에 만들어졌기 때문에, 우리는 net정의를 다시 거칠 필요가 없습니다.
실행 이후에, output blob안에 저장된 결과(numpy 배열 같은 tensor도 포함)를 확인 할 수 있습니다.
print(workspace.FetchBlob("softmax"))
print(workspace.FetchBlob("loss"))
역방향 패스(Backward pass)
이 net은 순방향 패스(forward pass)만을 포함하고 있으므로 아무것도 학습할 수 없습니다. 역방향 패스(backward pass)는 순방향 패스의 각 연산자에 대한 기울기(gradient) 연산자를 생성해 만들어집니다.
만약 이 예제를 해보길 원한다면, 다음의 예제를 통해 결과를 확인해보세요!
RunNetOnce()
를 호출하기 이전에 아래의 코드를 삽입합니다:
m.AddGradientOperators([loss])
protobuf output을 확인합니다:
print(str(m.net.Proto()))
이것으로 overview를 마칩니다. 그러나 이후 튜토리얼에서 더 많은 것을 배울 수 있을 것입니다.