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,168 @@
1
+ require 'coo-coo/consts'
2
+ require 'coo-coo/math'
3
+ require 'coo-coo/debug'
4
+ require 'coo-coo/layer_factory'
5
+ require 'coo-coo/neuron'
6
+ require 'coo-coo/sequence'
7
+ require 'coo-coo/weight_deltas'
8
+
9
+ module CooCoo
10
+ class NeuronLayer
11
+ LayerFactory.register_type(self)
12
+
13
+ attr_accessor :activation_function
14
+
15
+ def initialize(num_inputs, size, activation_function = CooCoo.default_activation)
16
+ @activation_function = activation_function
17
+ @neurons = Array.new
18
+ size.times do |i|
19
+ @neurons[i] = Neuron.new(num_inputs, activation_function)
20
+ end
21
+ end
22
+
23
+ def neurons
24
+ @neurons
25
+ end
26
+
27
+ def num_inputs
28
+ @neurons[0].num_inputs
29
+ end
30
+
31
+ def size
32
+ @neurons.size
33
+ end
34
+
35
+ def forward(input, hidden_state)
36
+ o = @neurons.each_with_index.inject(CooCoo::Vector.zeros(size)) do |acc, (neuron, i)|
37
+ acc[i] = neuron.forward(input)
38
+ acc
39
+ end
40
+
41
+ return o, hidden_state
42
+ end
43
+
44
+ def backprop(input, output, errors, hidden_state)
45
+ o = @neurons.each_with_index.inject(CooCoo::Vector.zeros(size)) do |acc, (n, i)|
46
+ acc[i] = n.backprop(input, output[i], errors[i])
47
+ acc
48
+ end
49
+
50
+ return o, hidden_state
51
+ end
52
+
53
+ def transfer_error(deltas)
54
+ @neurons.each_with_index.inject(CooCoo::Vector.zeros(num_inputs)) do |acc, (n, i)|
55
+ acc + n.transfer_error(deltas[i])
56
+ end
57
+ end
58
+
59
+ def transfer_input_error(expecting)
60
+ (output - expecting).to_a
61
+ end
62
+
63
+ def update_weights!(inputs, deltas)
64
+ adjust_weights!(weight_deltas(inputs, deltas))
65
+ end
66
+
67
+ def adjust_weights!(deltas)
68
+ @neurons.each_with_index do |n, i|
69
+ n.adjust_weights!(deltas.bias_deltas[i], deltas.weight_deltas[i])
70
+ end
71
+
72
+ self
73
+ end
74
+
75
+ def weight_deltas(inputs, deltas)
76
+ WeightDeltas.new(*@neurons.each_with_index.inject([ CooCoo::Vector.zeros(size), CooCoo::Sequence.new(size) ]) do |acc, (n, i)|
77
+ acc[0][i], acc[1][i] = n.weight_deltas(inputs, deltas[i])
78
+ acc
79
+ end)
80
+ end
81
+
82
+ def to_hash(network = nil)
83
+ { type: self.class.to_s,
84
+ outputs: @neurons.size,
85
+ neurons: @neurons.collect(&:to_hash)
86
+ }
87
+ end
88
+
89
+ def resize!(new_size)
90
+ n = @neurons + Array.new(new_size - @neurons.size)
91
+ (@neurons.size...new_size).each do |i|
92
+ n[i] = Neuron.new(num_inputs)
93
+ end
94
+
95
+ @neurons = n
96
+
97
+ self
98
+ end
99
+
100
+ def update_neuron_from_hash!(neuron_index, h)
101
+ if neuron_index > @neurons.size
102
+ resize!(neuron_index)
103
+ end
104
+
105
+ @neurons[neuron_index].update_from_hash!(h)
106
+ end
107
+
108
+ def update_from_hash!(h)
109
+ resize!(h[:outputs])
110
+
111
+ h[:outputs].times do |i|
112
+ update_neuron_from_hash!(i, h[:neurons][i])
113
+ end
114
+
115
+ self
116
+ end
117
+
118
+ def ==(other)
119
+ other.kind_of?(self.class) &&
120
+ size == other.size &&
121
+ neurons.zip(other.neurons).all? { |a, b| a == b }
122
+ end
123
+
124
+ class << self
125
+ def from_hash(h, network = nil)
126
+ self.new(h[:neurons].size, h[:outputs]).update_from_hash!(h)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ if __FILE__ == $0
133
+ layer = CooCoo::NeuronLayer.new(4, 2, CooCoo::ActivationFunctions.from_name(ENV.fetch("ACTIVATION", "Logistic")))
134
+ inputs = [ [ 1.0, 0.0, 0.0, 0.0 ],
135
+ [ 0.0, 0.0, 1.0, 0.0 ],
136
+ [ 0.0, 1.0, 0.0, 0.0],
137
+ [ 0.0, 0.0, 0.0, 1.0 ]
138
+ ].collect do |v|
139
+ CooCoo::Vector[v]
140
+ end
141
+ targets = [ [ 1.0, 0.0 ],
142
+ [ 0.0, 1.0 ],
143
+ [ 0.0, 0.0 ],
144
+ [ 0.0, 0.0 ]
145
+ ].collect do |v|
146
+ CooCoo::Vector[v]
147
+ end
148
+
149
+ inputs.zip(targets).cycle(ENV.fetch('LOOPS', 100).to_i).each do |(input, target)|
150
+ output, hidden_state = layer.forward(input, Hash.new)
151
+ puts("#{input} -> #{target}")
152
+ puts("\toutput: #{output}")
153
+
154
+ err = (output - target)
155
+ #err = err * err * 0.5
156
+ delta, hidden_state = layer.backprop(input, output, err, hidden_state)
157
+ puts("\tdelta: #{delta}")
158
+ puts("\terror: #{err}")
159
+ puts("\txfer: #{layer.transfer_error(delta)}")
160
+
161
+ layer.update_weights!(input, delta * 0.5)
162
+ end
163
+
164
+ inputs.zip(targets).each do |(input, target)|
165
+ output, hidden_state = layer.forward(input, Hash.new)
166
+ puts("#{input} -> #{output}\t#{target}")
167
+ end
168
+ end
@@ -0,0 +1,18 @@
1
+ require 'optparse'
2
+
3
+ module CooCoo
4
+ class OptionParser < ::OptionParser
5
+ def parse!(argv)
6
+ left_overs = []
7
+ begin
8
+ left_overs += super(argv)
9
+ rescue OptionParser::InvalidOption
10
+ left_overs += $!.args
11
+ left_overs << argv.shift
12
+ retry
13
+ end
14
+
15
+ left_overs
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ module CooCoo
2
+ module Platform
3
+ def self.windows?
4
+ RbConfig::CONFIG['PLATFORM_DIR'] == 'win32'
5
+ end
6
+
7
+ ROOT = File.dirname(File.dirname(File.dirname(__FILE__)))
8
+ def self.root
9
+ ROOT
10
+ end
11
+ end
12
+
13
+ def self.root
14
+ Platform.root
15
+ end
16
+ end
17
+
@@ -0,0 +1,11 @@
1
+ require 'ruby-progressbar'
2
+
3
+ module CooCoo
4
+ module ProgressBar
5
+ Defaults = { :format => "%t %c/%C |%B| %a / %e" }
6
+
7
+ def self.create(opts)
8
+ ::ProgressBar.create(Defaults.merge(opts))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,99 @@
1
+ require 'coo-coo/math'
2
+ require 'coo-coo/layer_factory'
3
+
4
+ module CooCoo
5
+ module Recurrence
6
+ class Backend
7
+ LayerFactory.register_type(self)
8
+
9
+ attr_reader :recurrence_layer
10
+
11
+ def initialize(recurrence_layer, outputs, recurrent_outputs)
12
+ @recurrence_layer = recurrence_layer
13
+ @outputs = outputs
14
+ @recurrent_size = recurrent_outputs
15
+ end
16
+
17
+ def num_inputs
18
+ size + recurrent_size
19
+ end
20
+
21
+ def size
22
+ @outputs
23
+ end
24
+
25
+ def recurrent_size
26
+ @recurrent_size
27
+ end
28
+
29
+ def activation_function
30
+ nil
31
+ end
32
+
33
+ def forward(inputs, hidden_state)
34
+ hidden_state ||= Hash.new
35
+ hidden_state[self] ||= Array.new
36
+ hidden_state[self].push(inputs[size, recurrent_size])
37
+
38
+ return inputs[0, size], hidden_state
39
+ end
40
+
41
+ def backprop(input, output, errors, hidden_state)
42
+ layer_state = hidden_state[@recurrence_layer]
43
+ rec_errors = (layer_state && layer_state.pop) || CooCoo::Vector.zeros(recurrent_size)
44
+ return errors.append(rec_errors), hidden_state
45
+ end
46
+
47
+ def transfer_error(deltas)
48
+ deltas
49
+ end
50
+
51
+ def update_weights!(inputs, deltas)
52
+ self
53
+ end
54
+
55
+ def adjust_weights!(deltas)
56
+ self
57
+ end
58
+
59
+ def weight_deltas(inputs, deltas)
60
+ inputs * deltas
61
+ end
62
+
63
+ def to_hash(network = nil)
64
+ { type: self.class.name,
65
+ outputs: @outputs,
66
+ recurrent_size: @recurrent_size,
67
+ recurrence_layer: network && network.layer_index(@recurrence_layer)
68
+ }
69
+ end
70
+
71
+ def ==(other)
72
+ other.kind_of?(self.class) &&
73
+ size = other.size &&
74
+ recurrence_layer == other.recurrence_layer &&
75
+ recurrent_size == other.recurrent_size
76
+ end
77
+
78
+ def update_from_hash!(h)
79
+ @outputs = h.fetch(:outputs)
80
+ @recurrent_size = h.fetch(:recurrent_size)
81
+ self
82
+ end
83
+
84
+ def self.from_hash(h, network)
85
+ frontend = network.layers[h.fetch(:recurrence_layer)]
86
+ raise ArgumentError.new("Frontend not found") unless frontend
87
+
88
+ layer = self.new(frontend,
89
+ h.fetch(:outputs),
90
+ h.fetch(:recurrent_size)).
91
+ update_from_hash!(h)
92
+
93
+ frontend.backend = layer
94
+
95
+ layer
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,101 @@
1
+ require 'coo-coo/math'
2
+ require 'coo-coo/layer_factory'
3
+ require 'coo-coo/recurrence/backend'
4
+
5
+ module CooCoo
6
+ module Recurrence
7
+ class Frontend
8
+ LayerFactory.register_type(self)
9
+
10
+ def initialize(num_inputs, num_recurrent_outputs)
11
+ @num_inputs = num_inputs
12
+ @num_recurrent_outputs = num_recurrent_outputs
13
+ end
14
+
15
+ def num_inputs
16
+ @num_inputs
17
+ end
18
+
19
+ def activation_function
20
+ nil
21
+ end
22
+
23
+ def size
24
+ @num_inputs + recurrent_size
25
+ end
26
+
27
+ def recurrent_size
28
+ @num_recurrent_outputs
29
+ end
30
+
31
+ def backend
32
+ @backend ||= Backend.new(self, @num_inputs, recurrent_size)
33
+ end
34
+
35
+ def backend=(layer)
36
+ @backend = layer
37
+ end
38
+
39
+ def forward(inputs, hidden_state)
40
+ layer_state = hidden_state[@backend]
41
+ recurrent_input = layer_state && layer_state.pop
42
+ return inputs.append(recurrent_input || empty_input), hidden_state
43
+ end
44
+
45
+ def backprop(input, outputs, errors, hidden_state)
46
+ # split for real and recurrent errors
47
+ norm_errors = errors[0, num_inputs]
48
+ recurrent_errors = errors[num_inputs, recurrent_size]
49
+
50
+ # buffer the recurrent output
51
+ hidden_state ||= Hash.new
52
+ hidden_state[self] ||= Array.new
53
+ hidden_state[self].push(recurrent_errors)
54
+
55
+ # return real errors
56
+ return norm_errors, hidden_state
57
+ end
58
+
59
+ def transfer_error(deltas)
60
+ deltas
61
+ end
62
+
63
+ def weight_deltas(inputs, deltas)
64
+ inputs * deltas
65
+ end
66
+
67
+ def adjust_weights!(deltas)
68
+ self
69
+ end
70
+
71
+ def ==(other)
72
+ other.kind_of?(self.class) &&
73
+ num_inputs == other.num_inputs &&
74
+ recurrent_size == other.recurrent_size
75
+ end
76
+
77
+ def to_hash(network = nil)
78
+ { type: self.class.name,
79
+ inputs: @num_inputs,
80
+ recurrent_outputs: @num_recurrent_outputs
81
+ }
82
+ end
83
+
84
+ def update_from_hash!(h)
85
+ @num_inputs = h.fetch(:inputs)
86
+ @num_recurrent_outputs = h.fetch(:recurrent_outputs)
87
+
88
+ self
89
+ end
90
+
91
+ def self.from_hash(h, network = nil)
92
+ self.new(h.fetch(:inputs), h.fetch(:recurrent_outputs)).update_from_hash!(h)
93
+ end
94
+
95
+ private
96
+ def empty_input
97
+ CooCoo::Vector.zeros(recurrent_size)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,187 @@
1
+ require 'coo-coo/math'
2
+
3
+ module CooCoo
4
+ class Sequence < Math::AbstractVector
5
+ def initialize(length, &init)
6
+ if length.kind_of?(Array)
7
+ @elements = length
8
+ else
9
+ @elements = Array.new(length, &init)
10
+ end
11
+ end
12
+
13
+ def self.[](value, max_size = nil)
14
+ self.new(value.to_a)
15
+ # ret = new(max_size || value.size)
16
+ # value.each_with_index do |v, i|
17
+ # ret[i] = v
18
+ # end
19
+ # ret
20
+ end
21
+
22
+ # def coerce(other)
23
+ # if other.respond_to?(:each)
24
+ # return self.class[other], self
25
+ # else
26
+ # return self.class.new(self.size, other), self
27
+ # end
28
+ # end
29
+
30
+ def to_a
31
+ @elements
32
+ end
33
+
34
+ def to_s
35
+ values = each.collect do |e|
36
+ e.to_s
37
+ end
38
+
39
+ "[#{values.join(', ')}]"
40
+ end
41
+
42
+ def [](i, len = nil)
43
+ v = @elements[i, len || 1]
44
+ if len
45
+ self.class[v]
46
+ else
47
+ v[0]
48
+ end
49
+ end
50
+
51
+ def []=(i, v)
52
+ @elements[i] = v
53
+ self
54
+ end
55
+
56
+ def each(&block)
57
+ @elements.each(&block)
58
+ end
59
+
60
+ def each_with_index(&block)
61
+ each.each_with_index(&block)
62
+ end
63
+
64
+ include Enumerable
65
+
66
+ def collect(&block)
67
+ self.class[super]
68
+ end
69
+
70
+ def reverse
71
+ self.class[@elements.reverse]
72
+ end
73
+
74
+ def last(*args)
75
+ @elements.last(*args)
76
+ end
77
+
78
+ def append(other)
79
+ v = self.class.new(size + other.size)
80
+ each_with_index do |e, i|
81
+ v[i] = e
82
+ end
83
+ other.each_with_index do |e, i|
84
+ v[i + size] = e
85
+ end
86
+ v
87
+ end
88
+
89
+ def zero
90
+ self.class.new(size) do |i|
91
+ self[0].zero
92
+ end
93
+ end
94
+
95
+ def sum
96
+ @elements.drop(1).inject(@elements[0]) do |acc, e|
97
+ acc + e
98
+ end
99
+ end
100
+
101
+ def average
102
+ sum / size.to_f
103
+ end
104
+
105
+ def sqrt
106
+ self.class[@elements.collect(&:sqrt)]
107
+ end
108
+
109
+ def -@
110
+ self.class[@elements.collect(&:-@)]
111
+ end
112
+
113
+ def +(other)
114
+ v = if other.respond_to?(:each)
115
+ raise ArgumentError.new("Size mismatch: #{size} != #{other.size}") if size != other.size
116
+ other.each.zip(each).collect do |oe, se|
117
+ se + oe
118
+ end
119
+ else
120
+ each.collect do |e|
121
+ e + other
122
+ end
123
+ end
124
+
125
+ self.class[v]
126
+ end
127
+
128
+ def -(other)
129
+ v = if other.respond_to?(:each)
130
+ raise ArgumentError.new("Size mismatch #{size} != #{other.size}") if size != other.size
131
+ other.each.zip(each).collect do |oe, se|
132
+ se - oe
133
+ end
134
+ else
135
+ each.collect do |e|
136
+ e - other
137
+ end
138
+ end
139
+
140
+ self.class[v]
141
+ end
142
+
143
+ def size
144
+ @elements.size
145
+ end
146
+
147
+ def length
148
+ @elements.size
149
+ end
150
+
151
+ def *(other)
152
+ v = if other.respond_to?(:each)
153
+ raise ArgumentError.new("Size mismatch: #{size} != #{other.size}") if size != other.size
154
+ other.each.zip(each).collect do |oe, se|
155
+ se * oe
156
+ end
157
+ else
158
+ each.collect do |e|
159
+ e * other
160
+ end
161
+ end
162
+
163
+ self.class[v]
164
+ end
165
+
166
+ def /(other)
167
+ v = if other.respond_to?(:each)
168
+ raise ArgumentError.new("Size mismatch: #{size} != #{other.size}") if size != other.size
169
+ other.each.zip(each).collect do |oe, se|
170
+ se / oe
171
+ end
172
+ else
173
+ each.collect do |e|
174
+ e / other
175
+ end
176
+ end
177
+
178
+ self.class[v]
179
+ end
180
+
181
+ def ==(other)
182
+ other.size == size && each.zip(other.each).all? do |a, b|
183
+ a == b
184
+ end
185
+ end
186
+ end
187
+ end