본문 바로가기

Learning/Recommendation System

03-3. Related Recommendation

Related Recommendation

- 해당 상품 구매가 꺼려지는 경우 연관된 다른 상품을 추천해주는 방법

 

"Transaction Data에서의 아이템 연관성 추출"

- 사용자 : 동일 사용자가 같이 본 아이템들은 연관성 존재

- 세션 : 동일 세션에서 같이 조회한 아이템들은 연관성 존재

- 인접 : 동일 사용자 로그의 인접한 아이템들은 연관성 존재

연관성 추출 기준

- 세션 기준이 경험상 가장 정확함.

유사도 지표

- 특별한 경우가 아니라면 코사인 유사도가 가장 유용함

가중치 할당

- 대중적인 아이템은 조회나 접근 횟수가 많아 단순 카운팅 기반으로 가중치를 부여할 경우 이상 현상이 발생할 수 있음

- 가중치를 0,1로만 한정할 수 있음

- 최대값을 한정하여 이상치를 조절할 수 있음

- 횟수에 로그를 취하여 영향을 조절할 수 있음

 

Case Study

A1. Co-occurrence with Frequency Counts
     - 단일상품주문이 많아,사용자 단위로 데이터를 묶음
     - 한 사용자가 함께 구매한 상품의 횟수를 기준으로 연관 결과를 생성
A2. Co-occurrence with Frequency Counts - IDF
     - 위 A1 결과에서 자주 구매한 상품의 가중치를 낮추기 위해 IDF를 도입하여 개선
A3. Co-occurrence with Normalized Frequency Counts - IDF
     - 위 A2 결과에서 너무 많이 구매된 상품(e.g., 아메리카노)의 이상치를 낮추기 위해 로그(log) 값으로 표준화
A4. Cosine Similarity with Binary Counts
     - 두 상품의 유사도를 계산하는데 코사인 유사도(cosine similarity)를 활용
     - 이때, 상품 횟수를 고려하지않고 구매가 발생되면 1로 통일하여 사용
A5. Cosine Similarity with Frequency Counts
     - 위 A4 결과에서 구매 횟수를 고려 하여 코사인 유사도 계산
A6. Cosine Similarity with Normalized Frequency Counts ‒ IDF
     - 위 A5 결과에서 IDF를 적용하여 자주 구매한 상품의 가중치를 낮춤

 

추천 결과가 부족한 경우

1. Content-based Related Recommendation 결과 활용
     • 영화, 뉴스와 같이 메타데이터가 풍부한 경우 잘 동작함
2. Best Recommendation 결과를 보완 로직으로 활용
     • 같은 카테고리의 베스트 상품 노출
3. 추천 결과의 연관 상품까지 결과로 활용
4. SVD 등Matrix Factorization 방법 활용

 


사용자 기준 Related Product

- 유저 기준으로 아이템이 얼마나 클릭이 되는지 확인

%%sql
drop table if exists cmc_user_product_click_cnt;

create table cmc_user_product_click_cnt as
    select user_no, item_no, count(*) cnt
        from cmc_event a
    where event_name = 'click_item'
        and a.event_timestamp between '2021-07-18' and '2021-07-25'
    group by user_no, item_no;

- 클릭 분포 확인

query = '''
    select cnt, count(*)
    from cmc_user_product_click_cnt
    group by cnt;
    '''

res1 = executeQuery(query)

import pandas as pd

df = pd.DataFrame(res1, columns=['cnt', 'count'])
df.plot.bar(x='cnt', y='count', rot=0, figsize=(20,5))

사용자 클릭 가중치 테이블 생성(normalized)

- count값을 활용하여 weight값 생성

%%sql
drop table if exists cmc_user_product_click_w;

create table cmc_user_product_click_w as
    -- normalized
    select user_no, item_no, w/sqrt(sum(w*w) over (partition by user_no)) w
    from (
        select user_no, item_no, (ln(cnt)+1) w --ln(cnt+1)로 사용해도 된다
        from cmc_user_product_click_cnt ) t;

-- user를 기준으로 access 하기 위한 index 생성
create index idx_cmc_user_product_click_w_1 on cmc_user_product_click_w (user_no, item_no, w);
-- item를 기준으로 access 하기 위한 index 생성
create index idx_cmc_user_product_click_w_2 on cmc_user_product_click_w (item_no, user_no, w);

 

"item_no = '6tt2Q2Jveb2qyFrH78Fwqw=='"

 

Co-Occurance 유사도 지표를 활용한 연관 상품 추천
item_no = '6tt2Q2Jveb2qyFrH78Fwqw=='

result = %sql select * from cmc_product where item_no = :item_no;
-- self join으로 weight값 계산
query1 = f'''
    with cmc_product_sim as (
        select b.item_no, count(*) sim  -- co-occur방법
        from cmc_user_product_click_w a 
            join cmc_user_product_click_w b 
                on a.user_no = b.user_no 
                    and a.item_no != b.item_no
        where a.item_no = '{item_no}'
        group by b.item_no
        order by sim desc
        limit 20)
    select a.sim, b.*
    from cmc_product_sim a 
        join cmc_product b on b.item_no = a.item_no
    '''
result1 = executeQuery(query1)

유사도 방법을 활용한 연관 상품 추천
query2 = '''
    with cmc_product_sim as (
        select b.item_no, sum(a.w * b.w) sim -- 유사도 방법
        from cmc_user_product_click_w a 
            join cmc_user_product_click_w b on a.user_no = b.user_no and a.item_no != b.item_no
        where a.item_no = '{item_no}'
        group by b.item_no
        order by sim desc
        limit 20)
    select a.sim, b.*
    from cmc_product_sim a 
        join cmc_product b on b.item_no = a.item_no
    '''
result2 = executeQuery(query2)


세션 기준 Related Product
%%sql
drop table if exists cmc_session_product_click_cnt;

create table cmc_session_product_click_cnt as
select session_id, item_no, count(*) cnt
from cmc_event a
where event_name = 'click_item'
	and a.event_timestamp between '2021-07-18' and '2021-07-25'
group by session_id, item_no;

- 클릭 분포 확인

query = '''
    select cnt, count(*)
    from cmc_session_product_click_cnt
    group by cnt;
    '''

res1 = executeQuery(query)

df = pd.DataFrame(res1, columns=['cnt', 'count'])
df.plot.bar(x='cnt', y='count', rot=0, figsize=(20,5))

세션 클릭 가중치 테이블 생성(normalized)
%%sql
drop table if exists cmc_session_product_click_w;

create table cmc_session_product_click_w as
select session_id, item_no, w/sqrt(sum(w*w) over (partition by session_id)) w
from (
	select session_id, item_no, (ln(cnt)+1) w
	from cmc_session_product_click_cnt ) t;

create index idx_cmc_session_product_click_w_1 on cmc_session_product_click_w (session_id, item_no, w);

create index idx_cmc_session_product_click_w_2 on cmc_session_product_click_w (item_no, session_id, w);
Co-Occurance 유사도 지표를 활용한 연관 상품 추천
result = %sql select * from cmc_product where item_no = :item_no;

query3 = f'''
    with cmc_product_sim as (
        select b.item_no, count(*) sim -- sum(a.w * b.w) sim
        from cmc_session_product_click_w a 
            join cmc_session_product_click_w b on a.session_id = b.session_id and a.item_no != b.item_no
        where a.item_no = '{item_no}'
        group by b.item_no
        order by sim desc
        limit 10)
    select a.sim, b.*
    from cmc_product_sim a 
        join cmc_product b on b.item_no = a.item_no
    '''
result3 = executeQuery(query3)

유사도 방법을 활용한 연관 상품 추천
query4 = f'''
    with cmc_product_sim as (
        select b.item_no, sum(a.w * b.w) sim
        from cmc_session_product_click_w a 
            join cmc_session_product_click_w b on a.session_id = b.session_id and a.item_no != b.item_no
        where a.item_no = '{item_no}'
        group by b.item_no
        order by sim desc
        limit 10)
    select a.sim, b.*
    from cmc_product_sim a 
        join cmc_product b on b.item_no = a.item_no
    '''
result4 = executeQuery(query4)

 


Consider Popularity

- idf값을 고려

 

%%sql

drop table if exists cmc_session_product_click_w2;
--idf값이 반영된 weight 구하기
create table cmc_session_product_click_w2 as
with df as (
	select session_id, count(*) df
	from cmc_session_product_click_cnt 
	group by session_id )
select session_id, item_no, w/sqrt(sum(w*w) over (partition by session_id)) w
from (
	select a.session_id, a.item_no, (ln(cnt)+1)*ln(100200.0/df + 1.0) w --적당한 값을 넣으면 상관 없음
	from cmc_session_product_click_cnt a left join df b on a.session_id = b.session_id
) t;


create index idx_cmc_session_product_click_w2_1 on cmc_session_product_click_w2 (session_id, item_no, w);

create index idx_cmc_session_product_click_w2_2 on cmc_session_product_click_w2 (item_no, session_id, w);

query5 = f'''
    with cmc_product_sim as (
        select b.item_no, sum(a.w * b.w) sim
        from cmc_session_product_click_w2 a 
            join cmc_session_product_click_w2 b on a.session_id = b.session_id and a.item_no != b.item_no
        where a.item_no = '{item_no}'
        group by b.item_no
        order by sim desc
        limit 20)
    select a.sim, b.*
    from cmc_product_sim a 
        join cmc_product b on b.item_no = a.item_no
    '''
result5 = executeQuery(query5)

 

대체제

- 상품정보(b)에서 카테고리에 대한 한정을 추가하면 된다.

query6 = f'''
    with cmc_product_sim as (
        select b.item_no, sum(a.w * b.w) sim
        from cmc_session_product_click_w2 a 
            join cmc_session_product_click_w2 b on a.session_id = b.session_id and a.item_no != b.item_no
        where a.item_no = '{item_no}'
        group by b.item_no
        order by sim desc)
    select a.sim, b.*
    from cmc_product_sim a 
        join cmc_product b on b.item_no = a.item_no
    -- 아이템의 카테고리와 같은 카테고리의 아이템들 중에서 추천
    where b.category3_code = (select category3_code from cmc_product where item_no = '{item_no}')
    order by a.sim desc
    limit 20
    '''
result6 = executeQuery(query6)

 

보완제

- 카테고리가 다른 것으로 한정하여 추천(category3으로 하면 신발도 나오기 때문에 더 상위 카테고리인 category2로 하는것이 좋음)

 

query7 = f'''
    with cmc_product_sim as (
        select b.item_no, sum(a.w * b.w) sim
        from cmc_session_product_click_w2 a 
            join cmc_session_product_click_w2 b on a.session_id = b.session_id and a.item_no != b.item_no
        where a.item_no = '{item_no}'
        group by b.item_no
        order by sim desc)
    select a.sim, b.*
    from cmc_product_sim a 
        join cmc_product b on b.item_no = a.item_no
    where b.category2_code != (select category2_code from cmc_product where item_no = '{item_no}')
    order by a.sim desc
    limit 20
    '''
result7 = executeQuery(query7)


출처 : The RED : 현실 데이터를 활용한 추천시스템 구현 A to Z by 번개장터 CTO 이동주

링크 : https://fastcampus.app/course-detail/205535