Building and Loading a Graph
The basic unit of getting output out of SmartEngine is a Graph. A graph contains input buffers and connected nodes that produce output.
This code demonstrates how to load and evaluate a graph at the lowest level. The graph definition can alternatively come from a json resource file. While the below code will work, it is recommended to use the Network and NetworkController functionality in the SmartEngine.Helpers namespace for graph lifetime control and evaluation. See the example projects for usage.
This code can be found in SmartEngine/Examples/QuickStart.cs
const string cGraphDefinition = @"
{
""Nodes"": [
{
""Name"": ""Input"",
""Type"": ""BufferInput"",
""Parameters"": {
""Dimension"": 2
}
},
{
""Name"": ""TrainingOutput"",
""Type"": ""BufferInput"",
""Parameters"": {
""Dimension"": 1
}
},
{
""Name"": ""LinearLayer1"",
""Type"": ""NeuronLayer"",
""Parameters"": {
""Input"": ""Input"",
""Type"": ""Linear"",
""ActivationType"": ""Selu"",
""NeuronCount"": 32
}
},
{
""Name"": ""Output"",
""Type"": ""NeuronLayer"",
""Parameters"": {
""Input"": ""LinearLayer1"",
""Type"": ""Linear"",
""ActivationType"": ""Tanh"",
""NeuronCount"": 1
}
}
]
}";
Context context = new Context();
Graph graph;
{
Graph.CInfo cinfo = new Graph.CInfo();
cinfo.Context = context;
graph = Graph.CreateFromDefinitionString(cinfo, cGraphDefinition);
}
graph.Deserialize("MyFolder", "MyGraph");
BufferInput input = graph.GetNode<BufferInput>("Input");
input.SetValues(new float[]{ 0.5f, 0.5f, 0.8f, -0.6f });
GraphNode output = graph.GetNode<GraphNode>("Output");
output.RetrieveOutput();
Debug.Log(string.Format("Graph output: [{0}, {1}]", output.Output[0], output.Output[1]));
Training a Graph Using Gradient Descent
Once you have a graph, you can create a gradient descent trainer to train a graph. The idea behind training is that we give it sample inputs and also the expected output for those inputs. After training, the network will have learned the relationship between the input and output and be able to extrapolate new outputs from inputs it has never seen. The network will only be as good as its training data, so the more accurate and broader range the data covers, the better.
Graph graph = null;
Context context = graph.GetContext();
BufferInput expectedOutput = graph.GetNode<BufferInput>("TrainingOutput");
Loss loss;
{
Loss.CInfo cinfo = new Loss.CInfo();
cinfo.Context = context;
cinfo.NetworkOutput = graph.GetNode<GraphNode>("Output");
cinfo.TargetValues = expectedOutput;
loss = new Loss(cinfo);
}
GradientDescentTrainer trainer;
{
GradientDescentTrainer.CInfo cinfo = new GradientDescentTrainer.CInfo();
cinfo.Context = graph.GetContext();
cinfo.Loss = loss;
cinfo.Graph = graph;
trainer = new GradientDescentTrainer(cinfo);
}
{
LossTrainingMethodInfo info = new LossTrainingMethodInfo();
info.BatchSize = 32;
trainer.SetTrainingMethod(info);
}
BufferInput input = graph.GetNode<BufferInput>("Input");
input.SetValues(new float[] { 0.5f, 0.5f, 0.8f, -0.6f });
expectedOutput.SetValues(new float[] { Constants.MaxTanh, 0.5f });
for (int i = 0; i < 500; i++)
{
trainer.Train(100);
Debug.Log("Loss: " + trainer.Loss);
yield return null;
}
graph.Serialize("MyFolder", "MyGraph");