for textmining

Word2Vec으로 문장 분류하기

|

이번 포스팅에선 요즘 인기를 끌고 있는 단어 임베딩 방법론 가운데 하나인 Word2Vec을 활용해서 문장을 분류하는 방법에 대해 이야기해보려고 합니다. 우선은 뽐뿌, 클리앙, 세티즌 등 휴대폰 리뷰사이트에 있는 스마트폰 리뷰들을 분석해 보려고 하는데요. 어떤 일을 하려고 하는지 한번 예를 들어보죠.

리뷰1: 발열은 LTE폰의 숙명 ㅠㅠ

리뷰2: HD촬영기능 하나만으로도 충분히 만족하시리라 봅니다~~

리뷰1을 작성한 사용자는 배터리를 휴대폰의 중요한 기능이라고 생각하는 모양입니다. 리뷰2의 경우엔 카메라겠지요. 이렇게 리뷰가 하나 주어졌을 때 해당 리뷰를 작성한 사용자가 휴대폰의 어떤 기능을 중시하는지를 알아맞혀 보고 싶은 겁니다. 다시 말해 휴대폰 리뷰 문장을 ‘배터리’, ‘카메라’, ‘디자인’, ‘사운드’ 따위의 기능(범주)으로 분류(classification)하는 문제가 관심이 되겠습니다. 물론 사람이 리뷰를 하나하나 읽어보면서 확인해도 되지만 21세기를 살아가는 우리는 컴퓨터의 힘을 빌려보죠.

Word2Vec으로 단어 임베딩하기

2013년 구글에서 개발한 Word2Vec이라는 방법론이 있습니다. 이름 그대로 단어(Word)를 벡터(Vector)로 바꿔주는 방법입니다. 이를 임베딩(Embedding)이라고 합니다. Word2Vec을 정확하게 이해하려면 역시 논문을 읽는 것을 추천하고요, Word2Vec 학습 방식에 관심이 있으신 분은 이곳을, GloVe, Fasttext 같은 다른 방법론과의 비교에 관심 있으시면 이곳에 한번 들러보셔요.

주지하다시피 Word2Vec의 효과는 놀랍습니다. 단어를 벡터화할 때 단어의 문맥적 의미를 보존하기 때문이죠. 위의 그림처럼 MAN과 WOMAN 사이의 거리는 KING과 QUEEN 거리와 유사합니다. 벡터로 바뀐 단어들은 ‘유클리디안 거리’니, ‘코사인 유사도’니 하는 방식들로 그 거리를 잴 수 있고 그 거리가 가까울(작을) 경우 의미가 비슷한 단어라고 해석할 수 있게 됩니다.

말로만 장황하게 얘기하는 것보다는 예를 드는 것이 좋겠네요. 우선 루리웹, 뽐뿌, 세티즌, 클리앙, 플레이웨어즈 5개 사이트에서 총 29만7906개의 리뷰를 수집했습니다. 이후 포스태깅(형태소분석) 작업을 실시했습니다. 대표적인 한국어 포스태거로 KoNLPy가 있는데요, 저는 이것 말고 김현중 서울대 박사과정이 개발한 cohesion tokenizer를 사용했습니다. cohesion tokenizer는 KoNLPy처럼 품사 정보까지 반환하지는 않지만 토크나이징을 분석 대상 코퍼스의 출현 빈도를 학습한 결과를 토대로 토큰을 나눠주기 때문에 분석 품질이 비교적 좋습니다. Word2Vec에 품사정보까지 넣을 필요는 없기도 하고요. 어쨌든 cohesion tokenizer로 토크나이징한 결과물은 다음과 같습니다.

발열,은,LTE,폰,의,숙명,ㅠㅠ

HD,촬영,기능,하나,만,으로,도,충분히,만족,하시리,라,봅,니다

이렇게 토크나이징한 결과물을 파이썬 gensim 패키지를 활용해 Word2Vec 방법론을 적용합니다. Word2Vec을 적용하는 데 단 두 줄이면 됩니다.

# Word2Vec embedding
from gensim.models import Word2Vec
embedding_model = Word2Vec(tokenized_contents, size=100, window = 2, min_count=50, workers=4, iter=100, sg=1)

위 코드 의미는 다음과 같습니다. 포스태깅된 컨텐츠를 100차원의 벡터로 바꿔라. 주변 단어(window)는 앞뒤로 두개까지 보되, 코퍼스 내 출현 빈도가 50번 미만인 단어는 분석에서 제외해라. CPU는 쿼드코어를 쓰고 100번 반복 학습해라. 분석방법론은 CBOW와 Skip-Gram 중 후자를 선택해라.

그럼 임베딩이 잘 됐는지 확인해볼까요? gensim 패키지가 제공하는 기능 중에 ‘most_similar’라는 함수가 있습니다. 두 벡터 사이의 코사인 유사도를 구해줍니다. 그 값이 클 수록 비슷한 단어라는 뜻인데, 아래 코드는 ‘디자인’이라는 단어와 가장 비슷한(코사인 유사도가 큰) 100개 단어를 출력하라는 지시입니다.

# check embedding result
print(embedding_model.most_similar(positive=["디자인"], topn=100))

Word2Vec 모델이 내뱉는 결과는 아래와 같습니다.

색감, 그립감, 외형, 색깔, UI, 색깔, 각진, 뒷면, 외관…

“오 생각보다 괜찮다.” 결과물을 처음 보고 느낀 점입니다. 같은 방식으로 ‘사운드’도 해봤습니다.

오디오, 음장, 음질, 퀄리티, 헤드, MP3, 스피커…

역시 굿입니다.

단어벡터로 가중치 행렬 만들기

우리가 하고 싶은 작업은 이것입니다.

문맥적 정보가 보존된 상태의 단어 벡터 사이의 거리(유사도)를 구하고 이를 가중치 삼아 각 문장별로 스코어를 구한다. 이렇게 구한 스코어를 바탕으로 각 리뷰 문장을 특정 기능에 할당(분류)한다.

뭔가 어렵죠? 앞으로 차근차근 설명해보겠습니다.

Word2Vec의 아웃풋은 아래와 같은 단어벡터 행렬입니다. 첫번째 열의 각 요소는 위의 코드 가운데 ‘min_count’ 조건을 만족하는 코퍼스 내 모든 단어가 됩니다. 아래 행렬의 열의 개수는 ‘‘임베딩 차원수(size) + 1(단어가 속한 열)’이 됩니다. 다시 말해 행벡터가 각 단어에 대응하는 단어벡터라는 뜻이지요. 하지만 행렬 각 요소의 숫자들은 사람이 이해하기 어렵습니다. 그도 그럴 것이 100차원 벡터공간의 좌표값들이기 때문입니다사람은 4차원도 못그리는데 말이죠.

- V1 V2 V3
배터리 0.1256 0.2211 0.5473
발열 0.2385 0.7562 0.8754
0.2845 0.1293 0.9483

이를 사람이 이해하기 편하게 거리행렬(distance matrix)로 바꿔볼까요? 거리를 재는 방식은 다양하지만 일단은 가장 친숙한 유클리디안 방법을 써보겠습니다. 직각삼각형의 너비와 높이를 알면 피타고라스 정리에 의해 빗변의 길이를 구할 수 있죠? 정확히 그 식을 사용해 100차원 공간 안에 있는 벡터들 사이의 길이를 재는 겁니다. 이렇게 구한 거리행렬은 아래와 같습니다.

- 배터리 발열
배터리 0 1 10
발열 1 0 8
10 8 0

설명의 편의를 위해 제가 임의의 숫자를 적었습니다만, 위의 거리행렬이 의미하는 바는 이렇습니다. ‘배터리’와 ‘배터리’ 사이의 거리는 얼마일까요? 정확하게 일치하기 때문에 0입니다. 마찬가지로 ‘발열’과 ‘발열’, ‘은’과 ‘은’ 거리도 0입니다. 이런 방식으로 거리행렬의 모든 대각성분은 0이 됩니다.

한편 예시에서는 ‘배터리’와 ‘발열’ 사이의 거리가 1, ‘배터리’와 ‘은’은 10으로 나왔습니다. 그렇다면 ‘은’보다는 ‘발열’이 ‘배터리’와 유사한 단어라고 볼 수 있겠네요. 이것이 이제 우리가 만들 가중치 행렬이 지향하는 핵심 원리입니다. 즉, 특정 쿼리 단어(예를 들어 ‘배터리’)와 거리가 가까운(=의미가 유사한) 단어는 높은 가중치, 그렇지 않은 단어는 낮은 가중치를 가지도록 가중치행렬을 만들어보자는 것입니다. 이를 수식으로 나타내면 다음과 같습니다.

\[{ W }_{ ij }=exp\left( -\frac { d{ ({ x }_{ i },{ x }_{ j }) }^{ 2 } }{ 2\sigma^{2} } \right)\]

왜 이렇게 복잡한 수식을 쓰냐고요? 통계학을 전공하신 분이라면 한눈에 알아보셨겠지만, $exp$ 앞의 계수를 제외하면 이는 정규분포(normal distribution) 식과 같습니다. 정규분포는 연속 확률 분포의 일종으로 수많은 자연, 사회현상을 설명하는 데 탁월한 분석도구로 정평이 나 있습니다. 위 식 지수의 분자에 $d$는 거리행렬의 ‘거리’를 의미합니다. 즉 0에서 ∞ 범위를 지니는 들쭉날쭉한 벡터간 거리들을 아주 예쁜 모양의 정규분포로, 그것도 특정 범위로 스케일링까지 해서 반환한다는 얘기입니다.

거리행렬에 위 수식을 적용한 행렬에서 우리가 원하는 특정 쿼리 단어(예를 들어 ‘배터리’와 ‘사운드’)만 남기고 나머지를 제거한 행렬이 바로 가중치 행렬이 됩니다. 아래와 최종 결과입니다. 다시 한번 가중치행렬의 의미를 곱씹어 보면, 쿼리단어인 ‘배터리’와 의미가 유사한 ‘발열’이라는 단어는 높은 가중치(0.9)를, ‘배터리’와 별 상관이 없는 조사 ‘은’은 낮은 가중치(0.1)를 갖습니다. 마찬가지로 ‘사운드’ 기준으로 보면 이와 관련이 높은 ‘음질’이라는 단어는 높은 가중치(0.84)를, 그렇지 않은 나머지 단어들은 낮은 가중치를 갖게 됩니다.

- 배터리 발열 음질
배터리 1 0.9 0.1 0.1
사운드 0.1 0.1 0.01 0.84

가중치 행렬로 문장별 스코어 구하기

이제 처음 우리가 풀려고 했던 문제를 다시 떠올려봅시다. 휴대폰 리뷰 문장을 ‘배터리’, ‘카메라’, ‘디자인’, ‘사운드’ 따위의 기능(범주)으로 분류(classification)하려고 합니다.

발열,은,LTE,폰,의,숙명,ㅠㅠ

자, 7개 단어로 이뤄진 위 문장의 스코어를 매겨볼까요? 처음 등장한 단어 ‘발열’의 가중치는 0.9입니다. ‘은’은 0.1이네요. 이렇게 문장에 등장한 7개 단어에 해당하는 가중치를 가중치행렬에서 Lookup 방식으로 가져올 수 있습니다. 7개 단어 가중치를 모두 더한 값이 바로 이 문장이 ‘배터리’ 기능을 얼마나 중시하는지를 알려주는 지표가 되는 겁니다. 즉, ‘배터리’ 스코어는 ‘배터리’라는 쿼리 단어와 나머지 단어 사이의 가중치들의 선형결합(linear combination)입니다.

그런데 여기서 문제가 하나 있습니다. 문장에 등장한 단어 하나하나마다 가중치행렬에서 Lookup 형식으로 가중치들을 가져오는 건 계산상 너무 비효율적입니다. 위 문장만 해도 단어가 7개 있으므로 무려 7번이나 Lookup을 해야 한다는 얘기입니다. 이를 좀 빠르고 편하게 하는 방법이 없을까요?

이럴 때 단어문서행렬(Term-Document Matrix)가 강력한 힘을 발휘합니다. TDM의 행은 ‘단어’, 열은 ‘문서’가 됩니다. 즉 위 문장을 아래와 같은 행렬로 변환하는 작업입니다.

- Doc1 Doc2
발열 1 0
1 0

위 행렬을 해석하면 이렇습니다. ‘발열,은,LTE,폰,의,숙명,ㅠㅠ’이라는 Doc1은 ‘발열’이라는 단어가 문장에 있기 때문에 ‘발열’ 행, ‘Doc1’ 열의 값은 1이 됩니다. 마찬가지로 ‘은’ 행, ‘Doc1’ 열도 1입니다. 만약 행에 해당하는 단어가 쓰이지 않은 리뷰(doc)의 요소 값은 0, 쓰였다면 1이 됩니다. 저의 경우 단어 등장여부를 binary로 TDM을 구축했습니다. 즉, ‘발열’이라는 단어가 한번 쓰였든 두번 쓰였든 그 값을 1로 했다는 뜻입니다. 물론 빈도수 그대로 TDM을 구축해도 될 겁니다.

자, 이제 거의 다 왔습니다!

TDM과 가중치행렬을 내적(inner product)해주면 문장별로 스코어를 한방에 구할 수 있습니다. 내적 개념이 생소하거나 헷갈리시는 분이라면 선형대수학(Linear Algebra)을 처음부터 파실 필요는 없고, 고등학교 수학 책을 다시 한번 보시면 됩니다. 직관적으로 이해할 수 있도록 아래 예시를 준비했는데요. 우선 말뭉치에 등장하는 전체 단어 수가 7개이고, 우리가 조사하고 싶은 기능은 $f_1$, $f_2$, $f_3$ 이렇게 세 가지가 있다고 가정해보겠습니다. 두번째와 여섯번째 단어로 구성된 리뷰의 각 기능별 스코어는 다음과 같습니다.

보시다시피 기능별 스코어는 TDM에서 1에 해당하는 위치의 가중치행렬 열벡터를 참조(Lookup)해 이를 기능별로 따로 합산하는 방식으로 계산하게 됩니다. 이를 전체 리뷰로 확장하게 되면 전체 내적의 결과는 (쿼리 단어의 수 x 문서의 수) 행렬 형태가 됩니다. 즉, Doc1의 ‘배터리’ 스코어가 이 스코어행렬의 1행 1열에 해당하는 요소가 되는 것이죠. 만약 쿼리 단어를 ‘배터리’뿐 아니라 ‘사운드’, ‘카메라’ 등까지 지정했다면 같은 방식으로 Doc1의 ‘사운드’ 스코어는 2행 1열, Doc1의 ‘카메라’ 스코어는 3행 1열이 됩니다. 마찬가지로 Doc2의 ‘배터리’, ‘사운드’, ‘카메라’ 스코어는 1행 2열, 2행 2열, 3행 2열이 됩니다.

파일럿 실험 결과

휴대폰 리뷰 29만7906개를 위와 같은 방식으로 스코어를 매겼습니다. 제가 임의로 선정한 쿼리 단어는 ‘디자인’, ‘화면’, ‘음질’, ‘스펙’, ‘촬영’, ‘운영체제’, ‘배터리’ 총 7개입니다. 위의 챕터에도 설명했듯 각 리뷰별로 쿼리단어 수(제 경우엔 7개)에 해당하는 스코어들이 나옵니다. 여기서 가장 높은 값을 지닌 스코어에 해당하는 쿼리단어를 해당 리뷰가 중시하는 기능으로 분류했습니다. 결과(각 기능별 스코어 상위 5개)는 아래와 같습니다.

배터리

케이스 끼웠고요. 발열은 액정위에서 일어나는겁니다.
ls 전자꺼 사용 중인데 충전 잘 되네요... 하지만 무선충전에 단점 발열 ㅠㅠ
근데 진짜 꼽을게..............발열뿐
아이패드2는 정말... 발열이라는 것을 몰랐는데...
베가레이서3은진짜 발열,베터리만좋으면 짱인데

촬영

갤럭시S 2 사진 촬영/ 동영상 촬영
촬영은 기존에 사용중인 SKT 갤투로 찰칵~
전 카메라 화질과 720p 촬영이 중요한데 어떨런지
3d 촬영이요
헉 그렇게 무겁나요;;;;3D로 영상촬영도 된다던데;;; 잘 나오려나;;

디자인

제가 코 파다 피나면 저 색깔 나오는데.. 아침에 똥 누다가도 가끔 나와요.. 그래서 웬지 친근한 색감
액정색깔차이만 눈에 들어오네요
이거 궁금해서 `색상` `색깔`로 한번씩 검색하니 많이 나오더라구요
미치도록 선명한 대화면 액정과 완전 편리한 UI 아몰레드에 푹 빠져봅시다!!
삼성 핸드폰은 기능은 다 좋은거같은데 ........... 색깔 드럽게 못뽑는다는 ....

화면

갤3 화면 액정이 나갔는지.. 이상한 화면이 뜨는데요...
액정 화면은 몇 인치 인가요?
갤럭시 s3 흰색 화면 잔상
사진만 봐서는 갤스2 화면이 더 좋아보이네요. 갤스는 푸르딩딩한게 촌티나는 색상같아요
아무래도 화면밝기 낮추면 조금이라도 더 버텨주겠죠?ㅋㅋ

운영체제

결국은 운영체제에 따라 갈릴듯.
운영체제가 다르니 비교하기 그렇죠
운영체제가... 달라서..
운영체제가 쉣이었음
단일 운영체제 바다는 왜 그 모양인가요...

사운드

아레나에서도 이렇게 평했군요. 정말 음질 표현을 너무 잘했네욬ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
음질은안드에서상급이죠 ㅎㅎ
음질은 좋았으니.. 잭꽃아야되지만:
음질은... 제가 막귀라서. 핸드폰 음질 다 똑같은거 아닌가요?
스피커는 저도 공감 ㅋㅋㅋㅋ 음질은 먹는건가여 수준

스펙

스펙으로만 따져도 옴냐2보다 아이폰이 훨씬 성능이 좋을텐데요... ^^;;
갤투가 하드웨어 스펙이 좋죠 그래서 갤투 이제 os도 같고
그렇군요... 하긴 하드웨어 스펙도 더 낫구 ㅠㅠ
갤럭시는 나올때마다 스펙종결수준이니까요..ㅎㅎ
삼성 스펙만 쩔지... 벤치는 믿을게 못될듯하오

마치며

이상으로 Word2Vec을 활용한 문장 분류 방법을 알아보았습니다. 이 프레임워크를 단순하게 요약하면 다음과 같습니다. 우선 TDM을 구축하고, Word2Vec 기반으로 가중치매트릭스를 생성합니다.

위와 같이 구축한 가중치매트릭스와 TDM을 내적해 스코어를 산출합니다.

이미 눈치 채셨겠지만 어떤 문서가 특정 쿼리 단어와 얼마나 연관성이 있는지 스코어를 낼 수 있습니다. 바꿔 말하면 쿼리 단어를 어떻게 선정하느냐에 따라 다양한 문장분류기를 만들어낼 수 있다는 얘기입니다. 질문이나 지적해주실 내용 있으시면 메일이나 댓글 등으로 해주시면 됩니다. 여기까지 읽어주셔서 감사합니다.



Comments