ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [추천] Surprise Package
    머신러닝 & 딥러닝 2021. 12. 16. 19:23

    Surprise 패키지

    • 파이썬에서 추천을 쉽게 제공하는 대표적인 패키지가 surprise. 이용 이용해 추천 프로세스를 쉽게 적용할 수 있다.
    • Surprise를 이용한 추천 수행 프로세스
    1. 필요한 라이브러리
    2. 데이터 로딩
    3. 행렬 분해를 수행할 알고리즘을 SVD 생성하고 학습용 데이터로 학습.
    4. 예측 및 평가
    pip install surprise
    Collecting surprise
      Downloading surprise-0.1-py2.py3-none-any.whl (1.8 kB)
    Collecting scikit-surprise
      Downloading scikit-surprise-1.1.1.tar.gz (11.8 MB)
         |████████████████████████████████| 11.8 MB 8.1 MB/s eta 0:00:01
    [?25hRequirement already satisfied: joblib>=0.11 in /Users/terrydawunhan/opt/anaconda3/lib/python3.8/site-packages (from scikit-surprise->surprise) (0.17.0)
    Requirement already satisfied: numpy>=1.11.2 in /Users/terrydawunhan/opt/anaconda3/lib/python3.8/site-packages (from scikit-surprise->surprise) (1.19.2)
    Requirement already satisfied: scipy>=1.0.0 in /Users/terrydawunhan/opt/anaconda3/lib/python3.8/site-packages (from scikit-surprise->surprise) (1.5.2)
    Requirement already satisfied: six>=1.10.0 in /Users/terrydawunhan/opt/anaconda3/lib/python3.8/site-packages (from scikit-surprise->surprise) (1.15.0)
    Building wheels for collected packages: scikit-surprise
      Building wheel for scikit-surprise (setup.py) ... [?25ldone
    [?25h  Created wheel for scikit-surprise: filename=scikit_surprise-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl size=758586 sha256=0dfb4285a81e42429997062436d3653c9407c0acb0f2afd7dd99248c40c48fc6
      Stored in directory: /Users/terrydawunhan/Library/Caches/pip/wheels/20/91/57/2965d4cff1b8ac7ed1b6fa25741882af3974b54a31759e10b6
    Successfully built scikit-surprise
    Installing collected packages: scikit-surprise, surprise
    Successfully installed scikit-surprise-1.1.1 surprise-0.1
    Note: you may need to restart the kernel to use updated packages.
    
    import surprise 
    
    print(surprise.__version__)
    1.1.1
    

     

    Surprise 를 이용한 추천 시스템 구축

    from surprise import SVD
    from surprise import Dataset 
    from surprise import accuracy 
    from surprise.model_selection import train_test_split

     

    내장 데이터를 로드하고 학습과 테스트 데이터로 분리

    data = Dataset.load_builtin('ml-100k') 
    trainset, testset = train_test_split(data, test_size=.25, random_state=0) 
    Dataset ml-100k could not be found. Do you want to download it? [Y/n] y
    Trying to download dataset from http://files.grouplens.org/datasets/movielens/ml-100k.zip...
    Done! Dataset ml-100k has been saved to /Users/terrydawunhan/.surprise_data/ml-100k
    

     

    추천 행렬 분해 알고리즘으로 SVD객체를 생성하고 학습수행

    algo = SVD()
    algo.fit(trainset) 
    
    

     

    테스트 데이터 세트에 예상 평점 데이터 예측. test()메소드 호출 시 Prediction 객체의 리스트로 평점 예측 데이터 반환

    predictions = algo.test(testset)
    print('prediction type :',type(predictions), ' size:',len(predictions))
    print('prediction 결과의 최초 5개 추출')
    predictions[:5]
    prediction type : <class 'list'>  size: 25000
    prediction 결과의 최초 5개 추출
    
    [Prediction(uid='120', iid='282', r_ui=4.0, est=3.7212516381350893, details={'was_impossible': False}),
     Prediction(uid='882', iid='291', r_ui=4.0, est=3.794397970688899, details={'was_impossible': False}),
     Prediction(uid='535', iid='507', r_ui=5.0, est=4.11178480852736, details={'was_impossible': False}),
     Prediction(uid='697', iid='244', r_ui=5.0, est=3.380854731006738, details={'was_impossible': False}),
     Prediction(uid='751', iid='385', r_ui=4.0, est=3.141280602802788, details={'was_impossible': False})]
    
    [ (pred.uid, pred.iid, pred.est) for pred in predictions[:3] ]
    [('120', '282', 3.7212516381350893),
     ('882', '291', 3.794397970688899),
     ('535', '507', 4.11178480852736)]
    

     

    predict()메소드는 개별 사용자,아이템에 대한 예측 평점 정보를 반환

    # 사용자 아이디, 아이템 아이디는 문자열로 입력해야 함. 
    uid = str(196)
    iid = str(302)
    pred = algo.predict(uid, iid)
    print(pred)
    user: 196        item: 302        r_ui = None   est = 4.36   {'was_impossible': False}
    

     

    반환된 Prediction의 리스트 객체를 기반으로 RMSE 평가

    accuracy.rmse(predictions)
    RMSE: 0.9482
    
    0.9481525968434303
    

     

    Surprise 주요 모듈 소개

    csv 파일로 사용자 평점 데이터 생성

    import pandas as pd
    
    ratings = pd.read_csv('./ml-latest-small/ratings.csv')
    # ratings_noh.csv 파일로 unload 시 index 와 header를 모두 제거한 새로운 파일 생성.  
    ratings.to_csv('./ml-latest-small/ratings_noh.csv', index=False, header=False)

     

    Reader클래스로 파일의 포맷팅 지정하고 Dataset의 load_from_file()을 이용하여 데이터셋 로딩

    from surprise import Reader
    
    reader = Reader(line_format='user item rating timestamp', sep=',', rating_scale=(0.5, 5))
    data=Dataset.load_from_file('./ml-latest-small/ratings_noh.csv',reader=reader)

     

    학습과 테스트 데이터 세트로 분할하고 SVD로 학습후 테스트데이터 평점 예측 후 RMSE평가

    trainset, testset = train_test_split(data, test_size=.25, random_state=0)
    
    # 수행시마다 동일한 결과 도출을 위해 random_state 설정 
    algo = SVD(n_factors=50, random_state=0)
    
    # 학습 데이터 세트로 학습 후 테스트 데이터 세트로 평점 예측 후 RMSE 평가
    algo.fit(trainset) 
    predictions = algo.test( testset )
    accuracy.rmse(predictions)
    RMSE: 0.8682
    
    0.8681952927143516
    

     

    판다스 DataFrame기반에서 동일하게 재 수행

    import pandas as pd
    from surprise import Reader, Dataset
    
    ratings = pd.read_csv('./ml-latest-small/ratings.csv') 
    reader = Reader(rating_scale=(0.5, 5.0))
    
    # ratings DataFrame 에서 컬럼은 사용자 아이디, 아이템 아이디, 평점 순서를 지켜야 합니다. 
    data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)
    trainset, testset = train_test_split(data, test_size=.25, random_state=0)
    
    algo = SVD(n_factors=50, random_state=0)
    algo.fit(trainset) 
    predictions = algo.test( testset )
    accuracy.rmse(predictions)
    RMSE: 0.8682
    
    0.8681952927143516
    

     

    교차 검증(Cross Validation)과 하이퍼 파라미터 튜닝

    cross_validate()를 이용한 교차 검증

    from surprise.model_selection import cross_validate 
    
    # Pandas DataFrame에서 Surprise Dataset으로 데이터 로딩 
    ratings = pd.read_csv('./ml-latest-small/ratings.csv') # reading data in pandas df
    reader = Reader(rating_scale=(0.5, 5.0))
    data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)
    
    algo = SVD(random_state=0) 
    cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=5, verbose=True) 
    Evaluating RMSE, MAE of algorithm SVD on 5 split(s).
    
                      Fold 1  Fold 2  Fold 3  Fold 4  Fold 5  Mean    Std     
    RMSE (testset)    0.8797  0.8710  0.8765  0.8707  0.8788  0.8753  0.0038  
    MAE (testset)     0.6746  0.6695  0.6765  0.6676  0.6716  0.6720  0.0032  
    Fit time          4.04    3.95    3.96    3.88    3.92    3.95    0.05    
    Test time         0.09    0.13    0.13    0.08    0.08    0.10    0.02    
    
    {'test_rmse': array([0.87973002, 0.87097599, 0.87650565, 0.87071133, 0.87876642]),
     'test_mae': array([0.67457227, 0.66952237, 0.67646247, 0.66763389, 0.67159836]),
     'fit_time': (4.036445140838623,
      3.953843832015991,
      3.9641458988189697,
      3.8815019130706787,
      3.916300058364868),
     'test_time': (0.08991098403930664,
      0.1314692497253418,
      0.13462305068969727,
      0.08193612098693848,
      0.07979702949523926)}
    

     

    GridSearchCV 이용

    from surprise.model_selection import GridSearchCV
    
    # 최적화할 파라미터들을 딕셔너리 형태로 지정. 
    param_grid = {'n_epochs': [20, 40, 60], 'n_factors': [50, 100, 200] }
    
    # CV를 3개 폴드 세트로 지정, 성능 평가는 rmse, mse 로 수행 하도록 GridSearchCV 구성
    gs = GridSearchCV(SVD, param_grid, measures=['rmse', 'mae'], cv=3)
    gs.fit(data)
    
    # 최고 RMSE Evaluation 점수와 그때의 하이퍼 파라미터
    print(gs.best_score['rmse'])
    print(gs.best_params['rmse'])
    0.878321328665093
    {'n_epochs': 20, 'n_factors': 50}
    

     

    Surprise 를 이용한 개인화 영화 추천 시스템 구축

    SVD 학습은 TrainSet 클래스를 이용해야 함

    # 아래 코드는 train_test_split( )으로 분리되지 않는 Dataset에 fit( )을 호출하여 오류를 발생합니다.
    data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)
    algo = SVD(n_factors=50, random_state=0)
    algo.fit(data)

     

    DatasetAutoFolds를 이용한 전체 데이터를 TrainSet클래스 변환

    from surprise.dataset import DatasetAutoFolds
    
    reader = Reader(line_format='user item rating timestamp', sep=',', rating_scale=(0.5, 5))
    # DatasetAutoFolds 클래스를 ratings_noh.csv 파일 기반으로 생성. 
    data_folds = DatasetAutoFolds(ratings_file='./ml-latest-small/ratings_noh.csv', reader=reader)
    
    #전체 데이터를 학습데이터로 생성함. 
    trainset = data_folds.build_full_trainset()

     

    SVD로 학습

    algo = SVD(n_epochs=20, n_factors=50, random_state=0)
    algo.fit(trainset)
    
    
    # 영화에 대한 상세 속성 정보 DataFrame로딩
    movies = pd.read_csv('./ml-latest-small/movies.csv')
    
    # userId=9 의 movieId 데이터 추출하여 movieId=42 데이터가 있는지 확인. 
    movieIds = ratings[ratings['userId']==9]['movieId']
    if movieIds[movieIds==42].count() == 0:
        print('사용자 아이디 9는 영화 아이디 42의 평점 없음')
    
    print(movies[movies['movieId']==42])
    사용자 아이디 9는 영화 아이디 42의 평점 없음
        movieId                   title              genres
    38       42  Dead Presidents (1995)  Action|Crime|Drama
    
    uid = str(9)
    iid = str(42)
    
    pred = algo.predict(uid, iid, verbose=True)
    user: 9          item: 42         r_ui = None   est = 3.13   {'was_impossible': False}
    
    def get_unseen_surprise(ratings, movies, userId):
        #입력값으로 들어온 userId에 해당하는 사용자가 평점을 매긴 모든 영화를 리스트로 생성
        seen_movies = ratings[ratings['userId']== userId]['movieId'].tolist()
    
        # 모든 영화들의 movieId를 리스트로 생성. 
        total_movies = movies['movieId'].tolist()
    
        # 모든 영화들의 movieId중 이미 평점을 매긴 영화의 movieId를 제외하여 리스트로 생성
        unseen_movies= [movie for movie in total_movies if movie not in seen_movies]
        print('평점 매긴 영화수:',len(seen_movies), '추천대상 영화수:',len(unseen_movies), \
              '전체 영화수:',len(total_movies))
    
        return unseen_movies
    
    unseen_movies = get_unseen_surprise(ratings, movies, 9)
    평점 매긴 영화수: 46 추천대상 영화수: 9696 전체 영화수: 9742
    
    def recomm_movie_by_surprise(algo, userId, unseen_movies, top_n=10):
        # 알고리즘 객체의 predict() 메서드를 평점이 없는 영화에 반복 수행한 후 결과를 list 객체로 저장
        predictions = [algo.predict(str(userId), str(movieId)) for movieId in unseen_movies]
    
        # predictions list 객체는 surprise의 Predictions 객체를 원소로 가지고 있음.
        # [Prediction(uid='9', iid='1', est=3.69), Prediction(uid='9', iid='2', est=2.98),,,,]
        # 이를 est 값으로 정렬하기 위해서 아래의 sortkey_est 함수를 정의함.
        # sortkey_est 함수는 list 객체의 sort() 함수의 키 값으로 사용되어 정렬 수행.
        def sortkey_est(pred):
            return pred.est
    
        # sortkey_est( ) 반환값의 내림 차순으로 정렬 수행하고 top_n개의 최상위 값 추출.
        predictions.sort(key=sortkey_est, reverse=True)
        top_predictions= predictions[:top_n]
    
        # top_n으로 추출된 영화의 정보 추출. 영화 아이디, 추천 예상 평점, 제목 추출
        top_movie_ids = [ int(pred.iid) for pred in top_predictions]
        top_movie_rating = [ pred.est for pred in top_predictions]
        top_movie_titles = movies[movies.movieId.isin(top_movie_ids)]['title']
        top_movie_preds = [ (id, title, rating) for id, title, rating in zip(top_movie_ids, top_movie_titles, top_movie_rating)]
    
        return top_movie_preds
    
    unseen_movies = get_unseen_surprise(ratings, movies, 9)
    top_movie_preds = recomm_movie_by_surprise(algo, 9, unseen_movies, top_n=10)
    print('##### Top-10 추천 영화 리스트 #####')
    
    for top_movie in top_movie_preds:
        print(top_movie[1], ":", top_movie[2])
    평점 매긴 영화수: 46 추천대상 영화수: 9696 전체 영화수: 9742
    ##### Top-10 추천 영화 리스트 #####
    Usual Suspects, The (1995) : 4.306302135700814
    Star Wars: Episode IV - A New Hope (1977) : 4.281663842987387
    Pulp Fiction (1994) : 4.278152632122758
    Silence of the Lambs, The (1991) : 4.226073566460876
    Godfather, The (1972) : 4.1918097904381995
    Streetcar Named Desire, A (1951) : 4.154746591122658
    Star Wars: Episode V - The Empire Strikes Back (1980) : 4.122016128534504
    Star Wars: Episode VI - Return of the Jedi (1983) : 4.108009609093436
    Goodfellas (1990) : 4.083464936588478
    Glory (1989) : 4.07887165526957
    

    댓글