텐서플로우를 이용하여 신경망 양자화(Quantize) 하는 방법
(v1.0)
최신 신경망이 개발될 때, 가장 큰 도전은 어떻게든 일을 하게 하는 것이였다. 이것 은 학습에서 정확도와 속도가 가장 중요했다는 것을 의미한다. 부동소수점을 이용한 연산은 정확도를 유지하기 가장 쉬운 방법이었다, 그리고 GPU들은 이런 부동소수점 계 산 가속에 특화되어 있다, 따라서 다른 형태의 연산 타입에는 많은 관심이 없었다.
최근에, 많은 모델들이 적용된 상용 제품들을 다수 보유 하고 있다. The computation demands of training grow with the number ofresearchers, but the cycles needed for inference expand in proportion to users. 이는 많은 팀에서 순수한 추론 효율이 상당히 중요한 이슈라는 것을 의미합니다.
이런 이유로 quantization이 발생한 것입니다. Quantization은 숫자를 저장하고, 계산 을 위한 32bit 부동 소수점 형식보다 컴팩트하고, 많은 부분을 커버하는 포괄적 용어 입니다. 나는 앞으로 8bit 고정소수점에 초점을 맞추고 풀어갈 예정입니다.
[TOC]
양자화는 왜 동작할까?
신경망을 학습시키기 위해서는, 가중치들을 여러번 조금씩 조정해야 합니다. 그리고 조금씩 증가 혹은 감소를 통해 동작 시키기 위해서는 부동소수점이 필요하게 됩니다 (양자화된 표현을 사용하려는 노력이 있기는 하지만 말이죠).
미리 학습된 모델을 이용하여 추론하는 것은 매우 다릅니다. 깊은 신경망에서 대단한 성능을 보이는 것은 그 deep network가 입력의 노이즈를 잘 감추기 때문입니다. 만약 당신이 방금 찍은 사진에서 물체를 인식하기를 바란다면, 신경망은 모든 CCD 잡음, 빛 변화, 그리고 학습예제로 사용했던 것과 다른 불필요한 것들을 무시할 필요가 있 습니다, 그리고 중요하다고 생각되는 것에 집중하게 되죠. 이 능력은 낮은 정밀도의 연산은 위에서 언급한 것 처럼 단순히 잡음으로 취급될 수 있다는 것을 의미합니다. 그리고 적은 정보를 가지고 있는 숫자 표현으로도 정확한 결과를 생산해 냅니다.
왜 양자일까?
신경망 모델들은 디스크의 많은 공간을 차지합니다, 예를 들어 AlexNet의 경우 200MB 가 넘는 부동 소수점 변수들이 필요합니다. 그 크기의 대부분은 신경망 연결을 위한 가중치에 할애되고 있습니다. 그 이유는 신경망 연결이 수백만개가 하나의 모델에 존 재하기 때문입니다. 왜냐하면, 모든 변수들은 다 조금 씩 다른 부동 소수점이기 때문 입니다, zip과 같이 단순한 압축 형식은 이 변수들을 잘 압축시키지 못합니다. 많은 층에 위치해 있지고, 각각의 층의 가중치들은 대부분 특정 영역에 분산되어 있습니다. 예를 들어 -3.0 부터 +6.0까지.
Quantization의 가장 단순한 동기는 파일의 크기를 줄이는 것이었습니다. 크기를 줄 이기 위해서 각 층의 최대, 최소 값을 저장하고, 각각의 부동 소수점 값을 256개의 범위 내에서 가장 가까운 실수 값을 8bit 정수형으로 압축시키는 것입니다. 예를 들어 -3.0에서 6.0까지의 범위에서, 0x0은 -3.0을 의미합니다, 그리고 0xff는 6.0을 표현 하겠죠, 그리고 0xef는 1.5정도를 표현 하겠죠. 정확한 계산은 뒤에서 합시다, 왜냐 하면 좀 미묘한 문제가 있습니다, 하지만 이런 방식을 사용하면 75%정도의 디스크 크 기를 아낄 수 있습니다, 그리고 다시 부동 소수점으로 바꾸면 우리의 모델은 변경 없 이 부동 소수점을 사용할 수 있습니다.
Quantize의 다른 이유는 연산을 통한 추론 과정에서 하드웨어 연산기를 줄이기 위함 입니다, 전체를 8bit 입력과 8bit 출력으로 사용하여서 말이죠. 계산이 필요한 모든 곳에서 변화가 필요하기 때문에 상당히 어렵습니다. 하지만 매우 높은 보상이 있습니 다. 8bit의 값을 읽어 오는 것은 부동 소수점을 읽어오는 것의 25%에 해당하는 메모리 대역폭만을 요구합니다, 따라서 RAM에 접근에 따른 병목 현상이나, 캐싱을 함에 있어 훨씬 더 좋은 성능을 낼 수 있습니다. 또한 SIMD(Single Instruction Multiple Data) 명령어를 이용하여 한 클럭당 더욱 많은 명령어를 수행할 수도 있습니다. 몇몇 경우에 는 DSP(Digital Signal Processing)칩을 이용하여 8bit 연산을 가속할 수도 있습니다.
8bit를 이용한 연산으로 옮겨갈 경우, 모델들을 더욱 빠르고, 저전력(휴대기기에서 중 요한 조건)으로 동작시킬 수 있습니다. 또한 부동 소수점 연산이 불가능한 많은 embed ded 시스템에 적용될 수 있습니다, 따라서 IoT 세상에 많은 애플리케이션에 적용 될 수 있습니다.
왜 낮은 정밀도로 바로 학습시키지 않는 것인가요?
적은 bit를 이용하여 학습시키는 실험들이 몇번 시도 되었었습니다, 하지만 결과는 역전파(backpropagation)과 기울기(gradient)를 위해서는 더욱 정밀한 bit 폭이 필 요한 것으로 파악되었습니다. 이런 문제는 학습을 더욱 복잡하게 만들고, 추론을 더욱 힘들게 합니다. 우리는 이미 부동 소수점으로 이루어진 잘알려진 모델들에 대해 실험 해보고, quantize로의 변환을 즉각적으로 매우 쉽게 할 수 있는 것을 확인 했습니다.
어떻게 당신의 모델을 Quantize하나요?
TensorFlow는 제품화 단계 등급의 8bit 연산기능을 지원하고 있습니다. 또한 부동 소수 점으로 학습된 많은 모델들을 양자화된 연산을 통해 동일한 추론이 가능하도록 변환할 수 있습니다. 예를들어 가장 최근의 GoogLeNet 모델을 8bit 연산으로 변경하는 방법을 소개하고 있습니다:
curl http://download.tensorflow.org/models/image/imagenet/inception-2015-12-05.tgz -o /tmp/inceptionv3.tgz
tar xzf /tmp/inceptionv3.tgz -C /tmp/
bazel build tensorflow/tools/quantization/tools:quantize_graph
bazel-bin/tensorflow/tools/quantization/tools/quantize_graph \
--input=/tmp/classify_image_graph_def.pb \
--output_node_names="softmax" --output=/tmp/quantized_graph.pb \
--mode=eightbit
이것은 내부적으로는 8bit 연산을 사용하고 가중치 또한 양자화 되었지만, 원본 모델 과 동일하게 동작합니다. 파일의 크기를 본다면, 원본과 비교하여 25%밖에 되지 않습니 다(원본은 91MB인 반면 새로 생성된 모델은 23MB이다). 같은 입력과 출력을 갖고, 같은 결과를 얻을 수 있습니다. 아래에 예제가 있습니다:
bazel build tensorflow/examples/label_image:label_image
bazel-bin/tensorflow/examples/label_image/label_image \
--input_graph=/tmp/quantized_graph.pb \
--input_width=299 \
--input_height=299 \
--mean_value=128 \
--std_value=128 \
--input_layer_name="Mul:0" \
--output_layer_name="softmax:0"
새롭게 양자화(quantized)된 그래프를 볼 수 있으며, 출력이 원본과 굉장히 비슷한 것을 볼 수 있습니다.
입력과 출력의 이름을 당신의 네트워크의 맞게 바꾸어 GraphDefs로 저장함으로서, 당 신의 모델도 같은 처리를 할 수 있습니다. 상수값들이 저장되어 있는 파일을 변환하기 위해 freeze_graph 스크립트를 수행하는 것을 추천합니다.
양자화 프로세스가 어떻게 동작하나요?
추론과정에 주로 사용되는 연산(operations)들을 8bit 버전의 동일한 동작을 할 수 있 는 코드를 구현하였습니다. Convolution, 행렬 곱셈기, 활성 함수, pooling 연산 그리 고 행렬 합산기를 포함하고 있습니다. 기존 연산과 양자화 연산이 동일하게 각각의 연산 (TensorFlow의 ops; 번역)들에 변환 스크립트를 적용하였습니다. 부동 소수점과 8bit간 의 이동을 위한 작은 부-그래프(sub-graph) 변환기를 위한 스크립트입니다. 아래 예제는 그것들이 어떻게 생겼는지에 대한 그림 입니다. 처음으로 소개할 것은 입력과 출력이 부동 소수점으로 이루어진 원본 ReLU 연산입니다:
그리고, 아래 그림은 동일하지만 변환된 subgraph입니다, 여전히 부동 소수점 입력과 출력을 갖고 있지만 내부 변환을 통해 연산은 8bit로 진행되는 것을 알 수 있습니다.
최대, 최소값 연산은 처음의 부동 소수점 tensor를 확인하고 역양자화(dequantize)연 산을 위해 공급됩니다. 양자화 표현 방법에 관한 설명은 나중에 언급하도록 하겠습 니다.
처음 각각의 연산(ReLU, convolution 등)이 변환 되었으면, 다음 단계는 부동 소수점 으로(부터, to/from)의 불필요한 변환을 없애는 것입니다. 만약 연속된 연산들이 부동 소수점과 동일(여기서는 8bit quantize를 의미)하다면, 양자화와 역 양자화는 필요가 없게 되고 서로 상쇄될 것입니다 아래와 같이요:
모든 연산(graph in tensorflow)들이 양자화 된 거대한 모델에 적용된다면 tensor들의 연산은 모두 부동 소수점으로의 변환 없이 8bit로 끝낼 수 있습니다.
양자화된 Tensor를 위해 사용된 표현방법은 무엇인가요?
우리는 부동소수점 숫자의 집합들을 8bit 표현으로 변경하는 방법을 압축 문제로 접근 했습니다. 우리는 학습되어 있는 신경망 모델들의 가중치와 활성 tensor들이 비교적 작은 범위로 분포되어 있다는 것을 알았습니다(예를 들어 영상 모델에서 -15부터 15까 지는 가중치, -500부터 1000까지는 활성 함수). 또한 우리는 신경망은 잡음에 상당히 강하고, 잡음과 같은 에러는 양자화 되어 결과에 영향을 미치기 힘들정도로 작아지는 것을 실험으로 확인하였습니다. 또한 거대한 행렬 곱과 같이 복잡한 연산에서 쉬운 방 법을 통한 변환을 선택하기를 원했습니다.
이런 아이디어는 두 개의 부동 소수점을 통해 최대, 최소값을 양자화 값으로 정하고 표현하는 방법을 선택하게 하였습니다. 각각의 입력에는 최대 최소값 사이에 일정한 분포를 가지고 있는 부동 소수점 집합이 존재합니다. 예를 들어 만약 우리가 -10을 최소 값으로 가지고 30을 최대값으로 가지면 8-bit 집합에서는 다음과 같이 양자화된 값이 표현될 수 있습니다:
Quantized | Float
--------- | -----
0 | -10.0
255 | 30.0
128 | 10.0
이런 형식의 장점은 범위를 통해 그 값을 표현할 수 있다는 점이고, 대칭일 필요가 없 습니다, 또한 signed 형식 혹은 unsigned 형식에 구애받지 않습니다, 선형 분포는 계산 을 바로 수행할 수 있습니다. 다음 Song Han's code books 에서와 같이 다른 양자 표현 방식 또한 존재합니다. 비 선형적 분포를 이용하여 부동 소수점을 표현하는 방법이지만, 계산은 복잡해지는 경향이 있습니다.
이런 방식으로 양자화 하는 것의 장점은 언제든지 이리저리 부동 소수점에서 양자화 값 으로 변환이 가능하다는 점 입니다, 또는 디버깅을 위해 tensor를 분석할 때도 항상 변환이 가능합니다. One implementation detail in TensorFlow that we're hoping to improve in the future is that the minimum and maximum float values need to be passed as separate tensors to the one holding the quantized values, so graphs can get a bit dense!
최대 최소값을 이용한 변환이 좋은 이유중 하나는 그 값들을 미리 알고 있을 수 있다는 점입니다. 가중치 파라미터들은 저장된 값을 읽을 때 알 수 있습니다, 따라서 그 값들 의 범위는 저장할 때 상수로 저장될 수 있습니다. 입력의 범위는 미리 알고 있는 경우가 대부분 이고(예를 들어 RGB는 0부터 255사이의 값만을 갖고 있습니다), 또한 많은 활 성 함수들의 범위도 알 고 있습니다. 이것은 행렬곱이나 convolution 같이 8bit 입력으 로부터 누적되어 생성되는 32-bit 출력 값의 결과도 미리 알 수 있어 분석해야 하는 경 우도 피할 수 있습니다.
다음으로는 무엇 인가요?
우리는 embedded 기기에서 8bit 수학적 연산을 통한 결과가 부동 소수점보다 훨씬 우수한 성능을 보인다는 사실을 알게 되었습니다(연산 속도인것 같습니다.;번역자). 우리가 사용하고 최적화한 행렬곱 framework를 gemmlowp 에서 확인 할 수 있습니다. TensorFlow의 ops들의 최대 성능을 모바일 기기에서 얻기 위해 우리가 이번 실험등을 통해 얻은 결과들을 이용하여 활발히 연구 하고 있습니다. 8-bit 모델을 더욱 폭넓고 다양한 기기들을 지원하기를 희망하고 있습 니다. 그러기 위해 지금은 양자화 구현이 빠르고 정확한 reference 구현이 되어야 할 것입니다. 또한 우리는 이번 데모가 낮은 수준의 정확도를 가진 신경망을 연구하는 그룹에게 힘을 주기를 바라고 있습니다.
<번역 완료일: 2016. 06.30. 번역자: [email protected] 의역 다수 존재> <중간 중간 의미 전달이 어려운 부분은 영어로 대체 및 번역을 하지 않았습니다> <피드백 및 오류 지적은 언제든 환영합니다>