Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

操作符

在核心操作符之上,适配器还有二十个张量操作符。每个都以 {"name": [arguments]} 的形式调用。下面的表格中,T 是张量,形状是非负整数列表,轴从 0 开始计数。预算一页列出了每个操作符的开销。

其中七个名称是普通单词:shape、full、cast、pad、crop、concat 和 stack。唯一的键是其中之一的对象会调用它。与之同名的数据键前面加上 $:{"$shape": [6, 7]} 就是数据 {"shape": [6, 7]},在节点上和在 tinybrains 中都是如此。

构建张量

操作符参数结果
zerosshape, dtype全零张量
fullshape, dtype, value用 value 填满的张量
tensorvalue, dtype?把嵌套的 JSON 数组变成张量,形状与数组相同
scatterpoints, shape, dtype, value?全零,再写入每个点:[r, c] 写入 value(默认 1),[r, c, v] 写入 v
rle_expandruns, shape, dtype[v0, n0, v1, n1, …] 按行优先顺序展开。游程超出末尾则失败;不足末尾的,操作符用零补齐
one_hotindices, depth, dtype形状 [len(indices), depth],在每个索引处放一个 1。索引超出 0 … depth − 1 的得到一行零。它不接受轴参数

scatter 会丢弃形状之外的点,不会把它环绕到地图另一边;同一格上后面的点会覆盖前面的点。每次写入都把值饱和到数据类型的范围:把 300 写入 i8 得到 127,而回绕式转换会得到 44。

点的第三个元素是值,所以在用敌人和蚁丘构建存在平面之前,先把它们裁成 [r, c]:

{"scatter": [
  {"map": [{"var": "foes"}, [{"var": "0"}, {"var": "1"}]]},
  {"var": "size"},
  "i8"
]}

其中的 map,在一份观测上运行的结果:

重塑与组合

操作符参数结果
stacktensors, axis在 axis 处新增一个轴。所有输入的形状和数据类型必须相同
concattensors, axis沿已有的轴拼接。其他各维必须一致
unstackT, axis一个张量的 JSON 列表,axis 上每个索引对应一个
reshapeT, shape同样的元素,换成新形状。元素数必须相等,且不接受 -1
transposeT, perm?重排各轴:perm[i] 是将成为第 i 轴的输入轴。不给 perm 时,轴顺序反转
padT, before, after, value在每个轴 d 上添加 before[d] 和 after[d] 个格子,以 value 填充(默认 0)
cropT, offset, shape从 offset 开始、大小为 shape 的区域。超出输入边缘的部分为零

把七个 [H, W] 平面在第 0 轴上堆叠得到 [7, H, W],再重塑为 [1, 7, H, W] 就加上了计算图期望的批次轴。一份真实的清单根据观测的大小构建第二个形状,所以一个适配器适用于每张地图。

stack 不接受数据类型参数。每个平面必须已经是输入所声明的数据类型,所以要给每个平面各自的构造操作符传入这个数据类型。数据类型混杂是错误,stack 从不做类型提升。

转换与归一化

操作符参数结果
castT, dtype转换后的张量。整数类型会饱和,并向零截断
normalizeT, mean, scale?(x − mean) × scale,结果为 f32。scale 默认为 1

这两个就是这门语言全部的算术。它有意不提供加法、乘法和卷积(为什么)。

没有 dilate。把非零格周围距离平方范围内的每一格都标记出来(含环绕),只服务于一个平面,即视野掩码,而观测为此携带了 vis。那个平面只需一次 rle_expand。

读取张量

操作符参数结果
argmaxT, axis一个扁平的 JSON 列表:其他各轴每个位置上沿 axis 的最大值索引,按行优先顺序。并列时取第一个
gatherT, indices, axis?沿 axis(默认 0)取 indices 处各切片组成的张量。索引超出末尾则调用失败
to_listT张量,以嵌套 JSON 列表表示
shapeT它的形状,以列表表示
dtypeT它的数据类型,以字符串表示

适配器必须以张量结尾,所以这些只能在中途使用,绝不能作为最后一步。shape 和 dtype 让程序能根据手上的东西分支。节点对 to_list 和其他操作符一样按元素计价,所以转换整张地图就要付整张地图的开销。

裁判用 argmax 和 gather 读取你的策略头,你不需要用它们来写策略头。适配器可以使用它们(为双输入计算图供数的适配器,可能会从自己构建的张量推导出第二个输入),但典型的参赛模型两者都不会调用。

这里没有的

at、get、len 和 range 不是这门语言的操作符。改用下面这些:

原想用改用
at(list, i){"val": [[N], …, {"var": ""}]}:引擎会对路径段求值,所以计算出的索引就是路径(作用域)
get(value, path)对 reduce 的累加器用 {"var": "accumulator.0"};对其他任何计算出的值,用单元素 reduce 投影取出(这种写法)
len(list){"length": […]},核心操作符之一
range(n)没有对应物。自己构建需要的列表,或者让 scatter 和 rle_expand 来做循环