R로 나이브 베이즈 규칙을 사용하여 트위터 분류 모델 만들기

안녕하세요~ LOPES의 배서연 입니다. 저번에 올린 글이 반응이 나쁘지 않아 용기 내어 올려 봅니다. LOPES 스터디 그룹에서도 각자 R을 이용하여 정리한 내용을 공유 차원에서 올릴 예정입니다. 어설프더라도 예쁘게 봐 주세요~^^

이번에는 『DATA SMART』 3장에 나오는 나이브 베이즈(Naive Bayes)를 이용한 트윗 분류하기를 R로 구현해봤습니다. 저희 팀에서는 R로 구현하는 방법을 독자적으로 해결하고자 하다가 다음과 같은 곳을 발견하여 R로 쉽게 구현할 수 있었습니다. 나중에 나오는 설명과 코드 또한 아래 링크에서 많은 부분 빚졌음을 밝혀둡니다. 그리고 두 번째 링크는 동일한 내용을 다르게 해결하는 곳이 있어 추가합니다.

Tweet Bayes Classification: Excel and R : Salem Marafi

Data Smart, Ch3, Classifying Tweets using Naive Bayes – Working Analytics

R로 풀어볼 나이브 베이즈(Naïve Bayes) 모델은 소위 ‘지도 학습법(supervised learning)’에 속합니다. 주로 스팸 메일을 구분할 때 사용합니다. 스팸 메일에서 자주 등장하는 단어들의 확률 값을 통해 다른 메일이 스팸 메일인지 아닌지를 판단할 수 있는 방법입니다.

『DATA SMART』 3장에서는 나이브 베이즈(Naïve Bayes) 모델을 이용해 트위터에서 맨드릴(Mandrill)이라는 단어가 언급되는 경우, 새로 출시한 앱(app)인 맨드릴Mandrill에 대한 트윗인지 아니면 관련없는 것들을 언급하는 것인지 설명하고 있습니다. 진행 과정은 다음과 같습니다.

새로 출시한 ‘맨드릴(Mandrill)’이라는 서비스를 이야기하는 트윗은 (App)이라고 표기하고 다른 것들은 이야기하는 트윗은 (Other)으로 표기하겠습니다. ‘맨드릴(Mandrill)’이라는 단어를 언급하는 트윗을 조사했더니, 예를 들어 그 트윗에는 단어1, 단어2, 단어3 등등으로 구성되었다고 생각해 봅시다. 이때 맨드릴 서비스를 이야기하고 있는 150개의 트윗들을 가지고 있는 문장 안의 단어들을 다 조사할 수 있습니다. 이때 각각의 맨드릴 서비스를 이야기하고 있는 각각의 트윗이 단어1, 단어2, 단어3 등등을 가지고 있는 확률과 다른 것을 이야기하고 있는 트윗이 단어 2, 단어 4, 단어 6 등등을 가질 확률은 다음과 같습니다.

P(App | 단어 1, 단어 3, 단어 5, …)

P(Other | 단어 2, 단어 4, 단어 6, …)

이 확률 조건표를 가지고 후에 테스트할 트윗이 앱에 대해 이야기하는 것인지 아니면 다른 것을 이야기하고 있는지 확인할 수 있습니다. 테스트 할 트윗 단어들이 앱에 관련될 확률과 아니면 다른 것에 관련될 확률을 비교해 가장 확률이 높은 클래스로 분류시키는 것이 바로 최대 후험 규칙(MAP rule, Maxium A Posteriori rule)입니다. 이렇게 만들기 위해서는 베이즈 규칙을 적용하되 바보같이 각 단어들이 문맥과 상관없이 독립적인 사건이라고 가정하여 확률을 구합니다. 이를 나이브 베이즈 규칙이라고 합니다. 예를 들어 감기와 콧물이 문맥 상 전혀 관계없는 단어가 아니더라도 일단 둘 다 독립적인 사건으로 가정하면 다음과 같이 규칙을 쓸 수 있습니다.

P(스팸메일|감기, 콧물 ) = P(감기|스팸메일) * P(콧물|스팸메일)

이를 활용하여 새로 테스트할 트윗의 각 단어들(단어1′, 단어2′, 단어3’…)이 앱에 관련된 것인지 아닌 지에 대해 비교할 확률은 다음과 같이 구할 수 있습니다. 이 두 개의 값 중 더 큰 값에 따라 클래스를 분류할 수 있습니다.

P(App | 단어1′ , 단어2′, 단어3′ …)
= P(App) * (단어1’|App) * P(단어2’|App) * P(단어3’|App)…

P(Other |단어1′ , 단어2′, 단어3′ …)
=P(Other) * 단어1’|Other) * P(단어2’|Other) * P(단어3’|Other)…

재밌게도 나이브 베이즈 모델은 대충 부정확한 가정을 세워도 후에 어느 것이 더 큰 확률이냐를 비교하기 때문에 이상하지만 비교적 정확한 모델이라고 평가 받고 있습니다. 그럼 앞의 과정에 따라 R 로 예제 트윗을 분류해봅시다.

1) 첫 번째 단계: 데이터를 가져와 깨끗하게 정리하기

『DATA SMART』 3장에서는 엑셀로 작업하고 있지만, 우리는 R에서 작업을 하기 때문에 엑셀 파일에 들어 있는 자료를 CSV 형식 파일로 바꿔서 가져오겠습니다. 이 자료는 맨드릴(Mandrill) 앱(App)에 관련된 트윗과 관련되지 않은(Other) 트윗, 각각 150개로 되어 있습니다. 사용환경에 따라 아래 링크에서 다운 받으시면 됩니다. Mandrill.csv, Other.csv, Test.csv 이렇게 파일 3개가 있을겁니다.
윈도우 사용자분들은 아래 링크에서 다운 받을 수 있습니다.

https://www.dropbox.com/sh/1k3df26mbwwbtcn/AACnrKwvjg6GGtriRAWXWZ-6a?dl=0

맥 사용자분들은 아래 링크에서 다운 받을 수 있습니다.

https://drive.google.com/file/d/0B7Ov50qkZFF5NnJHNzZLalYtcVU/view

혹여 파일에 깨져서 실행이 안되는 분들은 책 홈페이지인 Wiley: Data Smart: Using Data Science to Transform Information into Insight – John W. Foreman에서 Chapter 3 파일을 다운 받으시면 됩니다. 아니면 아래 여기, ch03.zip을 눌러서 다운 받으시면 됩니다. 받으신 파일을 압축을 푼 다음 엑셀에서 열어서 csv 파일로 바꿔주세요. 위의 파일을 다운 받으시면 그 안에 AboutMandrillApp / AboutOther / AppTokens 시트에 A열 만 떼내 각각 Mandrill.csv / Other.csv / Test.csv 파일로 저장한 뒤 진행하면 됩니다 ^^

2) 가져온 파일을 컴퓨터가 이해할 수 있도록 처리하기

가져온 파일은 단순하게 각각 150열로 되어 있는, 즉 각각 150개 트윗으로 되어 있는 자료입니다. 앞에서도 이야기했지만, 우리는 각각의 트윗이 가지고 있는 단어를 가지고 분석할 예정입니다. 따라서 우리가 가지고 있는 모든 트윗에서 단어를 다 분리해야 합니다. 『DATA SMART』에서는 엑셀을 이용하여 조금은 복잡하게 단어로 분리하고 있습니다. 그러나 R에서는 ‘tm’이라는 패키지를 이용해서 엑셀에서 하는 것보다는 쉽게 분리할 수 있습니다. 우선 단어로 쪼개기 전에 트윗이 가지고 있는 모든 단어를 소문자로 바꾸고 문장 부호를 빈칸으로 교체합니다. 아래 코드에서는 replacePunctuation()이라는 함수를 만들어서 대문자를 소문자로 만들고, 문장 부호, “.”, “: ”, “?”, “!”, “;”, “,”을 모두 빈칸으로 바꿉니다. 이 함수 안에서는 tolower()는 대문자를 소문자로, gsub()는 문장 부호를 빈칸으로 바꿔주는 역할을 합니다. 이렇게 하는 이유는 ~ 그 다음 빈칸을 기준으로 단어들을 분류 시켜 하나의 matrix로 만들어줍니다.

토큰화 된 단어의 길이를 구해 의미 없는 단어들은 Cut-off 하기

컷 오프하는 방식은 데이터를 다룰 때 언제나 중요하게 다뤄지는 것 같습니다 ^^. 여기서는 글자 수 3개 이하의 토큰들을 다 컷오프시킵니다. 단순하게 말하자면 영어 단어 중 3글자 아래는 모두 지웁니다. 영어 단어 중 3글자보다 작은 단어는 통상적으로 의미가 없는 단어이기 때문입니다. 보통 이런 작업에서는 몇 개 이하의 짧은 단어를 제거하는 대신 because, instead와 같이 멈춤 단어(stop words)라고 불리는 단어들을 컷오프 하기도 합니다.

3) 나이브 베이즈 모델을 이용하여 분류 모델 만들기 – 각 단어의 App/Other에서 조건부 확률 구하기

본격적으로 앱에 관련된 단어들과 아닌 단어들의 빈도를 확률로 계산하기 전에 크게 두 가지 문제를 풀어야 합니다. 첫 번째로 트윗에 거의 나오지 않은 드문 단어들을 처리 해야 하고, 두 번째로 이런 드문 단어들의 아주 작은 확률 값이 컴퓨터가 잘 처리할 수 있도록 처리해야 합니다.

가산 평활

앱에 관련된 트윗과 아닌 트윗에서 나오지 않은 단어가 후에 나왔다면 이 단어는 어느 쪽에 속해야할까요? 이런 단어는 나이브 베이즈 확률값을 계산할 때 전체 확률 값을 0으로 만들기 때문에 일단 이런 드문 단어는 한 번 나온 것으로 가정합니다. 그러면 정말 한 번 나타난 단어들의 확률이 불공정해지기 때문에 모든 단어들의 빈도 수에 1씩 더해줍니다. 이렇게 쭉 1씩 더해주는 방식을 가산 평활법(addictive smoothing)이라고 합니다. (다른 책에서는 라플라스 추정기라고 하기도 하더군요^^)

자연 로그 값으로 변환

예컨대 확률이 0.001인 단어들이 여러 개 있다면 후에 단어들의 확률을 곱해서 클래스를 비교할 때 소숫점 수 백 개 이하의 0이 나오게 됩니다. 이 경우 컴퓨터가 제대로 처리하지 못하기 때문에 언더 플로라고 하는데 이를 해결하기 위해 0과 1 사이의 값에 대해서 자연 로그를 취합니다, 그러면 소수점 몇 백 개 이하의 수가 아니라 비교적 잘 와 닿는 음수가 되게 됩니다. 그 다음 최대 후험 비교를 수행하면 컴퓨터가 무리가 가지 않게 처리할 수 있습니다.

이제 2 가지 문제를 해결했으면 이제 R에서 트윗의 각 단어들이 App에 들어갈 확률과 그 외에 들어갈 확률을 구해줍니다.

4) 앞에서 만든 모델을 가지고 새로운 트윗들이 어디에 속하는지 예측해보기

앞에서 나이브 베이즈 모델을 이용하여 만든 분류 모델은 사실상 두 개의 조건부 확률 표를 만드는 것에 불과합니다. 이제 이 확률표라는 증거들을 가지고 새로운 트윗이 App에 관련된 트윗인지 아닌지 분류해봅시다. 테스트할 20의 트윗은 반은 App에 관련된 트윗이고 반은 상관 없는 트윗으로 이루어져 있습니다. 앞에서 한 토큰화, 가산 평활, 자연 로그 과정을 거친 뒤 이미 만들어 놓은 조건부 확률을 통해 그 단어들이 app에 관련될 확률과 그 외에 관련된 확률을 구해 비교합니다. 비교했을 때 확률 값이 더 큰 쪽으로 클래스를 분류합니다. 이제 정말 이 모델이 정확한 지 확인해 봅시다. 20개 중에 하나 빼고는 다 맞혔네요^^ R로 구현하는 코드는 다음과 같습니다. 윈도우 사용자 분들은 R코드 마지막 줄에 View(cbind(classified,tweets.test$Tweet))으로 하시면 더 멋진 결과를 보실 수 있습니다. 긴 글 읽어주셔서 감사합니다~!

Leave a Reply