Unity 机器学习代理工具包 (ML-Agents) 是一个开源项目,它使游戏和模拟能够作为训练智能代理的环境。
您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 
 

263 行
9.3 KiB

using System;
using System.Collections.Generic;
using Unity.Barracuda;
using Unity.MLAgents.Actuators;
using Unity.MLAgents.Inference;
using Unity.MLAgents.Policies;
using Unity.MLAgents.Sensors;
using UnityEngine;
using UnityEngine.Analytics;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Analytics;
#endif
namespace Unity.MLAgents.Analytics
{
internal class InferenceAnalytics
{
const string k_VendorKey = "unity.ml-agents";
const string k_EventName = "ml_agents_inferencemodelset";
/// <summary>
/// Whether or not we've registered this particular event yet
/// </summary>
static bool s_EventRegistered = false;
/// <summary>
/// Hourly limit for this event name
/// </summary>
const int k_MaxEventsPerHour = 1000;
/// <summary>
/// Maximum number of items in this event.
/// </summary>
const int k_MaxNumberOfElements = 1000;
/// <summary>
/// Models that we've already sent events for.
/// </summary>
private static HashSet<NNModel> s_SentModels;
static bool EnableAnalytics()
{
if (s_EventRegistered)
{
return true;
}
#if UNITY_EDITOR
AnalyticsResult result = EditorAnalytics.RegisterEventWithLimit(k_EventName, k_MaxEventsPerHour, k_MaxNumberOfElements, k_VendorKey);
#else
AnalyticsResult result = AnalyticsResult.UnsupportedPlatform;
#endif
if (result == AnalyticsResult.Ok)
{
s_EventRegistered = true;
}
if (s_EventRegistered && s_SentModels == null)
{
s_SentModels = new HashSet<NNModel>();
}
return s_EventRegistered;
}
public static bool IsAnalyticsEnabled()
{
#if UNITY_EDITOR
return EditorAnalytics.enabled;
#else
return false;
#endif
}
/// <summary>
/// Send an analytics event for the NNModel when it is set up for inference.
/// No events will be sent if analytics are disabled, and at most one event
/// will be sent per model instance.
/// </summary>
/// <param name="nnModel">The NNModel being used for inference.</param>
/// <param name="behaviorName">The BehaviorName of the Agent using the model</param>
/// <param name="inferenceDevice">Whether inference is being performed on the CPU or GPU</param>
/// <param name="sensors">List of ISensors for the Agent. Used to generate information about the observation space.</param>
/// <param name="actionSpec">ActionSpec for the Agent. Used to generate information about the action space.</param>
/// <returns></returns>
public static void InferenceModelSet(
NNModel nnModel,
string behaviorName,
InferenceDevice inferenceDevice,
IList<ISensor> sensors,
ActionSpec actionSpec
)
{
// The event shouldn't be able to report if this is disabled but if we know we're not going to report
// Lets early out and not waste time gathering all the data
if (!IsAnalyticsEnabled())
return;
if (!EnableAnalytics())
return;
var added = s_SentModels.Add(nnModel);
if (!added)
{
// We previously added this model. Exit so we don't resend.
return;
}
var data = GetEventForModel(nnModel, behaviorName, inferenceDevice, sensors, actionSpec);
// Note - to debug, use JsonUtility.ToJson on the event.
// Debug.Log(JsonUtility.ToJson(data, true));
#if UNITY_EDITOR
EditorAnalytics.SendEventWithLimit(k_EventName, data);
#else
return;
#endif
}
/// <summary>
/// Generate an InferenceEvent for the model.
/// </summary>
/// <param name="nnModel"></param>
/// <param name="behaviorName"></param>
/// <param name="inferenceDevice"></param>
/// <param name="sensors"></param>
/// <param name="actionSpec"></param>
/// <returns></returns>
internal static InferenceEvent GetEventForModel(
NNModel nnModel,
string behaviorName,
InferenceDevice inferenceDevice,
IList<ISensor> sensors,
ActionSpec actionSpec
)
{
var barracudaModel = ModelLoader.Load(nnModel);
var inferenceEvent = new InferenceEvent();
// Hash the behavior name so that there's no concern about PII or "secret" data being leaked.
var behaviorNameHash = Hash128.Compute(behaviorName);
inferenceEvent.BehaviorName = behaviorNameHash.ToString();
inferenceEvent.BarracudaModelSource = barracudaModel.IrSource;
inferenceEvent.BarracudaModelVersion = barracudaModel.IrVersion;
inferenceEvent.BarracudaModelProducer = barracudaModel.ProducerName;
inferenceEvent.MemorySize = (int)barracudaModel.GetTensorByName(TensorNames.MemorySize)[0];
inferenceEvent.InferenceDevice = (int)inferenceDevice;
if (barracudaModel.ProducerName == "Script")
{
// .nn files don't have these fields set correctly. Assign some placeholder values.
inferenceEvent.BarracudaModelSource = "NN";
inferenceEvent.BarracudaModelProducer = "tensorflow_to_barracuda.py";
}
#if UNITY_2019_3_OR_NEWER && UNITY_EDITOR
var barracudaPackageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly(typeof(Tensor).Assembly);
inferenceEvent.BarracudaPackageVersion = barracudaPackageInfo.version;
#else
inferenceEvent.BarracudaPackageVersion = null;
#endif
inferenceEvent.ActionSpec = EventActionSpec.FromActionSpec(actionSpec);
inferenceEvent.ObservationSpecs = new List<EventObservationSpec>(sensors.Count);
foreach (var sensor in sensors)
{
inferenceEvent.ObservationSpecs.Add(EventObservationSpec.FromSensor(sensor));
}
inferenceEvent.TotalWeightSizeBytes = GetModelWeightSize(barracudaModel);
inferenceEvent.ModelHash = GetModelHash(barracudaModel);
return inferenceEvent;
}
/// <summary>
/// Compute the total model weight size in bytes.
/// This corresponds to the "Total weight size" display in the Barracuda inspector,
/// and the calculations are the same.
/// </summary>
/// <param name="barracudaModel"></param>
/// <returns></returns>
static long GetModelWeightSize(Model barracudaModel)
{
long totalWeightsSizeInBytes = 0;
for (var l = 0; l < barracudaModel.layers.Count; ++l)
{
for (var d = 0; d < barracudaModel.layers[l].datasets.Length; ++d)
{
totalWeightsSizeInBytes += barracudaModel.layers[l].datasets[d].length;
}
}
return totalWeightsSizeInBytes;
}
/// <summary>
/// Wrapper around Hash128 that supports Append(float[], int, int)
/// </summary>
struct MLAgentsHash128
{
private Hash128 m_Hash;
public void Append(float[] values, int count)
{
if (values == null)
{
return;
}
// Pre-2020 versions of Unity don't have Hash128.Append() (can only hash strings and scalars)
// For these versions, we'll hash element by element.
#if UNITY_2020_1_OR_NEWER
m_Hash.Append(values, 0, count);
#else
for (var i = 0; i < count; i++)
{
var tempHash = new Hash128();
HashUtilities.ComputeHash128(ref values[i], ref tempHash);
HashUtilities.AppendHash(ref tempHash, ref m_Hash);
}
#endif
}
public void Append(string value)
{
var tempHash = Hash128.Compute(value);
HashUtilities.AppendHash(ref tempHash, ref m_Hash);
}
public override string ToString()
{
return m_Hash.ToString();
}
}
/// <summary>
/// Compute a hash of the model's layer data and return it as a string.
/// A subset of the layer weights are used for performance.
/// This increases the chance of a collision, but this should still be extremely rare.
/// </summary>
/// <param name="barracudaModel"></param>
/// <returns></returns>
static string GetModelHash(Model barracudaModel)
{
var hash = new MLAgentsHash128();
// Limit the max number of float bytes that we hash for performance.
const int kMaxFloats = 256;
foreach (var layer in barracudaModel.layers)
{
hash.Append(layer.name);
var numFloatsToHash = Mathf.Min(layer.weights.Length, kMaxFloats);
hash.Append(layer.weights, numFloatsToHash);
}
return hash.ToString();
}
}
}