1 基于内容相似度的推荐

:为尊重大佬,博文第一部分来自:https://blog.csdn.net/qq_32690999/article/details/77434381 ,这篇文章一定要读一下,非常赞!

1.1概念

基于内容相似度的推荐就是把与你喜欢看的新闻内容相似新闻推荐给你。基于内容的推荐算法的主要优势在于无冷启动问题,只要用户产生了初始的历史数据,就可以开始进行推荐的计算。而且随着用户的浏览记录数据的增加,这种推荐一般也会越来越准确。

这里有两个重要的关键点需要首先有个基本理解:

  1. 怎么知道用户喜欢看那些新闻?用户有历史的浏览记录,我们可以从这些用户历史浏览的新闻中”提取”能代表新闻主要内容的关键词,看哪些关键词出现的最多。比如可以有”手机“,”电脑游戏“,”发布会“等等关键词。或者,统计这些新闻所属的领域是哪些,比如国际政治、社会、民生、娱乐,找出用户看的新闻来源最多的几个领域。不过按这种方式判断用户兴趣容易太宽泛,哪怕是同一个领域下的新闻,可能也会差异很大。比如某用户可能喜欢A女星,而不喜欢B女星,而如果你只是认为该用户喜欢娱乐新闻,结果把B女星的新闻不停给用户推,那就肯定不好。而上述的关键词就可以比较好地规避这个问题。
  2. 怎么判断两个新闻内容相似?找到定义用户喜好的方法——关键词,那么我们自然而然就可以想到,**能不能提取出两个新闻的关键词,然后对比看它们两的关键词是不是相同的呢?**恩!思路正确,不过毕竟一个新闻可以有好几个关键词,要想全部一样,还是比较困难的。所以我们需要对两个新闻的关键词匹配程度做一个合理的量化。那么这时就需要用到TFIDF算法了(第二部分介绍)。TFIDF算法可以能够返回给我们一组属于某篇文本的”关键词-TFIDF值”的词数对,这些关键词最好地代表了这篇文本的核心内容,而这些关键词的相对于本篇文章的关键程度由它的TFIDF值量化。那我们现在也有了提取关键词并量化关键程度的方法,那么我们现在就可以来对比两篇文本的相似程度了。公式如下:
    在这里插入图片描述
举例:
		  刚抓进系统的两个新闻,分别提取出关键词与TFIDF值如下:
		  A新闻:“美女模特”:100,“女装”:80,“奔驰”:40
		  B新闻:“程序员”:100,“女装”:90,“编程”:30
		  两篇文章只有一个共同关键词“女装”,故相似度为:80*90=7200。

1.2用户喜好衡量:喜好关键词表

但是实际操作中,以上思路有一个问题了,用户以前看了辣么多新闻,每个新闻有好些个关键词,我们难道拿刚抓进系统的新闻跟它们一个个比对吗?为了解决这个问题,我们需要引入新的东西:喜好关键词表

其实很好理解:我们为每个用户在数据库里维持一个map,这个map里放的都是“用户喜好的关键词-喜好程度”这样的Key-Value对。而这个map最开始当然是空的,而从任意时刻开始,我们可以开始跟踪某用户的浏览行为,每当该用户新浏览了一条新闻,我们就把该新闻的“关键词-TFIDF值”“插入”到该用户的喜好关键词表中。当然这个“插入”要考虑关键词表里已经预先有了某预插入的关键词的情况,那么在这个基础上,我们可以将预插入的关键词的TFIDF值直接和词表里的值加起来。

当然,考虑到存储问题,我们可以为用户的喜好关键词表设定一个容量上限,比如最多1000个词,当然具体数值还是需要在实际运行过程根据效果做调整。

1.3兴趣迁移——衰减机制

我们大家会不会想到,我们的兴趣点可能是会随时间改变的呢?比如这段时间苹果出了一款新产品,我关注一下,但一个月后,我可能就完全不在意这件事了,但是可能苹果相关的关键词还一直在我的关键词表里,那会不会导致我依然收到相似的我已经不关心的新闻的推荐呢?也就是如何处理这种兴趣迁移问题呢?

为了解决这个问题,我们可以引入一个衰减机制,即让用户的关键词表中的每个关键词喜好程度都按一定周期保持衰减。考虑到不同词的TFIDF值可能差异已经在不同的数量级,我们考虑用指数衰减的形式来相对进行公平的衰减。即引入一个λ 系数,1>λ>0 ,我们每隔一段时间,对所有用户的所有关键词喜好程度进行λ的衰减,那么就完成了模拟用户兴趣迁移的过程。

当然,一直衰减下去,也会使得一些本来就已经完全不感兴趣的关键词可能衰减到了0.0000001了,还在衰减,还死皮赖脸地待在词表里占位置,那么自然而然,我们可以设置一个阈值L,规定对每个用户的每次衰减更新完成后,将词表里喜好值小于L的关键词直接清除。

2 TFIDF算法

2.1概念

TF-IDF(term frequency–inverse document frequency)是一种用于资讯检索与资讯探勘的常用加权技术。

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

TF-IDF加权的各种形式常被搜寻引擎应用,作为文件与用户查询之间相关程度的度量或评级。除了TF-IDF以外,因特网上的搜寻引擎还会使用基于连结分析的评级方法,以确定文件在搜寻结果中出现的顺序。

2.2核心思想

TFIDF的主要思想是:如果某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。TFIDF实际上是:TF * IDF

在一份给定的文件里,词频 (term frequency, TF) 指某一个给定的词语在该文件中出现的次数。这个数字通常会被归一化(分子一般小于分母区别于IDF),以防止它偏向长的文件。(同一个词语在长文件里可能会比短文件有更高的词频,而不管该词语重要与否。)

逆向文件频率 (inverse document frequency, IDF) 是一个词语普遍重要性的度量。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。**如果包含词条t的文档越少,也就是n越小,IDF越大,则说明词条t具有很好的类别区分能力。**如果某一类文档C中包含词条t的文档数为m,而其它类包含t的文档总数为k,显然所有包含t的文档数n=m+k,当m大的时候,n也大,按照IDF公式得到的IDF的值会小,就说明该词条t类别区分能力不强。

某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保留重要的词语。

2.3公式

对于某个特定文件中的词语ti 而言,它的 TF 可以表示为
在这里插入图片描述
其中 ni,j 是该词在文件 dj 中出现的次数,而分母则是文件 dj 中所有词汇出现的次数总和。如果用更直白的表达是来描述就是:
在这里插入图片描述
某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数即可,IDF表示为
在这里插入图片描述
其中,|D| 是语料库中的文件总数。 | { j:ti∈dj } | 表示包含词语 ti 的文件数目(即 ni,j≠0 的文件数目)。如果该词语不在语料库中,就会导致分母为零,因此一般情况下使用 1+| { j:ti∈dj } |。同样,如果用更直白的语言表示就是 :
在这里插入图片描述
在这里插入图片描述
某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保留重要的词语。

2.4应用

:为尊重大佬,这部分应用取自:http://www.cnblogs.com/biyeymyhjob/archive/2012/07/17/2595249.html ,推荐阅读。

一:有很多不同的数学公式可以用来计算TF-IDF。这边的例子以上述的数学公式来计算。词频 (TF) 是一词语出现的次数除以该文件的总词语数。假如一篇文件的总词语数是100个,而词语“母牛”出现了3次,那么“母牛”一词在该文件中的词频就是3/100=0.03。一个计算文件频率 (DF) 的方法是测定有多少份文件出现过“母牛”一词,然后除以文件集里包含的文件总数。所以,如果“母牛”一词在1,000份文件出现过,而文件总数是10,000,000份的话,其逆向文件频率就是 log(10,000,000 / 1,000)=4。最后的TF-IDF的分数为0.03 * 4=0.12。

二:根据关键字k1,k2,k3进行搜索结果的相关性就变成TF1IDF1 + TF2IDF2 + TF3IDF3。比如document1的term总量为1000,k1,k2,k3在document1出现的次数是100,200,50。包含了 k1, k2, k3的docuement总量分别是 1000, 10000,5000。document set的总量为10000。 TF1 = 100/1000 = 0.1 TF2 = 200/1000 = 0.2 TF3 = 50/1000 = 0.05 IDF1 = log(10000/1000) = log(10) = 2.3 IDF2 = log(10000/100000) = log(1) = 0; IDF3 = log(10000/5000) = log(2) = 0.69 这样关键字k1,k2,k3与docuement1的相关性= 0.12.3 + 0.20 + 0.050.69 = 0.2645 其中k1比k3的比重在document1要大,k2的比重是0.

三:在某个一共有一千词的网页中“原子能”、“的”和“应用”分别出现了 2 次、35 次 和 5 次,那么它们的词频就分别是 0.002、0.035 和 0.005。 我们将这三个数相加,其和 0.042 就是相应网页和查询“原子能的应用” 相关性的一个简单的度量。概括地讲,如果一个查询包含关键词 w1,w2,…,wN, 它们在一篇特定网页中的词频分别是: TF1, TF2, …, TFN。 (TF: term frequency)。 那么,这个查询和该网页的相关性就是:TF1 + TF2 + … + TFN。

读者可能已经发现了又一个漏洞。在上面的例子中,词“的”站了总词频的 80% 以上,而它对确定网页的主题几乎没有用。我们称这种词叫“应删除词”(Stopwords),也就是说在度量相关性是不应考虑它们的频率。在汉语中,应删除词还有“是”、“和”、“中”、“地”、“得”等等几十个。忽略这些应删除词后,上述网页的相似度就变成了0.007,其中“原子能”贡献了 0.002,“应用”贡献了 0.005。细心的读者可能还会发现另一个小的漏洞。在汉语中,“应用”是个很通用的词,而“原子能”是个很专业的词,后者在相关性排名中比前者重要。因此我们需要给汉语中的每一个词给一个权重,这个权重的设定必须满足下面两个条件:

  1. 一个词预测主题能力越强,权重就越大,反之,权重就越小。我们在网页中看到“原子能”这个词,或多或少地能了解网页的主题。我们看到“应用”一次,对主题基本上还是一无所知。因此,“原子能“的权重就应该比应用大。

  2. 应删除词的权重应该是零。我们很容易发现,如果一个关键词只在很少的网页中出现,我们通过它就容易锁定搜索目标,它的权重也就应该大。反之如果一个词在大量网页中出现,我们看到它仍然不很清楚要找什么内容,因此它应该小。概括地讲,假定一个关键词 w 在 Dw 个网页中出现过,那么 Dw 越大,w的权重越小,反之亦然。在信息检索中,使用最多的权重是“逆文本频率指数” (Inverse document frequency 缩写为IDF),它的公式为log(D/Dw)其中D是全部网页数。比如,我们假定中文网页数是D=10亿,应删除词“的”在所有的网页中都出现,即Dw=10亿,那么它的IDF=log(10亿/10亿)= log (1) = 0。假如专用词“原子能”在两百万个网页中出现,即Dw=200万,则它的权重IDF=log(500) =6.2。又假定通用词“应用”,出现在五亿个网页中,它的权重IDF = log(2)则只有 0.7。也就只说,在网页中找到一个“原子能”的比配相当于找到九个“应用”的匹配。利用 IDF,上述相关性计算个公式就由词频的简单求和变成了加权求和,即 TF1IDF1 + TF2IDF2 +… + TFN*IDFN。在上面的例子中,该网页和“原子能的应用”的相关性为 0.0161,其中“原子能”贡献了 0.0126,而“应用”只贡献了0.0035。这个比例和我们的直觉比较一致了。

2.5python实现

2.5.1 数据预处理

import nltk
import math
import string
from nltk.corpus import stopwords
from collections import Counter
from nltk.stem.porter import *
from sklearn.feature_extraction.text import TfidfVectorizer

text1 = "Python is a 2000 made-for-TV horror movie directed by Richard \
Clabaugh. The film features several cult favorite actors, including William \
Zabka of The Karate Kid fame, Wil Wheaton, Casper Van Dien, Jenny McCarthy, \
Keith Coogan, Robert Englund (best known for his role as Freddy Krueger in the \
A Nightmare on Elm Street series of films), Dana Barron, David Bowe, and Sean \
Whalen. The film concerns a genetically engineered snake, a python, that \
escapes and unleashes itself on a small town. It includes the classic final\
girl scenario evident in films like Friday the 13th. It was filmed in Los Angeles, \
 California and Malibu, California. Python was followed by two sequels: Python \
 II (2002) and Boa vs. Python (2004), both also made-for-TV films."

text2 = "Python, from the Greek word (πύθων/πύθωνας), is a genus of \
nonvenomous pythons[2] found in Africa and Asia. Currently, 7 species are \
recognised.[2] A member of this genus, P. reticulatus, is among the longest \
snakes known."

text3 = "The Colt Python is a .357 Magnum caliber revolver formerly \
manufactured by Colt's Manufacturing Company of Hartford, Connecticut. \
It is sometimes referred to as a \"Combat Magnum\".[1] It was first introduced \
in 1955, the same year as Smith & Wesson's M29 .44 Magnum. The now discontinued \
Colt Python targeted the premium revolver market segment. Some firearm \
collectors and writers such as Jeff Cooper, Ian V. Hogg, Chuck Hawks, Leroy \
Thompson, Renee Smeets and Martin Dougherty have described the Python as the \
finest production revolver ever made."

构建分词函数:

def get_tokens(text):
    """
    首先我们来做分词,其中比较值得注意的地方是我们设法剔除了其中的标点符号(显然,标点符号不应该成为最终的关键词)。

    """
    lowers = text.lower()                                                              # 转换字符串中所有大写字符为小写。
    print('first step:',lowers,'\n')
    remove_punctuation_map = dict((ord(char), None) for char in string.punctuation)   # 即为去掉标点符号的 string,而line本身没有变化
    no_punctuation = lowers.translate(remove_punctuation_map)
    print('second step:',no_punctuation,'\n')
    tokens = nltk.word_tokenize(no_punctuation)                                         # 将成段落的语料转化为了一个以单词为单位的Python List对象完成分词
    print('third step:',tokens,'\n')
    return tokens

测试分词函数:

tokens = get_tokens(text1)
count = Counter(tokens)                                                                 # 统计每个单词出现的次数
print (count.most_common(10))                                                           

结果:
在这里插入图片描述
词干抽取:

def stem_tokens(tokens, stemmer):
    """
    进行对已经分完词进行词干抽取
    """
    stemmed = []
    for item in tokens:
        stemmed.append(stemmer.stem(item))
    return stemmed

测试词干抽取:

nltk.download('punkt')                                                                   # 分词
nltk.download('stopwords')                                                               # 停用词
tokens = get_tokens(text1)
filtered = [w for w in tokens if not w in stopwords.words('english')]                  # 去掉停用词
print('filtered\t\t\t',filtered,'\n')
stemmer = PorterStemmer()
stemmed = stem_tokens(filtered, stemmer)
print('stemmed\t\t\t',stemmed,'\n')
count = Counter(stemmed)
print('count\t\t\t',count)

结果:
在这里插入图片描述

2.5.2TFIDF实现

def tf(word, count):
    """
    计算TF
    """
    return count[word] / sum(count.values())

def n_containing(word, count_list):
    """
    计算包含word的文件数
    """
    return sum(1 for count in count_list if word in count)

def idf(word, count_list):
    """
    计算IDF
    """
    return math.log(len(count_list) / (1 + n_containing(word, count_list)))

def tfidf(word, count, count_list):
    """
    计算TFIDF
    """
    return tf(word, count) * idf(word, count_list)

实验:

def count_term(text): 
    tokens = get_tokens(text) 
    filtered = [w for w in tokens if not w in stopwords.words('english')] 
    stemmer = PorterStemmer() 
    stemmed = stem_tokens(filtered, stemmer) 
    count = Counter(stemmed) 
    return count

def main(): 
    texts = [text1, text2, text3] 
    countlist = []
    for text in texts: 
        countlist.append(count_term(text)) 
    for i, count in enumerate(countlist): 
        print("Top words in document {}".format(i + 1)) 
        scores = {word: tfidf(word, count, countlist) for word in count} 
        sorted_words = sorted(scores.items(), key = lambda x: x[1], reverse=True) 
        for word, score in sorted_words[:5]: 
            print("\tWord: {}, TF-IDF: {}".format(word, round(score, 5)))

if __name__ == "__main__": 
    main()

结果:
在这里插入图片描述

2.5.3 sklearn实现TFIDF

查阅文档吧

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐