귀찮음 주의!!
# 한글폰트 설정
## colab 환경에서 한글 폰트 설정
!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf
import matplotlib.pyplot as plt
plt.rc('font', family='NanumBarunGothic')
plt.title('안녕')
참고 문서
딥러닝을 이용한 자연어 처리 e-book : https://wikidocs.net/94600
박성호의 심화기계학습 팀프젝 코랩 코드 : https://colab.research.google.com/drive/1951HcDnn8PfdC7ucUpFkz4BnmVLhD3ed?usp=sharing
핸즈온 머신러닝 2판 github : https://nbviewer.org/github/rickiepark/handson-ml2/tree/master/
Jupyter Notebook Viewer
nbviewer.org
import re
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import urllib.request
from collections import Counter
#from konlpy.tag import okt
from sklearn.model_selection import train_test_split
urllib.request.urlretrieve(
"https://raw.githubusercontent.com/bab2min/corpus/master/sentiment/naver_shopping.txt",
filename="ratings_total.txt")
total_data = pd.read_table('ratings_total.txt', names=['ratings', 'reviews'])
total_data[:5]
네이버 영화리뷰와 다른점은 75:25비율로 학습 검증 셋이 나뉘어져있었는데
네이버 쇼핑리뷰 데이터는 초기에 urllib으로 가져왔을때의 원본 데이터가 total data인 것이다.
# 전처리 1
## 레이블 설정
# 평점 1,2,4,5 의 비율(value_counts)로 Bar Graph
total_data['ratings'].value_counts().plot(kind = 'bar')
# ratings 4, 5는 긍정(1), 1, 2는 부정(0)
total_data['label'] = np.select([total_data.ratings > 3], [1], default=0)
total_data[:5]
## 중복 확인
# unique한 값이 얼마나 있는지 출력
total_data['ratings'].nunique(), total_data['reviews'].nunique(), total_data['label'].nunique()
ratings열의 경우 1, 2, 4, 5라는 네 가지 값을 가지고 있습니다. reviews열에서 중복을 제외한 경우 199,908개입니다. 현재 20만개의 리뷰가 존재하므로 이는 현재 갖고 있는 데이터에 중복인 샘플들이 있다는 의미입니다. 중복인 샘플들을 제거해줍니다.
total_data.drop_duplicates(subset=['reviews'], inplace=True)
# reviews 열에서 중복인 내용이 있다면 중복 제거
print('총 샘플의 수 :',len(total_data))
# total_data의 row 수(length) 출력
total_data['label'].value_counts()
# 한글과 공백을 제외하고 모두 제거
total_data['reviews'] = total_data['reviews'].str.replace("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]","")
total_data['reviews'].replace('', np.nan, inplace=True)
print(total_data.isnull().sum())
# EDA
## 워드 클라우드
from wordcloud import WordCloud, STOPWORDS
stopwords = ['다','아' ,'그냥','진짜','너무','의',
'정말', '가','이','은','들','는','좀','잘','걍','과','도','를',
'으로','자','에','와','한','하다']
%matplotlib inline
wordcloud1 = WordCloud(font_path='NanumBarunGothic.ttf',
stopwords = stopwords,
colormap='Set3',
background_color = 'black',
width = 1200, height = 600).generate(' '.join(total_data['reviews']))
plt.figure(figsize = (15, 20))
plt.imshow(wordcloud1)
plt.axis("on")
plt.show()
wordcloud1.to_image()
train_data, test_data = train_test_split(total_data,
test_size = 0.1,
random_state = 42)
print('훈련용 리뷰의 개수 :', len(train_data))
print('테스트용 리뷰의 개수 :', len(test_data))
train_data['label'].value_counts().plot(kind = 'bar')
test_data['label'].value_counts().plot(kind = 'bar')
# 전처리 2
## konlpy install
# Konlpy 설치
!pip install konlpy
## tokenizer 정의
from konlpy.tag import Okt
okt = Okt()
def okt_tokenizer(text):
tokens = okt.morphs(text)
return tokens
## TF-IDF
시도 1 : 이번 프로젝트에서는 train_test를 하기전에 tf-idf를 먼저한다.
그 이유는 train_test의 분할 비율이 어떠할때 정확도가 최대가 되는 것인지를
보고싶은데 train_test를 먼저하고 tf-idf를 하면 비율이 정해져 있을때
tf-idf를 하기때문에 시간이 굉장히 오래 걸린다.
시간도 비용이기 때문에 비용의 최소화를 위해서는 tf-idf를 하고 train_test_split을 한다.
=> 이렇게 하면 dimension 오류로 실행안됨
# 토크나이저, TFIDF
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(tokenizer=okt_tokenizer,
stop_words=stopwords,
ngram_range=(1,3), min_df=3 , max_df=0.90)
tfidf.fit(total_data['reviews'])
train_tfidf = tfidf.transform(train_data['reviews'])
test_tfidf = tfidf.transform(test_data['reviews'])
#----------------9:1로 나눴을때 18분 걸림-------
end = time.time()
print('토큰화 수행 시간 :')
print(end - start)
tfidf_vocab = pd.DataFrame(sorted(tfidf.vocabulary_.items()),
columns=['vocab','counts'])
tfidf_vocab.to_csv('tfidf_vocabulary_items.csv')
tfidf_vocab
이런 쓸따리 없을 것 같은 vocab들은 제거하면 성능이 좋아질 것 같은데 언제 다 삭제할까,, 최대 난제다
# 모델링 : 정확도 91.1%
## 로지스틱 회귀
### 로지스틱 회귀 ###
import time
start = time.time()
#----------------------------------------------
lr = LogisticRegression(C=3, max_iter=50, random_state=0) # 모델 생성
lr.fit( train_tfidf, y_train ) # 모델 -> train data 학습
lr_pred = lr.predict( test_tfidf ) # 모델 -> test data 예측
lr_acc = lr.score( test_tfidf, y_test ) # 정확도 반환
#----------------------------------------------
print("로지스틱 회귀 training accuracy :", lr.score( train_tfidf , y_train )*100, "%")
print("로지스틱 회귀 testing accuracy :", lr_acc * 100, "%")
print("--------------------------------------------------------------------------")
print(classification_report( y_test, lr_pred )) # 모델 보고서 출력
print("--------------------------------------------------------------------------")
print(confusion_matrix( y_test, lr_pred )) # 오차행렬 출력
print("--------------------------------------------------------------------------")
#----------------------------------------------
end = time.time()
print('로지스틱 회귀 수행시간 :')
print(end - start)
plt.figure(figsize=(20, 20))
plot_confusion_matrix(lr , test_tfidf , y_test , cmap='Blues') # 오차행렬 시각화
plt.title("<< Logistic Regression >>")
from sklearn.model_selection import GridSearchCV
#효과적인 하이퍼 파라미터 세팅을 찾아줌
import numpy as np
lr = LogisticRegression( random_state = 0 )
params = {'C' : [3, 3.5, 4, 4.5, 5, 6],
'max_iter' : [10, 50, 100, 1000],
"penalty":["l1","l2"]
}
lr_grid_cv = GridSearchCV(lr,
param_grid=params,
cv=3,
scoring='accuracy',
verbose=1)
lr_grid_cv
start = time.time()
#----------------------------------------------
lr_grid_cv.fit(train_tfidf, y_train)
print("로지스틱 최적 점수 : {}".format(lr_grid_cv.best_score_))
print("로지스틱 최적 파라미터 : {}".format(lr_grid_cv.best_params_))
print(lr_grid_cv.best_estimator_)
#----------------------------------------------
end = time.time()
print("--------------------------------------------------------------------")
print('Execution time is:')
print(end - start)
이후 나이브 베이즈, 디시젼트리, 랜덤포레스트, SVM, KNN 등 해보았지만 로지스틱 회귀가 가장 성능이 좋았음
# 모델 평가
import pandas as pd
models_acc = {'로지스틱 회귀': lr_acc*100,
'나이브 베이즈': nb_acc*100 ,
'의사결정나무' : dt_clf_acc*100,
'K-최근접 이웃': knn_acc*100 ,
'랜덤포레스트': rnd_clf_acc*100,
'서포트 벡터 머신' : svm_acc*100
}
models_acc_df = pd.DataFrame(pd.Series(models_acc))
models_acc_df.columns = ['정확도']
models_acc_df['모델'] = ['로지스틱 회귀',
'나이브 베이즈',
'의사결정나무',
'K-최근접 이웃',
'랜덤포레스트',
'서포트 벡터 머신'
]
models_acc_df.set_index(pd.Index([1, 2, 3, 4 , 5, 6]))
import plotly.express as px
fig = px.bar(models_acc_df,
x='정확도',
y='모델' ,
color='모델',
range_x=(50, 100),
template="plotly_white",
text_auto='.4s',
title="<< 최종 모델평가 >>" )
fig.update_traces(textfont_size=15, textangle=0, textposition="inside")
fig.update_yaxes(categoryorder="total ascending")
fig.update_layout(height = 600, width = 1200, hovermode = 'closest')
# 새로운 텍스트를 직접 입력해 감성 예측 수행해봅시다!
st = input("감성을 분석할 문장을 입력하세요: ")
st = re.compile(r'[ㄱ-ㅣ가-힣]+').findall(st)
print(st)
st = [" ".join(st)]
print(st)
# 입력 텍스트의 벡터화
st_tfidf = tfidf.transform(st)
st_tfidf
# 감성 분석 모델에 적용하여 예측
st_predict = lr.predict(st_tfidf)
st_predict
>>> array([0])
# 예측값 출력
if(st_predict ==0):
print(st, "->> 부정 감성")
else:
print(st, "->> 긍정 감성")
['배송이 왜이렇게 느린거야'] ->> 부정 감성
import os
import sys
import urllib.request
import datetime
import time
import json
# 각자가 발급받은 Naver API 정보를 입력합니다- https://developers.naver.com/apps/#/myapps/0Oa0z62LyTJsVFqAPBZW/overview
client_id = ''
client_secret = ''
#[CODE 1]
def getRequestUrl(url):
req = urllib.request.Request(url)
req.add_header("X-Naver-Client-Id", client_id)
req.add_header("X-Naver-Client-Secret", client_secret)
try:
response = urllib.request.urlopen(req)
if response.getcode() == 200:
print ("[%s] Url Request Success" % datetime.datetime.now())
return response.read().decode('utf-8')
except Exception as e:
print(e)
print("[%s] Error for URL : %s" % (datetime.datetime.now(), url))
return None
#[CODE 2]
def getNaverSearch(node, srcText, start, display):
base = "https://openapi.naver.com/v1/search"
node = "/%s.json" % node
parameters = "?query=%s&start=%s&display=%s" % (urllib.parse.quote(srcText), start, display)
url = base + node + parameters
responseDecode = getRequestUrl(url) #[CODE 1]
if (responseDecode == None):
return None
else:
return json.loads(responseDecode)
#[CODE 3]
def getPostData(post, jsonResult, cnt):
title = post['title']
description = post['description']
org_link = post['originallink']
link = post['link']
pDate = datetime.datetime.strptime(post['pubDate'], '%a, %d %b %Y %H:%M:%S +0900')
pDate = pDate.strftime('%Y-%m-%d %H:%M:%S')
jsonResult.append({'cnt':cnt, 'title':title, 'description': description,
'org_link':org_link, 'link': org_link, 'pDate':pDate})
return
#[CODE 0]
def main():
node = 'news' # 크롤링 할 대상
srcText = input('검색어를 입력하세요: ')
cnt = 0
jsonResult = []
jsonResponse = getNaverSearch(node, srcText, 1, 100) #[CODE 2]
total = jsonResponse['total']
while ((jsonResponse != None) and (jsonResponse['display'] != 0)):
for post in jsonResponse['items']:
cnt += 1
getPostData(post, jsonResult, cnt) #[CODE 3]
start = jsonResponse['start'] + jsonResponse['display']
jsonResponse = getNaverSearch(node, srcText, start, 100) #[CODE 2]
print('전체 검색 : %d 건' %total)
with open('%s_naver_%s.json' % (srcText, node), 'w', encoding='utf8') as outfile:
jsonFile = json.dumps(jsonResult, indent=4, sort_keys=True, ensure_ascii=False)
outfile.write(jsonFile)
print("가져온 데이터 : %d 건" %(cnt))
print ('%s_naver_%s.json SAVED' % (srcText, node))
if __name__ == '__main__':
main()
우린 '겨울 and 쇼핑'으로 네이버 뉴스를 크롤링하여 다시 예측해보았다
import json
file_name = '겨울_쇼핑_naver_news.json'
with open(file_name, encoding='utf8') as j_f:
data = json.load(j_f)
import pandas as pd
data = pd.read_json('겨울_쇼핑_naver_news.json', orient='records')
data.to_csv('겨울_쇼핑_naver_news.csv')
data = pd.read_csv('겨울_쇼핑_naver_news.csv')
data
# title 부분에 대한 데이터의 피처 벡터화를 통한 감성 분석
data_title_tfidf = tfidf.transform(data['title'])
data_title_predict = lr.predict(data_title_tfidf)
data['title_label'] = data_title_predict
data
# description 부분에 대한 데이터의 피처 벡터화를 통한 감성 분석
data_description_tfidf = tfidf.transform(data['description'])
data_description_predict = lr.predict(data_description_tfidf)
data['description_label'] = data_description_predict
data
import pandas as pd
columns_name = ['title','title_label','description','description_label']
NEG_data_df = pd.DataFrame(columns=columns_name)
POS_data_df = pd.DataFrame(columns=columns_name)
for i, data in data.iterrows():
title = data['title']
description = data['description']
t_label = data['title_label']
d_label = data['description_label']
if d_label == 0: # 부정 감성 샘플만 추출
NEG_data_df = NEG_data_df.append(pd.DataFrame([[title, t_label, description, d_label]],columns=columns_name),
ignore_index=True)
else : # 긍정 감성 샘플만 추출
POS_data_df = POS_data_df.append(pd.DataFrame([[title, t_label, description, d_label]],columns=columns_name),
ignore_index=True)
# 파일에 저장.
NEG_data_df.to_csv('predict_news_NES.csv', encoding='utf-8')
POS_data_df.to_csv('predict_news_POS.csv', encoding='utf-8')
len(NEG_data_df), len(POS_data_df)
부정 기사가 545건, 긍정 기사가 455건
POS_description = POS_data_df['description']
POS_description_noun_tk = []
for d in POS_description:
POS_description_noun_tk.append(okt.nouns(d)) #형태소가 명사인 것만 추출
print(POS_description_noun_tk)
POS_description_noun_join = []
for d in POS_description_noun_tk:
d2 = [w for w in d if len(w) > 1] #길이가 1인 토큰 제외
POS_description_noun_join.append(" ".join(d2)) # 토큰을 연결(join)하여 리스트 구성
POS_tfidf = TfidfVectorizer(tokenizer = okt_tokenizer, min_df=2 )
POS_dtm = POS_tfidf.fit_transform(POS_description_noun_join)
POS_vocab = dict()
for idx, word in enumerate(POS_tfidf.get_feature_names()):
POS_vocab[word] = POS_dtm.getcol(idx).sum()
POS_words = sorted(POS_vocab.items(), key=lambda x: x[1], reverse=True)
POS_words #긍정 단어들 갯
긍정기사에 있는 단어들중에 가장 많이 출몰한 단어들이다
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings(action='ignore')
max = 15 #바 차트에 나타낼 단어의 수
# 긍정 뉴스 단어 시각화
plt.bar(range(max), [i[1] for i in POS_words[:max]], color="blue")
plt.title("긍정 뉴스의 단어 상위 %d 개" %max, fontsize=15)
plt.xlabel("Word", fontsize=12)
plt.ylabel("Sum of TF-IDF", fontsize=12)
plt.xticks(range(max), [i[0] for i in POS_words[:max]], rotation=70)
plt.show()
같은 방식으로 부정 단어들도 시각화
# 부정 뉴스 단어 시각화
max = 15
plt.bar(range(max), [i[1] for i in NEG_words[:max]], color="red")
plt.title("부정 뉴스의 단어 상위 %d개" %max, fontsize=25)
plt.xlabel("단어", fontsize=12)
plt.ylabel("TF-IDF의 합", fontsize=12)
plt.xticks(range(max), [i[0] for i in NEG_words[:max]], rotation=70, fontsize=25)
plt.show()
'아티피쎨 인텔리젼쓰 > 자연어 처리 (Natural Language Process)' 카테고리의 다른 글
[Python/NLP] 네이버 영화 리뷰 감성 분석 (0) | 2023.02.08 |
---|