require "tensor_stream/helpers/infer_shape" module TensorStream # TensorStream class that defines an operation class Operation < Tensor include OpHelper attr_accessor :name, :operation, :inputs, :rank, :device, :consumers, :breakpoint attr_reader :outputs, :options, :is_const, :data_type, :shape def initialize(graph, inputs: [], options: {}) @consumers = Set.new @outputs = [] @op = self @graph = graph @inputs = inputs @options = options end def inspect "Op(#{operation} name: #{name} shape: #{@shape || "?"} data_type: #{data_type})" end def to_s @name end def to_h { op: operation.to_s, name: name.to_s, data_type: @data_type, inputs: @inputs.map(&:name), attrs: serialize_options, } end def const_value @options ? @options[:value] : nil end def container_buffer @options[:container] ? @options[:container].buffer : nil end def set_input(index, value) @inputs[index] = value @shape = TensorShape.new(TensorStream::InferShape.infer_shape(self)) @rank = @shape.rank @is_const = infer_const @data_type = set_data_type(@options[:data_type]) end def set_option(key, value) @options.merge!(key.to_sym => value) end def infer_const return false if breakpoint case operation when :random_standard_normal, :random_uniform, :truncated_normal, :glorot_uniform, :print, :check_numerics false when :const true when :placeholder false when :variable_v2, :assign, :assign_add, :assign_sub false else non_const = @inputs.compact.find { |input| !input.is_const } non_const ? false : true end end def set_name @operation.to_s end def set_data_type(passed_data_type) case operation when :where @inputs[1].data_type when :case if @inputs[2] @inputs[2].data_type else @inputs[1].data_type end when :case_grad @inputs[2].data_type when :placeholder, :variable_v2, :const options[:data_type] when :fill @inputs[1].data_type when :logical_and :boolean when :shape, :rank, :shape_n options[:out_type] || :int32 when :zeros, :ones options[:dtype] || :float32 when :random_standard_normal, :random_uniform, :glorot_uniform, :truncated_normal passed_data_type || :float32 when :concat @inputs[1].data_type when :conv2d_backprop_input @inputs[1].data_type when :index if @inputs[0].is_a?(ControlFlow) if @inputs[1].is_const @inputs[0].inputs[@inputs[1].const_value].data_type else :unknown end else @inputs[0].data_type end else OpMaker.infer_data_type(self, self, passed_data_type) end end def to_math(name_only = false, max_depth = 99, cur_depth = 0) return @name if max_depth.zero? sub_input = auto_math(inputs[0], name_only, max_depth - 1, cur_depth + 1) sub_input2 = auto_math(inputs[1], name_only, max_depth - 1, cur_depth + 1) if inputs[1] out = case operation when :argmax "argmax(#{sub_input},#{options[:axis]})" when :negate "-#{sub_input}" when :index "#{sub_input}[#{sub_input2}]" when :slice "#{sub_input}[#{sub_input2}]" when :assign_sub "(#{inputs[0] ? inputs[0].name : "self"} -= #{auto_math(inputs[1], name_only, 1)})" when :assign_add "(#{inputs[0] ? inputs[0].name : "self"} += #{auto_math(inputs[1], name_only, 1)})" when :assign "(#{inputs[0] ? inputs[0].name : "self"} = #{auto_math(inputs[1], name_only, 1)})" when :sin, :cos, :tanh "#{operation}(#{sub_input})" when :add "(#{sub_input} + #{sub_input2})" when :sub "(#{sub_input} - #{sub_input2})" when :pow "(#{sub_input}^#{sub_input2})" when :div "(#{sub_input} / #{sub_input2})" when :mul if auto_math(inputs[0]) == 1 sub_input2 elsif auto_math(inputs[1]) == 1 sub_input else "(#{sub_input} * #{sub_input2})" end when :sum "sum(|#{sub_input}|, axis=#{sub_input2})" when :mean "mean(|#{sub_input}|, axis=#{sub_input2})" when :prod "prod(|#{sub_input}|, axis=#{sub_input2})" when :gradients "gradient(#{sub_input})" when :stop_gradient sub_input when :mat_mul "#{sub_input}.matmul(#{sub_input2})" when :eye "eye(#{sub_input})" when :transpose "transpose(#{sub_input})" when :shape "#{sub_input}.shape" when :exp "e^#{sub_input})" when :ones "ones(#{sub_input})" when :ones_like "ones_like(#{sub_input})" when :flow_group "flow_group(#{inputs.collect { |i| auto_math(i, name_only, max_depth - 1, cur_depth) }.join(",")})" when :zeros "zeros(#{sub_input})" when :reshape "reshape(#{sub_input},#{sub_input2})" when :rank "#{sub_input}.rank" when :less "#{sub_input} < #{sub_input2}" when :less_equal "#{sub_input} <= #{sub_input2}" when :greater "#{sub_input} > #{sub_input2}" when :greater_equal "#{sub_input} >= #{sub_input2}" when :square "#{sub_input}\u00B2" when :log "log(#{sub_input})" when :identity "identity(#{sub_input})" when :print "print(#{sub_input})" when :pad "pad(#{sub_input},#{auto_math(options[:paddings])})" when :equal "#{sub_input} == #{sub_input2}" when :not_equal "#{sub_input} != #{sub_input2}" when :logical_and "#{sub_input} && #{sub_input2}" when :sqrt "sqrt(#{sub_input})" when :log1p "log1p(#{sub_input})" when :zeros_like "zeros_like(#{sub_input})" when :where "where(#{auto_math(options[:pred], name_only, max_depth - 1, cur_depth)}, #{sub_input}, #{sub_input2})" when :max "max(#{sub_input},#{sub_input2})" when :cast "cast(#{sub_input}, #{data_type})" when :broadcast_transform "broadcast_transform(#{sub_input},#{sub_input2})" when :broadcast_gradient_args "broadcast_transform(#{sub_input},#{sub_input2})" else "#{operation}(#{sub_input})" if sub_input "#{operation}(#{sub_input}, #{sub_input2})" if sub_input && sub_input2 end ["\n", Array.new(cur_depth + 1) { " " }, out].flatten.join end def run eval end def op self end private def serialize_options excludes = %i[internal_name source] @options.reject { |k, v| excludes.include?(k) || v.nil? }.map { |k, v| v = case v.class.to_s when "TensorStream::TensorShape" v.shape when "Array" v when "String", "Integer", "Float", "Symbol", "FalseClass", "TrueClass" v when "TensorStream::Variable" {name: v.name, options: v.options, shape: v.shape.shape.dup} else raise "unknown type #{v.class}" end [k.to_sym, v] }.to_h end def add_consumer(consumer) @consumers << consumer.name if consumer.name != name end def setup_output(consumer) @outputs << consumer.name unless @outputs.include?(consumer.name) end def propagate_consumer(consumer) add_consumer(consumer) @inputs.compact.each do |input| if input.is_a?(Array) input.flatten.compact.map(&:op).select { |t| t.is_a?(Tensor) }.each do |t| next if t.consumers.include?(consumer.name) t.send(:propagate_consumer, consumer) end elsif input.name != name && !input.consumers.include?(consumer.name) input.send(:propagate_consumer, consumer) end end end def propagate_outputs @inputs.compact.each do |input| if input.is_a?(Array) input.flatten.compact.map(&:op).each do |t| t.send(:setup_output, self) if t.is_a?(Tensor) end elsif input.is_a?(Tensor) && (input.name != name) input.send(:setup_output, self) end end end def setup_initial_state(options) @outputs = [] @graph = options[:__graph] || TensorStream.get_default_graph @source = format_source(caller_locations) end end end