在文章 学习协同过滤推荐 \w 100行Python代码 中,介绍了基于物品的协同过滤推荐,根据 user-item 评分矩阵,找出与给定 item 评分最接近的物品,作为推荐结果。
在本文中,把书籍名称看作单词,以用户喜欢的书籍看作句子,利用 word2vec 模型构建了一个书籍的向量空间。对给定书籍,找出与其距离最近的书籍,作为推荐结果。
本文用 Python 60 行代码实现了一个 Demo,得到每本书籍在向量空间的表示,输出基于书籍的协同过滤推荐结果。
word2vec,是 Google 的 Mikolov 等人在 NIPS2013 发表的论文 Efficient Estimation of Word Representations in Vector Space 提出的模型,包括 Skip-gram 和 CBOW (Continuous Bag-of-Words) ,如下图所示。
word2vec 在没有词性标注的情况下,能够从原始语料学习到单词的向量表示,比较单词间的语义、句法的相似性。例如:
vector("King") - vector("Man") + vector("Woman") ≈ vector("Queen")
利用模型的特点,我们可以把书籍看作单词,把每个用户喜欢的书籍序列看作句子,用这些数据训练 word2vec 模型,得到每本书籍的向量表示。在推荐时,根据用户在读书籍 ,在向量空间中找到与其距离相近的书籍作为推荐。
与 Item-CF 相比,word2vec 在推荐时更加灵活,书籍向量可以相加、相减,能够更灵活地满足个性化推荐需求。例如,根据用户 amy 最近喜欢的《浪潮之巅》,考虑到他以前喜欢《从0到1》,不喜欢《寻路中国》,可以这样计算推荐书籍:
vector("浪潮之巅") + vector("从0到1") - vector("寻路中国") ≈ vector("推荐书籍")
假设窗口=5,对输入单词向量求和
结果 xxx 进入输出层
在 CBOW 模型中,输出层是 hierarchical softmax,以霍夫曼树的叶子节点表示输出单词。用霍夫曼树使得叶子节点的输出概率,累加和等于一。
对第 jjj 个非叶子节点,有待训练的参数 θj\theta_jθj,输入纯量 xxx选择左、右分支的概率
叶子节点输出纯量
x⋅sign(x,θj)
对每个进入输出层的纯量 xxx,按以上方法递归地求得输出
记
为
那么有
于是 θ\thetaθ 的更新公式
其中,η\etaη 是学习速率
同理得 x的梯度公式
因为 xwx_wxw 是多个单词向量 vvv 的累加,所以每轮迭代每个单词向量 vvv 的更新方法
输入为一个单词向量,经过 continuous projection layer,输入到 log-linear classifier,得到前 n、后 n 个单词。
用同样方法,得到损失函数、损失函梯度函数、更新函数后,用 随机梯度下降(Stochastic Gradien Descent)算法得到估计参数 θ\thetaθ 、单词向量 v(w)v(w)v(w)。
本文借助 gensim 包,完成上述的训练过程。
准备 user_prefs.txt
:
david 百年孤独
david 霍乱时期的爱情
david 从0到1
andy 霍乱时期的爱情
jack 浪潮之巅
jack 失控
jack 创业维艰
michale 寻路中国:从乡村到工厂的自驾之旅
michale 背包十年:我的职业是旅行
ann 活着
ann 霍乱时期的爱情
ann 迟到的间隔年
joel 巨人的陨落:世纪三部曲
joel 中国历代政治得失
joel 人类简史:从动物到上帝
joel 失控
jim 背包十年:我的职业是旅行
jim 迟到的间隔年
ray 霍乱时期的爱情
ray 迟到的间隔年
ray 枪炮、病菌与钢铁:人类社会的命运
安装 gensim 包
brew install pip3
pip3 install gesim
训练模型
#!/usr/sbin/python3
#-*- encoding:utf-8 -*-
from gensim.models.word2vec import *
model_file = 'word2vec.model'
with open('user_prefs.txt') as f:
prefs_str = ''.join(f.readlines())
# {'andy': {'霍乱时期的爱情': 1},...}
def read_prefs(prefs_str):
prefs = {}
for line in prefs_str.split('\n'):
parts = line.rstrip().split()
if len(parts) == 2:
userId, itemId = parts
prefs.setdefault(userId, {})
prefs[userId].update({itemId:1})
return prefs
prefs = read_prefs(prefs_str)
def sents_from_prefs(prefs):
sents = []
for v in prefs.values():
sent = ''
for b in v.keys():
sent += ' ' + b.replace(' ', '')
sents.append(sent)
return sents
def flatMap(vocab):
ret = []
for i in vocab:
if type(i) == type('a'):
ret.append(i)
elif type(i) == type([]):
for j in i:
ret.append(j)
return ret
def calc_item_cf():
sents = sents_from_prefs(prefs)
vocab = [s.split() for s in sents]
model = Word2Vec(vocab, size=100, window=5, min_count=1, workers=4)
model.save_word2vec_format(model_file, binary=False)
model = Word2Vec.load_word2vec_format(model_file, binary=False)
print('基于书籍的 word2vec 协同过滤推荐')
for item in flatMap(vocab):
print('\n根据 %s 推荐:' % item)
for item_score in model.most_similar(positive=[item]):
item, score = item_score
print('\t%s %.2f' % (item, score))
calc_item_cf()
书籍的向量表示保存在 word2vec.model
文件:
14 100
霍乱时期的爱情 -0.000769 -0.003323 -0.001202 -0.000913 0.003399 -0.001411 -0.003035 0.002229 0.004558 0.001640 ...
迟到的间隔年 -0.004531 0.004115 -0.004905 0.004209 -0.002952 0.002481 0.001582 ...
推荐结果:
基于书籍的 word2vec 协同过滤推荐
根据 背包十年:我的职业是旅行 推荐:
迟到的间隔年 0.22
人类简史:从动物到上帝 0.11
失控 0.09
枪炮、病菌与钢铁:人类社会的命运 0.09
浪潮之巅 0.07
霍乱时期的爱情 0.07
中国历代政治得失 0.06
寻路中国:从乡村到工厂的自驾之旅 0.05
巨人的陨落:世纪三部曲 0.03
活着 0.03
根据 枪炮、病菌与钢铁:人类社会的命运 推荐:
百年孤独 0.20
迟到的间隔年 0.16
中国历代政治得失 0.09
背包十年:我的职业是旅行 0.09
失控 0.06
巨人的陨落:世纪三部曲 0.06
创业维艰 -0.02
活着 -0.03
浪潮之巅 -0.03
从0到1 -0.09
根据 从0到1 推荐:
百年孤独 0.08
浪潮之巅 0.06
中国历代政治得失 0.03
寻路中国:从乡村到工厂的自驾之旅 0.03
创业维艰 -0.03
迟到的间隔年 -0.07
失控 -0.07
巨人的陨落:世纪三部曲 -0.07
霍乱时期的爱情 -0.08
背包十年:我的职业是旅行 -0.09
根据 寻路中国:从乡村到工厂的自驾之旅 推荐:
活着 0.14
背包十年:我的职业是旅行 0.05
人类简史:从动物到上帝 0.03
从0到1 0.03
霍乱时期的爱情 0.03
创业维艰 -0.03
中国历代政治得失 -0.03
百年孤独 -0.07
浪潮之巅 -0.10
迟到的间隔年 -0.10
根据 霍乱时期的爱情 推荐:
人类简史:从动物到上帝 0.20
巨人的陨落:世纪三部曲 0.17
中国历代政治得失 0.07
背包十年:我的职业是旅行 0.07
寻路中国:从乡村到工厂的自驾之旅 0.03
活着 0.02
失控 -0.01
创业维艰 -0.07
从0到1 -0.08
浪潮之巅 -0.10
根据 创业维艰 推荐:
浪潮之巅 0.14
活着 0.11
中国历代政治得失 0.01
巨人的陨落:世纪三部曲 -0.01
百年孤独 -0.02
枪炮、病菌与钢铁:人类社会的命运 -0.02
迟到的间隔年 -0.03
从0到1 -0.03
寻路中国:从乡村到工厂的自驾之旅 -0.03
失控 -0.05
根据 中国历代政治得失 推荐:
活着 0.19
枪炮、病菌与钢铁:人类社会的命运 0.09
霍乱时期的爱情 0.07
失控 0.06
背包十年:我的职业是旅行 0.06
百年孤独 0.04
迟到的间隔年 0.04
从0到1 0.03
创业维艰 0.01
巨人的陨落:世纪三部曲 -0.01
根据 巨人的陨落:世纪三部曲 推荐:
人类简史:从动物到上帝 0.17
霍乱时期的爱情 0.17
浪潮之巅 0.13
迟到的间隔年 0.07
枪炮、病菌与钢铁:人类社会的命运 0.06
背包十年:我的职业是旅行 0.03
失控 0.01
创业维艰 -0.01
中国历代政治得失 -0.01
从0到1 -0.07
根据 失控 推荐:
背包十年:我的职业是旅行 0.09
迟到的间隔年 0.07
中国历代政治得失 0.06
枪炮、病菌与钢铁:人类社会的命运 0.06
浪潮之巅 0.03
人类简史:从动物到上帝 0.02
巨人的陨落:世纪三部曲 0.01
霍乱时期的爱情 -0.01
百年孤独 -0.04
创业维艰 -0.05
根据 人类简史:从动物到上帝 推荐:
霍乱时期的爱情 0.20
巨人的陨落:世纪三部曲 0.17
背包十年:我的职业是旅行 0.11
寻路中国:从乡村到工厂的自驾之旅 0.03
失控 0.02
浪潮之巅 -0.01
迟到的间隔年 -0.05
百年孤独 -0.07
中国历代政治得失 -0.09
从0到1 -0.09
根据 活着 推荐:
中国历代政治得失 0.19
寻路中国:从乡村到工厂的自驾之旅 0.14
创业维艰 0.11
背包十年:我的职业是旅行 0.03
霍乱时期的爱情 0.02
浪潮之巅 0.02
枪炮、病菌与钢铁:人类社会的命运 -0.03
迟到的间隔年 -0.05
失控 -0.05
百年孤独 -0.07
根据 浪潮之巅 推荐:
创业维艰 0.14
巨人的陨落:世纪三部曲 0.13
背包十年:我的职业是旅行 0.07
从0到1 0.06
失控 0.03
活着 0.02
人类简史:从动物到上帝 -0.01
枪炮、病菌与钢铁:人类社会的命运 -0.03
中国历代政治得失 -0.08
百年孤独 -0.09
根据 迟到的间隔年 推荐:
背包十年:我的职业是旅行 0.22
枪炮、病菌与钢铁:人类社会的命运 0.16
百年孤独 0.10
失控 0.07
巨人的陨落:世纪三部曲 0.07
中国历代政治得失 0.04
创业维艰 -0.03
活着 -0.05
人类简史:从动物到上帝 -0.05
从0到1 -0.07
根据 百年孤独 推荐:
枪炮、病菌与钢铁:人类社会的命运 0.20
迟到的间隔年 0.10
从0到1 0.08
中国历代政治得失 0.04
创业维艰 -0.02
失控 -0.04
寻路中国:从乡村到工厂的自驾之旅 -0.07
活着 -0.07
人类简史:从动物到上帝 -0.07
浪潮之巅 -0.09
https://github.com/liuslevis/CFDemo 完整源码
http://www.shareditor.com/blogshow/?blogId=100 自己动手做聊天机器人 二十五-google的文本挖掘深度学习工具word2vec的实现原理