Seq2seq를 이용한 텍스트 Autoencoder + 이를 이용한 클러스터링

Posted by 적분 ∫2tdt=t²+c
2019.02.16 02:15 잉여

이론

오토인코더(Autoencoder)라는 개념이 있습니다. 쉽게 말하면, 어떤 상자 안에 값 x를 넣으면 그와 동일한 값인 x를 출력하도록 하는 녀석을 말합니다. 입력값을 그대로 출력해주면 되는 것인데, 이게 어떤 의미가 있냐 의아할 수 있습니다. 하지만 데이터 x가 단순히 하나의 숫자가 아니라, 여러 개의 숫자로 표현되는 복잡한 값(행렬이라던지, 이미지라던지, 음성이라던지...)이고, 상자 안의 저장 공간이 해당 입력을 전부 저장하기엔 부족하다면 이는 쉽지 않은 작업이 됩니다.


좀더 쉬운 예를 들자면, 숫자를 10개까지밖에 기억못하는 사람(오토인코더)한테 동시에 숫자 100개를 들려주고, 그대로 그걸 다시 말해보라고 하는 것이죠. 기억력에 한계가 있기 때문에 숫자 100개를 다 못 외울것 같지만, 계속 이걸 외우라고 협박하면, 갖은 방법을 동원해서 어떻게든 100개의 숫자를 비슷하게 기억하게 되겠죠. 

이게 가능해지면 다양한 작업이 편해집니다. 일단 숫자 10개만 기억하면 여기서 숫자 100개를 복원해낼 수 있으므로, 숫자 100개의 데이터를 숫자 10개의 데이터로 표현할 수 있습니다 (차원 축소). 게다가 이때의 숫자 10개는 원래 숫자 100개를 복원하는데에 가장 핵심적인 정보들만 뽑혀 있을 것이므로, 이 값을 전체 숫자 100개를 표현하는 주요 자질로 사용할 수 있습니다 (자질 추출). 그리고 핵심적인 정보인 숫자 10개의 값을 조절하면 다양한 100개짜리 숫자를 만들어 낼수도 있구요 (생성 모형).


좀더 엄밀하게 말하면, n개로 이뤄진 벡터 집합 X = {x1, x2, x3, ... , x_n}의 모든 원소에 대해 f(x_i)의 값과 x_i 값 사이의 오차를 최소화하도록 하는 함수 f를 찾는 것입니다. 이 때 f의 구조는 x_i 전체를 저장할 수 없게 하면, f는 x_i의 패턴 중 중요한 것을 찾아 이를 압축하는 표현을 찾게 됩니다. 이를 활용하는 것이 Autoencoder의 핵심적인 개념입니다. 주로 인공 신경망을 여러 층 쌓는 방법으로 구현하며, 차원 축소 및 자질 추출, 생성 모형에 유용하게 널리 사용되고 있습니다. (https://en.wikipedia.org/wiki/Autoencoder)


하지만, 흔히 알려진 Autoencoder 모형은 텍스트 데이터에 적용하기에 적합하지 않습니다. 텍스트는 전형적인 시간에 종속적인 데이터 타입이고, 그 길이가 가변적이기 때문에 고정된 크기의 네트워크에 입력이 불가능하기 때문이죠. 그 대신에 seq2seq를 이용해서 autoencoder와 유사한 녀석을 만들수가 있습니다.


그림 출처: https://tensorflowkorea.gitbooks.io/tensorflow-kr/content/g3doc/tutorials/seq2seq/

seq2seq 모형은 RNN 네트워크 2종류를 이어붙인 모형으로 기계 번역, 질문 답변 등의 작업을 처리하는데 널리 쓰입니다. encoder 부분에 A, B, C가 차례로 들어가면서 RNN이 이를 차례대로 인코딩하고, 최종적으로 인코딩 결과가 디코더로 넘겨져서 이를 W, X, Y, Z로 문장을 생성해 나갑니다. 충분한 양의 데이터를 학습시키면, 인코더와 디코더의 가중치들이 적절하게 학습되어 복잡한 문장에서도 그 구조를 추출하고 의미있는 패턴을 학습하는게 가능해집니다.


이때 A->B->C가 인코더에 들어간 후의 인코더의 상태값만으로 W, Y, Y, Z를 생성한다는 점이 매우 중요한데요, 다른 말로하면 가변적 길이의 문장을 통해 고정된 크기의 값을 생성하고, 또 고정된 크기의 값을 통해 가변적인 길이의 문장을 생성할 수 있다는 것입니다. 그렇다면, A->B->C가 입력됐을때, 출력으로 그대로 A, B, C를 생성하도록 학습시키면, 이는 결국 텍스트버전의 Autoencoder가 될 수 있겠죠. 


실전

과연 텍스트 버전의 오토인코더가 잘 작동할지 궁금해져서, 관련 구현들을 찾아봤습니다. Tensorflow로 구현된 코드들이 몇몇 있더라구요. (https://github.com/erickrf/autoencoder/) 이 코드를 바탕으로 조금 개량하여 Bidirectional GRU Encoder에 GRU Decoder를 사용해 텍스트 오토인코더를 구현하고 실제로 실험을 진행해보았습니다.


실험에 사용한 텍스트는 Expedia 호텔 영어 리뷰 데이터 148만개를 사용했습니다. 각 리뷰들에 대문자를 소문자로 통일시키고, 토큰화 처리한 것 이외의 전처리는 수행되지 않았습니다.

i loved this location due to the proximity to shops and food and would stay there again .

i would stay again due to cleanliness and central location .

the staff are friendly and helpful .

<전처리된 문장들 예시>


Hidden Size = 150, Hidden Layer = 3, Word Embedding Size = 200으로 설정하고, Tensorflow GPU 버전에서 전체 데이터에 대해 1 epoch 학습을 진행시켰습니다. 학습 후 오토인코더의 복원 정확도는 약 98%로, 사실상 입력 문장을 그대로 복원하는데에 성공해냈구요. 150 크기의 Hidden Layer가 3개 있으므로, 인코딩된 상태의 크기는 총 450이고, 양방향 데이터를 모두 사용했으므로, 총 900개의 숫자로 전체 문장을 표현하는 방법을 학습한것이죠. 


이렇게 학습된 오토인코더를 사용해 문장을 인코딩하면 다음과 같이 900차원의 벡터값을 돌려줍니다.

the staff are really nice / polite , breakfast was ok . [-0.287, -0.986, -1, 0.001, 0.006, -0.001, 0.021, -0.996, ...]

the staff are friendly and helpful . [0.236, -0.992, -1, -0.005, 0.006, -0.000, -0.732, -0.997, ...]

i don't recommend this hotel at all . [0.104, -0.989, -1, -0.004, 0.009, -0.001, -0.587, -0.997, ...]

혹은 거꾸로 900차원의 벡터값을 넣어주면 문장을 생성하는것도 가능하구요.


오토인코더 학습이 잘 진행되었다면, 비슷한 문장끼리는 분명 비슷한 임베딩 값을 가질 것입니다. 그래서 임베딩 값을 바탕으로 클러스터링을 실시해서 실제로 비슷한 문장끼리 묶이는지를 확인해봤습니다.


10만개의 문장을 랜덤으로 추출해서 k-means 클러스터링을 실시했습니다. K = 3000으로 두었으니, 평균적으로 한 클러스터에 33개의 문장이 들어갈 겁니다. 그리고 비교를 위해 같은 데이터를 대상으로 텍스트 오토인코더가 아닌 일반 tf-idf를 이용한 텍스트 클러스터링도 실시해 보았습니다.

먼저 클러스터링 결과의 통계를 살펴보면 다음과 같습니다.

Autoencoder를 사용한 경우 생성하는 벡터값이 dense하다보니 좀더 균일한 크기의 클러스터들이 많이 생성된 반면, tf-idf를 사용한 경우는 벡터값이 sparse하기 때문에 5개 이하의 소규모 클러스터도가 많이 생기고, 또 120개 이상의 대규모 클러스터도 잔뜩 생겼습니다. (크기가 300개 이상인 클러스터도 3개나 있었습니다.)


오토인코더TF-IDF
1

good facility .

view great .

good value .

good service .

good location .

good price .

fine location .

<하략>

good value .

it was a spacious room , good value ...

very good value breakfast .

good value , clean and comfortable .

good location snd good value .

good value for downtown hotel

good value for the location of the hotel .

<하략>

2

best environment , best stuff , best breakfast !

average hotel and meet our accommodations for the night .

good hotel , fair conditions for what you paid .

good stay with a comfortable bed and free breakfast .

good - except that the wifi connection was spotty .

good hotel w / good service .

good enough for a single night stay .

<하략>

clean room , good service .

service : good ;

good hotel w / good service .

service seemed good .

service at the hotel was extraordinarily good .

the service was good .

good service .

they people and service was good .

<하략>

3

this was a great location and a great stop over motel .

it was a great stay and we would definitely go back .

it was a beachfront property and a great location .

it was a great stay , will definately return again

this was a nice suprise , and i will stay there again .

it was a great price , which is why we stayed there .

<하략>

louis and will stay there again as this was our first time there .

this was a nice suprise , and i will stay there again .

<끝. 2개가 전부인 클러스터>

4

overall , a good place to stay at a good price .

this is a good one night hotel near airport .

this is a good hotel for a one night stay near the airport .

this is a good hotel for the night before you

this is a great place to stay with your dog .

overall , a good choice for access to terminal 4 .

this hotel was good for one night in guatemala city .

this hotel was amazing , in looks and service .

this hotel is clean and very convenient to the dfw airport .

<하략>

i stayed one night so i could sleep near the airport .

this is a good one night hotel near airport .

this is a good hotel for a one night stay near the airport .

this hotel was good for one night in guatemala city .

<끝. 4개가 전부인 클러스터>


위의 예시는 양쪽 클러스터링 결과 중 비슷한 클러스터 4쌍을 뽑아 놓은 것입니다. 

TF-IDF를 이용한 클러스터링 결과는 기법 그대로 단어가 얼마나 일치하는지를 바탕으로 유사도를 계산합니다. 그 때문에 공유하는 단어가 많은 문장들끼리 묶이게 됩니다. 반면, 오토인코더의 경우 겹치는 단어가 없더라도 의미적으로 유사하다면 충분히 같이 묶일 수 있습니다. 
1번에서 good location과 view great 이 한데 묶인것처럼 말이지요. 또한 오토인코더의 경우 문장의 형태에 굉장히 민감한 것으로 보입니다. 3번의 경우 it was 혹은 this was로 시작하고, and 혹은 관계절로 문장을 덧이은 경우들이 주로 묶이는 것을 확인할 수 있었습니다. 


일단 작은 데이터셋을 돌려보고 내린 결론은 다음과 같습니다.

  1. 텍스트 오토인코더는 의미적인 유사성을 어느 정도 반영할 수 있다.
  2. 또한 문장 구조의 유사성을 반영할 수도 있다.
  3. 다만 문장 구조가 유사해도 의미적으로는 전혀 상관이 없을수도 있으므로, 임베딩 결과가 항상 의미를 반영한다고 해석하긴 어려운 것 같다.
  4. 이와는 별개로 임베딩 값을 가지고 거의 완벽한 문장을 생성해낼수 있다는 건 아주 유용한 특징이다!


실험을 다 돌리고 난 다음에 생각해보니, 어쩌면 Hidden Layer 사이즈나 개수에 따라서도 반영하는 정도에 차이가 있을지도 모르겠다는 생각이 들었습니다. 흥미로운 주제지만, 사이즈를 더 키우기에는 집 컴퓨터의 GPU가 벅차하므로 컴퓨터를 먼저 업그레이드하고선 해봐야겠습니다.

이 댓글을 비밀 댓글로