python 基于标签的推荐Tag-based及SimpleTagBased、TagBased-TFIDF等算法实现

1. 概览

本篇主要介绍关于推荐算法相关的知识:基于标签的推荐,涉及了3个原理较简单的计算方法(Simple Tag-based、Normal Tag-based、Tag-based-Tfidf ),以及基于pandas dataframe的python代码实现。

dataframe的效率比用字典实现低很多,主要是因为该算法涉及到较多的取值操作,字典的取值的平均时间复杂度当然要比dataframe用loc快很多。

2. 基本概念

2.1 用户画像
2.1.1 定义

用户画像毋庸置疑,即是对用户行为特征的总结归纳和描述,以更好的提升业务质量。

2.1.2 步骤
  1. 定义全局的用户唯一标识id(例如身份证、手机号、用户id等)
  2. 给用户打标签
  3. 根据标签为为业务带来不同阶段的收益。在用户生命周期的3个阶段:获客、粘客、留客 中,根据各类的用户数据对用户进行标签化、用户画像等,尽最大限度对用户进行留存。
2.1.3 标签来源
  1. PGC:通过工作人员进行打标签(质量高,数据规范)

    通过聚类算法对物品进行降为形成不同的标签。物品的标签化,本身是对物品的降维,所以可以利用聚类、降维的方法进行物品标签的生成。目前主流的聚类方法有:Kmeans,EM,DBScan,层级聚类,PCA等等。

  2. UGC:通过用户进行打标签(质量低,数据不规范)

2.1.4 标签相关的数据结构
  • 用户打标签的记录【user_id, item_id, tag】
  • 用户打过的标签【user_id, tag】
  • 用户打过标签的item【user_id, item】
  • 打上某标签的item【tag, item】
  • 同样打过某标签的用户【tag, user_id】
2.1.5 如何给用户推荐标签

根据用户的标签行为数据,通过SimpleTagBased 、 NormTagBased 及 TagBased-TFIDF 等方法,当用户A给某item打了标签T,进行以下4种类型的推荐:

  • 给用户A推荐系统中热门标签的item

  • item上有热门的标签T-hot,给用户A推荐其他打了T-hot标签的items

  • 用户自己经常对不同item打上标签T1等,对同样有T1标签的其他item进行推荐

  • 将以上几种进行融合。

2.2 Simple Tag-based
2.2.1 计算公式

s c o r e ( u , i ) = ∑ t u s e r _ t a g s [ u , t ] ∗ t a g _ i t e m s [ t , i ] score(u, i)=\sum_t{user\_tags[u, t] * tag\_items[t, i]} score(u,i)=tuser_tags[u,t]tag_items[t,i]

其中 user_tags[u,t] 表示【用户u使用标签t的次数】,tag_items[t, i] 表示【物品i被打上标签t的次数】

2.3 Norm Tag-based
2.3.1 计算公式

s c o r e ( u , i ) = ∑ t u s e r _ t a g s [ u , t ] u s e r _ t a g s [ u ] ∗ t a g _ i t e m s [ t , i ] t a g _ i t e m s [ t ] score(u,i)=\sum_t{\frac{user\_tags[u, t]}{user\_tags[u]} * \frac{tag\_items[t, i]}{tag\_items[t]}} score(u,i)=tuser_tags[u]user_tags[u,t]tag_items[t]tag_items[t,i]

其中 user_tags[u] 表示【用户u打标签的总次数】,tag_items[t] 表示【标签t被使用的总次数】

2.4 Tag-based TFIDF
2.4.1 计算公式

s c o r e ( u , i ) = ∑ t u s e r _ t a g s [ u , t ] l o g ( 1 + t a g _ u s e r s [ t ] ) ∗ t a g _ i t e m s [ t , i ] score(u,i)=\sum_t{\frac{user\_tags[u, t]}{log(1 + tag\_users[t])} * tag\_items[t, i]} score(u,i)=tlog(1+tag_users[t])user_tags[u,t]tag_items[t,i]

其中 tag_users[t] 表示【标签t被用户使用的总次数】

3. 代码实现

3.1 数据介绍

Delicious Bookmarks书签数据
105,000 bookmarks from 1867 users
下载地址:
https://files.grouplens.org/datasets/hetrec2011/hetrec2011-delicious-2k.zip

在这里插入图片描述
标准的userID -> bookmarkID -> tagID 记录行数据

3.2 基于pandas dataframe的标签推荐
3.2.1 python代码
# -*- coding: utf-8 -*-
"""
Created on 2020/11/18 13:54

@author: Irvinfaith

@email: Irvinfaith@hotmail.com
"""
import operator
import time
import random
import pandas as pd
import numpy as np

class TagBased(object):
    def __init__(self, data_path, sep='\t'):
        self.data_path = data_path
        self.sep = sep
        self.calc_result = {}
        self.table = self.__load_data(data_path, sep)

    def __load_data(self, data_path, sep):
        table = pd.read_table(data_path, sep=sep)
        return table

    def __calc_frequency(self, table):
        # user -> item
        user_item = table.groupby(by=['userID','bookmarkID'])['tagID'].count()
        # user -> tag
        user_tag = table.groupby(by=['userID', 'tagID'])['bookmarkID'].count()
        # tag -> item
        tag_item = table.groupby(by=['tagID', 'bookmarkID'])['userID'].count()
        # tag -> user
        tag_user = table.groupby(by=['tagID', 'userID'])['bookmarkID'].count()
        return {"user_item": user_item, "user_tag": user_tag, "tag_item": tag_item, "tag_user": tag_user}

    def train_test_split(self, ratio, seed):
        return self.__train_test_split(self.table, ratio, seed)

    def __train_test_split(self, table, ratio, seed):
        random.seed(seed)
        t1 = time.time()
        stratify_count = table.groupby(by='userID')['userID'].count()
        stratify_df = pd.DataFrame({"count":stratify_count})
        stratify_df['test_num'] = (stratify_df['count'] * ratio ).apply(int)
        test_id = []
        train_id = []
        """
        ==========================================
        # 方法 1:dataframe的iterrows遍历行
        # 10次执行平均耗时约 2.3秒
        ==========================================
        # for index, row in stratify_df.iterrows():
        #     tmp_ids = table[table['userID'] == index].index.tolist()
        #     tmp_test_id = random.sample(tmp_ids, row['test_num'])
        #     test_id.extend(tmp_test_id)
        #     train_id.extend(list(set(tmp_ids) -set(tmp_test_id)))

        ==========================================
        # 方法 2:series map和dataframe apply
        # 10次执行平均耗时约 2.2秒
        ==========================================
        按理来说,apply + map会比 iterrows 快很多,可能下面这个写法存储了较多的list,主要是为了方便查看拆分的结果,
        所以在遍历取数的时候会花费更多的时间。
        虽然apply+map方法只比iterrows快了0.1秒左右,但是在写法上我还是喜欢用apply+map。
        """
        stratify_df['ids'] = stratify_df.index.map(lambda x: table[table['userID'] == x].index.tolist())
        stratify_df['test_index'] = stratify_df.apply(lambda x: random.sample(x['ids'], x['test_num']), axis=1)
        stratify_df['train_index'] = stratify_df.apply(lambda x: list(set(x['ids']) - set(x['test_index'])), axis=1)
        stratify_df['test_index'].apply(lambda x: test_id.extend(x))
        stratify_df['train_index'].apply(lambda x: train_id.extend(x))
        train_data = table.iloc[train_id].reset_index(drop=True)
        test_data = table.iloc[test_id].reset_index(drop=True)
        print("Split train test dataset by stratification, time took: %.4f" % (time.time() - t1))
        return {"train_data": train_data, "test_data": test_data}

    def __calc_item_recommendation(self, user_id, user_item, user_tag, tag_item, n, method):
        marked_item = user_item[user_id].index
        recommend = {}
        # t1 = time.time()
        # user_id -> tag -> item -> count
        marked_tag = user_tag.loc[user_id]
        marked_tag_sum = marked_tag.values.sum()
        for tag_index, tag_count in marked_tag.iteritems():
            selected_item = tag_item.loc[tag_index]
            selected_item_sum = selected_item.values.sum()
            tag_selected_users_sum = self.calc_result['tag_user'].loc[tag_index].values.sum()
            for item_index, tag_item_count in selected_item.iteritems():
                if item_index in marked_item:
                    continue
                if item_index not in recommend:
                    if method == 'norm':
                        recommend[item_index] = (tag_count / marked_tag_sum) * (tag_item_count / selected_item_sum)
                    elif method == 'simple':
                        recommend[item_index] = tag_count * tag_item_count
                    elif method == 'tfidf':
                        recommend[item_index] = tag_count / np.log(1 + tag_selected_users_sum) * tag_item_count
                    else:
                        raise TypeError("Invalid method `{}`, `method` only support `norm`, `simple` and `tfidf`".format(method))
                else:
                    if method == 'norm':
                        recommend[item_index] += (tag_count / marked_tag_sum) * (tag_item_count / selected_item_sum)
                    elif method == 'simple':
                        recommend[item_index] += tag_count * tag_item_count
                    elif method == 'tfidf':
                        recommend[item_index] += tag_count / np.log(1 + tag_selected_users_sum) * tag_item_count
                    else:
                        raise TypeError("Invalid method `{}`, `method` only support `norm`, `simple` and `tfidf`".format(method))
        # print(time.time() - t1)
        sorted_recommend = sorted(recommend.items(), key=lambda x: (x[1]), reverse=True)[:n]
        return {user_id: dict(sorted_recommend)}

    def __eval(self, train_recommend, test_data):
        user_id = [i for i in train_recommend.keys()][0]
        test_data_item = test_data['bookmarkID'].unique()
        tp = len(set(test_data_item) & set(train_recommend[user_id].keys()))
        # for item_id in test_data_item:
        #     if item_id in train_recommend[user_id]:
        #         tp += 1
        return tp

    def fit(self, train_data):
        self.calc_result = self.__calc_frequency(train_data)

    def predict(self, user_id, n, method='simple'):
        return self.__calc_item_recommendation(user_id,
                                               self.calc_result['user_item'],
                                               self.calc_result['user_tag'],
                                               self.calc_result['tag_item'],
                                               n,
                                               method)

    def eval(self, n, test_data):
        t1 = time.time()
        test_data_user_id = test_data['userID'].unique()
        total_tp = 0
        tpfp = 0
        tpfn = 0
        check = []
        for user_id in test_data_user_id:
            train_recommend = self.predict(user_id, n)
            user_test_data = test_data[test_data['userID'] == user_id]
            total_tp += self.__eval(train_recommend, user_test_data)
            tpfn += len(user_test_data['bookmarkID'].unique())
            tpfp += n
            check.append((user_id, total_tp, tpfn, tpfp))
        recall = total_tp / tpfn
        precision = total_tp / tpfp
        print("Recall: %10.4f" % (recall * 100))
        print("Precision: %10.4f" % (precision * 100))
        print(time.time() - t1)
        return recall, precision, check

if __name__ == '__main__':
    file_path = "user_taggedbookmarks-timestamps.dat"
    tb = TagBased(file_path, '\t')
    train_test_data = tb.train_test_split(0.2, 88)
    tb.fit(train_test_data['train_data'])
    calc_result = tb.calc_result
    # 使用3种方法,预测用户id为8 的排序前10的item
    p1_simple = tb.predict(8, 10)
    p1_tf = tb.predict(8, 10, method='tfidf')
    p1_normal = tb.predict(8, 10, method='norm')

3.2.2 结果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 8
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Python中的文本相似度可以通过基于TF-IDF和余弦相似度算法实现。TF-IDF(Term Frequency-Inverse Document Frequency)是用于评估一个词语在一个文档中的重要程度的方法。 首先,我们需要使用Python中的文本处理库(如nltk)来对文本进行预处理,包括分词、去除停用词、词干化等。接下来,我们可以使用sklearn库中的TF-IDF向量化器来将文本转换为TF-IDF特征向量。 然后,我们可以使用余弦相似度算法来计算两个文本之间的相似度。余弦相似度是通过计算两个向量之间的夹角来度量它们的相似程度的。 以下是一个简单的示例代码: ```python import nltk from nltk.corpus import stopwords from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity def preprocess_text(text): # 分词 tokens = nltk.word_tokenize(text) # 去除停用词 stop_words = set(stopwords.words('english')) tokens = [token for token in tokens if token.lower() not in stop_words] # 词干化 stemmer = nltk.PorterStemmer() tokens = [stemmer.stem(token) for token in tokens] # 返回处理后的文本 return " ".join(tokens) def calculate_similarity(text1, text2): # 预处理文本 processed_text1 = preprocess_text(text1) processed_text2 = preprocess_text(text2) # 转换为TF-IDF特征向量 vectorizer = TfidfVectorizer() tfidf_matrix = vectorizer.fit_transform([processed_text1, processed_text2]) # 计算余弦相似度 cosine_sim = cosine_similarity(tfidf_matrix[0], tfidf_matrix[1]) # 返回相似度 return cosine_sim[0][0] text1 = "今天天气不错" text2 = "今天天气很好" similarity = calculate_similarity(text1, text2) print("文本1和文本2的相似度为:", similarity) ``` 在以上示例中,我们先对文本进行了预处理,并使用TF-IDF向量化器将其转换为特征向量。然后,我们使用余弦相似度算法计算了文本1和文本2之间的相似度,并输出结果。 这只是一个简单的示例,实际应用中可能需要更多的预处理步骤和参数调整来获得更好的结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值