추천시스템 작동원리 이해를 위한 구현실습
2019-01-29
.
그림, 실습코드 등 학습자료 출처 : https://gitlab.com/radajin
[추천시스템 구현 예시]
- 여행지 평점데이터에 대한 추천시스템 구현
[구현순서]
- 샘플 데이터 생성
- 사용자 기반의 유사도 행렬 생성
- 유사도 행렬 기반 예측행렬 생성
- 기사추천 리스트 생성
- 모델에 대한 성능측정
1. 간단한 샘플 데이터 생성
[사용할 변수설명]
- sample_df : 샘플데이터
- sm_df : 유사도행렬
- closer_count : 유사한 사용자 데이터의 갯수
- ms_df : 유사도 행렬의 평균값
- predict_df : 예측값에 대한 행렬데이터
- recommand_def : 추천결과
from scipy import spatial
# 여행지 평점데이터 샘플 생성
# sample data set matrix
columns = ["두바이","뉴욕","런던","멜번","도쿄"]
index = ["또치", "둘리", "희동", "마이콜"]
data = np.array([
[5,3,0,0,2],
[2,0,0,1,4],
[0,0,4,3,1],
[4,0,4,5,0],
])
sample_df = pd.DataFrame(data, columns=columns, index=index)
sample_df
두바이 | 뉴욕 | 런던 | 멜번 | 도쿄 | |
---|---|---|---|---|---|
또치 | 5 | 3 | 0 | 0 | 2 |
둘리 | 2 | 0 | 0 | 1 | 4 |
희동 | 0 | 0 | 4 | 3 | 1 |
마이콜 | 4 | 0 | 4 | 5 | 0 |
2. 샘플데이터를 기반으로 유사도 행렬 생성
- 유사도 측정함수 정의
def Euclidean_Distance_Similarity(vector_1, vector_2):
## 샘플데이터에서 데이터가 0인 것들은 먼저 제거해줘야 하는데 방법은 아래와 같다.
## 0으로 비어있는 데이터 제거
idx = np.array(vector_1).nonzero()[0]
vector_1 = np.array(vector_1[idx])
vector_2 = np.array(vector_2[idx])
# idx = np.array(vector_2).nonzero()[0]
# vector_1 = np.array(vector_1[idx])
# vector_2 = np.array(vector_2[idx])
return np.linalg.norm(vector_1 - vector_2)
def Cosine_Similarity(vector_1, vector_2):
## 0으로 비어있는 데이터 제거
idx = np.array(vector_1).nonzero()[0]
vector_1 = np.array(vector_1[idx])
vector_2 = np.array(vector_2[idx])
# idx = np.array(vector_2).nonzero()[0]
# vector_1 = np.array(vector_1[idx])
# vector_2 = np.array(vector_2[idx])
return 1 - spatial.distance.cosine(vector_1, vector_2)
- 유사도 행렬 산출하는 함수정의
def similarity_matrix(sample_df, similarity_function):
index = sample_df.index
matrix = []
# sample_df의 로우를 하나씩 돌면서 데이터를 가져옴
for idx_1, value_1 in sample_df.iterrows():
row = []
for idx_2, value_2 in sample_df.iterrows():
row.append(similarity_function(value_1, value_2))
matrix.append(row)
return pd.DataFrame(matrix, columns = index, index=index)
sm_df =similarity_matrix(sample_df, Cosine_Similarity)
sm_df
또치 | 둘리 | 희동 | 마이콜 | |
---|---|---|---|---|
또치 | 1.000000 | 0.652929 | 0.324443 | 0.811107 |
둘리 | 0.729397 | 1.000000 | 0.483046 | 0.443039 |
희동 | 0.196116 | 0.332956 | 1.000000 | 0.949474 |
마이콜 | 0.529813 | 0.770054 | 0.821210 | 1.000000 |
3. 유사도에 대한 평균값 산출 (산출수단 : Mean Score)
# 추천할 대상, 유사한 데이터를 몇개까지 사용할지 결정
target, closer_count = '또치', 2
ms_df = sm_df.drop(target)
ms_df
또치 | 둘리 | 희동 | 마이콜 | |
---|---|---|---|---|
둘리 | 0.729397 | 1.000000 | 0.483046 | 0.443039 |
희동 | 0.196116 | 0.332956 | 1.000000 | 0.949474 |
마이콜 | 0.529813 | 0.770054 | 0.821210 | 1.000000 |
## 유사도값을 내림차순으로 정렬
ms_df = ms_df.sort_values(target, ascending = False)
ms_df
또치 | 둘리 | 희동 | 마이콜 | |
---|---|---|---|---|
둘리 | 0.729397 | 1.000000 | 0.483046 | 0.443039 |
마이콜 | 0.529813 | 0.770054 | 0.821210 | 1.000000 |
희동 | 0.196116 | 0.332956 | 1.000000 | 0.949474 |
## 사용자와 유사도가 높은 사용자를 필터링
## 가까운 2명만 산출
ms_df = ms_df[:closer_count]
ms_df
또치 | 둘리 | 희동 | 마이콜 | |
---|---|---|---|---|
둘리 | 0.729397 | 1.000000 | 0.483046 | 0.443039 |
마이콜 | 0.529813 | 0.770054 | 0.821210 | 1.000000 |
sample_df.loc[ms_df.index]
두바이 | 뉴욕 | 런던 | 멜번 | 도쿄 | |
---|---|---|---|---|---|
둘리 | 2 | 0 | 0 | 1 | 4 |
마이콜 | 4 | 0 | 4 | 5 | 0 |
# 평균값을 저장할 ndarray를 생성
mean = np.zeros(len(sample_df.columns))
mean
array([0., 0., 0., 0., 0.])
# ms_df의 데이터 평균값을 구함
for ms_user in ms_df.index:
mean += sample_df.loc[ms_user]
mean /= len(ms_df.index)
mean
두바이 3.0
뉴욕 0.0
런던 2.0
멜번 3.0
도쿄 2.0
Name: 둘리, dtype: float64
pre_df = pd.DataFrame(columns = sample_df.columns)
pre_df.loc['유저'] = sample_df.loc[target]
pre_df.loc['평균'] = mean
pre_df
두바이 | 뉴욕 | 런던 | 멜번 | 도쿄 | |
---|---|---|---|---|---|
유저 | 5 | 3 | 0 | 0 | 2 |
평균 | 3 | 0 | 2 | 3 | 2 |
- 위의 작업들을 한번에 처리할 수 있는 mean_score 함수 정의
def mean_score(sample_df, sm_df, target, closer_count):
ms_df = sm_df.drop(target)
ms_df = ms_df.sort_values(target, ascending = False)
ms_df = ms_df[:closer_count]
ms_df = sample_df.loc[ms_df.index]
# 결과데이터 생성
pred_df = pd.DataFrame(columns = sample_df.columns)
pred_df.loc['유저'] = sample_df.loc[target]
pred_df.loc['평균'] = ms_df.mean()
return pred_df
target, closer_count = "또치", 2
pred_df = mean_score(sample_df, sm_df, target, closer_count)
pred_df
두바이 | 뉴욕 | 런던 | 멜번 | 도쿄 | |
---|---|---|---|---|---|
유저 | 5 | 3 | 0 | 0 | 2 |
평균 | 3 | 0 | 2 | 3 | 2 |
4. 추천여행지 출력
recommand_df = pred_df.T
recommand_df
유저 | 평균 | |
---|---|---|
두바이 | 5 | 3 |
뉴욕 | 3 | 0 |
런던 | 0 | 2 |
멜번 | 0 | 3 |
도쿄 | 2 | 2 |
recommand_df = recommand_df[recommand_df["유저"]==0]
recommand_df
유저 | 평균 | |
---|---|---|
런던 | 0 | 2 |
멜번 | 0 | 3 |
recommand_df = recommand_df.sort_values("평균",ascending=False)
recommand_df
유저 | 평균 | |
---|---|---|
멜번 | 0 | 3 |
런던 | 0 | 2 |
list(recommand_df.index)
['멜번', '런던']
- 위의 작업들을 한번에 처리할 수 있는 recommand 함수 정의
def recommand(pred_df):
recommand_df = pred_df.T
recommand_df = recommand_df[recommand_df["유저"]==0]
recommand_df = recommand_df.sort_values("평균",ascending=False)
return list(recommand_df.index)
recommand(pred_df)
['멜번', '런던']
5. run function 생성
def run(sample_df, similarity_func, target, closer_count):
# 유사도 행렬 데이터 생성
sm_df = similarity_matrix(sample_df, similarity_func)
# 유사도가 높은 데이터의 평균값 산출
pred_df = mean_score(sample_df, sm_df, target, closer_count)
return recommand(pred_df)
run(sample_df, Cosine_Similarity, '또치', 2)
['멜번', '런던']
run(sample_df, Cosine_Similarity, '둘리', 2)
['런던', '뉴욕']
run(sample_df, Cosine_Similarity, '희동', 2)
['두바이', '뉴욕']
run(sample_df, Cosine_Similarity, '마이콜', 2)
['뉴욕', '도쿄']
6. 성능평가
- 평가수단 :
- MSE(Mean Squared Error) = \(\ \frac{1} \sum_{i=1}^n(y_{i} - \hat{y}_{i})^2\)
- RMSE(Root Mean Squared Error) = \(\ \sqrt{\frac{1} \sum_{i=1}^n(y_{i} - \hat{y}_{i})^2}\)
실제값 : \(\ y_{i}\), 예측치 : \(\ \hat{y}_{i}\)
def mse(value, pred):
# user 데이터에서 0인 데이터 제거
idx = value.nonzero()[0]
value, pred = np.array(value)[idx], np.array(pred)[idx]
# user 데이터에서 0인 데이터 제거
idx = pred.nonzero()[0]
value, pred = np.array(value)[idx], np.array(pred)[idx]
# 수식 계산후 결과 리턴
return sum((value - pred)**2) / len(idx)
mse(pred_df.loc["유저"], pred_df.loc["평균"])
2.0
def rmse(value, pred):
# user 데이터에서 0인 데이터 제거
idx = value.nonzero()[0]
value, pred = np.array(value)[idx], np.array(pred)[idx]
# user 데이터에서 0인 데이터 제거
idx = pred.nonzero()[0]
value, pred = np.array(value)[idx], np.array(pred)[idx]
# 수식 계산후 결과 리턴
return np.sqrt(sum((value - pred)**2) / len(idx))
rmse(pred_df.loc["유저"], pred_df.loc["평균"])
1.4142135623730951
def mae(value, pred):
# value의 0 데이터 제거
idx = value.nonzero()[0]
value, pred = np.array(value)[idx], np.array(pred)[idx]
# pred의 0 데이터 제거
idx = pred.nonzero()[0]
value, pred = np.array(value)[idx], np.array(pred)[idx]
return sum(np.absolute(value - pred)) / len(idx)
mae(pred_df.loc["유저"], pred_df.loc["평균"])
1.0
# 전체 추천 모델에 대한 성능평가
def evaluate(df, sm_df, closer_count, algorithm):
# 유저리스트
users = df.index
# 유저별 evaluate 값의 모음
evaluate_list = []
# 모든 user에 대해서 mae 값을 구함
for target in users:
# 하나의 유저에 대한 예측 값을 획득
result_df = mean_score(df, sm_df, target, closer_count)
evaluate_list.append(algorithm(result_df.loc["유저"], result_df.loc["평균"]))
# 모든 user의 mae값의 평균을 리턴
return np.average(evaluate_list)
evaluate(sample_df, sm_df, 2, mse)
3.916666666666667
evaluate(sample_df, sm_df, 2, rmse)
1.9009287182747576
evaluate(sample_df, sm_df, 2, mae)
1.666666666666667