|
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using Unity.Barracuda; |
|
using Unity.MLAgents.Actuators; |
|
using Unity.MLAgents.Sensors; |
|
using Unity.MLAgents.Policies; |
|
|
|
namespace Unity.MLAgents.Inference |
|
{ |
|
|
|
|
|
|
|
|
|
internal class BarracudaModelParamLoader |
|
{ |
|
internal enum ModelApiVersion |
|
{ |
|
|
|
|
|
|
|
|
|
|
|
|
|
MLAgents1_0 = 2, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
MLAgents2_0 = 3, |
|
MinSupportedVersion = MLAgents1_0, |
|
MaxSupportedVersion = MLAgents2_0 |
|
} |
|
|
|
internal class FailedCheck |
|
{ |
|
public enum CheckTypeEnum |
|
{ |
|
Info = 0, |
|
Warning = 1, |
|
Error = 2 |
|
} |
|
public CheckTypeEnum CheckType; |
|
public string Message; |
|
public static FailedCheck Info(string message) |
|
{ |
|
return new FailedCheck { CheckType = CheckTypeEnum.Info, Message = message }; |
|
} |
|
|
|
public static FailedCheck Warning(string message) |
|
{ |
|
return new FailedCheck { CheckType = CheckTypeEnum.Warning, Message = message }; |
|
} |
|
|
|
public static FailedCheck Error(string message) |
|
{ |
|
return new FailedCheck { CheckType = CheckTypeEnum.Error, Message = message }; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static FailedCheck CheckModelVersion(Model model) |
|
{ |
|
var modelApiVersion = model.GetVersion(); |
|
if (modelApiVersion < (int)ModelApiVersion.MinSupportedVersion) |
|
{ |
|
return FailedCheck.Error( |
|
"Model was trained with a older version of the trainer than is supported. " + |
|
"Either retrain with an newer trainer, or use an older version of com.unity.ml-agents.\n" + |
|
$"Model version: {modelApiVersion} Minimum supported version: {(int)ModelApiVersion.MinSupportedVersion}" |
|
); |
|
} |
|
|
|
if (modelApiVersion > (int)ModelApiVersion.MaxSupportedVersion) |
|
{ |
|
return FailedCheck.Error( |
|
"Model was trained with a newer version of the trainer than is supported. " + |
|
"Either retrain with an older trainer, or update to a newer version of com.unity.ml-agents.\n" + |
|
$"Model version: {modelApiVersion} Maximum supported version: {(int)ModelApiVersion.MaxSupportedVersion}" |
|
); |
|
} |
|
|
|
var memorySize = (int)model.GetTensorByName(TensorNames.MemorySize)[0]; |
|
|
|
if (modelApiVersion == (int)ModelApiVersion.MLAgents1_0 && memorySize > 0) |
|
{ |
|
|
|
|
|
|
|
|
|
return FailedCheck.Error( |
|
"Models from com.unity.ml-agents 1.x that use recurrent neural networks are not supported in newer versions. " + |
|
"Either retrain with an newer trainer, or use an older version of com.unity.ml-agents.\n" |
|
); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static IEnumerable<FailedCheck> CheckModel( |
|
Model model, |
|
BrainParameters brainParameters, |
|
ISensor[] sensors, |
|
ActuatorComponent[] actuatorComponents, |
|
int observableAttributeTotalSize = 0, |
|
BehaviorType behaviorType = BehaviorType.Default, |
|
bool deterministicInference = false |
|
) |
|
{ |
|
List<FailedCheck> failedModelChecks = new List<FailedCheck>(); |
|
if (model == null) |
|
{ |
|
var errorMsg = "There is no model for this Brain; cannot run inference. "; |
|
if (behaviorType == BehaviorType.InferenceOnly) |
|
{ |
|
errorMsg += "Either assign a model, or change to a different Behavior Type."; |
|
} |
|
else |
|
{ |
|
errorMsg += "(But can still train)"; |
|
} |
|
failedModelChecks.Add(FailedCheck.Info(errorMsg)); |
|
return failedModelChecks; |
|
} |
|
|
|
var hasExpectedTensors = model.CheckExpectedTensors(failedModelChecks, deterministicInference); |
|
if (!hasExpectedTensors) |
|
{ |
|
return failedModelChecks; |
|
} |
|
|
|
var modelApiVersion = model.GetVersion(); |
|
var versionCheck = CheckModelVersion(model); |
|
if (versionCheck != null) |
|
{ |
|
failedModelChecks.Add(versionCheck); |
|
} |
|
|
|
var memorySize = (int)model.GetTensorByName(TensorNames.MemorySize)[0]; |
|
if (memorySize == -1) |
|
{ |
|
failedModelChecks.Add(FailedCheck.Warning($"Missing node in the model provided : {TensorNames.MemorySize}" |
|
)); |
|
return failedModelChecks; |
|
} |
|
|
|
if (modelApiVersion == (int)ModelApiVersion.MLAgents1_0) |
|
{ |
|
failedModelChecks.AddRange( |
|
CheckInputTensorPresenceLegacy(model, brainParameters, memorySize, sensors) |
|
); |
|
failedModelChecks.AddRange( |
|
CheckInputTensorShapeLegacy(model, brainParameters, sensors, observableAttributeTotalSize) |
|
); |
|
} |
|
else if (modelApiVersion == (int)ModelApiVersion.MLAgents2_0) |
|
{ |
|
failedModelChecks.AddRange( |
|
CheckInputTensorPresence(model, brainParameters, memorySize, sensors, deterministicInference) |
|
); |
|
failedModelChecks.AddRange( |
|
CheckInputTensorShape(model, brainParameters, sensors, observableAttributeTotalSize) |
|
); |
|
} |
|
|
|
|
|
failedModelChecks.AddRange( |
|
CheckOutputTensorShape(model, brainParameters, actuatorComponents) |
|
); |
|
|
|
failedModelChecks.AddRange( |
|
CheckOutputTensorPresence(model, memorySize, deterministicInference) |
|
); |
|
return failedModelChecks; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static IEnumerable<FailedCheck> CheckInputTensorPresenceLegacy( |
|
Model model, |
|
BrainParameters brainParameters, |
|
int memory, |
|
ISensor[] sensors |
|
) |
|
{ |
|
var failedModelChecks = new List<FailedCheck>(); |
|
var tensorsNames = model.GetInputNames(); |
|
|
|
|
|
if ((brainParameters.VectorObservationSize != 0) && |
|
(!tensorsNames.Contains(TensorNames.VectorObservationPlaceholder))) |
|
{ |
|
failedModelChecks.Add( |
|
FailedCheck.Warning("The model does not contain a Vector Observation Placeholder Input. " + |
|
"You must set the Vector Observation Space Size to 0.") |
|
); |
|
} |
|
|
|
|
|
|
|
var visObsIndex = 0; |
|
for (var sensorIndex = 0; sensorIndex < sensors.Length; sensorIndex++) |
|
{ |
|
var sensor = sensors[sensorIndex]; |
|
if (sensor.GetObservationSpec().Shape.Length == 3) |
|
{ |
|
if (!tensorsNames.Contains( |
|
TensorNames.GetVisualObservationName(visObsIndex))) |
|
{ |
|
failedModelChecks.Add( |
|
FailedCheck.Warning("The model does not contain a Visual Observation Placeholder Input " + |
|
$"for sensor component {visObsIndex} ({sensor.GetType().Name}).") |
|
); |
|
} |
|
visObsIndex++; |
|
} |
|
if (sensor.GetObservationSpec().Shape.Length == 2) |
|
{ |
|
if (!tensorsNames.Contains( |
|
TensorNames.GetObservationName(sensorIndex))) |
|
{ |
|
failedModelChecks.Add( |
|
FailedCheck.Warning("The model does not contain an Observation Placeholder Input " + |
|
$"for sensor component {sensorIndex} ({sensor.GetType().Name}).") |
|
); |
|
} |
|
} |
|
} |
|
|
|
var expectedVisualObs = model.GetNumVisualInputs(); |
|
|
|
if (expectedVisualObs > visObsIndex) |
|
{ |
|
failedModelChecks.Add( |
|
FailedCheck.Warning($"The model expects {expectedVisualObs} visual inputs," + |
|
$" but only found {visObsIndex} visual sensors.") |
|
); |
|
} |
|
|
|
|
|
if (memory > 0) |
|
{ |
|
if (!tensorsNames.Any(x => x.EndsWith("_h")) || |
|
!tensorsNames.Any(x => x.EndsWith("_c"))) |
|
{ |
|
failedModelChecks.Add( |
|
FailedCheck.Warning("The model does not contain a Recurrent Input Node but has memory_size.") |
|
); |
|
} |
|
} |
|
|
|
|
|
if (model.HasDiscreteOutputs()) |
|
{ |
|
if (!tensorsNames.Contains(TensorNames.ActionMaskPlaceholder)) |
|
{ |
|
failedModelChecks.Add( |
|
FailedCheck.Warning("The model does not contain an Action Mask but is using Discrete Control.") |
|
); |
|
} |
|
} |
|
return failedModelChecks; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static IEnumerable<FailedCheck> CheckInputTensorPresence( |
|
Model model, |
|
BrainParameters brainParameters, |
|
int memory, |
|
ISensor[] sensors, |
|
bool deterministicInference = false |
|
) |
|
{ |
|
var failedModelChecks = new List<FailedCheck>(); |
|
var tensorsNames = model.GetInputNames(); |
|
for (var sensorIndex = 0; sensorIndex < sensors.Length; sensorIndex++) |
|
{ |
|
if (!tensorsNames.Contains( |
|
TensorNames.GetObservationName(sensorIndex))) |
|
{ |
|
var sensor = sensors[sensorIndex]; |
|
failedModelChecks.Add( |
|
FailedCheck.Warning("The model does not contain an Observation Placeholder Input " + |
|
$"for sensor component {sensorIndex} ({sensor.GetType().Name}).") |
|
); |
|
} |
|
} |
|
|
|
|
|
if (memory > 0) |
|
{ |
|
var modelVersion = model.GetVersion(); |
|
if (!tensorsNames.Any(x => x == TensorNames.RecurrentInPlaceholder)) |
|
{ |
|
failedModelChecks.Add( |
|
FailedCheck.Warning("The model does not contain a Recurrent Input Node but has memory_size.") |
|
); |
|
} |
|
} |
|
|
|
|
|
if (model.HasDiscreteOutputs(deterministicInference)) |
|
{ |
|
if (!tensorsNames.Contains(TensorNames.ActionMaskPlaceholder)) |
|
{ |
|
failedModelChecks.Add( |
|
FailedCheck.Warning("The model does not contain an Action Mask but is using Discrete Control.") |
|
); |
|
} |
|
} |
|
return failedModelChecks; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static IEnumerable<FailedCheck> CheckOutputTensorPresence(Model model, int memory, bool deterministicInference = false) |
|
{ |
|
var failedModelChecks = new List<FailedCheck>(); |
|
|
|
|
|
if (memory > 0) |
|
{ |
|
var allOutputs = model.GetOutputNames(deterministicInference).ToList(); |
|
if (!allOutputs.Any(x => x == TensorNames.RecurrentOutput)) |
|
{ |
|
failedModelChecks.Add( |
|
FailedCheck.Warning("The model does not contain a Recurrent Output Node but has memory_size.") |
|
); |
|
} |
|
} |
|
return failedModelChecks; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static FailedCheck CheckVisualObsShape( |
|
TensorProxy tensorProxy, ISensor sensor) |
|
{ |
|
var shape = sensor.GetObservationSpec().Shape; |
|
var heightBp = shape[0]; |
|
var widthBp = shape[1]; |
|
var pixelBp = shape[2]; |
|
var heightT = tensorProxy.Height; |
|
var widthT = tensorProxy.Width; |
|
var pixelT = tensorProxy.Channels; |
|
if ((widthBp != widthT) || (heightBp != heightT) || (pixelBp != pixelT)) |
|
{ |
|
return FailedCheck.Warning($"The visual Observation of the model does not match. " + |
|
$"Received TensorProxy of shape [?x{widthBp}x{heightBp}x{pixelBp}] but " + |
|
$"was expecting [?x{widthT}x{heightT}x{pixelT}] for the {sensor.GetName()} Sensor." |
|
); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static FailedCheck CheckRankTwoObsShape( |
|
TensorProxy tensorProxy, ISensor sensor) |
|
{ |
|
var shape = sensor.GetObservationSpec().Shape; |
|
var dim1Bp = shape[0]; |
|
var dim2Bp = shape[1]; |
|
var dim1T = tensorProxy.Channels; |
|
var dim2T = tensorProxy.Width; |
|
var dim3T = tensorProxy.Height; |
|
if ((dim1Bp != dim1T) || (dim2Bp != dim2T)) |
|
{ |
|
var proxyDimStr = $"[?x{dim1T}x{dim2T}]"; |
|
if (dim3T > 1) |
|
{ |
|
proxyDimStr = $"[?x{dim3T}x{dim2T}x{dim1T}]"; |
|
} |
|
return FailedCheck.Warning($"An Observation of the model does not match. " + |
|
$"Received TensorProxy of shape [?x{dim1Bp}x{dim2Bp}] but " + |
|
$"was expecting {proxyDimStr} for the {sensor.GetName()} Sensor." |
|
); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static FailedCheck CheckRankOneObsShape( |
|
TensorProxy tensorProxy, ISensor sensor) |
|
{ |
|
var shape = sensor.GetObservationSpec().Shape; |
|
var dim1Bp = shape[0]; |
|
var dim1T = tensorProxy.Channels; |
|
var dim2T = tensorProxy.Width; |
|
var dim3T = tensorProxy.Height; |
|
if ((dim1Bp != dim1T)) |
|
{ |
|
var proxyDimStr = $"[?x{dim1T}]"; |
|
if (dim2T > 1) |
|
{ |
|
proxyDimStr = $"[?x{dim1T}x{dim2T}]"; |
|
} |
|
if (dim3T > 1) |
|
{ |
|
proxyDimStr = $"[?x{dim3T}x{dim2T}x{dim1T}]"; |
|
} |
|
return FailedCheck.Warning($"An Observation of the model does not match. " + |
|
$"Received TensorProxy of shape [?x{dim1Bp}] but " + |
|
$"was expecting {proxyDimStr} for the {sensor.GetName()} Sensor." |
|
); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static IEnumerable<FailedCheck> CheckInputTensorShapeLegacy( |
|
Model model, BrainParameters brainParameters, ISensor[] sensors, |
|
int observableAttributeTotalSize) |
|
{ |
|
var failedModelChecks = new List<FailedCheck>(); |
|
var tensorTester = |
|
new Dictionary<string, Func<BrainParameters, TensorProxy, ISensor[], int, FailedCheck>>() |
|
{ |
|
{TensorNames.VectorObservationPlaceholder, CheckVectorObsShapeLegacy}, |
|
{TensorNames.PreviousActionPlaceholder, CheckPreviousActionShape}, |
|
{TensorNames.RandomNormalEpsilonPlaceholder, ((bp, tensor, scs, i) => null)}, |
|
{TensorNames.ActionMaskPlaceholder, ((bp, tensor, scs, i) => null)}, |
|
{TensorNames.SequenceLengthPlaceholder, ((bp, tensor, scs, i) => null)}, |
|
{TensorNames.RecurrentInPlaceholder, ((bp, tensor, scs, i) => null)}, |
|
}; |
|
|
|
foreach (var mem in model.memories) |
|
{ |
|
tensorTester[mem.input] = ((bp, tensor, scs, i) => null); |
|
} |
|
|
|
var visObsIndex = 0; |
|
for (var sensorIndex = 0; sensorIndex < sensors.Length; sensorIndex++) |
|
{ |
|
var sens = sensors[sensorIndex]; |
|
if (sens.GetObservationSpec().Shape.Length == 3) |
|
{ |
|
tensorTester[TensorNames.GetVisualObservationName(visObsIndex)] = |
|
(bp, tensor, scs, i) => CheckVisualObsShape(tensor, sens); |
|
visObsIndex++; |
|
} |
|
if (sens.GetObservationSpec().Shape.Length == 2) |
|
{ |
|
tensorTester[TensorNames.GetObservationName(sensorIndex)] = |
|
(bp, tensor, scs, i) => CheckRankTwoObsShape(tensor, sens); |
|
} |
|
} |
|
|
|
|
|
foreach (var tensor in model.GetInputTensors()) |
|
{ |
|
if (!tensorTester.ContainsKey(tensor.name)) |
|
{ |
|
if (!tensor.name.Contains("visual_observation")) |
|
{ |
|
failedModelChecks.Add( |
|
FailedCheck.Warning("Model contains an unexpected input named : " + tensor.name) |
|
); |
|
} |
|
} |
|
else |
|
{ |
|
var tester = tensorTester[tensor.name]; |
|
var error = tester.Invoke(brainParameters, tensor, sensors, observableAttributeTotalSize); |
|
if (error != null) |
|
{ |
|
failedModelChecks.Add(error); |
|
} |
|
} |
|
} |
|
return failedModelChecks; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static FailedCheck CheckVectorObsShapeLegacy( |
|
BrainParameters brainParameters, TensorProxy tensorProxy, ISensor[] sensors, |
|
int observableAttributeTotalSize) |
|
{ |
|
var vecObsSizeBp = brainParameters.VectorObservationSize; |
|
var numStackedVector = brainParameters.NumStackedVectorObservations; |
|
var totalVecObsSizeT = tensorProxy.shape[tensorProxy.shape.Length - 1]; |
|
|
|
var totalVectorSensorSize = 0; |
|
foreach (var sens in sensors) |
|
{ |
|
if ((sens.GetObservationSpec().Shape.Length == 1)) |
|
{ |
|
totalVectorSensorSize += sens.GetObservationSpec().Shape[0]; |
|
} |
|
} |
|
|
|
if (totalVectorSensorSize != totalVecObsSizeT) |
|
{ |
|
var sensorSizes = ""; |
|
foreach (var sensorComp in sensors) |
|
{ |
|
if (sensorComp.GetObservationSpec().Shape.Length == 1) |
|
{ |
|
var vecSize = sensorComp.GetObservationSpec().Shape[0]; |
|
if (sensorSizes.Length == 0) |
|
{ |
|
sensorSizes = $"[{vecSize}"; |
|
} |
|
else |
|
{ |
|
sensorSizes += $", {vecSize}"; |
|
} |
|
} |
|
} |
|
|
|
sensorSizes += "]"; |
|
return FailedCheck.Warning( |
|
$"Vector Observation Size of the model does not match. Was expecting {totalVecObsSizeT} " + |
|
$"but received: \n" + |
|
$"Vector observations: {vecObsSizeBp} x {numStackedVector}\n" + |
|
$"Total [Observable] attributes: {observableAttributeTotalSize}\n" + |
|
$"Sensor sizes: {sensorSizes}." |
|
); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static IEnumerable<FailedCheck> CheckInputTensorShape( |
|
Model model, BrainParameters brainParameters, ISensor[] sensors, |
|
int observableAttributeTotalSize) |
|
{ |
|
var failedModelChecks = new List<FailedCheck>(); |
|
var tensorTester = |
|
new Dictionary<string, Func<BrainParameters, TensorProxy, ISensor[], int, FailedCheck>>() |
|
{ |
|
{TensorNames.PreviousActionPlaceholder, CheckPreviousActionShape}, |
|
{TensorNames.RandomNormalEpsilonPlaceholder, ((bp, tensor, scs, i) => null)}, |
|
{TensorNames.ActionMaskPlaceholder, ((bp, tensor, scs, i) => null)}, |
|
{TensorNames.SequenceLengthPlaceholder, ((bp, tensor, scs, i) => null)}, |
|
{TensorNames.RecurrentInPlaceholder, ((bp, tensor, scs, i) => null)}, |
|
}; |
|
|
|
foreach (var mem in model.memories) |
|
{ |
|
tensorTester[mem.input] = ((bp, tensor, scs, i) => null); |
|
} |
|
|
|
for (var sensorIndex = 0; sensorIndex < sensors.Length; sensorIndex++) |
|
{ |
|
var sens = sensors[sensorIndex]; |
|
if (sens.GetObservationSpec().Rank == 3) |
|
{ |
|
tensorTester[TensorNames.GetObservationName(sensorIndex)] = |
|
(bp, tensor, scs, i) => CheckVisualObsShape(tensor, sens); |
|
} |
|
if (sens.GetObservationSpec().Rank == 2) |
|
{ |
|
tensorTester[TensorNames.GetObservationName(sensorIndex)] = |
|
(bp, tensor, scs, i) => CheckRankTwoObsShape(tensor, sens); |
|
} |
|
if (sens.GetObservationSpec().Rank == 1) |
|
{ |
|
tensorTester[TensorNames.GetObservationName(sensorIndex)] = |
|
(bp, tensor, scs, i) => CheckRankOneObsShape(tensor, sens); |
|
} |
|
} |
|
|
|
|
|
foreach (var tensor in model.GetInputTensors()) |
|
{ |
|
if (!tensorTester.ContainsKey(tensor.name)) |
|
{ |
|
failedModelChecks.Add(FailedCheck.Warning("Model contains an unexpected input named : " + tensor.name |
|
)); |
|
} |
|
else |
|
{ |
|
var tester = tensorTester[tensor.name]; |
|
var error = tester.Invoke(brainParameters, tensor, sensors, observableAttributeTotalSize); |
|
if (error != null) |
|
{ |
|
failedModelChecks.Add(error); |
|
} |
|
} |
|
} |
|
return failedModelChecks; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static FailedCheck CheckPreviousActionShape( |
|
BrainParameters brainParameters, TensorProxy tensorProxy, |
|
ISensor[] sensors, int observableAttributeTotalSize) |
|
{ |
|
var numberActionsBp = brainParameters.ActionSpec.NumDiscreteActions; |
|
var numberActionsT = tensorProxy.shape[tensorProxy.shape.Length - 1]; |
|
if (numberActionsBp != numberActionsT) |
|
{ |
|
return FailedCheck.Warning("Previous Action Size of the model does not match. " + |
|
$"Received {numberActionsBp} but was expecting {numberActionsT}." |
|
); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static IEnumerable<FailedCheck> CheckOutputTensorShape( |
|
Model model, |
|
BrainParameters brainParameters, |
|
ActuatorComponent[] actuatorComponents) |
|
{ |
|
var failedModelChecks = new List<FailedCheck>(); |
|
|
|
|
|
var modelContinuousActionSize = model.ContinuousOutputSize(); |
|
var continuousError = CheckContinuousActionOutputShape(brainParameters, actuatorComponents, modelContinuousActionSize); |
|
if (continuousError != null) |
|
{ |
|
failedModelChecks.Add(continuousError); |
|
} |
|
FailedCheck discreteError = null; |
|
var modelApiVersion = model.GetVersion(); |
|
if (modelApiVersion == (int)ModelApiVersion.MLAgents1_0) |
|
{ |
|
var modelSumDiscreteBranchSizes = model.DiscreteOutputSize(); |
|
discreteError = CheckDiscreteActionOutputShapeLegacy(brainParameters, actuatorComponents, modelSumDiscreteBranchSizes); |
|
} |
|
if (modelApiVersion == (int)ModelApiVersion.MLAgents2_0) |
|
{ |
|
var modelDiscreteBranches = model.GetTensorByName(TensorNames.DiscreteActionOutputShape); |
|
discreteError = CheckDiscreteActionOutputShape(brainParameters, actuatorComponents, modelDiscreteBranches); |
|
} |
|
|
|
if (discreteError != null) |
|
{ |
|
failedModelChecks.Add(discreteError); |
|
} |
|
return failedModelChecks; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static FailedCheck CheckDiscreteActionOutputShape( |
|
BrainParameters brainParameters, ActuatorComponent[] actuatorComponents, Tensor modelDiscreteBranches) |
|
{ |
|
var discreteActionBranches = brainParameters.ActionSpec.BranchSizes.ToList(); |
|
foreach (var actuatorComponent in actuatorComponents) |
|
{ |
|
var actionSpec = actuatorComponent.ActionSpec; |
|
discreteActionBranches.AddRange(actionSpec.BranchSizes); |
|
} |
|
|
|
int modelDiscreteBranchesLength = modelDiscreteBranches?.length ?? 0; |
|
if (modelDiscreteBranchesLength != discreteActionBranches.Count) |
|
{ |
|
return FailedCheck.Warning("Discrete Action Size of the model does not match. The BrainParameters expect " + |
|
$"{discreteActionBranches.Count} branches but the model contains {modelDiscreteBranchesLength}." |
|
); |
|
} |
|
|
|
for (int i = 0; i < modelDiscreteBranchesLength; i++) |
|
{ |
|
if (modelDiscreteBranches != null && modelDiscreteBranches[i] != discreteActionBranches[i]) |
|
{ |
|
return FailedCheck.Warning($"The number of Discrete Actions of branch {i} does not match. " + |
|
$"Was expecting {discreteActionBranches[i]} but the model contains {modelDiscreteBranches[i]} " |
|
); |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static FailedCheck CheckDiscreteActionOutputShapeLegacy( |
|
BrainParameters brainParameters, ActuatorComponent[] actuatorComponents, int modelSumDiscreteBranchSizes) |
|
{ |
|
|
|
var sumOfDiscreteBranchSizes = brainParameters.ActionSpec.SumOfDiscreteBranchSizes; |
|
|
|
foreach (var actuatorComponent in actuatorComponents) |
|
{ |
|
var actionSpec = actuatorComponent.ActionSpec; |
|
sumOfDiscreteBranchSizes += actionSpec.SumOfDiscreteBranchSizes; |
|
} |
|
|
|
if (modelSumDiscreteBranchSizes != sumOfDiscreteBranchSizes) |
|
{ |
|
return FailedCheck.Warning("Discrete Action Size of the model does not match. The BrainParameters expect " + |
|
$"{sumOfDiscreteBranchSizes} but the model contains {modelSumDiscreteBranchSizes}." |
|
); |
|
} |
|
return null; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static FailedCheck CheckContinuousActionOutputShape( |
|
BrainParameters brainParameters, ActuatorComponent[] actuatorComponents, int modelContinuousActionSize) |
|
{ |
|
var numContinuousActions = brainParameters.ActionSpec.NumContinuousActions; |
|
|
|
foreach (var actuatorComponent in actuatorComponents) |
|
{ |
|
var actionSpec = actuatorComponent.ActionSpec; |
|
numContinuousActions += actionSpec.NumContinuousActions; |
|
} |
|
|
|
if (modelContinuousActionSize != numContinuousActions) |
|
{ |
|
return FailedCheck.Warning( |
|
"Continuous Action Size of the model does not match. The BrainParameters and ActuatorComponents expect " + |
|
$"{numContinuousActions} but the model contains {modelContinuousActionSize}." |
|
); |
|
} |
|
return null; |
|
} |
|
} |
|
} |
|
|