improve: use python service api to provide tensorflow instead of using tensorflow.net
This commit is contained in:
@@ -1,198 +0,0 @@
|
|||||||
using Tensorflow;
|
|
||||||
using Tensorflow.Gradients;
|
|
||||||
using Tensorflow.Keras.Engine;
|
|
||||||
using Tensorflow.NumPy;
|
|
||||||
using static Tensorflow.Binding;
|
|
||||||
using static Tensorflow.KerasApi;
|
|
||||||
namespace InverseOfLife;
|
|
||||||
|
|
||||||
public class NeuralSolver
|
|
||||||
{
|
|
||||||
private int Width { get; set; }
|
|
||||||
private int Height { get; set; }
|
|
||||||
private int Steps { get; set; }
|
|
||||||
private bool QuotientX { get; set; }
|
|
||||||
private bool QuotientY { get; set; }
|
|
||||||
private IOptimizer Optimizer { get; set; }
|
|
||||||
|
|
||||||
private IModel ForwardModel { get; set; }
|
|
||||||
private IModel ReverseModel { get; set; }
|
|
||||||
|
|
||||||
private void BuildForwardModel()
|
|
||||||
{
|
|
||||||
Tensors inputs = keras.Input(shape: new Shape(Height, Width, 1), name: "InitialState");
|
|
||||||
Tensors hidden = keras.layers.Conv2D(32, kernel_size: 3, padding:"same", activation: keras.activations.Relu).Apply(inputs);
|
|
||||||
hidden = keras.layers.Conv2D(32, kernel_size: 3, padding: "same", activation: keras.activations.Relu).Apply(hidden);
|
|
||||||
Tensors outputs = keras.layers.Conv2D(1, kernel_size: 1, padding: "same", activation: keras.activations.Sigmoid).Apply(hidden);
|
|
||||||
ForwardModel = keras.Model(inputs, outputs, name: "ForwardModel");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BuildReverseModel()
|
|
||||||
{
|
|
||||||
Tensors inputs = keras.Input(shape: new Shape(Height, Width, 1), name: "FinalState");
|
|
||||||
Tensors hidden = keras.layers.Conv2D(32, kernel_size: 3, padding: "same", activation: keras.activations.Relu).Apply(inputs);
|
|
||||||
hidden = keras.layers.Conv2D(32, kernel_size:3, padding:"same", activation: keras.activations.Relu).Apply(hidden);
|
|
||||||
Tensors outputs = keras.layers.Conv2D(1, kernel_size: 1, padding:"same", activation: keras.activations.Sigmoid).Apply(hidden);
|
|
||||||
ReverseModel = keras.Model(inputs, outputs, name: "ReverseModel");
|
|
||||||
}
|
|
||||||
|
|
||||||
public NeuralSolver(int width, int height, int steps, bool quotientX, bool quotientY)
|
|
||||||
{
|
|
||||||
Width = width;
|
|
||||||
Height = height;
|
|
||||||
Steps = steps;
|
|
||||||
QuotientX = quotientX;
|
|
||||||
QuotientY = quotientY;
|
|
||||||
BuildForwardModel();
|
|
||||||
BuildReverseModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveModel(string basePath)
|
|
||||||
{
|
|
||||||
ForwardModel.save($"{basePath}/FM{Width}x{Height}_{Steps}_{QuotientX}_{QuotientY}");
|
|
||||||
ReverseModel.save($"{basePath}/RM{Width}x{Height}_{Steps}_{QuotientX}_{QuotientY}");
|
|
||||||
}
|
|
||||||
public void LoadModel(string basePath)
|
|
||||||
{
|
|
||||||
ForwardModel = keras.models.load_model($"{basePath}/FM{Width}x{Height}_{Steps}_{QuotientX}_{QuotientY}");
|
|
||||||
ReverseModel = keras.models.load_model($"{basePath}/RM{Width}x{Height}_{Steps}_{QuotientX}_{QuotientY}");
|
|
||||||
}
|
|
||||||
public (NDArray, NDArray) GenerateTrainingData(int datasetSize)
|
|
||||||
{
|
|
||||||
Random rnd = new Random();
|
|
||||||
|
|
||||||
float[] inputsData = new float[datasetSize * Height * Width];
|
|
||||||
float[] labelsData = new float[datasetSize * Height * Width];
|
|
||||||
|
|
||||||
for (int idx = 0; idx < datasetSize; idx++)
|
|
||||||
{
|
|
||||||
Board board = new Board(Width, Height, QuotientX, QuotientY);
|
|
||||||
int randomCells = rnd.Next(1, Width * Height / 4);
|
|
||||||
for (int c = 0; c < randomCells; c++)
|
|
||||||
{
|
|
||||||
int x = rnd.Next(0, Width);
|
|
||||||
int y = rnd.Next(0, Height);
|
|
||||||
board.Toggle(x, y, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
int offsetLabel = idx * Width * Height;
|
|
||||||
foreach ( (int x, int y) in board.Lives)
|
|
||||||
{
|
|
||||||
int pos = y * Width + x;
|
|
||||||
labelsData[offsetLabel + pos] = 1f;
|
|
||||||
}
|
|
||||||
|
|
||||||
board.Evaluate(Steps);
|
|
||||||
|
|
||||||
int offsetInput = idx * Width * Height;
|
|
||||||
foreach (var (x, y) in board.Lives)
|
|
||||||
inputsData[offsetInput + (y * Width + x)] = 1f;
|
|
||||||
}
|
|
||||||
NDArray inputsTensor = np.array(inputsData).reshape(new Shape(datasetSize, Height, Width, 1));
|
|
||||||
NDArray labelsTensor = np.array(labelsData).reshape(new Shape(datasetSize, Height, Width, 1));
|
|
||||||
return (inputsTensor, labelsTensor);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Train(int datasetSize = 1000, int batchSize = 8, int epochs = 10)
|
|
||||||
{
|
|
||||||
(NDArray trainFinal, NDArray trainInitial) = GenerateTrainingData(datasetSize);
|
|
||||||
Optimizer = keras.optimizers.Adam(learning_rate: 0.001f);
|
|
||||||
for (int epoch = 0; epoch < epochs; epoch++)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < datasetSize; i += batchSize)
|
|
||||||
{
|
|
||||||
NDArray initialBatch = trainInitial[$"{i}:{i + batchSize}"];
|
|
||||||
NDArray finalBatch = trainFinal[$"{i}:{i + batchSize}"];
|
|
||||||
using (GradientTape tape = tf.GradientTape())
|
|
||||||
{
|
|
||||||
Tensors predictedFinal = ForwardModel.Apply(initialBatch);
|
|
||||||
Tensors predictedInitial = ReverseModel.Apply(finalBatch);
|
|
||||||
Tensors reconstructedFinal = ForwardModel.Apply(predictedInitial);
|
|
||||||
|
|
||||||
Tensor forwardLoss = keras.losses.BinaryCrossentropy().Call(finalBatch, predictedFinal);
|
|
||||||
Tensor reverseLoss = keras.losses.BinaryCrossentropy().Call(initialBatch, predictedInitial);
|
|
||||||
Tensor cycleLoss = keras.losses.BinaryCrossentropy().Call(finalBatch, reconstructedFinal);
|
|
||||||
Tensor totalLoss = forwardLoss + reverseLoss + cycleLoss;
|
|
||||||
Tensor[] gradients = tape.gradient(totalLoss, ForwardModel.TrainableVariables.Concat(ReverseModel.TrainableVariables));
|
|
||||||
Optimizer.apply_gradients(zip(gradients, ForwardModel.TrainableVariables.Concat(ReverseModel.TrainableVariables)));
|
|
||||||
Console.WriteLine($"Epoch {epoch + 1}, Batch {i / batchSize + 1}, Loss: {totalLoss.numpy()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Board Predict(Board target)
|
|
||||||
{
|
|
||||||
float[] inputData = new float[Height * Width];
|
|
||||||
foreach (var (x, y) in target.Lives)
|
|
||||||
inputData[y * Width + x] = 1f;
|
|
||||||
NDArray input = np.array(inputData).reshape(new Shape(1, Height, Width, 1));
|
|
||||||
Tensors pred = ReverseModel.predict(input);
|
|
||||||
float[] predData = pred.ToArray<float>();
|
|
||||||
Board res = new Board(Width, Height);
|
|
||||||
for (int i = 0; i < predData.Length; i++)
|
|
||||||
{
|
|
||||||
int x = i % Width;
|
|
||||||
int y = i / Width;
|
|
||||||
if (predData[i] > 0.5f)
|
|
||||||
res.Lives.Add((x, y));
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<(int, int)> Circle()
|
|
||||||
{
|
|
||||||
int centerX = 10;
|
|
||||||
int centerY = 10;
|
|
||||||
int radius = 7;
|
|
||||||
int x = 0;
|
|
||||||
int y = radius;
|
|
||||||
|
|
||||||
int d = 1 - radius;
|
|
||||||
|
|
||||||
IEnumerable<(int, int)> PlotCirclePoints(int cx, int cy, int px, int py)
|
|
||||||
{
|
|
||||||
yield return (cx + px, cy + py);
|
|
||||||
yield return (cx - px, cy + py);
|
|
||||||
yield return (cx + px, cy - py);
|
|
||||||
yield return (cx - px, cy - py);
|
|
||||||
yield return (cx + py, cy + px);
|
|
||||||
yield return (cx - py, cy + px);
|
|
||||||
yield return (cx + py, cy - px);
|
|
||||||
yield return (cx - py, cy - px);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var point in PlotCirclePoints(centerX, centerY, x, y))
|
|
||||||
yield return point;
|
|
||||||
|
|
||||||
while (x < y)
|
|
||||||
{
|
|
||||||
x++;
|
|
||||||
if (d < 0)
|
|
||||||
d += 2 * x + 1;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
y--;
|
|
||||||
d += 2 * (x - y) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var point in PlotCirclePoints(centerX, centerY, x, y))
|
|
||||||
yield return point;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static void Run()
|
|
||||||
{
|
|
||||||
NeuralSolver solver = new NeuralSolver(20, 20, 10, false, false);
|
|
||||||
solver.Train(1000,8,20);
|
|
||||||
Board b = new Board(20, 20);
|
|
||||||
foreach ((int, int) cell in Circle())
|
|
||||||
b.Toggle(cell);
|
|
||||||
b.Evaluate(10);
|
|
||||||
Board z = solver.Predict(b);
|
|
||||||
Console.WriteLine(z.ToString());
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
188
src/NeuralSolver/NeuralSolver.cs
Normal file
188
src/NeuralSolver/NeuralSolver.cs
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using InverseOfLife.NeuralSolver.Requests;
|
||||||
|
using InverseOfLife.NeuralSolver.Responses;
|
||||||
|
|
||||||
|
namespace InverseOfLife.NeuralSolver;
|
||||||
|
|
||||||
|
public class NeuralSolver : IDisposable
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private int Width { get; set; }
|
||||||
|
private int Height { get; set; }
|
||||||
|
private bool QuotientX { get; set; }
|
||||||
|
private bool QuotientY { get; set; }
|
||||||
|
private string NeuralBackend { get; set; }
|
||||||
|
private HttpClient HttpClient { get; set; }
|
||||||
|
|
||||||
|
public NeuralSolver(int width, int height, bool quotientX = false, bool quotientY = false,
|
||||||
|
string neuralBackend = "http://localhost:8000")
|
||||||
|
{
|
||||||
|
Width = width;
|
||||||
|
Height = height;
|
||||||
|
QuotientX = quotientX;
|
||||||
|
QuotientY = quotientY;
|
||||||
|
NeuralBackend = neuralBackend;
|
||||||
|
HttpClient = new HttpClient();
|
||||||
|
Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
InitRequest initRequest = new InitRequest
|
||||||
|
{
|
||||||
|
Width = Width,
|
||||||
|
Height = Height,
|
||||||
|
QuotientX = QuotientX,
|
||||||
|
QuotientY = QuotientY
|
||||||
|
};
|
||||||
|
string json = JsonSerializer.Serialize(initRequest);
|
||||||
|
Console.WriteLine(json);
|
||||||
|
StringContent content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
HttpResponseMessage response = HttpClient.PostAsync($"{NeuralBackend}/initialize", content).Result;
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Train(TrainRequest request)
|
||||||
|
{
|
||||||
|
string jsonContent = JsonSerializer.Serialize(request);
|
||||||
|
StringContent content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||||
|
HttpResponseMessage response = HttpClient.PostAsync($"{NeuralBackend}/train", content).Result;
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
string json = response.Content.ReadAsStringAsync().Result;
|
||||||
|
StatusResponse statusResponse = JsonSerializer.Deserialize<StatusResponse>(json);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
string status = BackendStatus();
|
||||||
|
if (status == "ready" || status == "error")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Board Predict(PredictRequest request)
|
||||||
|
{
|
||||||
|
string jsonContent = JsonSerializer.Serialize(request);
|
||||||
|
Console.WriteLine(jsonContent);
|
||||||
|
StringContent content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
|
||||||
|
HttpResponseMessage response = HttpClient.PostAsync($"{NeuralBackend}/predict", content).Result;
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
string json = response.Content.ReadAsStringAsync().Result;
|
||||||
|
PredictResponse res = JsonSerializer.Deserialize<PredictResponse>(json);
|
||||||
|
Board board = new Board(Width, Height, QuotientX, QuotientY);
|
||||||
|
foreach (List<int> cell in res.Lives)
|
||||||
|
board.Lives.Add((cell[0], cell[1]));
|
||||||
|
return board;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BackendStatus()
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = HttpClient.GetAsync($"{NeuralBackend}/status").Result;
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
string json = response.Content.ReadAsStringAsync().Result;
|
||||||
|
return JsonSerializer.Deserialize<StatusResponse>(
|
||||||
|
json,
|
||||||
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true }
|
||||||
|
).Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveModel()
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = HttpClient.PostAsync($"{NeuralBackend}/save", null).Result;
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
string json = response.Content.ReadAsStringAsync().Result;
|
||||||
|
StatusResponse res = JsonSerializer.Deserialize<StatusResponse>(json);
|
||||||
|
if (res.Status != "ok")
|
||||||
|
throw new Exception(res.Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadModel()
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = HttpClient.PostAsync($"{NeuralBackend}/load", null).Result;
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
string json = response.Content.ReadAsStringAsync().Result;
|
||||||
|
StatusResponse res = JsonSerializer.Deserialize<StatusResponse>(json);
|
||||||
|
if (res.Status != "ok")
|
||||||
|
throw new Exception(res.Status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<(int, int)> Circle()
|
||||||
|
{
|
||||||
|
int centerX = 10;
|
||||||
|
int centerY = 10;
|
||||||
|
int radius = 7;
|
||||||
|
int x = 0;
|
||||||
|
int y = radius;
|
||||||
|
|
||||||
|
int d = 1 - radius;
|
||||||
|
|
||||||
|
IEnumerable<(int, int)> PlotCirclePoints(int cx, int cy, int px, int py)
|
||||||
|
{
|
||||||
|
yield return (cx + px, cy + py);
|
||||||
|
yield return (cx - px, cy + py);
|
||||||
|
yield return (cx + px, cy - py);
|
||||||
|
yield return (cx - px, cy - py);
|
||||||
|
yield return (cx + py, cy + px);
|
||||||
|
yield return (cx - py, cy + px);
|
||||||
|
yield return (cx + py, cy - px);
|
||||||
|
yield return (cx - py, cy - px);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var point in PlotCirclePoints(centerX, centerY, x, y))
|
||||||
|
yield return point;
|
||||||
|
|
||||||
|
while (x < y)
|
||||||
|
{
|
||||||
|
x++;
|
||||||
|
if (d < 0)
|
||||||
|
d += 2 * x + 1;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
y--;
|
||||||
|
d += 2 * (x - y) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var point in PlotCirclePoints(centerX, centerY, x, y))
|
||||||
|
yield return point;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Test()
|
||||||
|
{
|
||||||
|
using (NeuralSolver s = new NeuralSolver(20, 20, false, false))
|
||||||
|
{
|
||||||
|
//s.Initialize();
|
||||||
|
s.Train(new TrainRequest
|
||||||
|
{
|
||||||
|
ForwardDatasetSize = 2000,
|
||||||
|
ForwardBatchSize = 16,
|
||||||
|
ForwardEpochs = 15,
|
||||||
|
BackwardDatasetSize = 4000,
|
||||||
|
BackwardBatchSize = 16,
|
||||||
|
BackwardEpochs = 20,
|
||||||
|
});
|
||||||
|
Board b = new Board(20, 20);
|
||||||
|
foreach ((int, int) p in Circle())
|
||||||
|
b.Lives.Add(p);
|
||||||
|
b.Evaluate();
|
||||||
|
Board h = s.Predict(new PredictRequest
|
||||||
|
{
|
||||||
|
Lives = b.Lives.Select(((int x, int y) cell) => new List<int>{cell.x, cell.y}).ToList(),
|
||||||
|
Direction = "backward"
|
||||||
|
});
|
||||||
|
Console.WriteLine(h.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = HttpClient.PostAsync($"{NeuralBackend}/finish", null).Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
9
src/NeuralSolver/Requests/InitRequest.cs
Normal file
9
src/NeuralSolver/Requests/InitRequest.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace InverseOfLife.NeuralSolver.Requests;
|
||||||
|
|
||||||
|
public class InitRequest
|
||||||
|
{
|
||||||
|
public int Width { get; set; }
|
||||||
|
public int Height { get; set; }
|
||||||
|
public bool QuotientX { get; set; } = false;
|
||||||
|
public bool QuotientY { get; set; } = false;
|
||||||
|
}
|
||||||
7
src/NeuralSolver/Requests/PredictRequest.cs
Normal file
7
src/NeuralSolver/Requests/PredictRequest.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace InverseOfLife.NeuralSolver.Requests;
|
||||||
|
|
||||||
|
public class PredictRequest
|
||||||
|
{
|
||||||
|
public List<List<int>> Lives { get; set; }
|
||||||
|
public string Direction { get; set; } = "forward";
|
||||||
|
}
|
||||||
11
src/NeuralSolver/Requests/TrainRequest.cs
Normal file
11
src/NeuralSolver/Requests/TrainRequest.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace InverseOfLife.NeuralSolver.Requests;
|
||||||
|
|
||||||
|
public class TrainRequest
|
||||||
|
{
|
||||||
|
public int ForwardDatasetSize { get; set; } = 1000;
|
||||||
|
public int ForwardBatchSize { get; set; } = 8;
|
||||||
|
public int ForwardEpochs { get; set; } = 10;
|
||||||
|
public int BackwardDatasetSize { get; set; } = 1000;
|
||||||
|
public int BackwardBatchSize { get; set; } = 8;
|
||||||
|
public int BackwardEpochs { get; set; } = 10;
|
||||||
|
}
|
||||||
6
src/NeuralSolver/Responses/PredictResponse.cs
Normal file
6
src/NeuralSolver/Responses/PredictResponse.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace InverseOfLife.NeuralSolver.Responses;
|
||||||
|
|
||||||
|
public class PredictResponse
|
||||||
|
{
|
||||||
|
public List<List<int>> Lives { get; set; }
|
||||||
|
}
|
||||||
6
src/NeuralSolver/Responses/StatusResponse.cs
Normal file
6
src/NeuralSolver/Responses/StatusResponse.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace InverseOfLife.NeuralSolver.Responses;
|
||||||
|
|
||||||
|
public class StatusResponse
|
||||||
|
{
|
||||||
|
public string Status { get; set; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user