个性化推荐算法整理(二)基于内容的推荐算法content based

2021-11-02 16:47:52 浏览数 (1)

接个性化推荐算法整理

基于内容的推荐算法content based

  • 个性化召回算法Content Based背景介绍

基于内容的推荐不同于之前任何一种个性化召回算法,它属于独立的分支。像之前的CF、LFM、Personal Rank都同属于基于领域的推荐。Item2vec属于深度学习的推荐。

  • Content Based算法主体流程介绍

在这个算法的主体流程大部分并不属于个性化推荐的范畴,实际上应该从属于NLP或者用户画像的内容范畴。只有极少数的一部分属于个性化推荐算法的内容范畴。

背景

  • 思路极简,可解释性强。

任何一个推荐系统的初衷都是为了推荐出用户喜欢的Item。而基于内容的推荐恰是刻画出用户的喜好之后给予用户推荐这个喜好的物品。如果某一个用户访问系统的时候经常点击体育类的新闻,在这个用户下一次访问系统的时候,自然而然的系统更加倾向性的给他推荐体育类型的新闻。对于推荐结果可解释性非常的强。

  • 用户推荐的独立性

基于内容的推荐结果只与该用户本身的行为有关系,其余用户的行为是影响不到该用户的推荐结果。但是无论是CF、LFM、Personal Rank以及Item2vec,其余用户的行为都会一定程度上或多或少地干预到最后的推荐结果。

  • 问世较早,流行度高

基于内容推荐的极简性、可解释性,所以它出现的非常早,并且无论是在工业界还是研究界都作为一种基础的召回算法,流行度非常高。但是任何事物都是有两面性的,基于内容的推荐并不完美,它有非常明显的缺点:1、它对于推荐的扩展性是比较差的,也就是说如果一个用户之前经常访问体育类的新闻,那么在之后的推荐中,倾向于在体育范围内不断的挖掘,而很难完成跨领域的物品推荐。2、需要积累一定量的用户的行为,才能够完成基于内容的推荐。

Content Based算法主流程

  • Item Profile

针对于基于内容推荐下,Item的刻画大体可以分为两大类:1、关键词刻画;2、类别刻画。无论在什么场景下,都是这两个类的刻画。譬如信息流场景下,我们需要刻画出一件新闻属于财经还是娱乐。这件新闻讲的是某个球星还是某个明星。在电商场景下,我们需要刻画出这个物品它属于图书还是属于玩具,具体的关键词上,也会有是讲这个物品是讲机器学习的还是讲人文情感的。这个物品是参与满减的还是参与包邮的。

  • User Profile

第一步,我们完成了物品的内容刻画之后,第二步我们需要对用户进行刻画。传统范畴的用户画像是比较宽泛的,它不仅包含了用户的一些动态特征,还包含了它的一些静态特征。而我们用在基于内容推荐里的,更多的聚焦在用户长期短期的行为,继而通过这个行为的分析,将用户感兴趣的类别予以刻画。

  • Online Recommendation

有了Item的刻画,有了User的刻画,便是在线上完成个性化推荐的过程。也就是说给用户推荐他最感兴趣的一些类别。假使某个用户经常点击某个球星的新闻,当这个用户访问系统的时候,我们应该将该球星最新的新闻最及时的消息推荐给这个用户,这样点击率自然会很高。

经过这三步的介绍,我们发现前两步是从属于NLP和用户画像的范畴,第三步是个性化内容推荐的范畴。

Item Profile技术要点

  • Topic FInding

针对于Topic发现,我们首先要选定特征,这里的特征是title和内容主体的词语的分词。我们得到了这个词语的分词之后,针对于Topic的发掘,我们采用命名实体识别的方式,这个方式可以去匹配关键词词表。得到了这些关键词之后,我们需要对这些关键词进行一定的排名。将排名最高的Top 3或Top 5给Item完成Label。至于这里的排名,我们会用一些算法加一些规则,算法诸如像TF-IDF(关于TF-IDF的内容请参考Tensorflow深度学习算法整理(二) ),规则是基于我们自己的场景总结出来的一些来修正错误keys的一些规则。

  • Genre Classify

对于类别的划分,我们同样是首先选定好特征,这里同样是利用一些文本信息,比如说title或者正文中所有的去过标点之后的分词得到的词向量,这里词向量可以直接在浅层模型中进行one-hot编码。在深层模型中可以进行embedding。这里使用的分类模型主要是像早期的逻辑回归,像中期的FastText以及近期的Text CNN等等。这些分类器我们在使用的时候,只使用多种分类器分别在不同的权重,然后对结果进行一个线性的加权,从而得到正确的分类。

以上我们是针对于文档的Topic发掘或者是类别的分类进行的叙述。对于短视频,实际上现在引入了一些更多的特征,比如关键帧所对应图像的分类识别以及音频所对应语音识别后文字的处理等一些有益的尝试。

User Profile技术要点

  • Genre/Topic

用户对哪些种类的新闻或者说物品感兴趣,另一个层面就是说对哪些关键词感兴趣。现在多是基于统计的方式,也同时做一些有益的尝试,比如引入一些分类器等等。

  • Time Decay

对于用户的刻画,我们一定要注意时间衰减。也就是不同时期的行为所占权重是不同的。最终我们想刻画得到的结果是针对某个用户,我们想得到用户对于不同种类Item的倾向性,譬如某个用户对于娱乐的倾向性是0.7,对于财经的倾向性是0.3。

Online Recommendation技术要点

  • Find top k Genre/Topic

基于用户的刻画,找到用户最感兴趣的top k个分类。

  • Get the best n item from fixed Genre/Topic

由于这top k个分类都是带有权重的,相应的给每个分类得到n个最好的这个分类下的item。这里有两点需要说明,由于权重的不同,不同种类下召回的n个数目是不同的,譬如某人对财经感兴趣对娱乐也感兴趣,但是对娱乐感兴趣的程度更高,那么这里对娱乐召回的数目就要多于对财经的召回的数目。这里的best对于不是新item来讲,就是它后面CTR;如果是新的item,我们在入库的时候,都会给出一个预估的CTR,那么我们就用这个预估的CTR来作为item是否好的衡量标准。

代码实现

我们先来实现物品平均评分和均分物品类别权重以及物品类别的倒排

代码语言:javascript复制
import os

def get_ave_score(input_file):
    '''
    物品平均评分
    :param input_file: 评分文件
    :return:
    '''
    if not os.path.exists(input_file):
        return {}
    linenum = 0
    record = {}
    ave_score = {}
    with open(input_file, 'r') as fp:
        for line in fp:
            if linenum == 0:
                linenum  = 1
                continue
            item = line.strip().split(",")
            if len(item) < 4:
                continue
            userid, itemid, rating = item[0], item[1], float(item[2])
            if itemid not in record:
                record[itemid] = [0, 0]
            record[itemid][0]  = rating
            record[itemid][1]  = 1
    for itemid in record:
        ave_score[itemid] = round(record[itemid][0] / record[itemid][1], 3)
    return ave_score

def get_item_cate(ave_score, input_file):
    '''
    均分类别权重和物品类别倒排
    :param ave_score: 物品平均评分
    :param input_file: 物品详情文件
    :return:
    '''
    if not os.path.exists(input_file):
        return {}, {}
    linenum = 0
    item_cate = {}
    record = {}
    cate_item_sort = {}
    topk = 100
    with open(input_file, 'r') as fp:
        for line in fp:
            if linenum == 0:
                linenum  = 1
                continue
            item = line.strip().split(",")
            if len(item) < 3:
                continue
            itemid = item[0]
            cate_str = item[-1]
            cate_list = cate_str.strip().split("|")
            ratio = round(1 / len(cate_list), 3)
            if itemid not in item_cate:
                item_cate.setdefault(itemid, {})
            for fix_cate in cate_list:
                item_cate[itemid][fix_cate] = ratio
    for itemid in item_cate:
        for cate in item_cate[itemid]:
            if cate not in record:
                record.setdefault(cate, {})
            itemid_rating_score = ave_score.get(itemid, 0)
            record[cate][itemid] = itemid_rating_score
    for cate in record:
        if cate not in cate_item_sort:
            cate_item_sort.setdefault(cate, [])
        for combine in sorted(record[cate].items(),
                              key=lambda x: x[1],
                              reverse=True)[:topk]:
            cate_item_sort[cate].append(combine[0]   "_"   str(combine[1]))
    return item_cate, cate_item_sort

if __name__ == "__main__":

    ave_score = get_ave_score("../data/ratings.txt")
    print(len(ave_score))
    print(ave_score['31'])
    item_cate, cate_item_sort = get_item_cate(ave_score, "../data/movies.txt")
    print(item_cate['1'])
    print(cate_item_sort['Children'])

运行结果

代码语言:javascript复制
4382
3.167
{'Adventure': 0.2, 'Animation': 0.2, 'Children': 0.2, 'Comedy': 0.2, 'Fantasy': 0.2}
['250_5.0', '1030_5.0', '2091_5.0', '2102_5.0', '2430_5.0', '4519_5.0', '26084_5.0', '27790_5.0', '156025_5.0', '2046_4.75', '2138_4.5', '3034_4.5', '6350_4.5', '7164_4.5', '85736_4.5', '5971_4.4', '262_4.375', '26662_4.333', '81564_4.333', '1033_4.25', '3213_4.25', '78499_4.222', '2005_4.167', '76093_4.143', '1148_4.125', '60069_4.1', '745_4.083', '2987_4.059', '8_4.0', '314_4.0', '837_4.0', '917_4.0', '953_4.0', '1009_4.0', '1011_4.0', '1021_4.0', '1023_4.0', '1031_4.0', '1223_4.0', '1566_4.0', '2014_4.0', '2034_4.0', '2037_4.0', '2083_4.0', '2096_4.0', '2099_4.0', '2141_4.0', '2687_4.0', '2846_4.0', '3086_4.0', '3159_4.0', '3189_4.0', '3564_4.0', '5159_4.0', '6559_4.0', '6753_4.0', '6793_4.0', '6951_4.0', '8537_4.0', '27253_4.0', '27731_4.0', '37857_4.0', '50601_4.0', '56171_4.0', '68954_4.0', '79091_4.0', '84944_4.0', '86298_4.0', '87222_4.0', '95858_4.0', '106022_4.0', '110461_4.0', '1282_3.929', '1907_3.917', '59784_3.917', '50872_3.885', '919_3.861', '531_3.857', '596_3.85', '3396_3.833', '1_3.829', '2804_3.818', '1073_3.812', '1097_3.808', '34_3.795', '4886_3.792', '2081_3.773', '986_3.75', '2033_3.75', '2052_3.75', '2080_3.75', '2087_3.75', '5103_3.75', '6316_3.75', '8961_3.75', '38038_3.75', '44022_3.75', '46948_3.75', '65261_3.75', '103335_3.75']

0 人点赞