Python推荐系统——冷启动原理与项目实战

一、冷启动原理与项目实战

1 冷启动原理与技术原理

推荐系统需要根据用户的历史行为和兴趣预测用户未来的行为和兴趣,因此大量的用户行为数据就称为推荐系统的重要组成部分和先决条件。很多在开始阶段就希望有个性化推荐应用的网站来说,在没有大量用户数据的情况下设计个性化推荐系统并且让用户对推荐结果满意从而愿意使用推荐系统,就是冷启动问题。本质:商品或用户多、但行为历史数据或特征历史数据少。

1.1 冷启动问题

用户不确定性需求是客观存在的,在当今信息爆炸的时代,用户的不确定性需求更加明显,而推荐作为一种解决用户不确定性需求的有效手段在互联网产品中会越来越重要, 特别是随着短视频、新闻等应用的崛起,推荐的重要性被更多人认可。很多产品将推荐业务放到最核心的位置(如首页),或者是整个产品的核心。因此,新用户必须要面对冷启动这个问题。

新用户、新标的物是持续产生的,对互联网产品来说是常态,是无法避免的,所以冷启动问题会伴随整个产品的生命周期。既然很多产品将推荐放到这么好的位置, 而推荐作为一种有效提升用户体验的工具,在新用户留存中一定要起到非常关键的作用,如果推荐系统不能很好的为新用户推荐好的内容,新用户可能会流失。所以如果不解决冷启动问题,你的新用户一直会得不到好的推荐体验,极有可能会流失掉。

新用户的留存对一个公司来说非常关键, 服务不好新用户,并让用户留下来,你的用户增长将会停滞不前。对于互联网公司来说, 用户是公司赖以生存的基础,是利润的核心来源。因为互联网经济是建立在规模用户基础上的,只有用户足够多,你的产品才会有变现的价值。

同时,只有你的产品有很好的用户增长曲线, 投资人才会相信未来用户大规模增长的可能, 才能看得到产品未来的变现价值,才会愿意在前期投资你的产品。

  • 用户冷启动:用户冷启动主要解决如何给新用户做个性化推荐的问题。 当新用户到来时,没有他的行为数据,所以无法根据他的历史行为预测其兴趣,从而无法借此给他做个性化推荐。

  • 物品冷启动:物品冷启动主要解决如何将新的物品推荐给可能对它感兴趣的用户这一问题。

  • 系统冷启动:系统冷启动主要解决如何在一个新开发的网站上(没有用户,也没有用户行为,只有一些物品的信息)设计个性化推荐系统,从而在网站刚发布时就让用户体验到个性化推荐服务这一问题。

1.2 冷启动解决思路

我们先概述一下解决冷启动的一般思路:

  • 提供非个性化的推荐(用户冷启动)

  • 利用用户注册时提供的信息(用户冷启动、系统冷启动)

  • 基于内容做推荐(用户冷启动、系统冷启动)

  • 利用标的物的metadata信息做推荐(标的物冷启动)

  • 采用快速试探策略(用户冷启动、标的物冷启动)

  • 采用兴趣迁移策略(用户冷启动、系统冷启动)

  • 采用基于关系传递的策略(标的物冷启动)

下面针对用户冷启动与物品冷启动做一个详细的介绍。

  • 用户冷启动:

    • 关联第三方平台,获取特征
    • 利用注册信息
    • 在注册时,收集用户喜好
  • 物品冷启动:

    • 管理员标签
    • 内容特征

2 基于注册信息的冷启动推荐

2.1 数据集

Book-Crossings是由Cai-Nicolas Ziegler编写的图书评分数据集。 它包含90000个用户的270000本书的110万个评分。评分范围从1到10,包括显式和隐式的评分。Book-Crossings数据集是最不密集的数据集之一,也是具有明确评分的最不密集的数据集。

2.2 用户冷启动

  • 提供非个性推荐

    • 利用先验数据做推荐,人都是有喜新厌旧倾向的,推荐新的东西肯定能抓住用户的眼球。
    • 给用户提供多样化的选择,将内容按照标签分几大类,每大类选择一个推荐给新用户,这样总有一个是用户喜欢的。
  • 利用新用户在注册时提供的信息

    • 利用人口统计学数据,很多产品在新用户注册时是需要用户填写一些信息的, 这些用户注册时填的信息就可以作为为用户提供推荐的指导。
    • 利用社交关系,社交推荐最大的好处是用户基本不会反感推荐的标的物
    • 利用用户填写的兴趣点,用户在注册时提供你的兴趣点,有了这些兴趣点就可以为你推荐你喜欢的内容
  • 基于内容做推荐

    • 当用户只有很少的行为记录时,这时还无法给用户做很精准的推荐。这时可以采用基于内容的推荐算法,基于内容的推荐算法只要用户有少量行为就可以给用户推荐。
  • 采用快速试探策略

    • 随机或者按照非个性化推荐的策略给用户推荐,基于用户的点击反馈快速发现用户的兴趣点,从而在短时间内挖掘出用户的兴趣。
  • 采用兴趣迁移策略

    • 鉴了迁移学习的思路,在基于主产品拓展新产品形态的情况下,特别适合新产品做冷启动。

2.3 基于新用户注册提供的信息

2.3.1 分析数据

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 1 分析用户表
# 读取数据
df_user = pd.read_csv('./data/BX-Users.csv',sep=';')
df_user.head()

# 修改列名
df_user.columns = ['user_id','location','age']
df_user.head()

# 分析location
location_value_counts = df_user['location'].value_counts()
location_value_counts.count()

# 平均每个地区,存在的用户个数
location_value_counts.sum() / location_value_counts.count()

# 把location变成国家
df_user['location'] = df_user['location'].apply(lambda location:location.rsplit(',',maxsplit=1)[-1].strip())

len(df_user['location'].unique())

location_value_counts = df_user['location'].value_counts()

plt.plot(
    range(len(location_value_counts)),
    location_value_counts,
)
plt.show()

plt.pie(
    location_value_counts,
    labels = location_value_counts.index
)
plt.show()

#### 分析 年龄
df_user[
    df_user['age'].isnull()
].head()

df_user[
    df_user['age'].isnull()
].count()

df_user.info()

df_user_age_notnull = df_user[~df_user['age'].isnull()]
df_user_age_notnull.head()

age_value_counts_sort_by_age = df_user_age_notnull['age'].value_counts().sort_index()

plt.plot(
    age_value_counts_sort_by_age.index,
    age_value_counts_sort_by_age
)
plt.show()

def divide_age(age):
    """
    :return:
        Nan,0-6,>100  --> 0 年龄填写有误
        7 - 12        --> 1 少儿
        13 - 17       --> 2 青少年
        18 - 45       --> 3 青年
        46 - 69       --> 4 中年
        70 - 100      --> 5 老年
    """
    if np.isnan(age) or (age <= 6) or (age > 100):
        return 0
    if age >= 7 and age <= 12:
        return 1
    if age >= 13 and age <= 17:
        return 2
    if age >= 18 and age <= 45:
        return 3
    if age >= 46 and age <= 69:
        return 4
    if age >= 70 and age <= 100:
        return 5
df_user['age'] = df_user['age'].apply(divide_age)
df_user.head()

# 分析年龄段

plt.pie(
    df_user['age'].value_counts().sort_index(),
    labels = ['undefined','shaoer','qingshaonian','qingnian','zhongnian','laonian']
)
plt.show()


# 分析打分表
# 读取数据
df_rating = pd.read_csv('./data/BX-Book-Ratings.csv',sep=';')
df_rating.head()

df_rating.columns = ['user_id','item_id','rating']
df_rating.head()
df_rating.info()

# 删除有误数据
df_rating = df_rating.dropna()
df_rating.info()

# 用户活跃度
user_value_counts = df_rating['user_id'].value_counts()

plt.plot(
    range(len(user_value_counts)),
    user_value_counts,
)
plt.show()

# 商品流行度分析
item_value_counts = df_rating['item_id'].value_counts()
plt.plot(
    range(len(item_value_counts)),
    item_value_counts,
)
plt.show()

df_rating.info()

df_rating['rating'] = df_rating['rating'].astype(int)
df_rating.info()

# 合并用户表与打分表
df_user.head()

df_rating.head()

df_data = pd.merge(
    df_user,
    df_rating,
    on='user_id',
    how='inner',
)
df_data.head()

df_data.info()

df_data['age'] = df_data['age'].astype(int)
df_data.info()

# 保存表
import _pickle as cPickle

cPickle.dump(
    df_data,
    open('./data/df_data.pkl','wb')
)

object1 = cPickle.load(
    open('./data/df_data.pkl','rb')
)

object1.head()

2.3.2 划分数据集

import pandas as pd
import _pickle as cPickle

# 读取文件
df_data = cPickle.load(
    open('./data/df_data.pkl','rb')
)
df_data.head()

df_data.info()

# 划分数据集 70%训练集 30%测试集

import random

random.seed(123)
text_index_s = random.sample(
    df_data.index.tolist(),
    int(len(df_data) * 0.3)
)
text_index_s

df_data_text = df_data.loc[
    text_index_s
]
df_data_text.head()

df_data_text.info()

df_data_training = df_data[
    ~df_data.index.isin(text_index_s)
]
df_data_training.head()

df_data_training.info()

# 保存数据
cPickle.dump(
    df_data_text,open('./data/df_data_text.pkl','wb')
)
cPickle.dump(
    df_data_training,open('./data/df_data_training.pkl','wb')
)

2.3.3 推荐

import numpy as np
import pandas as pd
import _pickle as cPickle

#### 导入训练集
df_data_training = cPickle.load(open('./data/df_data_training.pkl','rb'))
df_data_training.head()

df_data_training.info()

df_data_training['location'].value_counts()[:11].index.tolist()

top10_location = ['usa',
 'canada',
 'united kingdom',
 'germany',
 'australia',
 'spain',
 'france',
 'n/a',
 'italy',
 'portugal']

df_data_training['location'] = df_data_training['location'].apply(lambda location : location if location in top10_location else 'other')

df_data_training['location'].value_counts()

"""
feature_recommand = {
    'usa':{
        '0':[item1,item2,item3],
        '1':[item1,item2,item3]
    },
    'canada':{
        '0':[item1,item2,item3],
        '1':[item1,item2,item3]
    },
}
feature_recommand_with_rating = {
    'usa':{
        '0':[{item1:10.0},{item2:9.7},item3],
        '1':[item1,item2,item3]
    },
    'canada':{
        '0':[item1,item2,item3],
        '1':[item1,item2,item3]
    },
}

"""

from collections import defaultdict

feature_recommand = defaultdict(dict)
feature_recommand_with_rating = defaultdict(dict)

for location,groupby_location in df_data_training.groupby('location'):
    for age,groupby_location_age in groupby_location.groupby('age'):
        item_id_value_counts = groupby_location_age['item_id'].value_counts()
        divide_boundary = max(np.percentile(item_id_value_counts,75),5)
        groupby_location_age_top = groupby_location_age[
            groupby_location_age['item_id'].isin(
            item_id_value_counts[item_id_value_counts >= divide_boundary].index
        )]
        item_mean_rating = groupby_location_age_top.groupby('item_id')['rating'].mean().sort_values(ascending=False)[:20]
        feature_recommand[location][age] = [
            item_id
            for item_id in item_mean_rating.index if item_mean_rating[item_id] >= 5
        ]
        feature_recommand_with_rating[location][age] = [
            {"%s" % item_id : round(item_mean_rating[item_id],2)}
            for item_id in item_mean_rating.index if item_mean_rating[item_id] >= 5
        ]
    
feature_recommand
feature_recommand_with_rating

#### 找到热门商品
top2000_item_id_s = df_data_training['item_id'].value_counts()[:2000].index.tolist()
top2000_item_id_s

df_data_training_top2000 = df_data_training[
    df_data_training['item_id'].isin(top2000_item_id_s)
]
df_data_training_top2000.head()

df_data_training_top2000.info()

top20_item_id_s = df_data_training_top2000.groupby('item_id')['rating'].mean().sort_values(ascending=False)[:20].index.tolist()
top20_item_id_s

for location in feature_recommand.keys():
    for age in feature_recommand[location].keys():
#         print(location,age)
        feature_recommand_len = len(feature_recommand[location][age])
        feature_recommand[location][age] += top20_item_id_s[:20-feature_recommand_len]

cPickle.dump(feature_recommand,open('./data/feature_recommand.pkl','wb'))

2.3.4 验证

import numpy as np
import pandas as pd
import _pickle as cPickle
from collections import defaultdict

# 读取推荐列表
feature_recommand = cPickle.load(open('./data/feature_recommand.pkl','rb'))
feature_recommand

import os
print(os.path.abspath(__file__))  #显示当前文件的地址

# 导入测试集
df_data_text = cPickle.load(open('./data/df_data_text.pkl','rb'))
df_data_text.head()

df_data_text.info()

# 处理location
top_location_s = list(feature_recommand.keys())

df_data_text['location'] = df_data_text['location'].apply(lambda location : location if location in top_location_s else 'other')

df_data_text['location'].value_counts()

# 得到准确率和召回率
union_quantity = 0
recommand_quantity = 0
user_fav_quantity = 0
for location,groupby_location in df_data_text.groupby('location'):
    for age,groupby_location_age in groupby_location.groupby('age'):
        print('执行到:',location,age)
        for user_id,groupby_location_age_userid in groupby_location_age.groupby('user_id'):
            items_rating = groupby_location_age_userid.groupby('item_id')['rating'].mean().sort_values(ascending=False)
            user_fav_items = [
                item_id
                for item_id in items_rating.index if items_rating[item_id] >= 5
            ]
            recommand_items = feature_recommand[location][age]
            union_quantity += len(
                set(user_fav_items) & set(recommand_items)
            )
            recommand_quantity += len(recommand_items)
            user_fav_quantity += len(user_fav_items)
print('准确率:',union_quantity / recommand_quantity)
print('召回率:',union_quantity / user_fav_quantity)

3 基于商品内容的冷启动推荐

3.1 商品内容冷启动

  • 利用标的物的metadata信息做推荐
    • 利用标的物跟用户行为的相似性,可以通过提取新入库的标的物的特征,通过计算标的物特征跟用户行为特征的相似性,从而将标的物推荐给与它最相似的用户。
    • 利用标的物跟标的物的相似性,根据这些属性找到与该标的物最相似的标的物,将该标的物推荐给这些消费过的用户。
  • 采用快速试探策略
    • 将新标的物曝光给随机一批用户,观察用户对标的物的反馈,找到对该标的物有正向反馈的用户, 后续将该标的物推荐给有正向反馈的用户或者与该用户相似的用户。
  • 采用基于关系传递的策略
    • 短视频与长视频有相似关系,长视频与喜欢它的用户有相似关系,最终得到短视频与用户有相似关系

3.2 Content-based算法

CB应该算是最早被使用的推荐方法,它根据用户过去喜欢的产品,为用户推荐和他过去喜欢的产品相似的产品。

过程步骤:
  • 为每个item抽取出一些特征(也就是item的content了)来表示此item;

  • 利用一个用户过去喜欢(及不喜欢)的item的特征数据,来学习出此用户的喜好特征(profile);

  • 通过比较上一步得到的用户profile与候选item的特征,为此用户推荐一组相关性最大的item。

包含的算法:
  • 最近邻方法(k-Nearest Neighbor,简称kNN)
    • 对于一个新的item,最近邻方法首先找用户u已经评判过并与此新item最相似的k个item,然后依据用户u对这k个item的喜好程度来判断其对此新item的喜好程度。
  • Rocchio算法
    • Rocchio算法是信息检索中处理相关反馈的一个著名算法。
  • 决策树算法(Decision Tree,简称DT)
    • 当item的属性较少而且是结构化属性时,决策树一般会是个好的选择。这种情况下决策树可以产生简单直观、容易让人理解的结果。
  • 线性分类算法(Linear Classifer,简称LC)
    • 线性分类器(LC)尝试在高维空间找一个平面,使得这个平面尽量分开两类点。也就是说,一类点尽可能在平面的某一边,而另一类点尽可能在平面的另一边。
  • 朴素贝叶斯算法(Naive Bayes,简称NB)
    • NB经常被用来做文本分类,它假设在给定一篇文章的类别后,其中各个词出现的概率相互独立。它的假设虽然很不靠谱,但是它的结果往往惊人地好。再加上NB的代码实现比较简单,所以它往往是很多分类问题里最先被尝试的算法。
CB的优缺点:
  • CB的优点:
  1. 用户之间的独立性:既然每个用户的profile都是依据他本身对item的喜好获得的,自然就与他人的行为无关。而CF刚好相反,CF需要利用很多其他人的数据。CB的这种用户独立性带来的一个显著好处是别人不管对item如何作弊(比如利用多个账号把某个产品的排名刷上去)都不会影响到自己。

  2. 好的可解释性:如果需要向用户解释为什么推荐了这些产品给他,你只要告诉他这些产品有某某属性,这些属性跟你的品味很匹配等等。

  3. 新的item可以立刻得到推荐:只要一个新item加进item库,它就马上可以被推荐,被推荐的机会和老的item是一致的。而CF对于新item就很无奈,只有当此新item被某些用户喜欢过(或打过分),它才可能被推荐给其他用户。所以,如果一个纯CF的推荐系统,新加进来的item就永远不会被推荐。

  • CB的缺点:
  1. item的特征抽取一般很难:如果系统中的item是文档(如个性化阅读中),那么我们现在可以比较容易地使用信息检索里的方法来“比较精确地”抽取出item的特征。但很多情况下我们很难从item中抽取出准确刻画item的特征,比如电影推荐中item是电影,社会化网络推荐中item是人,这些item属性都不好抽。其实,几乎在所有实际情况中我们抽取的item特征都仅能代表item的一些方面,不可能代表item的所有方面。这样带来的一个问题就是可能从两个item抽取出来的特征完全相同,这种情况下CB就完全无法区分这两个item了。比如如果只能从电影里抽取出演员、导演,那么两部有相同演员和导演的电影对于CB来说就完全不可区分了。

  2. 无法挖掘出用户的潜在兴趣:既然CB的推荐只依赖于用户过去对某些item的喜好,它产生的推荐也都会和用户过去喜欢的item相似。如果一个人以前只看与推荐有关的文章,那CB只会给他推荐更多与推荐相关的文章,它不会知道用户可能还喜欢数码。

  3. 无法为新用户产生推荐:新用户没有喜好历史,自然无法获得他的profile,所以也就无法为他产生推荐了。当然,这个问题CF也有。

3.3 Content-ItemKNN 算法

3.3.1 构建矩阵

import numpy as np
import pandas as pd

# 读取原商品
df_movie_old = pd.read_csv('./data/movie_old.csv',usecols=[0,2])
df_movie_old.head()

 # 统计原物品中所有的特征
total_genres = set()
for genres in df_movie_old['genres']:
    total_genres |= set(genres.split('|'))
total_genres = list(total_genres)
total_genres

# 读取新物品
df_movie_new = pd.read_csv('./data/movie_new.csv',usecols=[0,2])
df_movie_new.head()

# 建立原物品和新物品的特征矩阵
# 原物品id-->index
movie_old_id_to_index_dict = {}
# 新物品id-->index
movie_new_id_to_index_dict = {}

# 初始化原物品和新物品的特征矩阵
movie_old_genres_array = np.zeros(
    shape=(len(df_movie_old),len(total_genres))
)
movie_new_genres_array = np.zeros(
    shape=(len(df_movie_new),len(total_genres))
)

for index in range(len(df_movie_old)):
    movie_id = df_movie_old.iloc[index]['movie_id']
    movie_old_id_to_index_dict[movie_id] = index
    genres = df_movie_old.iloc[index]['genres'].split('|')
    # 创建特征行向量
    line_data = np.zeros(shape=len(total_genres))
    for i in range(len(total_genres)):
        if total_genres[i] in genres:
            line_data[i] = 1
    # 赋值给index行
    movie_old_genres_array[index] = line_data

for index in range(len(df_movie_new)):
    movie_id = df_movie_new.iloc[index]['movie_id']
    movie_new_id_to_index_dict[movie_id] = index
    genres = df_movie_new.iloc[index]['genres'].split('|')
    # 创建特征行向量
    line_data = np.zeros(shape=len(total_genres))
    for i in range(len(total_genres)):
        if total_genres[i] in genres:
            line_data[i] = 1
    # 赋值给index行
    movie_new_genres_array[index] = line_data

3.3.2 相似度矩阵

movie_old_genres_column_sum_array = np.sum(movie_old_genres_array,axis=1)
movie_new_genres_column_sum_array = np.sum(movie_new_genres_array,axis=1)

# 初始化相似度矩阵
movie_sim_array = np.zeros(
    shape=(len(df_movie_old),len(df_movie_new))
)
for index in range(len(df_movie_old)):
    v1 = np.dot(
        movie_old_genres_array[index],movie_new_genres_array.T
    )
    v2 = np.around(np.sqrt(
        movie_old_genres_column_sum_array[index] * movie_new_genres_column_sum_array),3)
    movie_sim_array[index] = np.around(v1 / v2,2)

movie_sim_array

# 导入原物品的打分
df_rating_old = pd.read_csv('./data/rating_old.csv')
df_rating_old.head()

# 变更id为index
df_rating_old['movie_id'] = df_rating_old['movie_id'].apply(lambda movie_id : movie_old_id_to_index_dict[movie_id])
df_rating_old.head()

df_rating_old.columns = ['user_id','movie_index','rating']

3.3.3 推荐商品并计算准确率与召回率

# 根据用户喜欢的原物品,生成新物品的推荐
user_recommend = {}

for index,(user_id,groupby_userid) in enumerate(df_rating_old.groupby('user_id')):
    movies_rating = groupby_userid.groupby('movie_index')['rating'].mean().sort_values(ascending=False)
    user_fav = movies_rating[
        movies_rating >= 4
    ].index.tolist()
    user_recommend[user_id] = set(np.where(
        movie_sim_array[user_fav] >= 0.85)[1].tolist()[:100])
    if index % 100 == 0:print(index,end='..')

# 读物用户对新物品的实际打分
df_rating_new = pd.read_csv('./data/rating_new.csv')
df_rating_new.head()

# id变index
df_rating_new['movie_id'] = df_rating_new['movie_id'].apply(lambda movie_id : movie_new_id_to_index_dict[movie_id])
df_rating_new.columns = ['user_id','movie_index','rating']
df_rating_new.head()

# 得到用户真正喜欢的新物品
user_fav = {}

for index,(user_id,groupby_userid) in enumerate(df_rating_new.groupby('user_id')):
    movies_rating = groupby_userid.groupby('movie_index')['rating'].mean().sort_values(ascending=False)
    movie_indexs = set(movies_rating[
        movies_rating >= 3
    ].index.tolist())
    user_fav[user_id] = movie_indexs
    if index % 100 == 0:print(index,end='..')

# 计算准确率和召回率
union_quantity = 0  # 为用户推荐的,用户也喜欢的个数   
recommend_quantity = 0 # 为用户推荐的商品总数
fav_quantity = 0 # 用户喜欢的商品总数


for user_id in user_recommend.keys():
    if user_id in user_fav.keys():
        union_quantity += len(
            user_recommend[user_id] & user_fav[user_id]
        )
        recommend_quantity += len(user_recommend[user_id])
        fav_quantity += len(user_fav[user_id])

print('准确率',union_quantity / recommend_quantity)
print('召回率',union_quantity / fav_quantity)

二、利用标签的推荐系统

1 UGC标签系统与TagBasedIFIDF++算法

1.1 UGC标签系统

标签应用一般分为两种:一种是让作者或者专家给物品打标签;另一种是让普通用户给物品打标签,也就是UGC的标签应用。UGC标签系统是很多网站、平台的必要组成成分。

标签的作用:

  1. 体现物品的属性

  2. 体现用户的喜好

用户不会倾向去找到他不感兴趣的商品,然后为其打标签。用户之所以能给物品打标签,通常都会对这个物品比较熟悉。熟悉体现用户偏好。

UGC(用户生产内容)型的产品或社交平台,一般采取个性化关注定制和机器算法推荐,把实际的内容生产任务下放,用户自己生产内容自己消费。UGC型平台的运营人员更偏向产品运营,他们要做的就是明确规则,引导用户群自发产出合适的内容,维护符合产品价值观的内容氛围,他们承担着内容运营的部分工作。UGC型平台这种个性化关注定制和机器算法推荐的好处是在用户接触信息很少有平台内容运营人员的干涉,可以满足自己的个性化需求

1.2 TagBasedIFIDF++算法

1.2.1 第一版本算法

  • 统计每个用户最常用的标签。

  • 对于每个标签,统计被打过这个标签次数最多的物品。

  • 对于一个用户,首先找到他常用的标签,然后找到具有这些标签的最热门物品推荐给这个用户。

  • 字符含义
    • b 标签
    • n(u,b) 用户u打过标签b的数量
    • n(i,b) 物品i被打过标签b的数量
  • 缺点
    • 公式倾向于给热门标签对应的热门物品很大的权重,因此会造成推荐热门的物品给用户,从而降低推荐结果的新颖性。

1.2.2第二版本算法

  • 字符含义
    • n(b,u) 标签b被多少不同用户使用过
  • 缺点
    • 倾向于给热门物品很大的权重

1.2.3 终极版本算法

  • 对热门物品进行惩罚

  • 字符含义
    • n(u,b) 用户u打过标签b的数目
    • n(b,u) 标签b被多少个不同用户使用过的数目
    • n(i,u) 物品i被打过标签b的数目
    • n(i,b) 物品i被多少个不同用户打过标签的数目

2 TagBasedIFIDF++中的矩阵知识

2.1 矩阵相乘

阵的乘法操作,要求左边矩阵的列和右边矩阵的行数要一致

import numpy as np
jz1 = np.mat(numpy.arange(9).reshape(3,3))
jz3 = np.mat(numpy.arange(6).reshape(2,3))
result = jz3*jz1
result = numpy.dot(jz3,jz1)

2.2 列变换

  • 需求
矩阵A: A = np.array([[1,2,3],[4,5,6],[7,8,9]])
    | 1 2 3 |             | 6  |
A = | 4 5 6 |  =====》B = | 15 |
    | 7 8 9 |             | 24 |
  • 方法
B = A.sum(axis=1)

    | 1 2 3 |   | 6  |   | 1*1 + 2*1 + 3*1 | = | 6  |
B = | 4 5 6 | * | 15 | = | 4*1 + 5*1 + 6*1 | = | 15 |
    | 7 8 9 |   | 24 |   | 7*1 + 8*1 + 9*1 | = | 24 | 
第一列乘a+第二列乘b+第三列乘c    
    | 1 2 3 |   | a |   | 1*a + 2*b + 3*c | = | 1a + 2b + 3c |
B = | 4 5 6 | * | b | = | 4*a + 5*b + 6*c | = | 4a + 5b + 6c |
    | 7 8 9 |   | c |   | 7*a + 8*b + 9*c | = | 7a + 8b + 9c |     

2.3 行变换

  • 需求
矩阵A: A = np.array([[1,2,3],[4,5,6],[7,8,9]])
    | 1 2 3 |             
A = | 4 5 6 |  =====》B = [ 12 15 18] 
    | 7 8 9 |            
  • 方法
              | 1 2 3 | 
B = [1 1 1] * | 4 5 6 | = [1*1+1*4+1*7 1*2+1*5+1*8 1*3+1*6+1*9] = [12 15 18]
              | 7 8 9 |   

2.4 numpy中的矩阵与向量

import numpy as np
 
# 创建一个一维数组表示一个行向量
vector_row = np.array([1, 2, 3])
 
# 创建一个一维数组表示一个列向量
vector_column = np.array([[1], [2], [3]])
 
# 创建一个二维数组表示一个矩阵
matrix1 = np.array([[1, 2], [1, 2], [1, 2]])

2.5 矩阵与非矩阵计算

A = np.array([[1, 2], [3, 4]])
B = A / 2 # 每一个元素都除以2

C = np.array([2, 4])
D = A / C # 第一列除以2,第二列除以4


E = np.array([[2], [4]])
F = A / E # 第一行除以2,第二行除以4

3 TagBasedIFIDF++的实现过程

3.1 实现思路

  • 找到用户使用过的标签与使用的次数
  • 找到这些标签被多少用户使用过
  • 找到被打过这些标签的电影
  • 找到这些电影被多少个不同用户打过标签
  • 得到用户u对这些电影的兴趣结果
  • 对兴趣值做倒序排列,取得用户感兴趣的电影

3.2 用户使用过的标签与使用的次数

import numpy as np
import pandas as pd

# 导入tag
df_tag = pd.read_csv('./data/tag.csv',usecols=[0,1,2])
df_tag.head()

df_tag.info()

# 删除出现次数小于20次的标签以及对应的数据
tag_value_counts = df_tag['tag'].value_counts()
top_tags = tag_value_counts[
    tag_value_counts>=20
].index.tolist()
df_tag = df_tag[
    df_tag['tag'].isin(top_tags)
]

df_tag.info()
df_tag.head()

3.3 标签被多少用户使用过

# 建立用户标签矩阵,行是用户的索引,列是标签的索引,值是用户使用过多少次这个标签

user_id_to_index_dict = {}
user_index_to_id_dict = {}

# 初始化一个0矩阵
user_tag_array = np.zeros(shape=(user_quantity,tag_quantity),dtype='i1')

for index,(user_id,groupby_userid) in enumerate(df_tag.groupby('user_id')):
    user_id_to_index_dict[user_id] = index
    user_index_to_id_dict[index] = user_id
    
    tag_value_counts = groupby_userid['tag_index'].value_counts()
    line_data = np.zeros(shape=tag_quantity,dtype='i1')
    for tag_index in tag_value_counts.index:
        line_data[tag_index] = tag_value_counts[tag_index]
    user_tag_array[index] = line_data
    if index % 100 == 0:print(index,end=' ')

user_tag_array

3.4 打过标签的电影

# 建立电影标签表,行是电影的索引,列是标签的索引,值是电影被打过这个标签的次数
movie_id_to_index_dict = {}
movie_index_to_id_dict = {}

movie_tag_array = np.zeros(shape = (movie_quantity,tag_quantity),dtype='i1')

for index,(movie_id,groupby_movieid) in enumerate(df_tag.groupby('movie_id')):
    movie_id_to_index_dict[movie_id] = index
    movie_index_to_id_dict[index] = movie_id
    
    tag_value_counts = groupby_movieid['tag_index'].value_counts()
    line_data = np.zeros(shape=tag_quantity,dtype='i1')
    for tag_index in tag_value_counts.index:
        line_data[tag_index] = tag_value_counts[tag_index]
    movie_tag_array[index] = line_data
    if index % 100 == 0:print(index,end=' ')
movie_tag_array

3.5 电影被多少个不同用户打过标签

#### 变更df_tag中的user_id为user_index,movie_id为movie_index
df_tag['user_id'] = df_tag['user_id'].apply(lambda user_id : user_id_to_index_dict[user_id])
df_tag['movie_id'] = df_tag['movie_id'].apply(lambda movie_id: movie_id_to_index_dict[movie_id])
df_tag.head()
df_tag.columns = ['user_index','movie_index','tag_index']
df_tag.head()

# 用户和电影的矩阵
user_movie_array = np.zeros(shape=(user_quantity,movie_quantity),dtype='i1')

for user_index,groupby_userindex in df_tag.groupby('user_index'):
    movie_indexs = groupby_userindex['movie_index'].unique().tolist()
    line_data = np.zeros(shape=movie_quantity,dtype='i1')
    for movie_index in movie_indexs:
        line_data[movie_index] = 1
    user_movie_array[user_index] = line_data
    if user_index % 100 == 0:print(user_index,end=' ')

user_movie_array
# 对热门标签做惩罚

user_tag_array2 = np.around(user_tag_array / np.log(1 + (user_tag_array > 0).astype(int).sum(axis=0)),3)
user_tag_array2

# 对热门商品做惩罚

movie_tag_array2 = np.around(movie_tag_array / np.log(1 + np.array([user_movie_array.sum(axis=0)]).T),3)
movie_tag_array2

3.6 得到用户对这些电影的兴趣结果

# 构建用户对商品的兴趣矩阵
user_movie_fav_array = np.zeros(shape=(user_quantity,movie_quantity))

for user_index in range(user_quantity):
    # 拿到用户打过的标签索引向量
    user_rated_tag_indexs = np.where(user_tag_array2[user_index] >0)[0].tolist()
    # 用户对标签的喜好程度
    user_rated_tag_values = user_tag_array2[user_index][user_rated_tag_indexs]
    # 被打过这些标签的电影索引
    taged_movie_indexs = np.where((movie_tag_array2[:,user_rated_tag_indexs] > 0).astype(int).sum(axis=1) > 0)[0].tolist()
#     print(taged_movie_indexs)
    sub_movie_tag_array = movie_tag_array2[taged_movie_indexs][:,user_rated_tag_indexs]
#     print(sub_movie_tag_array)
    movies_fav = np.around(np.dot(sub_movie_tag_array,np.array([user_rated_tag_values]).T),3).T[0].tolist()
    line_data = np.zeros(shape=movie_quantity)
    for i,movie_index in enumerate(taged_movie_indexs):
        line_data[movie_index] = movies_fav[i]
    user_movie_fav_array[user_index] = line_data
    print(user_index,end=' ')

3.7 取得用户感兴趣的电影

# 生成用户推荐
user_recommend = {}

for user_index in range(user_quantity):
    user_recommend[user_index] = np.where(user_movie_fav_array[user_index] > 2)[0].tolist()

# 读取打分表
df_rating = pd.read_csv('./data/rating.csv',usecols=[0,1,2])
df_rating.head()

df_rating.columns = ['user_id','movie_id','rating']

def deal_with_userid(user_id):
    if user_id in user_id_to_index_dict.keys():
        return user_id_to_index_dict[user_id]
    else:
        return None
    
def deal_with_movieid(movie_id):
    if movie_id in movie_id_to_index_dict.keys():
        return movie_id_to_index_dict[movie_id]
    else:
        return None

df_rating['user_id'] = df_rating['user_id'].apply(deal_with_userid)
df_rating['movie_id'] = df_rating['movie_id'].apply(deal_with_movieid)

df_rating.head()

df_rating.info()

df_rating = df_rating.dropna()

df_rating.info()

df_rating['user_id'] = df_rating['user_id'].astype(int)
df_rating['movie_id'] = df_rating['movie_id'].astype(int)
df_rating.head()

df_rating.columns = ['user_index','movie_index','rating']

df_rating.head()

# 生成用户喜欢的电影
user_fav = {}
for user_index,groupby_userindex in df_rating.groupby('user_index'):
    movies_rating = groupby_userindex.groupby('movie_index')['rating'].mean()
    fav_movie_indexs = movies_rating[
        movies_rating >= 3
    ].index.tolist()
    user_fav[user_index] = fav_movie_indexs

3.8 验证

#### 计算准确率和召回率

union_quantity = 0
recommend_quantity = 0
fav_quantity = 0

for user_index in user_recommend.keys():
    if user_index in user_fav.keys():
        union_quantity += len(
            set(user_recommend[user_index]) & set(user_fav[user_index])
        )
        recommend_quantity += len(user_recommend[user_index])
        fav_quantity += len(user_fav[user_index])

print('precision',union_quantity / recommend_quantity)
print('recall',union_quantity / fav_quantity)
©️2020 CSDN 皮肤主题: 游动-白 设计师:上身试试 返回首页