프로젝트 정리/소프트웨어 응용

11/27 변경 내용 정리 - 파일 로드 부분 통합, 스케일링

가 만든 시스템의 파일들은 다음과 같이 구성되어 있다.

 

1. recommendation_menu.py : 프로그램을 처음 시작할 때 실행된다. 콘솔 창에 사용자와 관리자를 위한 메뉴를 띄워준다.

 

2. project_bias.py : 아이템 - 사용자 평점 행렬을 만들며, 이를 이용하여 Model based Collaborative filtering 기법을 수행한다. SGD, minibatch를 적용한 SGD가 구현되어 있다.

 

3. User_mode.py : recommendation_menu.py에서 사용자 모드를 선택하면 이 파일의 클래스 User_mode가 실행된다. 여기서는 사용자의 ID를 입력하고 각 사용자에 맞는 추천 목록을 출력하거나, 사용자로부터 추천목록에서 마음에 들지 않는 항목들을 입력받아 이를 추천목록에서 지우기, 그리고 키워드를 입력하면 그와 연관된 추천 목록을 출력해주는 역할(구현 예정)을 한다.

 

4. TF_IDF.py : 사용자가 작성한 리뷰들로 TF_IDF 기법을 수행하고, 이 값들을 상품 별로 더한 뒤 해당 상품에 작성된 총 리뷰 갯수로 나누어 상품과 연관 있는 키워드를 뽑아낸다. 이를 이용하여 추천 목록 옆에 해당 아이템과 관련된 키워드를 출력해준다. 프로그램을 처음 실행했을 때 모든 아이템의 TF-IDF 값을 구하는 과정이 수행되며, 이를 파일로 저장한다. 이전에 프로그램을 실행한 적이 있어 이미 저장된 파일이 존재할 경우 이를 로드해서 사용한다.

 

 

어제 프로그램을 다시 한 번 점검해보니, 리뷰 데이터를 불러오는 작업이 project_bias.py, TF_IDF.py 두 곳에서 이루어지는 것을 확인할 수 있었다 데이터 로드를 한번만 하기 위해서 다음과 같은 새로운 파일 file_preprocessor.py를 만들었다. 

 

import json
import numpy as np

class File_pp:
    pp=None
    def __init__(self):
        try:
            self.data = open("Digital_Music_5.json", 'r')
        except IOError:
            print("file open failed!")
            sys.exit()
            
        self.items={}
        self.users={}
        self.reviews={}
        self.rating=None
        self.cal_rating_review_matrix()
        
    def get_Instance():
        if File_pp.pp==None:
            File_pp.pp=File_pp()
        return File_pp.pp
    
    def cal_rating_review_matrix(self):
        i=0
        j=0
        for line in self.data:
            k = json.loads(line)
            if k['asin'] not in self.items or k['asin'] not in self.reviews:
                if k['asin'] not in self.items:
                    self.items[k['asin']] = i
                    i += 1
                    
                if k['asin'] not in self.reviews:
                    self.reviews[k['asin']] = []
                    
            if k['reviewerID'] not in self.users:
                self.users[k['reviewerID']] = j
                j += 1

            self.reviews[k['asin']].append(k['reviewText'].replace('\'', '').replace('&#', ''))

        items_size = len(self.items)
        users_size = len(self.users)
        self.rating = np.zeros((items_size, users_size))

        self.data.seek(0)
        for line in self.data:
            k = json.loads(line)
            self.rating[self.items[k['asin']], self.users[k['reviewerID']]] = k['overall']

    def get_rating_matrix(self):
        return self.rating, self.items, self.users
    
    def get_review_matrix(self):
        return self.reviews

 

클래스 File_pp의 인스턴스는 프로그램이 처음 실행될 때, 즉 recommendation_menu.py에서 이 클래스의 정적 메소드인 get_Instance()를 통해서 생성된다. 생성자에서 데이터 파일을 로드하고cal_rating_review_matrix 메소드를 실행하여 아이템 - 사용자 평점 행렬, 사용자 - 인덱스 딕셔너리, 아이템 - 인덱스 딕셔너리, 사용자별 리뷰를 모아놓은 딕셔너리를 만든다. get_rating_matrix 메소드는 project_bias.py메소드를 통해 평점 행렬, 사용자 - 인덱스 딕셔너리, 아이템 - 인덱스 딕셔너리를 얻고,  TF_IDF.py는 get_review_matrix에서 사용하며, 사용자별 리뷰를 모아놓은 딕셔너리를 얻는다. 이렇게 함으로써 기존에 데이터 파일 로드가 project_bias.py, TF_IDF.py 두 곳에서 이루어는 것에서 file_preprocessor.py에서 한 번만 이루어지는 것으로 구조가 좀 더 좋게 바뀌었다. 

 

 

 

내가 사용하는 리뷰 데이터의 평점 범위는 1~5점 사이인데, 기존에 만들었던 추천 엔진은 예측 평점이 이 범위를 넘어가곤 했다. 이는 모델 기반 CF의 오차 때문에 일어나는 현상인데, 이를 그냥 놔두면 매우 부자연스러워 값을 다시 범위 안으로 맞춰주는 rescaling 과정이 필요했다. 이를 위해 먼저 예측 평점 값의 범위를 0에서 1로 맞추고, 여기에 4를 곱한 뒤 1을 더하여 범위를 1에서 5 사이로 맞추는 과정을 수행했다. rescaling 과정은 project_bias.py에 있는 Recommendation_engine 클래스의 rescaling 메소드를 통해 구현했다.

 

rescaling 메소드.

 

먼저 기존에 파일에 주어졌던 평점 정보들을 별도의 행렬 recover_matrix에 백업하고. 이 정보들은 리스케일링 대상에 포함되지 않는다. 그 후 copy_matrix에 예측 평점 행렬을 복사하고 백업해둔 평점들은 리스케일링 대상에서 제외하기 위해 0으로 만든 뒤 리스케일링을 수행한다. copy_matrix에서 최댓값과 최솟값을 구한 뒤, 각 평점에서 최솟값을 빼고 그 값을 (최댓값-최솟값)으로 나누어 범위를 0~1로 맞춘 뒤 여기에 4를 곱하고 1을 더해 범위를 1~5로 맞춘다. 모든 작업이 끝나면 copy_matrix에 0으로 만들었던 기존 평점들을 recover_matrix을 통해 복원한 뒤 copy_matrix를 리턴한다.

 

 

리스케일링 전과 후의 추천 목록 변화는 다음과 같다.

리스케일링 전.
리스케일링 후.

 

예상 평점이 5점을 넘지 않게되어 훨씬 깔끔해진 모습을 볼 수 있다. 

'프로젝트 정리 > 소프트웨어 응용' 카테고리의 다른 글

11/27 TF-IDF, 클래스 분리  (0) 2019.11.27
Recommendation System with SGD  (0) 2019.11.22