본문 바로가기

Deep Learning Tools/Nengo

Nengo로 구현하는 MNIST CNN spiking neural network 예제

Nengo tutorial을 보면 MINIST 데이타 셋을 학습하고 테스트하는 예제가 있다.



1) MIST 데이터 셋 다운로드


가장 먼저 데이터를 준비해야 하는데, 고맙게도 MNIST 데이타는 Pickle로 다운로드가 가능하다.

Pickle은 python 프로그램 실행 중에 특정 객체를 파일로 저장하는 기능이다.

저장해둔 pickle 파일을 로드하면, 실행 당시의 객체를 즉시 복원할 수 있다.

MINIST 데이터 셋도 이 pickle 파일을 load해서 사용한다.


다운을 위해서는 반드시 인터넷에 연결되어 있어야 한다.

처음 한 번 다운로드를 한 이후에는 local directory에 저장되어, 언제든 빠르게 데이터를 load할 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
%matplotlib inline
 
import gzip
import pickle
from urllib.request import urlretrieve
import zipfile
 
import nengo
import nengo_dl
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
 
urlretrieve("http://deeplearning.net/data/mnist/mnist.pkl.gz""mnist.pkl.gz")
with gzip.open("mnist.pkl.gz") as f:
    train_data, _, test_data = pickle.load(f, encoding="latin1")
train_data = list(train_data)
test_data = list(test_data)
 
 
cs





2) MINIST 데이터를 one hot encoding 


위와 같이 MNIST 데이터를 load하면, 아래과 같은 구조로 데이터가 저장된다.


train_data: 

[array([[0., 0., 0., ..., 0., 0., 0.],
        [0., 0., 0., ..., 0., 0., 0.],
        [0., 0., 0., ..., 0., 0., 0.],
        ...,
        [0., 0., 0., ..., 0., 0., 0.],
        [0., 0., 0., ..., 0., 0., 0.],
        [0., 0., 0., ..., 0., 0., 0.]], dtype=float32),
 array([5, 0, 4, ..., 8, 4, 8], dtype=int64)]

train_data[0]에는 784개의 값을 가지는 array들이 50000개 저장되어 있다.

train_data[0][1]에는 784개의 값을 가지는 손글씨 데이터가 저장되어 있다.

train_data[1]에는 train_data[0]인 50000개 손글씨 데이터에 대한 label 데이터가 저장되어 있다.

즉, train_data[0][1]의 label은 train_data[1][0]에 저장되어 있다.

test_data 역시 개수의 차이는 있지만, 형태는 마찬가지이다.


정답 label 배열의 경우, 5, 0, 4 이런 식으로 데이터가 저장되어 있으면, 신경망에서 데이터 맵핑이 어렵다.

그래서 one hot encoding으로 정답 데이터를 다르게 표현해보자.

one hot encoding이란, 정답을 뜻하는 원소만 1이고 나머지는 모두 0인 배열을 말한다.

즉, 정답이 5인 데이터의 경우엔

[0, 0, 0, 0, 0, 1, 0, 0, 0, 0] 이렇게 표현할 수 있고,

정답이 1인 데이터의 경우엔

[0, 1, 0, 0, 0, 0, 0, 0, 0, 0] 이렇게 표현할 수 있다.


이제 train_data와 test_data의 one hot vector를 만들어보자.

먼저, train_data를 가져와보자


1
2
data = train_data
 
cs


data[0].shape = (50000, 784) --> 784개의 값을 가지는 array 50000개

data[0].shape[0] = 50000


1
one_hot = np.zeros((data[0].shape[0], 10))
cs


위 코드를 실행하면, one_hot.shpae = (50000, 10) --> 10개의 데이터 값을 가지는 array 50000개가 0으로 초기화된다.


1
one_hot[np.arange(data[0].shape[0]), data[1]] = 1
cs


data[0].shape[0]는 50000이고, data[1]의 값은 정답 label list이다.

즉, 50000개의 데이터에 대해 정답 label에 해당하는 배열만 1로 설정된다.

결과로는?

array([[0., 0., 0., ..., 1., 0., 0.],
       [0., 0., 1., ..., 0., 0., 0.],
       [0., 1., 0., ..., 0., 0., 0.],
       ...,
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.],
       [0., 0., 0., ..., 0., 0., 0.]])

아주 이쁘게 변환됐다.


위의 과정을 반복문으로 돌아 train_data와 test_data를 고쳐보자


1
2
3
4
5
6
7
8
9
10
11
for data in (train_data, test_data):
    one_hot = np.zeros((data[0].shape[0], 10))
    one_hot[np.arange(data[0].shape[0]), data[1]] = 1
    data[1= one_hot
 
for i in range(3):
    plt.figure()
    plt.imshow(np.reshape(train_data[0][i], (2828)))
    plt.axis('off')
    plt.title(str(np.argmax(train_data[1][i])));
 
cs




MINIST 데이터와 label이 아주 이쁘게 저장됐다.


이제 코드의 핵심인 네트워크를 구성해보자


3) CNN 네트워크 설계


1
with nengo.Network() as net:
cs


Nengo에서는 network를 설계할 때, 위와 같이 선언한 후 네트워크를 설계한다.

그럼 'net'이라는 object가 nengo의 network() object이 되고, network를 구성하는 object들을 넣을 수 있게 된다.


1
2
3
    net.config[nengo.Ensemble].max_rates = nengo.dists.Choice([100])
    net.config[nengo.Ensemble].intercepts = nengo.dists.Choice([0])
    neuron_type = nengo.LIF(amplitude=0.01)
cs


네트워크의 특성을 설정해야 하는데, 이 때 config를 이용해 주요 parameter들을 세팅할 수 있다.

nengo.Ensemble은 all-to-all network를 사용할 때 쓰는데, 즉, 모든 network에 있는 neruon의 모든 설정을 정할 수 있다.

nengo.dists.Choice는 numpy의 random.choice와 정확하게 같게 동작한다.

위 경우엔 network에 존재하는 모든 세포의 max firing rate를 100으로 설정하였다.

intercapts 파라미터는 neuron들의 발화 특성을 나타내기 위한 turning curve를 조절한다.

neuron의 type은 Leaky integrate-and-fire (LIF) model로 설정하였다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
with nengo.Network() as net:

    net.config[nengo.Ensemble].max_rates = nengo.dists.Choice([100])
    net.config[nengo.Ensemble].intercepts = nengo.dists.Choice([0])
    neuron_type = nengo.LIF(amplitude=0.01)
 
    nengo_dl.configure_settings(trainable=False)
 
    inp = nengo.Node([0* 28 * 28)
 
    x = nengo_dl.tensor_layer(inp, tf.layers.conv2d, shape_in=(28281), filters=32, kernel_size=3)
    x = nengo_dl.tensor_layer(x, neuron_type)
 
    x = nengo_dl.tensor_layer(x, tf.layers.conv2d, shape_in=(262632), filters=64, kernel_size=3)
    x = nengo_dl.tensor_layer(x, neuron_type)
 
    x = nengo_dl.tensor_layer(x, tf.layers.average_pooling2d, shape_in=(242464), pool_size=2, strides=2)
 
    x = nengo_dl.tensor_layer(x, tf.layers.conv2d, shape_in=(121264), filters=128, kernel_size=3)
    x = nengo_dl.tensor_layer(x, neuron_type)
 
    x = nengo_dl.tensor_layer(x, tf.layers.average_pooling2d, shape_in=(1010128), pool_size=2, strides=2)
    x = nengo_dl.tensor_layer(x, tf.layers.dense, units=10)
 
    out_p = nengo.Probe(x)
    out_p_filt = nengo.Probe(x, synapse=0.1)
cs



configure_settings의 trainable을 False로 두면, network 내의 모든 object들이 학습이 수행되지 않는다.

단, tensornode로 만들어진 부분들은 위 코드에 영향을 받지 않는다.

따라서 False라고 선언하더라도, tensor_layer들로 이루어진 network들은 모두 학습이 적용될 것이다.


CNN 예제는 먼저 tensorflow로 CNN layer 껍데기를 만들고, 해당 껍데기에 LIF neuron을 넣음으로써

spiking CNN을 구축하는 방식으로 설계된다.


코드를 보면, 처음에 28 * 28의 MNIST input image를 받는 nengo.Node가 생성시킨다.

그 다음 첫 번쨰 CNN layer를 28 * 28로 생성하고, input과 연결해준다.

마찬가지로 총 4개의 layer를 반복시켜서 만들어주고, 마지막엔 10개의 linear readout layer를 붙여서 출력 결과를 받을 수 있게 한다.


이렇게 매우 간단한 5 layer짜리 spiking CNN을 구축하였다.


4) Simulation!

1
2
minibatch_size = 200
sim = nengo_dl.Simulator(net, minibatch_size=minibatch_size)
cs


1
2
3
4
5
6
train_inputs = {inp: train_data[0][:, None, :]}
train_targets = {out_p: train_data[1][:, None, :]}
 
n_steps = 30
test_inputs = {inp: np.tile(test_data[0][:minibatch_size*2, None, :], (1, n_steps, 1))}
test_targets = {out_p_filt: np.tile(test_data[1][:minibatch_size*2, None, :], (1, n_steps, 1))}
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
def objective(x, y):
    return tf.nn.softmax_cross_entropy_with_logits_v2(logits=x, labels=y)
 
 
opt = tf.train.RMSPropOptimizer(learning_rate=0.001)
 
def classification_error(outputs, targets):
    return 100 * tf.reduce_mean(
        tf.cast(tf.not_equal(tf.argmax(outputs[:, -1], axis=-1),
                             tf.argmax(targets[:, -1], axis=-1)),
                tf.float32))
 
print("error before training: %.2f%%" % sim.loss(
    test_inputs, test_targets, {out_p_filt: classification_error}))
cs



학습 전에는 error rate가 90.75%나 된다.



1
2
3
4
5
sim.train(train_inputs, train_targets, opt, objective={out_p: objective}, n_epochs=10)
sim.save_params("./mnist_params")
 
 
print("error after training: %.2f%%" % sim.loss(test_inputs, test_targets, {out_p_filt: classification_error}))
cs


학습 후에는?



와후!

무려 0.5%의 error rate로 줄어들었다!



1
2
3
4
5
6
7
8
9
10
11
12
sim.run_steps(n_steps, input_feeds={inp: test_inputs[inp][:minibatch_size]})
 
for i in range(5):
    plt.figure()
    plt.subplot(121)
    plt.imshow(np.reshape(test_data[0][i], (2828)))
    plt.axis('off')
 
    plt.subplot(122)
    plt.plot(sim.trange(), sim.data[out_p_filt][i])
    plt.legend([str(i) for i in range(10)], loc="upper left")
    plt.xlabel("time")
cs









정말 간단하게 MNIST spiking CNN network를 만들었다 ^^

끗!