[파이썬 머신러닝 완벽 가이드] 8장. 텍스트 분석 - 2
# 22.02.12 머신러닝 스터디 8장. 텍스트 분석 - 2
<파이썬 머신러닝 완벽 가이드>¶
텍스트 분석 - 2 ( p497 ~ p527 )¶
05. 감성 분석¶
감성 분석(Sentiment Analysis)은 문서의 주관적인 감성/의견/감정/기분 등을 파악하기 위한 방법으로 소셜 미디어, 여론조사, 온라인 리뷰, 피드백 등 다양한 분야에서 활용되고 있다. 감성 분석은 문서 내 텍스트가 나타내는 여러 가지 주관적인 단어와 문맥을 기반으로 감성 수치를 계산하는 방법을 이용한다. 이러한 감성 지수는 긍정 감성 지수와 부정 감성 지수로 구성되며 이들 지수를 합산해 긍정 감성 또는 부정 감성을 결정한다. 감성 분석은 머신러닝 관점에서 지도학습과 비지도학습 방식으로 나눌 수 있는데 우선 지도학습은 학습 데이터와 타깃 레이블 값을 기반으로 감성 분석 학습을 수행한 뒤 이를 기반으로 다른 데이터의 감성 분석을 예측하는 방법으로 일반적인 텍스트 기반의 분류와 거의 동일하다. 비지도학습의 경우에는 'Lexicon'이라는 일종의 감성 어휘 사전을 이용하는데, Lexicon은 감성 분석을 위한 용어와 문맥에 대한 다양한 정보를 가지고 있으며, 이를 이용해 긍정적, 부정적 감성 여부를 판단한다.
지도학습 기반 감성 분석 실습 - IMDB 영화평¶
import pandas as pd
review_df = pd.read_csv('./labeledTrainData.tsv', header = 0, sep = "\t", quoting = 3)
review_df.head(3)
id | sentiment | review | |
---|---|---|---|
0 | "5814_8" | 1 | "With all this stuff going down at the moment ... |
1 | "2381_9" | 1 | "\"The Classic War of the Worlds\" by Timothy ... |
2 | "7759_3" | 0 | "The film starts with a manager (Nicholas Bell... |
- id : 각 데이터의 id
- sentiment : 영화평의 Sentiment 결과 값 ( 1은 긍정적, 0은 부정적 )
- review : 영화평의 텍스트
# 텍스트가 어떻게 구성되어 있는지 확인
print(review_df['review'][0])
"With all this stuff going down at the moment with MJ i've started listening to his music, watching the odd documentary here and there, watched The Wiz and watched Moonwalker again. Maybe i just want to get a certain insight into this guy who i thought was really cool in the eighties just to maybe make up my mind whether he is guilty or innocent. Moonwalker is part biography, part feature film which i remember going to see at the cinema when it was originally released. Some of it has subtle messages about MJ's feeling towards the press and also the obvious message of drugs are bad m'kay.<br /><br />Visually impressive but of course this is all about Michael Jackson so unless you remotely like MJ in anyway then you are going to hate this and find it boring. Some may call MJ an egotist for consenting to the making of this movie BUT MJ and most of his fans would say that he made it for the fans which if true is really nice of him.<br /><br />The actual feature film bit when it finally starts is only on for 20 minutes or so excluding the Smooth Criminal sequence and Joe Pesci is convincing as a psychopathic all powerful drug lord. Why he wants MJ dead so bad is beyond me. Because MJ overheard his plans? Nah, Joe Pesci's character ranted that he wanted people to know it is he who is supplying drugs etc so i dunno, maybe he just hates MJ's music.<br /><br />Lots of cool things in this like MJ turning into a car and a robot and the whole Speed Demon sequence. Also, the director must have had the patience of a saint when it came to filming the kiddy Bad sequence as usually directors hate working with one kid let alone a whole bunch of them performing a complex dance scene.<br /><br />Bottom line, this movie is for people who like MJ on one level or another (which i think is most people). If not, then stay away. It does try and give off a wholesome message and ironically MJ's bestest buddy in this movie is a girl! Michael Jackson is truly one of the most talented people ever to grace this planet but is he guilty? Well, with all the attention i've gave this subject....hmmm well i don't know because people can be different behind closed doors, i know this for a fact. He is either an extremely nice but stupid guy or one of the most sickest liars. I hope he is not the latter."
문자열은 피처로 만들 필요가 없다 -> 공백으로
숫자, 특수문자 역시 모두 Sentiment를 위한 피처로는 의미가 없어 공란으로 변경 ( 정규 표현식 사용 )
-> 파이썬의 re모듈은 편리하게 정규 표현식을 지원한다.
import re
# <br> html 태그는 replace 함수로 공백으로 변환
review_df['review'] = review_df['review'].str.replace('<br />', ' ')
# 파이썬의 re 모듈을 이용해 영어 문자열이 아닌 문자는 모두 공백으로 변환
review_df['review'] = review_df['review'].apply(lambda x : re.sub("[^a-zA-Z]", " ", x))
# [^a-zA-Z] 는 영어 대/소문자가 아닌 모든 문자를 찾는 정규 표현식
# 결정 값 클래스인 sentiment 칼럼을 별도로 추출
from sklearn.model_selection import train_test_split
class_df = review_df['sentiment']
feature_df = review_df.drop(['id','sentiment'], axis=1, inplace=False)
X_train, X_test, y_train, y_test= train_test_split(feature_df, class_df, test_size=0.3, random_state=156)
X_train.shape, X_test.shape
((17500, 1), (7500, 1))
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, roc_auc_score
# 스톱 워드는 English, filtering, ngram은 (1,2)로 설정해 CountVectorization수행.
# LogisticRegression의 C는 10으로 설정.
pipeline = Pipeline([
('cnt_vect', CountVectorizer(stop_words='english', ngram_range=(1,2) )),
('lr_clf', LogisticRegression(C=10, max_iter = 500))]) # C 값이 작으면 훈련을 덜 복잡 ( 강한 규제)
# Pipeline 객체를 이용하여 fit(), predict()로 학습/예측 수행. predict_proba()는 roc_auc때문에 수행.
pipeline.fit(X_train['review'], y_train)
pred = pipeline.predict(X_test['review'])
pred_probs = pipeline.predict_proba(X_test['review'])[:,1]
print('예측 정확도는 {0:.4f}, ROC-AUC는 {1:.4f}'.format(accuracy_score(y_test ,pred),
roc_auc_score(y_test, pred_probs)))
예측 정확도는 0.8860, ROC-AUC는 0.9503
# 스톱 워드는 english, filtering, ngram은 (1,2)로 설정해 TF-IDF 벡터화 수행.
# LogisticRegression의 C는 10으로 설정.
pipeline = Pipeline([
('tfidf_vect', TfidfVectorizer(stop_words='english', ngram_range=(1,2) )),
('lr_clf', LogisticRegression(C=10, max_iter = 500))])
pipeline.fit(X_train['review'], y_train)
pred = pipeline.predict(X_test['review'])
pred_probs = pipeline.predict_proba(X_test['review'])[:,1]
print('예측 정확도는 {0:.4f}, ROC-AUC는 {1:.4f}'.format(accuracy_score(y_test ,pred),
roc_auc_score(y_test, pred_probs)))
예측 정확도는 0.8936, ROC-AUC는 0.9598
비지도학습 기반 감성 분석¶
비지도 감성 분석은 Lexicon을 기반으로 한다. Lexicon은 NLTK 패키지의 서브 모듈로 감성을 분석하기 위해 지원하는 감성 어휘 사전을 의미하고, 감성 사전은 긍정 감성 또는 부정 감성의 정도를 의미하는 수치를 가지고 있는데 이를 감성 지수라고 한다. 이 감성 지수는 단어의 위치나 주변 단어, 문맥, POS(Part of Speech) 등을 참고해 결정된다.
텍스트 분석에는 문맥상 의미를 나타내는 semantic이라는 용어가 자주 등장하는데 동일한 단어나 문장이라도 다른 환경과 문맥에서 다르게 표현되거나 이해될 수 있기 때문이다. 언어학에서 이러한 semantic을 표현하기 위해서 여러 가지 규칙을 정해왔으며 NLP 패키지는 semantic을 프로그램적으로 인터페이스할 수 있는 다양한 방법을 제공한다. 특히 NLP 패키지의 WordNet 모듈을 통해 semantic 분석을 진행할 수 있고 이 모듈은 다양한 상황에서 같은 어휘라도 다르게 사용되는 어휘의 semantic 정보를 제공하며 이를 위해 각각의 품사로 구성된 개별 단어를 Synset(Sets of cognitive synonyms)이라는 개념을 이용해 표현한다. Synset은 단순한 하나의 단어가 아니라 그 단어가 가지는 문맥, semantic 정보를 제공하는 WordNet의 핵심 개념이다. 하지만 NLTK 감성 사전은 예측 성능이 그리 좋지 않다는 단점이 있다.
대표적인 감성사전
SentiWordNet : NLTK 패키지의 WordNet과 유사하게 감성 단어 전용의 WordNet을 구현한 것. WordNet의 Synset 개념을 감성 분석에 적용한 것으로 Synset별로 3가지 감성 점수를 할당한다 (긍정 감성 지수, 부정 감성 지수, 객관성 지수). 여기서 객관성 지수는 긍정/부정 감성 지수와 완전히 반대되는 개념으로 단어가 감성과 관계없이 얼마나 객관적인지를 수치로 나타낸 것이다. 문장별로 단어들의 긍정 감성 지수와 부정 감성 지수를 합산하여 최종 감성 지수를 계산하고 이에 기반해 감성이 긍정인지 부정인지를 결정한다.
VADER : 주로 소셜 미디어의 텍스트에 대한 감성 분석을 제공하기 위한 패키지이다. 뛰어난 감성 분석 결과를 제공하며, 비교적 빠른 수행 시간을 보장해 대용량 텍스트 데이터에 잘 사용되는 패키지이다.
Pattern : 예측 성능 측면에서 가장 주목받는 패키지이다. (책에는 아직 파이썬 3.X 에서 호환되지 않는다고 나와 있는데 3.6 버전부터는 호환된다고 한다)
SentiWordNet을 이용한 감성 분석¶
WordNet Synset과 SentiWordNet SentiSynSet 클래스의 이해
import nltk
nltk.download('all')
[nltk_data] Downloading collection 'all' [nltk_data] | [nltk_data] | Downloading package abc to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package abc is already up-to-date! [nltk_data] | Downloading package alpino to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package alpino is already up-to-date! [nltk_data] | Downloading package averaged_perceptron_tagger to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package averaged_perceptron_tagger is already up- [nltk_data] | to-date! [nltk_data] | Downloading package averaged_perceptron_tagger_ru to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package averaged_perceptron_tagger_ru is already [nltk_data] | up-to-date! [nltk_data] | Downloading package basque_grammars to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package basque_grammars is already up-to-date! [nltk_data] | Downloading package biocreative_ppi to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package biocreative_ppi is already up-to-date! [nltk_data] | Downloading package bllip_wsj_no_aux to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package bllip_wsj_no_aux is already up-to-date! [nltk_data] | Downloading package book_grammars to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package book_grammars is already up-to-date! [nltk_data] | Downloading package brown to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package brown is already up-to-date! [nltk_data] | Downloading package brown_tei to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package brown_tei is already up-to-date! [nltk_data] | Downloading package cess_cat to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package cess_cat is already up-to-date! [nltk_data] | Downloading package cess_esp to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package cess_esp is already up-to-date! [nltk_data] | Downloading package chat80 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package chat80 is already up-to-date! [nltk_data] | Downloading package city_database to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package city_database is already up-to-date! [nltk_data] | Downloading package cmudict to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package cmudict is already up-to-date! [nltk_data] | Downloading package comparative_sentences to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package comparative_sentences is already up-to- [nltk_data] | date! [nltk_data] | Downloading package comtrans to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package comtrans is already up-to-date! [nltk_data] | Downloading package conll2000 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package conll2000 is already up-to-date! [nltk_data] | Downloading package conll2002 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package conll2002 is already up-to-date! [nltk_data] | Downloading package conll2007 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package conll2007 is already up-to-date! [nltk_data] | Downloading package crubadan to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package crubadan is already up-to-date! [nltk_data] | Downloading package dependency_treebank to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package dependency_treebank is already up-to-date! [nltk_data] | Downloading package dolch to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package dolch is already up-to-date! [nltk_data] | Downloading package europarl_raw to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package europarl_raw is already up-to-date! [nltk_data] | Downloading package extended_omw to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package extended_omw is already up-to-date! [nltk_data] | Downloading package floresta to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package floresta is already up-to-date! [nltk_data] | Downloading package framenet_v15 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package framenet_v15 is already up-to-date! [nltk_data] | Downloading package framenet_v17 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package framenet_v17 is already up-to-date! [nltk_data] | Downloading package gazetteers to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package gazetteers is already up-to-date! [nltk_data] | Downloading package genesis to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package genesis is already up-to-date! [nltk_data] | Downloading package gutenberg to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package gutenberg is already up-to-date! [nltk_data] | Downloading package ieer to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package ieer is already up-to-date! [nltk_data] | Downloading package inaugural to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package inaugural is already up-to-date! [nltk_data] | Downloading package indian to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package indian is already up-to-date! [nltk_data] | Downloading package jeita to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package jeita is already up-to-date! [nltk_data] | Downloading package kimmo to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package kimmo is already up-to-date! [nltk_data] | Downloading package knbc to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package knbc is already up-to-date! [nltk_data] | Downloading package large_grammars to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package large_grammars is already up-to-date! [nltk_data] | Downloading package lin_thesaurus to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package lin_thesaurus is already up-to-date! [nltk_data] | Downloading package mac_morpho to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package mac_morpho is already up-to-date! [nltk_data] | Downloading package machado to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package machado is already up-to-date! [nltk_data] | Downloading package masc_tagged to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package masc_tagged is already up-to-date! [nltk_data] | Downloading package maxent_ne_chunker to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package maxent_ne_chunker is already up-to-date! [nltk_data] | Downloading package maxent_treebank_pos_tagger to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package maxent_treebank_pos_tagger is already up- [nltk_data] | to-date! [nltk_data] | Downloading package moses_sample to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package moses_sample is already up-to-date! [nltk_data] | Downloading package movie_reviews to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package movie_reviews is already up-to-date! [nltk_data] | Downloading package mte_teip5 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package mte_teip5 is already up-to-date! [nltk_data] | Downloading package mwa_ppdb to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package mwa_ppdb is already up-to-date! [nltk_data] | Downloading package names to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package names is already up-to-date! [nltk_data] | Downloading package nombank.1.0 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package nombank.1.0 is already up-to-date! [nltk_data] | Downloading package nonbreaking_prefixes to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package nonbreaking_prefixes is already up-to-date! [nltk_data] | Downloading package nps_chat to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package nps_chat is already up-to-date! [nltk_data] | Downloading package omw to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package omw is already up-to-date! [nltk_data] | Downloading package omw-1.4 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package omw-1.4 is already up-to-date! [nltk_data] | Downloading package opinion_lexicon to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package opinion_lexicon is already up-to-date! [nltk_data] | Downloading package panlex_swadesh to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package panlex_swadesh is already up-to-date! [nltk_data] | Downloading package paradigms to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package paradigms is already up-to-date! [nltk_data] | Downloading package pe08 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package pe08 is already up-to-date! [nltk_data] | Downloading package perluniprops to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package perluniprops is already up-to-date! [nltk_data] | Downloading package pil to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package pil is already up-to-date! [nltk_data] | Downloading package pl196x to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package pl196x is already up-to-date! [nltk_data] | Downloading package porter_test to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package porter_test is already up-to-date! [nltk_data] | Downloading package ppattach to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package ppattach is already up-to-date! [nltk_data] | Downloading package problem_reports to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package problem_reports is already up-to-date! [nltk_data] | Downloading package product_reviews_1 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package product_reviews_1 is already up-to-date! [nltk_data] | Downloading package product_reviews_2 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package product_reviews_2 is already up-to-date! [nltk_data] | Downloading package propbank to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package propbank is already up-to-date! [nltk_data] | Downloading package pros_cons to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package pros_cons is already up-to-date! [nltk_data] | Downloading package ptb to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package ptb is already up-to-date! [nltk_data] | Downloading package punkt to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package punkt is already up-to-date! [nltk_data] | Downloading package qc to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package qc is already up-to-date! [nltk_data] | Downloading package reuters to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package reuters is already up-to-date! [nltk_data] | Downloading package rslp to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package rslp is already up-to-date! [nltk_data] | Downloading package rte to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package rte is already up-to-date! [nltk_data] | Downloading package sample_grammars to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package sample_grammars is already up-to-date! [nltk_data] | Downloading package semcor to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package semcor is already up-to-date! [nltk_data] | Downloading package senseval to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package senseval is already up-to-date! [nltk_data] | Downloading package sentence_polarity to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package sentence_polarity is already up-to-date! [nltk_data] | Downloading package sentiwordnet to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package sentiwordnet is already up-to-date! [nltk_data] | Downloading package shakespeare to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package shakespeare is already up-to-date! [nltk_data] | Downloading package sinica_treebank to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package sinica_treebank is already up-to-date! [nltk_data] | Downloading package smultron to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package smultron is already up-to-date! [nltk_data] | Downloading package snowball_data to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package snowball_data is already up-to-date! [nltk_data] | Downloading package spanish_grammars to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package spanish_grammars is already up-to-date! [nltk_data] | Downloading package state_union to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package state_union is already up-to-date! [nltk_data] | Downloading package stopwords to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package stopwords is already up-to-date! [nltk_data] | Downloading package subjectivity to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package subjectivity is already up-to-date! [nltk_data] | Downloading package swadesh to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package swadesh is already up-to-date! [nltk_data] | Downloading package switchboard to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package switchboard is already up-to-date! [nltk_data] | Downloading package tagsets to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package tagsets is already up-to-date! [nltk_data] | Downloading package timit to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package timit is already up-to-date! [nltk_data] | Downloading package toolbox to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package toolbox is already up-to-date! [nltk_data] | Downloading package treebank to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package treebank is already up-to-date! [nltk_data] | Downloading package twitter_samples to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package twitter_samples is already up-to-date! [nltk_data] | Downloading package udhr to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package udhr is already up-to-date! [nltk_data] | Downloading package udhr2 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package udhr2 is already up-to-date! [nltk_data] | Downloading package unicode_samples to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package unicode_samples is already up-to-date! [nltk_data] | Downloading package universal_tagset to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package universal_tagset is already up-to-date! [nltk_data] | Downloading package universal_treebanks_v20 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package universal_treebanks_v20 is already up-to- [nltk_data] | date! [nltk_data] | Downloading package vader_lexicon to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package vader_lexicon is already up-to-date! [nltk_data] | Downloading package verbnet to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package verbnet is already up-to-date! [nltk_data] | Downloading package verbnet3 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package verbnet3 is already up-to-date! [nltk_data] | Downloading package webtext to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package webtext is already up-to-date! [nltk_data] | Downloading package wmt15_eval to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package wmt15_eval is already up-to-date! [nltk_data] | Downloading package word2vec_sample to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package word2vec_sample is already up-to-date! [nltk_data] | Downloading package wordnet to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package wordnet is already up-to-date! [nltk_data] | Downloading package wordnet2021 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package wordnet2021 is already up-to-date! [nltk_data] | Downloading package wordnet31 to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package wordnet31 is already up-to-date! [nltk_data] | Downloading package wordnet_ic to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package wordnet_ic is already up-to-date! [nltk_data] | Downloading package words to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package words is already up-to-date! [nltk_data] | Downloading package ycoe to [nltk_data] | C:\Users\admin\AppData\Roaming\nltk_data... [nltk_data] | Package ycoe is already up-to-date! [nltk_data] | [nltk_data] Done downloading collection all
True
from nltk.corpus import wordnet as wn
term = 'present'
# 'present'라는 단어로 wordnet의 synsets 생성.
synsets = wn.synsets(term)
print('synsets() 반환 type :', type(synsets))
print('synsets() 반환 값 갯수:', len(synsets))
print('synsets() 반환 값 :', synsets)
synsets() 반환 type : <class 'list'> synsets() 반환 값 갯수: 18 synsets() 반환 값 : [Synset('present.n.01'), Synset('present.n.02'), Synset('present.n.03'), Synset('show.v.01'), Synset('present.v.02'), Synset('stage.v.01'), Synset('present.v.04'), Synset('present.v.05'), Synset('award.v.01'), Synset('give.v.08'), Synset('deliver.v.01'), Synset('introduce.v.01'), Synset('portray.v.04'), Synset('confront.v.03'), Synset('present.v.12'), Synset('salute.v.06'), Synset('present.a.01'), Synset('present.a.02')]
for synset in synsets :
print('##### Synset name : ', synset.name(),'#####')
print('POS :',synset.lexname())
print('Definition:',synset.definition())
print('Lemmas:',synset.lemma_names())
##### Synset name : present.n.01 ##### POS : noun.time Definition: the period of time that is happening now; any continuous stretch of time including the moment of speech Lemmas: ['present', 'nowadays'] ##### Synset name : present.n.02 ##### POS : noun.possession Definition: something presented as a gift Lemmas: ['present'] ##### Synset name : present.n.03 ##### POS : noun.communication Definition: a verb tense that expresses actions or states at the time of speaking Lemmas: ['present', 'present_tense'] ##### Synset name : show.v.01 ##### POS : verb.perception Definition: give an exhibition of to an interested audience Lemmas: ['show', 'demo', 'exhibit', 'present', 'demonstrate'] ##### Synset name : present.v.02 ##### POS : verb.communication Definition: bring forward and present to the mind Lemmas: ['present', 'represent', 'lay_out'] ##### Synset name : stage.v.01 ##### POS : verb.creation Definition: perform (a play), especially on a stage Lemmas: ['stage', 'present', 'represent'] ##### Synset name : present.v.04 ##### POS : verb.possession Definition: hand over formally Lemmas: ['present', 'submit'] ##### Synset name : present.v.05 ##### POS : verb.stative Definition: introduce Lemmas: ['present', 'pose'] ##### Synset name : award.v.01 ##### POS : verb.possession Definition: give, especially as an honor or reward Lemmas: ['award', 'present'] ##### Synset name : give.v.08 ##### POS : verb.possession Definition: give as a present; make a gift of Lemmas: ['give', 'gift', 'present'] ##### Synset name : deliver.v.01 ##### POS : verb.communication Definition: deliver (a speech, oration, or idea) Lemmas: ['deliver', 'present'] ##### Synset name : introduce.v.01 ##### POS : verb.communication Definition: cause to come to know personally Lemmas: ['introduce', 'present', 'acquaint'] ##### Synset name : portray.v.04 ##### POS : verb.creation Definition: represent abstractly, for example in a painting, drawing, or sculpture Lemmas: ['portray', 'present'] ##### Synset name : confront.v.03 ##### POS : verb.communication Definition: present somebody with something, usually to accuse or criticize Lemmas: ['confront', 'face', 'present'] ##### Synset name : present.v.12 ##### POS : verb.communication Definition: formally present a debutante, a representative of a country, etc. Lemmas: ['present'] ##### Synset name : salute.v.06 ##### POS : verb.communication Definition: recognize with a gesture prescribed by a military regulation; assume a prescribed position Lemmas: ['salute', 'present'] ##### Synset name : present.a.01 ##### POS : adj.all Definition: temporal sense; intermediate between past and future; now existing or happening or in consideration Lemmas: ['present'] ##### Synset name : present.a.02 ##### POS : adj.all Definition: being or existing in a specified place Lemmas: ['present']
어휘간의 유사도
path_similarity
# synset 객체를 단어별로 생성합니다.
tree = wn.synset('tree.n.01')
lion = wn.synset('lion.n.01')
tiger = wn.synset('tiger.n.02')
cat = wn.synset('cat.n.01')
dog = wn.synset('dog.n.01')
entities = [tree , lion , tiger , cat , dog]
similarities = []
entity_names = [ entity.name().split('.')[0] for entity in entities]
# 단어별 synset 들을 iteration 하면서 다른 단어들의 synset과 유사도를 측정합니다.
for entity in entities:
similarity = [ round(entity.path_similarity(compared_entity), 2) for compared_entity in entities ]
similarities.append(similarity)
# 개별 단어별 synset과 다른 단어의 synset과의 유사도를 DataFrame형태로 저장합니다.
similarity_df = pd.DataFrame(similarities , columns=entity_names,index=entity_names)
similarity_df
tree | lion | tiger | cat | dog | |
---|---|---|---|---|---|
tree | 1.00 | 0.07 | 0.07 | 0.08 | 0.12 |
lion | 0.07 | 1.00 | 0.33 | 0.25 | 0.17 |
tiger | 0.07 | 0.33 | 1.00 | 0.25 | 0.17 |
cat | 0.08 | 0.25 | 0.25 | 1.00 | 0.20 |
dog | 0.12 | 0.17 | 0.17 | 0.20 | 1.00 |
# WordNet의 Synset과 유사한 Senti_Synset 클래스
import nltk
from nltk.corpus import sentiwordnet as swn
senti_synsets = list(swn.senti_synsets('slow'))
print('senti_synsets() 반환 type :', type(senti_synsets))
print('senti_synsets() 반환 값 갯수:', len(senti_synsets))
print('senti_synsets() 반환 값 :', senti_synsets)
senti_synsets() 반환 type : <class 'list'> senti_synsets() 반환 값 갯수: 11 senti_synsets() 반환 값 : [SentiSynset('decelerate.v.01'), SentiSynset('slow.v.02'), SentiSynset('slow.v.03'), SentiSynset('slow.a.01'), SentiSynset('slow.a.02'), SentiSynset('dense.s.04'), SentiSynset('slow.a.04'), SentiSynset('boring.s.01'), SentiSynset('dull.s.08'), SentiSynset('slowly.r.01'), SentiSynset('behind.r.03')]
# 긍정/부정/객관성지수 ( 단어가 전혀 감성적이지 않으면 객관성 지수는 1, 감성 지수는 0)
import nltk
from nltk.corpus import sentiwordnet as swn
father = swn.senti_synset('father.n.01')
print('father 긍정감성 지수: ', father.pos_score())
print('father 부정감성 지수: ', father.neg_score())
print('father 객관성 지수: ', father.obj_score())
print('\n')
fabulous = swn.senti_synset('fabulous.a.01')
print('fabulous 긍정감성 지수: ',fabulous .pos_score())
print('fabulous 부정감성 지수: ',fabulous .neg_score())
print('fabulous 객관성 지수: ', fabulous.obj_score())
father 긍정감성 지수: 0.0 father 부정감성 지수: 0.0 father 객관성 지수: 1.0 fabulous 긍정감성 지수: 0.875 fabulous 부정감성 지수: 0.125 fabulous 객관성 지수: 0.0
SentiWordNet을 이용한 영화 감상평 감성 분석
- 문서(Document)를 문장(Sentence) 단위로 분해
- 다시 문장을 단어(Word) 단위로 토큰화하고 품사 태깅
- 품사 태킹된 단어 기반으로 synset 객체와 senti_synset 객체를 생성
- Senti_synset에서 긍정 감성/부정 감성 지수를 구하고 이를 모두 합산해 특정 임계치 값 이상일 때 긍정 감성으로, 그렇지 않을 때는 부정 감성으로 결정
SentiWordNet을 이용하기 위해 WordNet을 이용해 문서를 다시 단어로 토큰화한 뒤 어근 추출(Lemmatization)과 품사 태깅(POS Tagging)을 적용해야 한다.
# 품사 태깅
from nltk.corpus import wordnet as wn
# 간단한 NTLK PennTreebank Tag를 기반으로 WordNet기반의 품사 Tag로 변환
def penn_to_wn(tag):
if tag.startswith('J'):
return wn.ADJ
elif tag.startswith('N'):
return wn.NOUN
elif tag.startswith('R'):
return wn.ADV
elif tag.startswith('V'):
return wn.VERB
return
# 문장 -> 단어 토큰 -> 품사 태깅 후에 Polarity Score를 합산하는 함수를 생성
from nltk.stem import WordNetLemmatizer
from nltk.corpus import sentiwordnet as swn
from nltk import sent_tokenize, word_tokenize, pos_tag
def swn_polarity(text):
# 감성 지수 초기화
sentiment = 0.0
tokens_count = 0
lemmatizer = WordNetLemmatizer()
raw_sentences = sent_tokenize(text)
# 분해된 문장별로 단어 토큰 -> 품사 태깅 후에 SentiSynset 생성 -> 감성 지수 합산
for raw_sentence in raw_sentences:
# NTLK 기반의 품사 태깅 문장 추출
tagged_sentence = pos_tag(word_tokenize(raw_sentence))
for word , tag in tagged_sentence:
# WordNet 기반 품사 태깅과 어근 추출
wn_tag = penn_to_wn(tag)
if wn_tag not in (wn.NOUN , wn.ADJ, wn.ADV):
continue
lemma = lemmatizer.lemmatize(word, pos=wn_tag)
if not lemma:
continue
# 어근을 추출한 단어와 WordNet 기반 품사 태깅을 입력해 Synset 객체를 생성.
synsets = wn.synsets(lemma , pos=wn_tag)
if not synsets:
continue
# sentiwordnet의 감성 단어 분석으로 감성 synset 추출
# 모든 단어에 대해 긍정 감성 지수는 +로 부정 감성 지수는 -로 합산해 감성 지수 계산.
synset = synsets[0]
swn_synset = swn.senti_synset(synset.name())
sentiment += (swn_synset.pos_score() - swn_synset.neg_score())
tokens_count += 1
if not tokens_count:
return 0
# 총 score가 0 이상일 경우 긍정(Positive) 1, 그렇지 않을 경우 부정(Negative) 0 반환
if sentiment >= 0 :
return 1
return 0
review_df['preds'] = review_df['review'].apply( lambda x : swn_polarity(x) )
y_target = review_df['sentiment'].values
preds = review_df['preds'].values
from sklearn.metrics import accuracy_score, confusion_matrix, precision_score
from sklearn.metrics import recall_score, f1_score, roc_auc_score
import numpy as np
print(confusion_matrix( y_target, preds))
print("정확도:", np.round(accuracy_score(y_target , preds), 4))
print("정밀도:", np.round(precision_score(y_target , preds),4))
print("재현율:", np.round(recall_score(y_target, preds), 4))
[[7668 4832] [3636 8864]] 정확도: 0.6613 정밀도: 0.6472 재현율: 0.7091
VADER를 이용한 감성 분석¶
VADER는 소셜 미디어의 감성 분석 용도로 만들어진 룰 기반의 Lexicon이다. VADER는 NLTK 패키지의 서브 모듈로 제공될 수도 있고 단독 패키지로 제공될 수도 있다. 여기서는 이전에 nltk.download('all')을 통해 완료하였다.
from nltk.sentiment.vader import SentimentIntensityAnalyzer
senti_analyzer = SentimentIntensityAnalyzer()
senti_scores = senti_analyzer.polarity_scores(review_df['review'][0])
print(senti_scores)
{'neg': 0.13, 'neu': 0.743, 'pos': 0.127, 'compound': -0.7943}
- neg : 부정 감성 지수
- neu : 중립적인 감성 지수
- pos : 긍정 감성 지수
- compound : neg, neu, pos score를 적절히 조합한 -1 ~ 1 사이의 감성 지수 ( 보통 0.1 이상이면 긍정, 이하면 부정으로 판단 )
# 입력 파라미터로 영화 감상평 테스트와 긍정/부정을 결정하는 임곗값을 가지고 polarity_scores() 메소드를 호출해 감성 결과를 반환하는 함수
def vader_polarity(review,threshold=0.1):
analyzer = SentimentIntensityAnalyzer()
scores = analyzer.polarity_scores(review)
# compound 값에 기반하여 threshold 입력값보다 크면 1, 그렇지 않으면 0을 반환
agg_score = scores['compound']
final_sentiment = 1 if agg_score >= threshold else 0
return final_sentiment
# apply lambda 식을 이용하여 레코드별로 vader_polarity( )를 수행하고 결과를 'vader_preds'에 저장
review_df['vader_preds'] = review_df['review'].apply( lambda x : vader_polarity(x, 0.1) )
y_target = review_df['sentiment'].values
vader_preds = review_df['vader_preds'].values
print(confusion_matrix( y_target, vader_preds))
print("정확도:", np.round(accuracy_score(y_target , vader_preds),4))
print("정밀도:", np.round(precision_score(y_target , vader_preds),4))
print("재현율:", np.round(recall_score(y_target, vader_preds),4))
[[ 6747 5753] [ 1858 10642]] 정확도: 0.6956 정밀도: 0.6491 재현율: 0.8514
정확도가 SentiWordNet보다 향상되었다. 감성 사전을 이용한 감성 분석 예측 성능 ( 비지도 학습 )은 지도학습 분류 기반의 예측 성능에 비해 아직 낮은 수준이지만 결정 클래스 값이 없는 상황을 고려한다면 예측 성능에 일정 수준 만족할 수 있다.
06. 토픽 모델링(Topic Modeling) - 20 뉴스그룹¶
토픽 모델링(Topic Modeling)이란 문서 집합에 숨어 있는 주제를 찾아내는 것이다. 많은 양의 문서가 있을 때 사람이 이 문서를 다 읽고 핵심 주제를 찾는 것은 매우 많은 시간이 소모된다. 이 경우에 머신러닝 기반의 토픽 모델링을 적용해 숨어 있는 중요 주제를 효과적으로 찾아낼 수 있다. 사람이 수행하는 토픽 모델링은 더 함축적인 의미로 문장을 요약하는 것에 반해, 머신러닝 기반의 토픽 모델은 숨겨진 주제를 효과적으로 표현할 수 있는 중심 단어를 함축적으로 추출한다.
머신러닝 기반의 토픽 모델링에 자주 사용되는 기법은 LSA(Latent Semantic Analysis)와 LDA(Latent Dirichlet Allocation)이다.
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import LatentDirichletAllocation
# 모토사이클, 야구, 그래픽스, 윈도우즈, 중동, 기독교, 의학, 우주 주제를 추출.
cats = ['rec.motorcycles', 'rec.sport.baseball', 'comp.graphics', 'comp.windows.x',
'talk.politics.mideast', 'soc.religion.christian', 'sci.electronics', 'sci.med' ]
# 위에서 cats 변수로 기재된 category만 추출. featch_20newsgroups( )의 categories에 cats 입력
news_df= fetch_20newsgroups(subset='all',remove=('headers', 'footers', 'quotes'),
categories=cats, random_state=0)
#LDA 는 Count기반의 Vectorizer만 적용합니다.
count_vect = CountVectorizer(max_df=0.95, max_features=1000, min_df=2, stop_words='english', ngram_range=(1,2))
feat_vect = count_vect.fit_transform(news_df.data)
print('CountVectorizer Shape:', feat_vect.shape)
CountVectorizer Shape: (7862, 1000)
lda = LatentDirichletAllocation(n_components=8, random_state=0) # 토픽 개수를 조정 ( 뽑아낸 주제 개수에 맞게 )
lda.fit(feat_vect)
LatentDirichletAllocation(n_components=8, random_state=0)
print(lda.components_.shape) # .components_에 속성값을 가진다.
lda.components_
(8, 1000)
array([[3.60992018e+01, 1.35626798e+02, 2.15751867e+01, ..., 3.02911688e+01, 8.66830093e+01, 6.79285199e+01], [1.25199920e-01, 1.44401815e+01, 1.25045596e-01, ..., 1.81506995e+02, 1.25097844e-01, 9.39593286e+01], [3.34762663e+02, 1.25176265e-01, 1.46743299e+02, ..., 1.25105772e-01, 3.63689741e+01, 1.25025218e-01], ..., [3.60204965e+01, 2.08640688e+01, 4.29606813e+00, ..., 1.45056650e+01, 8.33854413e+00, 1.55690009e+01], [1.25128711e-01, 1.25247756e-01, 1.25005143e-01, ..., 9.17278769e+01, 1.25177668e-01, 3.74575887e+01], [5.49258690e+01, 4.47009532e+00, 9.88524814e+00, ..., 4.87048440e+01, 1.25034678e-01, 1.25074632e-01]])
def display_topics(model, feature_names, no_top_words):
for topic_index, topic in enumerate(model.components_):
print('Topic #',topic_index)
# components_ array에서 가장 값이 큰 순으로 정렬했을 때, 그 값의 array index를 반환.
topic_word_indexes = topic.argsort()[::-1] # array 정렬
top_indexes=topic_word_indexes[:no_top_words]
# top_indexes대상인 index별로 feature_names에 해당하는 word feature 추출 후 join으로 concat
feature_concat = ' '.join([feature_names[i] for i in top_indexes])
print(feature_concat)
# CountVectorizer객체내의 전체 word들의 명칭을 get_features_names( )를 통해 추출
feature_names = count_vect.get_feature_names()
# Topic별 가장 연관도가 높은 word를 15개만 추출
display_topics(lda, feature_names, 15)
Topic # 0 year 10 game medical health team 12 20 disease cancer 1993 games years patients good Topic # 1 don just like know people said think time ve didn right going say ll way Topic # 2 image file jpeg program gif images output format files color entry 00 use bit 03 Topic # 3 like know don think use does just good time book read information people used post Topic # 4 armenian israel armenians jews turkish people israeli jewish government war dos dos turkey arab armenia 000 Topic # 5 edu com available graphics ftp data pub motif mail widget software mit information version sun Topic # 6 god people jesus church believe christ does christian say think christians bible faith sin life Topic # 7 use dos thanks windows using window does display help like problem server need know run
- Topic #0 : 일반적인 단어가 많음
- Topic #1 : 명확하게 컴퓨터 그래픽스 영역의 주제어가 추출됨
- Topic #2 : 기독교에 관련된 주제어
- Topic #3 : 의학에 관련된 주제어
- Topic #4 : 윈도우 운영체제와 관련된 주제어
- Topic #5 : 일반적인 단어
- Topic #6 : 중동 분쟁 등에 관련된 주제어
- Topic #7 : 윈도우 운영체제와 관련된 주제어(애매)
Topic #0, #5, #7에서 주로 애매한 주제어가 추출됐다.
07. 문서 군집화 소개와 실습(Opinoin Review 데이터 세트)¶
문서 군집화 개념¶
문서 군집화는 비슷한 텍스트 구성의 문서를 군집화 하는 것이다. 문서 군집화는 동일한 군집에 속하는 문서를 같은 카테고리 소속으로 분류할 수 있으므로 앞에서 소개한 텍스트 분류 기반의 문서 분류와 유사하다. 하지만 텍스트 분류 기반의 문서 분류는 사전에 결정 카테고리 값을 가진 학습 데이터 세트가 필요한 데 반해, 문서 군집화는 학습 데이터 세트가 필요 없는 비지도학습 기반으로 동작한다.
Opinion Review 데이터 세트를 이용한 문서 군집화 수행하기¶
<파일경로 참고자료> https://wikidocs.net/83
import pandas as pd
import glob ,os
path = r'C:\Users\admin\Desktop\머신러닝 스터디\10차시\OpinosisDataset1.0\topics'
# path로 지정한 디렉토리 밑에 있는 모든 .data 파일들의 파일명을 리스트로 취합
all_files = glob.glob(os.path.join(path, "*.data"))
filename_list = []
opinion_text = []
# 개별 파일들의 파일명은 filename_list 리스트로 취합,
# 개별 파일들의 파일내용은 DataFrame로딩 후 다시 string으로 변환하여 opinion_text 리스트로 취합
for file_ in all_files:
# 개별 파일을 읽어서 DataFrame으로 생성
df = pd.read_table(file_,index_col=None, header=0,encoding='latin1')
# 절대경로로 주어진 file 명을 가공. 만일 Linux에서 수행시에는 아래 \\를 / 변경. 맨 마지막 .data 확장자도 제거
filename_ = file_.split('\\')[-1]
filename = filename_.split('.')[0]
#파일명 리스트와 파일내용 리스트에 파일명과 파일 내용을 추가.
filename_list.append(filename)
opinion_text.append(df.to_string())
# 파일명 리스트와 파일내용 리스트를 DataFrame으로 생성
document_df = pd.DataFrame({'filename':filename_list, 'opinion_text':opinion_text})
document_df.head()
filename | opinion_text | |
---|---|---|
0 | accuracy_garmin_nuvi_255W_gps | ... |
1 | bathroom_bestwestern_hotel_sfo | ... |
2 | battery-life_amazon_kindle | ... |
3 | battery-life_ipod_nano_8gb | ... |
4 | battery-life_netbook_1005ha | ... |
# 문서를 TF-IDF 형태로 피처 벡터화
from nltk.stem import WordNetLemmatizer
import nltk
import string
remove_punct_dict = dict((ord(punct), None) for punct in string.punctuation)
lemmar = WordNetLemmatizer()
def LemTokens(tokens):
return [lemmar.lemmatize(token) for token in tokens]
def LemNormalize(text):
return LemTokens(nltk.word_tokenize(text.lower().translate(remove_punct_dict)))
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vect = TfidfVectorizer(tokenizer=LemNormalize, stop_words='english' , \
ngram_range=(1,2), min_df=0.05, max_df=0.85 )
#opinion_text 컬럼값으로 feature vectorization 수행
feature_vect = tfidf_vect.fit_transform(document_df['opinion_text'])
C:\ProgramData\Anaconda3\lib\site-packages\sklearn\feature_extraction\text.py:388: UserWarning: Your stop_words may be inconsistent with your preprocessing. Tokenizing the stop words generated tokens ['ha', 'le', 'u', 'wa'] not in stop_words. warnings.warn('Your stop_words may be inconsistent with '
from sklearn.cluster import KMeans
# 5개 집합으로 군집화 수행. 예제를 위해 동일한 클러스터링 결과 도출용
km_cluster = KMeans(n_clusters=5, max_iter=10000, random_state=0)
km_cluster.fit(feature_vect)
cluster_label = km_cluster.labels_
cluster_centers = km_cluster.cluster_centers_
document_df['cluster_label'] = cluster_label
document_df.head()
filename | opinion_text | cluster_label | |
---|---|---|---|
0 | accuracy_garmin_nuvi_255W_gps | ... | 2 |
1 | bathroom_bestwestern_hotel_sfo | ... | 0 |
2 | battery-life_amazon_kindle | ... | 1 |
3 | battery-life_ipod_nano_8gb | ... | 1 |
4 | battery-life_netbook_1005ha | ... | 1 |
# cluster_label = 0 ( 호텔에 대한 리뷰)
document_df[document_df['cluster_label']==0].sort_values(by='filename')
filename | opinion_text | cluster_label | |
---|---|---|---|
1 | bathroom_bestwestern_hotel_sfo | ... | 0 |
32 | room_holiday_inn_london | ... | 0 |
30 | rooms_bestwestern_hotel_sfo | ... | 0 |
31 | rooms_swissotel_chicago | ... | 0 |
# cluster_label = 1 ( 전자기기에 대한 리뷰)
document_df[document_df['cluster_label']==1].sort_values(by='filename')
filename | opinion_text | cluster_label | |
---|---|---|---|
2 | battery-life_amazon_kindle | ... | 1 |
3 | battery-life_ipod_nano_8gb | ... | 1 |
4 | battery-life_netbook_1005ha | ... | 1 |
19 | keyboard_netbook_1005ha | ... | 1 |
26 | performance_netbook_1005ha | ... | 1 |
41 | size_asus_netbook_1005ha | ... | 1 |
42 | sound_ipod_nano_8gb | headphone jack i got a clear case for it a... | 1 |
44 | speed_windows7 | ... | 1 |
# cluster_label = 2 ( 킨들, 아이팟, 넷북, 차량용 네비게이션에 대한 리뷰)
document_df[document_df['cluster_label']==2].sort_values(by='filename')
filename | opinion_text | cluster_label | |
---|---|---|---|
0 | accuracy_garmin_nuvi_255W_gps | ... | 2 |
5 | buttons_amazon_kindle | ... | 2 |
8 | directions_garmin_nuvi_255W_gps | ... | 2 |
9 | display_garmin_nuvi_255W_gps | ... | 2 |
10 | eyesight-issues_amazon_kindle | ... | 2 |
11 | features_windows7 | ... | 2 |
12 | fonts_amazon_kindle | ... | 2 |
23 | navigation_amazon_kindle | ... | 2 |
33 | satellite_garmin_nuvi_255W_gps | ... | 2 |
34 | screen_garmin_nuvi_255W_gps | ... | 2 |
35 | screen_ipod_nano_8gb | ... | 2 |
36 | screen_netbook_1005ha | ... | 2 |
43 | speed_garmin_nuvi_255W_gps | ... | 2 |
48 | updates_garmin_nuvi_255W_gps | ... | 2 |
49 | video_ipod_nano_8gb | ... | 2 |
50 | voice_garmin_nuvi_255W_gps | ... | 2 |
# cluster_label = 3 ( 호텔에 대한 리뷰)
document_df[document_df['cluster_label']==3].sort_values(by='filename')
filename | opinion_text | cluster_label | |
---|---|---|---|
13 | food_holiday_inn_london | ... | 3 |
14 | food_swissotel_chicago | ... | 3 |
15 | free_bestwestern_hotel_sfo | ... | 3 |
20 | location_bestwestern_hotel_sfo | ... | 3 |
21 | location_holiday_inn_london | ... | 3 |
24 | parking_bestwestern_hotel_sfo | ... | 3 |
27 | price_amazon_kindle | ... | 3 |
28 | price_holiday_inn_london | ... | 3 |
38 | service_bestwestern_hotel_sfo | ... | 3 |
39 | service_holiday_inn_london | ... | 3 |
40 | service_swissotel_hotel_chicago | ... | 3 |
45 | staff_bestwestern_hotel_sfo | ... | 3 |
46 | staff_swissotel_chicago | ... | 3 |
# cluster_label = 4 ( 자동차에 대한 리뷰)
document_df[document_df['cluster_label']==4].sort_values(by='filename')
filename | opinion_text | cluster_label | |
---|---|---|---|
6 | comfort_honda_accord_2008 | ... | 4 |
7 | comfort_toyota_camry_2007 | ... | 4 |
16 | gas_mileage_toyota_camry_2007 | ... | 4 |
17 | interior_honda_accord_2008 | ... | 4 |
18 | interior_toyota_camry_2007 | ... | 4 |
22 | mileage_honda_accord_2008 | ... | 4 |
25 | performance_honda_accord_2008 | ... | 4 |
29 | quality_toyota_camry_2007 | ... | 4 |
37 | seats_honda_accord_2008 | ... | 4 |
47 | transmission_toyota_camry_2007 | ... | 4 |
# 군집 개수를 5 -> 3
from sklearn.cluster import KMeans
# 3개의 집합으로 군집화
km_cluster = KMeans(n_clusters=3, max_iter=10000, random_state=0)
km_cluster.fit(feature_vect)
cluster_label = km_cluster.labels_
# 소속 클러스터를 cluster_label 컬럼으로 할당하고 cluster_label 값으로 정렬
document_df['cluster_label'] = cluster_label
document_df.sort_values(by='cluster_label')
filename | opinion_text | cluster_label | |
---|---|---|---|
0 | accuracy_garmin_nuvi_255W_gps | ... | 0 |
48 | updates_garmin_nuvi_255W_gps | ... | 0 |
44 | speed_windows7 | ... | 0 |
43 | speed_garmin_nuvi_255W_gps | ... | 0 |
42 | sound_ipod_nano_8gb | headphone jack i got a clear case for it a... | 0 |
41 | size_asus_netbook_1005ha | ... | 0 |
36 | screen_netbook_1005ha | ... | 0 |
35 | screen_ipod_nano_8gb | ... | 0 |
34 | screen_garmin_nuvi_255W_gps | ... | 0 |
33 | satellite_garmin_nuvi_255W_gps | ... | 0 |
27 | price_amazon_kindle | ... | 0 |
26 | performance_netbook_1005ha | ... | 0 |
49 | video_ipod_nano_8gb | ... | 0 |
23 | navigation_amazon_kindle | ... | 0 |
19 | keyboard_netbook_1005ha | ... | 0 |
50 | voice_garmin_nuvi_255W_gps | ... | 0 |
9 | display_garmin_nuvi_255W_gps | ... | 0 |
2 | battery-life_amazon_kindle | ... | 0 |
3 | battery-life_ipod_nano_8gb | ... | 0 |
4 | battery-life_netbook_1005ha | ... | 0 |
5 | buttons_amazon_kindle | ... | 0 |
12 | fonts_amazon_kindle | ... | 0 |
11 | features_windows7 | ... | 0 |
10 | eyesight-issues_amazon_kindle | ... | 0 |
8 | directions_garmin_nuvi_255W_gps | ... | 0 |
47 | transmission_toyota_camry_2007 | ... | 1 |
37 | seats_honda_accord_2008 | ... | 1 |
6 | comfort_honda_accord_2008 | ... | 1 |
7 | comfort_toyota_camry_2007 | ... | 1 |
16 | gas_mileage_toyota_camry_2007 | ... | 1 |
25 | performance_honda_accord_2008 | ... | 1 |
17 | interior_honda_accord_2008 | ... | 1 |
18 | interior_toyota_camry_2007 | ... | 1 |
22 | mileage_honda_accord_2008 | ... | 1 |
29 | quality_toyota_camry_2007 | ... | 1 |
1 | bathroom_bestwestern_hotel_sfo | ... | 2 |
46 | staff_swissotel_chicago | ... | 2 |
45 | staff_bestwestern_hotel_sfo | ... | 2 |
14 | food_swissotel_chicago | ... | 2 |
20 | location_bestwestern_hotel_sfo | ... | 2 |
21 | location_holiday_inn_london | ... | 2 |
30 | rooms_bestwestern_hotel_sfo | ... | 2 |
38 | service_bestwestern_hotel_sfo | ... | 2 |
13 | food_holiday_inn_london | ... | 2 |
24 | parking_bestwestern_hotel_sfo | ... | 2 |
28 | price_holiday_inn_london | ... | 2 |
15 | free_bestwestern_hotel_sfo | ... | 2 |
32 | room_holiday_inn_london | ... | 2 |
31 | rooms_swissotel_chicago | ... | 2 |
39 | service_holiday_inn_london | ... | 2 |
40 | service_swissotel_hotel_chicago | ... | 2 |
# cluster_label = 0 ( 포터블 전자기기에 대한 리뷰)
document_df[document_df['cluster_label']==0].sort_values(by='filename')
filename | opinion_text | cluster_label | |
---|---|---|---|
0 | accuracy_garmin_nuvi_255W_gps | ... | 0 |
2 | battery-life_amazon_kindle | ... | 0 |
3 | battery-life_ipod_nano_8gb | ... | 0 |
4 | battery-life_netbook_1005ha | ... | 0 |
5 | buttons_amazon_kindle | ... | 0 |
8 | directions_garmin_nuvi_255W_gps | ... | 0 |
9 | display_garmin_nuvi_255W_gps | ... | 0 |
10 | eyesight-issues_amazon_kindle | ... | 0 |
11 | features_windows7 | ... | 0 |
12 | fonts_amazon_kindle | ... | 0 |
19 | keyboard_netbook_1005ha | ... | 0 |
23 | navigation_amazon_kindle | ... | 0 |
26 | performance_netbook_1005ha | ... | 0 |
27 | price_amazon_kindle | ... | 0 |
33 | satellite_garmin_nuvi_255W_gps | ... | 0 |
34 | screen_garmin_nuvi_255W_gps | ... | 0 |
35 | screen_ipod_nano_8gb | ... | 0 |
36 | screen_netbook_1005ha | ... | 0 |
41 | size_asus_netbook_1005ha | ... | 0 |
42 | sound_ipod_nano_8gb | headphone jack i got a clear case for it a... | 0 |
43 | speed_garmin_nuvi_255W_gps | ... | 0 |
44 | speed_windows7 | ... | 0 |
48 | updates_garmin_nuvi_255W_gps | ... | 0 |
49 | video_ipod_nano_8gb | ... | 0 |
50 | voice_garmin_nuvi_255W_gps | ... | 0 |
# cluster_label = 1 ( 호텔에 대한 리뷰)
document_df[document_df['cluster_label']==1].sort_values(by='filename')
filename | opinion_text | cluster_label | |
---|---|---|---|
6 | comfort_honda_accord_2008 | ... | 1 |
7 | comfort_toyota_camry_2007 | ... | 1 |
16 | gas_mileage_toyota_camry_2007 | ... | 1 |
17 | interior_honda_accord_2008 | ... | 1 |
18 | interior_toyota_camry_2007 | ... | 1 |
22 | mileage_honda_accord_2008 | ... | 1 |
25 | performance_honda_accord_2008 | ... | 1 |
29 | quality_toyota_camry_2007 | ... | 1 |
37 | seats_honda_accord_2008 | ... | 1 |
47 | transmission_toyota_camry_2007 | ... | 1 |
# cluster_label = 2 ( 자동차에 대한 리뷰)
document_df[document_df['cluster_label']==2].sort_values(by='filename')
filename | opinion_text | cluster_label | |
---|---|---|---|
1 | bathroom_bestwestern_hotel_sfo | ... | 2 |
13 | food_holiday_inn_london | ... | 2 |
14 | food_swissotel_chicago | ... | 2 |
15 | free_bestwestern_hotel_sfo | ... | 2 |
20 | location_bestwestern_hotel_sfo | ... | 2 |
21 | location_holiday_inn_london | ... | 2 |
24 | parking_bestwestern_hotel_sfo | ... | 2 |
28 | price_holiday_inn_london | ... | 2 |
32 | room_holiday_inn_london | ... | 2 |
30 | rooms_bestwestern_hotel_sfo | ... | 2 |
31 | rooms_swissotel_chicago | ... | 2 |
38 | service_bestwestern_hotel_sfo | ... | 2 |
39 | service_holiday_inn_london | ... | 2 |
40 | service_swissotel_hotel_chicago | ... | 2 |
45 | staff_bestwestern_hotel_sfo | ... | 2 |
46 | staff_swissotel_chicago | ... | 2 |
군집별 핵심 단어 추출하기¶
각 군집에 속한 문서는 핵심 단어를 주축으로 군집화되어 있을 것이다. KMeans 객체는 각 군집을 구성하는 단어 피처가 군집의 중심을 기준으로 얼마나 가깝게 위치해 있는지 clusters_centers_라는 속성으로 제공하는데, 이는 배열 값으로 되어 있고 행은 개별 군집을, 열은 개별 피처를 의미한다. 각 배열 내의 값은 개별 군집 내의 상대 위치를 숫자 값으로 표현한 일종의 좌표 값이다. 예를 들어 cluster_centers[0,1]는 0번 군집에서 두 번째 피처의 위치 값을 의미한다.
cluster_centers = km_cluster.cluster_centers_
print('cluster_centers shape :',cluster_centers.shape)
print(cluster_centers)
cluster_centers shape : (3, 4611) [[0.01005322 0. 0. ... 0.00706287 0. 0. ] [0. 0.00092551 0. ... 0. 0. 0. ] [0. 0.00099499 0.00174637 ... 0. 0.00183397 0.00144581]]
각 행의 배열 값은 피처의 위치가 개별 중심과 얼마나 가까운가를 상대 값으로 나타낸 것으로, 0 ~ 1 사이의 값을 가질 수 있으며 1에 가까울수록 중심과 가까운 값을 의미한다. clustercenters 속성은 넘파이의 darray로 argsort()[:,::-1]를 이용하면 cluster_centers 배열 내 값이 큰 순으로 정렬된 위치 인덱스 값을 반환한다.
# 군집별 top n 핵심단어, 그 단어의 중심 위치 상대값, 대상 파일명들을 반환함.
def get_cluster_details(cluster_model, cluster_data, feature_names, clusters_num, top_n_features=10):
cluster_details = {}
# cluster_centers array 의 값이 큰 순으로 정렬된 index 값을 반환
# 군집 중심점(centroid)별 할당된 word 피처들의 거리값이 큰 순으로 값을 구하기 위함.
centroid_feature_ordered_ind = cluster_model.cluster_centers_.argsort()[:,::-1]
#개별 군집별로 iteration하면서 핵심단어, 그 단어의 중심 위치 상대값, 대상 파일명 입력
for cluster_num in range(clusters_num):
# 개별 군집별 정보를 담을 데이터 초기화.
cluster_details[cluster_num] = {}
cluster_details[cluster_num]['cluster'] = cluster_num
# cluster_centers_.argsort()[:,::-1] 로 구한 index 를 이용하여 top n 피처 단어를 구함.
top_feature_indexes = centroid_feature_ordered_ind[cluster_num, :top_n_features]
top_features = [ feature_names[ind] for ind in top_feature_indexes ]
# top_feature_indexes를 이용해 해당 피처 단어의 중심 위치 상댓값 구함
top_feature_values = cluster_model.cluster_centers_[cluster_num, top_feature_indexes].tolist()
# cluster_details 딕셔너리 객체에 개별 군집별 핵심 단어와 중심위치 상대값, 그리고 해당 파일명 입력
cluster_details[cluster_num]['top_features'] = top_features
cluster_details[cluster_num]['top_features_value'] = top_feature_values
filenames = cluster_data[cluster_data['cluster_label'] == cluster_num]['filename']
filenames = filenames.values.tolist()
cluster_details[cluster_num]['filenames'] = filenames
return cluster_details
dictionary를 원소로 가지는 리스트인 cluster_details를 반환한다. 여기에는 개별 군집번호, 핵심 단어, 핵심단어 중심 위치 상대값, 파일명 속성 값 정보가 있다.
def print_cluster_details(cluster_details):
for cluster_num, cluster_detail in cluster_details.items():
print('####### Cluster {0}'.format(cluster_num))
print('Top features:', cluster_detail['top_features'])
print('Reviews 파일명 :',cluster_detail['filenames'][:7])
print('==================================================')
feature_names = tfidf_vect.get_feature_names()
cluster_details = get_cluster_details(cluster_model=km_cluster, cluster_data=document_df,\
feature_names=feature_names, clusters_num=3, top_n_features=10 )
print_cluster_details(cluster_details)
####### Cluster 0 Top features: ['screen', 'battery', 'keyboard', 'battery life', 'life', 'kindle', 'direction', 'video', 'size', 'voice'] Reviews 파일명 : ['accuracy_garmin_nuvi_255W_gps', 'battery-life_amazon_kindle', 'battery-life_ipod_nano_8gb', 'battery-life_netbook_1005ha', 'buttons_amazon_kindle', 'directions_garmin_nuvi_255W_gps', 'display_garmin_nuvi_255W_gps'] ================================================== ####### Cluster 1 Top features: ['interior', 'seat', 'mileage', 'comfortable', 'gas', 'gas mileage', 'transmission', 'car', 'performance', 'quality'] Reviews 파일명 : ['comfort_honda_accord_2008', 'comfort_toyota_camry_2007', 'gas_mileage_toyota_camry_2007', 'interior_honda_accord_2008', 'interior_toyota_camry_2007', 'mileage_honda_accord_2008', 'performance_honda_accord_2008'] ================================================== ####### Cluster 2 Top features: ['room', 'hotel', 'service', 'staff', 'food', 'location', 'bathroom', 'clean', 'price', 'parking'] Reviews 파일명 : ['bathroom_bestwestern_hotel_sfo', 'food_holiday_inn_london', 'food_swissotel_chicago', 'free_bestwestern_hotel_sfo', 'location_bestwestern_hotel_sfo', 'location_holiday_inn_london', 'parking_bestwestern_hotel_sfo'] ==================================================