| @@ -8,6 +8,10 @@ namespace Tensorflow.NumPy | |||
| { | |||
| public partial class np | |||
| { | |||
| [AutoNumPy] | |||
| public static NDArray concatenate((NDArray, NDArray) tuple, int axis = 0) | |||
| => new NDArray(array_ops.concat(new[] { tuple.Item1, tuple.Item2 }, axis)); | |||
| [AutoNumPy] | |||
| public static NDArray concatenate(NDArray[] arrays, int axis = 0) => new NDArray(array_ops.concat(arrays, axis)); | |||
| @@ -0,0 +1,43 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Text; | |||
| namespace Tensorflow.Keras.Callbacks | |||
| { | |||
| public class CallbackList | |||
| { | |||
| List<ICallback> callbacks = new List<ICallback>(); | |||
| public History History => callbacks[0] as History; | |||
| public CallbackList(CallbackParams parameters) | |||
| { | |||
| callbacks.Add(new History(parameters)); | |||
| callbacks.Add(new ProgbarLogger(parameters)); | |||
| } | |||
| public void on_train_begin() | |||
| { | |||
| callbacks.ForEach(x => x.on_train_begin()); | |||
| } | |||
| public void on_epoch_begin(int epoch) | |||
| { | |||
| callbacks.ForEach(x => x.on_epoch_begin(epoch)); | |||
| } | |||
| public void on_train_batch_begin(long step) | |||
| { | |||
| callbacks.ForEach(x => x.on_train_batch_begin(step)); | |||
| } | |||
| public void on_train_batch_end(long end_step, Dictionary<string, float> logs) | |||
| { | |||
| callbacks.ForEach(x => x.on_train_batch_end(end_step, logs)); | |||
| } | |||
| public void on_epoch_end(int epoch, Dictionary<string, float> epoch_logs) | |||
| { | |||
| callbacks.ForEach(x => x.on_epoch_end(epoch, epoch_logs)); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Text; | |||
| using Tensorflow.Keras.Engine; | |||
| namespace Tensorflow.Keras.Callbacks | |||
| { | |||
| public class CallbackParams | |||
| { | |||
| public IModel Model { get; set; } | |||
| public int Verbose { get; set; } | |||
| public int Epochs { get; set; } | |||
| public long Steps { get; set; } | |||
| } | |||
| } | |||
| @@ -0,0 +1,52 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Text; | |||
| namespace Tensorflow.Keras.Callbacks | |||
| { | |||
| public class History : ICallback | |||
| { | |||
| List<int> epochs; | |||
| CallbackParams _parameters; | |||
| public Dictionary<string, List<float>> history { get; set; } | |||
| public History(CallbackParams parameters) | |||
| { | |||
| _parameters = parameters; | |||
| } | |||
| public void on_train_begin() | |||
| { | |||
| epochs = new List<int>(); | |||
| history = new Dictionary<string, List<float>>(); | |||
| } | |||
| public void on_epoch_begin(int epoch) | |||
| { | |||
| } | |||
| public void on_train_batch_begin(long step) | |||
| { | |||
| } | |||
| public void on_train_batch_end(long end_step, Dictionary<string, float> logs) | |||
| { | |||
| } | |||
| public void on_epoch_end(int epoch, Dictionary<string, float> epoch_logs) | |||
| { | |||
| epochs.Add(epoch); | |||
| foreach (var log in epoch_logs) | |||
| { | |||
| if (!history.ContainsKey(log.Key)) | |||
| { | |||
| history[log.Key] = new List<float>(); | |||
| } | |||
| history[log.Key].Add((float)log.Value); | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Text; | |||
| namespace Tensorflow.Keras.Callbacks | |||
| { | |||
| public interface ICallback | |||
| { | |||
| void on_train_begin(); | |||
| void on_epoch_begin(int epoch); | |||
| void on_train_batch_begin(long step); | |||
| void on_train_batch_end(long end_step, Dictionary<string, float> logs); | |||
| void on_epoch_end(int epoch, Dictionary<string, float> epoch_logs); | |||
| } | |||
| } | |||
| @@ -0,0 +1,81 @@ | |||
| using PureHDF; | |||
| using System; | |||
| using System.Collections.Generic; | |||
| using System.Diagnostics; | |||
| using System.Linq; | |||
| using System.Text; | |||
| namespace Tensorflow.Keras.Callbacks | |||
| { | |||
| public class ProgbarLogger : ICallback | |||
| { | |||
| bool _called_in_fit = false; | |||
| int seen = 0; | |||
| CallbackParams _parameters; | |||
| Stopwatch _sw; | |||
| public ProgbarLogger(CallbackParams parameters) | |||
| { | |||
| _parameters = parameters; | |||
| } | |||
| public void on_train_begin() | |||
| { | |||
| _called_in_fit = true; | |||
| _sw = new Stopwatch(); | |||
| } | |||
| public void on_epoch_begin(int epoch) | |||
| { | |||
| _reset_progbar(); | |||
| _maybe_init_progbar(); | |||
| Binding.tf_output_redirect.WriteLine($"Epoch: {epoch + 1:D3}/{_parameters.Epochs:D3}"); | |||
| } | |||
| public void on_train_batch_begin(long step) | |||
| { | |||
| _sw.Restart(); | |||
| } | |||
| public void on_train_batch_end(long end_step, Dictionary<string, float> logs) | |||
| { | |||
| _sw.Stop(); | |||
| var elapse = _sw.ElapsedMilliseconds; | |||
| var results = string.Join(" - ", logs.Select(x => $"{x.Key}: {(float)x.Value:F6}")); | |||
| var progress = ""; | |||
| var length = 30.0 / _parameters.Steps; | |||
| for (int i = 0; i < Math.Floor(end_step * length - 1); i++) | |||
| progress += "="; | |||
| if (progress.Length < 28) | |||
| progress += ">"; | |||
| else | |||
| progress += "="; | |||
| var remaining = ""; | |||
| for (int i = 1; i < 30 - progress.Length; i++) | |||
| remaining += "."; | |||
| Binding.tf_output_redirect.Write($"{end_step + 1:D4}/{_parameters.Steps:D4} [{progress}{remaining}] - {elapse}ms/step - {results}"); | |||
| if (!Console.IsOutputRedirected) | |||
| { | |||
| Console.CursorLeft = 0; | |||
| } | |||
| } | |||
| public void on_epoch_end(int epoch, Dictionary<string, float> epoch_logs) | |||
| { | |||
| Console.WriteLine(); | |||
| } | |||
| void _reset_progbar() | |||
| { | |||
| seen = 0; | |||
| } | |||
| void _maybe_init_progbar() | |||
| { | |||
| } | |||
| } | |||
| } | |||
| @@ -31,7 +31,7 @@ namespace Tensorflow.Keras.Engine | |||
| bool use_multiprocessing = false, | |||
| bool return_dict = false) | |||
| { | |||
| data_handler = new DataHandler(new DataHandlerArgs | |||
| var data_handler = new DataHandler(new DataHandlerArgs | |||
| { | |||
| X = x, | |||
| Y = y, | |||
| @@ -46,7 +46,6 @@ namespace Tensorflow.Keras.Engine | |||
| StepsPerExecution = _steps_per_execution | |||
| }); | |||
| Binding.tf_output_redirect.WriteLine($"Testing..."); | |||
| foreach (var (epoch, iterator) in data_handler.enumerate_epochs()) | |||
| { | |||
| reset_metrics(); | |||
| @@ -56,22 +55,20 @@ namespace Tensorflow.Keras.Engine | |||
| foreach (var step in data_handler.steps()) | |||
| { | |||
| // callbacks.on_train_batch_begin(step) | |||
| results = test_function(iterator); | |||
| results = test_function(data_handler, iterator); | |||
| } | |||
| Binding.tf_output_redirect.WriteLine($"iterator: {epoch + 1}, " + string.Join(", ", results.Select(x => $"{x.Item1}: {(float)x.Item2}"))); | |||
| } | |||
| } | |||
| public KeyValuePair<string, float>[] evaluate(IDatasetV2 x) | |||
| { | |||
| data_handler = new DataHandler(new DataHandlerArgs | |||
| var data_handler = new DataHandler(new DataHandlerArgs | |||
| { | |||
| Dataset = x, | |||
| Model = this, | |||
| StepsPerExecution = _steps_per_execution | |||
| }); | |||
| Binding.tf_output_redirect.WriteLine($"Testing..."); | |||
| IEnumerable<(string, Tensor)> logs = null; | |||
| foreach (var (epoch, iterator) in data_handler.enumerate_epochs()) | |||
| { | |||
| @@ -82,22 +79,21 @@ namespace Tensorflow.Keras.Engine | |||
| foreach (var step in data_handler.steps()) | |||
| { | |||
| // callbacks.on_train_batch_begin(step) | |||
| logs = test_function(iterator); | |||
| logs = test_function(data_handler, iterator); | |||
| } | |||
| Binding.tf_output_redirect.WriteLine($"iterator: {epoch + 1}, " + string.Join(", ", logs.Select(x => $"{x.Item1}: {(float)x.Item2}"))); | |||
| } | |||
| return logs.Select(x => new KeyValuePair<string, float>(x.Item1, (float)x.Item2)).ToArray(); | |||
| } | |||
| IEnumerable<(string, Tensor)> test_function(OwnedIterator iterator) | |||
| IEnumerable<(string, Tensor)> test_function(DataHandler data_handler, OwnedIterator iterator) | |||
| { | |||
| var data = iterator.next(); | |||
| var outputs = test_step(data[0], data[1]); | |||
| var outputs = test_step(data_handler, data[0], data[1]); | |||
| tf_with(ops.control_dependencies(new object[0]), ctl => _test_counter.assign_add(1)); | |||
| return outputs; | |||
| } | |||
| List<(string, Tensor)> test_step(Tensor x, Tensor y) | |||
| List<(string, Tensor)> test_step(DataHandler data_handler, Tensor x, Tensor y) | |||
| { | |||
| (x, y) = data_handler.DataAdapter.Expand1d(x, y); | |||
| var y_pred = Apply(x, training: false); | |||
| @@ -5,6 +5,8 @@ using System.Linq; | |||
| using Tensorflow.Keras.ArgsDefinition; | |||
| using Tensorflow.Keras.Engine.DataAdapters; | |||
| using System.Diagnostics; | |||
| using Tensorflow.Keras.Callbacks; | |||
| using System.Data; | |||
| namespace Tensorflow.Keras.Engine | |||
| { | |||
| @@ -20,7 +22,7 @@ namespace Tensorflow.Keras.Engine | |||
| /// <param name="verbose"></param> | |||
| /// <param name="validation_split"></param> | |||
| /// <param name="shuffle"></param> | |||
| public void fit(NDArray x, NDArray y, | |||
| public History fit(NDArray x, NDArray y, | |||
| int batch_size = -1, | |||
| int epochs = 1, | |||
| int verbose = 1, | |||
| @@ -37,7 +39,7 @@ namespace Tensorflow.Keras.Engine | |||
| var val_x = x[new Slice(train_count)]; | |||
| var val_y = y[new Slice(train_count)]; | |||
| data_handler = new DataHandler(new DataHandlerArgs | |||
| var data_handler = new DataHandler(new DataHandlerArgs | |||
| { | |||
| X = train_x, | |||
| Y = train_y, | |||
| @@ -52,10 +54,10 @@ namespace Tensorflow.Keras.Engine | |||
| StepsPerExecution = _steps_per_execution | |||
| }); | |||
| FitInternal(epochs, verbose); | |||
| return FitInternal(data_handler, epochs, verbose); | |||
| } | |||
| public void fit(IDatasetV2 dataset, | |||
| public History fit(IDatasetV2 dataset, | |||
| IDatasetV2 validation_data = null, | |||
| int batch_size = -1, | |||
| int epochs = 1, | |||
| @@ -67,7 +69,7 @@ namespace Tensorflow.Keras.Engine | |||
| int workers = 1, | |||
| bool use_multiprocessing = false) | |||
| { | |||
| data_handler = new DataHandler(new DataHandlerArgs | |||
| var data_handler = new DataHandler(new DataHandlerArgs | |||
| { | |||
| Dataset = dataset, | |||
| BatchSize = batch_size, | |||
| @@ -81,67 +83,52 @@ namespace Tensorflow.Keras.Engine | |||
| StepsPerExecution = _steps_per_execution | |||
| }); | |||
| FitInternal(epochs, verbose); | |||
| return FitInternal(data_handler, epochs, verbose, validation_data: validation_data); | |||
| } | |||
| void FitInternal(int epochs, int verbose) | |||
| History FitInternal(DataHandler data_handler, int epochs, int verbose, IDatasetV2 validation_data = null) | |||
| { | |||
| stop_training = false; | |||
| _train_counter.assign(0); | |||
| Stopwatch sw = new Stopwatch(); | |||
| var callbacks = new CallbackList(new CallbackParams | |||
| { | |||
| Model = this, | |||
| Verbose = verbose, | |||
| Epochs = epochs, | |||
| Steps = data_handler.Inferredsteps | |||
| }); | |||
| callbacks.on_train_begin(); | |||
| foreach (var (epoch, iterator) in data_handler.enumerate_epochs()) | |||
| { | |||
| reset_metrics(); | |||
| on_epoch_begin(epoch, epochs); | |||
| callbacks.on_epoch_begin(epoch); | |||
| // data_handler.catch_stop_iteration(); | |||
| var logs = new Dictionary<string, float>(); | |||
| foreach (var step in data_handler.steps()) | |||
| { | |||
| sw.Start(); | |||
| var results = train_step_function(iterator); | |||
| sw.Stop(); | |||
| on_train_batch_begin(verbose, step, sw.ElapsedMilliseconds, results); | |||
| callbacks.on_train_batch_begin(step); | |||
| logs = train_step_function(data_handler, iterator); | |||
| var end_step = step + data_handler.StepIncrement; | |||
| callbacks.on_train_batch_end(end_step, logs); | |||
| } | |||
| // recycle memory more frequency | |||
| if (sw.ElapsedMilliseconds > 100) | |||
| if (validation_data != null) | |||
| { | |||
| var val_logs = evaluate(validation_data); | |||
| foreach(var log in val_logs) | |||
| { | |||
| GC.Collect(); | |||
| logs["val_" + log.Key] = log.Value; | |||
| } | |||
| sw.Reset(); | |||
| } | |||
| Console.WriteLine(); | |||
| callbacks.on_epoch_end(epoch, logs); | |||
| GC.Collect(); | |||
| GC.WaitForPendingFinalizers(); | |||
| } | |||
| } | |||
| void on_epoch_begin(int epoch, int epochs) | |||
| { | |||
| Binding.tf_output_redirect.WriteLine($"Epoch: {epoch + 1:D3}/{epochs:D3}"); | |||
| } | |||
| void on_train_batch_begin(int verbose, long step, long elapse, IEnumerable<(string, Tensor)> results) | |||
| { | |||
| if (verbose == 1) | |||
| { | |||
| var result_pairs = string.Join(", ", results.Select(x => $"{x.Item1}: {(float)x.Item2:F6}")); | |||
| var progress = ""; | |||
| for (int i = 0; i < step + 1; i++) | |||
| for (int j = 0; j < 30 / data_handler.Inferredsteps; j++) | |||
| progress += "="; | |||
| progress += ">"; | |||
| var remaining = ""; | |||
| for (int i = 1; i < 30 - progress.Length; i++) | |||
| remaining += "."; | |||
| Binding.tf_output_redirect.Write($"{step + 1:D4}/{data_handler.Inferredsteps:D4} [{progress}{remaining}] - {elapse}ms/step {result_pairs}"); | |||
| if (!Console.IsOutputRedirected) | |||
| { | |||
| Console.CursorLeft = 0; | |||
| } | |||
| } | |||
| return callbacks.History; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,6 +1,7 @@ | |||
| using System.Collections.Generic; | |||
| using System.Linq; | |||
| using Tensorflow.Gradients; | |||
| using Tensorflow.Keras.Engine.DataAdapters; | |||
| using Tensorflow.Keras.Optimizers; | |||
| using static Tensorflow.Binding; | |||
| @@ -8,10 +9,10 @@ namespace Tensorflow.Keras.Engine | |||
| { | |||
| public partial class Model | |||
| { | |||
| IEnumerable<(string, Tensor)> train_step_function(OwnedIterator iterator) | |||
| Dictionary<string, float> train_step_function(DataHandler data_handler, OwnedIterator iterator) | |||
| { | |||
| var data = iterator.next(); | |||
| var outputs = train_step(data[0], data[1]); | |||
| var outputs = train_step(data_handler, data[0], data[1]); | |||
| tf_with(ops.control_dependencies(new object[0]), ctl => _train_counter.assign_add(1)); | |||
| return outputs; | |||
| } | |||
| @@ -21,7 +22,7 @@ namespace Tensorflow.Keras.Engine | |||
| /// </summary> | |||
| /// <param name="data"></param> | |||
| /// <returns></returns> | |||
| List<(string, Tensor)> train_step(Tensor x, Tensor y) | |||
| Dictionary<string, float> train_step(DataHandler data_handler, Tensor x, Tensor y) | |||
| { | |||
| (x, y) = data_handler.DataAdapter.Expand1d(x, y); | |||
| using var tape = tf.GradientTape(); | |||
| @@ -37,7 +38,9 @@ namespace Tensorflow.Keras.Engine | |||
| _minimize(tape, optimizer, loss, TrainableVariables); | |||
| compiled_metrics.update_state(y, y_pred); | |||
| return metrics.Select(x => (x.Name, x.result())).ToList(); | |||
| var dict = new Dictionary<string, float>(); | |||
| metrics.ToList().ForEach(x => dict[x.Name] = (float)x.result()); | |||
| return dict; | |||
| } | |||
| void _minimize(GradientTape tape, OptimizerV2 optimizer, Tensor loss, List<IVariableV1> trainable_variables) | |||
| @@ -34,7 +34,6 @@ namespace Tensorflow.Keras.Engine | |||
| IVariableV1 _predict_counter; | |||
| bool _base_model_initialized; | |||
| bool stop_training; | |||
| DataHandler data_handler; | |||
| public Model(ModelArgs args) | |||
| : base(args) | |||
| @@ -147,7 +147,7 @@ namespace TensorFlowNET.Keras.UnitTest | |||
| Assert.AreEqual(expected_output, actual_output); | |||
| } | |||
| [TestMethod] | |||
| [TestMethod, Ignore("WIP")] | |||
| public void SimpleRNN() | |||
| { | |||
| tf.UseKeras<KerasInterface>(); | |||