Skip to main content

문서 단어 행렬

의미

의미가 정확히 무엇인지 모르지만, 대략적으로 비슷한 단어들이 있다. 이를 동의어라고 한다. 동의어란 문장에서 그 단어를 다른 단어로 바꿔도 의미가 변하지 않는 경우를 말한다.

의미의 철학적 의미

철학자들이 항상 논쟁하는 주제 중 하나는 의미에 관한 것이다. 대략적으로 주류설은 어떤 문장이 있을 때, 그 문장의 의미는 그 문장을 참으로 만드는 조건이라고 본다. 이를 진리 조건 의미론이라고 한다.

하지만 이 이론에도 문제가 있다. 철학자 버트란드 러셀은 이런 질문을 던졌다. "현재 프랑스의 왕은 대머리다"라는 문장의 의미가 무엇인가? 이는 철학자들이 주로 하는 방식으로, 자신도 답을 모르면서 상대방에게 대답하라고 하는 것이다.

철학적 의미에서 의미란 어떤 문장을 참으로 만드는 조건이다. 그런데 "현재 프랑스의 왕은 대머리다"라는 문장은 참인가 거짓인가? 보통 거짓인 문장을 부정하면 참이 되지만, "현재 프랑스의 왕은 대머리가 아니다"라고 해도 이상하다. 애초에 프랑스 왕이 없기 때문이다.

이 문장은 참도 아니고 거짓도 아닌 것 같다. 그렇다고 의미가 없다고 하기도 어렵다. 이런 문제 때문에 철학자들은 계속해서 의미에 대해 논쟁한다. 어떤 정의를 제시하면 다른 사람이 반례를 들고, 또 다른 정의를 제시하면 또 다른 반례가 나오는 식으로 논쟁이 계속된다.

대조의 원칙

'경찰'과 '짭새'는 동의어처럼 보이지만, 상황에 따라 의미가 달라질 수 있다. "어머님, 경찰이 찾아왔어요"와 "어머님, 짭새가 찾아왔어요"는 다른 뉘앙스를 준다.

이를 '대조의 원칙'이라고 하는데, 형태가 달라지면 의미도 변한다는 것이다. 완전히 동일한 의미를 가진 단어는 사실상 없다. '밥'과 '식사'도 동의어지만, "부장님 밥 먹었어요?"와 "부장님 식사하셨어요?"는 다른 의미를 갖는다.

중요한 점은 의미라는 것이 매우 어렵지만, 대충 비슷한 단어는 있다는 것이다. 우리는 의미가 정확히 무엇인지 모르더라도, 두 단어의 의미가 비슷한지는 대답할 수 있다.

사람들은 종종 본질을 알아야 한다고 생각하지만, 대부분의 경우 우리는 본질을 모른다. 우리는 실용적인 것만 알 수 있다. '사랑'이나 '인생'과 같은 본질적인 개념에 대해 정확한 답을 내기는 거의 불가능하지만, 우리는 그것들에 대해 대충 알고 살아갈 수 있다.

의미도 마찬가지다. 의미가 정확히 무엇인지는 모르지만, 대충 의미가 비슷하거나 통하는 단어들이 있다. 그러나 완전히 똑같은 것은 아니다. 단어들이 연관되는 방식은 여러 가지가 있으며, 여기서는 두 가지 예만 들었지만 그 외에도 많은 방식이 있다.

의미장

의미장은 특정 범위의 단어들을 포함하는 개념이다. 예를 들어, 레스토랑과 관련된 단어들(메뉴, 테이블, 음식, 접시, 서버, 술 등)이 하나의 의미장 안에 포함된다. 같은 의미장에 있는 단어들은 서로 연관되어 있다.

의미장과 관련해 재미있는 현상이 있다. 예를 들어, 병원과 관련된 단어들을 나열하면(의사, 병원, 침대, 붕대, 약, 주사, 주사기, 엑스레이, 처방전, 약국, 수술, 소독 등), 실제로 언급하지 않은 단어(예: 간호사)도 들은 것 같은 착각이 생길 수 있다. 이는 같은 의미장 안에 있는 단어들을 들으면 우리 뇌가 그 의미장에 있는 모든 단어를 전체적으로 활성화시키기 때문이다.

이러한 현상은 사기 수법에도 활용될 수 있다. 직접적으로 말하지 않고 관련된 단어들을 늘어놓아 청자로 하여금 특정 단어를 들었다고 착각하게 만드는 방법이다. 예를 들어, '보장'이라는 단어를 직접 사용하지 않고도 그와 관련된 단어들을 사용해 마치 보장한다고 말한 것 같은 인상을 줄 수 있다.

의미장에 대한 이해는 이런 종류의 사기를 피하는 데 도움이 될 수 있다. 토픽 모델링은 어떻게 보면 의미장을 발견하는 기법이라고 할 수 있다. 토픽과 의미장이 정확히 같은 개념은 아니지만, 의미장은 언어학에서, 토픽 모델링은 텍스트 분석에서 사용되며, 두 개념이 하고자 하는 바는 비슷하다.

의미 프레임

의미 프레임은 의미장과는 다른 개념이다. 의미장이 느슨하게 연관된 단어들의 집합이라면, 의미 프레임은 특정한 사건이나 장면에서의 역할들을 말한다.

예를 들어, '판매'라는 장면에는 파는 사람, 사는 사람, 물건, 그리고 대가로 주고받는 돈이 있다. 이러한 요소들을 의미역이라고 하며, 이들이 모여 의미 프레임을 구성한다.

의미 프레임을 통해 우리는 같은 상황을 다르게 표현한 문장들이 실제로는 같은 의미를 가지고 있음을 이해할 수 있다. 예를 들어, "내가 누구한테 뭘 팔았다"와 "누가 나한테 뭘 사갔다"는 표현은 다르지만 같은 프레임 안에서 같은 역할에 대해 말하고 있으므로 의미가 같다.

텍스트 분석에서 의미 프레임을 분석하는 기법들이 있다. 이를 통해 다양한 표현으로 된 문장들을 일정한 틀로 분석할 수 있다. 예를 들어, "A라는 회사가 신제품을 출시했다"와 "신제품이 A라는 회사에서 나왔다"는 같은 프레임을 가지고 있다.

이러한 분석은 챗봇과 같은 응용에서 유용하게 사용될 수 있다. 고객이 "물건을 주문하다", "물건을 달라고 하다", "물건을 요청하다" 등 다양한 표현을 사용하더라도, 이들이 같은 프레임에 속한다는 것을 인식하면 고객의 의도를 정확히 파악할 수 있다.

벡터 공간 모형

코노테이션

코노테이션(connotation)은 단어의 본질적 의미에 더해진 감정, 의견, 평가적 측면을 말한다. 예를 들어, '경찰'과 '짭새'는 같은 대상을 지칭하지만, '짭새'는 부정적인 감정이 묻어있다.

오스굿(Osgood)은 1957년에 코노테이션이 세 가지 차원으로 구성되어 있다고 제안했다:

  1. Valence: 좋다-나쁘다의 평가 차원
  2. Arousal: 감정의 강도 차원
  3. Dominance: 통제나 지배의 정도 차원

예를 들어, '용기'라는 단어는 긍정적이고(Valence), 감정 강도는 중간 정도이며(Arousal), 통제력이 강한(Dominance) 단어다. 반면 '겁쟁이'는 부정적이고, 감정 강도가 약하며, 통제력이 약한 단어다.

이러한 세 가지 차원으로 단어들에 점수를 매길 수 있다. 이는 마치 3차원 공간에서 위치를 좌표로 나타내는 것과 비슷하다. 예를 들어, 지구상의 위치를 위도, 경도, 고도로 나타내는 것처럼, 단어의 코노테이션도 세 가지 수치로 표현할 수 있다.

이렇게 표현된 단어들은 서로 비교할 수 있게 된다. 세 가지 차원의 수치가 비슷한 단어들은 비슷한 코노테이션을 가진다고 볼 수 있다.

분포 가설

여기서 단어의 의미를 숫자로 표현할 수 있다는 생각이 나오게 된다. 이 숫자가 객관적이라고 말하기는 어렵지만, 어쨌든 단어의 의미를 수치화할 수 있다는 아이디어가 발전하게 된다.

이 아이디어를 발전시키면 벡터 공간 모형이 나오게 된다. 벡터 공간 모형은 비슷한 맥락에서 사용되는 단어는 의미도 비슷하다는 관찰에서 시작된다. 예를 들어, '차'와 '커피'는 사용되는 맥락이 비슷하다.

분포 가설은 두 단어의 의미가 비슷하다는 것은 주변에 나오는 단어의 분포가 비슷하다는 것과 같다는 가설이다. 즉, 단어의 의미 자체는 모르더라도 그 단어가 사용되는 주변 단어들의 분포를 보면 의미의 유사성을 판단할 수 있다는 것이다.

이 분포 가설에서 벡터 공간 모형이 나오게 된다. 단어의 의미를 그 단어 주변에 어떤 단어가 얼마나 많이 나오는지를 수치화하여 표현할 수 있다는 것이다. 따라서 문서나 단어를 벡터로 표현하게 되는데, 여기서 벡터는 여러 개의 숫자로 이루어진 것을 말한다.

이렇게 표현하면, 두 문서가 비슷하다는 것은 그 문서에 나오는 단어들의 빈도가 비슷하다는 것을 의미하게 된다. 또한, 두 단어가 비슷하다는 것은 그 단어들이 나오는 주변 단어들의 통계가 비슷하다는 것을 의미하게 된다.

벡터 공간 모형

벡터 공간 모형은 단어나 문장을 벡터로 표현하는 여러 가지 방법을 통틀어 말한다. 여기서 벡터란 여러 개의 숫자를 모아놓은 것을 의미한다.

'공간'이라는 용어는 특별한 의미가 있는 것은 아니다. 수학에서 벡터를 여러 개 모아놓은 것을 벡터 공간이라고 부르기 때문에 사용된 용어다. 즉, '공간'은 단순히 벡터들의 모임을 의미한다.

따라서 벡터 공간 모형이란 단어들을 벡터로 표현하고 이들을 모아놓은 것을 말한다. 마찬가지로 문서를 벡터로 표현한다면, 문서들을 모아놓은 것도 벡터 공간이 된다.

결국 벡터 공간 모형은 벡터를 모아놓은 형태로 단어나 문서를 표현하겠다는 의미이다.

단어 자루 모형

문서 단어 행렬은 벡터 공간 모형이자, 단어 자루(Bag of Words) 모형이다.

글은 보통 단어가 순서대로 나열되어 있다. 예를 들어, "사람이 개를 물었다"와 "개가 사람을 물었다"는 들어가는 단어는 같지만 어순이 달라 의미가 완전히 다르다. 이는 언론계의 유명한 격언과 관련이 있다: "개가 사람을 물면 뉴스가 되지만, 사람이 개를 물면 뉴스가 된다."

그러나 단어 자루 모형은 이러한 어순을 무시한다. 텍스트를 모두 잘게 잘라 자루에 담는 것처럼 단어의 순서를 무시하고 각 단어가 몇 번 나왔는지만을 고려한다.

이 방법이 효과적일지 의문이 들 수 있지만, 실제로는 잘 작동한다. 사람들이 말할 때 항상 문법적으로 정확한 어순을 사용하지 않기 때문이다. 예를 들어, "밥 먹었어?"라는 질문에 대해 "어, 밥 먹었지?", "먹었지? 밥?", "먹었어? 밥?" 등 다양한 방식으로 대답할 수 있다. 이처럼 실제 대화에서는 어순이 그렇게 중요하지 않은 경우가 많아, 단어 자루 모형이 의외로 효과적일 수 있다.

단어의 사생활

단어 자루 스타일로 어순을 무시하고 단어만 세는 방식이 많이 사용된다. '단어의 사생활'이라는 책은 원제가 '대명사의 사생활'로, 저자가 평생 대명사만 연구한 내용을 담고 있다.

대명사 연구 결과, 영어에서는 권력에 따라 대명사 사용이 다르다는 것이 밝혀졌다. 권력이 강할 때는 'you'를, 약할 때는 'I'를 많이 사용한다. 한국어는 대명사 생략이 가능해 약간 다른 양상을 보인다.

이러한 분석은 정치 영역에서도 적용된다. 선거에서 이기는 후보는 'you'를, 지는 후보는 'I'를 많이 사용한다. 닉슨 대통령의 경우, 탄핵 과정에서 권력이 약해짐에 따라 'I'의 사용이 증가했다.

대통령 연설문 분석도 이루어진다. 뉴욕 타임즈 같은 언론사에서는 매년 대통령 연설문의 단어 통계를 분석하여 기사를 낸다. 예를 들어, 오바마 대통령이 사용한 주요 단어들을 분석하고 이를 역대 대통령과 비교한다.

이런 분석을 통해 정권에 따른 변화를 파악하고 미래를 예측할 수 있다. 한국에서는 아직 이런 분석이 활발하지 않지만, 대통령의 연설문 분석을 통해 정책 방향을 예측하는 등의 활용이 가능할 것이다.

문서 단어 행렬

벡터 공간 모형과 결합하여 단어를 세는 방식을 표로 정리할 수 있다. 이를 문서-단어 행렬이라고 한다. 이는 문서별로 어떤 단어가 몇 번 사용되었는지를 표로 정리한 것이다.

예를 들어, 두 개의 문서가 있다고 가정해보자:

  1. "오늘은 밥을 먹었다."
  2. "어제도 밥, 오늘도 밥."

이를 문서-단어 행렬로 표현하면 다음과 같다:

  • 문서1: 오늘(1), 어제(0), 밥(1), 먹다(1)
  • 문서2: 오늘(1), 어제(1), 밥(2), 먹다(0)

벡터는 이러한 수치들의 한 묶음을 말한다. 예를 들어:

  • '오늘'이라는 단어는 (1, 1)로 표현된다.
  • '어제'라는 단어는 (0, 1)로 표현된다.
  • '밥'이라는 단어는 (1, 2)로 표현된다.
  • '먹다'라는 단어는 (1, 0)으로 표현된다.

문서가 많아지면 이러한 벡터의 길이도 늘어나게 된다. 이때 숫자 패턴이 비슷하면 그 단어들의 의미도 비슷하다고 볼 수 있다.

문서도 벡터로 나타낼 수 있다. 하나의 문서를 하나의 숫자 묶음으로 표현하는 것이다. 이렇게 표현된 문서들 중 숫자 패턴이 비슷한 것들은 의미가 비슷하다고 볼 수 있다.

문서-단어 행렬에서 행 방향으로 보면 문서 벡터가 되고, 열 방향으로 보면 단어 벡터가 된다. 이것이 벡터 공간 모형의 한 형태가 되는 것이다. 이는 텍스트 분석의 가장 기본이 되는 형태이며, 현재 벡터 공간 모형 중 가장 대표적인 형태이다.

문서 단어 행렬을 만드는 절차

문서-단어 행렬을 만드는 절차는 여러 단계로 나뉜다. 먼저, 문서의 단어를 끊어야 한다. 때로는 단어보다 작은 단위인 형태소로 끊기도 한다.

그 다음, 조사와 같이 문법적 기능만 하는 요소들은 보통 제외한다. 이는 조사 자체로는 의미가 크지 않기 때문이다.

영어의 경우, 현재형과 과거형 같은 형태를 합치기도 한다.

단어를 개별적으로 사용할 때도 있지만, 단어 묶음으로 사용할 때도 있다. 이를 N-gram이라고 한다. 예를 들어, '인공지능'은 두 단어로 볼 수도 있지만, 자주 함께 나오므로 하나의 단위로 취급할 수 있다. 두 단어를 묶은 경우 2-gram 또는 bigram이라고 한다.

마지막으로, 문서를 표로 나타낼 때 Binary Encoding과 Count Encoding 방식이 있다. Count Encoding은 단어가 나온 횟수를 그대로 세는 방식이고, Binary Encoding은 단어의 출현 여부만을 1과 0으로 표시하는 방식이다. 예를 들어, "밥 먹었어? 밥 먹었지?"에서 '밥'이 두 번 나왔지만, Binary Encoding에서는 한 번만 나온 것으로 처리할 수 있다.

이러한 방식들은 상황과 데이터에 따라 응용할 수 있다. 예를 들어, 한 번이나 두 번 나온 경우는 1로 처리하고, 열 번 이상 나온 경우에만 2로 처리하는 등의 방식을 사용할 수 있다.

퀴즈

실습

파일 열기

import pandas as pd
df = pd.read_excel('yelp.xlsx')

pandas를 사용하여 엑셀 파일을 열 것이다. pandas는 엑셀 파일 등을 열 때 사용하는 파이썬 모듈이다.

import pandas는 pandas를 가져오라는 의미이다. pandas라는 이름이 긴 편이라 보통 약칭을 붙인다. as pd는 약칭을 붙이는 것인데, 이는 필수는 아니지만 관습적으로 많이 사용한다. 인터넷에서 pandas 관련 정보를 찾아보면 대부분 이렇게 줄여서 부른다.

약칭을 사용할 때는 통용되는 관습을 따르는 것이 좋다. 예를 들어, 모두가 'pd'로 줄여 부르는데 혼자 'pa'로 줄여 부르면 이상하게 보일 수 있다. 이는 중요한 것은 아니지만, 남들이 하는 대로 하는 것이 전문성을 보여줄 수 있다.

엑셀 파일을 불러오려면 pd.read_excel()을 사용한다. 여기서 '.'은 파이썬에서 소속을 나타낸다. 'pandas의 read_excel'이라고 읽으면 된다. 'yelp.xlsx'는 파일 이름이다. 이렇게 하면 파일을 읽어서 'df'라는 변수로 불러오게 된다.

데이터 보기

df.head()

df.head()를 추가하면 파일을 불러오고 데이터의 앞부분을 보여줄 것이다.

엘프(Yelp)는 맛집 리뷰 사이트이다. 여기서 리뷰를 가져온 것이다. 별점은 단계가 많아서 분석하기 어렵기 때문에, 여기서는 감성(sentiment)을 '좋다', '싫다'로 단순화했다.

보통 관습적으로 1은 긍정, 0은 부정을 나타낸다. 이는 머신러닝 전반에서 사용되는 관습이다. 1은 뭔가 좋은 것이거나 있는 것을, 0은 나쁜 것이거나 없는 것을 의미한다.

예를 들어, 'love'가 나오면 긍정적인 것이고, 'not good'이라고 하면 부정적인 것이다. 이런 식으로 리뷰를 볼 수 있다.

리뷰를 가지고 감성을 분석하는 것은 나중에 할 예정이고, 일단은 리뷰만 분석해볼 것이다.

CountVectorizer

from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(max_features=500, stop_words='english')

sklearn은 Scikit-Learn이라는 파이썬 머신러닝 라이브러리이다.

'feature extraction'은 데이터의 특징을 뽑아내는 것을 의미한다. 여기서는 텍스트와 관련된 기능들을 사용한다.

CountVectorizer는 텍스트에서 단어를 세어 벡터로 만드는 도구이다. 'max_features=500'은 가장 자주 나오는 500개의 단어만 사용하라는 의미이다. 자주 나오는 순서대로 500개를 선택하고, 나머지는 버린다.

'stop_words'는 분석에서 제외할 단어들을 의미한다. 'english'로 설정하면 영어의 관사, 전치사, be동사 등 일반적으로 분석에 크게 중요하지 않은 단어들을 자동으로 제외한다. 이는 고객 리뷰 분석에 큰 도움이 되지 않는 단어들을 제거하는 것이다.

그러나 항상 이런 단어들을 제외하는 것이 좋은 것은 아니다. 예를 들어, 화자의 권력 차이를 분석하고 싶다면 'I'나 'you' 같은 대명사를 제외하면 안 된다. 하지만 일반적인 고객 리뷰 분석에서는 이러한 정보가 필요하지 않으므로 제외한다.

한국어의 경우 'korean'이라고 설정해도 작동하지 않는다. 한국어 처리는 따로 설정해야 하며, 이는 나중에 다룰 예정이다.

fit_transform

dtm = cv.fit_transform(df['review'])

CV는 CountVectorizer를 설정한 것이다. CV에 df의 'review' 컬럼을 입력으로 주게 된다.

여기서 사용된 함수 이름은 'fit_transform'이다. 'fit'은 데이터에 맞춰 표의 모양을 짜는 것을 의미한다. 옷가게의 'fitting room'처럼 데이터에 맞추는 것이다. 'transform'은 실제로 그 표의 내용을 채우는 것을 의미한다.

이 함수들은 세 가지 형태로 존재한다:

  1. fit_transform(): 표의 모양을 짜고 내용도 채운다.
  2. fit(): 표의 모양만 짜고 내용은 채우지 않는다.
  3. transform(): 이미 짜놓은 모양에 숫자만 채운다.

이렇게 나누어 놓은 이유는 새로운 데이터가 들어왔을 때 유용하기 때문이다. 기존 리뷰로 표를 만들어 놓은 후, 새로운 데이터가 들어오면 기존 표와 같은 모양으로 만들어야 비교가 가능하다. 이때는 fit_transform()을 하지 않고 transform()만 하면 된다.

이렇게 해서 만들어진 dtm은 문서-단어 행렬(Document-Term Matrix)이 된다.

cv.get_feature_names_out()

이렇게 하면은 이제 말 그대로 feature names, 단어 이름을 보여다오 이런 얘기가 된다.

단어 총 빈도

word_count = pd.DataFrame({
'단어': cv.get_feature_names_out(),
'빈도': dtm.sum(axis=0).flat
})

dtm에 sum을 하고 axis=0을 지정하면, 표를 가로 방향으로 더하게 된다. 이는 각 단어의 총 빈도수를 계산하는 것이다. axis=1로 하면 세로 방향으로 더하게 되어 각 문서의 총 단어 수를 계산하게 된다.

axis의 숫자는 조금 헷갈릴 수 있다. axis=0은 실제로는 세로로 내려가면서 더하는 것이고, axis=1은 가로로 가면서 더하는 것이다. 이는 화살표 방향으로 더하는 것으로 이해하면 된다.

보통은 문서별 단어 수보다는 어떤 단어가 많이 나오는지에 더 관심이 있다. 예를 들어, 식당 리뷰에서 사람들이 주로 많이 언급하는 것이 무엇인지 알고 싶을 때 이 방법을 사용한다.

그러나 dtm.sum(axis=0)만 실행하면 단어 목록과 빈도수가 따로 나와서 보기 불편하다. 이를 해결하기 위해 pandas의 DataFrame을 사용하여 하나의 표로 만든다.

코드에서 .flat는 단어 합계를 구했을 때 나오는 형식을 DataFrame에 들어갈 수 있는 형태로 변환하는 역할을 한다. 이는 깊이 고민할 필요 없이 그냥 사용하면 된다.

결과적으로 '단어'라는 열에는 단어 목록이, '빈도'라는 열에는 각 단어의 총 빈도수가 들어가게 된다. 이렇게 만들어진 DataFrame은 word_count라는 변수에 저장된다.

정렬

word_count.sort_values('빈도', ascending=False).head()

'워드 카운트'를 정렬할 것이다. 'sort_values'는 데이터를 정렬하라는 의미이다. 여기서 'values'는 값 또는 데이터를 의미한다.

'빈도' 순으로 정렬하라고 지정했다. 'ascending=False'는 내림차순 정렬을 의미한다. 'ascending'은 오름차순이라는 뜻인데, 이를 False로 설정했으므로 내림차순이 된다.

오름차순은 숫자가 올라가는 순서, 즉 작은 수에서 큰 수로 가는 순서이다. 반대로 내림차순은 큰 수에서 작은 수로 가는 순서이다.

따라서 이 코드는 빈도가 큰 것부터 작은 것 순으로 정렬하라는 의미이다.

마지막의 'head()'는 정렬된 데이터의 앞부분만 보여달라는 의미이다.

이 코드를 실행하면 단어가 많이 나온 순서대로 정렬되어 상위 몇 개의 결과를 보여줄 것이다.