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