개인화된 추천목록 제공을 위한 정보
편의점 할인 행사 정보를 모아보여주는 우리 서비스에는 개인화된 추천 목록을 제공한다. 이를 위해서는 사용자 행동 추적 정보, 상품의 특성 정보 를 저장해야 한다.
•
사용자 행동 추적 정보 : 우리가 누군가에게 맛있는 음식을 소개하려면, 상대의 음식 취향을 알아야 한다. 이런 취향은 상대방이 어떤 음식을 좋아하는지 직접 말하거나, 어떤 음식을 주로 먹는지 관찰하여 알아낸다. 우리 서비스도 마찬가지로, 사용자에게 개인화된 추천 목록을 제공하기 위해서는 사용자가 서비스를 사용하는 행동을 추적하고 이를 저장할 필요가 있다.
•
상품의 특성 정보 : 우리가 누군가에게 맛있는 음식을 소개하려면, 음식의 맛이나 향, 성분과 같은 정보를 알아야 한다. 우리 서비스도 마찬가지로, 사용자에게 개인화된 추천 목록을 제공하기 위해서는 상품의 특성을 추출하고 이를 저장할 필요가 있다.
사용자 행동 추적 정보
서비스에서 추적할 수 있으며 의미가 있는 정보는 서비스마다 조금씩 다를 것이다. 우리 서비스에서는 검색기록, 상품 정보 조회 기록, 리뷰 작성 및 조회 기록이 유의미하다 판단했다. 이 중에서 우선 우리 서비스에서는 리뷰 조회와 작성 기록을 이용해 보기로 했다. 서비스의 UX 설계상 리뷰를 조회하는 시점과 상품의 정보를 확인하는 시점이 일치하기 때문이다.
앱 화면 - 상품 정보 조회 페이지
리뷰 조회나 작성 기록에 어떤 데이터가 포함될지 생각해봐야 한다. 우선, 어떤 상품에 대한 리뷰인지 알아야 한다. 또한, 상품의 할인 정보는 변경될 수 있으므로, 할인의 종류 역시 저장해야 한다. 마지막으로, 언제 접근했는지 그 시간을 알아야 한다. 이를 정리하면 다음과 같은 JSON으로 표현할 수 있다.
{
"accessTime" : "2023-10-17T00:47:32",
"item" : {
...
},
"promotionType" : "ONE_PLUS_ONE"
}
JavaScript
복사
이때, 할인 정보를 저장하는 테이블과 상품 정보를 저장하는 테이블은 외래키로 관계를 맺고 있으므로, 실제로는 할인 정보의 PK 만 저장하면 된다. 다시 정리하면 다음과 같은 JSON으로 표현할 수 있다.
{
"accessTime" : "2023-10-17T00:47:32",
"accessType" : "READ",
"promotionID" : 45
}
JavaScript
복사
상품의 특성 정보
상품의 특성 정보에 어떤 데이터가 포함되어야 하는지 역시 서비스마다 다르다. 이를 결정하기 위해선, 우선 보유하고 있는 데이터를 파악해야 한다. 서비스 구축을 위해 우리가 수집한 상품&할인 정보는 다음과 같다.
1.
상품 이름
2.
편의점 브랜드
3.
상품 분류 : 음료, 식품, 과자, 아이스크림, 생활용품 중 하나
4.
상품 가격 : 개별 상품 가격. 할인 적용 전
5.
행사 종류 : 1+1 혹은 2+1
6.
행사 기간 : 공개된 데이터 부족으로, 정확한 기간은 수집 실패. 매일 자정 오늘도 행사중인가? 를 확인하는 방식
상품의 고유한 특성이라 할 수 있는 것은 상품 이름과 상품 분류 뿐이다. 이 중 별도의 가공 없이 바로 사용할 수 있는 것은 상품 분류이지만, 지나치게 포괄적이라 추천 알고리즘에 사용하기는 부적절했다. 따라서 우리는 이런 특성에서 보다 자세한 데이터를 추출하는 방법을 고민했다.
추천 알고리즘의 본질적 특징
상품의 특성을 어떻게 추출하는가? 를 다루기 전에 추천 알고리즘의 본질적 특징을 언급할 필요가 있다. 추천 알고리즘을 통해 생성되는 추천 목록에는 정답이 없다. 단지 서비스 사용자가 이질감을 느끼지 않는 범위 내에서 적당한 콘텐츠를 제공하면 된다. 즉, 정답이 있고 그런 정답을 제공해야 하는 기능 아니기 때문에 다른 기능들에 비해 제공하는 결과에 대한 융통성이 높다.
이런 추천 알고리즘의 특징과 상품의 특성을 어떻게 추출하는가? 가 동시에 고려하면 다음과 같은 사실을 도출할 수 있다.
상품의 특성을 추출하는 정확도가 100%가 아니어도 된다. 구체적인 정확도 수치를 정할 수 없지만, 어느정도 납득 불가능한 특성이 추출되는 것이 용인된다.
이런 사실을 알고 나면 굳이 특성 추출의 정확도를 높이기 위해 고군분투 할 필요가 없어진다.
정규식을 이용한 세부 분류 추출
이런 이유로 인공지능을 사용하거나 보다 복잡한 방식을 사용하지 않고, 단순한 방식으로 특성을 분류하기로 했다.
상품을 만드는 제조사에서 상품의 이름을 지을 때, “상품의 이름이 상품의 특성을 잘 반영해 상품의 사용자가 상품의 이름만 듣고 어떤 상품인지 잘 알 수 있는가?”를 고려한다. 예를 들어 ”화이트 초코쿠키“라는 상품명을 딸기맛 과자에 붙이는 것이 아니라, ”화이트 초콜렛을 사용한 쿠키형태의 과자“에 붙일 것이다.
이런 사실에 입각해 우리의 경험을 통해 “세부 분류”를 정의하고, 각 세부 분류에 해당하는 상품의 이름이 가질 것으로 생각되는 문구들을 목록으로 만들어 이를 검사하는 방식으로 상품의 특성을 추출했다.
String fruitRegex = "(?i)(망고|딸기|바나나|사과|오렌지|포도|수박|파인애플|체리|레몬|라즈베리|블루베리|키위|복숭아|샤인머스|베리|자몽|과일|과즙|청귤|알로에|유자|과일야채샐러드|워터멜론|피치)";
boolean isFruit = calcByRegex(fruitRegex,"과일", promotion);
private boolean calcByRegex(String regex, String keyword, Promotion promotion){
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(promotion.getName());
if(matcher.find()){
promotion.addSubCategory(keyword, 1);
return true;
}
return false;
}
Java
복사
정규식을 이용한 세부 분류 추출
위 코드는 음료 분류에 속한 상품이 “과일" 세부 분류에 속하는지 확인하는 코드의 일부다. 정규 표현식을 사용해 여러 과일이름이 상품 이름에 포함되는 것이 확인되면 “과일” 세부 분류로 판단한다. 다른 세부 분류 역시 같은 방식으로 정의하고 분류하였다.
추천 알고리즘 설계
설계 후 테스트까지 진행한 추천 알고리즘은 누적 가중치 방식 , 최근 접근 기록 방식 이다. 두 방식은 사용자의 선호도를 판단하는 방식과 이를 반영해 추천 목록을 생성하는 방식에 차이가 있다.
누적 가중치 방식
이 방식은 사용자가 특정 상품의 리뷰에 접근 할 때, 그 상품의 분류와 세부 분류에 정해진 가중치를 누적해 부여하고, 추천목록을 생성할 때 가중치를 반영하는 방식이다. 이 알고리즘은 가중치를 얼마나 부여할 것인가? 와 가중치를 어떻게 반영할 것인가? 라는 질문의 답에 따라 그 결과가 많이 달라진다.
가중치를 얼마나 부여할 것인가?
이 질문은 리뷰 조회 vs 리뷰 작성 과 분류 vs 세부 분류 관점에서 생각해야 한다.
리뷰 조회 vs 리뷰 작성 관점에서, 리뷰 조회보다 리뷰 작성이 더욱 강한 반응이다. 따라서 리뷰 조회보다 리뷰 작성에 더 강한 가중치를 부여해야 한다. 또한, 리뷰 작성의 경우 그 평점에 따라 긍정적인 반응을 의미하기도 하고 부정적인 반응을 의미하기도 한다.
분류 vs 세부 분류 관점에서, 같은 분류에 속하는 여러 세부분류들에 대해 가중치를 적용할지, 한다면 얼마나 부여해야 하는지 결정해야 한다. 예를 들어 ‘식품’ 분류의 ‘라면’ 세부 분류에 해당하는 “신라면”의 리뷰가 조회되었을 때, ‘식품’ 분류의 ‘냉동식품’ 세부분류에 가중치를 부여할지 말지 결정해야 한다. 부여하지 않는다면 추천 목록이 과적합을 보일 수 있고, 부여한다면 거의 관련 없는 상품이 추천될 가능성이 있다. 이를 보안하기 위해서는 세부 분류 사이의 유사성에 따라 유사한 세부분류끼리만 가중치가 상호 부여되도록 할 수 있다. 물론 이 경우 세부 분류 사이의 유사성을 판단해 미리 정해둬야 한다.
가중치를 어떻게 반영할 것인가?
이 질문은 추천 목록에 노이즈를 얼마나 어떻게 반영할 것인가? 와 같은 질문이다. 가중치가 높은 분류의 상품만을 추천목록에 반영한다면 추천 되는 세부 분류에 과적합이 발생할 것이다. 우리 서비스는 콘텐츠의 수가 한정적이기 때문에 콘텐츠가 많은 다른 서비스에 비해 과적합에 더욱 치명적이다. 따라서, 관련 없는 상품이 추천목록에 보여지도록 노이즈를 추가해야 한다.
결론
앞서 언급한 요소들을 고려해 우리는 다음과 같은 규칙을 정해 알고리즘을 완성시켰다.
1.
추천 목록은 최대 10개의 상품으로 구성된다.
2.
추천 목록의 7개는 가장 가중치가 높은 세부 분류 3개에 해당하는 상품으로 구성하고, 나머지 3개는 무작위로 택한 세부 분류의 상품으로 구성한다.
a.
7개의 가중치 기반 목록에서 각 세부분류가 차지하는 개수는 다음과 같다.
i.
가중치 1위 : 3개
ii.
가중치 2, 3위 : 2개
b.
3개의 무작위 상품의 경우 가중치가 높은 3개의 세부 분류는 제외한다. 또한 3개의 세부 분류를 선택하고, 선정된 세부 분류마다 한개의 상품을 선택한다.
3.
가중치의 분류는 다음과 같다.
a.
리뷰 조회 대상 상품의 세부 분류에 가중치를 2 부여한다.
i.
리뷰 조회 대상 상품의 분류와 같은 분류에 속하는 세부 분류 중 조회대상이 속한 세부분류가 아닌 세부분류 중 절반을 무작위로 뽑아 가중치 1을 부여한다.
b.
리뷰 작성 대상 상품의 세부 분류에 가중치를 1 추가 부여한다.
i.
단, 리뷰의 평점이 3 미만인 경우 부여하지 않는다.
최근 접근 기록 방식
이 방식은 가중치가 누적되지 않고 최근에 접근한 세부 분류를 기반으로 추천 목록을 생성하는 방식이다. 이 알고리즘은 최근 몇개의 접근 기록을 반영할 것인가?와접근 기록간의 가중치는 어떻게 부여할 것인가? 라는 질문의 답에 따라 그 결과가 많이 달라진다.
최근 몇개의 접근 기록을 반영할 것인가?
너무 많은 접근 기록이 반영된다면 추천 목록이 난잡해질 가능성이 있고, 너무 적은 기록이 반영될 경우 과거의 행동이 지나치게 적게 반영될 수 있다.
접근 기록 간의 가중치는 어떻게 부여할 것인가?
이 질문은 최근 기록일 수록 선호도를 높게 반영하는가? 와 최근의 여러 접근 기록에서 중복되는 세부 분류에 더 높은 가중치를 부여할 것인가? 라는 질문으로 나뉜다. 첫번째 질문의 경우 얼핏 생각하면 타당하다고 생각할 수 있다. 하지만, 사용자의 행동이 단순한 실수일 가능성과 일시적인 관심일 경우를 고려하면 더 최근의 기록이 선호도를 더 크게 반영한다고 볼 수 없다. 두번째 질문의 경우 최근의 여러 접근 기록에서 반복적으로 보이는 특성은 단순 실수나 일시적인 관심일 가능성이 적으므로 타당하다는 것을 알 수 있다.
결론
앞서 언급한 요소들을 고려해 우리는 다음과 같은 규칙을 정해 알고리즘을 완성시켰다.
1.
추천 목록은 최대 10개의 상품으로 구성된다.
2.
최근 3개의 접근 기록을 반영해 추천 목록을 생성한다.
a.
최근 3개의 접근 기록에서 등장한 세부 분류를 그 등장 횟수와 함께 기록한다.
i.
이때, 리뷰 작성 기록의 경우 등장 횟수를 1회 가산한다.
1.
단, 같은 세부 분류에 속한 상품 여러개에 리뷰를 작성한 경우 한번만 가산한다. 예를 들어 ‘라면’ 세부 분류에 속한 ‘신라면’과 ‘열라면’에 리뷰를 작성한 기록이 있다면, ‘라면’ 세부 분류의 등장 횟수는 2번이나, 1회 가산되어 3번이 된다.
b.
각 세부 분류의 등장 횟수의 비율로 추천 목록을 생성한다.
i.
단, 모든 세부 분류가 1개 이상 포함되어야 한다.
다음 포스트
다음 포스트에선 위 추천 알고리즘을 구현하면서 데이터를 어떻게 저장했는지에 따른 내용을 다룰 것이다.