一个“朴素”的推荐系统
Introduction
在这篇文章中,我将使用朴素贝叶斯分类器(Naive Bayes Classifier)打造一个简单的个人推荐系统:给定一篇中文文章(article),返回我喜欢(like)这篇文章的概率。用数学的语言来描述的话,我们要计算的是
根据贝叶斯公式,
这样我们的问题就转化成计算\(P(like), P(dislike), P(article|like)\)和\(P(article|dislike)\)了。当然,在计算\(P(article|like)\)或者\(P(article|dislike)\)的时候,我们要将文章(article)量化成可以计算的特征。假设给定一篇文章,我们可以提取特征:
那么,
朴素贝叶斯分类器假设在确定喜欢或者不喜欢一篇文章的前提下,文章的特征\(x_1, \dots x_n\)是互相独立的。
在这个很强的假设下,
本文将使用Solidot的文章作为例子。
Features
我们首先要解决的问题是如何确定一篇文章的特征。让我们以Solidot的这篇文章为例:
硬件: 显卡价格开始下降了
过去一年的挖矿热导致显卡短缺,价格居高不下。但随着数字货币市场热度下降(或叫矿难),显卡开始不短缺了,价格也降下来了。今年 1 月,去百思买购买显卡的话,你会发现货架上几乎找不到几张,即使有价格也高得惊人, 8GB 的 Radeon 580 显卡售价 529.99 美元,店员还会告诉你错过了就买不到了;今天相同的显卡只需 419.99 美元,供应充足。除了 AMD 显卡价格下跌外,Nvidia 的显卡也存在类似现象。一名以太币矿工称,以太币的价格下跌导致了利润大幅减少,显卡短缺结束了。
最简单的想法是将文章的每个字作为一个特征,\(x_i\)表示某个字是否出现在文章中。本来在朴素贝叶斯中我们就假设特征之间没有关系,如果现在还将文章每个字单独拿出来作为特征就有点太过扭曲原文的意思啦。比如“过去”和“去过”这两个意思完全不一样的词语都含有“过”和“去”两个字,使用每个字作为特征的模型就无法区分这两个词语了。
为了使我们的模型不至于太过简单,我使用了文章中出现的词语作为特征。并且,我还将只有一个字的词语、标点符号和数字等去掉。
首先我们使用结巴分词对文章进行精确模式分词:
import jieba
text = "过去一年...短缺结束了。"
words = list(jieba.cut(text, cut_all=False))
print("/".join(words))
然后我们提取长度大于1的非数字词语。
def check(word):
word = word.strip()
try:
float(word)
return False
except Exception:
pass
return len(word) > 1
words = set(filter(check, words))
print(words)
上面这个这个集合中的词语就是文章的特征(features)了。
Model and Data
朴素贝叶斯分类器我在之前的一篇笔记Generative Model就讲过了。我们的模型需要训练以下参数:\(P(like), P(dislike), P(word|like)\)和\(P(word|dislike)\)。其中word
要遍历所有在数据中出现的词语。如果我们已经有数据了,这些参数都很容易计算出来,因为这些参数都是能通过统计得出来。比如\(P(word|like)\),我们只要统计喜欢的文章总数\(n\),以及在喜欢文章中包含word
这个词的文章数量\(w\),那么
其他参数都如此类似通过简单统计得到,这里就不一一赘述了。详情请看我的笔记Generative Model。
Solidot将文章分成了很多个子类别,比如“Linux”,“科学”,“科技”等等。我简单地将“科学”、“科技”、“移动”和“安全”这四个子类别下的文章标记成我喜欢,而将“苹果”、“硬件”、“软件”和“游戏”下的文章标记成我不喜欢的。然后我利用爬虫将Solidot上这8个类别从2013到2017年的文章全部抓取下来。
在Raspberry Pi3上使用10个线程耗时1个半小时,我一共抓取了11373
篇文章(有重复,某些文章会在多个类别出现),其中标记成喜欢的文章有8373
篇,标记成不喜欢的有3000
篇。经过6分钟的训练,我一共得到62167
个不重复的词语。
使用这个模型和训练数据集,我计算出我喜欢看上述样例文章“硬件: 显卡价格开始下降了”的概率:1.9263403800846358e-11
。这个结果跟我的训练集相符合:我将“硬件”类别的文章标记成不喜欢了。此时此刻,我对Solidot首页文章的喜欢概率如下。
Discussions
其实我对Solidot上面的文章都挺感兴趣的,上面只是为了简单获得数据而将一些文章标记成喜欢,另一些标记成不喜欢。所以以上计算出的概率并不能真实反映我的喜好。从另一个角度去思考,上面计算出来的概率其实是属于子类别“科学”、“科技”、“移动”或者“安全”的概率。