본문 바로가기

머신러닝/[Deep Learning Cookbook] 스터디

1. Word embedding - 텍스트 유사성 계산, 위키피디아를 이용한 영화 추천 시스템 (Chapter 3, Chapter 4)


Outline


(1)


기계학습에 사용되는 모든 데이터는 수량화된(Quantified) 값을 입력값 혹은 출력값으로 사용하게 됩니다.


가장 이상적인 것은 우리가 사용할 수 있는 모든 값들이 수량화되어있는 변수인 케이스겠지만,


현실에서 쉽게 접할 수 있는 많은 데이터는 별도의 수량화 작업이 필요한 경우가 많습니다.


물론 단순한 범주형(Categorical) 변수라면, 해당 범주에 해당하는 부분에 1, 그렇지 않은 부분에 0을 코딩하는 one-hot encoding(또는 dummy변수화)과 같은 작업을 통해 간단히 수량화시킬 수 있습니다.


그러나 모델이 언어, 음성, 영상 등을 처리해야 하는 단계에 이르다보면 이 문제는 더욱 복잡해지게 됩니다.


우리가 자연적으로 접하는 경우가 많은 이러한 데이터들은 보통 sparse한 특성을 지니고 있습니다.


이 말은 데이터가 매우 일부의 의미있는 부분과 대부분의 의미없는 부분으로 이루어져 있다는 뜻입니다.

또한 학습에 사용되는 데이터를 그대로 벡터화시킬 때, 그 차원 또한 매우 방대합니다.


예를 들면 1920*1080 해상도의 일반적인 환경의 PC 화면을 그대로 벡터로 수량화시키기 위해서는 각 픽셀 당 3개의 RGB 채널을 가지고 있기 때문에 1920*1080*3 = 6,220,800개의 차원이 필요할 것입니다.


만약 수량화할 데이터가 자연어(인간이 일상적으로 사용하는 언어)라면, 사용되는 단어들을 모두 one-hot encoding할 시 최소한 문서들에 등재된 단어의 종류들 만큼의 차원이 필요할 것입니다.


컴퓨터가 아닌, 사람 관점에서 생각해보면 이 특성이 함의하는 의미는 명확해집니다.


Bichon frise statue (출처)


예를 들면 위와 같은 사진이 '강아지'인지 아닌지 판단하는 모델을 만든다고 가정합시다.


사람은 이것이 강아지라는 것을 당연히 판별할 수 있겠지만, 200*200에 해당되는 모든 픽셀을 일일이 들여다보고 판단하지는 않습니다.


아마도 사람이라면 배경과 물체의 구분, 이목구비의 형태 등 수량적이지 않은것처럼 보이는 추상화된 기준을 통해 판별할 것입니다.


인간의 로직과 일대일로 대응할 수 있는 부분은 아니지만, 머신러닝에서도 이와 같은 관점을 적용할 수 있습니다.


즉, 데이터를 수량화시키되, 보다 추상적인 의미를 포착할 수 있고 계산적으로 훨씬 간단해질 수 있도록 수량화시켜야 함을 알 수 있습니다.


따라서 수량 데이터가 아닌 것들을 기계학습이 사용할 수 있는 수량화된 데이터로 바꾸기 위해선 다음과 같은 조건이 충족되어야 합니다.


- 원 데이터의 근본이 되는 구조(또는 manifold)를 잘 담고 있는가?
- 원 데이터를 그대로 사용하는 것에 비해 충분히 경제적인가?


보통 머신러닝하면 떠오르는 좁은 의미의 머신러닝은 입력변수와 출력변수 사이의 관계를 잘 포착해주는 모형을 생성하는 일이지만,


실제로는 이와 같이 머신러닝 알고리즘이 더욱 정확하고 효율적으로 학습할 수 있도록 데이터를 바꾸는 것(feature engineering) 또한 머신러닝에서 큰 부분을 차지합니다.


(2)


이번 파트를 비롯해 앞으로의 몇 챕터는 단어 임베딩(Word embedding)과 이를 활용한 응용에 대해 다룹니다.


임베딩은 앞서 설명한것과 같은 두 가지 조건을 만족시키도록 적절하게 변환된 일종의 변수입니다.


즉, 고차원의 원본의 구조를 크게 손상시키지 않고 그 의미를 보존하면서, 낮은 차원으로 수량화한 변수 벡터(또는 텐서)입니다.


자연어처리(NLP; Natural Language Processing)에서 또한 단어 임베딩을 어떻게 수행하는가가 전반적인 학습과정에서 핵심적인 부분을 차지합니다.


Word2vec 등 현재 대다수의 잘 알려진 단어 임베딩 모델은 문서에서 동시에 나타나는 빈도 등의 단어간 관계를 참고해 embedding을 생성하는데,


이와 같은 모델들이 one-hot encoding에 비해 더 적은 용량을 차지하면서도 더욱 원래 단어들 간의 관계를 잘 보존하는 것으로 알려져 있습니다.


[Deep learning cookbook]의 Chapter 3은 Word2vec 모델을 통해 이미 훈련된(pre-trained) embedding을 사용하여 word 간의 관계(상호관계, 포함관계 등)를 표현하는 실습입니다.


이 챕터에서는 단어 간 관계, 개체의 클래스 분류 문제 등 임베딩의 간단한 응용 예시를 다루고 있는데, 이를 통해 Word2vec 단어 임베딩의 효과성을 볼 수 있습니다.


한편, 이미 학습된 임베딩을 사용해도 되지만 경우에 따라서는 직접 임베딩을 구하는 것이 더욱 적절한 경우도 있습니다.


다행히 abstraction 수준이 높은 Keras에서는 embedding layer를 제공하여, 임베딩을 어렵지 않게 추출할 수 있습니다.


[Deep learning cookbook]의 Chapter 4는 덤프된 위키피디아 데이터베이스로부터 링크 연결관계를 파악하고, 이를 통해 영화 직접 임베딩을 추출합니다.


이와 같이 생성된 임베딩을 통해 Chapter 3과 마찬가지로 영화들 간의 유사도 측정이나, 영화 추천 시스템 구축 등의 머신러닝 작업이 가능하게 됩니다.


Algorithms


- Embedding algorithm : word2vec


Chapter 3에서는 구글의 뉴스 데이터를 통해 학습된 word2vec weight를 사용하고 있습니다.


CBOW (Continuous Bag Of Words) 모델이라고도 불리는 이 모델은 word2vec 네트워크는 입력과 출력이 닮도록 하는 weight를 찾는 아래와 같은 인공신경망 구조를 취하고 있습니다.


이와 같은 구조는 autoencoder와 같은 네트워크에서도 발견할 수 있는 형태로, 임베딩을 추출하는 네트워크의 전형적인 구조라고 볼 수 있습니다.



이 네트워크에 대해 조금 더 부연하자면, C개의 문맥 상 단어들에 대응하는 weight의 평균을 hidden neuron으로 정의하고,


이 weight들로부터 생성할 수 있는 softmax 함수 형태의 multinomial distribution (logistic distribution의 일반화)이 원래의 문맥과 가깝도록 오차를 역전파(back-propagation)하여 weight를 학습합니다.


임베딩은 원래의 단어 대신 이 weight를 사용하게 되는데, 직관적으로 보면 이것으로부터 본래 단어들을 예측할 수 있도록 하는 정보를 압축하고 있기 때문에 이와 같이 weight을 임베딩으로 사용하는 것은 타당해 보입니다.


재미있는 것은 이 모델에는 일반적으로 인공신경망의 hidden layer에 사용되는 sigmoid, ReLU 등의 비선형 활성화함수가 사용되지 않는다는 것입니다.


이 네트워크에서 유일하게 사용되는 비선형함수는 output neuron의 합을 1로 만드는 softmax 함수 뿐입니다.


따라서 word2vec을 통해 추출한 임베딩은 선형적인 형태로 문맥을 보존하는 유용하고 재미있는 성질이 있습니다.


예를 들면 Man과 Woman이라는 단어에 대한 임베딩의 차이를 Queen의 임베딩에 더하면 King의 임베딩이 도출되는 식입니다.


두 단어간의 유사도 또는 관계도는 어떻게 나타낼 수 있을까요? 비슷한 의미를 가지고 있다면 임베딩 벡터 또한 유사하게 나타날 것입니다.


일반적으로 거리에 많이 사용되는 norm을 통해 벡터 간의 거리를 측정할 수도 있지만,

보통 word2vec의 임베딩은 크기를 배제하는 대신 방향의 유사성을 강조하여 코사인 유사도(cosine similarity)를 사용하고 있습니다.


양수의 weight만 고려한다면, 코사인 유사도는 0부터 1까지의 값을 통해 유사도를 나타낼 수 있습니다.


코사인 유사도는 norm을 신경쓰지 않는다면, 두 벡터에 대한 코사인의 정의인



에 따라 두 벡터의 내적을 통해 유사도를 쉽게 계산할 수 있습니다.


cookbook example)


(1)

Chapter 3의 example에서는 google의 뉴스 데이터를 통해 미리 학습된 word2vec weight를 불러와서 코사인 유사도를 계산하기 위한 모델을 생성하였습니다.


MODEL = 'GoogleNews-vectors-negative300.bin'

path = get_file(MODEL + '.gz', 'https://s3.amazonaws.com/dl4j-distribution/%s.gz' % MODEL) unzipped = os.path.join('generated', MODEL) if not os.path.isfile(unzipped): with open(unzipped, 'wb') as fout: zcat = subprocess.Popen(['zcat'], stdin=open(path), stdout=fout ) 

zcat.wait()

model = gensim.models.KeyedVectors.load_word2vec_format(unzipped, binary=True)


cookbook에서는 코드 내에서 압축된 데이터를 직접 다운로드하고, Popen을 통해 쉘에 직접 접근하여 압축해제 및 gensim 라이브러리에 로드까지 진행하였습니다.


쉘에서 stdin 및 stdout과 같이 메모리에 접근하는 어플리케이션을 사용하는 코드는리눅스에서는 비교적 부드럽게 돌아갈 가능성이 큽니다.


하지만 일반적인 윈도우 환경이라면 이 부분을 수동으로 진행해주시거나 mingw64 등과 같이 윈도우 상에서 리눅스의 기본적인 어플리케이션들을 실행할 수 있도록 하는 툴을 사용하여, zcat과 같은 리눅스 어플리케이션들을 사용토록 하는 등 부가적인 절차가 필요합니다.


(2)


word2vec을 이용해 가장 단어 간의 관계를 유추하여, input에 그 관계를 대응하는 함수를 구성한 예제입니다.


def A_is_to_B_as_C_is_to(a, b, c, topn=1):
    a, b, c = map(lambda x:x if type(x) == list else [x], (a, b, c))
    res = model.most_similar(positive=b + c, negative=a, topn=topn)
    if len(res):
        if topn == 1:
            return res[0][0]
        return [x[0] for x in res]
    return None
for country in 'Italy', 'France', 'India', 'China':
    print('%s is the capital of %s' % 
          (A_is_to_B_as_C_is_to('Germany', 'Berlin', country), country))


out>>>


Rome is the capital of Italy

Paris is the capital of France
Delhi is the capital of India
Beijing is the capital of China

for company in 'Google', 'IBM', 'Boeing', 'Microsoft', 'Samsung':
    products = A_is_to_B_as_C_is_to(
        ['Starbucks', 'Apple'], 
        ['Starbucks_coffee', 'iPhone'], 
        company, topn=3)
    print('%s -> %s' % 
          (company, ', '.join(products)))

out>>>

Google -> personalized_homepage, app, Gmail
IBM -> DB2, WebSphere_Portal, Tamino_XML_Server
Boeing -> Dreamliner, airframe, aircraft
Microsoft -> Windows_Mobile, SyncMate, Windows
Samsung -> MM_A###, handset, Samsung_SCH_B###

출력된 결과물을 통해 이 임베딩이 국가대 수도의 관계, 기업과 대표 제품의 관계 등 단어 간의 hierarchy를 꽤 잘 포착하고 있다고 볼 수 있습니다.


- Dimension reduction : t-SNE


차원축소는 대표적인 비지도학습(Unsupervised learning)에 해당하는 문제입니다.


차원축소는 시각화, 요인 분석 등 다양한 분야에서 사용되는 머신러닝 기법이며, 여기서는 특히 word2vec에 사용된 300차원의 임베딩을 시각적으로 표현하기 위해 2차원으로 축소하여 사용하였습니다.


차원 축소는 주성분분석 (Principle component analysis), 요인분석 (Factor analysis) 등과 같이 직교기저를 통해 선형적으로 나타내는 기초적인 기법이 대표적으로 많이 사용됩니다.


본 예제에서는 원래의 임베딩에 대한 군집을 보존하며 2차원으로 다시 임베딩시키기 위해 t-SNE (t-distributed Stochastic Neighbor Embedding)라는 비선형 차원축소 알고리듬이 사용되었습니다.


t-SNE는 이름에서와 같이 t분포(정확하게는 자유도가 1인 t분포)를 사용하는 것에서 유래되었는데요,


원래 데이터의 Gaussian 함수 형태로 만든 분포와, 축소된 차원에서 t분포 함수 형태로 만든 분포와 유사하게 만들고,(정확하게는 K-L 괴리도를 최소화하도록 최적화하여)


이를 통해 축소된 차원에서의 거리 관계가 원래 차원에서의 거리 관계와 유사하도록 고안한 차원 축소 알고리듬입니다.


cookbook example)


앞의 예제에서는 단어 임베딩이 hierarchy를 잘 포착하는 지에 대해 살펴봤는데,


이번 예제에서는 같은 hierarchy에 해당하는 단어들이 실제로도 유사하게 군집을 이루는지 시각적으로 확인하기 위해 t-SNE를 이용하여 차원축소한 후 살펴봅니다.


이를 위해 음료, 스포츠, 국가와 같은 세 개의 카테고리에 속하는 임의의 단어 군집을 만들고, normalization 후(데이터의 범위를 0과 1사이로 scaling) t-SNE를 적용하여 2차원으로 축소한 후 plot합니다.


beverages = ['espresso', 'beer', 'vodka', 'wine', 'cola', 'tea'] countries = ['Italy', 'Germany', 'Russia', 'France', 'USA', 'India'] sports = ['soccer', 'handball', 'hockey', 'cycling', 'basketball', 'cricket']

items = beverages + countries + sports


vectors = np.asarray([x[1] for x in item_vectors]) lengths = np.linalg.norm(vectors, axis=1) norm_vectors = (vectors.T / lengths).T // tsne = TSNE(n_components=2, perplexity=10, verbose=2).fit_transform(norm_vectors)


x=tsne[:,0]
y=tsne[:,1]

fig, ax = plt.subplots()
ax.scatter(x, y)

for item, x1, y1 in zip(item_vectors, x, y):
    ax.annotate(item[0], (x1, y1), size=14)

plt.show()


out>>>


[t-SNE] Computing 17 nearest neighbors...
[t-SNE] Indexed 18 samples in 0.001s...
[t-SNE] Computed neighbors for 18 samples in 0.009s...
[t-SNE] Computed conditional probabilities for sample 18 / 18
[t-SNE] Mean sigma: 0.581543
[t-SNE] Computed conditional probabilities in 0.020s
[t-SNE] Iteration 50: error = 60.7847748, gradient norm = 0.7886704 (50 iterations in 0.027s)
(...)
[t-SNE] Error after 1000 iterations: 0.097477


추가적인 군집화 알고리즘을 적용하진 않지만, 시각적으로 볼 때 각 군집이 원래의 의도대로 잘 형성된 것 같습니다.


즉, 유사한 hierarchy로 구분할 수 있는 것들이 실제 거리도 가깝게 위치해있는 것을 볼 수 있습니다.



- SVM Classifier


SVM (Support Vector Machine) 분류기는 인공신경망이 머신러닝의 대세(?)로 자리잡기 이전까지 가장 활발하게 연구되고, 실용적으로 많이 사용되었던 분류기법입니다.


가장 원시적인 형태의 SVM은 두 그룹이 초평면(hyperplane, 각 변수의 선형결합으로만 이루어진 곡면)으로 완전히 분리 가능한 경우에서 시작합니다.


이 문제는 각 그룹에서 두 그룹을 분리하는 초평면과 떨어진 거리들의 합이 가장 크도록하는 최적화 문제로 풀 수 있습니다.


완전히 분리 가능하지 않거나 선형으로 두 그룹을 분리하기 어려운 경우에도 여유 변수, 커널 트릭 등 변형된 최적화 문제를 풂으로써,


빠른 시간안에 기존의 나무 모델, 로지스틱 회귀 등에 비해 효과적인 분류 모델을 얻을 수 있다는 이점으로 인해 널리 쓰였습니다.


앞으로의 챕터에서는 본격적으로 인공신경망을 활용한 모형을 세우겠지만, 여기에서는 단순히 두 그룹을 분류하는 문제를 빠르게 풀기 위해 SVM 분류기를 만들었습니다.


cookbook example)


Chapter 4의 example에선 외부 영화 링크와 영화 목록들로 직접 임베딩을 생성하여 영화를 추천하는 모델을 만들었습니다.


여기서 추천 모델에 쓰인것이 SVM입니다. 평점이 좋은 그룹과 평점이 나쁜 그룹을 분류하는 모형을 간단한 선형 SVM을 통해 구축한 후,

이 분류기로부터 떨어진 거리와 부호에 따라 추천의 강도, 비추천의 강도를 산출하는 모델입니다.


best = ['Star Wars: The Force Awakens', 'The Martian (film)', 'Tangerine (film)', 'Straight Outta Compton (film)',
        'Brooklyn (film)', 'Carol (film)', 'Spotlight (film)']
worst = ['American Ultra', 'The Cobbler (2014 film)', 'Entourage (film)', 'Fantastic Four (2015 film)',
         'Get Hard', 'Hot Pursuit (2015 film)', 'Mortdecai (film)', 'Serena (2014 film)', 'Vacation (2015 film)']
y = np.asarray([1 for _ in best] + [0 for _ in worst]) 

X = np.asarray([normalized_movies[movie_to_idx[movie]] for movie in best + worst])



clf = svm.SVC(kernel='linear') 

clf.fit(X, y)


estimated_movie_ratings = clf.decision_function(normalized_movies)
best = np.argsort(estimated_movie_ratings)
print('best:')
for c in reversed(best[-5:]):
    print(c, movies[c][0], estimated_movie_ratings[c])

print('worst:')
for c in best[:5]: 

print(c, movies[c][0], estimated_movie_ratings[c])


out>>>


best:
307 Les Misérables (2012 film) 1.246511730519127
66 Skyfall 1.1888723752441601
481 The Devil Wears Prada (film) 1.1348285888204566
630 The Tree of Life (film) 1.1295026844583682
81 Birdman (film) 1.1121067681173762
worst:
9694 The Marine (film series) -1.6472428525072056
5097 Ready to Rumble -1.6412750149090598
8837 The Santa Clause (film series) -1.6391878640118387
1782 Scooby-Doo! WrestleMania Mystery -1.610221193972685
3188 Son of the Mask -1.6013579562623643


필연적으로 분류기에서 떨어진 거리가 그 강도를 의미하는것은 아니지만, 분류 경계에서 멀리 떨어져있는 만큼, 혹은 해당 그룹에 더욱 높은 output weight이 부가될 경우 더욱 해당 그룹에 속할 개연성이 높습니다.



Reference


Introduction to word embedding and word2vec

word2vec Parameter Learning Explained

deep learning cookbox (D.Osinga@github)




 


 본 포스팅은 느린생각 출판사에서 출시된 Douwe Osinga 저자의 Deep Learning Cookbook
번역본을 바탕으로 작성되었습니다.


 도서는 전국 오프라인 서점 및 yes24, 인터파크 등 온라인 서점 등에서 구입하실 수 있습니다.


 본 도서의 개발 환경은 리눅스에서 최적화되어있으며, 윈도우에서 원활하게 컴파일을 시연하기
위해서는 mingw64 등 리눅스 커널 응용프로그램을 돌릴 수 있는 환경이 권장됩니다.
(구체적인 설정 방법은 여기를 참조하세요.)