01. 감정분석 서비스
이번 장에서 다루는 모델들은 비슷한 감정을 표현하는 문서는 유사한 언어적 특징을 보일 것이라는 가정을 사용합니다.
모델 학습을 위한 데이터 분할
이번 과정에서는 감정 분석 모델을 만들고 활용하는 방법을 배워보도록 하겠습니다. 본격적으로 모델을 만들어 보기에 앞서 주어진 데이터를 학습 데이터와 평가 데이터로 나누는 방법에 대해서 배워보도록 하겠습니다.
학습 데이터란 감정 분석 모델을 훈련 시키기 위해 문장과 해당 문장의 감정이 포함되어 있는 데이터셋을 의미합니다.
평가 데이터란 학습된 모델의 성능을 평가하기 위해 학습에 포함되지 않은 데이터셋을 의미합니다.
지시사항
- emotions_train.txt에 들어 있는 데이터를 불러와서 각 줄을 (문장, 감정) 형태의 튜플로 data 리스트에 저장하세요.
- 각 줄에서 문장과 감정은 ;으로 구분되어 있다는 점에 유의하세요.
- 모든 줄의 끝에 존재하는 \n은 제거하세요.
- scikit-learn의 train_test_split 함수를 사용하여, 학습 데이터와 평가 데이터를 8:2의 기준으로 분할하세요.
- 매개변수에서 test_size는 0.2, random_state는 7로 설정하세요.
- 학습 데이터의 문장을 Xtrain 변수에, 감정을 Ytrain 변수에 저장하세요.
- 학습 데이터 내 문장의 개수와 감정의 종류를 출력하세요.
- 평가 데이터의 문장을 Xtest 변수에 감정을 Ytest 변수에 저장하세요.
- 평가 데이터 내 문장의 개수를 출력하세요.
from sklearn.model_selection import train_test_split
# 1. 파일을 읽어오세요.
data = []
with open('emotions_train.txt', 'r') as f:
for line in f:
sent, emotion = line.rstrip().split(";")
data.append((sent, emotion)) #튜플형태로 변환해 data리스트에 저장
# 2. 읽어온 파일을 학습 데이터와 평가 데이터로 분할하세요.
train, test = train_test_split(data,test_size = 0.2, random_state = 7)
# 3.학습 데이터셋의 문장과 감정을 분리하세요.
Xtrain = []
Ytrain = []
for train_data in train:
Xtrain.append(train_data[0])
Ytrain.append(train_data[1])
print(len(Xtrain))
print(len(set(Ytrain))) #감정들만 남게됨
# 평가 데이터셋의 문장과 감정을 분리하세요.
Xtest = []
Ytest = []
for test_data in test:
Xtest.append(test_data[0])
Ytest.append(test_data[1])
print(len(Xtest))
print(len(set(Ytest)))
02. 나이브베이즈
- 나이브 베이즈는 감정 발생 확률과 단어들의 가능도(likehood)로 텍스트의 감정을 예측합니다. 여기서 가능도란 확률 분포의 모수가, 어떤 확률변수의 표집값과 일관되는 정도를 나타내는 값을 말합니다.
나이브 베이즈 학습
나이브 베이즈 기법에서는 각 감정 내 단어의 가능도(likelihood) 를 기반으로 문장의 감정을 예측합니다. 감정 내 단어의 가능도는 아래와 같은 공식으로 계산을 할 수 있습니다.

- cal_partial_freq() 함수를 완성하세요. 함수는 텍스트 데이터(texts)와 특정 감정(emotion)을 매개변수로 가지며, 해당 감정을 나타내는 문서를 filtered_texts에 저장합니다. 이를 사용해서, 입력되는 감정을 표현하는 문서 내 각 단어의 빈도수를 partial_freq 딕셔너리 변수에 저장하세요.
- cal_total_freq 함수를 완성하세요. 이 함수는 1번에서 생성된 partial_freq 딕셔너리를 입력받아, 특정 감정별 문서 내 전체 단어의 빈도수를 계산하여 반환합니다.
- Emotions dataset for NLP 데이터셋에서 joy라는 감정을 표현하는 문장에서 단어 happy가 발생할 가능도를 joy_likelihood 변수에 저장하세요.
- Emotions dataset for NLP 데이터셋에서 sadness라는 감정을 표현하는 문장에서 단어 happy가 발생할 가능도를 sad_likelihood 변수에 저장하세요.
- Emotions dataset for NLP 데이터셋에서 surprise라는 감정을 표현하는 문장에서 단어 can이 발생할 가능도를 sad_likelihood 변수에 저장하세요.
import pandas as pd
def cal_partial_freq(texts, emotion):
partial_freq = dict()
filtered_texts = texts[texts['emotion']==emotion]
filtered_texts = filtered_texts['sentence']
# 1. 전체 데이터 내 각 단어별 빈도수를 입력해 주는 부분을 구현하세요.
for sent in filtered_texts:
words = sent.rstrip().split()
for word in words:
if word not in partial_freq:
partial_freq[word] = 1
else:
partial_freq[word] += 1 #모든 단어의 빈도수를 저장하는 딕셔너리 구현 완료 !
return partial_freq
def cal_total_freq(partial_freq):
total = 0
# 2. partial_freq 딕셔너리에서 감정별로 문서 내 전체 단어의 빈도수를 계산하여 반환하는 부분을 구현하세요.
for word, freq in partial_freq.items():
total += freq
return total
# 3. Emotions dataset for NLP를 불러옵니다.
data = pd.read_csv("emotions_train.txt", delimiter=';', header=None, names=['sentence','emotion'])
# 4. happy가 joy라는 감정을 표현하는 문장에서 발생할 가능도를 구하세요.
joy_counter = cal_partial_freq(data, "joy")
joy_likelihood = joy_counter["happy"] / cal_total_freq(joy_counter)
#joy를 표현하는 문서 내 happy의 빈도수 / joy를 표현하는 문서내 모든 단어 빈도수
print(joy_likelihood)
# 5. happy가 sadness라는 감정을 표현하는 문장에서 발생할 가능도를 구하세요.
sad_counter = cal_partial_freq(data, "sadness")
sad_likelihood = sad_counter["happy"] / cal_total_freq(sad_counter)
print(sad_likelihood)
#sad를 표현하는 문서 내 happy의 빈도수 / sad를 표현하는 문서내 모든 단어 빈도수
# 6. can이 surprise라는 감정을 표현하는 문장에서 발생할 가능도를 구하세요.
sup_counter = cal_partial_freq(data, "surprise")
sup_likelihood = sup_counter["can"] / cal_total_freq(sup_counter)
#surprise를 표현하는 문서 내 can의 빈도수 / surprise를 표현하는 문서내 모든 단어 빈도수
print(sup_likelihood)
03. 나이브베이즈 기반 감정 예측
스무딩은 학습 데이터 내 존재하지 않는 빈도수를 보정하는 것으로 분모와 분자에 12씩 더해주면 됩니다.
(2+12)/(8+12)=0.7(2+12) / (8 + 12) = 0.7
나이브 베이즈 기반 문장 감정 예측
지시사항
- [실습 2]에서 구현한 cal_partial_freq와 cal_total_freq 함수를 완성하세요.
- 입력되는 data 내 특정 감정의 로그 발생 확률 을 반환해 주는 cal_prior_prob 함수를 완성하세요.
- 로그는 np.log() 함수를 사용하여 구할 수 있습니다.
- 매개변수 data를 학습 데이터로 사용하여 sent의 각 감정별 로그 확률을 계산해 주는 predict_emotion함수를 완성하세요. 감정별 로그 확률 계산을 위해 단어의 로그 가능도를 사용하세요. 그리고 스무딩 값을 10으로 설정해 주세요. 결과는 (감정, 확률)의 형태로 predictions 리스트에 저장하세요. 이 중 확률값이 가장 높은 (감정, 확률) 튜플을 반환하세요.
- Emotions dataset for NLP 데이터셋을 학습 데이터로 사용하여, 3번에서 작성한 함수로 test_sent의 감정을 예측한 결과를 확인하세요.
import pandas as pd
import numpy as np
def cal_partial_freq(texts, emotion):
filtered_texts = texts[texts['emotion'] == emotion]
filtered_texts = filtered_texts['sentence']
partial_freq = dict()
for sent in filtered_texts:
words = sent.rstrip().split()
for word in words:
if word not in partial_freq:
partial_freq[word] = 1
else:
partial_freq[word] += 1 #모든 단어의 빈도수를 저장하는 딕셔너리 구현 완료 !
return partial_freq
def cal_total_freq(partial_freq):
total = 0
for word, freq in partial_freq.items():
total += freq
return total
def cal_prior_prob(data, emotion):
filtered_texts = data[data['emotion'] == emotion]
# 2. data 내 특정 감정의 로그발생 확률을 반환하는 부분을 구현하세요.
return np.log(len(filtered_texts)/len(data))
def predict_emotion(sent, data):
emotions = ['anger', 'love', 'sadness', 'fear', 'joy', 'surprise']
predictions = []
train_txt = pd.read_csv(data, delimiter=';', header=None, names=['sentence', 'emotion'])
# 3. sent의 각 감정별 로그 확률을 predictions 리스트에 저장하세요. (로그가능도)
for emotion in emotions:
prob = 0 #각 감정별 확률을 저장할 변수
for word in sent.split():
emotion_counter = cal_partial_freq(train_txt, emotion)
prob += np.log((emotion_counter[word]+10) / (cal_total_freq(emotion_counter)+10))
prob += cal_prior_prob(train_txt, emotion)#이건 왜 더하는거지
predictions.append((emotion,prob)) #감정과 확률을 튜플 형태로 넣어주기
predictions.sort(key = lambda a: a[1])
return predictions[-1]
# 4. 아래 문장의 예측된 감정을 확인하세요.
test_sent = "i really want to go and enjoy this party"
predicted = predict_emotion(test_sent, "emotions_train.txt")
print(predicted)
04. 사이킷런을 통한 나이브베이즈 구현
scikit-learn으로 나이브 베이즈 학습 시, 단어 빈도수 벡터로 변환하는 CountVectorizer를 이용하여 텍스트를 만들어야 합니다.
실습: scikit-learn을 통한 나이브 베이즈 감정 분석
지금까지 직접 나이브 베이즈를 구현해봤습니다. 이제 나이브 베이즈의 작동 원리를 파악했으니, scitkit-learn을 통해 나이브 베이즈를 보다 간편하게 학습하고 예측하는 방법에 대해 배워보도록 하겠습니다.
scikit-learn이란 각종 머신 러닝 모델을 간편하게 사용할 수 있게 도와주는 파이썬 라이브러리입니다.
지시사항
- scikit-learn을 통해 학습 데이터의 문장을 단어의 빈도수 벡터로 생성해 주는 CountVectorizer 객체인 변수 cv를 만들고, fit_transform 메소드로 train_data를 변환하세요. 변환 결과를 transformed_text에 저장하세요.
- 학습 문장과 학습 감정은 각각 train_data와 train_emotion에 저장되어 있습니다.
- MultinomialNB 객체인 변수 clf를 생성하고, fit 메소드로 2번에서 변환된 train_data와 train_emotion을 학습하세요.
- test_data 안에 존재하는 5개 문장의 감정을 예측하고, 결과를 test_result 변수에 저장하세요. cv.transform을 이용해 단어의 빈도수 벡터를 만든 후, clf.predict를 이용해 감정을 예측하면 됩니다.
import pandas as pd
from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import CountVectorizer
raw_text = pd.read_csv("emotions_train.txt", delimiter=';', header=None, names=['sentence','emotion'])
train_data = raw_text['sentence']
train_emotion = raw_text['emotion']
# 1. CountVectorizer 객체인 변수 cv를 만들고, fit_transform 메소드로 train_data를 변환하세요.
cv = CountVectorizer()
transformed_text = cv.fit_transform(train_data)
# 2. MultinomialNB 객체인 변수 clf를 만들고, fit 메소드로 지시사항 1번에서 변환된 train_data와 train_emotion을 학습하세요.
clf = MultinomialNB()
clf.fit(transformed_text, train_emotion) #학습시키기, 이때 train데이터는 위에서 변환시킨 것으로 넣기!
# 3. 아래 문장의 감정을 예측하세요.
test_data = ['i am curious', 'i feel gloomy and tired', 'i feel more creative', 'i feel a little mellow today']
doc_vector = cv.transform(test_data)
test_result = clf.predict(doc_vector)
print(test_result)
--> 이거 우리 구현할 때 쓰면 될 거 같다!!
실습: 나이브 베이즈 기반 감정 분석 서비스
이번 실습에서는 flask를 활용하여, 웹 기반으로 나이브 베이즈 모델을 호출하는 방법에 대해 배워보도록 하겠습니다.
nb_flask.py에는 flask로 작성된 웹 서버의 코드가 포함되어 있습니다.
웹 서버 시작 시, 이전 실습에서 학습된 CountVectorizer 객체인 cv와 MultinomialNB 객체인 clf가 로딩됩니다
지시사항
- nb_flask.py 파일 내 predict 함수를 완성하세요. 함수 내 query 변수는 웹 서버로 전달된 문자열 리스트를 의미합니다. 여기서 기존에 학습된 CountVectorizer 객체인 cv와 MultinomialNB 객체인 clf를 사용해서 query에 전달된 리스트 내 각 문장의 감정을 예측하는 코드를 작성하세요.
- 1번에서 예측된 결과를 response 딕셔너리에 문장의 순서: 예측된 감정 형태로 저장하세요.
- 실행 버튼을 눌러 main.py 내 test_data의 감정을 웹 서버를 통해 예측하세요.
#메인
import requests
# 아래 문장의 감정을 예측합니다.
test_data = ['i am happy', 'i want to go', 'i wake too early so i feel grumpy', 'i feel alarmed']
myobj = {"infer_texts": test_data}
x = requests.post("http://0.0.0.0:8080/predict", json = myobj)
print("감정 분석 결과: " + x.text)
# 경고문을 무시합니다.
import warnings
warnings.filterwarnings(action='ignore')
from flask import Flask, request, jsonify
import pickle
app = Flask(__name__)
@app.route('/predict', methods=['POST'])
def predict():
json_ = request.json
query = json_['infer_texts'] #문자열들이 전달되어 있음
# 1. 학습된 객체 cv 변수와 clf 변수를 이용해 전달된 문자열의 감정을 예측하는 코드를 작성하세요.
prediction = clf.predict(cv.transform(query))
# 2. 예측된 결과를 response 딕셔너리에 "문서의 순서: 예측된 감점" 형태로 저장하세요.
response = dict()
for idx, pred in enumerate(prediction): #index값을 저절로 출력해주는 enumerate
response[idx] = pred
return jsonify(response)
if __name__ == '__main__':
with open('nb_model.pkl', 'rb') as f:
cv, clf = pickle.load(f)
app.run(host='0.0.0.0', port=8080)
05. 기타 감정 분석 기법
문장별 감정이 매핑되어 있는 데이터셋만 제공된다면 모든 지도학습 기반 알고리즘을 사용할 수 있습니다.
협업 필터링(CF)은 비지도 학습으로 감정 분석을 하기에 적절한 머신러닝 기법이 아닙니다.