浏览代码

Monitor without JSON Conversion (#724)

* [Refactor] Fixed line indentation
* Removed the library Newtonsoft.Json from the monitor
* Replaced calls to JSON converstion with manual conversion
* [Modified] The Monitor now has multiple
* Log methods that take different object types
/develop-generalizationTraining-TrainerController
Arthur Juliani 6 年前
当前提交
4d98b4c7
共有 2 个文件被更改,包括 281 次插入161 次删除
  1. 16
      docs/Feature-Monitor.md
  2. 426
      unity-environment/Assets/ML-Agents/Scripts/Monitor.cs

16
docs/Feature-Monitor.md


The monitor allows visualizing information related to the agents or training process within a Unity scene.
You can track many different things both related and unrelated to the agents themselves. To use the Monitor, call the Log function anywhere in your code :
Monitor.Log(key, value, displayType , target)
Monitor.Log(key, value, target)
* *`value`* is the information you want to display.
* *`displayType`* is a MonitorType that can be either `text`, `slider`, `bar` or `hist`.
* `text` will convert `value` into a string and display it. It can be useful for displaying error messages!
* `slider` is used to display a single float between -1 and 1. Note that value must be a float if you want to use a slider. If the value is positive, the slider will be green, if the value is negative, the slider will be red.
* `hist` is used to display multiple floats. Note that value must be a list or array of floats. The Histogram will be a sequence of vertical sliders.
* `bar` is used to see the proportions. Note that value must be a list or array of positive floats. For each float in values, a rectangle of width of value divided by the sum of all values will be show. It is best for visualizing values that sum to 1.
* *`value`* is the information you want to display. *`value`* can have different types :
* *`string`* - The Monitor will display the string next to the key. It can be useful for displaying error messages.
* *`float`* - The Monitor will display a slider. Note that the values must be between -1 and 1. If the value is positive, the slider will be green, if the value is negative, the slider will be red.
* *`float[]`* - The Monitor Log call can take an additional argument called `displayType` that can be either `INDEPENDENT` (default) or `PROPORTIONAL` :
* *`INDEPENDENT`* is used to display multiple independent floats as a histogram. The histogram will be a sequence of vertical sliders.
* *`PROPORTION`* is used to see the proportions between numbers. For each float in values, a rectangle of width of value divided by the sum of all values will be show. It is best for visualizing values that sum to 1.
* *`target`* is the transform to which you want to attach information. If the transform is `null` the information will be attached to the global monitor.

426
unity-environment/Assets/ML-Agents/Scripts/Monitor.cs


using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
/// <summary>
/// The type of monitor the information must be displayed in.
/// <slider> corresponds to a single rectangle whose width is given
/// by a float between -1 and 1. (green is positive, red is negative)
/// <hist> corresponds to n vertical sliders.
/// <text> is a text field.
/// <bar> is a rectangle of fixed length to represent the proportions
/// of a list of floats.
/// </summary>
public enum MonitorType
{
slider,
hist,
text,
bar
}
/// <summary>
/// Monitor is used to display information about the Agent within the Unity
/// scene. Use the log function to add information to your monitor.

/// <summary>
/// The type of monitor the information must be displayed in.
/// <slider> corresponds to a single rectangle whose width is given
/// by a float between -1 and 1. (green is positive, red is negative)
/// <hist> corresponds to n vertical sliders.
/// <text> is a text field.
/// <bar> is a rectangle of fixed length to represent the proportions
/// of a list of floats.
/// </summary>
public enum DisplayType
{
INDEPENDENT,
PROPORTION
}
/// <summary>
/// Represents how high above the target the monitors will be.
/// </summary>
[HideInInspector]

struct DisplayValue
{
public float time;
public object value;
public MonitorType monitorDisplayType;
public string stringValue;
public float floatValue;
public float[] floatArrayValues;
public enum ValueType
{
FLOAT,
FLOATARRAY_INDEPENDENT,
FLOATARRAY_PROPORTION,
STRING
}
public ValueType valueType;
}
static GUIStyle keyStyle;

/// <summary>
/// Use the Monitor.Log static function to attach information to a transform.
/// If displayType is <text>, value can be any object.
/// If sidplayType is <slider>, value must be a float.
/// If sidplayType is <hist>, value must be a List or Array of floats.
/// If sidplayType is <bar>, value must be a list or Array of positive floats.
/// Note that <slider> and <hist> caps values between -1 and 1.
/// <param name="value">The value you want to display.</param>
/// <param name="displayType">The type of display.</param>
/// <param name="target">
/// The transform you want to attach the information to.
/// <param name="value">The string value you want to display.</param>
/// <param name="target">The transform you want to attach the information to.
object value,
MonitorType displayType = MonitorType.text,
string value,
Transform target = null)
{
if (!isInstantiated)

}
if (target == null)
{
target = canvas.transform;
}
if (!displayTransformValues.Keys.Contains(target))
{
displayTransformValues[target] =
new Dictionary<string, DisplayValue>();
}
Dictionary<string, DisplayValue> displayValues =
displayTransformValues[target];
if (value == null)
{
RemoveValue(target, key);
return;
}
if (!displayValues.ContainsKey(key))
{
var dv = new DisplayValue();
dv.time = Time.timeSinceLevelLoad;
dv.stringValue = value;
dv.valueType = DisplayValue.ValueType.STRING;
displayValues[key] = dv;
while (displayValues.Count > 20)
{
string max = (
displayValues
.Aggregate((l, r) => l.Value.time < r.Value.time ? l : r)
.Key
);
RemoveValue(target, max);
}
}
else
{
DisplayValue dv = displayValues[key];
dv.stringValue = value;
dv.valueType = DisplayValue.ValueType.STRING;
displayValues[key] = dv;
}
}
/// <summary>
/// Use the Monitor.Log static function to attach information to a transform.
/// </summary>
/// <returns>The log.</returns>
/// <param name="key">The name of the information you wish to Log.</param>
/// <param name="value">The float value you want to display.</param>
/// <param name="target">The transform you want to attach the information to.
/// </param>
public static void Log(
string key,
float value,
Transform target = null)
{
if (!isInstantiated)
{
InstantiateCanvas();
isInstantiated = true;
}
if (target == null)

Dictionary<string, DisplayValue> displayValues = displayTransformValues[target];
if (value == null)
if (!displayValues.ContainsKey(key))
{
var dv = new DisplayValue();
dv.time = Time.timeSinceLevelLoad;
dv.floatValue = value;
dv.valueType = DisplayValue.ValueType.FLOAT;
displayValues[key] = dv;
while (displayValues.Count > 20)
{
string max = (
displayValues.Aggregate((l, r) => l.Value.time < r.Value.time ? l : r).Key);
RemoveValue(target, max);
}
}
else
RemoveValue(target, key);
return;
DisplayValue dv = displayValues[key];
dv.floatValue = value;
dv.valueType = DisplayValue.ValueType.FLOAT;
displayValues[key] = dv;
}
/// <summary>
/// Use the Monitor.Log static function to attach information to a transform.
/// </summary>
/// <returns>The log.</returns>
/// <param name="key">The name of the information you wish to Log.</param>
/// <param name="value">The array of float you want to display.</param>
/// <param name="displayType">The type of display.</param>
/// <param name="target">The transform you want to attach the information to.
/// </param>
public static void Log(
string key,
float[] value,
Transform target = null,
DisplayType displayType = DisplayType.INDEPENDENT
)
{
if (!isInstantiated)
{
InstantiateCanvas();
isInstantiated = true;
}
if (target == null)
{
target = canvas.transform;
}
if (!displayTransformValues.Keys.Contains(target))
{
displayTransformValues[target] = new Dictionary<string, DisplayValue>();
}
Dictionary<string, DisplayValue> displayValues = displayTransformValues[target];
dv.value = value;
dv.monitorDisplayType = displayType;
dv.floatArrayValues = value;
if (displayType == DisplayType.INDEPENDENT)
{
dv.valueType = DisplayValue.ValueType.FLOATARRAY_INDEPENDENT;
}
else
{
dv.valueType = DisplayValue.ValueType.FLOATARRAY_PROPORTION;
}
string max = displayValues.Aggregate((l, r) => l.Value.time < r.Value.time ? l : r).Key;
string max = (
displayValues.Aggregate((l, r) => l.Value.time < r.Value.time ? l : r).Key);
RemoveValue(target, max);
}
}

dv.value = value;
dv.floatArrayValues = value;
if (displayType == DisplayType.INDEPENDENT)
{
dv.valueType = DisplayValue.ValueType.FLOATARRAY_INDEPENDENT;
}
else
{
dv.valueType = DisplayValue.ValueType.FLOATARRAY_PROPORTION;
}
/// <summary>
/// Remove a value from a monitor.

canvas.name = "AgentMonitorCanvas";
canvas.AddComponent<Monitor>();
}
displayTransformValues = new Dictionary<Transform, Dictionary<string, DisplayValue>>();
}
/// Convert the input object to a float array. Returns a float[0] if the
/// conversion process fails.
float[] ToFloatArray(object input)
{
try
{
return JsonConvert.DeserializeObject<float[]>(
JsonConvert.SerializeObject(input, Formatting.None));
}
catch
{
}
try
{
return new float[1]
{JsonConvert.DeserializeObject<float>(
JsonConvert.SerializeObject(input, Formatting.None))
};
}
catch
{
}
return new float[0];
displayTransformValues = new Dictionary<Transform,
Dictionary<string, DisplayValue>>();
}
/// <summary> <inheritdoc/> </summary>

float paddingwidth = 10 * widthScaler;
float scale = 1f;
var origin = new Vector3(Screen.width / 2 - keyPixelWidth, Screen.height);
var origin = new Vector3(
Screen.width / 2 - keyPixelWidth, Screen.height);
scale = Mathf.Min(1, 20f / (Vector3.Dot(cam2obj, Camera.main.transform.forward)));
Vector3 worldPosition = Camera.main.WorldToScreenPoint(target.position + new Vector3(0, verticalOffset, 0));
origin = new Vector3(worldPosition.x - keyPixelWidth * scale, Screen.height - worldPosition.y);
scale = Mathf.Min(
1,
20f / (Vector3.Dot(cam2obj, Camera.main.transform.forward)));
Vector3 worldPosition = Camera.main.WorldToScreenPoint(
target.position + new Vector3(0, verticalOffset, 0));
origin = new Vector3(
worldPosition.x - keyPixelWidth * scale, Screen.height - worldPosition.y);
}
keyPixelWidth *= scale;
keyPixelHeight *= scale;

Dictionary<string, DisplayValue> displayValues = displayTransformValues[target];
int index = 0;
foreach (string key in displayValues.Keys.OrderBy(x => -displayValues[x].time))
var orderedKeys = displayValues.Keys.OrderBy(x => -displayValues[x].time);
float[] vals;
GUIStyle s;
foreach (string key in orderedKeys)
GUI.Label(new Rect(origin.x, origin.y - (index + 1) * keyPixelHeight, keyPixelWidth, keyPixelHeight), key, keyStyle);
if (displayValues[key].monitorDisplayType == MonitorType.text)
GUI.Label(
new Rect(
origin.x, origin.y - (index + 1) * keyPixelHeight,
keyPixelWidth, keyPixelHeight),
key,
keyStyle);
switch(displayValues[key].valueType)
valueStyle.alignment = TextAnchor.MiddleLeft;
GUI.Label(new Rect(
origin.x + paddingwidth + keyPixelWidth,
origin.y - (index + 1) * keyPixelHeight,
keyPixelWidth, keyPixelHeight),
JsonConvert.SerializeObject(displayValues[key].value, Formatting.None), valueStyle);
}
else if (displayValues[key].monitorDisplayType == MonitorType.slider)
{
float sliderValue = 0f;
if (displayValues[key].value is float)
{
sliderValue = (float)displayValues[key].value;
}
else
{
Debug.LogError(string.Format("The value for {0} could not be displayed as " +
"a slider because it is not a number.", key));
}
sliderValue = Mathf.Min(1f, sliderValue);
GUIStyle s = greenStyle;
if (sliderValue < 0)
{
sliderValue = Mathf.Min(1f, -sliderValue);
s = redStyle;
}
GUI.Box(new Rect(
origin.x + paddingwidth + keyPixelWidth,
origin.y - (index + 0.9f) * keyPixelHeight,
keyPixelWidth * sliderValue, keyPixelHeight * 0.8f),
GUIContent.none, s);
}
else if (displayValues[key].monitorDisplayType == MonitorType.hist)
{
float histWidth = 0.15f;
float[] vals = ToFloatArray(displayValues[key].value);
for (int i = 0; i < vals.Length; i++)
{
float value = Mathf.Min(vals[i], 1);
GUIStyle s = greenStyle;
if (value < 0)
case DisplayValue.ValueType.STRING:
valueStyle.alignment = TextAnchor.MiddleLeft;
GUI.Label(
new Rect(
origin.x + paddingwidth + keyPixelWidth,
origin.y - (index + 1) * keyPixelHeight,
keyPixelWidth, keyPixelHeight),
displayValues[key].stringValue,
valueStyle);
break;
case DisplayValue.ValueType.FLOAT:
float sliderValue = displayValues[key].floatValue;
sliderValue = Mathf.Min(1f, sliderValue);
s = greenStyle;
if (sliderValue < 0)
value = Mathf.Min(1f, -value);
sliderValue = Mathf.Min(1f, -sliderValue);
GUI.Box(new Rect(
origin.x + paddingwidth + keyPixelWidth + (keyPixelWidth * histWidth + paddingwidth / 2) * i,
origin.y - (index + 0.1f) * keyPixelHeight,
keyPixelWidth * histWidth, -keyPixelHeight * value),
GUIContent.none, s);
}
GUI.Box(
new Rect(
origin.x + paddingwidth + keyPixelWidth,
origin.y - (index + 0.9f) * keyPixelHeight,
keyPixelWidth * sliderValue, keyPixelHeight * 0.8f),
GUIContent.none,
s);
break;
}
else if (displayValues[key].monitorDisplayType == MonitorType.bar)
{
float[] vals = ToFloatArray(displayValues[key].value);
float valsSum = 0f;
float valsCum = 0f;
foreach (float f in vals)
{
valsSum += Mathf.Max(f, 0);
}
if (valsSum == 0)
{
Debug.LogError(string.Format("The Monitor value for key {0} must be "
+ "a list or array of positive values and cannot be empty.", key));
}
else
{
case DisplayValue.ValueType.FLOATARRAY_INDEPENDENT:
float histWidth = 0.15f;
vals = displayValues[key].floatArrayValues;
float value = Mathf.Max(vals[i], 0) / valsSum;
GUI.Box(new Rect(
origin.x + paddingwidth + keyPixelWidth + keyPixelWidth * valsCum,
origin.y - (index + 0.9f) * keyPixelHeight,
keyPixelWidth * value, keyPixelHeight * 0.8f),
GUIContent.none, colorStyle[i % colorStyle.Length]);
valsCum += value;
float value = Mathf.Min(vals[i], 1);
s = greenStyle;
if (value < 0)
{
value = Mathf.Min(1f, -value);
s = redStyle;
}
GUI.Box(
new Rect(
origin.x + paddingwidth + keyPixelWidth +
(keyPixelWidth * histWidth + paddingwidth / 2) * i,
origin.y - (index + 0.1f) * keyPixelHeight,
keyPixelWidth * histWidth, -keyPixelHeight * value),
GUIContent.none,
s);
}
break;
case DisplayValue.ValueType.FLOATARRAY_PROPORTION:
float valsSum = 0f;
float valsCum = 0f;
vals = displayValues[key].floatArrayValues;
foreach (float f in vals)
{
valsSum += Mathf.Max(f, 0);
}
if (valsSum < float.Epsilon)
{
Debug.LogError(
string.Format("The Monitor value for key {0} " +
"must be a list or array of " +
"positive values and cannot " +
"be empty.", key));
else
{
for (int i = 0; i < vals.Length; i++)
{
float value = Mathf.Max(vals[i], 0) / valsSum;
GUI.Box(
new Rect(
origin.x + paddingwidth +
keyPixelWidth + keyPixelWidth * valsCum,
origin.y - (index + 0.9f) * keyPixelHeight,
keyPixelWidth * value, keyPixelHeight * 0.8f),
GUIContent.none,
colorStyle[i % colorStyle.Length]);
valsCum += value;
}
}
}
break;
}
index++;

valueStyle = GUI.skin.label;
valueStyle.clipping = TextClipping.Overflow;
valueStyle.wordWrap = false;
barColors = new Color[6] { Color.magenta, Color.blue, Color.cyan, Color.green, Color.yellow, Color.red };
barColors = new Color[6] {
Color.magenta,
Color.blue,
Color.cyan,
Color.green,
Color.yellow,
Color.red};
colorStyle = new GUIStyle[barColors.Length];
for (int i = 0; i < barColors.Length; i++)
{

正在加载...
取消
保存