Keras Embedding은 word2vec이 아니다

김희규
7 min readJul 22, 2020

--

이걸 이제야 깨닫다니… 나는 여지껏 NLP Task 등에서 Embedding 레이어를 쓰면서 당연히 word2vec이라고 생각을 하고있었다. 하지만 어디에서도 Embedding레이어가 word2vec이라고 말한 적은 없다…! 이 글은 깨달은 기념으로 다시 정리하는 word2vec과 그럼 Embedding 레이어는 뭘까 파헤쳐본 기록이다. 개념요약식으로 간단히 넘어가고 자세한 건 참고할 링크를 달도록 하겠음.

임베딩

임베딩은 어떤 데이터를 벡터로 바꾸는 것이다. 벡터로 바꾸므로 산술 연산이 가능해지고 머신러닝에 활용할 수 있다. 예를 들면

사과 = [0.13, -4.22]
딸기 = [1.33, 0.125]

단어를 위처럼 2차원 벡터로 나타내는 것이다.

원-핫

10개의 단어가 있다면, 단어마다 순서를 정하고 10차원의 벡터를 만든뒤 그 단어의 순서에 해당하는 원소만 1이고 나머지는 0인 벡터이다.

만약 '사과'가 10개의 단어 중 3번째 단어라면,
사과 = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
만약 '딸기'가 10개의 단어 중 6번째 단어라면,
딸기 = [0, 0, 0, 0, 0, 1, 0, 0, 0, 0]

무작위 임베딩

무작위 임베딩은 말그대로 단어마다 특정 차원의 랜덤값으로 채워진 벡터로 임베딩을 부여하는 것이다.

통계기반 임베딩

TF(Term Frequency)는 문장을 벡터로 임베딩하는 기법으로 문장 내에 특정 단어가 등장한 횟수를 원소로 갖는다 예를들어

나는 정말 정말 맛있는 사과를 먹었다.

문장이 있을 때 위 문장은

순서대로 [나는, 정말, 맛있는, 사과를, 딸기를, 먹었다] 단어를 의미
u = [1, 2, 1, 1, 0, 1]

단어의 종류 수가 벡터의 차원수가 되고, 단어가 문장 내에서 등장한 횟수가 해당 단어의 위치의 값이 된다. 텍스트는 특정 단어가 자주 나오는 경향이 있기에, 여러 문장에서 자주 나오는 단어는 덜 중요하게 여기고(낮은 값을 갖고) 덜 나오는 단어라도 특정 문장에서 나와서 문장을 나타내기에 적합한 값을 더 중요하게 여기기 위해(더 높은값을 가짐)

차원 축소

보통 단어수가 줄여도 수만-수십만은 되다보니 TF-IDF 벡터는 크기가 엄청나게 크다. 메모리 절약과 빠른 연산을 위해 TF-IDF를 사용할 때는 행렬분해(TruncateSVD)와 같은 차원축소기법을 이용해서 벡터 크기를 줄인다.

문맥 기반(Word2vec)

word2vec은 문맥기반의 임베딩 기법으로, 주변에 등장하는 단어가 비슷할수록 유사한 임베딩 값을 갖는다. 예를 들어

너 남자친구 있어?
너 여자친구 있어?

‘남자친구’와 ‘여자친구’ 두 단어는 사용되는 문맥이(주변단어가) 비슷할 것이므로 유사한 값을 갖는다. 재밌는건 이들을 더하고 빼서

한국 - 서울 + 도쿄 = 일본

과 같은 결과를 얻을 수 있다. 아래 링크에서 테스트 ㄱㄱ

https://word2vec.kr/search/

word2vec 벡터는 크게 CBOW와 Skip-gram 2가지 방법으로 생성한다.

CBOW(Continous Bag-Of-Words)

임베딩할 단어 A 주변에 등장하는 단어 B, C, D, E들을 임베딩하고 그 평균값을 다시 원핫벡터로 바꾼 결과가 최대한 A가 되도록 학습한다. 이때 앞뒤로 주변단어를 가져오는 개수를 윈도우 사이즈라고 한다.

문장: 나 오늘 김밥 맛있게 먹었어
윈도우 크기가 4일 때,
f(나, 오늘, 맛있게, 먹었어) = 김밥식으로 표현하면(각각 단어는 one-hot 벡터, W, W'=가중치, b, b'=bias)
김밥 = softmax(W'×(W×(나+오늘+맛있게+먹었어) + b) + b')

W 혹은 W’ 중 하나 혹은 평균을 임베딩 벡터로 사용한다. Skip-Gram

skip-gram은 반대로 중심 단어가 주어지면 주변 단어를 맞춘다.

y_pred(주변단어의 확률) = softmax(W'×(W×맛있게+b)+b')
loss = sum((주변단어1-y_pred) + 주변단어2-y_pred) + (주변단어3-y_pred) + 주변단어4-y_pred)

나는 주변단어가 4개면 예측확률 4개를 만드나 싶었는데 그게 아니고, 예측확률은 하나만 만들고 4개의 주변단어의 one-hot 벡터와 diff를 계산하는 방식이었다.

근데 구현예제들을 보는데 활성화함수는 따로 없는 모양이다? 그것과 관련되서 찾아보니…

word2vec을 만든 사람들은 활성화함수를 넣지않고 선형 관계를 갖도록 의도한 것으로 보인다고 한다. 지나치게 워드에 오버피팅되는것을 막기 위함인 것 같다. 또 활성화함수는 임베딩 공간을 크게 줄인다. 예를 들어 relu는 무조건 0이상이여야 하고, tanh, sigmoid 등은 -1~+1이고… 어떤 task 를 위한 것이 아닌 유사하지 않은 단어를 가르고 차원을 축소하는데 목표가 있는 것 같다.

word2vec은 아래 링크에서 설명이 더 잘되어있다

위 링크는 실제 skip-gram을 구현한 예제이다.

그럼 Embedding 레이어는?

Embedding 레이어는 처음에 무작위로 초기화된 상태에서 정수로 오는 word를 정해진 크기의 벡터로 바꿔서 다음 레이어로 넘기고, 학습단계에서는 역전파되는 기울기를 바탕으로 해당 word의 임베딩 값을 조정한다. 즉 주변 문맥을 반영하지 않는다.

그럼 Dense레이어랑 사실상 똑같은거 아닌가? 싶은데 차이점은 원핫벡터로 값을 안넣어줘도 되서 덕분에 메모리 절약할 수 있고, 케라스에서 지원하는 masking 기능을 사용할 수 있고, 꼭 문장뿐만아니라 추천 시스템 등에서 User 등을 벡터로 나타낼 때도 사용할 수 있다.

하지만 어지간하면 weights 지정 기능을 이용해서 pre-trained 된 word2vec 등을 지정해야 할 것이다.

아 이걸 왜 이제 알았지. 근데 적으면서 word2vec에 대해서도 이제야 제대로 안 것 같은 느낌… ㅠㅠ

References

--

--

김희규

나는 최고의 선수다. 나를 최고라고 믿지 않는 사람은 최고가 될 수 없다.