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

Eri*_*ric 2 ruby hash ruby-on-rails redis dot-product

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

Feature.find(1).token_vector = { "a" => 0.1, "b" => 0.2, "c" => 0.3 }
Run Code Online (Sandbox Code Playgroud)

这些功能有25个.首先,我将数据输入Redis,其中包括script/console:

REDIS.set(  "feature1",
            "#{ TokenVector.to_json Feature.find(1).token_vector }"
)
# ...
REDIS.set(  "feature25",
            "#{ TokenVector.to_json Feature.find(25).token_vector }"
)
Run Code Online (Sandbox Code Playgroud)

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

我有一个叫做的方法Analysis#locate.此方法采用两个token_vectors之间的点积.哈希的点积如下:

hash1 = { "a" => 1, "b" => 2, "c" => 3 }
hash2 = { "a" => 4, "b" => 5, "c" => 6, "d" => 7 }
Run Code Online (Sandbox Code Playgroud)

散列中的每个重叠键(在这种情况下为a,b和c,而不是d)将它们的值成对地相乘,然后相加.

对于价值ahash1是1,该值ahash2是4乘这些获得1*4 = 4.

对于价值bhash1是2,该值bhash2为5乘这些获得2*5 = 10.

对于该值chash13,对于价值chash2是6乘这些获得3*6 = 18.

该值dhash1不存在,该值dhash2是7.在这种情况下,设定d = 0为所述第一散列值.将这些乘以得到0*7 = 0.

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

Analysis.locate( hash1, hash2 ) # => 32
Run Code Online (Sandbox Code Playgroud)

我有一个经常使用的方法,Analysis#topicize.此方法接受一个参数,token_vector它只是一个哈希,类似于上面.Analysis#topicizetoken_vector25个特征中的每个特征的点积token_vectors,并创建这些25个点产品的新矢量,称为feature_vector.A feature_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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它采用了我在上面输入Redis 的点积token_vector和每个特征token_vector,并将值推送到数组中.

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

Jor*_*ing 5

您必须对其进行分析才能确定,但​​我怀疑您在序列化/反序列化JSON对象时会浪费大量时间.而不是token_vector变成JSON字符串,为什么不将它直接放入Redis,因为Redis有自己的哈希类型

REDIS.hmset "feature1",   *Feature.find(1).token_vector.flatten
# ...
REDIS.hmset "feature25",  *Feature.find(25).token_vector.flatten
Run Code Online (Sandbox Code Playgroud)

什么Hash#flatten是将哈希变成类似{ 'a' => 1, 'b' => 2 }的数组[ 'a', 1, 'b', 2 ],然后我们使用splat(*)将数组的每个元素作为参数发送到Redis#hmset("hmset"中的"m"表示"倍数",如"set multiple"哈希值一次").

然后当你想让它恢复使用时Redis#hgetall,会自动返回Ruby Hash:

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
Run Code Online (Sandbox Code Playgroud)

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

你可能花费很多周期的第二个地方是locate,你没有提供源代码,但是有很多方法可以在Ruby中编写一个dot产品方法,其中一些方法比其他方法更有效.这个ruby-talk主题涵盖了一些有价值的理由.其中一张海报指向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
Run Code Online (Sandbox Code Playgroud)

希望有用!