CooCoo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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