目录html
更新、更全的《机器学习》的更新网站,更有python、go、数据结构与算法、爬虫、人工智能教学等着你:http://www.javashuo.com/article/p-vozphyqp-cm.htmlpython
本推荐系统采用中等大小的MovieLens数据集,该数据集包含6000多用户对4000多部电影的100万条评分。该数据集是一个评分数据集,用户能够给电影评5个不一样等级的分数(1~5分)。本篇文章主要研究隐反馈数据集中的TopN推荐问题,所以将会忽略数据集中的评分记录。也就是说,TopN推荐的任务是预测用户会不会对某部电影评分,而不是预测用户在准备对某部电影评分的前提下对电影评多少分。算法
from numpy import random def split_data(data, M, k, seed): """ 切割训练集,防止过拟合 data: 训练集 M: 切割训练集的份数 k: 测试集的索引 seed: 随机数种子 """ test = [] train = [] random.seed(seed) for user, item in data: if random.randint(0, M) == k: test.append([user, item]) else: train.append([user, item]) return train, test
对用户\(u\)推荐\(N\)个物品,记做\(R(u)\),用户\(u\)在测试集上喜欢的物品集合为\(T(u)\),可使用准确率/召回率评测推荐算法的精度。数据结构
准确率公式为:
\[ \text{Precision}=\frac{\sum_{u\in{U}}|R(u)\bigcap{T(u)}|}{\sum_{u\in{U}}|R(u)|} \]
其中\(R(u)\)是用户在训练集上的行为给用户做出的推荐列表。app
召回率公式为:
\[ \text{Recall}=\frac{\sum_{u\in{U}}|R(u)\bigcap{T(u)}|}{\sum_{u\in{U}}|T(u)|} \]
其中\(T(u)\)是用户在测试集上的行为给用户做出的推荐列表。dom
准确率描述最终的推荐列表中有多少比例是发生过的用户-物品评分记录;召回率描述有多少比例的用户-物品评分记录包含在最终的推荐列表中。机器学习
def recall_(train, test, N): """计算召回率""" hit = 0 all_ = 0 for user in train.keys(): tu = test[user] rank = get_recommendation(user, N) for item, pui in rank: if item in tu: hit += 1 all_ += len(tu) return hit/(all_*1.) def precision(train, test, N): """计算准确率""" hit = 0 all_ = 0 for user in train.keys(): tu = test[user] rank = get_recommendation(user, N) for item, pui in rank: if item in tu: hit += 1 all_ += N return hit/(all_*1.)
覆盖率反映了推荐算法发掘长尾的能力,覆盖率越高,说明推荐算法越可以将长尾中的物品推荐给用户。学习
\[ \text{Coverate}=\frac{|\bigcup_{u\in{U}}R(u)|}{|I|} \]测试
def coverage(train, test, N): """计算覆盖率""" recommend_items = set() all_items = set() for user in train.keys(): for item in train[user].keys(): all_items.add(item) rank = get_recommendation(user, N) for item, pui in rank: recommend_items.add(item) return len(recommend_items)/len((all_items)*1.)
若是推荐出的物品都很热门,说明推荐的新颖度较低;不然说明推荐结果比较新颖。网站
def popularity(train, test, N): """计算新颖度""" item_popularity = dict() for user, items in train.items(): for item in items.keys(): if item not in item_popularity: item_popularity[item] = 0 item_popularity[item] += 1 ret = 0 n = 0 for user in train.keys(): rank = get_recommendation(user, N) for item, pui in rank: # 物品的流行度分布知足长尾分布,对流行度取对数后,流行度的平均值更稳定 ret += math.log(1+item_popularity[item]) n += 1 ret /= n*1. return ret
基于领域的算法分为两大类,一类是基于用户的协同过滤算法,另外一类是基于物品的协同过滤算法。
在一个基于用户的协同过滤算法的在线个性化推荐系统中,当一个用户A须要个性化推荐时,通常须要如下两个步骤:
对于步骤1,咱们须要计算两个用户的兴趣类似程度,协同过滤算法主要利用用户的行为的类似度计算用户间兴趣的类似度,可使用如下两种方法计算用户间的兴趣类似度(其中\(u\)和\(v\)表示不一样的用户,\(N(u)\)表示用户\(u\)曾经拥有正反馈的物品集合;\(N(v)\)表示用户\(v\)曾经拥有正反馈的物品集合。):
Jaccard公式:
\[ w_{uv}=\frac{|N(u)\bigcap{N(v)}|}{|N(u)\bigcup{N(v)}|} \]
余弦类似度:
\[ w_{uv}=\frac{|N(u)\bigcap{N(v)}|}{\sqrt{|N(u)||{N(v)}|}} \]
假设上图为某个网站用户行为记录,UserCF(基于用户的协同过滤算法)时使用余弦类似度计算用户A和用户B的兴趣类似度为:
\[ w_{AB} = \frac{|\{a,b,d\}\bigcap{\{a,c\}|}}{\sqrt{|\{a,b,d\}||\{a,c\}|}} = \frac{1}{\sqrt{6}} \]
同理咱们能够计算出用户A和用户C、D的类似度为:
\[ \begin{aligned} & w_{AC} = \frac{|\{a,b,d\}\bigcap{\{b,e\}|}}{\sqrt{|\{a,b,d\}||\{b,e\}|}} = \frac{1}{\sqrt{6}} \\ & w_{AD} = \frac{|\{a,b,d\}\bigcap{\{c,d,e\}|}}{\sqrt{|\{a,b,d\}||\{c,d,e\}|}} = \frac{1}{\sqrt{9}} = \frac{1}{3} \end{aligned} \]
def user_similarity(train): """计算用户间的余弦类似度""" W = dict() for u in train.keys(): for v in train.keys(): if u == v: continue W[u][v] = len(train[u] & train[v]) W[u][v] /= math.sqrt(len(train[u])*len(train[v])*1.) return W
对于上面的余弦类似度计算,有着很大的计算开销。由于不少用户之间并无对一样的物品产生过行为,即有时候\(|N(u)\bigcap{N(v)}|=0\).所以咱们能够考虑首先计算\(|N(u)\bigcap{N(v)}|\neq0\)的用户对\((u,v)\),而后再对这些用户进行余弦类似度计算,即下面改进的余弦类似度计算。
改进的余弦类似度计算,须要如下三个步骤:
def user_similarity(train): """计算用户之间的类似度""" # 构建物品-用户的倒排表 item_users = dict() for u, items in train.items(): for i in items.keys(): if i not in item_users: item_users[i] = set() item_users[i].add(u) # 计算用户之间的共同有过行为的物品 C = dict() N = dict() for i, users in item_users.items(): for u in users: N[u] += 1 for v in users: if u == v: continue C[u][v] = +=1 # 计算最后的余弦类似度矩阵W W = dict() for u, related_users in C.items(): for v, cuv in related_users.items(): W[u][v] = cuv/math.sqrt(N[u]*N[v]) return W
File "<ipython-input-8-8d19b8833b0f>", line 19 C[u][v] = +=1 ^ SyntaxError: invalid syntax
经过上述获得用户之间的兴趣类似度以后,UserCF算法会给用户推荐和他兴趣最类似的K个用户喜欢的物品。而且能够经过下述公式计算用户\(u\)对物品\(i\)的感兴趣程度:
\[ p(u,i) = \sum_{v\in{S}(u,K)\bigcap{N(i)}}w_{uv}r_{vi} \]
其中\(S(u,K)\)包含和用户\(u\)兴趣最接近的\(K\)个用户,\(N(i)\)是对物品\(i\)有过行为的用户集合,\(w_{uv}\)是用户\(u\)和用户\(v\)的兴趣类似度,\(r_{vi}\)表明用户\(v\)对物品\(i\)的兴趣,由于使用的是单一行为的隐反馈数据,所以全部的\(r_{vi}=1\)。
def recommend(user, train, W): """实现UserCF算法""" rank = dict() interacted_items = train[user] for v, wuv in sorted(W[u].items, key=itemgetter(1), reverse=True)[0:K]: for i, rvi in train[v].items(): if i in interacted_items: # 在继续以前咱们应该筛选用户间的行为 rank[i] += wuv*rvi return rank
若是两个用户都买过《新华字典》,并不能证实两我的兴趣类似,由于中国绝大多数人都买过《新华字典》,可是两个用户都买过冷门商品,如《机器学习》,则能够认为两个用户的兴趣类似。所以咱们能够经过以下公式计算用户间的兴趣类似度:
\[ w_{uv}=\frac{\sum_{i\in{N(u)}\bigcap{N(v)}}\frac{1}{\log{(1+|N(i)|)}}}{\sqrt{|N(u)||N(v)|}} \]
其中\(\frac{1}{\log{(1+|N(i)|)}}\)惩罚了用户\(u\)和用户\(v\)共同兴趣列表中热门物品对他们类似度的影响。
基于上述用户间兴趣类似度则能够改造UserCF算法为User-IIF算法。
def user_similarity(train): """计算用户之间的类似度""" # 构建物品-用户的倒排表 item_users = dict() for u, items in train.items(): for i in items.keys(): if i not in item_users: item_users[i] = set() item_users[i].add(u) # 计算用户之间的共同有过行为的物品 C = dict() N = dict() for i, users in item_users.items(): for u in users: N[u] += 1 for v in users: if u == v: continue C[u][v] = +=1/math.log(1+len(users)) # 计算最后的余弦类似度矩阵W W = dict() for u, related_users in C.items(): for v, cuv in related_users.items(): W[u][v] = cuv/math.sqrt(N[u]*N[v]) return W
基于物品的协同过滤算法的推荐系统主要分为两步:
因为基于物品的协同过滤算法的思想是——购买了该商品的用户也会常常购买其余的商品。根据这句话,能够用下面的公式定义物品的类似度:
\[ w_{ij} = \frac{|N(i)\bigcap{N(j)|}}{|N(j)|} \]
其中\(N(i)\)是喜欢物品\(i\)的用户数,\(|N(i)\bigcap{N(j)|}\)是同时喜欢物品\(i\)和物品\(j\)的用户数。
对于上述简单的公式,若是物品\(j\)很热门,则该公式会形成任何物品都会和热门的物品有很大的类似度,这对于致力于挖掘长尾信息的推荐系统来讲不是一个好的特性,所以能够把上述公式改形成:
\[ w_{ij} = \frac{|N(i)\bigcap{N(j)}|}{\sqrt{|N(i)||N(j)|}} \]
其中\(\sqrt{|N(i)||N(j)|}\)至关于惩罚了物品\(j\)的权重,减轻了热门物品会和不少物品类似的可能性。
ItemCF算法的思想是,假设每一个用户的兴趣都局限在某几个方面。若是两个物品属于一个用户的兴趣列表,那么这两个物品可能就属于有限的几个领域;若是两个物品属于不少用户的兴趣列表,则它们可能就属于同一个领域,于是有很大的类似度。
ItemCF算法相似于UserCF算法,也须要创建用户-物品倒排表,创建该表流程以下:
import math def item_similarity(train): """计算物品间的类似度""" # 计算用户共同拥有的物品 C = dict() N = dict() for u, items in train.items(): for i in items: try: N[i] += 1 except: N[i] = 1 for j in items: if i == j: continue try: try: if not isinstance(C[i], dict): C[i] = dict() except: C[i] = dict() C[i][j] += 1 except: C[i][j] = 1 # 计算最后的类似矩阵W W = dict() for i, related_items in C.items(): for j, cij in related_items.items(): W[i] = dict() W[i][j] = cij / math.sqrt(N[i]*N[j]) return W train = {'A': ['a', 'b', 'd'], 'B': ['b', 'c', 'e'], 'C': [ 'c', 'd'], 'd': ['b', 'c', 'd'], 'e': ['a', 'd']} W = item_similarity(train)