2

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

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

其中有 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 }"
)

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)将它们的值成对相乘,然后相加。

ain的值为hash11,ain的hash2值为 4。将它们相乘得到1*4 = 4

bin的值为hash12,bin的hash2值为 5。将它们相乘得到2*5 = 10

cin的值为hash13,cin的hash2值为 6。将它们相乘得到3*6 = 18

din的值hash1不存在,din的hash2值为 7。在这种情况下,d = 0为第一个散列设置。将这些相乘得到0*7 = 0.

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

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

我有一个经常使用的方法,Analysis#topicize. 这个方法接受一个参数,token_vector,它只是一个哈希,类似于上面。取25 个特征中的每一个Analysis#topicize的点积,并创建这 25 个点积的新向量,称为。A只是一个数组。代码如下所示:token_vectortoken_vectorsfeature_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

如您所见,它采用我在上面输入 Redistoken_vector的每个特征的点积token_vector,并将值推送到数组中。

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

4

2 回答 2

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

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

然后当你想把它取回 useRedis#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

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

第二个你可能会花费很多周期的地方是locate,你没有提供源代码,但是有很多方法可以在 Ruby 中编写点积方法,其中一些比其他的性能更高。这个 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

希望这会有所帮助!

于 2011-09-25T10:11:39.053 回答
0

这并不是真正的答案,只是对我之前评论的跟进,因为这可能不适合评论。看起来 Hash/TokenVector 问题可能不是唯一的问题。我愿意:

token_vector = Feature.find(1).token_vector
Analysis.locate( token_vector, TokenVector[ REDIS.hgetall( "feature1" ) ] )

并得到这个错误:

TypeError: String can't be coerced into Float
from /Users/RedApple/S/lib/analysis/vectors.rb:26:in `*'
from /Users/RedApple/S/lib/analysis/vectors.rb:26:in `block in dot'
from /Users/RedApple/S/lib/analysis/vectors.rb:24:in `each'
from /Users/RedApple/S/lib/analysis/vectors.rb:24:in `inject'
from /Users/RedApple/S/lib/analysis/vectors.rb:24:in `dot'
from /Users/RedApple/S/lib/analysis/analysis.rb:223:in `locate'
from (irb):6
from /Users/RedApple/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16:in `<main>'

分析#locate 看起来像这样:

def self.locate vector1, vector2
  vector1.dot vector2
end

这是 analysis/vectors.rb 第 23-28 行的相关部分,TokenVector#dot 方法:

def dot vector
  inject 0 do |product,item|
    axis, value = item
    product + value * ( vector[axis] || 0 )
  end
end

我不确定问题出在哪里。

于 2011-09-26T00:20:53.717 回答