require "bundler/setup"
require "tensor_stream"
# require 'tensor_stream/opencl'

# This neural network will predict the species of an iris based on sepal and petal size
# Dataset: http://en.wikipedia.org/wiki/Iris_flower_data_set
tf = TensorStream
rows = File.readlines("samples/datasets/iris.data").map {|l| l.chomp.split(",") }

rows.shuffle!

label_encodings = {
  "Iris-setosa" => [1, 0, 0],
  "Iris-versicolor" => [0, 1, 0],
  "Iris-virginica" => [0, 0, 1],
}

x_data = rows.map {|row| row[0, 4].map(&:to_f) }
y_data = rows.map {|row| label_encodings[row[4]] }

# Normalize data values before feeding into network
normalize = ->(val, high, low) { (val - low) / (high - low) } # maps input to float between 0 and 1

columns = (0..3).map { |i|
  x_data.map {|row| row[i] }
}

x_data.map! do |row|
  row.map.with_index do |val, j|
    max, min = columns[j].max, columns[j].min
    normalize.call(val, max, min)
  end
end

x_train = x_data.slice(0, 100)
y_train = y_data.slice(0, 100)

x_test = x_data.slice(100, 50)
y_test = y_data.slice(100, 50)

test_cases = []
x_train.each_with_index do |x, index|
  test_cases << [x, y_train[index]]
end

validation_cases = []
x_test.each_with_index do |x, index|
  validation_cases << [x, y_test[index]]
end

def init_weights(shape)
  # Weight initialization
  TensorStream.random_normal(shape, stddev: 0.1).var
end

def forwardprop(x, w_1, w_2)
  # Forward-propagation.
  # IMPORTANT: yhat is not softmax since TensorFlow's softmax_cross_entropy_with_logits() does that internally.
  h = TensorStream.nn.sigmoid(x.matmul(w_1)) # The \sigma function
  h.matmul w_2 # The \varphi function
end

x_size = x_train[0].size
y_size = y_train[0].size
h_size = 256
X = tf.placeholder(:float32, shape: [nil, x_size])
y = tf.placeholder(:float32, shape: [nil, y_size])

# Weight initializations
w_1 = init_weights([x_size, h_size])
w_2 = init_weights([h_size, y_size])

# Forward propagation
yhat    = forwardprop(X, w_1, w_2)
predict = tf.argmax(yhat, 1)

# Backward propagation
cost    = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels: y, logits: yhat))

# updates =  TensorStream::Train::GradientDescentOptimizer.new(0.01).minimize(cost)
# updates =  TensorStream::Train::MomentumOptimizer.new(0.01, 0.5, use_nesterov: true).minimize(cost)
updates = TensorStream::Train::RMSPropOptimizer.new(0.01).minimize(cost)

# Run SGD
sess = tf.session
init = tf.global_variables_initializer
sess.run(init)
loss = sess.run(cost, feed_dict: {X => x_test, y => y_test})
puts "loss test data set #{loss}"
loss = sess.run(cost, feed_dict: {X => x_train, y => y_train})
puts "Testing the untrained network..."
puts loss
start_time = Time.now
(0..100).each do |epoch|
  x_train.size.times do |i|
    sess.run(updates, feed_dict: {X => [x_train[i]], y => [y_train[i]]})
  end

  loss = sess.run(cost, feed_dict: {X => x_train, y => y_train})
  puts "epoch: #{epoch}, loss #{loss}"
end

loss = sess.run(cost, feed_dict: {X => x_train, y => y_train})
puts "loss after training #{loss}"
loss = sess.run(cost, feed_dict: {X => x_test, y => y_test})
puts "loss test data set #{loss}"
puts("time elapsed ", Time.now.to_i - start_time.to_i)
