{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Author : Jeonghun Yoon"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Naive Bayes를 이용하여 영화 리뷰를 예측하는 감정 분류기를 구현하라. \n",
" - 0 : 부정\n",
" - 1 : 긍정"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. 데이터 생성"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# 영화 리뷰를 load한다. 사랑/장르라는 단어를 포함하고 있는 document를 load 한다.\n",
"reviews = pd.read_csv('./inputs/ratings_train.txt', delimiter='\\t')"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" id | \n",
" document | \n",
" label | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" 9976970 | \n",
" 아 더빙.. 진짜 짜증나네요 목소리 | \n",
" 0 | \n",
"
\n",
" \n",
" | 1 | \n",
" 3819312 | \n",
" 흠...포스터보고 초딩영화줄....오버연기조차 가볍지 않구나 | \n",
" 1 | \n",
"
\n",
" \n",
" | 2 | \n",
" 10265843 | \n",
" 너무재밓었다그래서보는것을추천한다 | \n",
" 0 | \n",
"
\n",
" \n",
" | 3 | \n",
" 9045019 | \n",
" 교도소 이야기구먼 ..솔직히 재미는 없다..평점 조정 | \n",
" 0 | \n",
"
\n",
" \n",
" | 4 | \n",
" 6483659 | \n",
" 사이몬페그의 익살스런 연기가 돋보였던 영화!스파이더맨에서 늙어보이기만 했던 커스틴 ... | \n",
" 1 | \n",
"
\n",
" \n",
" | 5 | \n",
" 5403919 | \n",
" 막 걸음마 뗀 3세부터 초등학교 1학년생인 8살용영화.ㅋㅋㅋ...별반개도 아까움. | \n",
" 0 | \n",
"
\n",
" \n",
" | 6 | \n",
" 7797314 | \n",
" 원작의 긴장감을 제대로 살려내지못했다. | \n",
" 0 | \n",
"
\n",
" \n",
" | 7 | \n",
" 9443947 | \n",
" 별 반개도 아깝다 욕나온다 이응경 길용우 연기생활이몇년인지..정말 발로해도 그것보단... | \n",
" 0 | \n",
"
\n",
" \n",
" | 8 | \n",
" 7156791 | \n",
" 액션이 없는데도 재미 있는 몇안되는 영화 | \n",
" 1 | \n",
"
\n",
" \n",
" | 9 | \n",
" 5912145 | \n",
" 왜케 평점이 낮은건데? 꽤 볼만한데.. 헐리우드식 화려함에만 너무 길들여져 있나? | \n",
" 1 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" id document label\n",
"0 9976970 아 더빙.. 진짜 짜증나네요 목소리 0\n",
"1 3819312 흠...포스터보고 초딩영화줄....오버연기조차 가볍지 않구나 1\n",
"2 10265843 너무재밓었다그래서보는것을추천한다 0\n",
"3 9045019 교도소 이야기구먼 ..솔직히 재미는 없다..평점 조정 0\n",
"4 6483659 사이몬페그의 익살스런 연기가 돋보였던 영화!스파이더맨에서 늙어보이기만 했던 커스틴 ... 1\n",
"5 5403919 막 걸음마 뗀 3세부터 초등학교 1학년생인 8살용영화.ㅋㅋㅋ...별반개도 아까움. 0\n",
"6 7797314 원작의 긴장감을 제대로 살려내지못했다. 0\n",
"7 9443947 별 반개도 아깝다 욕나온다 이응경 길용우 연기생활이몇년인지..정말 발로해도 그것보단... 0\n",
"8 7156791 액션이 없는데도 재미 있는 몇안되는 영화 1\n",
"9 5912145 왜케 평점이 낮은건데? 꽤 볼만한데.. 헐리우드식 화려함에만 너무 길들여져 있나? 1"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# 데이터 확인\n",
"reviews.head(10)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"neg = reviews[(reviews.document.str.len() >= 30) & (reviews.label == 0)].sample(3000, random_state=43)\n",
"pos = reviews[(reviews.document.str.len() >= 30) & (reviews.label == 1)].sample(3000, random_state=43)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"/anaconda3/lib/python3.6/site-packages/konlpy/tag/_okt.py:16: UserWarning: \"Twitter\" has changed to \"Okt\" since KoNLPy v0.4.5.\n",
" warn('\"Twitter\" has changed to \"Okt\" since KoNLPy v0.4.5.')\n"
]
}
],
"source": [
"# 형태소 분석기\n",
"import re\n",
"import konlpy\n",
"from konlpy.tag import Twitter\n",
"\n",
"okt = Twitter()"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"def parse(s):\n",
" s = re.sub(r'[?$.!,-_\\'\\\"(){}~]+', '', s)\n",
" try:\n",
" return okt.nouns(s)\n",
" except:\n",
" return []"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"neg['parsed_doc'] = neg.document.apply(parse)\n",
"pos['parsed_doc'] = pos.document.apply(parse)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" id | \n",
" document | \n",
" label | \n",
" parsed_doc | \n",
"
\n",
" \n",
" \n",
" \n",
" | 149713 | \n",
" 6674102 | \n",
" 정말 댓글에 속아서 보게된 시간 아까운 영화. 전체적으로 코믹적인 요소를 많이 넣으... | \n",
" 0 | \n",
" [정말, 댓글, 속, 시간, 영화, 전체, 코믹, 요소, 정말, 웃기, 스토리] | \n",
"
\n",
" \n",
" | 52541 | \n",
" 8514375 | \n",
" 점수를 줘야 하는거임? 30년후 구구절절 설명하며 끝나는모습 이라니... | \n",
" 0 | \n",
" [점수, 임, 년후, 절절, 설명, 모습] | \n",
"
\n",
" \n",
" | 72078 | \n",
" 1173600 | \n",
" 주제는 좋으나 영화 자체는 주제를 포함하기엔 좀 조잡하다 | \n",
" 0 | \n",
" [주제, 영화, 자체, 주제, 포함, 좀] | \n",
"
\n",
" \n",
" | 19403 | \n",
" 8384839 | \n",
" 이걸 영화라고 만들다니...말그대로 '돈만 떡으로 바르고 여운의 감동, 의미는 없는... | \n",
" 0 | \n",
" [걸, 영화, 그대로, 돈, 떡, 여운, 감동, 의미, 영화, 이건, 그냥, 영화,... | \n",
"
\n",
" \n",
" | 106111 | \n",
" 8997490 | \n",
" 과장되고 어설픈 연기에 인류의 보편적 정서와는 거리가 먼 일본식 정서간의 괴리감, ... | \n",
" 0 | \n",
" [과장, 연기, 인류, 보편, 정서, 거리, 일본, 정서, 괴리감, 무엇, 공포영화... | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" id document label \\\n",
"149713 6674102 정말 댓글에 속아서 보게된 시간 아까운 영화. 전체적으로 코믹적인 요소를 많이 넣으... 0 \n",
"52541 8514375 점수를 줘야 하는거임? 30년후 구구절절 설명하며 끝나는모습 이라니... 0 \n",
"72078 1173600 주제는 좋으나 영화 자체는 주제를 포함하기엔 좀 조잡하다 0 \n",
"19403 8384839 이걸 영화라고 만들다니...말그대로 '돈만 떡으로 바르고 여운의 감동, 의미는 없는... 0 \n",
"106111 8997490 과장되고 어설픈 연기에 인류의 보편적 정서와는 거리가 먼 일본식 정서간의 괴리감, ... 0 \n",
"\n",
" parsed_doc \n",
"149713 [정말, 댓글, 속, 시간, 영화, 전체, 코믹, 요소, 정말, 웃기, 스토리] \n",
"52541 [점수, 임, 년후, 절절, 설명, 모습] \n",
"72078 [주제, 영화, 자체, 주제, 포함, 좀] \n",
"19403 [걸, 영화, 그대로, 돈, 떡, 여운, 감동, 의미, 영화, 이건, 그냥, 영화,... \n",
"106111 [과장, 연기, 인류, 보편, 정서, 거리, 일본, 정서, 괴리감, 무엇, 공포영화... "
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"neg.head()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" id | \n",
" document | \n",
" label | \n",
" parsed_doc | \n",
"
\n",
" \n",
" \n",
" \n",
" | 148005 | \n",
" 8918849 | \n",
" 마지막에 눈물이 흐를뻔... 너무 감동적이고 재미있는 영화 | \n",
" 1 | \n",
" [마지막, 눈물, 뻔, 감동, 영화] | \n",
"
\n",
" \n",
" | 41010 | \n",
" 9304354 | \n",
" 오! 평점 높네요. 저도 진짜 좋아하는 영화에요. 소소한 일상의 반전. 아즈키판다 짱! | \n",
" 1 | \n",
" [오, 평점, 저, 진짜, 영화, 일상, 반전, 키, 판다, 짱] | \n",
"
\n",
" \n",
" | 97924 | \n",
" 9324301 | \n",
" 전 아프리카 구호 활동중 에볼라에 걸려 눈을 감는 순간 누군가 틀어준 이 영화를 보... | \n",
" 1 | \n",
" [전, 아프리카, 구호, 활동, 에볼라, 눈, 순간, 누군가, 이, 영화, 보고, ... | \n",
"
\n",
" \n",
" | 68271 | \n",
" 10274621 | \n",
" 전작을 본적이 없는데도 너무 재미있게 봤습니다. 크리스 에반스와 사무엘 L.잭슨의 ... | \n",
" 1 | \n",
" [전작, 본적, 크리스, 에반스, 사무엘, 잭슨, 카리스마, 그, 외, 스토리, 액... | \n",
"
\n",
" \n",
" | 41106 | \n",
" 9908746 | \n",
" 신선하고 환경파괴에대해서 자연과 인간의 공존 전쟁 등등 많은 교훈을 주는 애니메이션... | \n",
" 1 | \n",
" [환경, 파괴, 대해, 자연, 인간, 공존, 전쟁, 등등, 교훈, 애니메이션, 원작... | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" id document label \\\n",
"148005 8918849 마지막에 눈물이 흐를뻔... 너무 감동적이고 재미있는 영화 1 \n",
"41010 9304354 오! 평점 높네요. 저도 진짜 좋아하는 영화에요. 소소한 일상의 반전. 아즈키판다 짱! 1 \n",
"97924 9324301 전 아프리카 구호 활동중 에볼라에 걸려 눈을 감는 순간 누군가 틀어준 이 영화를 보... 1 \n",
"68271 10274621 전작을 본적이 없는데도 너무 재미있게 봤습니다. 크리스 에반스와 사무엘 L.잭슨의 ... 1 \n",
"41106 9908746 신선하고 환경파괴에대해서 자연과 인간의 공존 전쟁 등등 많은 교훈을 주는 애니메이션... 1 \n",
"\n",
" parsed_doc \n",
"148005 [마지막, 눈물, 뻔, 감동, 영화] \n",
"41010 [오, 평점, 저, 진짜, 영화, 일상, 반전, 키, 판다, 짱] \n",
"97924 [전, 아프리카, 구호, 활동, 에볼라, 눈, 순간, 누군가, 이, 영화, 보고, ... \n",
"68271 [전작, 본적, 크리스, 에반스, 사무엘, 잭슨, 카리스마, 그, 외, 스토리, 액... \n",
"41106 [환경, 파괴, 대해, 자연, 인간, 공존, 전쟁, 등등, 교훈, 애니메이션, 원작... "
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pos.head()"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"# 학습 데이터 : 5,800개 / 테스트 데이터 : 200개\n",
"neg_train = neg[:2900]\n",
"pos_train = pos[:2900]\n",
"neg_test = neg[2900:]\n",
"pos_test = pos[2900:]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Corpus 생성하기"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [],
"source": [
"neg_corpus = set(neg_train.parsed_doc.sum())\n",
"pos_corpus = set(pos_train.parsed_doc.sum())"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"corpus 길이 9836\n"
]
}
],
"source": [
"corpus = list((neg_corpus).union(pos_corpus))\n",
"print('corpus 길이', len(corpus))"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['끝', '쿵푸', '탈출', '조교', '슬', '김호진', '작문', '흡혈귀', '만족', '거슬러']"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"corpus[:10]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Bag of words vector 생성하기 (형태를 살펴보고 익히는 용도로 사용하자.)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [],
"source": [
"from collections import OrderedDict"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [],
"source": [
"neg_bow_vecs = []\n",
"for _, doc in neg.parsed_doc.items():\n",
" bow_vecs = OrderedDict()\n",
" for w in corpus:\n",
" if w in doc:\n",
" bow_vecs[w] = 1\n",
" else:\n",
" bow_vecs[w] = 0\n",
" neg_bow_vecs.append(bow_vecs)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"# neg_bow_vecs[0]"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"pos_bow_vecs = []\n",
"for _, doc in pos.parsed_doc.items():\n",
" bow_vecs = OrderedDict()\n",
" for w in corpus:\n",
" if w in doc:\n",
" bow_vecs[w] = 1\n",
" else:\n",
" bow_vecs[w] = 0\n",
" pos_bow_vecs.append(bow_vecs)"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"# pos_bow_vecs[0]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. 모델 training"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$n$ : Document의 차원 즉 전체 corpus의 크기\n",
"\n",
"$$p(pos|doc) = \\frac{p(doc|pos) \\times p(pos)}{p(doc)} = \\frac{\\Pi_{i=1}^{n}p(word_i|pos) \\times p(pos)}{p(doc)}$$\n",
"\n",
"$$p(neg|doc) = \\frac{p(doc|neg) \\times p(neg)}{p(doc)} = \\frac{\\Pi_{i=1}^{n}p(word_i|neg) \\times p(neg)}{p(neg)}$$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$p(word_i|pos), p(word_i|neg), p(pos), p(neg)$를 구하는 것이 모델의 학습이다."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Likelihood\n",
"$p(word_i|pos)$\n",
" - $\\frac{\\text{해당 word를 포함하고 있는 positive 문장의 갯수}}{\\text{Positive 문장의 총 갯수}}$\n",
" \n",
"$p(word_i|neg)$\n",
" - $\\frac{\\text{해당 word를 포함하고 있는 negative 문장의 갯수}}{\\text{Negative 문장의 총 갯수}}$"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [],
"source": [
"neg_words_likelihood_cnts = {}\n",
"for w in corpus:\n",
" cnt = 0\n",
" for _, doc in neg_train.parsed_doc.items():\n",
" if w in doc:\n",
" cnt += 1\n",
" neg_words_likelihood_cnts[w] = cnt"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"pos_words_likelihood_cnts = {}\n",
"for w in corpus:\n",
" cnt = 0\n",
" for _, doc in pos_train.parsed_doc.items():\n",
" if w in doc:\n",
" cnt += 1\n",
" pos_words_likelihood_cnts[w] = cnt"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [],
"source": [
"import operator"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[('영화', 1052),\n",
" ('진짜', 254),\n",
" ('스토리', 228),\n",
" ('이', 199),\n",
" ('점', 189),\n",
" ('평점', 186),\n",
" ('감독', 186),\n",
" ('정말', 185),\n",
" ('것', 184),\n",
" ('왜', 173)]"
]
},
"execution_count": 23,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sorted(neg_words_likelihood_cnts.items(), key=operator.itemgetter(1), reverse=True)[:10]"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[('영화', 1135),\n",
" ('정말', 319),\n",
" ('이', 249),\n",
" ('최고', 221),\n",
" ('것', 212),\n",
" ('연기', 209),\n",
" ('생각', 196),\n",
" ('때', 188),\n",
" ('진짜', 185),\n",
" ('감동', 182)]"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sorted(pos_words_likelihood_cnts.items(), key=operator.itemgetter(1), reverse=True)[:10]"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Prior\n",
"$p(pos)$\n",
" - $\\frac{\\text{Positive 문서의 갯수}}{\\text{문서의 총 갯수}}$\n",
" \n",
"$p(neg)$\n",
" - $\\frac{\\text{Negative 문서의 갯수}}{\\text{문서의 총 갯수}}$"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 5. Classifier"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"- Document는 0, 1의 bag of word vector로 표현한다. Corpus의 크기가 $n$이면, 각 문장의 bag of word vector의 크기는 $n$이다.\n",
" - ex) 전체 corpus가 `cat, love, i, do, like, him, you`라고 하자. 문장이 `i love you` 이면, 이 문장의 bag of word vector는 `(0, 1, 1, 0, 0, 0, 1)` 이다.\n",
"\n",
"- 단어가 없는 경우는 **Laplacian smoothing** 을 사용한다.\n",
" - $m$은 전체 문장의 갯수, 즉 pos 문장 + neg 문장의 수이다."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"$$p(word_j|\\text{neg})=\\frac{\\sum_{i=1}^{m}I(word_j^{(i)}=1 \\text{ and }y^{(i)}=\\text{neg}) + 1}{\\sum_{i=1}^{m}I(y^{(i)} = \\text{neg}) + \\text{the number of words in corpus}}$$\n",
"\n",
"$$p(word_j|\\text{pos})=\\frac{\\sum_{i=1}^{m}I(word_j^{(i)}=1 \\text{ and }y^{(i)}=\\text{pos}) + 1}{\\sum_{i=1}^{m}I(y^{(i)} = \\text{pos}) + \\text{the number of words in corpus}}$$"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {},
"outputs": [],
"source": [
"test_data = pd.concat([neg_test, pos_test], axis=0)"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {},
"outputs": [],
"source": [
"def predict(doc):\n",
" pos_prior, neg_prior = 1/2, 1/2\n",
"\n",
" # Posterior of pos\n",
" pos_prob = np.log(1)\n",
" for word in corpus:\n",
" if word in doc:\n",
" # 단어가 현재 문장에 존재하고, pos 문장에 나온적이 있는 경우\n",
" if word in pos_words_likelihood_cnts:\n",
" pos_prob += np.log((pos_words_likelihood_cnts[word] + 1) / (len(pos_train) + len(corpus)))\n",
" else:\n",
" # 단어가 현재 문장에 존재하고, pos 문장에서 한 번도 나온적이 없는 경우 : 라플라시안 스무딩\n",
" pos_prob += np.log(1 / (len(pos_train) + len(corpus)))\n",
" else:\n",
" # 단어가 현재 문장에 존재하지 않고, pos 문장에 나온적이 있는 경우 (pos에서 해당단어가 없는 확률을 구할 수 있다.)\n",
" if word in pos_words_likelihood_cnts:\n",
" pos_prob += \\\n",
" np.log((len(pos_train) - pos_words_likelihood_cnts[word] + 1) / (len(pos_train) + len(corpus)))\n",
" else:\n",
" # 단어가 현재 문장에 존재하지 않고, pos 문장에서 단 한 번도 나온적이 없는 경우 : 라플라시안 스무딩\n",
" pos_prob += np.log((len(pos_train) + 1) / (len(pos_train) + len(corpus)))\n",
" pos_prob += np.log(pos_prior)\n",
" \n",
" # Posterior of neg\n",
" neg_prob = 1\n",
" for word in corpus:\n",
" if word in doc:\n",
" # 단어가 현재 문장에 존재하고, neg 문장에 나온적이 있는 경우\n",
" if word in neg_words_likelihood_cnts:\n",
" neg_prob += np.log((neg_words_likelihood_cnts[word] + 1) / (len(neg_train) + len(corpus)))\n",
" else:\n",
" # 단어가 현재 문장에 존재하고, neg 문장에서 한 번도 나온적이 없는 경우 : 라플라시안 스무딩\n",
" neg_prob += np.log(1 / (len(neg_train) + len(corpus)))\n",
" else:\n",
" # 단어가 현재 문장에 존재하지 않고, neg 문장에 나온적이 있는 경우 (neg에서 해당단어가 없는 확률을 구할 수 있다.)\n",
" if word in neg_words_likelihood_cnts:\n",
" neg_prob += \\\n",
" np.log((len(neg_train) - neg_words_likelihood_cnts[word] + 1) / (len(neg_train) + len(corpus)))\n",
" else:\n",
" # 단어가 현재 문장에 존재하지 않고, pos 문장에서 단 한 번도 나온적이 없는 경우 : 라플라시안 스무딩\n",
" neg_prob += np.log((len(neg_train) + 1) / (len(neg_train) + len(corpus)))\n",
" neg_prob += np.log(neg_prior)\n",
" \n",
" if pos_prob >= neg_prob:\n",
" return 1\n",
" else:\n",
" return 0"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {},
"outputs": [],
"source": [
"test_data['pred'] = test_data.parsed_doc.apply(predict)"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" id | \n",
" document | \n",
" label | \n",
" parsed_doc | \n",
" pred | \n",
"
\n",
" \n",
" \n",
" \n",
" | 91417 | \n",
" 5079332 | \n",
" 쉴새없이 떠드는 폴도 싫지만...영화를 이렇게 만든 감독은 더 싫다. | \n",
" 0 | \n",
" [새, 폴, 영화, 감독, 더] | \n",
" 0 | \n",
"
\n",
" \n",
" | 122641 | \n",
" 2776735 | \n",
" 별표 반개도 아깝다..2시간 동안 본 짜증이 확밀려오네.. | \n",
" 0 | \n",
" [별표, 반개, 시간, 동안, 짜증, 확] | \n",
" 0 | \n",
"
\n",
" \n",
" | 72606 | \n",
" 8477996 | \n",
" 내용은 봐줄만해. 하지만 15금과 19금 사이에 서있다. 포스터는 호러 귀신나올것같... | \n",
" 0 | \n",
" [내용, 만해, 금, 금, 사이, 포스터, 호러, 귀신, 막상, 차마, 두, 최악,... | \n",
" 0 | \n",
"
\n",
" \n",
" | 138243 | \n",
" 7923759 | \n",
" 진정한 수면제...졸다가 비명지르는 소리에 깼다가 다시 자다 | \n",
" 0 | \n",
" [수면제, 비명, 소리, 다시] | \n",
" 0 | \n",
"
\n",
" \n",
" | 118247 | \n",
" 8142893 | \n",
" \"진짜 슈스케 버스커 버스커, 허각, 울랄라세션 나올땐 재밌는데 갈수록 재미가 없어... | \n",
" 0 | \n",
" [진짜, 슈스케, 버스커, 버스커, 허각, 울랄라세션, 땐, 갈수록, 재미, 제, ... | \n",
" 1 | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" id document label \\\n",
"91417 5079332 쉴새없이 떠드는 폴도 싫지만...영화를 이렇게 만든 감독은 더 싫다. 0 \n",
"122641 2776735 별표 반개도 아깝다..2시간 동안 본 짜증이 확밀려오네.. 0 \n",
"72606 8477996 내용은 봐줄만해. 하지만 15금과 19금 사이에 서있다. 포스터는 호러 귀신나올것같... 0 \n",
"138243 7923759 진정한 수면제...졸다가 비명지르는 소리에 깼다가 다시 자다 0 \n",
"118247 8142893 \"진짜 슈스케 버스커 버스커, 허각, 울랄라세션 나올땐 재밌는데 갈수록 재미가 없어... 0 \n",
"\n",
" parsed_doc pred \n",
"91417 [새, 폴, 영화, 감독, 더] 0 \n",
"122641 [별표, 반개, 시간, 동안, 짜증, 확] 0 \n",
"72606 [내용, 만해, 금, 금, 사이, 포스터, 호러, 귀신, 막상, 차마, 두, 최악,... 0 \n",
"138243 [수면제, 비명, 소리, 다시] 0 \n",
"118247 [진짜, 슈스케, 버스커, 버스커, 허각, 울랄라세션, 땐, 갈수록, 재미, 제, ... 1 "
]
},
"execution_count": 28,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"test_data.head()"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"46"
]
},
"execution_count": 29,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum(test_data.label ^ test_data.pred)"
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"0.77"
]
},
"execution_count": 30,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"1 - sum(test_data.label ^ test_data.pred)/len(test_data)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.7"
}
},
"nbformat": 4,
"nbformat_minor": 2
}