CooCoo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/CooCoo.gemspec +47 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +88 -0
  6. data/README.md +123 -0
  7. data/Rakefile +81 -0
  8. data/bin/cuda-dev-info +25 -0
  9. data/bin/cuda-free +28 -0
  10. data/bin/cuda-free-trend +7 -0
  11. data/bin/ffi-gen +267 -0
  12. data/bin/spec_runner_html.sh +42 -0
  13. data/bin/trainer +198 -0
  14. data/bin/trend-cost +13 -0
  15. data/examples/char-rnn.rb +405 -0
  16. data/examples/cifar/cifar.rb +94 -0
  17. data/examples/img-similarity.rb +201 -0
  18. data/examples/math_ops.rb +57 -0
  19. data/examples/mnist.rb +365 -0
  20. data/examples/mnist_classifier.rb +293 -0
  21. data/examples/mnist_dream.rb +214 -0
  22. data/examples/seeds.rb +268 -0
  23. data/examples/seeds_dataset.txt +210 -0
  24. data/examples/t10k-images-idx3-ubyte +0 -0
  25. data/examples/t10k-labels-idx1-ubyte +0 -0
  26. data/examples/train-images-idx3-ubyte +0 -0
  27. data/examples/train-labels-idx1-ubyte +0 -0
  28. data/ext/buffer/Rakefile +50 -0
  29. data/ext/buffer/buffer.pre.cu +727 -0
  30. data/ext/buffer/matrix.pre.cu +49 -0
  31. data/lib/CooCoo.rb +1 -0
  32. data/lib/coo-coo.rb +18 -0
  33. data/lib/coo-coo/activation_functions.rb +344 -0
  34. data/lib/coo-coo/consts.rb +5 -0
  35. data/lib/coo-coo/convolution.rb +298 -0
  36. data/lib/coo-coo/core_ext.rb +75 -0
  37. data/lib/coo-coo/cost_functions.rb +91 -0
  38. data/lib/coo-coo/cuda.rb +116 -0
  39. data/lib/coo-coo/cuda/device_buffer.rb +240 -0
  40. data/lib/coo-coo/cuda/device_buffer/ffi.rb +109 -0
  41. data/lib/coo-coo/cuda/error.rb +51 -0
  42. data/lib/coo-coo/cuda/host_buffer.rb +117 -0
  43. data/lib/coo-coo/cuda/runtime.rb +157 -0
  44. data/lib/coo-coo/cuda/vector.rb +315 -0
  45. data/lib/coo-coo/data_sources.rb +2 -0
  46. data/lib/coo-coo/data_sources/xournal.rb +25 -0
  47. data/lib/coo-coo/data_sources/xournal/bitmap_stream.rb +197 -0
  48. data/lib/coo-coo/data_sources/xournal/document.rb +377 -0
  49. data/lib/coo-coo/data_sources/xournal/loader.rb +144 -0
  50. data/lib/coo-coo/data_sources/xournal/renderer.rb +101 -0
  51. data/lib/coo-coo/data_sources/xournal/saver.rb +99 -0
  52. data/lib/coo-coo/data_sources/xournal/training_document.rb +78 -0
  53. data/lib/coo-coo/data_sources/xournal/training_document/constants.rb +15 -0
  54. data/lib/coo-coo/data_sources/xournal/training_document/document_maker.rb +89 -0
  55. data/lib/coo-coo/data_sources/xournal/training_document/document_reader.rb +105 -0
  56. data/lib/coo-coo/data_sources/xournal/training_document/example.rb +37 -0
  57. data/lib/coo-coo/data_sources/xournal/training_document/sets.rb +76 -0
  58. data/lib/coo-coo/debug.rb +8 -0
  59. data/lib/coo-coo/dot.rb +129 -0
  60. data/lib/coo-coo/drawing.rb +4 -0
  61. data/lib/coo-coo/drawing/cairo_canvas.rb +100 -0
  62. data/lib/coo-coo/drawing/canvas.rb +68 -0
  63. data/lib/coo-coo/drawing/chunky_canvas.rb +101 -0
  64. data/lib/coo-coo/drawing/sixel.rb +214 -0
  65. data/lib/coo-coo/enum.rb +17 -0
  66. data/lib/coo-coo/from_name.rb +58 -0
  67. data/lib/coo-coo/fully_connected_layer.rb +205 -0
  68. data/lib/coo-coo/generation_script.rb +38 -0
  69. data/lib/coo-coo/grapher.rb +140 -0
  70. data/lib/coo-coo/image.rb +286 -0
  71. data/lib/coo-coo/layer.rb +67 -0
  72. data/lib/coo-coo/layer_factory.rb +26 -0
  73. data/lib/coo-coo/linear_layer.rb +59 -0
  74. data/lib/coo-coo/math.rb +607 -0
  75. data/lib/coo-coo/math/abstract_vector.rb +121 -0
  76. data/lib/coo-coo/math/functions.rb +39 -0
  77. data/lib/coo-coo/math/interpolation.rb +7 -0
  78. data/lib/coo-coo/network.rb +264 -0
  79. data/lib/coo-coo/neuron.rb +112 -0
  80. data/lib/coo-coo/neuron_layer.rb +168 -0
  81. data/lib/coo-coo/option_parser.rb +18 -0
  82. data/lib/coo-coo/platform.rb +17 -0
  83. data/lib/coo-coo/progress_bar.rb +11 -0
  84. data/lib/coo-coo/recurrence/backend.rb +99 -0
  85. data/lib/coo-coo/recurrence/frontend.rb +101 -0
  86. data/lib/coo-coo/sequence.rb +187 -0
  87. data/lib/coo-coo/shell.rb +2 -0
  88. data/lib/coo-coo/temporal_network.rb +291 -0
  89. data/lib/coo-coo/trainer.rb +21 -0
  90. data/lib/coo-coo/trainer/base.rb +67 -0
  91. data/lib/coo-coo/trainer/batch.rb +82 -0
  92. data/lib/coo-coo/trainer/batch_stats.rb +27 -0
  93. data/lib/coo-coo/trainer/momentum_stochastic.rb +59 -0
  94. data/lib/coo-coo/trainer/stochastic.rb +47 -0
  95. data/lib/coo-coo/transformer.rb +272 -0
  96. data/lib/coo-coo/vector_layer.rb +194 -0
  97. data/lib/coo-coo/version.rb +3 -0
  98. data/lib/coo-coo/weight_deltas.rb +23 -0
  99. data/prototypes/convolution.rb +116 -0
  100. data/prototypes/linear_drop.rb +51 -0
  101. data/prototypes/recurrent_layers.rb +79 -0
  102. data/www/images/screamer.png +0 -0
  103. data/www/images/screamer.xcf +0 -0
  104. data/www/index.html +82 -0
  105. metadata +373 -0
@@ -0,0 +1,75 @@
1
+ class Numeric
2
+ [ :exp, :sqrt, :log, :log10, :log2,
3
+ :sin, :asin, :cos, :acos, :tan, :atan,
4
+ :sinh, :asinh, :cosh, :acosh, :tanh, :atanh,
5
+ :ceil, :floor, :round
6
+ ].each do |f|
7
+ define_method(f) do
8
+ ::Math.send(f, self)
9
+ end
10
+ end
11
+
12
+ def identity
13
+ coerce(1)[0]
14
+ end
15
+
16
+ def zero
17
+ coerce(0)[0]
18
+ end
19
+ end
20
+
21
+ class Object
22
+ def self.instance_defines?(method)
23
+ instance_methods.include?(method)
24
+ end
25
+
26
+ def self.define_once(method, &definition)
27
+ unless instance_defines?(method)
28
+ define_method(method, &definition)
29
+ end
30
+ end
31
+
32
+ def self.delegate(*args)
33
+ opts = args.pop
34
+
35
+ args.each do |meth|
36
+ define_method(meth) do |*a|
37
+ send(opts[:to]).send(meth, *a)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ class Array
44
+ def zero
45
+ self.class.new(size, 0.0)
46
+ end
47
+ end
48
+
49
+ class File
50
+ def self.write_to(path, &block)
51
+ tmp = path.to_s + ".tmp"
52
+ bak = path.to_s + "~"
53
+
54
+ # write to temp file
55
+ File.open(tmp, "w", &block)
56
+
57
+ # create a backup file
58
+ if File.exists?(path)
59
+ # remove any existing backup
60
+ if File.exists?(bak)
61
+ File.delete(bak)
62
+ end
63
+
64
+ File.rename(path, bak)
65
+ end
66
+
67
+ # finalize the save
68
+ File.rename(tmp, path)
69
+
70
+ self
71
+ rescue
72
+ File.delete(tmp)
73
+ raise
74
+ end
75
+ end
@@ -0,0 +1,91 @@
1
+ require 'coo-coo/from_name'
2
+
3
+ module CooCoo
4
+ # CostFunctions are used with a {Trainer} to determine how close a {Network}
5
+ # is coming to its target. CostFunctions are functions of two variables.
6
+ #
7
+ # To get a cost function instance use the included {#from_name}.
8
+ # Then you can +#call+ or +#derivative+ any cost function.
9
+ #
10
+ # To create a new cost function that can be used with a {Trainer},
11
+ # you must call {CostFunctions.register} and implement the
12
+ # +#call+ and +#derivative+ class methods.
13
+ module CostFunctions
14
+ class << self
15
+ include FromName
16
+ end
17
+
18
+ # @abstract Defines and documents the cost functions' interface.
19
+ # Be sure to call {CostFunctions.register} inside your subclass.
20
+ class Base
21
+ # Returns the cost between the target output and actual output.
22
+ #
23
+ # @param target [Vector] Desired value
24
+ # @param x [Vector] A network's actual output
25
+ # @return [Vector] The cost of the target for this output
26
+ def self.call(target, x)
27
+ raise NotImplementedError.new
28
+ end
29
+
30
+ # Returns the derivative of the cost function, +#call+. This is
31
+ # what gets fed into the network to determine the changes.
32
+ #
33
+ # @param target [Vector] Desired value
34
+ # @param x [Vector] A network's actual output
35
+ # @param y [Vector] The results from a previous +#call+
36
+ # @return [Vector]
37
+ def self.derivative(target, x, y = nil)
38
+ raise NotImplementedError.new
39
+ end
40
+ end
41
+
42
+ # Implements the mean square cost function. Its derivative is
43
+ # a simple difference between the target and actual output.
44
+ class MeanSquare < Base
45
+ CostFunctions.register(self, name)
46
+
47
+ def self.call(target, x)
48
+ d = derivative(target, x)
49
+ d * d * 0.5
50
+ end
51
+
52
+ def self.derivative(target, x, y = nil)
53
+ x - target
54
+ end
55
+ end
56
+
57
+ # Implements the log cross-entropy cost function that is used with
58
+ # {ActivationFunctions::SoftMax} and
59
+ # {ActivationFunctions::ShiftedSoftMax}. This calls +Math.log+ on
60
+ # the network's output and multiples that by the target. Therefore
61
+ # good target values are +0...1+.
62
+ class CrossEntropy < Base
63
+ CostFunctions.register(self, name)
64
+
65
+ def self.call(target, x)
66
+ -x.log * target
67
+ end
68
+
69
+ def self.derivative(target, x)
70
+ -target / x
71
+ end
72
+ end
73
+
74
+ # Combines a SoftMax activation with CrossEntropy. Due to math this
75
+ # is more optimal than having a SoftMax layer and doing CrossEntropy
76
+ # seperately.
77
+ #
78
+ # @see http://peterroelants.github.io/posts/neural_network_implementation_intermezzo02/
79
+ class SoftMaxCrossEntropy < CrossEntropy
80
+ CostFunctions.register(self, name)
81
+
82
+ def self.call(target, x)
83
+ super(target, ActivationFunctions::ShiftedSoftMax.call(x))
84
+ end
85
+
86
+ def self.derivative(target, x)
87
+ x - target
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,116 @@
1
+ require 'coo-coo/debug'
2
+ begin
3
+ require 'coo-coo/cuda/runtime'
4
+
5
+ module CooCoo
6
+ module CUDA
7
+ def self.available?
8
+ ENV["COOCOO_USE_CUDA"] != "0"# && Runtime.device_count > 0
9
+ end
10
+
11
+ def self.memory_info
12
+ Runtime.memory_info
13
+ end
14
+
15
+ def self.collect_garbage(size = nil)
16
+ free, total = memory_info
17
+ if size == nil || (3 * size + free) >= total
18
+ GC.start
19
+ new_free, total = memory_info
20
+ diff = free - new_free
21
+ if size && (size + new_free) >= total
22
+ raise NoMemoryError.new(size)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ require 'coo-coo/cuda/host_buffer'
30
+ require 'coo-coo/cuda/device_buffer'
31
+ require 'coo-coo/cuda/vector'
32
+
33
+ rescue LoadError
34
+ CooCoo.debug("LoadError #{__FILE__}: #{$!}")
35
+ module CooCoo
36
+ module CUDA
37
+ def self.available?
38
+ false
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ if __FILE__ == $0
45
+ require 'pp'
46
+
47
+ puts("Cuda not available") unless CooCoo::CUDA.available?
48
+
49
+ puts("Resetting #{CooCoo::CUDA::Runtime.cudaDeviceReset}")
50
+ puts("Device = #{CooCoo::CUDA::Runtime.get_device} / #{CooCoo::CUDA::Runtime.device_count}")
51
+ puts("Init = #{CooCoo::CUDA::DeviceBuffer::FFI.buffer_init(0)}")
52
+ puts("Block size = #{CooCoo::CUDA::DeviceBuffer::FFI.buffer_block_size}")
53
+ puts("Grid size = #{CooCoo::CUDA::DeviceBuffer::FFI.buffer_max_grid_size}")
54
+ puts("Total memory = #{CooCoo::CUDA.memory_info.join('/')}")
55
+ props = CooCoo::CUDA::Runtime::DeviceProperties.new
56
+ puts(CooCoo::CUDA::Runtime.cudaGetDeviceProperties(props, 0).inspect)
57
+ puts("Properties")
58
+ props.members.each do |m|
59
+ value = props[m]
60
+ if m != :name && value.kind_of?(FFI::Struct::InlineArray)
61
+ value.each_with_index do |v, i|
62
+ puts("#{m}[#{i}]\t#{v}")
63
+ end
64
+ else
65
+ puts("#{m}\t#{value}")
66
+ end
67
+ end
68
+ dev = CooCoo::CUDA::Runtime.get_device
69
+ puts("Device #{dev}")
70
+ puts("Creating")
71
+ WIDTH = 256
72
+ HEIGHT = 256
73
+ SIZE = WIDTH * HEIGHT # 1024 * 1024 * 1
74
+ h = CooCoo::CUDA::HostBuffer.new(SIZE)
75
+ arr = SIZE.times.collect { |n| n }
76
+ h.set(arr)
77
+ a = CooCoo::CUDA::Vector.new(SIZE)
78
+ a.set(h)
79
+ puts("Size = #{a.size}")
80
+ puts("Getting")
81
+ b = ((a.dot(WIDTH, HEIGHT, a) * 3 - a) / 3.0).sin #* 2 + 1
82
+ #b = b.get.to_a
83
+ puts(b[0, 10].to_s)
84
+ puts(b[-10, 10].to_s)
85
+ puts("Sum = #{b.sum} #{b.each.sum}")
86
+
87
+ require 'benchmark'
88
+ require 'coo-coo/math'
89
+ require 'nmatrix'
90
+
91
+ Benchmark.bm(3) do |bm|
92
+ bm.report("cuda add") do
93
+ b = a.clone
94
+ 10000.times do |i|
95
+ #puts("%i %i" % [ CooCoo::CUDA::DeviceBuffer::FFI.buffer_total_bytes_allocated, CooCoo::CUDA::Runtime.total_global_mem ]) if i % 1000
96
+ b = b + b
97
+ end
98
+ #puts("CUDA sum", b.get.to_a.inspect)
99
+ #puts("Last error: ", CooCoo::CUDA::FFI.cudaGetLastError)
100
+ end
101
+ bm.report("ruby vector add") do
102
+ b = CooCoo::Ruby::Vector[arr]
103
+ 10000.times do
104
+ b = b + b
105
+ end
106
+ #puts("Vector sum", b.inspect)
107
+ end
108
+ bm.report("nmatrix add") do
109
+ b = NMatrix[arr]
110
+ 10000.times do
111
+ b = b + b
112
+ end
113
+ #puts("NMatrix sum", b.inspect)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,240 @@
1
+ require 'pathname'
2
+ require 'ffi'
3
+ require 'coo-coo/cuda/error'
4
+ require 'coo-coo/cuda/host_buffer'
5
+
6
+ module CooCoo
7
+ module CUDA
8
+ class DeviceBuffer < ::FFI::Struct
9
+ layout(:data, :pointer,
10
+ :size, :size_t)
11
+
12
+ def self.create(size, initial_value = 0.0)
13
+ FFI.new(size, initial_value.to_f)
14
+ end
15
+
16
+ def self.release(ptr)
17
+ FFI.buffer_free(ptr)
18
+ rescue
19
+ CooCoo.debug(__method__, $!.inspect)
20
+ end
21
+
22
+ require 'coo-coo/cuda/device_buffer/ffi'
23
+
24
+ def size
25
+ FFI.buffer_length(self)
26
+ end
27
+
28
+ def clone
29
+ self.class.
30
+ create(self.size).
31
+ set(self)
32
+ end
33
+
34
+ def self.[](other, length = nil)
35
+ if other.respond_to?(:each)
36
+ length ||= other.size
37
+ else
38
+ length ||= 1
39
+ end
40
+ self.create(length).set(other)
41
+ end
42
+
43
+ def set(buffer)
44
+ case buffer
45
+ when self.class then FFI.set(self, buffer)
46
+ when Numeric then FFI.setd(self, buffer.to_f, 0, size)
47
+ else
48
+ buffer = HostBuffer[buffer]
49
+ FFI.setv(self, buffer.to_ptr, buffer.size)
50
+ end
51
+
52
+ self
53
+ end
54
+
55
+ def []=(index, value, length = nil)
56
+ index = size + index if index < 0
57
+ raise RangeError.new("#{index} >= #{size}") if index >= size
58
+ raise RangeError.new("#{index} < 0") if index < 0
59
+
60
+ if length
61
+ value, length = length, value
62
+ if value.kind_of?(self.class)
63
+ FFI.setn(self, index, value, length)
64
+ else
65
+ buffer = HostBuffer[value, length]
66
+ FFI.setvn(self, index, buffer.to_ptr, buffer.size)
67
+ end
68
+ else
69
+ FFI.set_element(self, index, value)
70
+ end
71
+ end
72
+
73
+ def get
74
+ out = HostBuffer.new(size)
75
+ FFI.get(self, out.to_ptr, size)
76
+ out
77
+ end
78
+
79
+ def [](index, len = nil, pad = false)
80
+ return super(index) if index.kind_of?(Symbol)
81
+
82
+ index = size + index if index < 0
83
+ raise RangeError.new if index >= size || index < 0
84
+ if len
85
+ len = (size - index) if pad == false && (index + len) >= size
86
+ raise ArgumentError.new("length must be > 0") if len <= 0
87
+ end
88
+
89
+ if len
90
+ FFI.slice(self, index, len)
91
+ else
92
+ out = HostBuffer.new(1)
93
+ FFI.host_slice(self, out.to_ptr, index, 1)
94
+ out[0]
95
+ end
96
+ end
97
+
98
+ def each(&block)
99
+ get.each(&block)
100
+ end
101
+
102
+ def each_slice(n, &block)
103
+ return to_enum(__method__, n) unless block
104
+
105
+ (size / n.to_f).ceil.to_i.times do |i|
106
+ block.call(self[i * n, n, true])
107
+ end
108
+ end
109
+
110
+ def sum
111
+ FFI.buffer_sum(self)
112
+ end
113
+
114
+ def dot(w, h, other, ow = nil, oh = nil)
115
+ if other.kind_of?(self.class)
116
+ ow ||= w
117
+ oh ||= h
118
+ raise ArgumentError.new("width (#{w}) must match the other's height (#{oh})") if w != oh
119
+ raise ArgumentError.new("width * height != size") if size != w * h
120
+ raise ArgumentError.new("other's width * height != other's size (#{ow} * #{oh} != #{other.size})") if other.size != ow * oh
121
+ raise ArgumentError.new("other is null") if other.null?
122
+ raise ArgumentError.new("self is null") if null?
123
+
124
+ FFI.dot(self, w, h, other, ow, oh)
125
+ else
126
+ b, a = coerce(other)
127
+ dot(w, h, b, ow, oh)
128
+ end
129
+ end
130
+
131
+ def slice_2d(width, height, x, y, out_width, out_height, initial = 0.0)
132
+ FFI.slice_2d(self, width, height, x, y, out_width, out_height, initial)
133
+ end
134
+
135
+ def set2d!(width, src, src_width, x, y)
136
+ case src
137
+ when self.class then FFI.set2d(self, width, src, src_width, x, y)
138
+ else
139
+ src = HostBuffer[src] unless src.kind_of?(HostBuffer)
140
+ FFI.set2dv(self, width, src.to_ptr, src_width, src.size / src_width, x, y)
141
+ end
142
+
143
+ self
144
+ end
145
+
146
+ def ==(other)
147
+ if other.kind_of?(self.class)
148
+ 1 == FFI.buffer_eq(self, other)
149
+ else
150
+ return false
151
+ end
152
+ end
153
+
154
+ { :< => "lt",
155
+ :<= => "lte",
156
+ :>= => "gte",
157
+ :> => "gt",
158
+ :collect_equal? => 'eq',
159
+ :collect_not_equal? => 'neq'
160
+ }.each do |comp_op, func|
161
+ define_method(comp_op) do |other|
162
+ if other.kind_of?(self.class)
163
+ FFI.send("collect_#{func}", self, other)
164
+ elsif other.kind_of?(Numeric)
165
+ FFI.send("collect_#{func}d", self, other)
166
+ else
167
+ raise TypeError.new("wrong type #{other.class}")
168
+ end
169
+ end
170
+ end
171
+
172
+ [ :abs, :exp, :log, :log10, :log2, :sqrt,
173
+ :sin, :asin, :cos, :acos, :tan, :atan,
174
+ :sinh, :asinh, :cosh, :acosh, :tanh, :atanh,
175
+ :ceil, :floor, :round,
176
+ :collect_nan, :collect_inf
177
+ ].each do |f|
178
+ define_method(f) do
179
+ r = FFI.send(f, self)
180
+ raise NullResultError.new("NULL result") if r.null?
181
+ r
182
+ end
183
+ end
184
+
185
+ def coerce(other)
186
+ if other.respond_to?(:each)
187
+ return self.class[other], self
188
+ else
189
+ return self.class.create(self.size).set(other), self
190
+ end
191
+ end
192
+
193
+ def to_a
194
+ get.to_a
195
+ end
196
+
197
+ def null?
198
+ super || self[:data].null?
199
+ end
200
+
201
+ def self.ffi_operator(op, ffi_method)
202
+ define_method(op) do |other|
203
+ if other.respond_to?(:each)
204
+ other = self.class[other] unless other.kind_of?(self.class)
205
+ raise ArgumentError.new("size mismatch: #{size} != #{other.size}") if size != other.size
206
+ FFI.send(ffi_method, self, other)
207
+ else
208
+ FFI.send(ffi_method.to_s + "d", self, other.to_f)
209
+ end
210
+ end
211
+ end
212
+
213
+ ffi_operator(:+, :add)
214
+ ffi_operator(:-, :sub)
215
+ ffi_operator(:*, :mul)
216
+ ffi_operator(:**, :pow)
217
+ ffi_operator(:/, :div)
218
+
219
+ def self.identity(w, h)
220
+ FFI.buffer_identity(w, h)
221
+ end
222
+
223
+ def diagflat
224
+ FFI.buffer_diagflat(self)
225
+ end
226
+
227
+ def min
228
+ FFI.buffer_min(self)
229
+ end
230
+
231
+ def max
232
+ FFI.buffer_max(self)
233
+ end
234
+
235
+ def minmax
236
+ return min, max
237
+ end
238
+ end
239
+ end
240
+ end