딥러닝 프레임워크로 임베딩 제대로 학습해보기

“gensim이 아닌 직접 딥러닝 네크워크 구조를 구현해 임베딩을 성공적으로 학습해본 경험이 있는지요?”

이 글은 네트워크 구조의 임베딩 학습을 숫하게 실패해본 분들을 위한 글이다.

많은 온라인 문서에서든 책에서든 word2vec을 설명하는 부분에서 딥러닝 프레임워크 기반 그래프 구조로 설명을 한다. 게다가 코드와 학습까지 Keras와 같은 프레임워크로 동작하는 예제를 제공하나, 추출된 단어 벡터를 기반으로 Word Analogy나 정성적인 평가에 대한 시도는 꼭 gensim과 같은 소프트웨어를 기반으로 학습한 벡터로 확인한다. 예를 들어 이러한 형태 말이다. 필자 역시 이런 저런 시도를 해본적이 있으나,과거 단 한번도 직접 네트워크를 만들어 gensim과 같은 퀄리티를 가진 임베딩을 생성해 본적이 없다.

최근에 큰 마음을 먹고 논문과 여러 글을 찾아본 결과 학습을 위한 중요한 몇가지가 빠져 있다는 사실을 발견했고, 이들 자료를 기반으로 Gluon으로 직접 활용 가능한 임베딩 학습 로직을 만들어 봤다.

이 글에서 Gluon기반의 NLP API1가 유용하게 사용되었다. 아마도 글을 보면서 그 간결함을 느껴보는 것도 좋을 것이라 생각된다.

늘상 해왔듯이 세종 말뭉치 기반 학습을 수행했으며, 마침 한글 임베딩에 대한 검증셋도 얼마전에 구할 수 있어 검증셋으로 임베딩 평가를 했다. 그 셋에 대한 설명은 아래에서 설명할 것이다.


임베딩 학습

예를 들어 아래와 같은 문장으로 Skip-Gram(중심단어를 기반으로 주변단어를 예측하는)학습을 시킨다고 가정해보자.

명절이 다가오면 주부들은 차례/제사상에 올릴 배와 사과 등 과일에 눈이 갈 수밖에 없다.

간단하게 하기 위해 위와 같은 문장에서 명사만 추출해서 학습한다.
“명절, 주부, 차례, 상, 배, 사과, 과일, 눈, 수”가 명사로 추출될 것이다. 윈도 사이즈를 2로 하면 Skip-Gram 학습셋은 아래와 같이 구성된다(1은 두 토큰이 윈도우 내에서 존재할때의 레이블 값이다).

명절, 주부, 차례, 상, 배, 사과, 과일, 눈, 수” -> (명절, 주부, (1)), (명절, 차례, (1))
명절, 주부, 차례, 상, 배, 사과, 과일, 눈, 수” -> (주부, 명절, (1)), (주부, 차례), (1), (주부, 상), (1)
명절, 주부, 차례, 상, 배, 사과, 과일, 눈, 수” -> (차례, 명절, (1)), (차례, 명절, (1)), (차례, 상, (1)), (차례, 배, (1))

“명절, 주부, 차례, 상, 배, 사과, 과일, 눈, 수” -> (사과, 상, (1)), (사과, 배, (1)), (사과, 과일, (1)), (사과, 눈, (1))

(사과, 과일)이라는 셋이 네트워크에서 학습되는 과정은 아래와 같이 내적계산을 기반으로 수행된다.

 

학습이 수행됨에 따라 $ W $값은 두 토큰이 유사한 방향으로 변화될 것이다. 이 방식이 개략적인 Skip-Gram 기반의 임베딩 학습 방법이다.
실제 임베딩의 학습은 위와 같은 셋으로 입력되지 않고 아래와 같은 형태로 입력된다.
(사과, (상, 배, 과일, 눈, 고양이, 사자, 가을, 눈사람), (1,1,1,1,0,0,0,0))
(중심단어, 주변단어, 정답)순이며, 주변단어에는 확률적으로 샘플링한 비 주변단어들이 함께 학습된다.
실제 학습 원리는 아래 수식과 같다.
$$ P(o|c)=\frac { exp({ u }_{ o }^{ T }{ v }_{ c }) }{ \sum _{ w=1 }^{ W }{ exp({ u }_{ w }^{ T }{ v }_{ c }) } } $$
$P(o|c)$는 중심단어가 주어졌을때 주변 단어가 나올 확률값이며 이를 최대화 하는게 학습의 목표가 된다. 확률을 최대화 하기 위해서는 $v_c$(중심단어에 대한 $W$의 벡터)와 $u_o$(주변단어에 대한 $W’$의 벡터)의 내적을 최대화 하는 방향으로의 임베딩 행렬이 업데이트 되어야 되며, 또한 분모의 값을 감소시키기 위해서 비 주변단어($u_w$)와의 유사도는 감소되어야 된다. 분모에서 모든 비 주변단어를 고려하는건 계산 리소스 낭비가 심하기 때문에 확률적인 샘플링을 통해 일정수의 비 주변단어를 함께 학습하는 방향으로 구현이 된다(이를 네거티브 샘플링(negative sampling)이라 한다).
또한 자주 출현하는 단어들에 대해서 학습셋에 포함되는 확률을 줄여주기 위한 서브샘플링(subsampling)기법을 통해 학습의 효율을 획기적으로 올리는 기법도 사용된다. 이를 통해 학습 데이터 자체를 줄여주어 학습속도를 올릴 수 있게 된다.
사실 일반적인 Skip-Gram을 학습하기 위해서는 네트워크 구조만큼이나 위 트릭들이 중요하며, 제대로된 임베딩 생성 여부를 판가름한다. 대부분의 word2vec 예제들 (특히 Keras 기반)의 재현이 잘 안되는건 이 때문이다.

그럼 word2vec을 학습하는 코드를 작성해보자. 역시 MXNet Gluon으로 작업했다.

이제부터는 핵심적인 코드 설명만 할 생각이다. 동작하는 전체 코드는 이곳에서 찾아볼 수 있다.

>> context_sampler = nlp.data.ContextSampler(coded=coded_dataset, batch_size=2048, window=5)
>> negatives_weights = nd.array([counter[w] for w in vocab.idx_to_token])
>> negatives_sampler = nlp.data.UnigramCandidateSampler(negatives_weights)

ContextSampler는 Gluon NLP에서 제공되는 API로 입력된 데이터셋을 기반으로 배치를 만들어주는역할을 하며 주어진 윈도우 크기를 기반으로 주변단어와 주변단어 길이에 따른 마스킹을 중심단어와 함께 생성해준다. UnigramCandidateSampler는 우리가 구한 단어 빈도를 기반으로 샘플링을 수행하는 API이다. 네거티브 샘플들은 빈도에 기반해서 무작위로 생성되게 된다.

또한 자주 출현하는 단어들에 대해서 학습셋에 포함되는 확률을 줄여주기 위한 서브샘플링(subsampling)기법을 통해 학습의 효율을 획기적으로 올리는 기법도 사용된다. 이를 통해 학습 데이터 자체를 줄여주어 학습속도를 올릴 수 있게 된다.

서브샘플링 로직은 아래 수식과 같은 방식으로 샘플링 확률이 결정된다.

$$ P({ w }_{ i })=1-\sqrt { \frac { t }{ f({ w }_{ i }) } } $$

$ P(w_i) $가 작아야 학습셋에 들어갈 확률이 높아지는데, 여기서 $f(w_i)$는 단어의 출현 확률을 의미한다. 따라서 출현 확률이 낮은 단어일 수록 학습셋에 포함될 확률이 높아진다.

>> frequent_token_subsampling = 1E-4
>> idx_to_counts = np.array([counter[w] for w in vocab.idx_to_token])
>> f = idx_to_counts / np.sum(idx_to_counts)
>> idx_to_pdiscard = 1 - np.sqrt(frequent_token_subsampling / f)
>> coded_dataset = [[vocab[token] for token in sentence
                     if token in vocab
                     and random.uniform(0, 1) > idx_to_pdiscard[vocab[token]]] 
                     for sentence in sejong_dataset]

 

데이터와 전처리

학습에 쓰인 데이터는 세종코퍼스약 9만 4천 문장이며, Gluon NLP의 API를 통해 Vocab 객체로 변환한다. 토크나이저는 KoNLPy의 mecab을 사용했다.

>> sejong_dataset = nlp.data.dataset.CorpusDataset('data/training_corpus_sejong_2017_test_U8_norm.txt', 
>>                                 tokenizer=lambda x:mecab.morphs(x.strip()))
>> counter = nlp.data.count_tokens(itertools.chain.from_iterable(sejong_dataset))
>> 
>> vocab = nlp.Vocab(counter, unknown_token='<unk>', padding_token=None,
                  bos_token=None, eos_token=None, min_freq=5)

 

모델

먼저 두개의 임베딩 레이어를 선언해준다. 이 레이어에서 생성된 가중치가 결국 우리가 원하던 word2vec 결과물이된다.

>> class embedding_model(nn.Block):
>>     def __init__(self, input_dim, output_dim, neg_weight, num_neg=5):
>>         super(embedding_model, self).__init__()
>>         self.num_neg = num_neg
>>         self.negatives_sampler = nlp.data.UnigramCandidateSampler(neg_weight)
>>         with self.name_scope():
>>             #center word embedding 
>>             self.w  = nn.Embedding(input_dim, output_dim)
>>             #context words embedding 
>>             self.w_ = nn.Embedding(input_dim, output_dim)
>>     
>>     def forward(self, center, context, context_mask):
>>         #이렇게 해주면 
>>         #nd.array를 선언시 디바이스를 지정하지 않아도 된다. 
>>         #멀티 GPU 학습시 필수 
>>         with center.context:
>>             #주변단어의 self.num_neg 배수 만큼 비 주변단어를 생성한다.  
>>             negs = self.negatives_sampler((context.shape[0], context.shape[1] * self.num_neg))
>>             negs = negs.as_in_context(center.context)
>>             context_negs = nd.concat(context, negs, dim=1)
>>             embed_c = self.w(center)
>>             #(n_batch, context_length, embedding_vector)
>>             embed_u = self.w_(context_negs)
>> 
>>             #컨텍스트 마스크의 크기를 self.num_neg 만큼 복제해 값이 있는 영역을 표현한다.
>>             #결국 주어진 주변단어 수 * self.num_neg 만큼만 학습을 하게 된다. 
>>             context_neg_mask = context_mask.tile((1, 1 + self.num_neg))
>> 
>>             #(n_batch, 1 , embedding_vector) * (n_batch, embedding_vector, context_length)
>>             #(n_batch, 1, context_length)
>>             pred = nd.batch_dot(embed_c, embed_u.transpose((0,2,1)))
>>             pred = pred.squeeze() * context_neg_mask
>>             
>>             #네거티브 샘플들은 레이블이 모두 0이다. 
>>             label = nd.concat(context_mask, nd.zeros_like(negs), dim=1)
>>         return pred, label

코드에서 특징적인 부분은 비 중심단어 생성을 위해 네거티브 샘플러를 쓰는 부분이다. Gluon NLP에서 잘 구현이 되어 있으니 감사히 쓸 수 밖에…

나머지 부분은 주석과 공식 API문서를 기반으로 생각해보면 큰 무리없이 이해가 가능할 것이다.

평가

임베딩 네트워크를 평가하는 방법은 사람이 스코어링한 단어의 관계 데이터를 기반으로 수행된다. 아쉽게도 지금까지 한글 임베딩에 대한 평가셋이 존재하지 않았으나, 최근 ACL Paper2에서 연구 데이터3를 공개해 간편하게 평가해 볼 수 있게 되었다.

정답셋에 존재하는 단어쌍의 스코어가 우리가 만든 word2vec의 단어 유사도와 순위가 얼마나 같은지 Spearman Rank Correlation4으로 평가한다.

>> import pandas as pd
>> 
>> wv_golden = pd.read_csv('data/WS353_korean.csv')
>> 
>> word1 = wv_golden['word 1'] 
>> word2 = wv_golden['word 2']
>> score = wv_golden['kor_score']
>> 
>> res = [[vocab.token_to_idx[i],vocab.token_to_idx[j],k] for i,j,k in zip(word1, word2, score) 
>>        if vocab.token_to_idx[i] != 0 and vocab.token_to_idx[j] != 0]
>> 
>> word12score = nd.array(res, ctx=ctx)
>> 
>> word1, word2, scores = (word12score[:,0], word12score[:,1], word12score[:,2])
>>
>> def pearson_correlation(w2v, word1, word2, scores):
>>     from scipy import stats
>>     evaluator = nlp.embedding.evaluation.WordEmbeddingSimilarity(
>>         idx_to_vec=w2v,
>>         similarity_function="CosineSimilarity")
>>     evaluator.initialize(ctx=ctx)
>>     evaluator.hybridize()
>>     pred = evaluator(word1, word2)
>>     scorr = stats.spearmanr(pred.asnumpy(), scores.asnumpy())
>>     return(scorr)

Gluon은 임베딩의 워드 유사도를 계산해주는 함수를 제공하고 있고, 이 함수를 통해서 빠르고 간단하게 계산이 가능하다. 위 함수를 매 에폭마다 수행하게해 성능 향상 여부를 확인한다.

학습

>> from tqdm import tqdm
>> 
>> ctx = mx.gpu()
>> 
>> num_negs = 5
>> vocab_size = len(vocab.idx_to_token)
>> vec_size = 100
>> 
>> embed = embedding_model(vocab_size, vec_size, negatives_weights, 5)
>> embed.initialize(mx.init.Xavier(), ctx=ctx)
>> 
>> loss = gluon.loss.SigmoidBinaryCrossEntropyLoss()
>> optimizer = gluon.Trainer(embed.collect_params(), 'adam', {'learning_rate':0.001})
>> 
>> avg_loss = []
>> corrs = []
>> interval = 50
>> 
>> epoch = 70
>> 
>> for e in range(epoch):    
>>     for i, batch in enumerate(tqdm(context_sampler)):
>>         center, context, context_mask  = [d.as_in_context(ctx) for d in batch]
>>         with autograd.record():
>>             pred, label = embed(center, context, context_mask)
>>             loss_val = loss(pred, label)
>>         loss_val.backward()
>>         optimizer.step(center.shape[0])
>>         avg_loss.append(loss_val.mean().asscalar())
>>     
>>     corr = pearson_correlation(embed.w.weight.data(), word1, word2, scores)
>>     corrs.append(corr.correlation)
>>     print("{} epoch, loss {}, corr".format(e + 1, loss_val.mean().asscalar()), corr.correlation)

코드는 학습 레이블이 forward()함수에서 생성된다는 것을 제외하고는 일반적인 학습 로직과 같다. 많이 쓰이는 Adam 옵티마이저를 사용하고 약 70 에폭을 학습했다.

학습 중 매 에폭마다의 상관관계를 시각화한 결과이며, 예상대로 상승하는 추세이며 약 0.5의 상관관계까지 상승하는 것을 알 수 있다.

논문에서 훨씬 더 많은 학습셋과 Skip-Gram으로 약 0.59의 상관관계까지 도출된 것에 비교하면 어느정도 만족할 만한 수준의 임베딩이 학습된 것을 알 수 있다.


전체 코드와 데이터는 이곳에서 확인할 수 있다.

References

  • https://arxiv.org/abs/1301.3781
  • http://gluon-nlp.mxnet.io/examples/word_embedding/word_embedding_training.html

AI, 빅 데이터 연합 동아리 컨퍼런스 자문 후기

회사 HR의 요청으로 6월 28일 연세대에서 있었던 빅데이터, AI 연합 동아리 컨퍼런스 자문에 참석 중 받았던 질문을 늦었지만 정리해보고자 한다. 바로 정리하려 했으나 개인적인 용무로 좀 늦었고 지금 기억나는 주된 질문 몇가지만 정리한다. 이렇게 정리를 하는 이유는 타 대학에서 관련 연구를 하시는 분들과 정보격차를 해소해드리기 위함과 개인적으로는 추후 비슷한 질문이 나올시 좀더 일관성 있는 답변을 하기 위함이다.

곧 서울대에도 갈 예정(?)인데, 그곳에서는 어떠한 질문이 나올지 궁금하다.

 

Text Analysis Developers’ Workshop 2018 참석 후기

작년부터 1년엔 한번씩 Text Analysis Developers’ Workshop에 참석을 하게 되었고 작년 런던 정경대에서의 워크샵 참석 이후 NYU의 워크샵에 다시 초대되었다. 워크샵 참석을 위한 숙박비 및 비행티켓 등은 NYU와 rOpenSci에서 펀딩을 받았다. 기간동안의 일비, 로밍 비용은 SK Telecom에서 지원해주었다.

세계적으로 많이 쓰이는 텍스트 분석 오픈소스 개발자들을 대상으로 초대가 이루어 졌고, 초청받은 사람만 참석 가능한 특징을 가지고 있다.


워크샵 Schedule

Where: Center for Data Science, New York University, 60 5th Avenue, Room 150 this map.

Day 1

  • 9-10:00 Introductions (Everyone introduces him/herself, describes package(s), objectives and interests). Max 4-5 mins each, including one slide added to the Google Slides intro slide deck. (Link via email.)
  • 10:00-11:00 Roundtable 1: NLP
  • 11:00-11:15 Coffee break
  • 11:15-12:00 Roundtable 2: User’s Corner
  • 12:00-12:45 Roundtable 3: Interoperability
  • 12:45-13:30 Lunch
  • 13:30-14:00 Prep for hackathon issues
  • 14:00-15:30 Working sessions/hackathon
  • 15:30-15:45 Coffee break
  • 15:45-17:00 Working sessions/hackathon
  • 17:00-18:00 Roundtable 4: Deep Learning
  • 18:30- Dinner on site

Day 2

  • 9:30-10:00 from future import (Everyone shares what they are working on now or in the near future, outside the workshop).
  • 10:00-10:15 Coffee break
  • 10:15-11:00 Demos
  • 11:00-12:00 Sneak previews: Work in progress
  • 12:00-12:45 Lunch
  • 12:45-13:30 Wish lists
  • 13:30-14:30 Working sessions/hackathon
  • 14:30-14:45 Coffee break
  • 14:45-15:45 Working sessions/hackathon
  • 15:45-17:30 Working groups present results, identify future goals, etc.

첫날

워크샵에서 해결하고 싶었던 것은 어떻게 하면 딥러닝 모형을 좀더 자연스럽게 사용자들이 사용할 수 있는 패키지에 탑재 할 수 있는지 어떠한 힌트 혹은 해결책을 알고 싶었다. 배경 지식을 위해 Model as a Program이라는 개념을 설명하자면, 현재 많은 소프트웨어가 딥러닝이나 머신러닝 모델을 탑재하고 있고 앞으로 이 빈도는 더 늘어날 것으로 보고 있다. 이는 모형의 성능이 사람이 직접 코딩한 로직보다 훨씬 좋은 성능을 보이기 때문인데, 이렇게 Model이 프로그램의 핵심적인 동작을 관활하게 만들어지는 소프트웨어를 Model as a Program이라 한다. 나는 이 Model as a Program을 어떻게 하면 여러 소프트웨어에 의존하지 않고 만들 수 있을지에 대한 고민을 안고 워크샵에 참석하게 되었다. 하지만 지금과 같이 다양한 딥러닝 프레임웍과 빠른 변화 속에서 안정적으로 소프트웨어를 유지하는건 작은 소망에 가깝다는 생각도 마음 한켠에 간직하고 참석을 하였다.

궁극적인 Model as a Program(from Deep Learning with Python)

이러한 연유 때문에 워크샵 일정 시작 이전부터 KoSpacing을 최대한 정리해서 설치 가능하게 Github에 올려두었으며, 워크샵 시작 전날에 Python 버전의 PyKoSpacing을 공개했다. 이 작업은 뉴욕에서의 가족여행일정을 시작하면서 작업을 시작해 워크샵 시작 직전에 마무리가 되었다.

KoSpacingPyKoSpacing을 간단하게 설명하면, 자동 한글 띄어쓰기 패키지이다. 한글은 같은 문장에 대해서 다양한 띄어쓰기가 가능하며, 정확한 띄어쓰기 여부는 이후 형태소 분석 등의 분석 작업에 지대한 영향을 끼치며, 잘못된 띄어쓰기는 문장이 전혀 다른 의미로 의도되게 만들기도 한다. 이러한 이슈 때문에 많은 한글 텍스트 분석 패키지는 암암리에 형태소분석 혹은 명사 추출과 같은 작업 이전에 자동띄어쓰기 모듈을 동작하게 하나, 이들의 성능이 그렇기 좋지 않았던 상황이었다. 이 패키지는 필자가 만든 첫 Model as a Program으로 대부분의 로직은 딥러닝 모델이 커버하고 있다. 그리고 유일한 공개된 한글 띄어쓰기 엔진이다.

아침에 일어나 워크샵 장소인 NYC의 Center of Data Science로 향했다. NYC에서 잡아준 Sheraton Tribeca Hotel을 나와 5 Av/w 20 St 역에 도착했다. 뉴욕의 아침은 전날의 흐린 날씨와는 다르게 맑은 가을 날씨를 뽐내고 있었다.

역에서 NYU 에 가는길

학교 건물 옆에 있던 First Presbyterian Church.

학교건물의 측면, 캠퍼스가 전무함…

뉴욕의 대학은 캠퍼스가 거의 없고, 건물로 개별 단과대가 나뉘어 있다는게 좀 신기했다. 그만큼 땅값이 비싼 곳이라서 그럴 것이지만, 다소 낭만과 여유가 없어 보이는건 사실이다.

워크샵 시작 직전 강의실 전경. NYC의 펀딩을 주도했던 페트릭 교수가 강단에서 누군가와 이야기하는 중이다.

강의장에 좀 일찍 도착해서 자리를 잡고 사람들이 오길 기다렸다. 마침 rOpenSci의 제롬이 도착하는 모습을 보고 안부를 물은 뒤 한국에서 부탁받은 10월말 한국에서 열리는 R컨퍼런스 참석 여부를 물어보았다. 일단 스케줄 보고 알려주겠단다. 제롬은 rOpenSci의 핵심 개발자로 rOpenSci의 철학을 몸소 실천하고 있는 사람중에 하나로 한국에서 rOpenSci에 대한 관심이 높아져 초청을 진행하려 했다.

물병을 기념품으로 줬는데, 이야기하느라 챙길 여유가 없었다.

몇몇 작년 CJK에 대한 논의를 같이 했던 코헤이아키와 인사 및 안부 작년 진행했던 한국어 분석기를 ICU 컨소시엄에 컨트리뷰트 하는 이야기를 잠시 하다가 워크샵 시작 시간이 되어 자리에 앉았다.

첫 소개 세션에서 각기 준비한 1장의 슬라이드를 기반으로 자신이 관심 있는 영역과 개발한 패키지 그리고 이 워크샵에서 관심있어 하는 주제들에 대해서 이야기 했다.

 

내 소개 자료

이번 워크샵은 작년과 다소 다르게 Python영역의 분들이 대거 참여하였다. 따라서 머신러닝이나 딥러닝 관련 분들의 비율이 대거 높아졌으며, 특히나 scikit-learn 개발자와 spaCy 개발자가 참여하고 있는건 큰 변화였고, 실제 한글관련해서 spaCy와 이야기한 부분도 이후 소개를 할 예정이다.

사실 전통적인 텍스트 분석의 목적인 사회과학에서의 연구와 매우 가깝다. 따라서 이들이 딥러닝과 같은 기술을 쓰기에도 적합하지 않고 그러기 때문에 리서치를 하기가 쉽지 않은데, 이 부분에 대한 차이가 2일자의 라운드테이블 논의에서 부각되었다.

사실 NLP 라운드테이블 논의에서는 어떠한 도메인(언어, 뉴스 등)에서 POS Tagging이나 Sentiment Analysis 등의 기술들이 사용되고 있고 거의 정답에 가깝게 성능이 발현되었는지와 사용자들이 이들 기술에 대해서 필요로 하는 기능이 무엇인지에 대해서 논의했다. 이 부분에 대해서 재밋었던 부분은 불용어에 대한 부분이었다. 불용어 리스트를 소프트웨어에서 제공하는게 맞는지와 아닌지, 그리고 그 수준은 어떠해야 되는지 등의 논의였는데, 과연 불용어의 정의가 무엇인지 명확한 기준이 있어야 된다는 사실에 모두 공감했다. “불용어에 대한 국제적 표준을 마렵합시다!”라는 이야기까지 나왔다.

첫번째 NLP 라운드테이블이 끝난 뒤 이 워크샵에서 꼭 뵙고 싶었던 조경현 교수님과 이런 저런 이야기를 했다. 특히 관심있는 소프트웨어적인 딥러닝 프레임웍간 포맷 전환 문제와 이를 손쉽게 소프트웨어화 하는 방식에 대한 질문에 깊이 공감했고, 몇가지 관련 소프트웨어가 개발되고 있다는 이야기(ONNX)와 실제 자신의 연구에서는 이 부분때문에 과거 논문 결과에 대한 재현을 하기 위해 많은 개발을 별도로 하고 있다는 이야기까지 했다. 논문 연구를 하는데에도 기존 연구 결과를 재현하고자 많은 개발이 진행되고 있다는 사실에 생각보다 놀랐고, 고민의 수준도 예상을 상회해다. 특히나 zeroMQ와 같은 인터페이싱 프레임웍을 사이에 두고 각기 딥러닝 프레임웍에 대한 공통적인 인터페이스를 하게 하면 어떨까 하는 교수님의 코멘트는 교수가 아닌 개발자와 이야기한다는 느낌까지 들게 할 정도로 재미있는 코멘트였다. 사실 조경현 교수님은 구글과도 많은 개발 협업을 하고 있는 개발자이자 연구자였다.

사실 이 시점부터 현 워크샵이 내가 가고자 하는 방향과 다소 차이가 있다는 생각을 하게되었는데, 이 차이는 뒤이은 딥러닝 라운드테이블 논의에서 극화 되었다.

Interoperability 라운드테이블에서는 이 부분의 해석에 대해서는 매우 많은 해석이 가능해서 다양한 논의가 나올줄 예상했으나 그렇지 않았다. 모델 프레임웍간의 상호 변환이 호환에 대한 이야기에 대한 좀더 활발한 논의가 있었으면 했는데, 코퍼스, 단어 여기서 조금 더 나아가서 워드임베딩의 공통 포멧에 대한 논의가 있어졌고, 워드임베딩의 공통 포맷에 대한 이야기를 소주제 논의 항목으로 살려갈 수 있었다. 워드임베딩의 공통 포맷은 현재 다양한 패키지에서 개발된 포맷을 하나의 포맷으로 정의하고자 하는 논의였고, 이 부분은 러시아에서 온 드미트리가 주도해서 진행했다. 드미트리는 러시아에서 꽤 유명한 개발자로 작년에 이어 올해도 아내와 방문했으며, 여전히 극비의 스타트업에서 일하고 있다.

오후엔 아래와 같은 방식으로 소주제 영역으로 나뉘어서 진행이 되었다.

모임의 주최자인 켄 교수가 소주제를 잡고 있다.

내가 속한 주제영역은 Internalization 부분이었다. 이 소주제에 포함된 사람은 코헤이와 프린스턴 대학의 윌 교수였는데, 가장 먼저 아라빅 언어의 POS Tagging 결과의 방향 문제에 대해서 이슈가 있었으나, 이 부분은 역시나 R과 윈도우의 오묘한 인코딩 이슈때문인 것으로 판단되어 일단은 적당한 후처리 이후 진행이 되는것으로 이야기 되었다. 여기서 이미 R에서 인코딩 관련 이슈에 대해서 다소 늦었지만 코어 내부에서 논의가 진행되고 있다는 사실을 알았다. 게다가 이 영역이 RStudio까지 연결되어 문제가 커져 역시나 Internalization의 시작과 끝은 다시금 인코딩 이슈라는 걸 실감했다. 이 시점에 코헤이의 요청으로 내가 개발한 KoSpacing에 대한 소개를 하였으며, 일본어와는 다르게 한글에서 이 전처리 영역이 매우 중요하다는 사실을 소개하고 KoSpacing으로 해결 가능하다는 것을 인지시킬 수 있었다. 사실 일본어나 중국어에서는 띄어쓰기 문제가 크지 않고 한국어 특유의 이슈다.

임베딩에 대한 논의를 하고 있는 리치몬드 대학의 테일러.

최근 CRAN에 올라온 RmecabKo에 대한 이슈도 논의 대상이 되었는데, 이 부분에서 우리 한국어 전문 처리 패키지들이 왜 발전이 더디고 지속적으로 관리되고 있지 않는지 많은 고민을 하게 하였다. 사실 KoSpacing도 마찬가지 문제를 가지고 있었는데, 주제 영역에서는 왜 패키지 이름에 Ko를 붙이느냐는 코헤이의 질문이 그 고민의 시작이었다. 그리고 그 고민은 왜 KoNLP를 개발하고 유지한지 5년이 넘었는데로 단 한번의 pull request만을 받았는지 그 이유를 설명하기 충분했다. 고헤이의 제안으로 필라델피아에 거주하는 RmecabKo개발자 분에게 연락해 혹시 일본어를 지원 대상에 넣을 수 있을지 문의했고, 다행히 긍정적인 답변을 받을 수 있었다. 한국어 뿐만 아니라 다양한 언어를 지원하는 오픈소스 소프트웨어가 좀더 많은 개발자들이 오픈소스에 기여하기 만들 수 있으며, 이렇게 되면 단순히 한국어만 지원되던 오픈소스 소프트웨어보다 생명력은 좀더 길어지게 될 것이다. 여튼 RmecabKo 개발자인 김준혁씨가 매우 긍정적으로 이 방향에 대해서 생각하고 있다고 맴버들에게 알려줄 수 있었다.

첫날 하루를 마치며 한국어 뿐만 아니라 다은 언어를 포함하는 프레임웍을 구상하여 오픈소스화 하는것이 NLP관련 오픈소스 개발을 하는데 훨씬 생명력을 길게 유지하는 방향이라는 조언이 기억에 남는다. 혼자하는 것보다 나은건 큰 판에 참여하는 것이고 참여보다 더 나은건 다른이들이 쉽게 참여할 수 있는 판을 만드는것이다. 라는 조언은 지금까지 내가 힘들게 오픈소스를 해오던일이 왜 그렇게 어려웠는지에 대한 이유를 알려주기 충분했다. 한국에 돌아가서 이 부분에 대해서 고민을 좀더 해보고 사람들을 모아서 같이 큰 판에 참여를 해볼 수 있는 포인트를 마련해 봐야겠다. 사실 그 포인트라는건 상당히 많다. 딥러닝이든 아니면 고전적인 텍스트 분석 도구이든 한국어는 모두 취약한 상황이니까.

둘째날

이틀차는 첫날 1층에서 주로 시행된거와는 다르게 NYU Center of Data Science 7층에서 시작되었다. 첫날과 비슷한 과정이나 딥러닝 라운드 테이블 세션이 있었고, 내가 원하는 이야기와 질문들이 활발하게 나오길 기대했다.

텍스트 분석을 위해서 딥러닝을 어떻게 활용해야 되는가? 라는 질문에 딥러닝이 블랙박스여서 분석을 하기위한 도구로는 사회과학 연구에서 사용하기 어렵다라는 의견이 지배적이었다. 이 의견에 패널로 올라론 조경현 교수는 이미 딥러닝 도구의 예측성능이 좋다라는 이야기를 하며 이런 도구의 내재된 기재를 연구하는게 사회과학 연구자들이 취해야 되는 자세가 아닐까 하는 이야기를 했다. 또 한명의 패널인 세일즈포스 엔지니어인 제임스의 코멘트가 자못 흥미로웠는데, “이미 세상은 딥러닝으로 차츰 돌아가고 있는데, 사회과학 연구자가 딥러닝을 연구하지 않는건 이상한것 같다.”라는 코멘트는 연구자들에게 큰 부담으로 다가왔을 것이라는 생각을 해본다.

사회연구에 토픽모델이 적용되기 시작한지도 불과 얼마 되지 않았는데, 딥러닝이야 아직은 시기상조라는 생각이 들었고, 워크샵에 참여한 대부분의 사회과학 연구자들도 비슷한 느낌을 주는 코멘트를 하였다. 이러한 내용이 오가면서 다소 내가 가지고 있던 워크샵에 대한 질문이 얼마나 앞으로 나아간 질문인지 깨달을 수 있었고, 답변을 얻기 어려울 수 있다는 생각을 하게 되었는데, 이 시간 이후부터는 개별 전문가에게 직접적인 조언이나 의견을 구하는 방향으로 워크샵 참여 전략을 바꾸게 되었다.

가장 먼저 제임스에게 현재 가지고 있던 문제점을 이야기하고 좋은 해결책이 없는지 문의하였으나, 그 친구 역시 뾰족한 해결책을 주지 못했다. 대신 spaCy의 딥러닝 프레임웍을 사용하고 그곳에 모형을 올려두면 절대 모형이 작동하지 않는 순간은 오지 않을거라고 이야기했다. 그래서 spaCy개발자 메튜를 찾아가서 딥러닝 프레임웍이 Thinc에 대한 이야기를 시작했다.

한국어, 일본어 NLP 학습셋에 대해서 논의중인 spaCy의 핵심 개발자인 메튜

spaCy에는 기본적으로 POS Tagger나 개체명 인식기 등의 모델에 딥러닝으로 학습된 모형이 기본적으로 탑재되어 있다. 물론 지원하는 언어가 8개 내외로 그렇게 많지 않으나, 아무래도 NLP 영역에서 딥러닝이 매우 좋은 성능을 보이고 있으니 이러한 딥러닝 프레임웍 탑재는 당연한게 아닐까 하는 생각을 했다. 타 패키지와는 다르게 spaCy는 자체 구현된 딥러닝 프레임웍이 존재하고 이를 Thinc(씽크)라고 한다. 딥러닝으로 학습된 NLP 모형을 안정적으로 지원하는게 씽크의 가장 큰 목적이기 때문에 타 딥러닝 프레임웍처럼 버전업이 되면서 기존 모형이 돌아가지 않거나 하지 않고 딥러닝 프레임웍 자체가 spaCy에 잘 녹아들어 있기 때문에 둘간의 디펜던시가 깨져 시간이 지나 모형이 동작하지 않게 되는 경우를 방지할 수 있다는 설명 등을 메튜가 자세히 해주었다. 사실 내가 가지고 있는 딥러닝 모형(Keras, MXNet)을 다시 재학습 시켜야 된다는 부담감이 있으나 복잡한 딥러닝 모델을 소프트웨어 내부에 가지고 있고 이를 안정적으로 유지하기 위한 노력을 최소화 하기 위해서는 spaCy는 가장 이상적인 패키지가 아닐까 하는 생각을 했다.

이러한 설명 이후 메튜가 한국어 학습결과가 잘 나오지 않는다는 이슈를 제기해서 함께 이슈를 파헤쳐보기 시작했다. 학습셋을 살펴보고, 한국어 학습이 잘 안되는 이유는 사실 학습셋 자체가 잘못되었기 때문이라고 설명해 주었다. 예를 들어 “학교에”라는 어절을 “학교”, “에”로 파싱 후 개별 단어에 명사, 조사라는 태그를 달아줘야 되는데 “학교에”를 하나의 형태소로 설정해 학습을 시킨것이다. 메튜와 원천 학습 데이터를 확인했을때 해당 정보가 있었음에도 불구하고 한국어에 대한 사전지식이 없어서 위와 같은 학습셋으로 잘못 전처리해 학습을 시킨것이었다. 다행히 빨리 원인을 찾았고, 개체명인식의 경우 내가 가지고 있는 학습셋이 spaCy에 적용 가능한지 같이 확인하고 학습하는 방법을 간단하게 메튜가 소개해 주는 시간을 가졌다. 그리고 한국에 가서 한국어 관련 spaCy 모형을 컨트리뷰트 해보도록 하겠다는 약속을 하고 메튜와의 미팅을 마치게 되었다.

NYU의 조경현 교수님과…

이후 첫날에 이어 GRU로 유명한 조경현 교수님과 이런 저런 이야기를 나눴다. SKT에서 어떠한 모형을 딥러닝으로 구축하고 있는지 궁금해 하셔서 어떻게 일하고 있고 현재 어떠한 변화가 되고 있는지 이야기를 했다. 딥러닝 때문에 하는 일과 패턴이 상당히 많이 달라지고 있다는 이야기에는 깊이 공감하는 분위기였다. 6월에 한국에 오시면 한번 뵙기로 하고 교수님은 학생과의 미팅때문에 다시 사무실로 향하셨다.

 


마치며

작년과는 다르게 Python 영역의 개발자들이 대거 참여를 하면서 자연스래 딥러닝에 대한 논의가 나오게 된건 매우 고무적이었다. 하지만 기존 R기반의 연구자들이 사회과학에 대한 연구를 주로하시는 분들이어 다소 두 영역간의 격차는 사용하는 랭귀지 이상으로 컸다고 생각한다. 그리고 SKT에서 내가 고민하고 있는 내용들이 생각보다 매우 발전적이고 앞서나간 고민이라는 것도 새삼 느끼게 되었다.

무엇보다 오픈소스 개발을 6년 넘게 해오면서 좀더 많은 참여자를 만들기 위해 전략적으로 움직여야겠다는 생각을 해본다. 좀더 좋은 판에서 기여를 하고 더 나아가서 스스로 좋은 판을 만들기 위해 노력해야겠다는 생각을 해본다.

한국에 가서 해야될 일들을 정리해 보자면 아래와 같다.

  1. spaCy에 한국어 딥러닝 모델 추가
  2. 불용어 사전 정리 및 공유
  3. 위의 작업을 체계적으로 같이 해볼 계획 만들기…
  4. 딥러닝은 더더욱 열심히…