MNIST 데이터 셋을 이용한 손글씨 인식 Deep Nerual Network 구현
Deep Nerual net에 여러 기술을 적용해서 정확도를 점점 향상시켜보는 내용이다.
MNIST Database overview
머신러닝 최고 Guru중 한명인 Yann LeCun 뉴욕대 교수
가 제공하는 데이터 셋이다.
교수 겸임과 함께 Facebook과 함께 일하고 있다.
숫자 0~9까지의 손글씨 이미지의 집합이다.
학습데이터
60,000개, 테스트데이터
10,000개로 구성 되어 있다.
이전과 다르게 모든 모델이 검증은 이 테스트 데이터로 수행하게 된다.
재치환 에러가 아니라, unseen data에 대한 평가이기 때문에 진짜 평가라고 할 수 있다.
이 손글씨는 size-normalized 되어 있고 centered 되어 있다.
사이즈는 28x28
의 크기를 가진다. 이미지의 값은 0 또는 1이다 (흑,백)
결국 이 데이터 셋은 패턴인식이나 기계학습 기술을 적용하기 위해 사용할 수 있는 최적의 이미지 셋이다. 이미 preprocessing이나 formatting이 모두 완료 되었기 때문이다.
4개의 파일을 아래의 주소에서도 다운받을 수 있고, 아래의 input_data.py
를 통해서도 자동으로 다운 받을 수 있다.
a Link of the MNIST Database of handwritten digits, Yann LeCun
train-images-idx3-ubyte.gz: training set images (9912422 bytes)
train-labels-idx1-ubyte.gz: training set labels (28881 bytes)
t10k-images-idx3-ubyte.gz: test set images (1648877 bytes)
t10k-labels-idx1-ubyte.gz: test set labels (4542 bytes)
편리한 이미지 제어를 위해서 TensorFlow
자체에서 제공하는 script인 input_data.py
를 사용한다.
다운로드
기능은 크게 두 가지이다.
- dataset downloading
- Loading the entire dataset into numpy array
Softmax Logistic regression for MNIST
단일 layer의 logistic regression에다가 softmax를 붙여서 0~9 사이의 숫자로 classifier 해주는 code 이다.
이런 단순한 방법의 경우 정확도가 얼마나 나오는지를 확인해보자.
import tensorflow as tf
import input_data
learning_rate = 0.01
training_epochs = 25
batch_size = 100
display_step = 1
mnist = input_data.read_data_sets("./MNIST_DATA", one_hot=True)
# tensorflow graph input
X = tf.placeholder('float', [None, 784]) # mnist data image of shape 28 * 28 = 784
Y = tf.placeholder('float', [None, 10]) # 0-9 digits recognition = > 10 classes
# set model weights
W = tf.Variable(tf.zeros([784, 10]))
b = tf.Variable(tf.zeros([10]))
# Our hypothesis
activation = tf.add(tf.matmul(X, W),b) # Softmax
# Cost function: cross entropy
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(activation, Y))
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) # Gradient Descen
# Before starting, initialize the variables. We will `run` this first.
init = tf.initialize_all_variables()
# Launch the graph,
with tf.Session() as sess:
sess.run(init)
# Training cycle
for epoch in range(training_epochs):
avg_cost = 0.
total_batch = int(mnist.train.num_examples/batch_size)
# Fit the line.
for step in xrange(total_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
# Fit training using batch data
sess.run(optimizer, feed_dict={X: batch_xs, Y: batch_ys})
# Compute average loss
avg_cost += sess.run(cost, feed_dict={X: batch_xs, Y: batch_ys})/total_batch
# Display logs per epoch step
if epoch % display_step == 0:
print "Epoch:", '%04d' %(epoch+1), "cost=", "{:.9f}".format(avg_cost)
print "Optimization Finished!"
# Test model
correct_prediction = tf.equal(tf.argmax(activation, 1), tf.argmax(Y, 1))
# Calculate accuracy
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print ("Accuracy:", accuracy.eval({X: mnist.test.images, Y: mnist.test.labels}))
동작을 할떄 수행 과정이 너무 많기 때문에 batch단위로 나눠서 보여주게 된다.
총 트레이닝에 사용되는 example은 55,000
개이다.
batch size
는 100
이다. 즉 한번에 550
개를 이용해서 training을 수행 하게 된다.
위와 같이 수행하면 아래와 같은 결과를 얻는다.
테스트 데이터 1만개에 대해서 91%
정확도로 숫자를 판별 할 수 있었다. 훌륭한 결과이다.
이제 이것을 계속 개선 시켜 나간다.
Epoch: 0001 cost= 0.358261517
Epoch: 0002 cost= 0.281931180
Epoch: 0003 cost= 0.271845694
Epoch: 0004 cost= 0.265042298
Epoch: 0005 cost= 0.261528213
Epoch: 0006 cost= 0.260637467
Epoch: 0007 cost= 0.259087178
Epoch: 0008 cost= 0.255747356
Epoch: 0009 cost= 0.256775191
Epoch: 0010 cost= 0.254862096
Epoch: 0011 cost= 0.253365451
Epoch: 0012 cost= 0.250304825
Epoch: 0013 cost= 0.250392489
Epoch: 0014 cost= 0.248266828
Epoch: 0015 cost= 0.252892739
Epoch: 0016 cost= 0.245914599
Epoch: 0017 cost= 0.246812203
Epoch: 0018 cost= 0.248447235
Epoch: 0019 cost= 0.245472498
Epoch: 0020 cost= 0.243310648
Epoch: 0021 cost= 0.244416240
Epoch: 0022 cost= 0.244889498
Epoch: 0023 cost= 0.242864834
Epoch: 0024 cost= 0.242400869
Epoch: 0025 cost= 0.244274715
Optimization Finished!
('Accuracy:', 0.91829997)
Deep Neural Nets (DNN) for MNIST
Hidden Layer를 하나 추가해서 multiple nerual network을 구성해보자.
학습을 위해서 activation function
으로 ReLU
를 사용 한다.
weight 초기는 아래와 같이 random_normal
을 이용해서 랜덤으로 설정해 준다.
정규 분포에 의해서 0~1
사이의 값으로 만들어 낸다.
W1 = tf.Variable(tf.random_normal([784, 256]))
W2 = tf.Variable(tf.random_normal([256, 256]))
W3 = tf.Variable(tf.random_normal([256, 10]))
B1 = tf.Variable(tf.random_normal([256]))
B2 = tf.Variable(tf.random_normal([256]))
B3 = tf.Variable(tf.random_normal([10]))
그냥 무작정 0
으로 wegiht을 모두 초기화한 다음 수행하면 cost 값이 converge 되지 않는다.
import tensorflow as tf
import input_data
learning_rate = 0.01
training_epochs = 15
batch_size = 100
display_step = 1
mnist = input_data.read_data_sets("./MNIST_DATA", one_hot=True)
# tensorflow graph input
X = tf.placeholder('float', [None, 784]) # mnist data image of shape 28 * 28 = 784
Y = tf.placeholder('float', [None, 10]) # 0-9 digits recognition = > 10 classes
# set model weights
W1 = tf.Variable(tf.random_normal([784, 256]))
W2 = tf.Variable(tf.random_normal([256, 256]))
W3 = tf.Variable(tf.random_normal([256, 10]))
B1 = tf.Variable(tf.random_normal([256]))
B2 = tf.Variable(tf.random_normal([256]))
B3 = tf.Variable(tf.random_normal([10]))
# Construct model
L1 = tf.nn.relu(tf.add(tf.matmul(X,W1),B1))
L2 = tf.nn.relu(tf.add(tf.matmul(L1,W2),B2)) # Hidden layer with RELU activation
hypothesis = tf.add(tf.matmul(L2, W3), B3) # No need to use softmax here
# Define loss and optimizer
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(hypothesis, Y)) # Softmax loss
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) # Adam Optimizer
# Initializing the variables
init = tf.initialize_all_variables()
# Launch the graph,
with tf.Session() as sess:
sess.run(init)
# Training cycle
for epoch in range(training_epochs):
avg_cost = 0.
total_batch = int(mnist.train.num_examples/batch_size)
# Fit the line.
for step in xrange(total_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
# Fit training using batch data
sess.run(optimizer, feed_dict={X: batch_xs, Y: batch_ys})
# Compute average loss
avg_cost += sess.run(cost, feed_dict={X: batch_xs, Y: batch_ys})/total_batch
# Display logs per epoch step
if epoch % display_step == 0:
print "Epoch:", '%04d' %(epoch+1), "cost=", "{:.9f}".format(avg_cost)
print "Optimization Finished!"
# Test model
correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(Y, 1))
# Calculate accuracy
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print ("Accuracy:", accuracy.eval({X: mnist.test.images, Y: mnist.test.labels}))
activation function을 ReLU
로 변경하고 1개의 hidden layer만 추가 했는데도 정확도가 많이 좋아졌다.
심지어 epoch를 더 적게 수행해서 트레닝 데이터 55,000개에 대해서 모두 bach processing을 다 하지도 않았다.
하지만, initial wegiht을 zero로 주거나 잘못 줄경우 아에 학습이 안될 수도 있는 불안 요소가 있고
초기에 cost값이 너무 크게 시작한다는 문제점이 있다. 자칫 layer가 다수가 된다면 정상적으로 동작 안할 수도 있다.
/root/tensorflow/bin/python /root/DataScience/TensorFlowLecture/DNNforMNIST.py
Extracting ./MNIST_DATA/train-images-idx3-ubyte.gz
/usr/lib/python2.7/gzip.py:268: VisibleDeprecationWarning: converting an array with ndim > 0 to an index will result in an error in the future
Extracting ./MNIST_DATA/train-labels-idx1-ubyte.gz
chunk = self.extrabuf[offset: offset + size]
Extracting ./MNIST_DATA/t10k-images-idx3-ubyte.gz
/root/DataScience/TensorFlowLecture/input_data.py:47: VisibleDeprecationWarning: converting an array with ndim > 0 to an index will result in an error in the future
data = data.reshape(num_images, rows, cols, 1)
Extracting ./MNIST_DATA/t10k-labels-idx1-ubyte.gz
Epoch: 0001 cost= 36.855221693
Epoch: 0002 cost= 6.358338172
Epoch: 0003 cost= 2.991123097
Epoch: 0004 cost= 1.751982349
Epoch: 0005 cost= 1.301591293
Epoch: 0006 cost= 1.072350917
Epoch: 0007 cost= 0.896756601
Epoch: 0008 cost= 0.748790441
Epoch: 0009 cost= 0.573228549
Epoch: 0010 cost= 0.474619466
Epoch: 0011 cost= 0.429405935
Epoch: 0012 cost= 0.421186241
Epoch: 0013 cost= 0.325840454
Epoch: 0014 cost= 0.297165287
Epoch: 0015 cost= 0.260923379
Optimization Finished!
('Accuracy:', 0.95840001)
Process finished with exit code 0
Weight initialization
지금 까지는 아래의 코드와 같이
초기 weight vector들을 그냥 0으로 초기화 했었다.
또는 그냥 정규분포를 이용해서 0~1사이의 값으로 대충 초기화 했었다.
이렇게하면 계층구조가 복잡해질 경우 학습이 이뤄지지 않는 문제가 있다.
W = tf.Variable(tf.zeros([784, 10]))
tf.random_normal([784, 256])
이렇게 무식하게 하지말고 weight 값을 좀 더 올바르게 주고 시작하자는 의미 이다.
Hinton
교수가 2006년에 발표한 논문 A Fast Learning Algorithm for Deep Belief Nets
에서 제안한Restricted Boatman Machine (RBM)
방법에 기초 한다.
해당 방법은 인접한 두개의 layer 들에 대해서만 pre-training step을 거쳐서 wegiht 값을 정하는 것을 말한다.
그렇게 한다고 쳐도 RBM 방식은 약간 복잡하다고 할 수 있다.
좋은 소식은 아래와 같이 더 쉽지만 비슷한 효과를 얻는 pre-training 방법들이 많이 고안 되었다.
- Xavier initialization: X. Glorot and Y. Bengio,
“Understanding the difficulty of training deep feedforward neural networks,”
in International conference on artificial intelligence and statistics, 2010. - He’s initialization: K. He, X. Zhang, S. Ren, and J. Sun,
“Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification,”
2015.
Xavier initialization
은 URL에서 코드를 알 수 있다.
import tensorflow as tf
import input_data
def xavier_initializer(n_inputs, n_outputs, uniform = True):
if uniform:
init_range = tf.sqrt(6.0/ (n_inputs + n_outputs))
return tf.random_uniform_initializer(-init_range, init_range)
else:
stddev = tf.sqrt(3.0 / (n_inputs + n_outputs))
return tf.truncated_normal_initializer(stddev=stddev)
learning_rate = 0.01
training_epochs = 15
batch_size = 100
display_step = 1
mnist = input_data.read_data_sets("./MNIST_DATA", one_hot=True)
# tensorflow graph input
X = tf.placeholder('float', [None, 784]) # mnist data image of shape 28 * 28 = 784
Y = tf.placeholder('float', [None, 10]) # 0-9 digits recognition = > 10 classes
# set model weights
W1 = tf.get_variable("W1", shape=[784, 256], initializer=xavier_initializer(784, 256))
W2 = tf.get_variable("W2", shape=[256, 256], initializer=xavier_initializer(256, 256))
W3 = tf.get_variable("W3", shape=[256, 10], initializer=xavier_initializer(256, 10))
B1 = tf.Variable(tf.zeros([256]))
B2 = tf.Variable(tf.zeros([256]))
B3 = tf.Variable(tf.zeros([10]))
# Construct model
L1 = tf.nn.relu(tf.add(tf.matmul(X,W1),B1))
L2 = tf.nn.relu(tf.add(tf.matmul(L1,W2),B2)) # Hidden layer with RELU activation
hypothesis = tf.add(tf.matmul(L2, W3), B3) # No need to use softmax here
# Define loss and optimizer
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(hypothesis, Y)) # Softmax loss
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) # Adam Optimizer
# Initializing the variables
init = tf.initialize_all_variables()
# Launch the graph,
with tf.Session() as sess:
sess.run(init)
# Training cycle
for epoch in range(training_epochs):
avg_cost = 0.
total_batch = int(mnist.train.num_examples/batch_size)
# Fit the line.
for step in xrange(total_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
# Fit training using batch data
sess.run(optimizer, feed_dict={X: batch_xs, Y: batch_ys})
# Compute average loss
avg_cost += sess.run(cost, feed_dict={X: batch_xs, Y: batch_ys})/total_batch
# Display logs per epoch step
if epoch % display_step == 0:
print "Epoch:", '%04d' %(epoch+1), "cost=", "{:.9f}".format(avg_cost)
print "Optimization Finished!"
# Test model
correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(Y, 1))
# Calculate accuracy
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print ("Accuracy:", accuracy.eval({X: mnist.test.images, Y: mnist.test.labels}))
정확도 면에서는 크게 차이없지만, cost function이 converge하는 속도가 다르다.
처음 시작 할 때 cost 값이 0.2수준으로 이전과는 엄청난 차이가 있다.
/root/tensorflow/bin/python /root/DataScience/TensorFlowLecture/DNNforMNIST.py
Extracting ./MNIST_DATA/train-images-idx3-ubyte.gz
/usr/lib/python2.7/gzip.py:268: VisibleDeprecationWarning: converting an array with ndim > 0 to an index will result in an error in the future
chunk = self.extrabuf[offset: offset + size]
/root/DataScience/TensorFlowLecture/input_data.py:47: VisibleDeprecationWarning: converting an array with ndim > 0 to an index will result in an error in the future
data = data.reshape(num_images, rows, cols, 1)
Extracting ./MNIST_DATA/train-labels-idx1-ubyte.gz
Extracting ./MNIST_DATA/t10k-images-idx3-ubyte.gz
Extracting ./MNIST_DATA/t10k-labels-idx1-ubyte.gz
Epoch: 0001 cost= 0.237908776
Epoch: 0002 cost= 0.094579589
Epoch: 0003 cost= 0.076395853
Epoch: 0004 cost= 0.065301783
Epoch: 0005 cost= 0.061461856
Epoch: 0006 cost= 0.055610619
Epoch: 0007 cost= 0.055899355
Epoch: 0008 cost= 0.048690692
Epoch: 0009 cost= 0.044984061
Epoch: 0010 cost= 0.042311433
Epoch: 0011 cost= 0.046229366
Epoch: 0012 cost= 0.041400560
Epoch: 0013 cost= 0.037953107
Epoch: 0014 cost= 0.035833549
Epoch: 0015 cost= 0.034003958
Optimization Finished!
('Accuracy:', 0.96890002)
Process finished with exit code 0
Dropout을 이용한 5 layer nerual network
Layer 수를 증가 시킨다.
그리고 overffiting
을 막기 위해서 training을 할 때 dropout
을 적용해서 training을 수행 한다.
dropout은 모든 레이어의 perceptron을 연결 시키지않고 임의로 지정된 비율만큼 단절 시킨 상태에서 weight값을 계산하는 기법을 말한다. 이를 통해서 overffting을 어느정도 막을 수 있다.
그리고 prediction을 수행 할 때는 다시 dropout 비율을 1로 해서 모든 perceptron을 연결한 상태에서 모델예측을 수행 한다.
import tensorflow as tf
import input_data
def xaver_init(n_inputs, n_outputs, uniform = True):
if uniform:
init_range = tf.sqrt(6.0/ (n_inputs + n_outputs))
return tf.random_uniform_initializer(-init_range, init_range)
else:
stddev = tf.sqrt(3.0 / (n_inputs + n_outputs))
return tf.truncated_normal_initializer(stddev=stddev)
learning_rate = 0.001
training_epochs = 25
batch_size = 100
display_step = 1
mnist = input_data.read_data_sets("./MNIST_DATA", one_hot=True)
# tensorflow graph input
X = tf.placeholder('float', [None, 784]) # mnist data image of shape 28 * 28 = 784
Y = tf.placeholder('float', [None, 10]) # 0-9 digits recognition = > 10 classes
# set dropout rate
dropout_rate = tf.placeholder("float")
# set model weights
W1 = tf.get_variable("W1", shape=[784, 256], initializer=xaver_init(784, 256))
W2 = tf.get_variable("W2", shape=[256, 256], initializer=xaver_init(256, 256))
W3 = tf.get_variable("W3", shape=[256, 256], initializer=xaver_init(256, 256))
W4 = tf.get_variable("W4", shape=[256, 256], initializer=xaver_init(256, 256))
W5 = tf.get_variable("W5", shape=[256, 10], initializer=xaver_init(256, 10))
B1 = tf.Variable(tf.random_normal([256]))
B2 = tf.Variable(tf.random_normal([256]))
B3 = tf.Variable(tf.random_normal([256]))
B4 = tf.Variable(tf.random_normal([256]))
B5 = tf.Variable(tf.random_normal([10]))
# Construct model
_L1 = tf.nn.relu(tf.add(tf.matmul(X,W1),B1))
L1 = tf.nn.dropout(_L1, dropout_rate)
_L2 = tf.nn.relu(tf.add(tf.matmul(L1, W2),B2)) # Hidden layer with ReLU activation
L2 = tf.nn.dropout(_L2, dropout_rate)
_L3 = tf.nn.relu(tf.add(tf.matmul(L2, W3),B3)) # Hidden layer with ReLU activation
L3 = tf.nn.dropout(_L3, dropout_rate)
_L4 = tf.nn.relu(tf.add(tf.matmul(L3, W4),B4)) # Hidden layer with ReLU activation
L4 = tf.nn.dropout(_L4, dropout_rate)
hypothesis = tf.add(tf.matmul(L4, W5), B5) # No need to use softmax here
# Define loss and optimizer
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(hypothesis, Y)) # Softmax loss
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(cost) # Adam Optimizer
# Initializing the variables
init = tf.initialize_all_variables()
# Launch the graph,
with tf.Session() as sess:
sess.run(init)
# Training cycle
for epoch in range(training_epochs):
avg_cost = 0.
total_batch = int(mnist.train.num_examples/batch_size)
# Fit the line.
for step in xrange(total_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
# Fit training using batch data
# set up 0.7 for training time
sess.run(optimizer, feed_dict={X: batch_xs, Y: batch_ys, dropout_rate: 0.7})
# Compute average loss
avg_cost += sess.run(cost, feed_dict={X: batch_xs, Y: batch_ys, dropout_rate: 0.7})/total_batch
# Display logs per epoch step
if epoch % display_step == 0:
print "Epoch:", '%04d' %(epoch+1), "cost=", "{:.9f}".format(avg_cost)
print "Optimization Finished!"
# Test model
correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(Y, 1))
# Calculate accuracy
accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
print ("Accuracy:", accuracy.eval({X: mnist.test.images, Y: mnist.test.labels, dropout_rate: 1}))
위코드에서 아래의 코드가 dropout을 나타낸다.
prediction을 할떄는 dropout_rate = 1로 설정한 다음 수행 한다.
learning_rate = 0.001
으로 설정한것이 이전 코드와 차이점이다.
0.01
로 할경우 오히려 정확도가 떨어진다.
L1 = tf.nn.dropout(_L1, dropout_rate)
최종 결과는 아래와 같다.
testing set 10000
개에 대해서 98%
의 정확도로 prediction을 하고 있다.
매우 훌륭한 결과를 얻었다.
/root/tensorflow/bin/python /root/DataScience/TensorFlowLecture/DNN_DropoutForMNIST.py
Extracting ./MNIST_DATA/train-images-idx3-ubyte.gz
/usr/lib/python2.7/gzip.py:268: VisibleDeprecationWarning: converting an array with ndim > 0 to an index will result in an error in the future
chunk = self.extrabuf[offset: offset + size]
/root/DataScience/TensorFlowLecture/input_data.py:47: VisibleDeprecationWarning: converting an array with ndim > 0 to an index will result in an error in the future
data = data.reshape(num_images, rows, cols, 1)
Extracting ./MNIST_DATA/train-labels-idx1-ubyte.gz
Extracting ./MNIST_DATA/t10k-images-idx3-ubyte.gz
Extracting ./MNIST_DATA/t10k-labels-idx1-ubyte.gz
Epoch: 0001 cost= 0.541453579
Epoch: 0002 cost= 0.206996800
Epoch: 0003 cost= 0.157958631
Epoch: 0004 cost= 0.131259738
Epoch: 0005 cost= 0.115274848
Epoch: 0006 cost= 0.098876665
Epoch: 0007 cost= 0.089464739
Epoch: 0008 cost= 0.088287840
Epoch: 0009 cost= 0.078404236
Epoch: 0010 cost= 0.072852779
Epoch: 0011 cost= 0.066189782
Epoch: 0012 cost= 0.066654595
Epoch: 0013 cost= 0.060475496
Epoch: 0014 cost= 0.057644885
Epoch: 0015 cost= 0.054782469
Epoch: 0016 cost= 0.052261842
Epoch: 0017 cost= 0.053377932
Epoch: 0018 cost= 0.049687267
Epoch: 0019 cost= 0.050434303
Epoch: 0020 cost= 0.049436003
Epoch: 0021 cost= 0.044065983
Epoch: 0022 cost= 0.043885315
Epoch: 0023 cost= 0.044500848
Epoch: 0024 cost= 0.044421383
Epoch: 0025 cost= 0.040629214
Optimization Finished!
('Accuracy:', 0.98079997)
Process finished with exit code 0
구현 코드는 GitHub에 올려 두었다.
참고자료
모두를 위한 머신 러닝 by 홍콩과기대 김성훈 교수