[중급] 가볍게 이것저것
[자연어 처리]와인 추천 시스템 간단하게 구현하기
PassionPython
2019. 9. 24. 17:28
NLP 간단하게 구현해보기 - 와인 추천 시스템
kaggle 의 wine-reviews 데이터 셋을 이용하여
키워드 입력시 이와 가장 유사한 와인을 찾는 알고리즘
keyword - TFIDF, SGDClassifier, pandas, merge, outer-join, index, boolean-indexing
import pandas as pd
# https://www.kaggle.com/zynicide/wine-reviews
path = 'winemag-data-130k-v2.csv'
data = pd.read_csv(path, index_col=0)
data.shape
(129971, 13)
# 95점 이상 데이터만 추려냅니다.
pre = data.loc[data['points'] >= 95]
pre.shape
(2416, 13)
# sklearn 으로부터 TFIDF 모듈을 불러와 사용합니다.
from sklearn.feature_extraction.text import TfidfVectorizer
sentences = list(pre['description'])
vect = TfidfVectorizer()
X = vect.fit_transform(sentences)
# TF 및 TFIDF 개념은 아래 링크 참고:
# https://en.wikipedia.org/wiki/Tf%E2%80%93idf
# sklearn 으로부터 SGD Classifier 를 불러와 사용합니다.
import numpy as np
from sklearn.linear_model import SGDClassifier
model = SGDClassifier(loss = 'log')
y = pre['title']
# 학습에 사용되는 독립변수와 종속변수인 X -> y 를 정해줍니다.
# 맞추고자 하는 classes 는 이름에 중복이 있으므로 np.unique() 처리합니다.
model.partial_fit(X, y, classes=np.unique(y))
SGDClassifier(alpha=0.0001, average=False, class_weight=None,
early_stopping=False, epsilon=0.1, eta0=0.0, fit_intercept=True,
l1_ratio=0.15, learning_rate='optimal', loss='log', max_iter=1000,
n_iter_no_change=5, n_jobs=None, penalty='l2', power_t=0.5,
random_state=None, shuffle=True, tol=0.001,
validation_fraction=0.1, verbose=0, warm_start=False)
# 추천에 사용될 검색어를 입력합니다.
key_word = 'fruity'
# 기존 문서들을 TFIDF 변환한 것과 같이
# 새로운 검색어에 해당되는 키워드도 TFIDF 변환 해 줍니다.
X_pred = vect.transform([key_word])
# 검색어를 TFIDF 변환하여 학습한 모델에 집어넣습니다.
# 보통 점수가 가장 높은 하나만 뽑는 model.predict() 를 사용하는데
# 지금은 모든 와인에 대해 각각 점수를 보는 predict_proba 를 사용합니다.
y_pred = model.predict_proba(X_pred)
# 모든 와인에 대한 점수 정보와 제목 정보를 하나의 데이터프레임 형태로
# 결합하기 위해 일단 각각 데이터 프레임 형태로 변환해 줍니다.
# y_pred 에 .T 를 하는 이유는 차원을 맞춰주기 위함입니다.
score = pd.DataFrame(y_pred.T, columns=['score'])
label = pd.DataFrame(np.unique(y), columns=['label'])
# score와 label 은 앞으로 한 데이터프레임으로 합칠 것인데
# 가로 * 세로 차원이 같아야 의미가 있습니다.
# 예) label 에서 10 번째 row 정보가 와인의 이름이면
# score 에서 10 번째 row 정보는 해당 와인의 점수 입니다.
print(score.shape)
print(label.shape)
# score 의 (2377, 1) 은
# index = class(와인 인덱스), column = 예측 정밀도 입니다.
# label 의 (2377, 1) 은
# index = class(와인 인덱스), column = 와인의 이름 입니다.
(2377, 1)
(2377, 1)
# score 와 label 두 데이터프레임을 하나로 합쳐 줍니다.
# how='outer' 는 full-outer-join 을 의미합니다.
# left_index=True 와 right_index=True 는
# 각 인덱스(순서) 를 유지한다는 의미입니다.
merged = pd.merge(score, label,
how='outer',
left_index=True,
right_index=True)
# score 컬럼에는 probability 가 담겨 있습니다.
# 따라서 score 컬럼을 기준으로 내림차순 정렬하여
# head() 로 상위 5개만 보면 추천 순위 top_5 가 나오게 됩니다.
top_5 = merged.sort_values(by='score', ascending=False).head()
top_5
score | label | |
---|---|---|
1830 | 0.000979 | Quinta do Passadouro 2011 Vintage (Port) |
552 | 0.000702 | Château Smith Haut Lafitte 2010 Pessac-Léognan |
2365 | 0.000699 | Woodward Canyon 2009 Chardonnay (Washington) |
642 | 0.000698 | Cálem 2011 Port |
607 | 0.000697 | Cockburn's 2011 Vintage (Port) |
# 잘 추천되었는지 확인하기 위해 가장 score가 높은 와인의 이름으로
# 원본 데이터를 검색하여 와인 설명 항목을 보는 부분입니다.
first = top_5['label'].values[0]
data.loc[data['title'] == first]['description'].values[0]
'Big, bold and fruity, this wine shows richness, opulence and swathes of black plum fruit that cushions the structure. Powerful and fruity now, with long aging potential shown by the ample tannins and acid.'