Tekhartha의 인공지능 기술블로그

word2vec에 대하여

|


[단어 임베딩]

텍스트 데이터를 컴퓨터가 인식할 수 있게 바꿔주는 방법 중 하나가 임베딩(Embedding)인데, 말이 어렵지 그냥 단어(Word)를 벡터(Vector)로 변환시키는 방법이다. 단어들을 임베딩하는 방법들은 여러 가지가 있는데, 그 중 가장 쉽게 생각할 수 있는 방법은 주어진 텍스트에 있는 단어 수만큼의 차원을 만들어 one-hot-encoding으로 만드는 방식이다. 이 방법은 자연어 처리 초창기에 등장한 NNLM(Neural Network Language Model)은 이러한 방식으로 벡터를 만들어 임베딩을 하였지만, one-hot-encoding이었기 때문에 비슷한 단어들끼리의 유사성을 나타내기 어려웠고, 또 너무 많은 차원 수가 필요했으므로 매우 느렸다. (자세한 내용은 Beomsu Kim님의 블로그 로)

[word2vec의 등장]

그래서 등장한 것이 word2vec이다. word2vec은 2013년 구글의 Mikolov를 필두로 발표된 모델로, 논문은 여기에서 볼 수 있다. word2vec은 각 단어들을 “P(o|c) : 중심단어 c가 등장했을 때 주변단어 o가 등장할 조건부 확률” 로 정의한다.

그림으로 나타내면 요렇다. 출처 : cs224n 2강

이 때 P(o|c)를 수식으로 나타내면 다음과 같다 :

$u_{o}$는 주변단어들의 벡터, $v_{c}$는 중심단어의 벡터, $u_{w}$는 말뭉치 내의 모든 단어를 나타낸다. (V의 범위는 설정하기 나름이다 -> hyperparameter) 이 값을 최대한 크게 해야 모델로부터 생성되는 벡터들의 매핑이 보다 정확하게 된다. 이 값을 크게 하려면 분자를 최대화, 분모를 최소화 시켜야 하는데,

분자의 최대화 : 주변에 등장한 단어와 중심 단어의 내적을 크게

분모의 최소화 : 주변에 등장하지 않은 단어와 중심 단어의 내적값을 작게

요런 뜻을 가지고 있다.

이 값을 최대화시키는 방법은 해당 식을 $v_{c}$에 관하여 편미분하여 극점이 되는 부분을 찾으면 가능하다. 자세한 유도는 아래와 같다 :

최종 결과 식에서 $u_{o}$는 바꿀 수 없는 값이고(이미 observed된 것), 바꿀 수 있는 것은 minus 부호 뒤의 값들인데, 이들은 V의 범위를 조정함으로써 바꿀 수 있다. 이 식의 값을 딥 러닝 기법을 통해 최적화 하는 것이 word2vec 모델에서 쓰인 수학적 기법이라 할 수 있겠다.

Tensorflow로 선형 회귀 만들기

|


[선형 회귀]

선형 회귀에 관한 내용은 따로 포스팅하도록 한다.

[텐서플로우로 선형 회귀 만들기]

일반적인 선형 회귀식은 y=ax+b의 직선으로 나타낼 수 있다. 최소자승법에 의해 x와 y의 값으로 하여금 적절한 a와 b를 찾아내게 하는 것이 선형 회귀인데, 텐서플로우에서는 다음과 같이 표현한다 :

$H(x) = Wx+b$

이 때, 최소자승식은 다음과 같다 :

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

딥 러닝에서는 최소화시켜주어야 하는 값을 cost라고 지칭한다. 따라서 위의 식을 W와 b에 관한 cost로 나타내면 :

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

우리는 텐서플로우로 하여금 많은 x와 y 세트를 집어넣어 $H(x^{(i)})$를 구해낼 것이다. 최소가 될 때 $H(x^{(i)})$(기대값)과 $y^{(i)}$(실제값)의 차이는 0에 가깝게 다가갈 것이다.

Gradient Descent 기법을 사용하여 cost함수를 최소화시키는 코드를 구현하였다.

import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

필요한 라이브러리를 import한다. (seaborn은 pyplot을 조금 더 예쁘게 보여주는 라이브러리)

ph_input = tf.placeholder(tf.float32, shape = [128, 1])
ph_output = tf.placeholder(tf.float32, shape = [128, 1])

128행 1열로 구성된 placeholder들을 선언한다. 한 번에 128개씩의 데이터까지 받을 수 있게 만들겠다는 뜻이다. 그러니까 최대로 맞출 수 있는 batch의 개념이 된다는 말.

variable1 = tf.Variable(tf.random_normal([3]))  # W
variable2 = tf.Variable(tf.random_normal([3]))  # b

W와 b의 역할을 할 variable들을 선언한다. 이 때 [3]은 batch의 사이즈가 된다. 최대 128까지 맞출 수 있다. (그 이상으로 넣어도 돌아가긴 하지만 결과가 이상하다)

output = variable1 * ph_input + variable2
loss = tf.reduce_mean(tf.square(output - ph_output))
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)

output은 기대값이 될 것이다. loss는 cost를 텐서플로우 형식에 맞추어 나타낸 것이고, optimizer는 데이터들을 어떠한 학습률로, 어떤 최적화 기법을 사용하여 최적화할 것인지를 정의한 변수이다. train은 optimizer의 minize 메소드를 사용하여 loss를 optimizer의 방법에 맞춰 최적화한다는 뜻을 담고 있다. 결국 session을 열어 그래프를 돌릴 때 우리는 train을 기점으로 시작하면 된다는 이야기다.

(거의) 모든 텐서플로우를 활용한 딥 러닝은 위와 같은 구조에 맞추어서 그래프를 생성한다. 머릿속에 담아둘 것.

init_op = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init_op)

변수를 초기화하고 세션을 시작한다.

varnum = []
var1print = []
var2print = []

이들은 나중에 그래프를 그릴 때 사용될 list들이다. varnum에는 epoch 숫자, varprint들에는 각 에폭마다의 예측된 W와 b값들이 들어간다.

for i in range(10000):
    input_value = np.random.random_sample(128)
    input_value = np.reshape(input_value, (128, 1))
    output_value = input_value * 2 + 0.5
    output_value = np.reshape(output_value, (128, 1))

    sess.run(train, feed_dict={ph_input: input_value, ph_output: output_value})

epoch(반복횟수)를 10000으로 설정하고, 학습에 쓰일 데이터를 생성한다.

input_value는 random하게 생성한 128개의 숫자를 numpy([128,1]) 형식에 맞추어 넣고, output value는 input_value * 2 + 5의 값들을 똑같은 형식에 맞추어 넣는다. 그러니까 이 데이터가 제대로 학습된다면, 모델은 var1을 2로 수렴시킬 것이고, var2를 0.5로 수렴시킬 것이다.

마지막 줄에서는 만들어진 데이터들을 각각 placeholder들에 feeding하고 train을 진행시킨다.

    if i % 1000 == 0:
        loss_value = sess.run(loss, feed_dict={ph_input: input_value, ph_output: output_value})
        var1_value, var2_value = sess.run([variable1, variable2])

        print('loss: %.3f' % loss_value)
        print('var1: ', var1_value)
        print('var2: ', var2_value)
        print('')
        var1print.append(var1_value[0])
        var2print.append(var2_value[0])

1000번마다 한 번씩 loss값과 var1, var2들의 값을 출력하기 위해서 따로 세션을 돌려서 값을 받아낸다.

var1,var2print에는 var1,var2_value의 첫 번째 값들을 추가시켜 나간다. (그래프 그릴려고)

결과는 다음과 같았다 :


loss: 6.168 var1: [-0.25723684 0.07613225 0.9911254 ] var2: [-0.07696786 -2.0157108 -0.8618597 ]

loss: 0.027 var1: [1.0213816 1.7091606 1.8589249] var2: [1.0217636 0.6544363 0.5748732]

loss: 0.011 var1: [1.3697628 1.8128656 1.9092383] var2: [0.8386766 0.6005624 0.5487737]

loss: 0.006 var1: [1.5941049 1.8794786 1.9415462] var2: [0.71761054 0.56461424 0.53133845]

loss: 0.002 var1: [1.7380259 1.9222114 1.9622731] var2: [0.6404018 0.54168946 0.52021956]

loss: 0.001 var1: [1.8307557 1.9497468 1.9756279] var2: [0.59064263 0.52691466 0.51305306]

loss: 0.000 var1: [1.8907079 1.9675488 1.9842607] var2: [0.5585419 0.51738256 0.5084307 ]

loss: 0.000 var1: [1.9295092 1.979069 1.9898494] var2: [0.5378429 0.5112365 0.5054495]

loss: 0.000 var1: [1.9544458 1.9864748 1.9934407] var2: [0.5242374 0.5071963 0.5034901]

loss: 0.000 var1: [1.9707134 1.9913046 1.9957832] var2: [0.5156961 0.5046603 0.50226 ]


var1들의 값은 2로, var2들의 값은 0.5로 수렴해 감을 볼 수 있다.

for i in range(len(var1print)) :
    varnum.append(i*1000)
plt.plot(varnum, var1print)
plt.plot(varnum, var2print)
plt.show()

값들이 어떻게 최적화되어 가는지를 보기 위해 그래프를 만들어 출력한다.

결과는 다음과 같다 :

파란색이 var1, 노란색이 var2이다. 대략 6000번의 epoch를 거치면서 각각 2.0과 0.5로 수렴하여 간다.



포스팅을 진행하면서 몇 가지 생각이 들었다. 첫 번째는 이런 간단한 회귀(고전 통계 프로그램으로는 단숨에 결과가 나온다)도 6000번이 넘는 횟수를 수행해야 적절한 값을 찾아내는 모델을 보면서 훨씬 더 복잡한 문제를 푸는 모델들은 도대체 epoch를 몇 번을 돌리는 것인가..에 대한 경외심이었고, 두 번째는 이런 많은 계산을 몇 초만에 해내는 GPU의 성능에 대한 경외심이었다. (750ti만 되어도 이 정도 속도가 나오네..)

아래는 전체 코드이다.

import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()


ph_input = tf.placeholder(tf.float32, shape = [128, 1])
ph_output = tf.placeholder(tf.float32, shape = [128, 1])


variable1 = tf.Variable(tf.random_normal([3]))  # W
variable2 = tf.Variable(tf.random_normal([3]))  # b

output = variable1 * ph_input + variable2
loss = tf.reduce_mean(tf.square(output - ph_output))
optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)

init_op = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init_op)
varnum = []
var1print = []
var2print = []
for i in range(10000):
    input_value = np.random.random_sample(128)
    input_value = np.reshape(input_value, (128, 1))
    output_value = input_value * 2 + 0.5
    output_value = np.reshape(output_value, (128, 1))

    sess.run(train, feed_dict={ph_input: input_value, ph_output: output_value})

    if i % 1000 == 0:
        loss_value = sess.run(loss, feed_dict={ph_input: input_value, ph_output: output_value})
        var1_value, var2_value = sess.run([variable1, variable2])

        print('loss: %.3f' % loss_value)
        print('var1: ', var1_value)
        print('var2: ', var2_value)
        print('')
        var1print.append(var1_value[0])
        var2print.append(var2_value[0])

for i in range(len(var1print)) :
    varnum.append(i*1000)

plt.plot(varnum, var1print)
plt.plot(varnum, var2print)
plt.show()


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

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

CFG(Context Free Grammer), Parsing

|


[포스트 시작 전]

Chomsky의 Hierarchy에 의한 언어의 종류 -

1) Unrestricted Grammer(type 0)

2) Context Sensitive Grammer(type 1)

3) Context Free Grammer(type 2)

4) Regular Grammer(type 3)

4가지로 나눌 수 있는데, 이 중 type 0과 1은 Natural Language(자연 언어)에 속하고, type 2 와 3은 Programming Language에 속한다.(인공 언어)


[CFG(Context Free Grammer, 문맥 자유 문법)]

CFG는 1950년대 중반, Noam Chomsky에 의해서 개발되었다.


[문맥 자유 문법의 정의]

문법 G = (V, T, S, P) 에서 모든 생성규칙이

A → x

의 형태를 가지면 G 를 문맥-자유 문법 (context-free grammar : CFG) 이라고 한다. 여기서 A ∈ V 이고, x ∈ (V∪T)* 이다.

또한 언어 L 에 대해서 L = L(G) 를 만족하는 문맥-자유 문법 G 가 존재하고 오직 그럴 때에만 L 을 문맥-자유 언어 (context-free language : CFL) 라고 한다.


위의 G 구성 요소 중 V는 Non-terminal의 기호 집합, T는 Terminal 기호 집합, S는 Non-terminal 시작 기호, P는 문장을 생성하기 위한 생성 규칙이다.

[Parsing(파싱)]

파싱이란, 일련의 문자열을 의미있는 토큰 (token) 으로 분해하고 이들로 이루어진 파스 트리 (parse tree)를 만드는 과정을 말한다. 이 때 분해하는 방법은 주어진 문법을 따른다.

[문맥 자유 문법을 이용한 파싱의 예시]

다음과 같은 문맥 자유 문법이 있다고 하자.

R1 : S -> NP VP

R2 : NP -> N

R3 : NP -> Det N

R4 : VP -> V NP

R5 : VP -> V

R6 : N -> Person Name | He | She | boy | Girl | It |cricket | song | book

R7 : V -> likes | reads | dogs

이 문법을 통하여 “He likes cricket”를 파싱하면,

와 같이 된다.

텐서플로우 시작하기

|


[Tensorflow(텐서플로우) 시작하기]

  • Tensorflow(텐서플로우) : 구글이 오픈소스로 공개한 머신러닝 및 딥러닝 라이브러리. 그래프 형식으로 되어 있어 일반적인 언어 사용과는 약간 다른 사용법을 요한다. 일반인들이 사용하기 쉽도록 다양한 라이브러리를 제공하여 널리 쓰인다.
  • placeholder, constant, variable 3가지 변수 유형을 가지고 있다.

[텐서플로우 설치하기 (Ubuntu 16.04 기준)]

리눅스 운영체제(기준) 설치 방법은 아래 블로그에 자세히 기재되어 있어 이것으로 대체 :

Yahwang의 기술블로그 - Ubuntu(16.04)에 Tensorflow-gpu 1.8.0 설치 (+ CUDA & CUDNN )

[텐서플로우 자료형]

  1. placeholder : Input Data를 담아두는 공간

  2. variable : Weight(가중치)를 담아둘 때 쓰임
  3. constant : 상수를 넣는 부분.

y = Wx + b 에서 x와 y는 placeholder, W는 variant, b는 constant

보통 텐서플로우로 연산을 수행 시, input data를 가공하여 placeholder에 담아 두고, variable과 constant을 조정해 가면서 모델을 만든다.

[텐서플로우로 “Hello, Tensorflow! 찍어보기”]

  • 텐서플로우는 변수 자체로 연산을 하는 것을 허용하지 않는다. 자료형 선언과 계산식 등을 정의하고 run을 시키면 그곳에서부터 input data가 있는 곳까지 거슬러 올라가서 거기서부터의 모든 그래프를 Device에 올리고 계산한 결과를 출력한다. 이 때 run을 포함한 그래프를 Session이라고 한다. 세션을 file 형태로 존재하고 우리가 입출력을 할 수 있다.
import tensorflow as tf
hello = tf.constant("Hello, TensorFlow!")
sess = tf.Session()
print(sess.run(hello))

문자열을 출력하려면 위와 같이 해야 한다.

그냥 print(hello)를 하면 오류가 뜬다. 왜? 그래프를 device에 올려서 돌리지 않았기 때문.

특정 자료형을 실행시키려면 위와 같이 sess = tf.Session()으로 세션을 돌리겠다는 메소드를 sess에 저장하고, sess.run(자료형) 으로 실행시킨다.


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

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

NLP(Natural Language Processing, 자연어 처리) & Corpus(코퍼스)

|


 [자연어 처리] 

  • 자연어 처리(Natural Language Processing, NLP)란 인간의 자연어를 처리하기 위한 계산 기술 또는 계산 언어학에 대한 능력이다.
  • 자연어 처리란 컴퓨터와 인간 언어 간의 상호작용과 관련된 전산과학, 인공지능, 컴퓨터 언어학의 한 분야다.
  • 자연어 처리란 인간 자연어의 자동(또는 반자동) 처리로 정의할 수 있다.

[코퍼스(Corpus)]

코퍼스는 컴퓨터에 저장된 자연어 자료 모음이며 언어가 어떻게 사용됐는지 알아내는 데에 사용한다.

더 정확히 정의하면, 코퍼스는 언어 분석에 사용되는 실제 언어의 체계적 디지털 모음이다.

둘 이상의 코퍼스가 있으면 코포라(Corpora) 라고 부른다.

※ nltk(파이썬의 자연어 처리 패키지)의 4가지 코포라 타입

  • Isolate Corpus(아이솔레이트 코퍼스) : 텍스트 또는 자연어 모음
  • Categorized Corpus(카테고리화 코퍼스) : 다양한 타입의 부류로 그룹화된 텍스트 모음. ex) brown 코퍼스
  • Overlapping Corpus(오버래핑 코퍼스) : 그룹화된 코퍼스지만 카테고리가 겹침
  • Temporal Corpus(템포럴 코퍼스) : 일정 기간 동안 자연어를 사용하는 모음. ex) inaugural address 코퍼스