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,2 @@
1
+ require 'coo-coo/data_sources/xournal.rb'
2
+ require 'coo-coo/data_sources/xournal/training_document.rb'
@@ -0,0 +1,25 @@
1
+ require 'coo-coo/data_sources/xournal/document'
2
+ require 'coo-coo/data_sources/xournal/loader'
3
+ require 'coo-coo/data_sources/xournal/saver'
4
+ require 'coo-coo/data_sources/xournal/renderer'
5
+ require 'coo-coo/data_sources/xournal/training_document'
6
+
7
+ module CooCoo
8
+ module DataSources
9
+ module Xournal
10
+ # Load a Xournal from a file.
11
+ # @param path [String] The file's path.
12
+ # @return [Document]
13
+ def self.from_file(path)
14
+ Loader.from_file(path)
15
+ end
16
+
17
+ # Load Xournal from an XML string.
18
+ # @param xml [String] Unprocessed XML
19
+ # @return [Document]
20
+ def self.from_xml(xml)
21
+ Loader.from_xml(xml)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,197 @@
1
+ require 'pathname'
2
+ require 'coo-coo/math'
3
+ require 'coo-coo/data_sources/xournal/training_document'
4
+ require 'coo-coo/data_sources/xournal/renderer'
5
+ require 'coo-coo/drawing/cairo_canvas'
6
+
7
+ module CooCoo
8
+ module DataSources
9
+ module Xournal
10
+ class BitmapStream
11
+ attr_reader :training_documents
12
+ attr_reader :labels
13
+ attr_reader :example_width, :example_height
14
+ attr_accessor :canvas_klass
15
+ attr_accessor :pen_scale
16
+ attr_reader :use_color
17
+ attr_accessor :shuffle
18
+
19
+ def initialize(options = Hash.new)
20
+ @training_documents = Array.new
21
+ @document_paths = Array.new
22
+ @pen_scale = options.fetch(:pen_scale, 1.0)
23
+ @example_width = options.fetch(:width, 28)
24
+ @example_height = options.fetch(:height, 28)
25
+ @num_labels = options[:num_labels]
26
+ if options[:labels]
27
+ @labels = File.read(options[:labels]).split("\n")
28
+ else
29
+ @labels = Array.new
30
+ end
31
+ @canvas_klass = options.fetch(:canvas, Drawing::CairoCanvas)
32
+ @use_color = options.fetch(:use_color, false)
33
+ @shuffle = options.fetch(:shuffle, 16)
34
+
35
+ options[:training_documents].each do |td|
36
+ add_training_document(td)
37
+ end
38
+ end
39
+
40
+ def add_training_document(path_or_td)
41
+ td = case path_or_td
42
+ when String then TrainingDocument.from_file(path_or_td)
43
+ when Pathname then TrainingDocument.from_file(path_or_td.to_s)
44
+ when TrainingDocument then path_or_td
45
+ else raise ArgumentError.new("#{path_or_td.inspect} is not a String, Pathname, or TrainingDocument")
46
+ end
47
+ process_training_document(td)
48
+ @document_paths << path_or_td unless td == path_or_td
49
+ @training_documents << td
50
+ self
51
+ end
52
+
53
+ def process_training_document(td)
54
+ td.labels.each do |l|
55
+ add_label(l)
56
+ end
57
+ end
58
+
59
+ def size
60
+ training_documents.reduce(0) do |total, td|
61
+ total + td.each_example.reduce(0) do |subtotal, ex|
62
+ subtotal + ex.size
63
+ end
64
+ end
65
+ end
66
+
67
+ def input_size
68
+ example_width * example_height * (@use_color ? 3 : 1)
69
+ end
70
+
71
+ def output_size
72
+ Math.max(@labels.size, @num_labels)
73
+ end
74
+
75
+ def add_label(label)
76
+ @labels << label unless @labels.find_index(label)
77
+ self
78
+ end
79
+
80
+ def encode_label(label)
81
+ i = @labels.find_index(label)
82
+ v = Vector.zeros(output_size)
83
+ v[i] = 1.0
84
+ v
85
+ end
86
+
87
+ def decode_output(output)
88
+ @labels[output.each.with_index.max[1]]
89
+ end
90
+
91
+ def encode_strokes_to_canvas(strokes, canvas)
92
+ canvas.fill_color = 'white'
93
+ canvas.stroke_color = 'white'
94
+ canvas.rect(0, 0, @example_width, @example_height)
95
+ ren = Renderer.new
96
+
97
+ strokes.each do |stroke|
98
+ ren.render_stroke(canvas, stroke, 0, 0, 1, 1, @example_width, @example_height)
99
+ end
100
+ end
101
+
102
+ def encode_strokes(strokes, return_canvas = false)
103
+ canvas = @canvas_klass.new(@example_width, @example_height)
104
+ if pen_scale != 1.0
105
+ strokes = strokes.collect { |s| s.scale(1.0, 1.0, pen_scale) }
106
+ end
107
+
108
+ encode_strokes_to_canvas(strokes, canvas)
109
+
110
+ if return_canvas
111
+ canvas.flush
112
+ else
113
+ canvas.to_vector(!@use_color) / 256.0
114
+ end
115
+ end
116
+
117
+ def each(yield_canvas = false, &block)
118
+ return to_enum(__method__, yield_canvas) unless block_given?
119
+
120
+ training_documents.each do |td|
121
+ stroke_set = 0
122
+
123
+ loop do
124
+ td.each_example.each_slice(shuffle) do |slice|
125
+ examples = slice.collect do |ex|
126
+ strokes = ex.stroke_sets[stroke_set]
127
+ [ ex.label, strokes ] unless strokes.nil? || strokes.empty?
128
+ end.reject(&:nil?)
129
+
130
+ raise StopIteration if examples.empty?
131
+
132
+ examples.shuffle.each do |(label, strokes)|
133
+ yield(encode_label(label), encode_strokes(strokes, yield_canvas))
134
+ end
135
+ end
136
+
137
+ stroke_set += 1
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ if $0 != __FILE__
147
+ require 'ostruct'
148
+
149
+ @options = OpenStruct.new
150
+ @options.training_documents = Array.new
151
+ @options.labels_path = nil
152
+ @options.width = 28
153
+ @options.height = 28
154
+ @options.shuffle = 128
155
+
156
+ require 'coo-coo/option_parser'
157
+
158
+ @opts = CooCoo::OptionParser.new do |o|
159
+ o.banner = "Xournal Training Document Bitmap Stream Generator"
160
+
161
+ o.on('--data-path PATH', String, 'Adds a Xournal training document to be loaded.') do |p|
162
+ @options.training_documents += Dir.glob(p).to_a
163
+ end
164
+
165
+ o.on('--data-labels PATH', String, 'Predefined list of labels to preset the one hot encoding.') do |p|
166
+ @options.labels = p
167
+ end
168
+
169
+ o.on('--data-num-labels NUMBER', Integer, 'Minimum number of labels in the model') do |n|
170
+ @options.num_labels = n.to_i
171
+ end
172
+
173
+ o.on('--data-width NUMBER', Integer, 'Width in pixels of the generated bitmaps.') do |n|
174
+ n = n.to_i
175
+ raise ArgumentError.new('data-width must be > 0') if n <= 0
176
+ @options.width = n
177
+ end
178
+
179
+ o.on('--data-height NUMBER', Integer, 'Height in pixels of the generated bitmaps.') do |n|
180
+ n = n.to_i
181
+ raise ArgumentError.new('data-height must be > 0') if n <= 0
182
+ @options.height = n
183
+ end
184
+
185
+ o.on('--data-shuffle NUMBER', Integer, 'Number of examples to shuffle before yielding.') do |n|
186
+ n = n.to_i
187
+ raise ArgumentError.new('data-shuffle must be > 0') if n <= 0
188
+ @options.shuffle = n
189
+ end
190
+ end
191
+
192
+ def training_set
193
+ CooCoo::DataSources::Xournal::BitmapStream.new(@options.to_h)
194
+ end
195
+
196
+ [ method(:training_set), @opts ]
197
+ end
@@ -0,0 +1,377 @@
1
+ module CooCoo
2
+ module DataSources
3
+ module Xournal
4
+ Colors = %w(black blue red green gray lightblue lightgreen magenta orange yellow white)
5
+
6
+ # The root of a Xournal document. Each document contains multiple
7
+ # {Page pages} which contain {Layer layers} with actual ink {Stroke strokes}, {Text text}, and {Image images}.
8
+ #
9
+ # More information on what is allowed can be found at:
10
+ # {http://xournal.sourceforge.net/manual.html#file-format}
11
+ class Document
12
+ VERSION = '0.4.8'
13
+
14
+ attr_accessor :title
15
+ attr_accessor :version
16
+ attr_reader :pages
17
+
18
+ def initialize(title = "Untitled Document", version = VERSION)
19
+ @title = title
20
+ @version = version
21
+ @pages = Array.new
22
+ yield(self) if block_given?
23
+ end
24
+
25
+ def add_page(page)
26
+ @pages << page
27
+ self
28
+ end
29
+
30
+ def delete_page_at(page_num)
31
+ @pages.delete_at(page_num)
32
+ self
33
+ end
34
+
35
+ def delete_page(page)
36
+ @pages.delete(page)
37
+ self
38
+ end
39
+
40
+ def each_page(&block)
41
+ @pages.each(&block)
42
+ end
43
+
44
+ def size
45
+ @pages.size
46
+ end
47
+
48
+ def save(*args)
49
+ Saver.save(self, *args)
50
+ end
51
+ end
52
+
53
+ class Page
54
+ attr_accessor :width, :height, :background
55
+ attr_reader :layers
56
+
57
+ def initialize(width, height, background = Background::Default)
58
+ @width = width
59
+ @height = height
60
+ @background = background
61
+ @layers = Array.new
62
+ yield(self) if block_given?
63
+ end
64
+
65
+ def add_layer(layer)
66
+ @layers << layer
67
+ self
68
+ end
69
+
70
+ def delete_layer_at(layer)
71
+ @layers.delete_at(layer)
72
+ end
73
+
74
+ def delete_layer(layer)
75
+ @layers.delete(layer)
76
+ end
77
+
78
+ def each_layer(&block)
79
+ @layers.each(&block)
80
+ end
81
+ end
82
+
83
+ class Background
84
+ attr_accessor :color
85
+ attr_accessor :style
86
+
87
+ Styles = [ 'plain', 'lined', 'ruled', 'graph' ]
88
+
89
+ def initialize(color = 'white', style = 'plain')
90
+ self.color = color
91
+ self.style = style
92
+ end
93
+
94
+ def style=(s)
95
+ raise ArgumentError.new("Invalid style #{s}") unless s == nil || Styles.include?(s)
96
+ @style = s
97
+ end
98
+
99
+ Default = self.new
100
+ end
101
+
102
+ class PixmapBackground
103
+ attr_accessor :domain
104
+ attr_accessor :filename
105
+
106
+ Domains = [ 'absolute', 'attach', 'clone' ]
107
+
108
+ def initialize(filename, domain = 'attach')
109
+ self.filename = filename
110
+ self.domain = domain
111
+ end
112
+
113
+ def domain=(d)
114
+ raise ArgumentError.new("Invalid domain #{d}") unless d == nil || Domains.include?(d)
115
+ @domain = d
116
+ end
117
+ end
118
+
119
+ class PDFBackground
120
+ attr_accessor :domain
121
+ attr_accessor :filename
122
+ attr_accessor :page_number
123
+
124
+ Domains = [ 'absolute', 'attach' ]
125
+
126
+ def initialize(filename, page_number = nil, domain = 'attach')
127
+ self.filename = filename
128
+ self.domain = domain
129
+ self.page_number = page_number
130
+ end
131
+
132
+ def domain=(d)
133
+ raise ArgumentError.new("Invalid domain #{d}") unless d == nil || Domains.include?(d)
134
+ @domain = d
135
+ end
136
+ end
137
+
138
+ class Layer
139
+ attr_reader :children
140
+
141
+ def initialize
142
+ @children = Array.new
143
+ end
144
+
145
+ def each(&block)
146
+ @children.each(&block)
147
+ end
148
+
149
+ def delete_child_at(n)
150
+ @children.delete_at(n)
151
+ self
152
+ end
153
+
154
+ def delete_child(child)
155
+ @children.delete(child)
156
+ self
157
+ end
158
+
159
+ def add_stroke(stroke)
160
+ @children << stroke
161
+ self
162
+ end
163
+
164
+ def strokes
165
+ @children.select { |c| c.kind_of?(Stroke) }
166
+ end
167
+
168
+ def each_stroke(&block)
169
+ strokes.each(&block)
170
+ end
171
+
172
+ def add_text(text)
173
+ @children << text
174
+ self
175
+ end
176
+
177
+ def text
178
+ @children.select { |c| c.kind_of?(Text) }
179
+ end
180
+
181
+ def each_text(&block)
182
+ text.each(&block)
183
+ end
184
+
185
+ def add_image(img)
186
+ @children << img
187
+ end
188
+
189
+ def images
190
+ @children.select { |c| c.kind_of?(Image) }
191
+ end
192
+
193
+ def each_image(&block)
194
+ images.each(&block)
195
+ end
196
+ end
197
+
198
+ class Stroke
199
+ attr_reader :samples
200
+ attr_accessor :tool
201
+ attr_accessor :color
202
+
203
+ Tools = [ 'pen', 'highlighter', 'eraser' ]
204
+ DefaultTool = Tools.first
205
+
206
+ def initialize(tool = 'pen', color = 'black', samples = nil)
207
+ self.tool = tool
208
+ @color = color
209
+ @samples = samples || Array.new
210
+ end
211
+
212
+ def tool=(t)
213
+ raise ArgumentError.new("Invalid tool: #{t.inspect}") unless t == nil || Tools.include?(t)
214
+ @tool = t
215
+ end
216
+
217
+ def add_sample(x, y, w = 1)
218
+ @samples << Sample.new(x, y, w)
219
+ self
220
+ end
221
+
222
+ def delete_sample_at(n)
223
+ @samples.delete_at(n)
224
+ self
225
+ end
226
+
227
+ def delete_sample(sample)
228
+ @samples.delete(sample)
229
+ self
230
+ end
231
+
232
+ def size
233
+ @samples.size
234
+ end
235
+
236
+ def each_sample(&block)
237
+ @samples.each(&block)
238
+ end
239
+
240
+ def translate(dx, dy)
241
+ self.class.new(tool, color, samples.collect { |s| s.translate(dx, dy) })
242
+ end
243
+
244
+ def scale(sx, sy, sw = 1.0)
245
+ self.class.new(tool, color, samples.collect { |s| s.scale(sx, sy, sw) })
246
+ end
247
+
248
+ def minmax
249
+ xmin = nil
250
+ xmax = nil
251
+ ymin = nil
252
+ ymax = nil
253
+
254
+ xmin, xmax = @samples.collect(&:x).minmax
255
+ ymin, ymax = @samples.collect(&:y).minmax
256
+
257
+ [ [ xmin, ymin ], [ xmax, ymax ] ]
258
+ end
259
+ end
260
+
261
+ class Sample
262
+ attr_accessor :width, :x, :y
263
+
264
+ def initialize(x, y, width = nil)
265
+ @x = x
266
+ @y = y
267
+ @width = width
268
+ end
269
+
270
+ def translate(dx, dy)
271
+ self.class.new(x + dx, y + dy, width)
272
+ end
273
+
274
+ def scale(sx, sy, sw)
275
+ self.class.new(x * sx, y * sy, width * sw)
276
+ end
277
+ end
278
+
279
+ class Text
280
+ attr_accessor :text, :size, :x, :y, :color, :font
281
+
282
+ def initialize(text, x, y, size = 12, color = 'black', font = 'Sans')
283
+ @text = text
284
+ @x = x
285
+ @y = y
286
+ @size = size
287
+ @color = color
288
+ @font = font
289
+ end
290
+
291
+ def left
292
+ x
293
+ end
294
+
295
+ def top
296
+ y
297
+ end
298
+
299
+ def right
300
+ x + width
301
+ end
302
+
303
+ def width
304
+ # TODO but how?
305
+ @text.length * @size
306
+ end
307
+
308
+ def bottom
309
+ y + height
310
+ end
311
+
312
+ def height
313
+ @size * @text.count("\n")
314
+ end
315
+ end
316
+
317
+ class Image
318
+ attr_accessor :left, :right, :top, :bottom
319
+ attr_accessor :data, :raw_data
320
+
321
+ def initialize(left, top, right, bottom, data = nil)
322
+ @left = left.to_f
323
+ @top = top.to_f
324
+ @right = right.to_f
325
+ @bottom = bottom.to_f
326
+ self.data = data
327
+ end
328
+
329
+ def data=(data)
330
+ case data
331
+ when String then
332
+ data = Base64.decode64(data)
333
+ @data = decode_image(data) rescue nil
334
+ @raw_data = data
335
+ when ChunkyPNG::Image then
336
+ @data = data
337
+ @raw_data = nil
338
+ when nil then @data = @raw_data = nil
339
+ else @raw_data = data
340
+ end
341
+ end
342
+
343
+ def raw_data
344
+ @raw_data || @data.to_blob
345
+ end
346
+
347
+ def sized_data(zx = 1.0, zy = 1.0)
348
+ if zx == 1.0 && zy == 1.0
349
+ @sized_data ||= @data.resample_bilinear(width, height)
350
+ else
351
+ @data.resample_bilinear((width * zx).to_i, (height * zy).to_i)
352
+ end
353
+ end
354
+
355
+ def width
356
+ (right - left).to_i
357
+ end
358
+
359
+ def height
360
+ (bottom - top).to_i
361
+ end
362
+
363
+ def decode_image(data)
364
+ ChunkyPNG::Image.from_string(data)
365
+ end
366
+
367
+ def data_string
368
+ Base64.encode64(raw_data)
369
+ end
370
+
371
+ def raw_data
372
+ @raw_data ||= @data.to_s
373
+ end
374
+ end
375
+ end
376
+ end
377
+ end