Ruby Ruby 实现的简易推荐系统 (译)

zhaowenchina · 2014年03月25日 · 最后由 ytwman 回复于 2014年03月28日 · 11996 次阅读
本帖已被管理员设置为精华贴

首发:http://zhaowen.me/blog/2014/03/25/ruby-recommendation-system/ 原文:Simple recommendation system written in Ruby

我在求职,于是昨天我回顾了一下以前做过的 Rails 项目,试图能给我的简历添上几笔。我找到了一个有意思的老项目,我在其中实现了推荐系统。但也没有很出彩,只是基于博客文章的标签做了推荐。我决定拿出其中的一些代码来写一篇文章。

推荐系统的算法基于 Jaccard 系数,也被称为 Jaccard 相似度系数。Jaccard 系数是由植物学家 Paul Jaccard 提出的,是用来体现样本相似性和差异性的数值。

运作原理

取出当前的 item(比如博客文章)和能够很好地描述它的属性(标签、分类或单词)。然后对其它每个 item 计算出它们交集和并集的商。数学过程可以表述为以下公式。

该方程的计算结果在 0 到 1 之间。你也能够方便地用以下公式来计算项目间的差异性。

Ruby 的实现示例

下面我将使用书名中的单词来推荐书籍。使用简单的 Book 类即可。

class Book < Struct.new(:title)

  # 长度大于2的不重复的单词的数组
  # 也可以是「标签」或「分类」的数组
  def words
    @words ||= self.title.gsub(/[a-zA-Z]{3,}/).map(&:downcase).uniq.sort
  end

end

BookRecommender 类使用当前的书籍和书籍数组进行初始化。recommendations 方法会循环数组并给每一个元素设定 jaccard_index 值,最后将书籍进行排序。

class BookRecommender

  def initialize book, books
    @book, @books = book, books
  end

  def recommendations

    # 计算每个元素的 jaccard_index 值并排序
    @books.map! do |this_book|

      # 运行中定义 jaccard_index 的取值 singleton 方法
      this_book.define_singleton_method(:jaccard_index) do
        @jaccard_index
      end

      # 还有赋值方法
      this_book.define_singleton_method("jaccard_index=") do |index|
        @jaccard_index = index || 0.0
      end

      # 计算样本的交集
      intersection = (@book.words & this_book.words).size
      # ... 和并集
      union = (@book.words | this_book.words).size

      # 将除法运算的结果赋值,如无法计算则捕捉异常并赋值为0
      this_book.jaccard_index = (intersection.to_f / union.to_f) rescue 0.0

      this_book

      # 排序
    end.sort_by { |book| 1 - book.jaccard_index }

  end

end

演示推荐过程:

# ...
# 读取数据并定义书籍数组
BOOKS = DATA.read.split("\n").map { |l| Book.new(l) }

# 定义当前书籍
current_book = Book.new("Ruby programming language")

# 进行推荐...
books = BookRecommender.new(current_book, BOOKS).recommendations

books.each do |book|
  puts "#{book.title} (#{'%.2f' % book.jaccard_index})"
end

__END__
Finding the best language for the job
Could Ruby save the day
Python will rock your world
Is Ruby better than Python
Programming in Ruby is fun
Python to the moon
Programming languages of the future

下面是名为「Ruby programming language」的书籍的输出结果,右边的数值就是 Jaccard 指数。

Programming in Ruby is fun (0.50)
Programming languages of the future (0.17)
Is Ruby better than Python (0.17)
Could Ruby save the day (0.14)
Finding the best language for the job (0.12)
Python to the moon (0.00)
Python will rock your world (0.00)

总结

以上就是纯粹基于 Ruby 的解决方案,源代码在我的 gist 上。我还写了使用标签的 PostgresSQL 版本。但要注意的是,当样本变得很大以后代码的执行速度也会变慢,因此不要每次操作 item 时都运行推荐,更好的方法是定义后台服务在后台进行推荐的计算。

希望你能够在自己的项目中用到这个方法,也请给我反馈。谢谢!:)

翻译的文章非常有趣,谢谢,加精!另外楼主的 Blog 很有料,学到很多新东西。

挺不错的

是不是用 R 语音来处理数据科学计算,然后把结果输出给 Ruby 更好,瞎猜的。哈哈

非常有意思啊,我个做测试的都看懂了。。。

不错,说不定以后就能用到。

学习了,很有用

不错,就是算法比较朴素

《R 和 Ruby 数据分析之旅》用 ruby 与 R 处理数据 其中有章 用音频和视频来计算的心率 很有意思

这都可以 牛肉

不久前在 hacker news 上看到原文,这么快就看到译文了,good job!

如果中文书名该怎么处理好?如果只是用 "中国文化史".split('') ["中", "国", "文", "化", "史"] 不晓得就中文而言,怎么来分词而不是分字? 而在用到这个 jaccard 上?

要用词法分析算法,简单贪心算法也 OK

#11 楼 @rco 这个问题很好,中文需要分词,然后才能用 jaccard 的,否则效果非常次

忽然发现这个太精妙了

end.sort_by { |book| 1 - book.jaccard_index }

いいですれ

#11 楼 @rco 对应的词库,先用书名去词库查证出所有词库匹配出来的词组放入数组中即可。

利用 @mojidong 所推介, 抄了 rmmseg-cpp 上的范例来替换成

require "rmmseg"
RMMSeg::Dictionary.load_dictionaries
class Book < Struct.new(:title)
  def words
    @words ||= split(title).select{|x| x.size > 3}
  end

  private
  def split(text)
    arr = []
    algor = RMMSeg::Algorithm.new(text)
    loop do
      tok = algor.next_token
      break if tok.nil?
      arr << tok.text
    end
    arr
  end
end

就可应用到中文书名。 另外,我实在不知道,该何处找 @ytwman 所讲的中文词库, 好在在 rmmseg-cpp 里有现成的字档及词档。 不知道一般中文词库,该到哪边可以载下来?

看到过一个叫 nlpir 的东东,用于中文分词的

21 楼 已删除
ouyang Ruby 的机器学习项目 提及了此话题。 12月08日 19:41
需要 登录 后方可回复, 如果你还没有账号请 注册新账号