ruby-on-rails - 如何在 Ruby on Rails 中使用 Redis 有效地获取两个哈希值的点积

标签 ruby-on-rails ruby hash redis dot-product

我在数据库的特征表中有一个这样的数据结构,称为token_vector(哈希):

Feature.find(1).token_vector = { "a" => 0.1, "b" => 0.2, "c" => 0.3 }

其中有 25 个功能。首先,我在 script/console 中将数据输入到 Redis 中:

REDIS.set(  "feature1",
            "#{ TokenVector.to_json Feature.find(1).token_vector }"
)
# ...
REDIS.set(  "feature25",
            "#{ TokenVector.to_json Feature.find(25).token_vector }"
)

TokenVector.to_json 首先将哈希值转换为 JSON 格式。 Redis 中存储的 25 个 JSON 哈希大约占用 8 MB。

我有一个方法,称为Analysis#locate。此方法采用两个 token_vector 之间的点积。哈希的点积的工作原理如下:

hash1 = { "a" => 1, "b" => 2, "c" => 3 }
hash2 = { "a" => 4, "b" => 5, "c" => 6, "d" => 7 }

哈希中的每个重叠键(本例中为 a、b 和 c,而不是 d)的值都成对相乘,然后相加。

hash1a 的值为 1,hash2a 的值为 4。 相乘这些得到1*4 = 4

hash1b 的值为 2,hash2b 的值为 5。 相乘这些得到2*5 = 10

hash1c 的值为 3,hash2c 的值为 6。 相乘这些得到 3*6 = 18

hash1d 的值为不存在,hash2d 的值为 7。在这种情况下,为第一个哈希设置d = 0。将它们相乘即可得到 0*7 = 0

现在将相乘的值相加。 4 + 10 + 18 + 0 = 32。这是 hash1 和 hash2 的点积。

Analysis.locate( hash1, hash2 ) # => 32

我有一个经常使用的方法,Analysis#topicize。该方法接受一个参数token_vector,它只是一个哈希值,与上面类似。 Analysis#topicize 采用 token_vector 与 25 个特征的 token_vectors 中每一个的点积,并创建这 25 个点积的新向量,称为feature_vectorfeature_vector 只是一个数组。代码如下:

def self.topicize token_vector

  feature_vector = FeatureVector.new

  feature_vector.push(
    locate( token_vector, TokenVector.from_json( REDIS.get "feature1" ) )
  )
  # ...
  feature_vector.push(
    locate( token_vector, TokenVector.from_json( REDIS.get "feature25" ) )
  )

  feature_vector

end

如您所见,它采用了我在上面输入到 Redis 中的 token_vector 和每个功能的 token_vector 的点积,并将该值推送到数组中。

我的问题是,每次调用该方法大约需要 18 秒。我是否误用了 Redis?我认为问题可能是我不应该将 Redis 数据加载到 Ruby 中。我是否应该向 Redis 发送数据 (token_vector) 并编写一个 Redis 函数来让它执行 dot_product 函数,而不是使用 Ruby 代码编写它?

最佳答案

您必须对其进行分析才能确定,但​​我怀疑您在序列化/反序列化 JSON 对象时浪费了大量时间。与其将 token_vector 转换为 JSON 字符串,为什么不直接将其放入 Redis 中,因为 Redis 有 its own hash type

REDIS.hmset "feature1",   *Feature.find(1).token_vector.flatten
# ...
REDIS.hmset "feature25",  *Feature.find(25).token_vector.flatten

Hash#flatten 的作用是将 { 'a' => 1, 'b' => 2 } 这样的哈希值转换为 [ 'a', 1, 'b', 2 ],然后我们使用 splat (*) 将数组的每个元素作为参数发送给 Redis#hmset (“hmset”中的“m”代表“多个”,如“一次设置多个哈希值”)。

然后,当您想将其取回时,请使用 Redis#hgetall,它会自动返回 Ruby 哈希值:

def self.topicize token_vector
  feature_vector = FeatureVector.new

  feature_vector.push locate( token_vector, REDIS.hgetall "feature1" )
  # ...
  feature_vector.push locate( token_vector, REDIS.hgetall "feature25" )

  feature_vector
end

但是!由于您只关心哈希中的值,而不是键,因此您可以使用 Redis#hvals 来简化事情,它只返回值的数组,而不是 hgetall.

您可能会花费大量周期的第二个地方是 locate,您尚未提供其源代码,但是有很多方法可以在 Ruby 中编写点积方法其中一些比其他的表现更好。 This ruby-talk thread涵盖了一些有值(value)的基础。其中一张海报指向NArray ,一个用 C 语言实现数值数组和向量的库。

如果我正确理解你的代码,它可以重新实现这样的东西(先决条件:gem install narray):

require 'narray'

def self.topicize token_vector
  # Make sure token_vector is an NVector
  token_vector  = NVector.to_na token_vector unless token_vector.is_a? NVector
  num_feats     = 25

  # Use Redis#multi to bundle every operation into one call.
  # It will return an array of all 25 features' token_vectors.
  feat_token_vecs = REDIS.multi do
    num_feats.times do |feat_idx|
      REDIS.hvals "feature#{feat_idx + 1}"
    end
  end 

  pad_to_len = token_vector.length

  # Get the dot product of each of those arrays with token_vector
  feat_token_vecs.map do |feat_vec|
    # Make sure the array is long enough by padding it out with zeroes (using
    # pad_arr, defined below). (Since Redis only returns strings we have to
    # convert each value with String#to_f first.)
    feat_vec = pad_arr feat_vec.map(&:to_f), pad_to_len

    # Then convert it to an NVector and do the dot product
    token_vector * NVector.to_na(feat_vec)

    # If we need to get a Ruby Array out instead of an NVector use #to_a, e.g.:
    # ( token_vector * NVector.to_na(feat_vec) ).to_a
  end
end

# Utility to pad out array with zeroes to desired size
def pad_arr arr, size
  arr.length < size ?
    arr + Array.new(size - arr.length, 0) : arr
end

希望对您有帮助!

关于ruby-on-rails - 如何在 Ruby on Rails 中使用 Redis 有效地获取两个哈希值的点积,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7544207/

相关文章:

ruby-on-rails - 一般建议和推荐的文件夹结构 - Sinatra

ruby - 比较 2 个数组,每个数组包含 200,000 多个值

ruby - 处理并发 ruby​​ 线程池中的异常

ruby-on-rails - ruby respond_to_missing?叫 super 还是不叫?

algorithm - 哈希表索引设计

mysql - 静态页面中的名称错误#home

ruby-on-rails - 如何将 aria-label 属性添加到 View 中的链接?

ruby-on-rails - 如何为收件箱消息设置上一条/下一条消息

algorithm - 生成/压缩唯一 key

PHP 5.5 password_* 函数重新散列