microduck-rolling-start / training /export_actor.py
JackDH's picture
Add Rolling Start talent policy, timeline and training recipe
b791e4c verified
Raw History Blame Contribute Delete
4.27 kB
"""Export the skating, rolling and learned landing actions as one ONNX policy."""
import learn
import numpy as np
import onnx
from onnx import TensorProto, helper, numpy_helper
def export(weights, destination):
nodes, constants = [], []
def constant(name, value, dtype=np.float32):
constants.append(numpy_helper.from_array(np.asarray(value, dtype=dtype), name))
return name
def node(kind, name, *inputs, **attributes):
nodes.append(helper.make_node(kind, inputs, [name], name=name, **attributes))
return name
def column(name, ix):
return node(
"Gather", name, "obs", constant(name + "_idx", [ix], np.int64), axis=1
)
def scaled_clip(value, factor, name):
scaled = node(
"Mul", name + "_scaled", value, constant(name + "_factor", factor)
)
return node("Clip", name, scaled, neg_one, one)
zero = constant("zero", 0)
one = constant("one", 1)
neg_one = constant("neg_one", -1)
cue = column("cue", 60)
phase = [
node("Greater", "after_" + str(i), cue, constant("boundary_" + str(i), i + 0.5))
for i in range(5)
]
models = {}
for name in learn.BASE_MODELS:
file = learn.model_file(name)
source = onnx.compose.add_prefix(onnx.load(str(file)), name + "/")
scale = 1.12 if name == "roll" else 1.0
divisor = np.ones(61, np.float32)
divisor[34:48] = scale
mask = np.ones(61, np.float32)
mask[48 if name == "roll" else 49 :] = 0
prepared = node(
"Div", name + "_obs", "obs", constant(name + "_divisor", divisor)
)
node(
"Mul", source.graph.input[0].name, prepared, constant(name + "_mask", mask)
)
nodes.extend(source.graph.node)
constants.extend(source.graph.initializer)
models[name] = node(
"Mul",
name + "_action",
source.graph.output[0].name,
constant(name + "_gain", scale),
)
base = node("Where", "recovery_base", phase[3], models["rise"], models["roll"])
arr = np.asarray(weights, np.float32)
biases = arr[:24].reshape(4, 6)
bias = constant("phase2", biases[0])
for n in range(1, 4):
bias = node(
"Where",
"bias" + str(n),
phase[n + 1],
constant("phase" + str(n + 2), biases[n]),
bias,
)
pitch_gravity = column("pitch_gravity", 3)
roll_gravity = column("roll_gravity", 4)
pitch_rate = scaled_clip(column("pitch_rate_body", 1), 0.14, "pitch_rate")
roll_rate = scaled_clip(column("roll_rate_body", 0), 0.15, "roll_rate")
def vector_term(obs, name, arr):
return node("Mul", name, obs, constant(name + "_weights", arr))
# Sum the feedback before adding the offsets, as in the trained policy.
pitch_terms = node(
"Add",
"pitch_feedback",
vector_term(pitch_gravity, "pitch_gravity_term", arr[24:27]),
vector_term(pitch_rate, "pitch_rate_term", arr[27:30]),
)
roll_terms = node(
"Add",
"roll_feedback",
vector_term(roll_gravity, "roll_gravity_term", arr[30]),
vector_term(roll_rate, "roll_rate_term", arr[31]),
)
zeros = node("Mul", "zeros", pitch_rate, zero)
feedback = node("Concat", "feedback", pitch_terms, zeros, zeros, roll_terms, axis=1)
activated = node("Tanh", "bounded", node("Add", "raw", bias, feedback))
adjustment = node(
"MatMul", "joint_adjustment", activated, constant("joint_map", learn.MAP)
)
corrected = node("Add", "corrected", base, adjustment)
roll_or_recovery = node("Where", "trick", phase[1], corrected, base)
node("Where", "actions", phase[0], roll_or_recovery, models["skate"])
graph = helper.make_graph(
nodes,
"skate_land_skate",
[helper.make_tensor_value_info("obs", TensorProto.FLOAT, [1, 61])],
[helper.make_tensor_value_info("actions", TensorProto.FLOAT, [1, 14])],
constants,
)
model = helper.make_model(
graph, ir_version=8, opset_imports=[helper.make_opsetid("", 18)]
)
onnx.checker.check_model(model, full_check=True)
onnx.save(model, str(destination))