Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions com.unity.ml-agents/Runtime/Agent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace MLAgents
/// Struct that contains all the information for an Agent, including its
/// observations, actions and current status, that is sent to the Brain.
/// </summary>
public struct AgentInfo
internal struct AgentInfo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

{
/// <summary>
/// Keeps track of the last vector action taken by the Brain.
Expand Down Expand Up @@ -145,7 +145,6 @@ public abstract class Agent : MonoBehaviour
/// Whether or not the agent requests a decision.
bool m_RequestDecision;


/// Keeps track of the number of steps taken by the agent in this episode.
/// Note that this value is different for each agent, and may not overlap
/// with the step counter in the Academy, since agents reset based on
Expand All @@ -168,6 +167,14 @@ public abstract class Agent : MonoBehaviour
/// </summary>
DemonstrationRecorder m_Recorder;

/// <summary>
/// Set of DemonstrationStores that the Agent will write its step information to.
/// If you use a DemonstrationRecorder component, this will automatically register its DemonstrationStore.
/// You can also add your own DemonstrationStores; the Agent is not responsible for creating or closing the
/// stores, only opening them.
/// </summary>
public ISet<DemonstrationStore> DemonstrationStores = new HashSet<DemonstrationStore>();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can hide this a bit and add AddDemonstrationStore/RemoveDemonstrationStore methods.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like it would be great if we could just completely remove this demonstration stuff from Agent. It feels like it could happen. But there is no way to hook into Agent at the moment to know when SendInfo is called on a specific one.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could put this in the policy factory maybe? This way we can wrap the policy in a recording policy that looks for demo recorder components at initialization.


/// <summary>
/// List of sensors used to generate observations.
/// Currently generated from attached SensorComponents, and a legacy VectorSensor
Expand Down Expand Up @@ -199,8 +206,6 @@ public void LazyInitialize()
// Grab the "static" properties for the Agent.
m_EpisodeId = EpisodeIdCounter.GetEpisodeId();
m_PolicyFactory = GetComponent<BehaviorParameters>();
m_Recorder = GetComponent<DemonstrationRecorder>();


m_Info = new AgentInfo();
m_Action = new AgentAction();
Expand All @@ -221,6 +226,8 @@ public void LazyInitialize()
/// becomes disabled or inactive.
void OnDisable()
{
DemonstrationStores.Clear();

// If Academy.Dispose has already been called, we don't need to unregister with it.
// We don't want to even try, because this will lazily create a new Academy!
if (Academy.IsInitialized)
Expand Down Expand Up @@ -491,9 +498,10 @@ void SendInfoToBrain()

m_Brain.RequestDecision(m_Info, sensors);

if (m_Recorder != null && m_Recorder.record && Application.isEditor)
// If we have any DemonstrationStores, write the AgentInfo and sensors to them.
foreach(var demoWriter in DemonstrationStores)
{
m_Recorder.WriteExperience(m_Info, sensors);
demoWriter.Record(m_Info, sensors);
}
}

Expand All @@ -505,6 +513,7 @@ void UpdateSensors()
}
}


/// <summary>
/// Collects the vector observations of the agent.
/// The agent observation describes the current environment from the
Expand Down
4 changes: 2 additions & 2 deletions com.unity.ml-agents/Runtime/Demonstration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace MLAgents
/// Used for imitation learning, or other forms of learning from data.
/// </summary>
[Serializable]
public class Demonstration : ScriptableObject
internal class Demonstration : ScriptableObject
{
public DemonstrationMetaData metaData;
public BrainParameters brainParameters;
Expand All @@ -26,7 +26,7 @@ public void Initialize(BrainParameters brainParams,
/// Kept in a struct for easy serialization and deserialization.
/// </summary>
[Serializable]
public class DemonstrationMetaData
internal class DemonstrationMetaData
{
public int numberExperiences;
public int numberEpisodes;
Expand Down
106 changes: 83 additions & 23 deletions com.unity.ml-agents/Runtime/DemonstrationRecorder.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.IO.Abstractions;
using System.Text.RegularExpressions;
using UnityEngine;
using System.Collections.Generic;
using System.IO;

namespace MLAgents
{
Expand All @@ -12,23 +12,24 @@ namespace MLAgents
[AddComponentMenu("ML Agents/Demonstration Recorder", (int)MenuGroup.Default)]
public class DemonstrationRecorder : MonoBehaviour
{
[Tooltip("Whether or not to record demonstrations.")]
public bool record;

[Tooltip("Base demonstration file name. Will have numbers appended to make unique.")]
public string demonstrationName;
string m_FilePath;

[Tooltip("Base directory to write the demo files. If null, will use {Application.dataPath}/Demonstrations.")]
public string demonstrationDirectory;

DemonstrationStore m_DemoStore;
public const int MaxNameLength = 16;
internal const int MaxNameLength = 16;

void Start()
{
if (Application.isEditor && record)
{
InitializeDemoStore();
}
}
const string k_ExtensionType = ".demo";
IFileSystem m_FileSystem;

void Update()
{
if (Application.isEditor && record && m_DemoStore == null)
if (record)
{
InitializeDemoStore();
}
Expand All @@ -37,22 +38,49 @@ void Update()
/// <summary>
/// Creates demonstration store for use in recording.
/// </summary>
public void InitializeDemoStore(IFileSystem fileSystem = null)
internal DemonstrationStore InitializeDemoStore(IFileSystem fileSystem = null)
{
m_DemoStore = new DemonstrationStore(fileSystem);
if (m_DemoStore != null)
{
return m_DemoStore;
}

m_FileSystem = fileSystem ?? new FileSystem();
var behaviorParams = GetComponent<BehaviorParameters>();
if (string.IsNullOrEmpty(demonstrationName))
{
demonstrationName = behaviorParams.behaviorName;
}
if (string.IsNullOrEmpty(demonstrationDirectory))
{
demonstrationDirectory = Path.Combine(Application.dataPath, "Demonstrations");
}

demonstrationName = SanitizeName(demonstrationName, MaxNameLength);
var filePath = MakeDemonstrationFilePath(m_FileSystem, demonstrationDirectory, demonstrationName);
var stream = m_FileSystem.File.Create(filePath);
m_DemoStore = new DemonstrationStore(stream);

m_DemoStore.Initialize(
demonstrationName,
behaviorParams.brainParameters,
behaviorParams.fullyQualifiedBehaviorName);
behaviorParams.fullyQualifiedBehaviorName
);

var agent = GetComponent<Agent>();
if (agent != null)
{
agent.DemonstrationStores.Add(m_DemoStore);
}

return m_DemoStore;
}

/// <summary>
/// Removes all characters except alphanumerics from demonstration name.
/// Shorten name if it is longer than the maxNameLength.
/// </summary>
public static string SanitizeName(string demoName, int maxNameLength)
internal static string SanitizeName(string demoName, int maxNameLength)
{
var rgx = new Regex("[^a-zA-Z0-9 -]");
demoName = rgx.Replace(demoName, "");
Expand All @@ -65,31 +93,63 @@ public static string SanitizeName(string demoName, int maxNameLength)
}

/// <summary>
/// Forwards AgentInfo to Demonstration Store.
/// Gets a unique path for the demonstrationName in the demonstrationDirectory.
/// </summary>
public void WriteExperience(AgentInfo info, List<ISensor> sensors)
/// <param name="fileSystem"></param>
/// <param name="demonstrationDirectory"></param>
/// <param name="demonstrationName"></param>
/// <returns></returns>
internal static string MakeDemonstrationFilePath(
IFileSystem fileSystem, string demonstrationDirectory, string demonstrationName
)
{
m_DemoStore.Record(info, sensors);
// Create the directory if it doesn't already exist
if (!fileSystem.Directory.Exists(demonstrationDirectory))
{
fileSystem.Directory.CreateDirectory(demonstrationDirectory);
}

var literalName = demonstrationName;
var filePath = Path.Combine(demonstrationDirectory, literalName + k_ExtensionType);
var uniqueNameCounter = 0;
while (fileSystem.File.Exists(filePath))
{
// TODO should we use a timestamp instead of a counter here? This loops an increasing number of times
// as the number of demos increases.
literalName = demonstrationName + "_" + uniqueNameCounter;
filePath = Path.Combine(demonstrationDirectory, literalName + k_ExtensionType);
uniqueNameCounter++;
}

return filePath;
}

/// <summary>
/// Close the DemonstrationStore and remove it from the Agent.
/// Has no effect if the DemonstrationStore is already closed (or wasn't opened)
/// </summary>
public void Close()
{
if (m_DemoStore != null)
{
var agent = GetComponent<Agent>();
if (agent != null)
{
agent.DemonstrationStores.Remove(m_DemoStore);
}

m_DemoStore.Close();
m_DemoStore = null;
}
}

/// <summary>
/// Closes Demonstration store.
/// Clean up the DemonstrationStore when shutting down.
/// </summary>
void OnApplicationQuit()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not onDestroy ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's how it was before :) But yeah, sounds like a better place for it.

{
if (Application.isEditor && record)
{
Close();
}
Close();
}

}
}
Loading