tensor_stream 0.8.1 → 0.8.5
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +8 -0
- data/README.md +12 -6
- data/lib/tensor_stream.rb +1 -0
- data/lib/tensor_stream/evaluator/base_evaluator.rb +1 -1
- data/lib/tensor_stream/evaluator/ruby/array_ops.rb +282 -0
- data/lib/tensor_stream/evaluator/ruby/images_ops.rb +61 -0
- data/lib/tensor_stream/evaluator/ruby/math_ops.rb +111 -0
- data/lib/tensor_stream/evaluator/ruby/nn_ops.rb +48 -9
- data/lib/tensor_stream/evaluator/ruby/random_ops.rb +51 -0
- data/lib/tensor_stream/evaluator/ruby_evaluator.rb +20 -433
- data/lib/tensor_stream/images.rb +16 -0
- data/lib/tensor_stream/ops.rb +5 -1
- data/lib/tensor_stream/session.rb +15 -15
- data/lib/tensor_stream/tensor.rb +1 -1
- data/lib/tensor_stream/train/adadelta_optimizer.rb +52 -0
- data/lib/tensor_stream/train/adam_optimizer.rb +17 -2
- data/lib/tensor_stream/train/gradient_descent_optimizer.rb +7 -1
- data/lib/tensor_stream/trainer.rb +1 -0
- data/lib/tensor_stream/types.rb +4 -0
- data/lib/tensor_stream/utils.rb +4 -0
- data/lib/tensor_stream/variable_scope.rb +1 -0
- data/lib/tensor_stream/version.rb +1 -1
- data/samples/linear_regression.rb +4 -1
- data/samples/mnist_data.rb +64 -0
- data/samples/nearest_neighbor.rb +1 -2
- data/samples/raw_neural_net_sample.rb +1 -1
- data/tensor_stream.gemspec +1 -0
- metadata +23 -57
- data/lib/tensor_stream/evaluator/opencl/kernels/_bool_operand.cl +0 -45
- data/lib/tensor_stream/evaluator/opencl/kernels/_operand.cl +0 -45
- data/lib/tensor_stream/evaluator/opencl/kernels/abs.cl +0 -20
- data/lib/tensor_stream/evaluator/opencl/kernels/acos.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/add.cl +0 -3
- data/lib/tensor_stream/evaluator/opencl/kernels/apply_adam.cl +0 -23
- data/lib/tensor_stream/evaluator/opencl/kernels/apply_gradient.cl +0 -9
- data/lib/tensor_stream/evaluator/opencl/kernels/apply_momentum.cl +0 -16
- data/lib/tensor_stream/evaluator/opencl/kernels/argmax.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/argmin.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/asin.cl +0 -9
- data/lib/tensor_stream/evaluator/opencl/kernels/cast.cl +0 -10
- data/lib/tensor_stream/evaluator/opencl/kernels/ceil.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/cond.cl.erb +0 -6
- data/lib/tensor_stream/evaluator/opencl/kernels/cos.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/div.cl.erb +0 -3
- data/lib/tensor_stream/evaluator/opencl/kernels/exp.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/floor.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/floor_div.cl +0 -48
- data/lib/tensor_stream/evaluator/opencl/kernels/floor_mod.cl +0 -3
- data/lib/tensor_stream/evaluator/opencl/kernels/gemm.cl +0 -32
- data/lib/tensor_stream/evaluator/opencl/kernels/log.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/log1p.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/log_softmax.cl +0 -26
- data/lib/tensor_stream/evaluator/opencl/kernels/max.cl +0 -46
- data/lib/tensor_stream/evaluator/opencl/kernels/min.cl +0 -46
- data/lib/tensor_stream/evaluator/opencl/kernels/mod.cl +0 -3
- data/lib/tensor_stream/evaluator/opencl/kernels/mul.cl +0 -3
- data/lib/tensor_stream/evaluator/opencl/kernels/negate.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/pack.cl +0 -24
- data/lib/tensor_stream/evaluator/opencl/kernels/pow.cl +0 -46
- data/lib/tensor_stream/evaluator/opencl/kernels/real_div.cl +0 -3
- data/lib/tensor_stream/evaluator/opencl/kernels/reciprocal.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/round.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/sigmoid.cl +0 -9
- data/lib/tensor_stream/evaluator/opencl/kernels/sigmoid_grad.cl +0 -55
- data/lib/tensor_stream/evaluator/opencl/kernels/sign.cl +0 -21
- data/lib/tensor_stream/evaluator/opencl/kernels/sin.cl +0 -9
- data/lib/tensor_stream/evaluator/opencl/kernels/softmax.cl +0 -26
- data/lib/tensor_stream/evaluator/opencl/kernels/softmax_cross.cl +0 -32
- data/lib/tensor_stream/evaluator/opencl/kernels/softmax_cross_grad.cl +0 -28
- data/lib/tensor_stream/evaluator/opencl/kernels/softmax_grad.cl +0 -46
- data/lib/tensor_stream/evaluator/opencl/kernels/sqrt.cl +0 -9
- data/lib/tensor_stream/evaluator/opencl/kernels/square.cl +0 -9
- data/lib/tensor_stream/evaluator/opencl/kernels/squared_difference.cl +0 -53
- data/lib/tensor_stream/evaluator/opencl/kernels/sub.cl +0 -3
- data/lib/tensor_stream/evaluator/opencl/kernels/tan.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/tanh.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/kernels/tanh_grad.cl +0 -7
- data/lib/tensor_stream/evaluator/opencl/kernels/where.cl +0 -8
- data/lib/tensor_stream/evaluator/opencl/opencl_buffer.rb +0 -35
- data/lib/tensor_stream/evaluator/opencl/opencl_device.rb +0 -5
- data/lib/tensor_stream/evaluator/opencl/opencl_evaluator.rb +0 -1230
- data/lib/tensor_stream/evaluator/opencl/opencl_template_helper.rb +0 -95
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1d6e9e482de719b709bfe554085718cb01a3bbfd089983eef51ace418b1b7d2d
|
4
|
+
data.tar.gz: 287e63d7a7269e7143ef1016677297c44235a4ac6afb03234f72bb2b6774d348
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ec2af3376d4cc671eb7bfc549290145d3e14167fe3b472f1c4dd1a05a6ab78ddeddc608d6c1c26a93ddc542ce605341172212e7d5d2f5beeb2542ef3790b03f
|
7
|
+
data.tar.gz: 961e74c0acac0179affca04974f7933fb1162a1a88b3bcdde6c57f679db6d5e23c2e92dd4fff8454bda7ba87dc2eaad7f9424458440348151ea4ac90258a8acc
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
6
6
|
|
7
|
+
## [0.8.5] - 2018-09-06
|
8
|
+
|
9
|
+
### Added
|
10
|
+
- [TRAINING] Added AdadeltaOptimizer
|
11
|
+
- [NEW OP] squeeze, encode_png, decode_png
|
12
|
+
|
13
|
+
### Others
|
14
|
+
- The OpenCL evaluator has now been decoupled and is not on its own gem (tensor_stream-opencl)
|
7
15
|
|
8
16
|
## [0.8.1] - 2018-08-30
|
9
17
|
- [TRAINING] Added AdamOptimizer
|
data/README.md
CHANGED
@@ -22,10 +22,16 @@ The goal of this gem is to have a high performance machine learning and compute
|
|
22
22
|
TensorStream comes with a pure ruby and OpenCL implementation out of the box. The pure ruby implementation
|
23
23
|
is known to work with most ruby implementations including TruffleRuby, JRuby as well as jit enabled versions of mri (ruby-2.6.0).
|
24
24
|
|
25
|
-
OpenCL is supported only on mri implementations of ruby. This can be enabled by
|
25
|
+
OpenCL is supported only on mri implementations of ruby. This can be enabled by adding OpenCL evaluator gem (Make sure you have OpenCL drivers installed correctly on your system):
|
26
|
+
|
27
|
+
```Gemfile
|
28
|
+
gem 'tensor_stream-opencl'
|
29
|
+
```
|
30
|
+
|
31
|
+
and then (without bundler)
|
26
32
|
|
27
33
|
```ruby
|
28
|
-
require 'tensor_stream
|
34
|
+
require 'tensor_stream-opencl'
|
29
35
|
```
|
30
36
|
|
31
37
|
OpenCL is basically a requirement for deep learning and image processing tasks as the ruby implementation is too slow even with jit speedups using latest ruby implementations.
|
@@ -199,14 +205,14 @@ Also OpenCL only supports ruby-mri at the moment.
|
|
199
205
|
|
200
206
|
Also include the following gem in your project:
|
201
207
|
|
202
|
-
```
|
203
|
-
gem '
|
208
|
+
```Gemfile
|
209
|
+
gem 'tensor_stream-opencl'
|
204
210
|
```
|
205
211
|
|
206
|
-
To use the opencl evaluator instead of the ruby evaluator simply
|
212
|
+
To use the opencl evaluator instead of the ruby evaluator simply require it (if using rails this should be loaded automatically).
|
207
213
|
|
208
214
|
```ruby
|
209
|
-
require 'tensor_stream
|
215
|
+
require 'tensor_stream-opencl'
|
210
216
|
```
|
211
217
|
|
212
218
|
Adding the OpenCL evaluator should expose additional devices available to tensor_stream
|
data/lib/tensor_stream.rb
CHANGED
@@ -30,6 +30,7 @@ require 'tensor_stream/math_gradients'
|
|
30
30
|
require "tensor_stream/debugging/debugging"
|
31
31
|
require 'tensor_stream/utils'
|
32
32
|
require 'tensor_stream/train/utils'
|
33
|
+
require 'tensor_stream/images'
|
33
34
|
require 'tensor_stream/trainer'
|
34
35
|
|
35
36
|
# require 'tensor_stream/libraries/layers'
|
@@ -129,7 +129,7 @@ module TensorStream
|
|
129
129
|
def global_eval(tensor, input, execution_context, op_options = {})
|
130
130
|
return nil unless input
|
131
131
|
return input unless input.is_a?(Tensor)
|
132
|
-
|
132
|
+
@context[:_cache][:placement][input.name] = @session.assign_evaluator(input) if @context[:_cache][:placement][input.name].nil?
|
133
133
|
if object_id != @context[:_cache][:placement][input.name][1].object_id # tensor is on another device or evaluator
|
134
134
|
cache_key = "#{tensor.graph.object_id}_#{input.name}:#{object_id}"
|
135
135
|
return @context[:_cache][cache_key] if @context[:_cache].key?(cache_key)
|
@@ -0,0 +1,282 @@
|
|
1
|
+
module TensorStream
|
2
|
+
module ArrayOps
|
3
|
+
def ArrayOps.included(klass)
|
4
|
+
klass.class_eval do
|
5
|
+
register_op :slice do |context, tensor, inputs|
|
6
|
+
input = inputs[0]
|
7
|
+
start = inputs[1]
|
8
|
+
size = complete_eval(tensor.options[:size], context)
|
9
|
+
raise "start index and size not of the same shape #{start.size} != #{size.size}" if start.size != size.size
|
10
|
+
slice_tensor(input, start, size)
|
11
|
+
end
|
12
|
+
|
13
|
+
register_op %i[flow_dynamic_stitch dynamic_stitch] do |_context, _tensor, inputs|
|
14
|
+
indexes, data = inputs
|
15
|
+
merged = []
|
16
|
+
merge_dynamic_stitch(merged, indexes, data)
|
17
|
+
merged
|
18
|
+
end
|
19
|
+
|
20
|
+
register_op :gather do |_context, _tensor, inputs|
|
21
|
+
params, indexes = inputs
|
22
|
+
gather(params, indexes)
|
23
|
+
end
|
24
|
+
|
25
|
+
register_op %i[concat concat_v2] do |_context, tensor, inputs|
|
26
|
+
concat_array(inputs, tensor.options[:axis])
|
27
|
+
end
|
28
|
+
|
29
|
+
register_op :stack do |_context, tensor, inputs|
|
30
|
+
axis = tensor.options[:axis] || 0
|
31
|
+
shape = shape_eval(inputs[0])
|
32
|
+
rank = shape.size + 1
|
33
|
+
elem_size = shape.empty? ? 1 : shape.reduce(:*)
|
34
|
+
output_buffer = Array.new(inputs.size * elem_size) { 0 }
|
35
|
+
new_shape = [inputs.size]
|
36
|
+
shape.inject(new_shape) { |ns, s| ns << s }
|
37
|
+
|
38
|
+
divisors = new_shape.dup.drop(1).reverse.inject([1]) do |a, s|
|
39
|
+
a << s * a.last
|
40
|
+
end.reverse
|
41
|
+
|
42
|
+
axis = rank + axis if axis < 0
|
43
|
+
rotated_shape = Array.new(axis + 1) { new_shape.shift }
|
44
|
+
new_shape = rotated_shape.rotate! + new_shape
|
45
|
+
|
46
|
+
multipliers = new_shape.dup.drop(1).reverse.inject([1]) do |a, s|
|
47
|
+
a << s * a.last
|
48
|
+
end.reverse
|
49
|
+
|
50
|
+
inputs.each_with_index do |input, index|
|
51
|
+
raw_input = input.is_a?(Array) ? input.flatten : [input]
|
52
|
+
start = index * divisors.first
|
53
|
+
|
54
|
+
raw_input.each_with_index do |x, index2|
|
55
|
+
index_map = []
|
56
|
+
ptr = start + index2
|
57
|
+
divisors.each_with_object(index_map) do |div, a|
|
58
|
+
a << (ptr / div.to_f).floor
|
59
|
+
ptr = ptr % div
|
60
|
+
end
|
61
|
+
|
62
|
+
rotated_index = Array.new(axis + 1) { index_map.shift }
|
63
|
+
index_map = rotated_index.rotate! + index_map
|
64
|
+
|
65
|
+
ptr2 = 0
|
66
|
+
multipliers.each_with_index do |m, idx|
|
67
|
+
ptr2 += index_map[idx] * m
|
68
|
+
end
|
69
|
+
|
70
|
+
output_buffer[ptr2] = x
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
TensorShape.reshape(output_buffer, new_shape)
|
75
|
+
end
|
76
|
+
|
77
|
+
register_op :squeeze do |_context, tensor, inputs|
|
78
|
+
val = inputs[0]
|
79
|
+
shape = shape_eval(val)
|
80
|
+
|
81
|
+
axis = !tensor.options[:axis].is_a?(Array) ? [tensor.options[:axis]] : tensor.options[:axis]
|
82
|
+
|
83
|
+
if !axis.empty?
|
84
|
+
|
85
|
+
axis.each do |axis|
|
86
|
+
if shape[axis] == 1
|
87
|
+
shape[axis] = nil
|
88
|
+
else
|
89
|
+
raise TensorStream::ValueError, "unable to squeeze dimension that does not have a size of 1"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
else
|
93
|
+
shape = shape.map { |s| s == 1 ? nil : s }
|
94
|
+
end
|
95
|
+
|
96
|
+
TensorShape.reshape(val.flatten, shape.compact)
|
97
|
+
end
|
98
|
+
|
99
|
+
register_op :expand_dims do |_context, _tensor, inputs|
|
100
|
+
val, axis = inputs
|
101
|
+
axis = axis.nil? ? 0 : axis
|
102
|
+
|
103
|
+
shape = shape_eval(val)
|
104
|
+
axis = -axis if axis == shape.size
|
105
|
+
|
106
|
+
new_shape = shape.dup.insert(axis, 1).compact
|
107
|
+
|
108
|
+
TensorShape.reshape([val].flatten, new_shape)
|
109
|
+
end
|
110
|
+
|
111
|
+
register_op :fill do |_context, _tensor, inputs|
|
112
|
+
shape = inputs[0]
|
113
|
+
value = inputs[1]
|
114
|
+
|
115
|
+
func = -> { value }
|
116
|
+
|
117
|
+
if shape.is_a?(Array) && shape.size.zero?
|
118
|
+
func.call
|
119
|
+
else
|
120
|
+
shape = [shape.to_i] unless shape.is_a?(Array)
|
121
|
+
generate_vector(shape, generator: func)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
register_op :invert_permutation do |_context, _tensor, inputs|
|
126
|
+
input = inputs[0]
|
127
|
+
output = input.dup
|
128
|
+
|
129
|
+
unless input.nil?
|
130
|
+
input.size.times.each do |index|
|
131
|
+
output[input[index]] = index
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
output
|
136
|
+
end
|
137
|
+
|
138
|
+
register_op :index, no_eval: true do |_context, _tensor, inputs|
|
139
|
+
f = inputs[0]
|
140
|
+
index = inputs[1]
|
141
|
+
if f.is_a?(TensorStream::Evaluator::OutputGroup)
|
142
|
+
f.outputs[index]
|
143
|
+
else
|
144
|
+
f[index]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
register_op :setdiff1d do |_context, tensor, inputs|
|
149
|
+
input, remove = inputs
|
150
|
+
idx = []
|
151
|
+
out = []
|
152
|
+
input.each_with_index do |x, index|
|
153
|
+
next if remove.include?(x)
|
154
|
+
out << x
|
155
|
+
idx << index
|
156
|
+
end
|
157
|
+
idx = idx.map { |i| Tensor.cast_dtype(i, tensor.options[:index_dtype]) } unless tensor.options[:index_dtype] == :int32
|
158
|
+
TensorStream::Evaluator::OutputGroup.new([out, idx], tensor.inputs.map(&:data_type))
|
159
|
+
end
|
160
|
+
|
161
|
+
register_op :size do |_context, tensor, inputs|
|
162
|
+
input = inputs[0]
|
163
|
+
Tensor.cast_dtype(input.flatten.size, tensor.options[:out_type])
|
164
|
+
end
|
165
|
+
|
166
|
+
register_op :range do |_context, _tensor, inputs|
|
167
|
+
start, limit, delta = inputs
|
168
|
+
raise " delta !=0 " if delta.zero?
|
169
|
+
raise " Requires start <= limit when delta > 0" if (start > limit) && delta > 0
|
170
|
+
raise " Requires start >= limit when delta < 0" if (start < limit) && delta < 0
|
171
|
+
|
172
|
+
cur_step = start
|
173
|
+
r = []
|
174
|
+
Kernel.loop do
|
175
|
+
break if start == limit
|
176
|
+
break if (start < limit) && (cur_step >= limit)
|
177
|
+
break if (start > limit) && (cur_step <= limit)
|
178
|
+
r << cur_step
|
179
|
+
cur_step += delta
|
180
|
+
end
|
181
|
+
r
|
182
|
+
end
|
183
|
+
|
184
|
+
register_op :eye do |_context, tensor, inputs|
|
185
|
+
rows, columns = inputs
|
186
|
+
|
187
|
+
Array.new(rows) do |i|
|
188
|
+
Array.new(columns) do |col|
|
189
|
+
if fp_type?(tensor.data_type)
|
190
|
+
i == col ? 1.0 : 0.0
|
191
|
+
else
|
192
|
+
i == col ? 1 : 0
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
register_op %i[zeros ones zeros_like ones_like] do |_context, tensor, inputs|
|
199
|
+
shape = if %i[zeros_like ones_like].include?(tensor.operation)
|
200
|
+
shape_eval(inputs[0])
|
201
|
+
else
|
202
|
+
inputs[0] || tensor.shape.shape
|
203
|
+
end
|
204
|
+
|
205
|
+
func = if %i[zeros zeros_like].include?(tensor.operation)
|
206
|
+
-> { int_type?(tensor.data_type) ? 0 : 0.0 }
|
207
|
+
else
|
208
|
+
-> { int_type?(tensor.data_type) ? 1 : 1.0 }
|
209
|
+
end
|
210
|
+
|
211
|
+
if shape.is_a?(Array) && shape.size.zero?
|
212
|
+
func.call
|
213
|
+
else
|
214
|
+
shape = [shape.to_i] unless shape.is_a?(Array)
|
215
|
+
|
216
|
+
cache_key = "#{tensor.operation}_#{shape}"
|
217
|
+
if @context[:_cache].key?(cache_key)
|
218
|
+
@context[:_cache][cache_key]
|
219
|
+
else
|
220
|
+
generate_vector(shape, generator: func).tap do |v|
|
221
|
+
@context[:_cache][cache_key] = v
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
register_op :truncate do |_context, _tensor, inputs|
|
228
|
+
truncate(inputs[0], inputs[1])
|
229
|
+
end
|
230
|
+
|
231
|
+
register_op :rank do |_context, _tensor, inputs|
|
232
|
+
get_rank(inputs[0])
|
233
|
+
end
|
234
|
+
|
235
|
+
register_op :reshape do |_context, _tensor, inputs|
|
236
|
+
arr, new_shape = inputs
|
237
|
+
|
238
|
+
arr = [arr] unless arr.is_a?(Array)
|
239
|
+
|
240
|
+
flat_arr = arr.flatten
|
241
|
+
if new_shape.size.zero? && flat_arr.size == 1
|
242
|
+
flat_arr[0]
|
243
|
+
else
|
244
|
+
new_shape = TensorShape.fix_inferred_elements(new_shape, flat_arr.size)
|
245
|
+
TensorShape.reshape(flat_arr, new_shape)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
register_op :pad do |context, tensor, inputs|
|
250
|
+
p = complete_eval(tensor.options[:paddings], context)
|
251
|
+
|
252
|
+
arr_pad(inputs[0], p, tensor.data_type)
|
253
|
+
end
|
254
|
+
|
255
|
+
register_op :tile do |_context, _tensor, inputs|
|
256
|
+
input, multiples = inputs
|
257
|
+
rank = get_rank(input)
|
258
|
+
raise '1D or higher tensor required' if rank.zero?
|
259
|
+
raise "invalid multiple size passed #{rank} != #{multiples.size}" if rank != multiples.size
|
260
|
+
|
261
|
+
tile = tile_arr(input, 0, multiples)
|
262
|
+
tile.nil? ? [] : tile
|
263
|
+
end
|
264
|
+
|
265
|
+
register_op :cond, noop: true do |context, tensor, inputs|
|
266
|
+
pred = global_eval(tensor, tensor.options[:pred], context)
|
267
|
+
|
268
|
+
if all_true?(pred)
|
269
|
+
global_eval(tensor, inputs[0], context)
|
270
|
+
else
|
271
|
+
global_eval(tensor, inputs[1], context)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
register_op %i[select where] do |context, tensor, inputs|
|
276
|
+
pred = complete_eval(tensor.options[:pred], context)
|
277
|
+
call_3way_vector_op(pred, inputs[0], inputs[1], context, ->(t, u, v) { t ? u : v })
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'chunky_png'
|
2
|
+
|
3
|
+
module TensorStream
|
4
|
+
module ImagesOps
|
5
|
+
def ImagesOps.included(klass)
|
6
|
+
klass.class_eval do
|
7
|
+
register_op :decode_png do |_context, tensor, inputs|
|
8
|
+
content = inputs[0]
|
9
|
+
channels = tensor.options[:channels]
|
10
|
+
channels = 4 if channels.zero?
|
11
|
+
|
12
|
+
image = ChunkyPNG::Image.from_blob(content)
|
13
|
+
|
14
|
+
image.grayscale! if channels == 1
|
15
|
+
image_data = image.pixels.collect do |pixel|
|
16
|
+
color_values = if channels == 4
|
17
|
+
[ ChunkyPNG::Color.r(pixel),
|
18
|
+
ChunkyPNG::Color.g(pixel),
|
19
|
+
ChunkyPNG::Color.b(pixel),
|
20
|
+
ChunkyPNG::Color.a(pixel) ]
|
21
|
+
elsif channels == 3
|
22
|
+
[ ChunkyPNG::Color.r(pixel),
|
23
|
+
ChunkyPNG::Color.g(pixel),
|
24
|
+
ChunkyPNG::Color.b(pixel)]
|
25
|
+
elsif channels == 1
|
26
|
+
[ ChunkyPNG::Color.r(pixel) ]
|
27
|
+
else
|
28
|
+
raise "Invalid channel value #{channels}"
|
29
|
+
end
|
30
|
+
|
31
|
+
if fp_type?(tensor.data_type)
|
32
|
+
color_values.map! { |v| v.to_f }
|
33
|
+
end
|
34
|
+
|
35
|
+
color_values
|
36
|
+
end
|
37
|
+
TensorShape.reshape(image_data.flatten, [image.height, image.width, channels])
|
38
|
+
end
|
39
|
+
|
40
|
+
register_op :encode_png do |_context, tensor, inputs|
|
41
|
+
image_data = inputs[0]
|
42
|
+
height, width, channels = shape_eval(image_data)
|
43
|
+
|
44
|
+
png = ChunkyPNG::Image.new(width, height)
|
45
|
+
image_data.each_with_index do |rows, h_index|
|
46
|
+
rows.each_with_index do |p_data, w_index|
|
47
|
+
if channels == 4
|
48
|
+
png[w_index, h_index] = ChunkyPNG::Color.rgba(p_data[0], p_data[1], p_data[2], p_data[3])
|
49
|
+
elsif channels == 3
|
50
|
+
png[w_index, h_index] = ChunkyPNG::Color.rgb(p_data[0], p_data[1], p_data[2])
|
51
|
+
elsif channels == 1
|
52
|
+
png[w_index, h_index] = ChunkyPNG::Color.rgb(p_data[0], p_data[0], p_data[0])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
png.to_s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -138,6 +138,117 @@ module TensorStream
|
|
138
138
|
register_op :tanh_grad, no_eval: true do |context, _tensor, inputs|
|
139
139
|
call_op(:tanh_grad, inputs[0], context, ->(t, _b) { 1 - Math.tanh(t) * Math.tanh(t) })
|
140
140
|
end
|
141
|
+
|
142
|
+
register_op(%i[argmax arg_max]) do |_context, tensor, inputs|
|
143
|
+
axis = tensor.options[:axis] || 0
|
144
|
+
rank = get_rank(inputs[0])
|
145
|
+
raise TensorStream::InvalidArgumentError, "Expected dimension in the range [#{-rank},#{rank}) but got #{axis}" if axis < -rank || axis >= rank
|
146
|
+
get_op_with_axis(inputs[0], axis, 0, tensor.data_type)
|
147
|
+
end
|
148
|
+
|
149
|
+
register_op(%i[argmin arg_min]) do |_context, tensor, inputs|
|
150
|
+
axis = tensor.options[:axis] || 0
|
151
|
+
rank = get_rank(inputs[0])
|
152
|
+
raise TensorStream::InvalidArgumentError, "Expected dimension in the range [#{-rank},#{rank}) but got #{axis}" if axis < -rank || axis >= rank
|
153
|
+
get_op_with_axis(inputs[0], axis, 0, tensor.data_type, ->(a, b) { a < b })
|
154
|
+
end
|
155
|
+
|
156
|
+
register_op :cumprod do |context, tensor, inputs|
|
157
|
+
x = inputs[0]
|
158
|
+
c = fp_type?(tensor.data_type) ? 1.0 : 1
|
159
|
+
reverse_option = tensor.options[:reverse]
|
160
|
+
exclusive = tensor.options[:exclusive]
|
161
|
+
|
162
|
+
func = lambda do |arr|
|
163
|
+
return c if arr.nil?
|
164
|
+
count = arr.size
|
165
|
+
|
166
|
+
|
167
|
+
arr = arr.reverse if reverse_option
|
168
|
+
arr = [1] + arr if exclusive
|
169
|
+
|
170
|
+
start_prod = arr[0]
|
171
|
+
mapped = arr[1...count].map do |v|
|
172
|
+
start_prod = vector_op(start_prod, v, ->(a, b) { a * b })
|
173
|
+
end
|
174
|
+
|
175
|
+
arr = [arr[0]] + mapped
|
176
|
+
reverse_option ? arr.reverse : arr
|
177
|
+
end
|
178
|
+
reduction(context, tensor, func)
|
179
|
+
end
|
180
|
+
|
181
|
+
register_op :sum, noop: true do |context, tensor, _inputs|
|
182
|
+
func = lambda do |arr|
|
183
|
+
reduced_val = arr[0]
|
184
|
+
arr[1..arr.size].each do |v|
|
185
|
+
reduced_val = vector_op(reduced_val, v, ->(t, u) { t + u })
|
186
|
+
end
|
187
|
+
reduced_val
|
188
|
+
end
|
189
|
+
|
190
|
+
reduction(context, tensor, func)
|
191
|
+
end
|
192
|
+
|
193
|
+
register_op :prod, noop: true do |context, tensor, _inputs|
|
194
|
+
c = fp_type?(tensor.data_type) ? 1.0 : 1
|
195
|
+
func = lambda do |arr|
|
196
|
+
return c if arr.nil?
|
197
|
+
|
198
|
+
reduced_val = arr[0]
|
199
|
+
arr[1..arr.size].each do |v|
|
200
|
+
reduced_val = vector_op(reduced_val, v, ->(a, b) { a * b })
|
201
|
+
end
|
202
|
+
reduced_val
|
203
|
+
end
|
204
|
+
|
205
|
+
reduction(context, tensor, func)
|
206
|
+
end
|
207
|
+
|
208
|
+
register_op :sigmoid_grad, no_eval: true do |context, tensor, inputs|
|
209
|
+
a, b = inputs
|
210
|
+
call_vector_op(tensor, :sigmoid_grad, a, b, context, ->(t, u) { u * sigmoid(t) * (1 - sigmoid(t)) })
|
211
|
+
end
|
212
|
+
|
213
|
+
register_op :mean, noop: true do |context, tensor, _inputs|
|
214
|
+
c = fp_type?(tensor.data_type) ? 0.0 : 0
|
215
|
+
func = lambda do |arr|
|
216
|
+
return c if arr.nil?
|
217
|
+
|
218
|
+
reduced_val = arr[0]
|
219
|
+
arr[1..arr.size].each do |v|
|
220
|
+
reduced_val = vector_op(reduced_val, v, ->(a, b) { a + b })
|
221
|
+
end
|
222
|
+
|
223
|
+
vector_op(reduced_val, nil, ->(a, _b) { a / arr.size })
|
224
|
+
end
|
225
|
+
|
226
|
+
reduction(context, tensor, func)
|
227
|
+
end
|
228
|
+
|
229
|
+
register_op :mat_mul do |_context, tensor, inputs|
|
230
|
+
matrix_a, matrix_b = inputs
|
231
|
+
rank_a = get_rank(matrix_a)
|
232
|
+
rank_b = get_rank(matrix_b)
|
233
|
+
raise "#{tensor.inputs[0].name} rank must be greater than 1" if rank_a < 2
|
234
|
+
raise "#{tensor.inputs[1].name} rank must be greater than 1" if rank_b < 2
|
235
|
+
|
236
|
+
matrix_a = matrix_a.transpose if tensor.options[:transpose_a]
|
237
|
+
matrix_b = matrix_b.transpose if tensor.options[:transpose_b]
|
238
|
+
|
239
|
+
# check matrix dimensions
|
240
|
+
raise "incompatible shape sizes for matrix multiplication (#{matrix_a[0].size} != #{matrix_b.size}) #{shape_eval(matrix_a)} vs #{shape_eval(matrix_b)}" if matrix_a[0].size != matrix_b.size
|
241
|
+
|
242
|
+
(Matrix[*matrix_a] * Matrix[*matrix_b]).to_a
|
243
|
+
end
|
244
|
+
|
245
|
+
register_op %i[max maximum], noop: true do |context, tensor, inputs|
|
246
|
+
call_vector_op(tensor, :max, inputs[0], inputs[1], context, ->(t, u) { [t, u].max })
|
247
|
+
end
|
248
|
+
|
249
|
+
register_op %i[min minimum], noop: true do |context, tensor, inputs|
|
250
|
+
call_vector_op(tensor, :min, inputs[0], inputs[1], context, ->(t, u) { [t, u].min })
|
251
|
+
end
|
141
252
|
end
|
142
253
|
end
|
143
254
|
end
|