추천시스템 작동원리 이해를 위한 구현실습

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}\)

112

실제값 : \(\ 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