用python写一个简单的推荐系统

hmsc6242 8年前
   <h2>前言</h2>    <p>在上篇文章 <a href="/misc/goto?guid=4959673442466158538" rel="nofollow,noindex">豆瓣电影,电视剧DM实战</a> 中提及到,我和室友们产生了剧荒,萌生出要做一个个人用的推荐系统,解决剧荒的问题,经过一轮的死缠烂打,这个个人推荐系统终于成型了。</p>    <p>今天来分享一下心得,对此感兴趣的朋友可以自己对着写一个。</p>    <h2>传统推荐系统算法</h2>    <p>首先介绍一下传统的推荐系统方法,之所以叫它传统,是因为大部分学习资料上都是用这一个方法。</p>    <p>我们来假设有这么一个矩阵(用 python 的列表表示):</p>    <pre>    [# A B C D E      [2,0,0,4,4], #1      [5,5,5,3,3], #2      [2,4,2,1,2]  #3      ......    ]  </pre>    <p>矩阵的行代表用户,列表示物品,其交点表示用户对该物品的评分。</p>    <p>假设现在 <strong>用户1</strong> 需要选商品,推荐系统则假设其会选择并未选择过的商品,因此,系统会在第一行中寻找评分为0的物品,显然会找到 <strong>B</strong> 和 <strong>C</strong> 。这时,该怎么知道是推荐 <strong>B</strong> 还是 <strong>C</strong> 呢?(假设用户只需推荐一个),这时则需要计算B、C和用户以前选择过的物品(已评分)的相似度。</p>    <p>仅仅算出相似度还不够,因为你不能判断这到底是好的那一部分相似还是坏的部分相似。所以这时,我们需要 <strong>引入用户的评分作为相似度计算的权重</strong> , 评分X相似度 得到最后的得分(该得分会一直累加, <strong>则B的推荐得分会是B与A,D,E的相似得分的累加和</strong> )。这样一来,评分低物品的最后得分自然就低,评分高的物品自然得分就高,这时问题就简化成排序问题了。</p>    <p>显然,上述问题的核心在于如何计算相似度。</p>    <p>这里给出计算相似度的两种方法:</p>    <ul>     <li> <p>欧式距离法</p> <p>以 <strong>B</strong> 和 <strong>A</strong> 的相似度为例:</p> <p>similar = 1/sqrt((0-2)^2 + (5-5)^2 + (4-2)^2 ……) 最后B与A的相似得分还得乘上评分, score = similar * 2</p> </li>     <li> <p>余弦相似度</p> <p>$$costheta=frac{Acdot B}{||A||||B||}$$</p> <p>AB为两列向量,||A||表示A的2范数</p> <p>特别注意一点的是,cos的取值是-1~1,我们需要将其归一化,即把范围弄成在0~1上。于是相似度计算公司变成 0.5 + 0.5*cos</p> </li>    </ul>    <h2>少用户推荐系统的创新</h2>    <p>在上述的内容中,我们可以发现传统的方法有一个特出的问题, 传统的算法需要大量的用户评分,即矩阵的行数需要较多才能得出的结果才值得参考 。这一个需求咋看起来是没什么问题,也符合我们的逻辑,唯有数据量足够,我们才能找到较为准确的规律嘛。</p>    <p>但回到我的需求上来说,这可是一个明显的缺点,在前言我说明的需求上说过 <strong>这是一个给宿舍甚至是个人使用的推荐系统。</strong></p>    <p>也就是说:</p>    <ul>     <li> <p>我们无法提供大量数据。</p> </li>     <li> <p>我们很懒,我们最可能是告诉系统我从它的推荐中采纳了哪一部的电影,而不会去评分,我们可能告诉它质量是否可以接受,但不会像豆瓣用户那样给出0~10的准确分数。</p> </li>    </ul>    <p>因此,传统的推荐算法有很多不适合我需求的地方,但是看问题要看本质。 无非就是根据用户的特性,或者根据商品特性,进行与训练好的模型进行相似性比较。 抓住这些特点,我做了少少的"创新"</p>    <ul>     <li> <p>不基于用户的评分作相似度,而是用商品的 label 做标准</p> <p>现在很多商品尤其是音乐或者电影,都会具有自己的 label ,比如说 喜剧 , 悬疑 ,其次还有 主演 , 导演 等可以作为其特征。电商平台上也有诸如商品种类 衣服 , 女鞋 , 包包 ,等,而某些物品,例如衣服,那么衣服的 品牌 , size ,都可以作为用户的一个选择的特征。</p> </li>    </ul>    <ul>     <li> <p>用户模型是动态更新的</p> <p>这一点不难理解,如果一个用户长期使用使用该系统,那么他的选择中很可能已经覆盖了大量的label,这时基于label的推荐系统则很难区分该用户的喜好。这是我们有两个解决方法。第一个是允许用户自定义label,比如SF就可以自定义问题或文章的标签,这样增大了label的多样性。当然,这个解决方案只能算一个缓解的方案,要想彻底解决,我觉得需要给特征选定有效期。</p> <p>增加有效期后,用户的选择可以反应出一个时间段内的需求。 假设这样一个场景,一名用户准备去旅游了,他可能会大量浏览旅游用品的出售页面,例如一次性牙膏等,这时,就可以向该用户推荐出售旅行用品的网站了。而超过了特征的有效期,例如一周,这时用户已经旅游回来,因为特征已经无效,推荐系统不再推荐旅游用品(这样用户不会觉得莫名其妙。个人经历,现在某些网站就往往会出现明显已经超过我感兴趣时限的推荐),而是开始重新收集用户新一周浏览的特征,动态构建用户模型,推荐用户下一阶段他可能需要的物品</p> </li>    </ul>    <p>实现上述想法,在python中,我们可以这么做,实现如下字典</p>    <pre>  record = {      "labelName":(weight,time),      "labelName2":(weight,time)      ……  }    #labelName是标签名称,在该标签下有一个元组,元组的第一个字段是这个标签的权重。  #权重越大,表示用户越喜欢这个标签。  #第二个字段是创建该标签的起始时间</pre>    <p>在实现推荐时,则较为容易实现,给定 testList 。这时需要:</p>    <ul>     <li> <p>创建名 res 的空字典</p> </li>     <li> <p>遍历 testList ,每一个对象命名为 t</p> </li>     <li> <p>遍历 t 具有的 label ,根据 label 从 record 上获取信息。</p> </li>     <li> <p>同时获取当前时间 time2 ,如果 time2-time 超出了规定时限,则该标签的信息无效,忽略该 label ,同时删除 record 里面的对应的字段。</p> </li>     <li> <p>若该标签有效,则 t 的得分加1,并将t的下标 index 作为 key 假如到一个 res 中</p> </li>     <li> <p>遍历完成后,对 res 字典按 value 排序</p> </li>     <li> <p>最后,可以根据需要对排序结果进行访问。并入只获取最高的前5名。</p> </li>    </ul>    <p>这样,一个 <strong>适合少用户的推荐系统</strong> 就弄出来啦~</p>    <p>现在正在宿舍投入运行,至于效果如何可能要一段时间才知道了</p>    <h2>后话</h2>    <p><a href="/misc/goto?guid=4959673442555217507" rel="nofollow,noindex">github 地址</a></p>    <p>说明一下,github上 <strong>只是提供了一个实现了上述改进后思路的类</strong> recommend.py ,并不是一个成型的推荐系统,你可以下载后,根据这个类进行二次开发,比如:</p>    <ul>     <li> <p>利用flask框架包装成一个web应用</p> </li>     <li> <p>结合该类并利用SMTP协议,弄一个自动往邮箱发信息的脚本,推荐的电影信息</p> </li>     <li> <p>将类实例化,弄出简单的命令行应用</p> </li>    </ul>    <p>迟下我会上传一个使用falsk封装的一个简单的 webserver 去github,可以通过 web API 请求,返回 json 格式的电影信息。</p>    <p>如有错误,望指正。</p>    <p> </p>    <p>来自: <a href="/misc/goto?guid=4959673442635318251" rel="nofollow">https://segmentfault.com/a/1190000005152849</a></p>    <p> </p>