python 추천 구현

chi yeong Yu·2022년 3월 3일
0
post-thumbnail

추천 시스템 구현해보기

해당 데이터는 'Online Retail'라 불리는 데이터 셋 (영국에 있는 온라인 소매 기업 8개월 간의 구매기록)

import pandas as pd
import scipy.sparse as sparse
import numpy as np
from scipy.sparse.linalg import spsolve
import openpyxl
import implicit
# conda install -c conda-forge openpyxl

website_url = 'http://archive.ics.uci.edu/ml/machine-learning-databases/00352/Online%20Retail.xlsx'
retail_data = pd.read_excel(website_url, engine='openpyxl') 
# openpyxl error일 시에 engine='openpyxl'
#송장번호, 아이템ID, 아이템 설명, 구매량,
#구매일자, 아이템 가격, 고객ID, 고객 국적 -columns
--------------------------------------------#image_1

clean_data = retail_data[retail_data['CustomerID'].notna()]
clean_data.info()
# customer_id 결측치 or NA 제거
--------------------------------------------#image_2

item_lookup = clean_data[['StockCode', 'Description']].drop_duplicates()
item_lookup['StockCode'] = item_lookup['StockCode'].astype(str)
item_lookup.head()
#아이템 ID에 따른 아이템 설명 테이블을 만듦
#추천한 아이템이 어떤 아이템인지, 어떤 아이템을 구매한 건지 파악 하기위함
----------------------------------------------#image_3

#ALS 알고리즘을 사용하기에 앞서 데이터 형태를 만들어 줘야함
# unique 고객ID -> 행
# unique ItemID -> 열
# 각 유저가 각 아이템을 구매한 총 횟수 -> 값
# 희소행렬 (Sparse Matrix)

clean_data['CustomerID'] = clean_data['CustomerID'].astype(int)
#matrix가 불필요한 메모리까지 차지하기에 sparse matrix로 만듦 -> 값이 없는 매트릭스도 존재하기 때문
clean_data = clean_data[['CustomerID', 'StockCode', 'Quantity']]
# 고객 ID, 아이템 ID, 구매량 

group_clean = clean_data.groupby(['CustomerID', 'StockCode']).sum().reset_index()
# 고객과 아이템 별로 총 얼마나 구매했는지 -> .sum()
group_clean[group_clean['Quantity'] == 0] = 1
#구매 수량이 0인 데이터는 환불한 고객이기에 1로 변환
group_purchased = group_clean[group_clean['Quantity']> 0]
# 구매한 물건만 뽑기
group_purchased.head()
----------------------------------------------#image_4

customers = list(np.sort(group_purchased['CustomerID'].unique()))
products = list(group_purchased['StockCode'].unique())
quantity = list(group_purchased['Quantity'])

rows = group_purchased['CustomerID'].astype('category').cat.codes
cols = group_purchased['StockCode'].astype('category').cat.codes

#print(len(customers))
#print(len(products))

purchased_sparse = sparse.csr_matrix((quantity, (rows, cols)), shape=(len(customers), len(products)))

# unique 고객ID -> 행
# unique ItemID -> 열
# 각 유저가 각 아이템을 구매한 총 횟수 -> 값
# 희소행렬 (Sparse Matrix)을 만들어줌
----------------------------------------------#image_5
def make_train (matrix, percentage =0.2):
    '''
    -----------------------------------------------------
    설명
    유저-아이템 행렬 (matrix)에서 
    1. 0이상의 값을 가지면 1의 값을 갖도록 binary하게 테스트 데이터를 만들고
    2. 훈련 데이터는 원본 행렬에서 percentage 비율만큼 0으로 바뀜
    
    -----------------------------------------------------
    반환
    training_set: 훈련 데이터에서 percentage 비율만큼 0으로 바뀐 행렬
    test_set:     원본 유저-아이템 행렬의 복사본
    user_inds:    훈련 데이터에서 0으로 바뀐 유저의 index
    '''
    
    test_set = matrix.copy()
    test_set[test_set !=0] = 1 # binary하게 만들기
    
    training_set = matrix.copy()
    nonzero_inds = training_set.nonzero()
    nonzero_pairs = list(zip(nonzero_inds[0], nonzero_inds[1]))
    
    random.seed(0)
    num_samples = int(np.ceil(percentage * len(nonzero_pairs)))
    samples = random.sample (nonzero_pairs, num_samples)
    
    user_inds = [index[0] for index in samples]
    item_inds = [index[1] for index in samples]
    
    training_set[user_inds, item_inds] = 0
    training_set.eliminate_zeros()
    
    return training_set, test_set, list(set(user_inds))
    
product_train, product_test, product_users_altered = make_train(purchase_sparse, 0.2)

# import implicit
# Cython과 OpenMP를 이용해 병렬적으로 모형을 처리할 수 있기 때문에 훨씬 빠르게 ALS를 구현할 수 있습니다.
alpha = 15
model = implicit.als.AlternatingLeastSquares(factors=20, regularization = 0.1, iterations = 50)

conf = (product_train*alpha).astype('double') #->행렬 형태를 double로 지정

model.fit(conf.T)
user_vecs, item_vecs = model.item_factors, model.user_factors

#함수 구현
def get_items_purchased(customer_id, mf_train, customer_list, products_list, item_lookup):
    cust_ind = np.where (customer_list == customer_id)[0][0]
    purchased_ind = mf_train[cust_ind,:].nonzero()[1]
    prod_codes = products_list[purchased_ind]
    
    return item_lookup.loc[item_lookup.StockCode.isin(prod_codes)]

customers_arr = np.array(customers)
products_arr = np.array(products)

image_1

image_2

image_3

image_4

image_5

image_6

from sklearn.preprocessing import MinMaxScaler

def rec_items(customer_id, mf_train, user_vecs, item_vecs, customer_list, item_list, item_lookup, num_items = 10):
    '''
    유저의 추천 아이템 반환
    -----------------------------------------------------
    INPUT
    1. customer_id - Input the customer's id number that you want to get recommendations for
    2. mf_train: 훈련 데이터
    3. user_vecs: 행렬 분해에 쓰인 유저 벡터
    4. item_vecs: 행렬 분해에 쓰인 아이템 벡터
    5. customer_list: 평점 행렬의 행에 해당하는 고객 ID
    6. item_list: 평점 행렬의 열에 해당하는 아이템 ID
    7. item_lookup: 아이템 ID와 설명을 담은 테이블
    8. num_items: 추천할 아이템 개수
    -----------------------------------------------------
    반환    
    구매한 적이 없는 아이템 중 예측 평점이 높은 최고 n개의 추천 아이템
    '''
    
    cust_ind = np.where(customer_list == customer_id)[0][0]
    pref_vec = mf_train[cust_ind,:].toarray()                   # 훈련 데이터의 실제 평점
    pref_vec = pref_vec.reshape(-1) + 1                         # 1을 더해서 환불한 것도 구매한 걸로 간주
    pref_vec[pref_vec > 1] = 0                                  # 구매한 것들을 모두 0으로 
    rec_vector = user_vecs[cust_ind,:].dot(item_vecs.T)         # 추천 시스템에 기반한 예측 평점
    
    # Min-Max Scaling
    min_max = MinMaxScaler()
    rec_vector_scaled = min_max.fit_transform(rec_vector.reshape(-1,1))[:,0] 
    recommend_vector = pref_vec*rec_vector_scaled  # 구매하지 않은 아이템에 대해서만 예측 평점이 남도록
    
    product_idx = np.argsort(recommend_vector)[::-1][:num_items] # num_items만큼 내림차순으로 평점 정렬한 index
    
    rec_list = []
    
    for index in product_idx:
        code = item_list[index] # 아이템 id
        # id와 description 담기
        rec_list.append([code, item_lookup['Description'].loc[item_lookup['StockCode'] == code].iloc[0]]) 
    
    codes = [item[0] for item in rec_list]
    descriptions = [item[1] for item in rec_list]
    final_frame = pd.DataFrame({'StockCode': codes, 'Description': descriptions})
    
    return final_frame[['StockCode', 'Description']]

image_7

참고문헌

https://assaeunji.github.io/machine%20learning/2020-11-29-implicitfeedback/

profile
호기심천국

0개의 댓글