brunch

You can make anything
by writing

C.S.Lewis

by 김종민 Sep 08. 2022

자연어 처리: 컴퓨터가 사람의 말을 이해하는 방식은?

자연어 처리의 핵심적인 두 축, Tokenizer와 Embedder

위의 대화는 한 때 논란에 휩싸이기도 했던 인공지능 챗봇 이루다입니다. 사람의 말을 이해하고 커뮤니케이션하는 것처럼 보이는데요. 이런 컴퓨터 프로그램들은 어떻게 사람의 말을 이해하고 반응하는 것일까요?


(이번 글은 저번 글에서 이어지는 내용을 담고 있습니다. 저번 글에서는 자연어 처리가 어려운 이유를 '언어' 자체의 관점에서 살펴보았습니다. 자연어 처리가 어려운 이유는, 완벽한 언어 자체가 존재하지 않으며, 언어는 변화하는 인간 삶의 산물이기 때문이었는데요.)



이 글에서는 컴퓨터가 자연어를 처리하는 과정에 대해서 간략하게 소개하고자 합니다.


우선 자연어 처리의 세부 분야는 다양합니다. 문서에서 토픽을 요약하는 토픽 모델링, 문서의 유사도를 분석하는 문장 유사도 분석, 문장 생성기, 자동 번역기 등등이 있습니다. 아래 그림은 다양한 머신러닝/딥러닝 모델을 게시할 수 있는 플랫폼인 'Hugging Face'에서 분류한 자연어 처리의 분야입니다.



분야는 이렇게 각기 다르지만, 자연어 처리의 과정을 크게 두 가지로 나누어 볼 수 있습니다.


1. 언어 분석을 위해, 기본적인 단위로 문장을 나눈다.

2. 나눠진 단위를 수치로 표현한다.


하나씩 살펴보겠습니다.


1. 분석하고자 하는 문서를 '단위'로 나누기

1단계는 분석에 용이한 기초 단위로 나누는 과정을 의미합니다. 이를 'Tokenize'한다고 칭하며 이를 돕는 라이브러리들을 Tokenizer라고 부르는데요. 학창시절 국어 시간에 한국어 문장을 나누어 보신 경험이 있을 겁니다. 한국어의 경우 "의미를 이루는 기본 단위"를 형태소라고 하며, 다들 이 형태소를 분석하신 적이 있을 겁니다. Tokenizer가 하는 일이 이와 비슷합니다. 분석에 용이한 단위로 문서 내의 단어들을 나누는 작업을 합니다.

사진=스마일게이트


컴퓨터가 Tokenize하는 과정에서 겪는 문제는 무엇이 있을까요? 우선 동의어가 문제입니다. '한 술 더 뜬다'에서 쓴 '뜬다'라는 동사는, '이 노래가 요새 뜬다'에서 사용된 '뜬다'라는 의미와 다릅니다. 그러나 Tokenizer에서는 이 둘을 구분하기가 어렵습니다. 신조어나 줄임말도 문제입니다. MZ세대들의 유행어인 '어쩔티비' 라는 단어를 생각해보시면 어쩔+티비의 결합일텐데요. '어쩔'과 '티비'로 나눠져버리면, 어쩔티비라는 단어의 맥락은 사라지고 맙니다.


신조어나 줄임말은 잘 나누지 못하는 기존의 Tokenizer

이처럼 대부분의 Tokenizer는 기본적인 문법 규칙을 기반으로 만들어져서, 문서를 분석 단위로 나누고자 하지만 한계가 있을 수밖에 없습니다. 어째서 다양한 특징을 반영하는 Tokenizer가 존재하기 어려운 걸까요? 이는 저번 글에서 다뤘던 이유 때문입니다. 언어 자체가 사람들이 산출해내는 복잡한 산물들이기 때문에, 예외적인 상황이 너무나도 많습니다. 나누는 규칙을 미리 정하지 않은채로, 문서 데이터를 주고 귀납적으로 규칙을 학습시키려고 해도 마찬가지입니다. 미리 학습시킬 데이터 자체가, 사람들이 만든 문서들이기 때문에 정확한 문법 규칙대로 작성될 리가 없습니다. 정확한 Tokenizer를 위해선 정확한 언어가 필요한데, 언어는 사람들이 사는 삶 그 자체라서 정확할 수 없기 때문에, 모든 영역에 만능인 Tokenizer는 존재하기 어렵습니다. 


그렇기 때문에 Tokenizer를 만들기 위해서는 사람의 손이 상당히 개입되게 됩니다. 학습 데이터를 마련하는 과정에서부터, 예외 규칙/신조어 등을 담은 '맞춤형 사전'이 필요하기 마련입니다. 이처럼 사람의 판단을 필요로하기 때문에, 자연어 처리리를 기반으로 한 소프트웨어에는 그 사용 목적과, 목적에 맞는 도메인 지식이 매우 중요합니다.


Medium에 올라온 글, Domain Knowledge는 결국 사람이 반영해야하는 영역


2. 분석의 단위를, 컴퓨터가 이해할 수 있는 '숫자'로 만들기

지금까지 우리는 Tokenizer를 이용해 문서를 분석의 단위로 나누었습니다. 다음은 그 단위에 '수치'를 부여해서 컴퓨터가 계산할 수 있도록 입력하게 됩니다. 여기서 수치란, 주로 벡터와 텐서 등 행렬로 표현되는 것들을 말합니다. 이러한 과정을 임베딩(Embedding)이라고 부르며 이 작업을 하는 라이브러리를 대체로 Embedder라고 합니다.


(사실, 정확하게는 앞서 언급드린 Tokenizer 역시도 Embedder를 활용합니다. 문서를 컴퓨터가 '단위로 구분'하기 위해서는, 애초에 문서 자체를 컴퓨터가 이해할 수 있는 수치로 변환해야하기 때문입니다. 단순한 형태의 Tokenizer는 단어 하나하나에 대응하도록 숫자를 매칭시키는 방식을 사용하기도 합니다.)


임베딩을 위한 가장 쉽고 간단한 방법은 무엇이 있을까요? 문서에서 단어가 몇 번 출현했는지를 세서 수치화하는 방법이 있을 겁니다. (여기서는 분석의 단위=단어라고 두었습니다.) 단순한 모델로는 BoW(Bag of Words)라는 방법이 있습니다. Bag of Words는 각 단어가 문서에서 출현한 빈도를 행렬로서 표현합니다. 아래 예시를 살펴보겠습니다.



위와 같은 4개의 문장을 마련했는데요. 각 문장을 Tokenizer를 이용해 나누고, BoW로 표현하고자 합니다. 여기선 Okt라는 오픈소스의 Tokenizer를 활용했으며, 문장에서 명사만 추출해 나누려고 합니다. 그런데 문제가 발생합니다.



Tokenizer의 기본 세팅된 값으로 나누려고 하면, '자고', '가기' 등 명사가 아닌 것 같아보이는 녀석들이 함께 나타납니다. 이를 제외하기 위해서는 직접 예외를 지정해주어야하는데요. 이러한 것들이 바로 사람의 판단이 개입되는 영역입니다. 예외를 지정한 뒤의 결과는 아래와 같습니다.



위의 예시에서 벡터를 만든다면 어떻게 만들 수 있을까요? 1번 단어가 1열, 2번 단어가 2열,....이런 식으로 규칙을 정해서 분석하고자 하는 모든 단어의 수와 문장의 수가 전체 크기가 되는 행렬(4X4)을 만들 수 있겠습니다.


가장 좌측의 0, 1, 2, 3은 각각 1번 문장, 2번 문장 3번 문장, 4번 문장을 의미합니다. 1번 문장인 "집에 가고 싶다"에서는 집이라는 단어가 1번 출현했으며, 마지막 4번 문장인 "집이 제일 좋다. 집에서 자고 싶다"에서는 집이라는 단어가 두번 출현했음을 알 수 있겠습니다.


이런 식으로 BoW를 활용하면, 문장마다 출현한 단어의 빈도를 계산하여 어떤 문장이 비슷한지를 판별할 수 있는데요. 이 문장이 IT와 관련된 내용인지, 축구와 관련된 내용인지 등등을 분류하는 것도 가능합니다.


BoW의 경우 등장한 단어의 수를 단순히 셌는데요. 사람들이 전반적으로 많이 쓰는 단어의 점수를 낮추고, 특정 문서에서 특별히 많이 쓰는 경우 가산점을 주는 방식 등 다양한 변주도 가능합니다. 이 방식은 TF-IDF라고 불리는 유명한 방식인데요. 파이썬 라이브러리에서 TF-IDF로 문서를 벡터로 만드는 과정을 쉽게 실습할 수 있습니다. 아래 그림은 위의 문장 예시들을 활용한 TF-IDF 실습 결과물입니다.


보시면 TF-IDF는 1번 문장과 4번 문장에서 공통되게 나타나는 '집'이라는 단어에는 패널티를 부여해서 점수가 낮고, '회사', '제일' 등 전체 문서에서 적게 출현하는 단어에는 높은 점수를 부여함을 확인하실 수 있습니다. 


BoW든 TF-IDF든 공통점은 단순합니다. 컴퓨터가 이해할 수 있도록 단어를 수치의 형태(행렬, 벡터)로 만든다는 것입니다. 물론 더 복잡한 방식도 있으므로, 조금 더 소개하고자 합니다.


BoW나 TF-IDF의 경우에는 단순히 등장한 단어를 세는 방식이라, 단어의 출현 순서나 문서의 맥락을 고려하지 않는다는 단점이 있습니다. 그런데 '문서의 맥락'을 고려하여 수치화하는 것도 역시 가능합니다. 문서의 맥락, 문장의 의미는 상당히 추상적인 개념인데요. 그렇기 때문에 수치화하기 위해서는 상당히 많은 양의 연산을 필요로 합니다. 딥러닝 등의 테크닉이 주로 활용되는 이유입니다.


최근까지 각광받고 있는 모델인 구글의 BERT, 그리고 그 기반이 되는 어텐션 메커니즘이 한 예시입니다. 이러한 기법들은 디테일하게 요약하기엔 상당히 복잡하지만, 간단히 동작 원리늘 표현하자면 다음과 같습니다. (이 부분은 넘어가셔도 무방합니다.)


분석하고자하는 문서를 D라고 합시다. 그리고 D로부터 만들어진 벡터나 행렬을 y라고 하면, 문서를 수치로 변환하는 과정을 y = f(D)라는 함수의 형태로 둘 수 있겠습니다. 이 f를 어떻게 잘 만들어서(혹은 잘 만들어진 f를 활용해서), D를 y로 잘 바꾸는 지에 대해서 우리는 관심을 두고 있겠지요. 


여기서는 f가 어떻게 만들어지는지, f가 내부에서 어떻게 동작하는지(=임베딩하는지)를 간단히 짚고 넘어가고자 합니다.


우선 문서 D를 Tokenizer를 이용해 단위로 나눕니다. 이 단위를 a, b, c라고 하면 각 단위들은 행렬로 표현됩니다. 다음은 a, b, c를 이용해서 행렬연산을 합니다. 이때 단어가 출현한 순서를 반영할 수 있도록 합니다. 이를테면 처음에는 행렬 A를 이용해 Aa라는 연산 결과물을 내고, 이 연산 결과물로부터 B(Aa+b)를 또 연산하고, 다음은 C(B(Aa+b)+c)...쭉쭉쭉 이런 방식으로 진행됩니다.'

이렇게 연산으로 얻어진 결과물(행렬)들이 있을 텐데요. 그 결과물들을 k1, k2, k3...라고 하면 최종적으로 이 k들의 가중치를 합해서 y를 구합니다. y = w1*k1 + w2*k2....이런식으로 표현될텐데요. 그러면 우리가 f(D) = y를 얻기 위한 과정에서 해야할 일은 무엇일까요? D가 y로 변환되는 과정에서 A, B, C 등의 행렬을 사용했으며, 가중치 w1, w2, w3...등등도 역시 사용되었습니다. 즉, 딥러닝 기법을 통해 문서를 '수치'로 변환한다는 것은, 문서 데이터를 학습시켜 적절한 A, B, C 및 w1, w2, w3의 값을 결정하는 것을 의미합니다.

좀 복잡했는데요. 자연어 문서를 수치로 만드는 과정을 요약하면 다음과 같습니다. 문서를 수치로 바꾸기 위해서(=임베딩하기 위해서)는 행렬 연산이 필요하고, 그 연산을 적절히 잘 수행하는 행렬(혹은 가중치)을 잘 학습시켜 구하거나, 이미 잘 학습된 모델을 사용하게 됩니다.




위 예시는 간략하게 요약된 것이며, Embedder들은 디테일하게 다양한 기법들을 활용하고 있습니다. 그러나 Bow든, TF-IDF든, 딥러닝 기반의 Embedder를 쓰든 자연어 처리의 전 과정은 맨 처음 말씀드린 대로 두 가지로 나눌 수 있습니다.


1) Tokenizer를 이용해 문서를 단위로 나눈다. 

2) 행렬 연산을 통해 그 단위를 수치화한다(=임베딩한다).


그리고 더욱 중요한  마지막 스텝이 있겠죠? 3) 임베딩한 수치를 기반으로 여러가지를 계산하여 필요한 결과물을 얻는다. 


임베딩된 수치를 바탕으로, 다음에 어떤 문장이 나타날지, 문서의 토픽은 무엇인지 등등을 계산할 수 있습니다. 계산된 수치는 다시 역변환되어 사람이 이해할 수 있는 자연어로 표현되게 됩니다.


Embedder 역시 마찬가지로 학습 데이터의 영향을 강하게 받을 수 밖에 없습니다. 문서를 수치로 변환하기 위해, 내부의 파라미터(가중치 등)를 결정하는 과정에서 '이미 답이 주어진' 학습 데이터를 활용하게 됩니다. 무엇을 어떻게 학습시킬지는 결국 사람이 결정합니다. 또한 Embedder가 잘 작동하고 있는지, 그럴듯한 결과물을 내는지 결국 평가하는 것도 사람입니다. 그런데 완벽한 사람도 완벽한 언어도 없기 때문에, 결국 완벽한 사람처럼 작동하는 Embedder가 있기는 대단히 어렵습니다. 특정 목적에서는 비교적 잘 작동하지만요.


Embedder의 성능을 결정하는 한 가지 요인은 컴퓨팅 파워(연산 능력)입니다. 대규모의 행렬 연산을 활용하기 때문에, 행렬의 크기가 커서 많은 변수를 포함할수록 예측도는 높아집니다. 성능이 뛰어나면 연산을 빠르게 수행할 수 있습니다. 이렇게 잘 만들어진 AI는 사람의 언어 구사에 상당히 근접한 모습을 보여줍니다. 많은 데이터를 학습시켜서, 행렬 계산에 사용되는 파라미터를 정교하게 세팅하면, 맨 처음 이루다의 예시와 같이 사람과 유사한 정도의 대화도 가능하게 될 것입니다.


딥러닝에 요구되는 컴퓨팅 파워, Thompson et al.


저는 한가지 재밌는 의문이 드는데요. 역사적으로, 문서의 맥락이나 의미를 파악하는 것은 인간 고유의 직관으로 여겨져 왔습니다. 추상적인 언어 표현을 이해하는 것은 인간(혹은 소수의 동물)만이 가능한 것이라고 생각됐는데요. 매우매우 크고 복잡한 행렬 연산을 수행하는 컴퓨터는, 마치 사람처럼 '의미를 고려한' 결과를 내놓습니다. 그렇다면 결국 사람의 언어 능력도 복잡한 연산의 과정에서 나타나는 것이 아닐까요? 사람의 뇌는 수백 조 단위의 시냅스로 연결되어 있다고 하니, 엄청난 컴퓨터인 셈이니까요. 추상적 언어 표현, 언어적 직관이 그러하다면, 사람이 느끼는 감정 혹은  감정 표현도 뉴런 연산의 결과물로 나타나는 것일지도 모릅니다. 너무 복잡하고 거대해서 쉽게 규명할 수는 없겠지만요.


이런 점에서 아직 컴퓨터 프로그램의 '인공지능'은 갈 길이 먼 것 같습니다. 정말 많은 데이터를 학습시켜서, 대용량의 파라미터를 바탕으로 결과를 내놓는 프로그램일지라도, 여전히 감정이나 직관과 같이 더 복잡한 상호작용의 산물들을 보여주지는 않는 것 같습니다. 그렇지만 이루다의 사례를 보면, 어떤 영역에서는 상당히 인간과 비슷해지고 있는 것 같다는 생각도 듭니다.


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari