개요

앞선 시리즈에서 신경망이 결국 숫자를 받아 숫자를 뱉는 함수라는 걸 정리했다. 그런데 여기서 아주 기본적인 질문이 생긴다.

“안녕하세요”는 몇 번인가?

이미지는 픽셀 밝기가 이미 숫자다. 소리도 진폭이 숫자다. 그런데 글자는 그렇지 않다. 컴퓨터에 “사과”를 넣으려면 누군가는 이걸 숫자로 바꿔야 한다.

자연어처리는 바로 이 지점에서 시작한다. 이번 글의 주제는 “단어를 어떻게 숫자로 바꿀 것인가”다. 별것 아닌 문제처럼 보이는데, 여기서 고른 방식이 모델이 언어를 얼마나 이해할 수 있는지의 한계를 결정한다.

교육 자료는 대외비라 슬라이드 이미지나 예제 데이터는 싣지 않는다. 개념 흐름만 이해한 방식으로 재구성했고, 그림은 전부 직접 생성하였다.


1. 가장 단순한 답 — 원-핫 인코딩

제일 먼저 떠오르는 방법은 번호표를 나눠주는 것이다. 사전에 있는 단어를 전부 줄 세우고, 각자 자기 자리에만 1을 쓰고 나머지는 0으로 채운다.

이걸 원-핫(one-hot) 인코딩이라고 한다.

원-핫 인코딩과 워드 임베딩

왼쪽이 원-핫이다. 단어마다 자기 칸 하나만 1이고 나머지는 전부 0이다. 규칙이 단순해서 만들기도 쉽고, 어떤 단어인지 헷갈릴 일도 없다.

전통적인 자연어처리는 오랫동안 이 방식을 썼다. 단어를 더 이상 쪼갤 수 없는 기호로 취급한 것이다.

그런데 칸이 몇 개나 필요할까

여기서 첫 번째 문제가 나온다. 칸의 개수 = 사전에 든 단어 수다.

데이터 종류 단어 수
음성 인식용 사전 약 2만
일반적인 연구용 말뭉치 약 5만
규모가 큰 사전 50만
웹 전체 규모 1000만 이상

단어 하나를 표현하는 데 1000만 칸짜리 배열이 필요하고, 그중 9,999,999칸이 0이다. 이걸 차원의 저주라고 부른다.

메모리도 문제지만 더 큰 문제는 정보 밀도다. 배열 전체가 사실상 “몇 번째 칸이 1인가” 하나만 말하고 있다. 1000만 칸을 써서 숫자 하나를 전달하는 셈이다.

진짜 심각한 문제 — 유사도를 잴 수 없다

두 번째 문제가 훨씬 치명적이다. 예를 들어 검색어와 문서를 비교한다고 하자.

사용자가 검색한 말 : "삼성 갤럭시 S시리즈 최신폰"
문서에 적힌 말     : "삼성 갤럭시 S26 시리즈"

사람이 보면 같은 말이다. 그런데 원-핫으로 바꿔놓고 두 벡터를 비교하면 어떻게 될까?

최신폰S26서로 다른 칸에 1이 찍혀 있다. 겹치는 칸이 하나도 없으니, 두 벡터를 곱해서 더하면 정확히 0이 나온다.

완전히 무관한 단어라는 결론이 나온다. “삼성”과 “갤럭시”가 겹치는 건 셀 수 있어도, “S시리즈 최신폰”과 “S26 시리즈”가 비슷하다는 건 영원히 알 수 없다.

이건 특정 단어쌍의 문제가 아니다. 원-핫에서는 모든 단어쌍의 유사도가 똑같이 0이다. “고양이-호랑이”도 0, “고양이-냉장고”도 0이다.

단어를 서로 다른 서랍에 하나씩 넣어놨더니, 서랍들 사이의 관계가 통째로 사라진 것이다.


2. 발상의 전환 — 뜻은 이웃이 알려준다

그럼 단어의 의미를 어떻게 숫자에 담을 수 있을까? 사전을 통째로 집어넣어야 하나?

여기서 아주 유명한 아이디어가 등장한다.

“어떤 단어의 뜻은, 그 단어와 함께 어울리는 단어들이 말해준다.” — 분포 가설(distributional hypothesis)

분포 가설

그림처럼 어떤 단어를 가리고 문장을 여러 개 보여준다고 하자. 주변에 라떼, 자리, 사람이 많다 같은 말이 반복해서 나온다면, 가려진 자리에 들어갈 단어가 무엇인지 짐작이 된다.

뜻을 직접 정의하지 않고, 이웃들로 둘러싸서 위치를 특정하는 방식이다.

더 쉬운 비유로

전학 온 친구가 어떤 사람인지 알고 싶을 때, 우리는 그 애한테 “너는 어떤 사람이야?”라고 묻지 않는다. 누구랑 같이 다니는지를 본다. 매일 도서관에 있는 무리랑 붙어 다니면 대충 감이 오고, 농구장에 있는 무리랑 다니면 또 다른 감이 온다.

단어도 똑같다. 누구 옆에 자주 서 있는지만 봐도 그 단어가 어떤 부류인지 알 수 있다.

이 발상이 좋은 이유가 하나 더 있다. 사람이 뜻을 적어줄 필요가 없다. 인터넷에 굴러다니는 문장을 잔뜩 모아서 “누가 누구 옆에 자주 오는지”만 세면 된다. 정답표가 없어도 되는 학습인 셈이다.


3. 워드 임베딩

이 아이디어를 실제 숫자로 구현한 게 워드 임베딩(word embedding)이다.

워드 임베딩 = 단어를 밀집된(dense) 실수 벡터로 표현해서, 단어 사이의 의미적 관계까지 담아내는 방법

앞의 그림 오른쪽이 그거다. 원-핫과 비교하면 성격이 정반대다.

  원-핫 인코딩 워드 임베딩
칸 개수 단어 수만큼 (수만~수천만) 100~300개 정도
0 아니면 1 실수 (0.43, −0.85 …)
채워진 정도 거의 다 0 (희소) 전부 채워짐 (밀집)
만드는 법 규칙으로 바로 데이터로 학습
유사도 항상 0 (잴 수 없음) 가까울수록 비슷

핵심은 마지막 줄이다. 비슷한 뜻의 단어는 벡터도 서로 가까운 곳에 놓인다.

임베딩 공간

학습이 끝난 임베딩 공간을 눌러서 그려보면 이런 모양이 된다. 과일은 과일끼리, 운동은 운동끼리 모여 있다.

여기서 짚고 갈 게 있다. 누가 “이것들은 과일이야”라고 알려준 적이 없다. 그냥 문장을 잔뜩 읽혔을 뿐인데, “사과”와 “배”가 비슷한 자리에 등장하니까 저절로 옆자리로 모인 것이다.

각 칸은 무슨 뜻인가

처음에 가장 궁금했던 부분이다. 임베딩 벡터의 첫 번째 칸이 0.432라면, 그 0.432는 무슨 뜻일까?

정해진 뜻이 없다. 사람이 “1번 칸은 과일 정도, 2번 칸은 크기”라고 정해준 게 아니라, 학습 과정에서 알아서 잡힌 값이다. 그래서 칸 하나하나를 사람이 해석하기는 어렵다.

대신 전체 벡터의 위치에는 의미가 있다. 개별 좌표값은 몰라도, “이 점이 저 점과 가깝다”는 사실은 확실하게 읽을 수 있다.

이 성질이 처음엔 좀 어색했다. 내가 이해할 수 없는 숫자들을 쓰는 셈이니까. 그런데 생각해보면 해시값도 마찬가지다. 해시 결과 자체는 사람이 읽어봐야 아무 의미가 없지만 “같으면 같은 것”이라는 성질 하나로 충분히 쓸모가 있다. 임베딩은 거기서 한 발 더 나가 “가까우면 비슷한 것”까지 보장해주는 셈이다.


4. Word2Vec — 어떻게 학습하나

워드 임베딩을 만드는 대표적인 방법이 Word2Vec이다. 2013년에 나왔고, 지금도 개념 설명의 출발점으로 쓰인다.

아이디어는 앞의 분포 가설을 그대로 문제로 바꾼 것이다.

어떤 단어와 그 주변 단어의 관계를 맞히는 문제를 풀게 시킨다.

여기서 중요한 포인트가 있다. 우리가 원하는 건 그 문제의 정답이 아니다. 문제를 푸는 과정에서 모델이 만들어내는 단어의 표현(벡터)이 진짜 목적이다.

시험 문제를 풀리는 목적이 점수가 아니라 문제를 풀면서 길러지는 감각인 것과 비슷하다. Word2Vec에서 예측 정확도 자체는 버려도 되는 부산물이고, 부산물을 얻으려고 본 문제를 푸는 구조다.

이 구조가 재밌었다. 정답표를 사람이 만들지 않아도 되기 때문이다. 문장 자체가 이미 정답을 품고 있다. “카페” 옆에 “라떼”가 있었다는 사실이 곧 정답이다.

두 가지 방식 — Skip-gram과 CBOW

Word2Vec에는 방향이 반대인 두 알고리즘이 있다.

Skip-gram과 CBOW

  Skip-gram CBOW
푸는 문제 가운데 단어로 이웃을 맞힌다 이웃들로 가운데 단어를 맞힌다
화살표 방향 가운데 → 바깥 바깥 → 가운데
한 문장에서 나오는 문제 수 여러 개 (이웃 하나당 하나) 한 개

먼저 알아둘 개념이 윈도우 크기(window size)다. 가운데 단어 양옆으로 몇 칸까지를 “이웃”으로 볼 것인가를 정하는 값이다. 그림에서는 2로 잡았으니 앞뒤 두 칸씩, 총 네 개가 이웃이 된다.

이 값을 키우면 더 넓은 문맥을 보지만 관계가 희미해지고, 줄이면 촘촘하지만 좁게 본다. 직접 정해줘야 하는 설정값이다.

둘의 성격 차이

모델 장점 한계
Skip-gram 데이터가 적어도 잘 동작 / 드물게 나오는 단어에 강하다 학습이 느리다
CBOW 학습이 빠르다 / 자주 나오는 단어에 강하다 드문 단어 표현이 약하다

왜 이런 차이가 날까? 한 문장에서 만들어지는 문제의 개수가 다르기 때문이다.

  • CBOW는 이웃 넷을 한꺼번에 뭉쳐서 가운데를 맞히니 문제가 하나
  • Skip-gram은 가운데 단어로 이웃을 하나씩 맞히니 문제가 이다

같은 문장에서 Skip-gram이 네 배 더 많은 연습을 하는 셈이다. 그러니 느리지만 꼼꼼하다. 특히 드물게 등장하는 단어는 등장 횟수 자체가 적어서 연습 기회가 귀한데, Skip-gram은 그 몇 번을 여러 문제로 쪼개 쓴다.

반대로 CBOW는 이웃을 평균 내서 한 번에 처리하니 빠르지만, 평균을 내는 과정에서 개성이 묻힌다. 흔한 단어들 사이에 드문 단어가 끼면 그 특징이 희석된다.

정리하면 Skip-gram은 소수 정예를 챙기는 쪽, CBOW는 다수를 빠르게 훑는 쪽이다.


5. 그래서 무엇이 달라졌나

이번 내용을 한 문장으로 줄이면 이렇다.

단어를 “서로 다른 기호”로 보던 것에서, “의미 공간 위의 한 점”으로 보게 되었다.

이 전환이 가져온 변화가 크다.

  • 검색어와 문서가 글자가 달라도 비슷하다는 걸 잴 수 있게 됐다
  • 단어 수가 아무리 늘어도 벡터 길이는 그대로
  • 학습에 사람이 만든 정답표가 필요 없다

그리고 이건 이후 모든 자연어처리 모델의 입구가 된다. 문장을 다루든 번역을 하든, 첫 단계는 언제나 단어를 벡터로 바꾸는 일이다.

남은 문제

물론 여기서 끝은 아니다. 워드 임베딩에는 뚜렷한 한계가 하나 있다.

단어 하나에 벡터 하나가 고정된다.

“배”라는 단어를 생각해보자. 먹는 배, 타는 배, 신체의 배가 전부 같은 벡터를 받는다. 문장에서 어떤 뜻으로 쓰였는지는 반영되지 않는다.

게다가 아직 문장을 다루지도 못했다. 단어 벡터를 순서대로 늘어놓기만 해서는 “나는 너를 좋아해”와 “너는 나를 좋아해”를 구별할 수 없다. 두 문장에 쓰인 단어가 완전히 똑같기 때문이다.

순서를 다루는 문제가 다음 글의 주제다.


정리

  • 컴퓨터에 언어를 넣으려면 단어를 숫자로 바꿔야 한다
  • 원-핫 인코딩 = 자기 칸만 1, 나머지는 0. 단순하지만 두 가지가 치명적이다
    • 차원의 저주 — 단어 수만큼 칸이 필요하고 거의 전부 0
    • 유사도를 잴 수 없다 — 모든 단어쌍이 똑같이 무관하게 나온다
  • 분포 가설 — 단어의 뜻은 함께 등장하는 이웃이 말해준다
  • 워드 임베딩 = 100~300칸짜리 밀집 실수 벡터. 비슷한 단어는 가까이 놓인다
  • 각 칸의 뜻은 사람이 해석하기 어렵지만, 위치 관계는 확실하게 읽을 수 있다
  • Word2Vec — 주변 단어 예측 문제를 풀리고, 그 부산물인 벡터를 챙긴다
    • Skip-gram : 가운데 → 이웃. 느리지만 드문 단어에 강하다
    • CBOW : 이웃 → 가운데. 빠르지만 드문 단어에 약하다
  • 한계 — 단어당 벡터가 하나로 고정되고, 아직 순서를 다루지 못한다

다음 글에서

다음 글에서는 순서를 다룬다. 왜 언어에서 순서가 결정적인지, 그리고 순서를 기억하기 위해 등장한 RNN의 구조를 정리한다.

한줄 평

  • 이걸 단 30분만에 압축해서 발표하신 교수님이 놀랍다..