본문 바로가기
Studies/AI for Hardware

Binarized-Neural-Network-based CIM Accelerator's Baseline Algorithm (Demo)

by veritedemoi 2021. 3. 19.

Backgrounds


 수많은 ML/DL Accelerator가 기본적으로 이용하는 뉴럴 네트워크의 구조로 binarized neural network가 쓰이는 이유는, 낮은 bit-precision을 사용한다는 특징 때문이다.

기존의 CPU/GPU 등은 보통 full-precision의 데이터들을 연산에 사용했고 그 결과, 정확도는 우리가 흔히 일컫는 software baseline의 높은 정확도를  가질 수 있었지만 다소 긴 latency와 높은 에너지 소모라는 단점을 피할 수 없었다.

하지만 binarized neural network은 binary 형태의 데이터들을 연산에 사용해 latency와 에너지 문제에서 혁신적인 개선을 가져왔고, 정확도 측면에서도 용인될만한 수준의 loss가 있다는 것이 증명되어 accelerator 아키텍처에 아주 높은 빈도로 사용되게 되었다.

 

 사실, binarized neural network 뿐만 아니라, 전체적으로 full-precision이 아닌 알고리즘들이 Quantized Neural Network (QNN)이라고 일컫어지며 연산의 효율성(에너지 비용, 속도 등)을 극대화시키는 데에 계속 사용되고 있다. 그러나, HW 측면 ─자세하게는 Processing-in-memory 측면─ 에서, Quantizing을 하는 DAC와 같은 component를 전반적으로 다량 설계하는 것이 힘들기 때문에, 아직까지 QNN 기반 Accelerator들은 inference-only 아키텍처들인 것이 맹점이다.

 

  Inference-only라는 구조적 한계로 인해 QNN 기반 Accelerator들은 software baseline의 역할이 더욱 더 중요할 수밖에 없다. 풀어서 설명하자면, layer 사이의 값들이 HW적으로 제대로 매핑되었는지, 그 값들을 측정해 SW로 quantizing을 하는 기준을 어떻게 세워야 할 것인지 등에 대한 설계 전략을 세우기 위해서 software baseline이 굉장히 큰 역할을 하고 있다는 것이다.

 

 아래는 가장 간단한 Dense 혹은 Fully Connected Layer들로 이루어진 512개의 neuron을 가진 3개의 hidden layer를 포함하는 Multilayer Perceptron 구조에 관한 코드다. Binarized Neural Network를 제안한 논문에서 사용한 PyTorch나 Theano를 사용하진 않았고, Tensorflow 기반으로 QNN을 구현하기 위해 만들어진 Larq라는 라이브러리를 사용했다. 

 

     

 

 

BinaryNet Code (MNIST Classification with MLP, 784-512-512-512-10)


import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers
from keras.layers import Input
from keras.models import Model
import larq

# Import MNIST dataset
(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()
train_images = train_images.reshape((60000, 28, 28))
test_images = test_images.reshape((10000, 28, 28))

# Normalize pixel values to be between -1 and +1, and binarize it to -1 and +1
train_images, test_images = train_images / 127.5 -1, test_images / 127.5 -1
train_images_binary = np.where(train_images > 0, 1, -1)
test_images_binary = np.where(test_images > 0, 1, -1)

# Returns input tensor
nonflat_input_layer = Input(shape=(28, 28, 1))
input_layer = keras.layers.Flatten()(nonflat_input_layer)

# It is possible to callback intermediate activations
hidden1 = larq.layers.QuantDense(
    512,
    kernel_quantizer= "ste_sign",
    kernel_constraint= "weight_clip",
    name = "first_hidden_layer")(input_layer)
batchnorm1 = tf.keras.layers.BatchNormalization(momentum = 0.99, scale=False)(hidden1)
quantize1 = larq.quantizers.SteSign()(batchnorm1)
hidden2 = larq.layers.QuantDense(
    512,
    kernel_quantizer= "ste_sign",
    kernel_constraint= "weight_clip",
    name = "second_hidden_layer")(quantize1)
batchnorm2 = tf.keras.layers.BatchNormalization(momentum= 0.99, scale=False)(hidden2)
quantize2 = larq.quantizers.SteSign()(batchnorm2)
hidden3 = larq.layers.QuantDense(
    512,
    kernel_quantizer= "ste_sign",
    kernel_constraint= "weight_clip",
    name = "third_hidden_layer")(quantize2)
batchnorm3 = tf.keras.layers.BatchNormalization(momentum= 0.99, scale=False)(hidden3)
quantize3 = larq.quantizers.SteSign()(batchnorm3)
output_layer = larq.layers.QuantDense(
    10,
    activation= "softmax",
    kernel_quantizer= "ste_sign",
    kernel_constraint="weight_clip",
    name = "output_layer")(quantize3)

# Implementing a model based on given functional API codes
model = Model(inputs= nonflat_input_layer, outputs= output_layer)

# Compile & Train the model
model.compile(
    optimizer = 'adam',
    loss = 'sparse_categorical_crossentropy',
    metrics = ['accuracy'])

# Extract & Store the model's weights
with larq.context.quantized_scope(True):
    model.save("Larq_Binary_MLP_functionalAPI_version.h5")
    func_api_weights = model.get_weights()

# Train & Evaluate
model.fit(train_images_binary, train_labels, batch_size = 300, epochs = 30)
test_loss, test_acc = model.evaluate(test_images_binary, test_labels)
print(f"Test accuracy {test_acc * 100:.2f} %")

# Extracting intermediate layer's outputs (activations)
features_list = [layer.output for layer in model.layers]
feat_extraction_model = keras.Model(inputs = model.input, outputs = features_list)
activations = feat_extraction_model(test_images_binary)
act_1 = activations[4].numpy()
act_2 = activations[7].numpy()
act_3 = activations[10].numpy()

Sequential API를 써도 상관은 없지만, Larq의 경우 input_quantizer 라는 constraint 가 존재하기 때문에 binarize된 activation 값들을 추출하기 위해서는 quantizing layer를 따로 써야 했기에 Functional API를 사용했다. 그리고 weight을 추출하기 위한 code에서 quantized_scope(True) 는 binarize된 weight을 추출하기 위한 조건문이다 (False일 경우 full-precision, latent weight이 추출된다).

 

 

 

Environments


  • Ubuntu 18.04.5 LTS (Bionic Beaver)
  • Python 3.8.5 (Spyder 4.2.1)
  • Tensorflow 2.4.1 (GPU)
  • Keras 2.4.3
  • Numpy 1.19.5
  • Larq 0.11.2

 

 

 

References


  1. Itay Hubara, , Matthieu Courbariaux, Daniel Soudry, Ran El-Yaniv, and Yoshua Bengio. "Binarized Neural Networks." . In NIPS (pp. 4107-4115). 2016.
  2. docs.larq.dev/larq/

'Studies > AI for Hardware' 카테고리의 다른 글

Computing-in-Memory (2): Trade-offs & Limitations  (0) 2023.01.18
Computing-in-Memory (1) : Backgrounds  (0) 2022.01.03

댓글