美文网首页大数据
推荐系统简介--协同过滤

推荐系统简介--协同过滤

作者: 爱摄影Sure | 来源:发表于2019-11-01 14:45 被阅读0次

    推荐系统简介:协同过滤

    ​ 我之前说过我有多喜欢吴恩达的关于机器学习的课程。然而,我也意识到他的课程在推荐系统方面的介绍有一些欠缺。在学习了分类和回归的基本模型之后,我认为推荐系统是机器学习三大基本模型的支柱。

    ​ 在ecommmerce公司工作时,我思考了很多关于推荐系统的事情,并希望对推荐系统做一个介绍。推荐模型的目的是给定输入对象而呈现出对象的排名列表。通常情况下,此排名是基于输入对象和列出的对象之间的相似性。为了减少推荐的不确定性,人们通常想要既展示出与给定产品类似的产品,也要展示出基于给定用户推荐的产品(基于物品的协同过滤和基于用户的协同过滤)

    ​ 令人惊讶的是,如果一个人拥有足够多的用户-产品数据(评分,购买等),那么就不需要其他维度的信息就能实现不错的推荐。

    ​ 在本文中,我将使用MovieLens数据集-用于训练推荐模型的经典数据集。数据集可以从GroupLens网站上获得。这上面有各种数据集,但是我将在下面使用的一个数据集是包含100,000条用户对电影评分(1-5分)。主数据文件由一个制表符分隔的列表组成,其中用户ID(从1开始),电影ID(从1开始),评分和时间戳记是四个字段。我们可以在Jupyter note中使用bash命令来下载文件,然后通过pandas读取它。

    import numpy as np
    import pandas as pd
    
    cd ml-100k
    
    C:\Users\wums\Documents\jupytefLab\ml-100k
    
    ls
    
     驱动器 C 中的卷没有标签。
     卷的序列号是 00EC-08CC
    
     C:\Users\wums\Documents\jupytefLab\ml-100k 的目录
    
    2019/10/31  13:58    <DIR>          .
    2019/10/31  13:58    <DIR>          ..
    2019/10/31  11:27    <DIR>          .ipynb_checkpoints
    2000/07/19  16:09               716 allbut.pl
    2019/10/31  13:57           162,329 item.csv
    2000/07/19  16:09               643 mku.sh
    2016/01/29  14:26             6,750 README
    2000/07/19  16:09         1,979,173 u.data
    2000/07/19  16:09               202 u.genre
    2000/07/19  16:09                36 u.info
    2000/07/19  16:09           236,344 u.item
    2000/07/19  16:09               193 u.occupation
    2000/07/19  16:09            22,628 u.user
    2001/03/08  12:33         1,586,544 u1.base
    2001/03/08  12:32           392,629 u1.test
    2001/03/08  12:33         1,583,948 u2.base
    2001/03/08  12:33           395,225 u2.test
    2001/03/08  12:33         1,582,546 u3.base
    2001/03/08  12:33           396,627 u3.test
    2001/03/08  12:33         1,581,878 u4.base
    2001/03/08  12:33           397,295 u4.test
    2001/03/08  12:34         1,581,776 u5.base
    2001/03/08  12:33           397,397 u5.test
    2001/03/08  12:34         1,792,501 ua.base
    2001/03/08  12:34           186,672 ua.test
    2001/03/08  12:34         1,792,476 ub.base
    2001/03/08  12:34           186,697 ub.test
                  24 个文件     16,263,225 字节
                   3 个目录 11,675,602,944 可用字节
    
    names = ['user_id', 'item_id', 'rating', 'timestamp']
    df = pd.read_csv('u.data', sep='\t', names=names)
    df.head()
    
    user_id item_id rating timestamp
    0 196 242 3 881250949
    1 186 302 3 891717742
    2 22 377 1 878887116
    3 244 51 2 880606923
    4 166 346 1 886397596
    n_users = df.user_id.unique().shape[0]
    n_items = df.item_id.unique().shape[0]
    print str(n_users) + ' users'
    print str(n_items) + ' items'
    
    943 users
    1682 items
    

    ​ 大多数推荐模型包括建立一个基于某种“交互”数值的用户物品矩阵。如果其中包含用户给出的对于物品的评分,则称为显式反馈模型。要不就是隐式反馈,隐式反馈是基于用户的行为来判定用户的喜恶(例如,是否在线查看过该物品)。

    ​ 对于MovieLens数据集,我们具有评分,因此我们将重点关注显式反馈模型。首先,我们必须构造用户物品矩阵。通过删除偏移量为0的一行(因为id是从1开始,索引是从0开始),我们可以轻松地将用户/商品ID映射到用户/商品索引。

    ratings = np.zeros((n_users, n_items))
    for row in df.itertuples():
        ratings[row[1]-1, row[2]-1] = row[3]
    ratings
    
    array([[5., 3., 4., ..., 0., 0., 0.],
           [4., 0., 0., ..., 0., 0., 0.],
           [0., 0., 0., ..., 0., 0., 0.],
           ...,
           [5., 0., 0., ..., 0., 0., 0.],
           [0., 0., 0., ..., 0., 0., 0.],
           [0., 5., 0., ..., 0., 0., 0.]])
    
    sparsity = float(len(ratings.nonzero()[0]))
    sparsity /= (ratings.shape[0] * ratings.shape[1])
    sparsity *= 100
    print 'Sparsity: {:4.2f}%'.format(sparsity)
    
    Sparsity: 6.30%
    

    ​ 在此数据集中,每个用户至少评分了20部电影,其拥有一个合理的稀疏度为6.3%。这就意味着6.3%的用户-物品的评分具有值。请注意,尽管我们将缺失的评分填写为0,但我们不应假定这些值就是0。更恰当地说,它们只是空项。我们会通过将每个用户的10个评分从数据集中剔除放入测试集这种方式把数据集分为训练集和测试集。

    def train_test_split(ratings):
        test = np.zeros(ratings.shape)
        train = ratings.copy()
        for user in xrange(ratings.shape[0]):
            test_ratings = np.random.choice(ratings[user, :].nonzero()[0], 
                                            size=10, 
                                            replace=False)
            train[user, test_ratings] = 0.
            test[user, test_ratings] = ratings[user, test_ratings]
            
        # Test and training are truly disjoint
        assert(np.all((train * test) == 0)) 
        return train, test
    
    train, test = train_test_split(ratings)
    

    协同过滤

    ​ 今天,我们将重点关注协作过滤模型,该模型通常可以分为两类:基于用户和基于物品的协同过滤。在这两种情况下,都会建立一个相似度矩阵。对于基于用户的协作过滤,用户相似度矩阵将由一些测定任意两对用户之间的相似性的距离度量组成。同样,物品相似度矩阵将测量任意两对物品之间的相似度。

    ​ 常见的距离度量是余弦相似度。如果人们将评分矩阵的给定用户(物品)行(列)视为向量,则可以从几何角度考虑该指标。对于基于用户的协作过滤,将两个用户的相似度作为两个用户向量之间的角度的余弦值来衡量。对于用户u {u} u和u'{u ^ {\ prime}} u',余弦相似度为:
    sim(u, u') = cos(\theta{}) = \frac{\textbf{r}_{u} \dot{} \textbf{r}_{u'}}{\| \textbf{r}_{u} \| \| \textbf{r}_{u'} \|} = \sum_{i} \frac{r_{ui}r_{u'i}}{\sqrt{\sum\limits_{i} r_{ui}^2} \sqrt{\sum\limits_{i} r_{u'i}^2} }

    ​ 可以使用代码将其编写为for循环,但是Python代码运行起来会很慢。相反,你应该尝试用NumPy函数表达任何方程式。慢速功能花了很长时间,最终我因为厌倦了等待而取消了它。 另一方面,快速功能大约需要200毫秒。
    在我们的情况下,余弦相似度的范围是0到1(因为没有负额定值)。 请注意,它是对称的,并且沿对角线有一个。

    def slow_similarity(ratings, kind='user'):
        if kind == 'user':
            axmax = 0
            axmin = 1
        elif kind == 'item':
            axmax = 1
            axmin = 0
        sim = np.zeros((ratings.shape[axmax], ratings.shape[axmax]))
        for u in xrange(ratings.shape[axmax]):
            for uprime in xrange(ratings.shape[axmax]):
                rui_sqrd = 0.
                ruprimei_sqrd = 0.
                for i in xrange(ratings.shape[axmin]):
                    sim[u, uprime] = ratings[u, i] * ratings[uprime, i]
                    rui_sqrd += ratings[u, i] ** 2
                    ruprimei_sqrd += ratings[uprime, i] ** 2
                sim[u, uprime] /= rui_sqrd * ruprimei_sqrd
        return sim
    
    def fast_similarity(ratings, kind='user', epsilon=1e-9):
        # epsilon -> small number for handling dived-by-zero errors
        if kind == 'user':
            sim = ratings.dot(ratings.T) + epsilon
        elif kind == 'item':
            sim = ratings.T.dot(ratings) + epsilon
        norms = np.array([np.sqrt(np.diagonal(sim))])
        return (sim / norms / norms.T)
    
    #%timeit slow_similarity(train)
    
    %timeit fast_similarity(train, kind='user')
    
    The slowest run took 4.46 times longer than the fastest. This could mean that an intermediate result is being cached.
    10 loops, best of 3: 51.1 ms per loop
    
    user_similarity = fast_similarity(train, kind='user')
    item_similarity = fast_similarity(train, kind='item')
    print item_similarity[:4, :4]
    
    [[1.         0.37818115 0.31374392 0.46337508]
     [0.37818115 1.         0.26787593 0.51811819]
     [0.31374392 0.26787593 1.         0.33069551]
     [0.46337508 0.51811819 0.33069551 1.        ]]
    

    ​ 对于基于用户的协同过滤,我们预测用户对物品i的u’的评分由物品i的所有其他用户评分的加权总和得出,其中加权值是每个用户与输入用户u之间的余弦相似度。

    \hat{r}_{ui} = \frac{\sum\limits_{u'} sim(u, u') r_{u'i}}{\sum\limits_{u'}|sim(u, u')|}

    我们还必须通过r_{u′i}评分的数量进行归一化:

    \hat{r}_{ui} = \frac{\sum\limits_{u'} sim(u, u') r_{u'i}}{\sum\limits_{u'}|sim(u, u')|}

    ​ 和以前一样,NumPy函数相较于for循环,将大大提高我们的计算速度。 使用下面的慢速函数,即使我使用NumPy方法,for循环的存在仍然会减慢算法的速度

    def predict_slow_simple(ratings, similarity, kind='user'):
        pred = np.zeros(ratings.shape)
        if kind == 'user':
            for i in xrange(ratings.shape[0]):
                for j in xrange(ratings.shape[1]):
                    pred[i, j] = similarity[i, :].dot(ratings[:, j])\
                                 /np.sum(np.abs(similarity[i, :]))
            return pred
        elif kind == 'item':
            for i in xrange(ratings.shape[0]):
                for j in xrange(ratings.shape[1]):
                    pred[i, j] = similarity[j, :].dot(ratings[i, :].T)\
                                 /np.sum(np.abs(similarity[j, :]))
    
            return pred
    
    def predict_fast_simple(ratings, similarity, kind='user'):
        if kind == 'user':
            return similarity.dot(ratings) / np.array([np.abs(similarity).sum(axis=1)]).T
        elif kind == 'item':
            return ratings.dot(similarity) / np.array([np.abs(similarity).sum(axis=1)])
    
    #%timeit predict_slow_simple(train, user_similarity, kind='user')
    
    %timeit predict_fast_simple(train, user_similarity, kind='user')
    
    10 loops, best of 3: 127 ms per loop
    

    ​ 我们将使用scikit-learn的均方误差函数作为验证指标。 比较基于用户和物品的协同过滤,似乎基于用户的协同过滤可以为我们提供更好的结果。

    from sklearn.metrics import mean_squared_error
    
    def get_mse(pred, actual):
        # Ignore nonzero terms.
        pred = pred[actual.nonzero()].flatten()
        actual = actual[actual.nonzero()].flatten()
        return mean_squared_error(pred, actual)
    item_prediction = predict_fast_simple(train, item_similarity, kind='item')
    user_prediction = predict_fast_simple(train, user_similarity, kind='user')
    
    print 'User-based CF MSE: ' + str(get_mse(user_prediction, test))
    print 'Item-based CF MSE: ' + str(get_mse(item_prediction, test))
    
    User-based CF MSE: 8.484219927982092
    Item-based CF MSE: 11.577294640714918
    

    Top-k 协同过滤

    ​ 我们可以通过仅考虑与输入用户最相似的top-k用户(或类似地,top-k物品)来尝试改善我们的预测MSE。 也就是说,当我们计算u′的总和时:

    \hat{r}_{ui} = \frac{\sum\limits_{u'} sim(u, u') r_{u'i}}{\sum\limits_{u'}|sim(u, u')|}

    ​ 我们只会汇总前k个最相似的用户。 下面显示了该算法的慢速执行。 尽管我确定有一种方法可以使用numpy排序来避免嵌套for循环,但对2D argsort输出结果的解析却让我感到很蛋疼,并且这样做只是为了加快速度,不值得。

    ​ 如下所示,使用此方法实际上将我们的错误减半!

    def predict_topk(ratings, similarity, kind='user', k=40):
        pred = np.zeros(ratings.shape)
        if kind == 'user':
            for i in xrange(ratings.shape[0]):
                top_k_users = [np.argsort(similarity[:,i])[:-k-1:-1]]
                for j in xrange(ratings.shape[1]):
                    pred[i, j] = similarity[i, :][top_k_users].dot(ratings[:, j][top_k_users]) 
                    pred[i, j] /= np.sum(np.abs(similarity[i, :][top_k_users]))
        if kind == 'item':
            for j in xrange(ratings.shape[1]):
                top_k_items = [np.argsort(similarity[:,j])[:-k-1:-1]]
                for i in xrange(ratings.shape[0]):
                    pred[i, j] = similarity[j, :][top_k_items].dot(ratings[i, :][top_k_items].T) 
                    pred[i, j] /= np.sum(np.abs(similarity[j, :][top_k_items]))        
        
        return pred
    
    from warnings import simplefilter
    simplefilter(action='ignore', category=FutureWarning) #关闭FutureWarning
    pred = predict_topk(train, user_similarity, kind='user', k=40)
    print 'Top-k User-based CF MSE: ' + str(get_mse(pred, test))
    
    pred = predict_topk(train, item_similarity, kind='item', k=40)
    print 'Top-k Item-based CF MSE: ' + str(get_mse(pred, test))
    
    Top-k User-based CF MSE: 6.509719476786268
    Top-k Item-based CF MSE: 7.736969949737556
    

    ​ 我们可以尝试调整k的参数以找到最佳值,以最小化我们的测试MSE。 在这里,通常可视化结果有助于我们了解发生的情况。

    k_array = [5, 15, 30, 50, 100, 200]
    user_train_mse = []
    user_test_mse = []
    item_test_mse = []
    item_train_mse = []
    
    def get_mse(pred, actual):
        pred = pred[actual.nonzero()].flatten()
        actual = actual[actual.nonzero()].flatten()
        return mean_squared_error(pred, actual)
    
    for k in k_array:
        user_pred = predict_topk(train, user_similarity, kind='user', k=k)
        item_pred = predict_topk(train, item_similarity, kind='item', k=k)
        
        user_train_mse += [get_mse(user_pred, train)]
        user_test_mse += [get_mse(user_pred, test)]
        
        item_train_mse += [get_mse(item_pred, train)]
        item_test_mse += [get_mse(item_pred, test)] 
    
    %matplotlib inline
    import matplotlib.pyplot as plt
    import seaborn as sns
    sns.set()
    
    pal = sns.color_palette("Set2", 2)
    
    plt.figure(figsize=(8, 8))
    plt.plot(k_array, user_train_mse, c=pal[0], label='User-based train', alpha=0.5, linewidth=5)
    plt.plot(k_array, user_test_mse, c=pal[0], label='User-based test', linewidth=5)
    plt.plot(k_array, item_train_mse, c=pal[1], label='Item-based train', alpha=0.5, linewidth=5)
    plt.plot(k_array, item_test_mse, c=pal[1], label='Item-based test', linewidth=5)
    plt.legend(loc='best', fontsize=20)
    plt.xticks(fontsize=16);
    plt.yticks(fontsize=16);
    plt.xlabel('k', fontsize=30);
    plt.ylabel('MSE', fontsize=30);
    
    output_28_0.png

    看起来k分别为50和15时,分别为基于用户和基于物品的协同过滤的最小误差。

    Bias-subtracted 协同过滤

    ​ 改进建议的最后一种方法,我们将尝试消除物品和用户相关的偏见。 因为某些用户可能倾向于总是给所有电影高或低评级。 可以想象,这些用户给出的评级差比绝对评级值更为重要。

    ​ 我们尝试在相加相似用户的评分时减去每个用户的平均评分,然后在最后再加回该平均值。 在数学上,这看起来像

    \hat{r}_{ui} = \bar{r_{u}} + \frac{\sum\limits_{u'} sim(u, u') (r_{u'i} - \bar{r_{u'}})}{\sum\limits_{u'}|sim(u, u')|}

    这里的\tilde r_{u}就是u′s的平均评分

    def predict_nobias(ratings, similarity, kind='user'):
        if kind == 'user':
            user_bias = ratings.mean(axis=1)
            ratings = (ratings - user_bias[:, np.newaxis]).copy()
            pred = similarity.dot(ratings) / np.array([np.abs(similarity).sum(axis=1)]).T
            pred += user_bias[:, np.newaxis]
        elif kind == 'item':
            item_bias = ratings.mean(axis=0)
            ratings = (ratings - item_bias[np.newaxis, :]).copy()
            pred = ratings.dot(similarity) / np.array([np.abs(similarity).sum(axis=1)])
            pred += item_bias[np.newaxis, :]
            
        return pred
    
    user_pred = predict_nobias(train, user_similarity, kind='user')
    print 'Bias-subtracted User-based CF MSE: ' + str(get_mse(user_pred, test))
    
    item_pred = predict_nobias(train, item_similarity, kind='item')
    print 'Bias-subtracted Item-based CF MSE: ' + str(get_mse(item_pred, test))
    
    Bias-subtracted User-based CF MSE: 8.755875634173117
    Bias-subtracted Item-based CF MSE: 9.771864597489678
    

    就这些了

    ​ 最后,我们可以尝试结合使用Top-k算法和Bias-subtracted算法。 奇怪的是,这实际上比原始的Top-k算法要差。用数据说话。

    def predict_topk_nobias(ratings, similarity, kind='user', k=40):
        pred = np.zeros(ratings.shape)
        if kind == 'user':
            user_bias = ratings.mean(axis=1)
            ratings = (ratings - user_bias[:, np.newaxis]).copy()
            for i in xrange(ratings.shape[0]):
                top_k_users = [np.argsort(similarity[:,i])[:-k-1:-1]]
                for j in xrange(ratings.shape[1]):
                    pred[i, j] = similarity[i, :][top_k_users].dot(ratings[:, j][top_k_users]) 
                    pred[i, j] /= np.sum(np.abs(similarity[i, :][top_k_users]))
            pred += user_bias[:, np.newaxis]
        if kind == 'item':
            item_bias = ratings.mean(axis=0)
            ratings = (ratings - item_bias[np.newaxis, :]).copy()
            for j in xrange(ratings.shape[1]):
                top_k_items = [np.argsort(similarity[:,j])[:-k-1:-1]]
                for i in xrange(ratings.shape[0]):
                    pred[i, j] = similarity[j, :][top_k_items].dot(ratings[i, :][top_k_items].T) 
                    pred[i, j] /= np.sum(np.abs(similarity[j, :][top_k_items])) 
            pred += item_bias[np.newaxis, :]
            
        return pred
    
    k_array = [5, 15, 30, 50, 100, 200]
    user_train_mse = []
    user_test_mse = []
    item_test_mse = []
    item_train_mse = []
    
    for k in k_array:
        user_pred = predict_topk_nobias(train, user_similarity, kind='user', k=k)
        item_pred = predict_topk_nobias(train, item_similarity, kind='item', k=k)
        
        user_train_mse += [get_mse(user_pred, train)]
        user_test_mse += [get_mse(user_pred, test)]
        
        item_train_mse += [get_mse(item_pred, train)]
        item_test_mse += [get_mse(item_pred, test)]  
    
    pal = sns.color_palette("Set2", 2)
    
    plt.figure(figsize=(8, 8))
    plt.plot(k_array, user_train_mse, c=pal[0], label='User-based train', alpha=0.5, linewidth=5)
    plt.plot(k_array, user_test_mse, c=pal[0], label='User-based test', linewidth=5)
    plt.plot(k_array, item_train_mse, c=pal[1], label='Item-based train', alpha=0.5, linewidth=5)
    plt.plot(k_array, item_test_mse, c=pal[1], label='Item-based test', linewidth=5)
    plt.legend(loc='best', fontsize=20)
    plt.xticks(fontsize=16);
    plt.yticks(fontsize=16);
    plt.xlabel('k', fontsize=30);
    plt.ylabel('MSE', fontsize=30);
    
    output_36_0.png

    验证

    ​ 在扩展了基本协作过滤算法之后,我展示了如何通过增加模型复杂度来减小均方误差。 但是,我们如何真正知道这些改进是否有意义? 我忽略的一件事,就是我们对相似性指标的选择。 我们怎么知道余弦相似度是一个很好的度量标准? 由于我们处理的是一个我们很多人都有直觉(电影)的领域,所以我们可以查看物品(电影)相似度矩阵,看看相似物品是否“有意义”。

    ​ 单纯为了有趣,让我们看一下项目文件。 MovieLens数据集包含一个文件,其中包含有关每个电影的信息。 事实证明,有一个名为themoviedb.org的网站,该网站具有免费的API。 如果我们有电影的IMDB“电影ID”,则可以使用此API返回电影的海报。 查看下面的电影数据文件,看来我们至少每个电影都有IMDB网址。

    ​ 如果您遵循此数据集中的链接之一,则您的网址将被访问。 生成的URL包含IMDB电影ID作为URL中以“ tt”开头的最后信息。 例如,“玩具总动员”将访问URL为http://www.imdb.com/title/tt0114709/,IMDB电影ID为tt0114709。

    ​ 使用Python http请求库,我们可以自动提取此电影ID。 玩具总动员示例如下所示。

    import requests
    import json
    #由于年代久远,已经无法通过url获取imdbId
    response = requests.get('http://us.imdb.com/M/title-exact?Toy%20Story%20(1995)')
    print response.status_code
    print response.url.split('/')[-2]
    
    404
    M
    
    # Get base url filepath structure. w185 corresponds to size of movie poster.
    #接口多次调用存在卡住的清况
    headers = {'Accept': 'application/json'}
    payload = {'api_key': 'a'pi'key'} 
    response = requests.get("http://api.themoviedb.org/3/configuration", params=payload, headers=headers)
    response = json.loads(response.text)
    base_url = response['images']['base_url'] + 'w185'
    
    def get_poster(imdb_url, base_url):
        # Get IMDB movie ID
        response = requests.get(imdb_url)
        movie_id = response.url.split('/')[-2]
        
        # Query themoviedb.org API for movie poster path.
        movie_url = 'http://api.themoviedb.org/3/movie/{:}/images'.format(movie_id)
        headers = {'Accept': 'application/json'}
        payload = {'api_key': 'apikey'} 
        response = requests.get(movie_url, params=payload, headers=headers)
        try:
            file_path = json.loads(response.text)['posters'][0]['file_path']
        except:
            # IMDB movie ID is sometimes no good. Need to get correct one.
            movie_title = imdb_url.split('?')[-1].split('(')[0]
            payload['query'] = movie_title
            response = requests.get('http://api.themoviedb.org/3/search/movie', params=payload, headers=headers)
            movie_id = json.loads(response.text)['results'][0]['id']
            payload.pop('query', None)
            movie_url = 'http://api.themoviedb.org/3/movie/{:}/images'.format(movie_id)
            response = requests.get(movie_url, params=payload, headers=headers)
            file_path = json.loads(response.text)['posters'][0]['file_path']
            
        return base_url + file_path
    
    from IPython.display import Image
    from IPython.display import display
    
    toy_story = 'http://us.imdb.com/tt0114709/title-exact?Toy%20Story%20(1995)'
    Image(url=get_poster(toy_story, base_url))
    
    Toy Story

    ​ 现在,我们有了一个流程,可以直接从数据文件中的IMDB URL转到显示电影海报。 有了这种机制,我们研究电影相似度矩阵就更直观了。

    ​ 我们可以建立字典来将电影索引从相似矩阵映射到电影的网址。 我们还将创建一个辅助函数,以根据输入的电影返回前k个最相似的电影。 使用此功能,返回的第一部电影将是输入电影(因为它与自身最相似)。

    通过尝试使用原作者这种酷酷的验证方式,发现数据和接口调用都存在问题。所以这里用直接显示电影名称的方式来验证结果

    # Load in movie data
    idx_to_movie = {}
    with open('u.item', 'r') as f:
        for line in f.readlines():
            info = line.split('|')
            idx_to_movie[int(info[0])-1] = info[1]
            
    def top_k_movies(similarity, mapper, movie_idx, k=6):
        return [mapper[x] for x in np.argsort(similarity[movie_idx,:])[:-k-1:-1]]
    
    
    idx = 0 # Toy Story
    movies = top_k_movies(item_similarity, idx_to_movie, idx)
    for movie in movies:
        print movie
    # posters = tuple(Image(url=get_poster(movie, base_url)) for movie in movies)
    
    Toy Story (1995)
    Star Wars (1977)
    Return of the Jedi (1983)
    Independence Day (ID4) (1996)
    Rock, The (1996)
    Star Trek: First Contact (1996)
    

    ​ 嗯,这些推荐似乎不太好! 让我们再来看几个。

    idx = 1 # GoldenEye
    movies = top_k_movies(item_similarity, idx_to_movie, idx)
    for movie in movies:
        print movie
    # posters = tuple(Image(url=get_poster(movie, base_url)) for movie in movies)
    # display(*posters)
    
    GoldenEye (1995)
    Under Siege (1992)
    Top Gun (1986)
    Batman (1989)
    Stargate (1994)
    Cliffhanger (1993)
    
    idx = 20 # Muppet Treasure Island
    movies = top_k_movies(item_similarity, idx_to_movie, idx)
    for movie in movies:
        print movie
    # posters = tuple(Image(url=get_poster(movie, base_url)) for movie in movies)
    # display(*posters)
    
    Muppet Treasure Island (1996)
    Naked Gun 33 1/3: The Final Insult (1994)
    Independence Day (ID4) (1996)
    Hot Shots! Part Deux (1993)
    Aladdin (1992)
    Sgt. Bilko (1996)
    
    idx = 40 # Billy Madison
    movies = top_k_movies(item_similarity, idx_to_movie, idx)
    for movie in movies:
        print movie
    # posters = tuple(Image(url=get_poster(movie, base_url)) for movie in movies)
    # display(*posters)
    
    Billy Madison (1995)
    Dumb & Dumber (1994)
    Ace Ventura: Pet Detective (1994)
    Hot Shots! Part Deux (1993)
    Young Guns II (1990)
    Tommy Boy (1995)
    

    ​ 瞧吧,也许我们并没有使用好这个相似度矩阵。 其中一些推荐非常糟糕-《星球大战》是与《玩具总动员》最相似的电影? 在前5名与GoldenEye最相似的电影中,没有其他詹姆斯·邦德电影吗?

    ​ 问题可能是像《星球大战》这样的非常受欢迎的电影受到青睐。 我们可以通过考虑不同的相似性指标-皮尔逊相关性来消除这种偏见。 我只是获取内置的scikit-learn函数来进行计算。

    from sklearn.metrics import pairwise_distances
    # Convert from distance to similarity
    item_correlation = 1 - pairwise_distances(train.T, metric='correlation')
    item_correlation[np.isnan(item_correlation)] = 0.
    

    ​ 让我们再看看这些推荐

    idx = 1 # GoldenEye
    movies = top_k_movies(item_correlation, idx_to_movie, idx)
    for movie in movies:
        print movie
    # posters = tuple(Image(url=get_poster(movie, base_url)) for movie in movies)
    # display(*posters)
    
    GoldenEye (1995)
    Under Siege (1992)
    Cliffhanger (1993)
    Top Gun (1986)
    Stargate (1994)
    Batman (1989)
    
    idx = 20 # Muppet Treasure Island
    movies = top_k_movies(item_correlation, idx_to_movie, idx)
    for movie in movies:
        print movie
    # posters = tuple(Image(url=get_poster(movie, base_url)) for movie in movies)
    # display(*posters)
    
    Muppet Treasure Island (1996)
    Naked Gun 33 1/3: The Final Insult (1994)
    Jingle All the Way (1996)
    Hot Shots! Part Deux (1993)
    Air Up There, The (1994)
    Sgt. Bilko (1996)
    
    idx = 40 # Billy Madison
    movies = top_k_movies(item_correlation, idx_to_movie, idx)
    for movie in movies:
        print movie
    # posters = tuple(Image(url=get_poster(movie, base_url)) for movie in movies)
    # display(*posters)
    
    Billy Madison (1995)
    Dumb & Dumber (1994)
    Ace Ventura: Pet Detective (1994)
    Hot Shots! Part Deux (1993)
    Young Guns II (1990)
    Tommy Boy (1995)
    

    ​ 尽管顺序有所变化,但我们在很大程度上去除了相同的电影-现在您可以看到推荐系统为何如此艰难! 之后,我们将探索更高级的模型,并分析它们是如何影响推荐结果的。

    本文翻译自https://www.ethanrosenthal.com/2015/11/02/intro-to-collaborative-filtering/ 有翻译不准确的地方,欢迎指正。

    相关文章

      网友评论

        本文标题:推荐系统简介--协同过滤

        本文链接:https://www.haomeiwen.com/subject/vqbfbctx.html