multi-armed-bandit 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 366b2c8ed586d54d3710a4124f3e0d27598ee162
4
+ data.tar.gz: 21592d2c199e013b35e0def04742b9e857aa0de7
5
+ SHA512:
6
+ metadata.gz: cdbabf315694cc7a90ffc522a45ce060cffbff28cbefa90cca151b666c7685ea0d12dab07d8827d4e4b6f3576084430b2df1d64f4a7b74f917bd2d478f0b8e0a
7
+ data.tar.gz: 515b9877b81c8a7c85d154baa8bab15857458a12f0745d5df01c8d4f3fe191f0e114cbf5dc71f40d20a72879cf60f9259378d82646d30f1ebe6b6e4fe08e4db7
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 David Dai
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # MultiArmedBandit
2
+ A Redis backed multi-armed bandit library. Currently Thompson Sampling is implemented.
3
+
4
+ ## Usage
5
+
6
+ ```ruby
7
+ require 'multi-armed-bandit'
8
+ mab = MultiArmedBandit::ThompsonSampling.new Redis.new(:port => 6379), 'colors'
9
+ mab.create! ['red', 'green', 'blue'], :alpha => 10, :beta => 10
10
+ mab.draw # => 'red'
11
+ mab.draw # => 'green'
12
+ mab.draw # => 'blue'
13
+ mab.draw_multi(3) # => ['red', blue', 'green']
14
+
15
+ mab.put 'yellow', :alpha => 5, :beta => 5
16
+ mab.draw # => 'yellow'
17
+ mab.update_success('red')
18
+ mab.update_success('green', 2)
19
+ mab.draw # => 'green'
20
+ mab.remove 'green'
21
+ mab.draw # => 'red'
22
+ mab.disable 'red'
23
+ mab.draw # => 'yellow'
24
+ mab.enable 'red'
25
+ mab.draw # => 'red'
26
+ ```
27
+
28
+ ```ruby
29
+ mab = MultiArmedBandit::ThompsonSampling.new Redis.new(:port => 6379), 'colors'
30
+ mab.load!
31
+ mab.stats # => {:arms=>["yellow", "red", "blue"], :state=>{"alpha"=>"5", "beta"=>"5", "red:count"=>"3", "red:success"=>"1", "blue:count"=>"2", "blue:success"=>"0.0", "yellow:count"=>"1", "yellow:success"=>"0.0", "green:count"=>"1"}, :means=>[["blue", 0.45454545454545453], ["green", 0.47619047619047616], ["yellow", 0.47619047619047616], ["red", 0.4782608695652174]]}
32
+ ```
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ require "multi-armed-bandit/version"
2
+ require "multi-armed-bandit/thompson_sampling"
3
+ require "redis"
4
+
5
+ module MultiArmedBandit
6
+
7
+ end
@@ -0,0 +1,126 @@
1
+ module MultiArmedBandit
2
+ class ThompsonSampling
3
+ def self.beta_mean(success, count, alpha, beta)
4
+ fail_count = count - success
5
+ 1 / (1 + (fail_count+beta).to_f / (success+alpha))
6
+ end
7
+
8
+ attr_reader :name, :arms
9
+ attr_accessor :redis
10
+
11
+ def initialize(redis, name, options={})
12
+ @redis, @name, = redis, name
13
+ @arms_key, @means_key = "#{@name}:arms", "#{@name}:means"
14
+ end
15
+
16
+ def create!(arms, options={})
17
+ @arms = Set.new arms
18
+ ts = {:alpha=>5, :beta=>5}
19
+ means = []
20
+ @arms.each do |arm|
21
+ ts["#{arm}:count"] = 0
22
+ ts["#{arm}:success"] = 0.0
23
+ end
24
+ ts.merge!(options)
25
+
26
+ @alpha, @beta = ts[:alpha].to_f, ts[:beta].to_f
27
+ means = []
28
+
29
+ @arms.each do |arm|
30
+ mean = self.class.beta_mean(
31
+ ts["#{arm}:success"],
32
+ ts["#{arm}:count"],
33
+ (ts["#{arm}:alpha"] || @alpha).to_f,
34
+ (ts["#{arm}:beta"] || @beta).to_f
35
+ )
36
+ means << [mean, arm]
37
+ end
38
+
39
+ @redis.multi do |r|
40
+ r.mapped_hmset @name, ts
41
+ r.sadd @arms_key, arms
42
+ r.zadd @means_key, means
43
+ end
44
+ self
45
+ end
46
+
47
+ def load!
48
+ @arms = Set.new @redis.smembers(@arms_key)
49
+ alpha, beta = @redis.hmget(@name, :alpha, :beta)
50
+ @alpha, @beta = alpha.to_f, beta.to_f
51
+ self
52
+ end
53
+
54
+ def delete!
55
+ @redis.multi do |r|
56
+ r.del @means_key
57
+ r.del @name
58
+ r.del @arms_key
59
+ end
60
+ end
61
+
62
+ def put(arm, options={})
63
+ ts = {"#{arm}:count" => 0, "#{arm}:success" => 0.0}
64
+ ts.merge! options
65
+ @arms << arm
66
+ @redis.multi do |r|
67
+ r.mapped_hmset @name, ts
68
+ r.sadd @arms_key, arm
69
+ end
70
+ update_mean arm
71
+ self
72
+ end
73
+
74
+ def remove(arm)
75
+ @arms.delete arm
76
+ @redis.multi do |r|
77
+ r.srem @arms_key, arm
78
+ r.hdel @name, ["#{arm}:success", "#{arm}:count", "#{arm}:alpha", "#{arm}:beta"]
79
+ r.zrem @means, arm
80
+ end
81
+ end
82
+
83
+
84
+ def disable(arm)
85
+ @redis.zrem @means, arm
86
+ end
87
+
88
+ def draw_multi(n)
89
+ drawn = []
90
+ n.times { drawn << draw }
91
+ drawn
92
+ end
93
+
94
+ def draw
95
+ max_arm = @redis.zrange(@means_key, -1, -1)[0]
96
+ @redis.hincrby @name, "#{max_arm}:count", 1
97
+ update_mean(max_arm)
98
+ max_arm
99
+ end
100
+
101
+ def update_success(arm, reward=1.0)
102
+ @redis.hincrbyfloat @name, "#{arm}:success", reward
103
+ update_mean(arm)
104
+ end
105
+
106
+ def update_mean(arm)
107
+ @redis.zadd @means_key, mean(arm), arm
108
+ end
109
+ alias enable update_mean
110
+
111
+ def mean(arm)
112
+ success, count, alpha, beta = @redis.hmget(@name, "#{arm}:success", "#{arm}:count", "#{arm}:alpha", "#{arm}:beta")
113
+ self.class.beta_mean success.to_f, count.to_f, (alpha || @alpha).to_f, (beta || @beta).to_f
114
+ end
115
+
116
+ def stats
117
+ arms, state, means = @redis.multi do |r|
118
+ r.smembers(@arms_key)
119
+ r.hgetall @name
120
+ r.zrange @means_key, 0, -1, :with_scores => true
121
+ end
122
+
123
+ { :arms => arms, :state => state, :means => means }
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,3 @@
1
+ module MultiArmedBandit
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'multi-armed-bandit/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "multi-armed-bandit"
8
+ gem.version = MultiArmedBandit::VERSION
9
+ gem.authors = ["David Dai"]
10
+ gem.email = ["ddai@scribd.com"]
11
+ gem.description = %q{A Redis backed multi-armed bandit library.}
12
+ gem.summary = %q{A Redis backed multi-armed bandit library.}
13
+ gem.homepage = "https://github.com/newtonapple/multi-armed-bandit"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ gem.add_dependency "redis", '~> 3.0.3'
20
+ end
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multi-armed-bandit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - David Dai
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redis
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.3
27
+ description: A Redis backed multi-armed bandit library.
28
+ email:
29
+ - ddai@scribd.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - .gitignore
35
+ - Gemfile
36
+ - LICENSE.txt
37
+ - README.md
38
+ - Rakefile
39
+ - lib/multi-armed-bandit.rb
40
+ - lib/multi-armed-bandit/thompson_sampling.rb
41
+ - lib/multi-armed-bandit/version.rb
42
+ - multi-armed-bandit.gemspec
43
+ homepage: https://github.com/newtonapple/multi-armed-bandit
44
+ licenses: []
45
+ metadata: {}
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ requirements: []
61
+ rubyforge_project:
62
+ rubygems_version: 2.0.0
63
+ signing_key:
64
+ specification_version: 4
65
+ summary: A Redis backed multi-armed bandit library.
66
+ test_files: []