利用Surprise包進行電影推薦


Surprise(Simple Python Recommendation System Engine)是一款推薦系統庫,是scikit系列中的一個。簡單易用,同時支持多種推薦算法(基礎算法、協同過濾、矩陣分解等)。

設計surprise時考慮到以下目的:

  • 讓用戶完美控制他們的實驗。為此特別強調 文檔,試圖通過指出算法的每個細節盡可能清晰和准確。
  • 減輕數據集處理的痛苦。用戶可以使用內置數據集(Movielens, Jester)和他們自己的自定義 數據集。
  • 提供各種即用型預測算法, 例如基線算法, 鄰域方法,基於矩陣因子分解( SVD, PMF, SVD ++NMF等等此外, 內置了各種相似性度量(余弦,MSD,皮爾遜......)。
  • 可以輕松實現新的算法思路
  • 提供評估, 分析 和 比較 算法性能的工具。使用強大的CV迭代器(受scikit-learn優秀工具啟發)以及 對一組參數的詳盡搜索,可以非常輕松地運行交叉驗證程序 

1.Surprise安裝

pip install numpy
pip install scikit-surprise

在安裝之前首先確認安裝了numpy模塊。

2.基本算法

算法類名 說明
random_pred.NormalPredictor 根據訓練集的分布特征隨機給出一個預測值
baseline_only.BaselineOnly 給定用戶和Item,給出基於baseline的估計值
knns.KNNBasic 最基礎的協同過濾
knns.KNNWithMeans 將每個用戶評分的均值考慮在內的協同過濾實現
knns.KNNBaseline 考慮基線評級的協同過濾
matrix_factorization.SVD SVD實現
matrix_factorization.SVDpp SVD++,即LFM+SVD
matrix_factorization.NMF 基於矩陣分解的協同過濾
slope_one.SlopeOne 一個簡單但精確的協同過濾算法
co_clustering.CoClustering 基於協同聚類的協同過濾算法

其中基於近鄰的方法(協同過濾)可以設定不同的度量准則

相似度度量標准 度量標准說明
cosine 計算所有用戶(或物品)對之間的余弦相似度。
msd 計算所有用戶(或物品)對之間的均方差異相似度。
pearson 計算所有用戶(或物品)對之間的Pearson相關系數。
pearson_baseline 計算所有用戶(或物品)對之間的(縮小的)Pearson相關系數,使用基線進行居中而不是平均值。

支持不同的評估准則

評估准則 准則說明
rmse 計算RMSE(均方根誤差)。
mae 計算MAE(平均絕對誤差)。
fcp 計算FCP(協調對的分數)。

3.Surprise使用

(1)載入自帶的數據集

#-*- coding:utf-8 -*-
# 可以使用上面提到的各種推薦系統算法
from surprise import SVD
from surprise import Dataset, print_perf
from surprise.model_selection import cross_validate

# 默認載入movielens數據集
data = Dataset.load_builtin('ml-100k')
# k折交叉驗證(k=3),此方法現已棄用
# data.split(n_folds=3)
# 試一把SVD矩陣分解
algo = SVD()
# 在數據集上測試一下效果
perf = cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=3)
#輸出結果
print_perf(perf)

運行結果:

 

(2)載入自己的數據集

from surprise import SVD
from surprise import Dataset, print_perf, Reader
from surprise.model_selection import cross_validate
import os

# 指定文件所在路徑
file_path = os.path.expanduser('data.csv')
# 告訴文本閱讀器,文本的格式是怎么樣的
reader = Reader(line_format='user item rating', sep=',')
# 加載數據
data = Dataset.load_from_file(file_path, reader=reader)
algo = SVD()
# 在數據集上測試一下效果
perf = cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=3)
#輸出結果
print_perf(perf)

需要注意:

1.無法識別中文,如果有中文,需要將其轉換成ID號再進行操作(以下列出一種簡單的轉換方式)

2.不能有表頭,需要去掉表頭和元數據中有中文的列

3.需要修改Reader,line_format 就是數據的列,sep 是分隔方式(表格格式初始分割方式是‘,’)

一種簡單的數據轉換方式:

#-*- coding:utf-8 -*-
# 構建物品id

import pandas as pd

df = pd.read_csv('train_score.csv', encoding="gbk")
# 讀取第二列的數據
item_name = df.iloc[:, 1]
item = {}
item_id = []
num = 0
# 將每個不同的物品與id號進行關聯
for i in item_name:
    if i in item:
        item_id.append(item[i])
    else:
        item[i] = num
        item_id.append(num)
        num += 1
print item_id
df['itemId'] = item_id
df.to_csv("data.csv", encoding="gbk", index=False)

4.算法調參

這里實現的算法用到的算法無外乎也是SGD等,因此也有一些超參數會影響最后的結果,我們同樣可以用sklearn中常用到的網格搜索交叉驗證(GridSearchCV)來選擇最優的參數。簡單的例子如下所示:

# 定義好需要優選的參數網格
param_grid = {'n_epochs': [5, 10], 'lr_all': [0.002, 0.005],
              'reg_all': [0.4, 0.6]}
# 使用網格搜索交叉驗證
grid_search = GridSearch(SVD, param_grid, measures=['RMSE', 'FCP'])
# 在數據集上找到最好的參數
data = Dataset.load_builtin('ml-100k')
data.split(n_folds=3)
grid_search.evaluate(data)
# 輸出調優的參數組 
# 輸出最好的RMSE結果
print(grid_search.best_score['RMSE'])
# >>> 0.96117566386

# 輸出對應最好的RMSE結果的參數
print(grid_search.best_params['RMSE'])
# >>> {'reg_all': 0.4, 'lr_all': 0.005, 'n_epochs': 10}

# 最好的FCP得分
print(grid_search.best_score['FCP'])
# >>> 0.702279736531

# 對應最高FCP得分的參數
print(grid_search.best_params['FCP'])
# >>> {'reg_all': 0.6, 'lr_all': 0.005, 'n_epochs': 10}
GridSearchCV 方法:
# 定義好需要優選的參數網格
param_grid = {'n_epochs': [5, 10], 'lr_all': [0.002, 0.005],
              'reg_all': [0.4, 0.6]}
# 使用網格搜索交叉驗證
grid_search = GridSearchCV(SVD, param_grid, measures=['RMSE', 'FCP'], cv=3)
# 在數據集上找到最好的參數
data = Dataset.load_builtin('ml-100k')
# pref = cross_validate(grid_search, data, cv=3)
grid_search.fit(data)
# 輸出調優的參數組
# 輸出最好的RMSE結果
print(grid_search.best_score)
1.estimator
選擇使用的分類器,並且傳入除需要確定最佳的參數之外的其他參數。
每一個分類器都需要一個scoring參數,或者score方法:
如estimator=RandomForestClassifier(
	min_samples_split=100,
	min_samples_leaf=20,
	max_depth=8,
	max_features='sqrt',
	random_state=10),
 
2.param_grid
需要最優化的參數的取值,值為字典或者列表,例如:
	param_grid =param_test1,
	param_test1 = {'n_estimators':range(10,71,10)}。
 
3. scoring=None
模型評價標准,默認None,這時需要使用score函數;或者如scoring='roc_auc',
根據所選模型不同,評價准則不同。字符串(函數名),或是可調用對象,
需要其函數簽名形如:scorer(estimator, X, y);如果是None,則使用estimator的誤差估計函數。
 
4.n_jobs=1
n_jobs: 並行數,int:個數,-1:跟CPU核數一致, 1:默認值
 
5.cv=None
 
交叉驗證參數,默認None,使用三折交叉驗證。指定fold數量,默認為3,也可以是yield產生訓練/測試數據的生成器。
 
6.verbose=0, scoring=None
verbose:日志冗長度,int:冗長度,0:不輸出訓練過程,1:偶爾輸出,>1:對每個子模型都輸出。
 
7.pre_dispatch=‘2*n_jobs’
指定總共分發的並行任務數。當n_jobs大於1時,數據將在每個運行點進行復制,這可能導致OOM,
而設置pre_dispatch參數,則可以預先划分總共的job數量,使數據最多被復制pre_dispatch次
 
8.return_train_score=’warn’
如果“False”,cv_results_屬性將不包括訓練分數。
 
9.refit :默認為True,程序將會以交叉驗證訓練集得到的最佳參數,重新對所有可用的訓練集與開發集進行,
作為最終用於性能評估的最佳模型參數。即在搜索參數結束后,用最佳參數結果再次fit一遍全部數據集。
 
10.iid:默認True,為True時,默認為各個樣本fold概率分布一致,誤差估計為所有樣本之和,而非各個fold的平均。
 
進行預測的常用方法和屬性
grid.fit():運行網格搜索
grid_scores_:給出不同參數情況下的評價結果
best_params_:描述了已取得最佳結果的參數的組合
best_score_:成員提供優化過程期間觀察到的最好的評

5.使用不同的推薦系統算法進行建模比較

from surprise import Dataset, print_perf
from surprise.model_selection import cross_validate
data = Dataset.load_builtin('ml-100k')
### 使用NormalPredictor
from surprise import NormalPredictor
algo = NormalPredictor()
perf = cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=3)
print_perf(perf)

### 使用BaselineOnly
from surprise import BaselineOnly
algo = BaselineOnly()
perf = cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=3)
print_perf(perf)

### 使用基礎版協同過濾
from surprise import KNNBasic, evaluate
algo = KNNBasic()
perf = cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=3)
print_perf(perf)

### 使用均值協同過濾
from surprise import KNNWithMeans, evaluate
algo = KNNWithMeans()
perf = cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=3)
print_perf(perf)

### 使用協同過濾baseline
from surprise import KNNBaseline, evaluate
algo = KNNBaseline()
perf = cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=3)
print_perf(perf)

### 使用SVD
from surprise import SVD, evaluate
algo = SVD()
perf = cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=3)
print_perf(perf)

### 使用SVD++
from surprise import SVDpp, evaluate
algo = SVDpp()
perf = cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=3)
print_perf(perf)

### 使用NMF
from surprise import NMF
algo = NMF()
perf = cross_validate(algo, data, measures=['RMSE', 'MAE'], cv=3)
print_perf(perf)

6.movielens推薦實例

#-*- coding:utf-8 -*-
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import os
import io
from surprise import KNNBaseline
from surprise import Dataset

import logging

logging.basicConfig(level=logging.INFO,
                    format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s',
                    datefmt='%a, %d %b %Y %H:%M:%S')


# 訓練推薦模型 步驟:1
def getSimModle():
    # 默認載入movielens數據集
    data = Dataset.load_builtin('ml-100k')
    trainset = data.build_full_trainset()
    #使用pearson_baseline方式計算相似度  False以item為基准計算相似度 本例為電影之間的相似度
    sim_options = {'name': 'pearson_baseline', 'user_based': False}
    ##使用KNNBaseline算法
    algo = KNNBaseline(sim_options=sim_options)
    #訓練模型
    algo.fit(trainset)
    return algo


# 獲取id到name的互相映射  步驟:2
def read_item_names():
    """
    獲取電影名到電影id 和 電影id到電影名的映射
    """
    file_name = (os.path.expanduser('~') +
                 '/.surprise_data/ml-100k/ml-100k/u.item')
    rid_to_name = {}
    name_to_rid = {}
    with io.open(file_name, 'r', encoding='ISO-8859-1') as f:
        for line in f:
            line = line.split('|')
            rid_to_name[line[0]] = line[1]
            name_to_rid[line[1]] = line[0]
    return rid_to_name, name_to_rid


# 基於之前訓練的模型 進行相關電影的推薦  步驟:3
def showSimilarMovies(algo, rid_to_name, name_to_rid):
    # 獲得電影Toy Story (1995)的raw_id
    toy_story_raw_id = name_to_rid['Toy Story (1995)']
    logging.debug('raw_id=' + toy_story_raw_id)
    #把電影的raw_id轉換為模型的內部id
    toy_story_inner_id = algo.trainset.to_inner_iid(toy_story_raw_id)
    logging.debug('inner_id=' + str(toy_story_inner_id))
    #通過模型獲取推薦電影 這里設置的是10部
    toy_story_neighbors = algo.get_neighbors(toy_story_inner_id, 10)
    logging.debug('neighbors_ids=' + str(toy_story_neighbors))
    #模型內部id轉換為實際電影id
    neighbors_raw_ids = [algo.trainset.to_raw_iid(inner_id) for inner_id in toy_story_neighbors]
    #通過電影id列表 或得電影推薦列表
    neighbors_movies = [rid_to_name[raw_id] for raw_id in neighbors_raw_ids]
    print('The 10 nearest neighbors of Toy Story are:')
    for movie in neighbors_movies:
        print(movie)


if __name__ == '__main__':
    # 獲取id到name的互相映射
    rid_to_name, name_to_rid = read_item_names()

    # 訓練推薦模型
    algo = getSimModle()

    ##顯示相關電影
    showSimilarMovies(algo, rid_to_name, name_to_rid)

運行結果:

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM