Tekhartha의 인공지능 기술블로그

MNIST-기본

|


딥 러닝 학습하는 사람들은 누구나 아는, 모르면 간첩인, MNIST 관련 사항을 정리해 본다. MNIST를 정리하면서, 딥 러닝 모델들에서 나올 수 있는 여러 가지 추가할 수 있는 옵션들을 모두 담아 보려고 한다.

[MNIST - 소개]

기본적으로 소개글은 텐서플로우 문서 한글 번역 사이트에 있는 MNIST 초급 페이지를 참고했음을 먼저 밝힌다.

MNIST는 Modified National Institute of Standards and Technology database의 약자로, 손으로 쓴 숫자들로 이루어진 대형 데이터베이스이다. 1995년 미국의 NIST(표준 및 기술협회)에서 취합했던 손글씨 데이터 샘플들을 재혼합 및 보정하여 만들어졌다. 각각의 데이터는 0~9 까지의 숫자를 쓴 그림들이며, 트레이닝 데이터는 60,000개, 테스트 데이터는 10,000개이다. 각 데이터는 28*28 = 784픽셀로 이루어져 있는 이미지 데이터이며, 클래스는 0~9까지 10개이다.

워낙에 유명한 데이터라서 텐서플로우에 아예 이 데이터를 받을 수 있게 하고 편집도 가능한 라이브러리가 존재한다(!)

아래에는 이 데이터셋을 로드하는 방법부터 시작해서 딥 러닝 모델을 이용해 분류하고 테스트 데이터를 예측까지 해 보는 과정을 정리했다.

[MNIST - 코드를 통한 이해]

import tensorflow as tf
import random
import matplotlib.pyplot as plt
tf.set_random_seed(777)  # for reproducibility

먼저 필요한 라이브리를 import한다.

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

위와 같은 코드로 mnist 데이터를 로드한다. 보다시피 텐서플로가 로딩을 지원하는 모습을 볼 수 있다. 이미지나 텍스트 파일로만 input을 받다가 이 코드를 처음 봤을 땐 굉장히 당황했다. 어..그럼 이 데이터가 어떤 모양으로 생겼는지는 어떻게 확인하지?

print(mnist.train.images.shape)
print(mnist.validation.images.shape)
print(mnist.test.images.shape)

위와 같이 메소드를 차근차근 찾다 보면 shape를 확인할 수 있는 메소드가 존재한다. 위 코드의 출력은 다음과 같다 :

(55000, 784)
(5000, 784)
(10000, 784)

train data는 55,000개, validation data는 5,000개, test data는 10,000개로 구성되어 있음을 알 수 있다.

(validation은 예비 test를 위한 데이터라고 보면 된다. 그냥 train data와 합쳐도 무관)

마찬가지 방법으로 mnist.train.labels.shape 같은 형식으로 label들의 shape를 찾아보면 10개의 클래스로 나누어져 있는 것을 확인할 수 있다.

그럼 데이터 하나가 어떻게 생겼는지를 자세히 볼까?

print(mnist.train.images[0])를 사용해서 첫 번째의 이미지를 출력해 보았더니…

이렇게 784개의 float형식의 숫자로 이루어진 리스트가 나타난다. 그러니까 28*28개의 각 픽셀들을 일렬로 쭈욱 나래비 세운 1차원 벡터 형식으로 저장되어 있다 이 말이다. (28*28 2차원 벡터가 아니다. 그러니까 인풋 데이터에는 줄 구분이 없다는 얘기다.)

그럼 각 숫자가 의미하는 바는? 가장 어두운 검정을 0, 가장 밝은 흰색을 1이라고 놓았을 때, 0~1 사이의 값으로 각 픽셀의 밝기를 나타내는 것이다.

그럼 이 784개의 숫자를 그림으로 나타내면 어떻게 보일까?

import matplotlib.pyplot as plt
plt.imshow(
        mnist.train.images[0].reshape(28, 28),
        cmap='Greys',
        interpolation='nearest')
plt.show()

위와 같이 28*28로 reshape하고 matplotlib를 사용하면 그림은 이렇게 그려지게 된다 :

(cmap 설정 안하면 0이 보라색으로, 1이 노란색으로 나오기 때문에 별로 예쁘지가 않다)

(interpolation은 보간법 또는 내삽법이라 불리는데 일종의 필터라고 생각하면 된다)

(* 참고로 일반적인 그림 파일의 형식은 이렇지 않다. 각 픽셀마다 RGB값이 숫자로 저장되어 있어서 보통 3차원 Numpy 행렬로 나타내어진다)


이제 데이터가 어떻게 생겼는지를 알았으니 다음으로 넘어가면,

nb_classes = 10

# MNIST data image of shape 28 * 28 = 784
X = tf.placeholder(tf.float32, [None, 784])
# 0 - 9 digits recognition = 10 classes
Y = tf.placeholder(tf.float32, [None, nb_classes])

W = tf.Variable(tf.random_normal([784, nb_classes]))
b = tf.Variable(tf.random_normal([nb_classes]))

nb_classes는 분류할 class의 개수(0~9까지 10개)를 나타내고,

X는 784개의 숫자로 이루어진 1차원 벡터를 한꺼번에 받는데, 한 번에 들어올 데이터의 개수(배치)를 None을 써서 사용자가 쉽게 바꿀 수 있도록 한다. 그러므로 X의 shape는 [None, 784]가 되어야 하고,

Y는 class 개수만큼 열을 설정해 주어야 한다. 마찬가지로 배치는 얼마나 될 지 설정할 수 있게끔 None을 사용한다. 그러므로 shape는 [None, nb_classes].

이번 코드에서는 Y = XW + b의 연산을 한 번만 수행할 것이므로 W와 b를 위와 같이 설정한다. W는 x에서 784개의 열로 된 데이터를 받아서 nb_classes만큼의 노드에 집어넣는다. 여기에 nb_classes 개수만큼의 숫자로 된 상수열 b를 추가한다.

# Hypothesis (using softmax)
hypothesis = tf.nn.softmax(tf.matmul(X, W) + b)
# cost (cross entropy)
cost = tf.reduce_mean(-tf.reduce_sum(Y * tf.log(hypothesis), axis=1))
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(cost)

그래프의 마지막을 softmax로 장식하기 위해 hypothesis 노드를 위와 같이 정의한다.

hypothesis는 [None, nb_classes]의 형태를 가질 것이고, cost는 cross entropy를 사용해서 값을 계산한다. cost는 배치의 각 데이터의 개별 cost들의 합이다. 이 cost를 gradient-descent(경사하강) 방식으로 업데이트 해나간다.

# Test model
is_correct = tf.equal(tf.arg_max(hypothesis, 1), tf.arg_max(Y, 1))
# Calculate accuracy
accuracy = tf.reduce_mean(tf.cast(is_correct, tf.float32))

정확도를 측정해보기 위해 위와 같이 작성한다. is_correct는 hypothesis에서의 가장 확률이 높은 클래스와 실제 Y에서 가장 확률이 높은 클래스가 일치하는지를 본다. (Y는 one-hot encoding이므로 1인 클래스가 리턴될 것이다.)

accuracy에는 is_correct의 평균을 낸 값을 저장한다.

# parameters
training_epochs = 15
batch_size = 100

에폭(모든 데이터를 한 바퀴 도는 사이클)을 15, 배치 사이즈를 100으로 설정 후

with tf.Session() as sess:
    # Initialize TensorFlow variables
    sess.run(tf.global_variables_initializer())
    # Training cycle
    for epoch in range(training_epochs):
        avg_cost = 0
        total_batch = int(mnist.train.num_examples / batch_size)

        for i in range(total_batch):
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)
            c, _ = sess.run([cost, optimizer], feed_dict={X: batch_xs, Y: batch_ys})
            avg_cost += c / total_batch

        print('Epoch:', '%04d' % (epoch + 1),
              'cost =', '{:.9f}'.format(avg_cost))

    print("Learning finished")
    # Test the model using test sets
    print("Accuracy: ", accuracy.eval(session=sess, feed_dict={X: mnist.test.images, Y: mnist.test.labels}))

세션을 열고 epoch만큼 for문을 돌면서 그 안에서 (전체 데이터 개수/배치 크기) 만큼 또 for문을 돌리면서 batch 수만큼 데이터를 그래프에 공급한다. avg_cost에는 배치마다의 평균 cost의 합을 넣는다. 15번의 epoch마다 cost를 출력하고 학습을 완료한다.

마지막의 print(“Accuracy : “)… 문장에서 모든 테스트 데이터들에 대한 평가를 수행한다. 그런데 이게 GPU메모리에 모든 데이터를 한 방에 넣고 돌리는 형식이라서 본인 컴퓨터에선 안 돌아간다. (지포스 750ti 기준 안됨)

성능이 딸리는 컴퓨터에서 하고 싶으면 테스트도 트레이닝 같이 배치로 나누어서 돌리면 된다.

돌아가는 컴퓨터에서는 대략 정확도가 0.88가량이 나온다고 한다.

   # Get one and predict
    r = random.randint(0, mnist.test.num_examples - 1)
    print("Label: ", sess.run(tf.argmax(mnist.test.labels[r:r + 1], 1)))
    print("Prediction: ", sess.run(tf.argmax(hypothesis, 1), feed_dict={X: mnist.test.images[r:r + 1]}))

랜덤하게 1개의 데이터를 뽑아서 실제 Y값을 출력해보고, 학습된 모델로 Prediction을 해본다.


쉬운 듯 어렵고, 어려운 듯 쉬운 텐서플로우 모델. 간단한 Y=WX+b의 모델만으로도 0.88의 정확도를 보여주는데, cnn 등 좀더 깊은 신경망을 사용하면 거의 100%에 이르는 정확도를 보인다고 한다.

아래에는 전체 코드이다.

import tensorflow as tf
import random
import matplotlib.pyplot as plt
tf.set_random_seed(777)  # for reproducibility

from tensorflow.examples.tutorials.mnist import input_data
# Check out https://www.tensorflow.org/get_started/mnist/beginners for
# more information about the mnist dataset
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)

nb_classes = 10

# MNIST data image of shape 28 * 28 = 784
X = tf.placeholder(tf.float32, [None, 784])
# 0 - 9 digits recognition = 10 classes
Y = tf.placeholder(tf.float32, [None, nb_classes])

W = tf.Variable(tf.random_normal([784, nb_classes]))
b = tf.Variable(tf.random_normal([nb_classes]))

# Hypothesis (using softmax)
hypothesis = tf.nn.softmax(tf.matmul(X, W) + b)

cost = tf.reduce_mean(-tf.reduce_sum(Y * tf.log(hypothesis), axis=1))
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(cost)

# Test model
is_correct = tf.equal(tf.arg_max(hypothesis, 1), tf.arg_max(Y, 1))
# Calculate accuracy
accuracy = tf.reduce_mean(tf.cast(is_correct, tf.float32))

# parameters
training_epochs = 15
batch_size = 100

with tf.Session() as sess:
    # Initialize TensorFlow variables
    sess.run(tf.global_variables_initializer())
    # Training cycle
    for epoch in range(training_epochs):
        avg_cost = 0
        total_batch = int(mnist.train.num_examples / batch_size)

        for i in range(total_batch):
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)
            c, _ = sess.run([cost, optimizer], feed_dict={X: batch_xs, Y: batch_ys})
            avg_cost += c / total_batch

        print('Epoch:', '%04d' % (epoch + 1),
              'cost =', '{:.9f}'.format(avg_cost))

    print("Learning finished")
    # Test the model using test sets
    print("Accuracy: ", accuracy.eval(session=sess, feed_dict={X: mnist.test.images, Y: mnist.test.labels}))

    # Get one and predict
    r = random.randint(0, mnist.test.num_examples - 1)
    print("Label: ", sess.run(tf.argmax(mnist.test.labels[r:r + 1], 1)))
    print("Prediction: ", sess.run(tf.argmax(hypothesis, 1), feed_dict={X: mnist.test.images[r:r + 1]}))

* 본 포스트는 김성훈 교수님의 “모두를 위한 딥러닝” 강좌를 참고하여 작성되었습니다.

김성훈 교수님의 “모두를 위한 딥러닝”

Fight! - Kotaro Oshio(코타로 오시오)

|


[10th Anniversary, Played by 정성하]

(Tuning : Baritone)


[Original, Played by 기재]

(Tuning : Whole Step Down)


황혼과 마찬가지로, 핑거스타일 한다는 사람들은 다 한 번씩은 듣거나 쳐본, 그러나 여러 가지 스킬들이 들어가 있어 완곡하기도 어렵고, 완곡을 해도 원곡의 비트와 그루브를 살리기 참 어려운 곡.

개인적으로 연주곡에서 제목이 차지하는 비중이 참 크다고 생각하는데, Fight!는 제목과 연주곡의 매칭이 매우 잘 된 곡이라고 생각한다. 격투 영화나 게임의 한 장면이 떠오른다.

Fight! 는 오리지널 버전이 있고, 10주년 기념 버전 2가지가 존재하는데, 10주년 기념 버전이 소리가 조금 더 다채롭다.

원곡은 바리톤 튜닝인지라 일반 기타로 똑같은 음을 내려면 5음을 내려서 연주해야 하는데, 이러면 소리도 예쁘지 않고 버징도 많이 발생한다. 그냥 바리톤 기타를 사서 하는 게 제일 속 편하지만 국내에 잘 팔지도 않을 뿐더러 가격도 어마무시하다.

그래서 두 번째 영상과 같이 하프 스텝 또는 홀 스텝으로 다운 튜닝하여 연주들 많이 한다. 요 정도만 내려도 느낌이 꽤나 비슷해진다.

개인적으로 가장 어려운 부분은 코러스다. 퍼커시브와 뮤트가 일정하게 들어가야 하는데, 매번 칠 때마다 일정하지 않고 박자 지키기도 어렵다.

다변수 회귀분석+파일 읽어오기

|


김성훈 교수님 왈,

“일변수 회귀는 Toy Project 수준이고, 일반적으로 많이 쓰이는 회귀는 다변수 회귀(Multivariable Regression)이다”

꽤나 맞는 말. 다변수 회귀 개념도 되돌아볼 겸, 텐서플로우로 다변수 회귀를 진행하는 방법을 포스팅한다.

추가적으로 외부 파일에 있는 많은 데이터들을 텐서플로우에 올리는 방법을 알아본다.

[텐서플로우에서 다변수 표현하는 방법]

일반적으로 회귀식을 나타낼 때는 로 표현한다. 이건 x가 일변량일 때다. 하지만 다변수일 때는?

“행렬”을 사용하여 표현한다.

요 식을

요로코롬.

x1, x2, x3 3개의 변수를 가진 5개의 데이터 셋으로 각각의 가중치(w1,w2,w3)를 알아내기 위해서 위와 같은 행렬의 곱을 이용해 표현했다. 앞의 행렬을 X, 가중치 행렬을 W라고 하면 다음과 같이 표현 가능하다 :

텐서플로우에서는 위와 같이, 가중치 행렬이 변량들의 뒤로 가게 된다. 위 그림의 경우에서, X는 [5, 3]이고 W는 [3, 1], 우변은 [5, 1]의 shape를 가진다. 요것이 일반화되면

X가 [a, b]의 shape를 가지고 W가 [b, c]의 shape를 가지면 우변은 [a, c]의 shape를 가진다.

shape 계산이 헷갈릴 때 요것만 머릿속에 넣고 있으면 안 헷갈린다.

[텐서플로우에서 대용량의 파일을 읽어들이고 처리하는 방법]

파일이 용량이 클 땐 메모리에 모든 데이터를 올려서 처리가 불가능하다. 그래서 텐서플로우에서는 ‘Queue Runners’ 라는 방법을 사용하는데, 이 방법은 밑의 그림처럼 file들을 queue로 나누어서 적절한 용량만큼 read, decode하여 example queue에 메모리 부하가 과도하게 걸리지 않을 만큼 잘라서 넣는 방법이다.

개념은 어려워 보이지만 막상 구현은 어렵지 않다. 이를 사용해서 다변수 회귀분석을 텐서플로우 코드로 만들어 보면,

import tensorflow as tf
tf.set_random_seed(777)  # for reproducibility

텐서플로우 로딩하고

filename_queue = tf.train.string_input_producer(
    ['data-01-test-score.csv'], shuffle=False, name='filename_queue')

reader = tf.TextLineReader()
key, value = reader.read(filename_queue)

csv 파일로부터 뭔가를 읽어들이는데,

먼저 filename_queue라는 파일 이름의 리스트를 만든다.

다음 reader로 어떻게 생긴 종류의 파일을 읽어들일 건지 정의하고,

key와 value 값을 통해 파일에 있는 데이터를 읽어들인다.

record_defaults = [[0.], [0.], [0.], [0.]]
xy = tf.decode_csv(value, record_defaults=record_defaults)

recode_defaults는 읽어들일 행이 어떤 default값을 가질 건지를 정해준다. 지금 읽어들일 data-01-test-score.csv 파일에는 각 행이 4개의 열로 이루어져 있으므로 4개의 floating point 값을 저러한 이중 리스트 형식으로 넣어준다.

그리고 xy로 선언된 example queue에다가 reader에 들어있는 값들을 decode해주어 넣어준다. xy는 결과적으로 각 열들이 리스트로 들어있는 이중 리스트가 될 것이다. [[a,b,…], [c,d,…], [e,f,…], [g,h,…]]

train_x_batch, train_y_batch = \
    tf.train.batch([xy[0:-1], xy[-1:]], batch_size=10)

이제 train_x_batch에는 xy의 첫 열~마지막 바로 전 열, train_y_batch에는 xy의 마지막 열을 넣고, batchsize(한 번에 세션에 올릴 데이터의 양)을 10으로 맞춘다. batch_size는 hyperparameter이다.

X = tf.placeholder(tf.float32, shape=[None, 3])
Y = tf.placeholder(tf.float32, shape=[None, 1])

W = tf.Variable(tf.random_normal([3, 1]), name='weight')
b = tf.Variable(tf.random_normal([1]), name='bias')

그 다음부터는 익숙한 코드가 나온다. 여기에서 X의 shape는 행에는 몇 개의 행이 올지 모르기 때문에 None으로 놓고, 열이 3개이기 때문에 [None, 3]으로 설정한다. 마찬가지로 Y는 [None, 1]로 설정한다. 이게 다변량 회귀분석의 전부다.

그리고 위에서 적어놓은 방법에 따라 W의 Shape는 [3,1]로, b는 [1]로 설정한다.

hypothesis = tf.matmul(X, W) + b
cost = tf.reduce_mean(tf.square(hypothesis - Y))
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1e-5)
train = optimizer.minimize(cost)
sess = tf.Session()
sess.run(tf.global_variables_initializer())

cost와 train을 설정하고, 세션을 초기화한다.

coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)

이 부분은 queue runner을 사용할 때 사용 시작 전에 쓰는 코드이다. 그냥 통째로 갖다 쓰면 된다.

for step in range(2001):
    x_batch, y_batch = sess.run([train_x_batch, train_y_batch])
    
    cost_val, hy_val, _ = sess.run(
        [cost, hypothesis, train], feed_dict={X: x_batch, Y: y_batch})
    if step % 100 == 0:
        print(step, "Cost: ", cost_val, "\nPrediction:\n", hy_val)

2001번 돌면서 학습을 진행한다.

coord.request_stop()
coord.join(threads)

학습이 끝난 후 queue runner를 종료하는 구문을 다음과 같이 넣어준다.

print("Your score will be ",
      sess.run(hypothesis, feed_dict={X: [[100, 70, 101]]}))

print("Other scores will be ",
      sess.run(hypothesis, feed_dict={X: [[60, 70, 110], [90, 100, 80]]}))

테스트 데이터를 넣고 예측을 해 본다.

생각보다 많이 간단하다.

아래는 전체 코드이다.

import tensorflow as tf
tf.set_random_seed(777)  # for reproducibility

filename_queue = tf.train.string_input_producer(
    ['data-01-test-score.csv'], shuffle=False, name='filename_queue')

reader = tf.TextLineReader()
key, value = reader.read(filename_queue)

# Default values, in case of empty columns. Also specifies the type of the
# decoded result.
record_defaults = [[0.], [0.], [0.], [0.]]
xy = tf.decode_csv(value, record_defaults=record_defaults)

# collect batches of csv in
train_x_batch, train_y_batch = \
    tf.train.batch([xy[0:-1], xy[-1:]], batch_size=10)

# placeholders for a tensor that will be always fed.
X = tf.placeholder(tf.float32, shape=[None, 3])
Y = tf.placeholder(tf.float32, shape=[None, 1])

W = tf.Variable(tf.random_normal([3, 1]), name='weight')
b = tf.Variable(tf.random_normal([1]), name='bias')

# Hypothesis
hypothesis = tf.matmul(X, W) + b

# Simplified cost/loss function
cost = tf.reduce_mean(tf.square(hypothesis - Y))

# Minimize
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1e-5)
train = optimizer.minimize(cost)

# Launch the graph in a session.
sess = tf.Session()
# Initializes global variables in the graph.
sess.run(tf.global_variables_initializer())
# print(sess.run(xy))
# Start populating the filename queue.
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)

for step in range(2001):
    x_batch, y_batch = sess.run([train_x_batch, train_y_batch])
    
    cost_val, hy_val, _ = sess.run(
        [cost, hypothesis, train], feed_dict={X: x_batch, Y: y_batch})
    if step % 100 == 0:
        print(step, "Cost: ", cost_val, "\nPrediction:\n", hy_val)

coord.request_stop()
coord.join(threads)

# Ask my score
print("Your score will be ",
      sess.run(hypothesis, feed_dict={X: [[100, 70, 101]]}))

print("Other scores will be ",
      sess.run(hypothesis, feed_dict={X: [[60, 70, 110], [90, 100, 80]]}))

'''
Your score will be  [[ 177.78144836]]
Other scores will be  [[ 141.10997009]
 [ 191.17378235]]
'''

* 본 포스트는 김성훈 교수님의 “모두를 위한 딥러닝” 강좌를 참고하여 작성되었습니다.

김성훈 교수님의 “모두를 위한 딥러닝”

* 이번 포스팅에 사용된 csv 파일은 아래 링크에 있습니다.

파일 링크

딥 러닝/머신 러닝에 나오는 수학 개념 정리

|


딥 러닝 및 머신 러닝을 공부하기 위해 반드시 필요한 대학 수준의 수학 개념들을 정리한 블로그 및 사이트를 소개한다.

<지속 업데이트>

[선형대수]

[미분적분학]

블로그에 각종 개체 삽입 방법 (지킬 블로그 기준)

|


아래에 포스팅할 삽입 방법은 제 블로그(지킬 깃허브 블로그)에 적용 가능함을 밝힙니다. (다른 종류의 블로그에는 그에 맞는 삽입 방법을 쓰세요)

[블로그에 유튜브 동영상 삽입하기]

매우 간단하다.

먼저 삽입할 유튜브 동영상의 주소를 복사한 다음,

embedresponsively.com에 들어가서 붙여넣기 하고 Embed 클릭. -> HTML 코드 생성.

블로그 편집창에 먼저 [div] 태그로 html 구간을 설정한 다음, 위에서 생성한 html 코드를 붙여넣는다. (저는 Typora 편집기를 쓰기 때문에 HTML 코드를 자동으로 인식해서 동영상을 보여줍니다. 다른 마크다운 편집기를 쓰시는 분들은 HTML을 인식하게끔 처리해 주세요)

동영상을 삽입하는 방법은 몇 가지가 있는데, 이 방법이 모바일에서도 잘리지 않고 깔끔하게 재생되며 가장 쉽다.


[블로그에 사진 삽입하기]

사진을 자신의 깃허브에 올리고 그곳의 링크를 가지고 와도 상관없지만, 깃허브의 용량 제한 때문에 그림이 많아지면 나중에 문제가 발생할 수 있다고 판단, ImageShack 사이트를 이용하기로 했다. 이곳에 가입하고 그림을 올리면 오른쪽에 GET LINKS / IMAGE SIZES 버튼이 나오는데, 여길 눌러서 나오는 링크를 블로그에 <img src ="이미지 주소"> 형식으로 걸어주면 된다.

깔끔하다. 개발자 도구 확인해 보시길.


[블로그에 수식 삽입하기]

몇 가지 방법이 있지만, mathjax를 활용한 삽입이 가장 다루기 쉽고 깔끔하기에 이 방법을 소개한다. mathjax는 크로스 브라우저 자바스크립트 라이브러리라고 하는데; 삽입하는 방법만 알면 되기 때문에 자세한 건 알 필요 없고,

먼저, 블로그 html의 head 부분에 다음을 붙여넣는다 :

<script type="text/x-mathjax-config">
  MathJax.Hub.Config({
    tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}
  });
</script>

mathjax는 latex문법으로 수식을 생성하는데, 사용자가 편집하기 좋은 사이트를 소개한다 :

https://www.codecogs.com/latex/eqneditor.php

여기에 들어가면 수식을 gui로 편집하기 쉽게 되어 있다. 마우스로 열심히 클릭해 수식을 만들면 중앙에 Latex 문법으로 된 수식이 자리잡을 것이다. 이 수식을 복사해 와서 블로그에 붙여넣는다. 그리고 붙여넣은 수식 앞뒤에 $를 붙여주면 완성.

편집기에서는 안 보일 건데, 업로드한 후에는 예쁘게 나와 있는 모습을 확인할 수 있을 것이다.

$\frac{1}{m}\sum_{i=1}^{m}(H(x^{(i)})-y^{(i)})^{2}$

헿헿