我在数据库的特征表中有一个这样的数据结构,称为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)的值都成对相乘,然后相加。
hash1
中 a
的值为 1,hash2
中 a
的值为 4。 相乘这些得到1*4 = 4
。
hash1
中 b
的值为 2,hash2
中 b
的值为 5。 相乘这些得到2*5 = 10
。
hash1
中 c
的值为 3,hash2
中 c
的值为 6。 相乘这些得到 3*6 = 18
。
hash1
中 d
的值为不存在,hash2
中 d
的值为 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_vector
。 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
如您所见,它采用了我在上面输入到 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/