推荐算法之TF-IDF

最近开始正式学习推荐系统相关的算法,相较于之前李航统计学习的部分,这一章和研究方向密切相关,所以在理论知识的学习之后,还要进行相关的代码实践。

推荐相关的理论知识,包含了很多之前学过的监督学习知识,但是也有很多无监督学习的东西,最明显的就是各种聚类算法,对于这些无监督学习的知识,还需进一步的学习。推荐系统算法还分几个大类,针对每个大类会做响应的理论说明和代码实践。

推荐算法种类

  • 基于人口统计学的推荐算法

    这一类是最简单的推荐算法了,它只是简单的根据系统用户的基本信息发现用户的相关程度,然后进行推荐,目前在大型系统中已经较少使用。

    直观一点,举个这种类别的推荐算法的例子,用户A选择了一个商品,并且用户B的属性特征和A非常相似,那么就可以给用户B推荐这个产品。

    此外,对于没有明确含义的用户信息(比如登录时间、地域等上下文信息),可以通过聚类等手段,给用户打上标签。用户信息标签画的过程一般也成为了用户画像,目前的用户画像,是根据大数据数据分析和挖掘以及用户的一些行为信息,抽象出一个用户的全貌,然后进行精准推送。

  • 基于规则的推荐

    这类算法常见的比如基于最多用户点击,最多用户浏览等,属于大众型的推荐方法,在目前的大数据时代并不主流。

  • 混合推荐

    这个类似我们机器学习中的集成学习,博才众长,通过多个推荐算法的结合,得到一个更好的推荐算法,起到三个臭皮匠顶一个诸葛亮的作用。比如通过建立多个推荐算法的模型,最后用投票法决定最终的推荐结果。混合推荐理论上不会比单一任何一种推荐算法差,但是使用混合推荐,算法复杂度就提高了,在实际应用中有使用,但是并没有单一的协调过滤推荐算法,比如逻辑回归之类的二分类推荐算法广泛。

  • 基于内容的推荐

    这一类一般依赖于自然语言处理NLP的一些知识,通过挖掘文本的TF-IDF特征向量,来得到用户的偏好,进而做推荐。这类推荐算法可以找到用户独特的小众喜好,而且还有较好的解释性。本文详细说明这一种类的推荐。

  • 协调过滤推荐

    本文后面要专门讲的内容。协调过滤是推荐算法中目前最主流的种类,花样繁多,在工业界已经有了很多广泛的应用。它的优点是不需要太多特定领域的知识,可以通过基于统计的机器学习算法来得到较好的推荐效果。最大的优点是工程上容易实现,可以方便应用到产品中。目前绝大多数实际应用的推荐算法都是协同过滤推荐算法。

特征工程

基本内容

  • 特征(feature):数据中抽取出来的对结果预测有用的信息。
  • 特征的个数就是数据的观测维度
  • 特征工程是使用专业背景知识和技巧处理数据,使得特征能在机器学习算法上发挥更好的作用的过程
  • 特征工程一般包括特征清洗(采样、清洗异常样本),特征处理和特征选择
  • 特征按照不同的数据类型分类,有不同的特征处理方法
    • 数值型
    • 类别型
    • 时间型
    • 统计型

归一化与离散化

  • 归一化

    特征与特征之间应该是平等的,区别应该体现在特征内部。

    例如房屋价格和住房面积的幅度是不同的,房屋价格可能在3000000~15000000(万)之间,而住房面积在40300(平方米)之间,那么明明是平等的两个特征,输入到相同的模型中后由于本身的幅值不同导致产生的效果不同,这是不合理的。

    根据以下公式进行归一化处理,即新的特征值等于就特征值,除以最大特征值差值,这样就将特征定位到同一水平上。

    \[\text { Feature }_{\text {new }}=\frac{\text { Feature }_{\text {old }}}{\text { Feature }_{\text {max }}-\text { Feature}_{min }} \]

  • 离散化

    对于一些特殊的运用环境,所需要的其实是分类结果,比如电商推荐项目,用户的喜好不一定是一个商品,而是一类商品。比如,对于用户需要按年龄分段

    离散化的两种方法:

    • 等步长
      简单但不一定有效。因为不同领域的商品数量不一定一样,那划分细则也不能直接定死。
    • 等频
      min > 25% > 75% > max。即根据不同的区间大小,进行百分比的划分。
    • 两种方法对比
      等频的离散化方法很精准,但需要每次都对数据分布进行一遍从新计算,因为昨天用户在淘宝上买东西的价格分布和今天不一定相同,因此昨天做等频的切分点可能并不适用,而线上最需要避免的就是不固定,需要现场计算,所以昨天训练出的模型今天不一定能使用等频不固定,但很精准,等步长是固定的,非常简单,因此两者在工业上都有应用.

类型特征处理

类别型数据本身没有大小关系,需要将它们编码为数字,但它们之间不能有预先设定的大小关系,因此既要做到公平,又要区分开它们,那么直接开辟多个空间。

比如红、黄、蓝这三种颜色,如果直接按数字分类为1、2、3,那么就存在大小关系,这就会影响结果。所以可以分类为100、010、001,这就消除了大小关系

基于内容的推荐算法

  • Content-based Recommendations(CB)根据推荐物品或内容的元数据,发现物品的相关性,再基于用户过去的喜好记录,为用户推荐相以的物品。

  • 通过抽取物品内在或者外在的特征值,实现相以度计算。

    • 比如一个电影,有导演、演员、用户标签UGC、用户评论、时长、风格等等,都可以算是特征
  • 将用户(user)个人信息的特征(基于喜好记录或是预设兴趣标签),和物品(item)的特征相匹配,就能得到用户对物品感兴趣的程度

    • 在一些电影、音乐、图书的社交网站有很成功的应用,有些网站还请专业的人员对物品进行基因编码/打标签(PGC)
  • 对于物品的特征提取一打标签(tag)

    • 专家标签(PGC)
    • 用户自定义标签(UGC)
    • 降维分析数据,提取隐语义标签(LFM)(后续协同过滤章节会有详细描述)
  • 对于文本信息的特征提取一一关键词(涉及到NLP,对整篇文章进行分词和特征提取,以及潜在语义分析)

    • 分词、语义处理和情感分析(NLP)
    • 潜在语义分析(LSA)

以下是基于内容的推荐算法的高层次结构图

uTools_1686223779213

基于UGC的推荐

  • 用户用标签来描述对物品的看法,所以用户生成标签(UGC)是联系用户和物品的纽带,也是反应用户兴趣的重要数据源

  • 一个用户标签行为的数据集一般由一个三元组(用户,物品,标签)的集合表示,其中一条记录(u,i,b)表示用户u给物品i打上了标签b

  • 一个最简单的算法

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

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

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

    • 所以用户U对物品ⅰ的兴趣公式为

      \[\mathrm{p}(\mathrm{u}, \mathrm{i})=\sum_{b} n_{u, b} n_{b, i} \\其中,n_{u,b}是用户u打过标签b的次数,n_{bi}是物品i被打过标签b的次数 \]

基于UGC推荐的问题

  • 简单算法中直接将用户打出标签的次数和物品得到的标签次数相乘,可以简单地表现出用户对物品某个特征的兴趣
  • 这种方法倾向于给热门标签(谁都会给的标签,如“大片”、“搞笑”等)、热门物品(打标签人数最多)比较大的权重,如果一个热门物品同时对应着热门标签,那它就会“霸榜”,推荐的个性化、新颖度就会降低

TF-IDF

理论

  1. 词频-逆文档频率(Term Frequency--Inverse Document Frequency,TF-lDF)是一种用于资讯检索与文本挖掘的常用加权技术

  2. TF-DF是一种统计方法,用以评估一个字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降 \(TFIDF=TF*IDF\)

  3. TF-DF的主要思想是:如果某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类TF-IDF加权的各种形式常被搜索引擎应用,作为文件与用户查询之间相关程度的度量或评级。

    简而言之就是,推荐的东西是你特别喜欢的,但是又不是那种大众都喜欢的,更加强调个性化推荐。

  4. 词频(Term Frequency,TF)指的是某一个给定的词语在该文件中出现的频率。这个数字是对词数的归一化,以防止偏向更长的文件。(同一个词语在长文件里可能会比短文件有更高的词数,而不管该词语重要与否)

    \[TF_{i,j}=\frac{n_{i,j}}{n_{*,j}} \\其中TF,表示词语i在文档j中出现的频率,n,:表示i在j中出现的次数,n:表示文档j的总词数 \]

  5. 逆向文件频率(Inverse Document Frequency,IDF)是一个词语普遍重要性的度量,某一特定词语的DF,可以由总文档数目除以包含该词语之文档的数目,再将得到的商取对数得到

    \[IDF_i=log(\frac{N+1}{N_i+1} ) \\其中IDF~i~表示词语ⅰ在文档集中的逆文档频率,N表示文档集中的文档总数,N~i~表示文档集中包含了词语ⅰ的文档数 \]

TF-IDF对基于UGC推荐的改进

  • 为了避免热门标签和热门物品获得更多的权重,我们需要对“热门”进行惩罚

  • 借鉴TF-DF的思想,以一个物品的所有标签作为“文档”,标签作为“词语”,从而计算标签的“词频”(在物品所有标签中的频率)和“逆文档频率”(在其它物品标
    签中普遍出现的频率)

  • 由于“物品i的所有标签”*:应该对标签权重没有影响,而“所有标签总数”N对于所有标签是一定的,所以这两项可以略去。在简单算法的基础上,直接加入对热门标签和热门物品的惩罚项:

    \[\mathrm{p}(\mathrm{u}, \mathrm{i})=\sum_{b} \frac{n_{u, b}}{\log{(1+n_{b}^{(u)})}} \frac{n_{b,i}}{\log{(1+n_{i}^{(u)})}} \\其中,n_{b}^{(u)}记录了标签b被多少个不同的用户使用过,n_{i}^{(u)}记录了物品i被多少个不同的用户打过标签 \\从而削弱了热门标签的影响,削弱了热门产品的影响 \]

代码

基于python实现

# 0. 引入依赖
import pandas as pd


def pre_data():
    # 1. 定义数据和预处理
    docA = "The cat sat on my bed"
    docB = "The dog sat on my knees"
    bowA = docA.split(" ")
    bowB = docB.split(" ")
    print(bowA)

    # 构建词库(union方法,可以去重两个列表中的重复元素)
    wordSet = set(bowA).union(set(bowB))

    # 2. 进行词数统计
    # 用统计字典来保存词出现的次数
    wordDictA = dict.fromkeys(wordSet, 0)
    wordDictB = dict.fromkeys(wordSet, 0)

    # 遍历文档,统计词数
    for word in bowA:
        wordDictA[word] += 1
    for word in bowB:
        wordDictB[word] += 1
    print(pd.DataFrame([wordDictA, wordDictB]))
    return wordDictA, wordDictB, bowA, bowB


# 3. 计算词频TF
def computeTF(wordDict, bow):
    # 用一个字典对象记录tf,把所有的词对应在bow文档里的tf都算出来
    tfDict = {}
    nbowCount = len(bow)

    for word, count in wordDict.items():
        tfDict[word] = count / nbowCount
    return tfDict


# 4. 计算逆文档频率idf
def computeIDF(wordDictList):
    # 用一个字典对象保存idf结果,每个词作为key,初始值为0
    idfDict = dict.fromkeys(wordDictList[0], 0)
    N = len(wordDictList)
    import math

    for wordDict in wordDictList:
        # 遍历字典中的每个词汇,统计Ni
        for word, count in wordDict.items():
            if count > 0:
                # 先把Ni增加1,存入到idfDict
                idfDict[word] += 1

    # 已经得到所有词汇i对应的Ni,现在根据公式把它替换成为idf值
    for word, ni in idfDict.items():
        idfDict[word] = math.log10((N + 1) / (ni + 1))

    return idfDict


# 5. 计算TF-IDF
def computeTFIDF(tf, idfs):
    tfidf = {}
    for word, tfval in tf.items():
        tfidf[word] = tfval * idfs[word]
    return tfidf


if __name__ == '__main__':
    wordDictA, wordDictB, bowA, bowB = pre_data()
    # 得到AB的词频和逆向词频
    tfA = computeTF(wordDictA, bowA)
    tfB = computeTF(wordDictB, bowB)
    idfs = computeIDF([wordDictA, wordDictB])

    # 计算tfidf
    tfidfA = computeTFIDF(tfA, idfs)
    tfidfB = computeTFIDF(tfB, idfs)
    print(pd.DataFrame([tfidfA, tfidfB]))

基于pytorch实现

from sklearn.feature_extraction.text import TfidfVectorizer
import numpy as np

# 定义文本数据
corpus = ["The cat sat on my bed",
          "The dog sat on my knees"]

# 创建一个TfidfVectorizer对象
vector = TfidfVectorizer()
# 对语料库进行向量化
tfidf = vector.fit_transform(corpus)
# 获取词汇表
word_arr = np.array(vector.get_feature_names_out()).reshape(1, -1)
# 将稀疏矩阵转化为密集矩阵并转化为字符串类型
weightarr = tfidf.todense().astype('str')
a = np.concatenate((word_arr, weightarr))
print(a)

代码结果

两种方法实现之后,发现结果存在一定的差异,可能需要对pytorch内部实现tfidf的源码进行更深一步的了解。

posted @ 2023-06-08 22:46  DoubleSails  阅读(679)  评论(0)    收藏  举报