acuity.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. /* jshint esversion: 6 */
  2. /* eslint "indent": [ "error", 4, { "SwitchCase": 1 } ] */
  3. var acuity = acuity || {};
  4. var json = json || require('./json');
  5. acuity.ModelFactory = class {
  6. match(context) {
  7. const extension = context.identifier.split('.').pop().toLowerCase();
  8. if (extension === 'json') {
  9. const obj = context.open('json');
  10. if (obj && obj.MetaData && obj.Layers) {
  11. return true;
  12. }
  13. }
  14. return false;
  15. }
  16. open(context) {
  17. return acuity.Metadata.open(context).then((metadata) => {
  18. const extension = context.identifier.split('.').pop().toLowerCase();
  19. switch (extension) {
  20. case 'json': {
  21. const obj = context.open('json');
  22. if (obj && obj.MetaData && obj.Layers) {
  23. return new acuity.Model(metadata, obj);
  24. }
  25. }
  26. }
  27. });
  28. }
  29. };
  30. acuity.Model = class {
  31. constructor(metadata, model, data, quantization) {
  32. this._name = model.MetaData.Name;
  33. this._format = 'Acuity ' + 'v' + model.MetaData.AcuityVersion;
  34. this._runtime = model.MetaData.Platform;
  35. this._graphs = [ new acuity.Graph(metadata, model, data, quantization) ];
  36. }
  37. get format() {
  38. return this._format;
  39. }
  40. get name() {
  41. return this._name;
  42. }
  43. get runtime() {
  44. return this._runtime;
  45. }
  46. get graphs() {
  47. return this._graphs;
  48. }
  49. };
  50. acuity.Graph = class {
  51. constructor(metadata, model) {
  52. this._nodes = [];
  53. this._inputs = [];
  54. this._outputs = [];
  55. const args = new Map();
  56. const arg = (name) => {
  57. if (!args.has(name)) {
  58. args.set(name, { name: name, shape: null });
  59. }
  60. return args.get(name);
  61. };
  62. for (const layerName of Object.keys(model.Layers)) {
  63. const layer = model.Layers[layerName];
  64. layer.inputs = layer.inputs.map((input) => {
  65. return arg(input);
  66. });
  67. layer.outputs = layer.outputs.map((port) => {
  68. const argument = arg("@" + layerName + ":" + port);
  69. let shape = null;
  70. if (layer.op.toLowerCase() == 'input' ||
  71. layer.op.toLowerCase() == 'variable') {
  72. if (Object.prototype.hasOwnProperty.call(layer.parameters, 'shape') && layer.parameters.shape.length > 0) {
  73. shape = layer.parameters.shape;
  74. }
  75. else if (Object.prototype.hasOwnProperty.call(layer.parameters, 'size') && Object.prototype.hasOwnProperty.call(layer.parameters, 'channels')) {
  76. const sizes = layer.parameters.size.split(' ');
  77. shape = [0, parseInt(sizes[0]), parseInt(sizes[1]), layer.parameters.channels];
  78. }
  79. }
  80. argument.shape = shape;
  81. return argument;
  82. });
  83. }
  84. new acuity.Inference(model.Layers);
  85. for (const pair of args) {
  86. const type = new acuity.TensorType(null, new acuity.TensorShape(pair[1].shape));
  87. const arg = new acuity.Argument(pair[0], type, null, null);
  88. args.set(pair[0], arg);
  89. }
  90. for (const layerName of Object.keys(model.Layers)) {
  91. const layer = model.Layers[layerName];
  92. switch (layer.op.toLowerCase()) {
  93. case 'input': {
  94. this._inputs.push(new acuity.Parameter(layerName, true, [
  95. args.get(layer.outputs[0].name)
  96. ]));
  97. break;
  98. }
  99. case 'output': {
  100. this._outputs.push(new acuity.Parameter(layerName, true, [
  101. args.get(layer.inputs[0].name)
  102. ]));
  103. break;
  104. }
  105. default: {
  106. this._nodes.push(new acuity.Node(metadata, layerName, layer, args));
  107. break;
  108. }
  109. }
  110. }
  111. }
  112. get inputs() {
  113. return this._inputs;
  114. }
  115. get outputs() {
  116. return this._outputs;
  117. }
  118. get nodes() {
  119. return this._nodes;
  120. }
  121. };
  122. acuity.Node = class {
  123. constructor(metadata, name, layer, args) {
  124. this._name = name;
  125. this._type = metadata.type(layer.op) || { name: layer.op };
  126. this._inputs = [];
  127. this._outputs = [];
  128. this._attributes = [];
  129. this._layer = layer;
  130. if (this._type) {
  131. if (layer.parameters) {
  132. for (const key of Object.keys(layer.parameters)) {
  133. const attributeMetadata = metadata.attribute(this._type, key);
  134. this._attributes.push(new acuity.Attribute(attributeMetadata, key, layer.parameters[key]));
  135. }
  136. }
  137. }
  138. for (let i = 0; i < layer.inputs.length; i++) {
  139. const input = layer.inputs[i];
  140. const arg = args.get(input.name);
  141. const name = this._type && this._type.inputs && i < this._type.inputs.length ? this._type.inputs[i].name : 'input' + i.toString();
  142. this._inputs.push(new acuity.Parameter(name, true, [ arg ]));
  143. }
  144. if (this._type && this._type.constants) {
  145. for (const constant of this._type.constants) {
  146. // const name = "@" + this._name + ":" + constant.name;
  147. const type = new acuity.TensorType(null, new acuity.TensorShape(null));
  148. const argument = new acuity.Argument('', type, null, new acuity.Tensor(type));
  149. this._inputs.push(new acuity.Parameter(constant.name, true, [ argument ]));
  150. }
  151. }
  152. for (let i = 0; i < layer.outputs.length; i++) {
  153. const output = layer.outputs[i];
  154. const arg = args.get(output.name);
  155. const name = this._type && this._type.outputs && i < this._type.outputs.length ? this._type.outputs[i].name : 'output' + i.toString();
  156. this._outputs.push(new acuity.Parameter(name, true, [arg]));
  157. }
  158. }
  159. get type() {
  160. return this._type;
  161. }
  162. get name() {
  163. return this._name;
  164. }
  165. get inputs() {
  166. return this._inputs;
  167. }
  168. get outputs() {
  169. return this._outputs;
  170. }
  171. get attributes() {
  172. return this._attributes;
  173. }
  174. };
  175. acuity.Attribute = class {
  176. constructor(metadata, name, value) {
  177. this._type = null;
  178. this._name = name;
  179. this._value = value;
  180. if (metadata) {
  181. this._type = metadata.type || null;
  182. if (Object.prototype.hasOwnProperty.call(metadata, 'default')) {
  183. if (metadata.default === value) {
  184. this._visible = false;
  185. }
  186. }
  187. }
  188. }
  189. get name() {
  190. return this._name;
  191. }
  192. get type() {
  193. return this._type;
  194. }
  195. get value() {
  196. return this._value;
  197. }
  198. get visible() {
  199. return this._visible == false ? false : true;
  200. }
  201. };
  202. acuity.Parameter = class {
  203. constructor(name, visible, args) {
  204. this._name = name;
  205. this._visible = visible;
  206. this._arguments = args;
  207. if (this._arguments.some((arg) => !arg)) {
  208. throw "";
  209. }
  210. }
  211. get name() {
  212. return this._name;
  213. }
  214. get visible() {
  215. return this._visible;
  216. }
  217. get arguments() {
  218. return this._arguments;
  219. }
  220. };
  221. acuity.Argument = class {
  222. constructor(name, type, quantization, initializer) {
  223. if (typeof name !== 'string') {
  224. throw new acuity.Error("Invalid argument identifier '" + JSON.stringify(name) + "'.");
  225. }
  226. this._name = name;
  227. this._type = type || null;
  228. this._quantization = quantization || null;
  229. this._initializer = initializer || null;
  230. }
  231. get name() {
  232. return this._name;
  233. }
  234. get type() {
  235. return this._type;
  236. }
  237. get quantization() {
  238. return this._quantization;
  239. }
  240. set quantization(quantization) {
  241. this._quantization = quantization;
  242. }
  243. get initializer() {
  244. return this._initializer;
  245. }
  246. set initializer(initializer) {
  247. this._initializer = initializer;
  248. }
  249. };
  250. acuity.TensorType = class {
  251. constructor(dataType, shape) {
  252. this._dataType = dataType || '?';
  253. this._shape = shape;
  254. }
  255. get dataType() {
  256. return this._dataType;
  257. }
  258. set dataType(dataType) {
  259. this._dataType = dataType;
  260. }
  261. get shape() {
  262. return this._shape;
  263. }
  264. set shape(shape) {
  265. this._shape = shape;
  266. }
  267. toString() {
  268. return (this.dataType || '?') + this._shape.toString();
  269. }
  270. };
  271. acuity.TensorShape = class {
  272. constructor(dimensions) {
  273. this._dimensions = dimensions || null;
  274. }
  275. get dimensions() {
  276. return this._dimensions;
  277. }
  278. set dimensions(dimensions) {
  279. this._dimensions = dimensions;
  280. }
  281. toString() {
  282. if (!this._dimensions || this._dimensions.length == 0) {
  283. return '';
  284. }
  285. return '[' + this._dimensions.map((dimension) => dimension.toString()).join(',') + ']';
  286. }
  287. };
  288. acuity.Tensor = class {
  289. constructor(type) {
  290. this._type = type;
  291. }
  292. get kind() {
  293. return 'Constant';
  294. }
  295. get type() {
  296. return this._type;
  297. }
  298. get state() {
  299. return 'Not supported.';
  300. }
  301. toString() {
  302. return '';
  303. }
  304. };
  305. acuity.Metadata = class {
  306. static open(context) {
  307. if (acuity.Metadata._metadata) {
  308. return Promise.resolve(acuity.Metadata._metadata);
  309. }
  310. return context.request('acuity-metadata.json', 'utf-8', null).then((data) => {
  311. acuity.Metadata._metadata = new acuity.Metadata(data);
  312. return acuity.Metadata._metadata;
  313. }).catch(() => {
  314. acuity.Metadata._metadata = new acuity.Metadata(null);
  315. return acuity.Metadata._metadata;
  316. });
  317. }
  318. constructor(data) {
  319. this._map = new Map();
  320. if (data) {
  321. const metadata = JSON.parse(data);
  322. this._map = new Map(metadata.map((item) => [ item.name, item ]));
  323. }
  324. }
  325. type(name) {
  326. return this._map.get(name);
  327. }
  328. attribute(type, name) {
  329. const schema = this.type(type);
  330. if (schema) {
  331. let attributeMap = schema.attributeMap;
  332. if (!attributeMap) {
  333. attributeMap = {};
  334. if (schema.attributes) {
  335. for (const attribute of schema.attributes) {
  336. attributeMap[attribute.name] = attribute;
  337. }
  338. }
  339. schema.attributeMap = attributeMap;
  340. }
  341. const attributeSchema = attributeMap[name];
  342. if (attributeSchema) {
  343. return attributeSchema;
  344. }
  345. }
  346. return null;
  347. }
  348. };
  349. acuity.Inference = class {
  350. constructor(layers) {
  351. this._outputs = new Map();
  352. const outputLayers = [];
  353. for (const layerName of Object.keys(layers)) {
  354. const layer = layers[layerName];
  355. if (layer.op.toLowerCase() == 'output') {
  356. outputLayers.push(layer);
  357. }
  358. for (const output of layer.outputs) {
  359. this._outputs.set(output.name, layer);
  360. }
  361. }
  362. this._passthroughs = new Set([
  363. 'a_times_b_plus_c', 'abs', 'cast', 'clipbyvalue', 'dequantize', 'dtype_converter',
  364. 'elu', 'exp', 'floor', 'floor_div', 'hard_swish', 'leakyrelu', 'log', 'log_softmax',
  365. 'neg', 'pow', 'prelu', 'quantize', 'relu', 'relu_keras', 'relun', 'rsqrt', 'sigmoid',
  366. 'sin', 'softmax', 'softrelu', 'sqrt', 'square', 'tanh'
  367. ]);
  368. this._operators = new Map();
  369. this._operators.set('concat', (inputs, parameters) => {
  370. const outputShape = inputs[0].slice();
  371. outputShape[parameters.dim] = 0;
  372. for (const shape of inputs) {
  373. outputShape[parameters.dim] += shape[parameters.dim];
  374. }
  375. return [outputShape];
  376. });
  377. this._operators.set('convolution', (inputs, parameters) => {
  378. if (parameters.padding == 'VALID') {
  379. const out_h = ~~((inputs[0][1] + parameters.stride_h - parameters.ksize_h) / parameters.stride_h);
  380. const out_w = ~~((inputs[0][2] + parameters.stride_w - parameters.ksize_w) / parameters.stride_w);
  381. return [[inputs[0][0], out_h, out_w, parameters.weights]];
  382. }
  383. else if (parameters.padding == 'SAME') {
  384. const out_h = ~~((inputs[0][1] + parameters.stride_h - 1) / parameters.stride_h);
  385. const out_w = ~~((inputs[0][2] + parameters.stride_w - 1) / parameters.stride_w);
  386. return [[inputs[0][0], out_h, out_w, parameters.weights]];
  387. }
  388. });
  389. this._operators.set('fullconnect', (inputs, parameters) => {
  390. return [inputs[0].slice(0, parameters.axis).concat([parameters.weights])];
  391. });
  392. this._operators.set('pooling', (inputs, parameters) => {
  393. if (parameters.padding == 'VALID') {
  394. const out_h = ~~((inputs[0][1] + parameters.stride_h - parameters.ksize_h) / parameters.stride_h);
  395. const out_w = ~~((inputs[0][2] + parameters.stride_w - parameters.ksize_w) / parameters.stride_w);
  396. return [[inputs[0][0], out_h, out_w, inputs[0][3]]];
  397. }
  398. else if (parameters.padding == 'SAME') {
  399. const out_h = ~~((inputs[0][1] + parameters.stride_h - 1) / parameters.stride_h);
  400. const out_w = ~~((inputs[0][2] + parameters.stride_w - 1) / parameters.stride_w);
  401. return [[inputs[0][0], out_h, out_w, inputs[0][3]]];
  402. }
  403. });
  404. for (const layer of outputLayers) {
  405. for (const output of layer.outputs) {
  406. this._infer(output);
  407. }
  408. }
  409. }
  410. _infer(output) {
  411. if (this._outputs.has(output.name)) {
  412. let inputShapeReady = true;
  413. const layer = this._outputs.get(output.name);
  414. for (const input of layer.inputs) {
  415. if (input.shape === null) {
  416. this._infer(input);
  417. if (input.shape === null) {
  418. inputShapeReady = false;
  419. break;
  420. }
  421. }
  422. }
  423. if (inputShapeReady) {
  424. let callback = null;
  425. if (this._operators.has(layer.op)) {
  426. callback = this._operators.get(layer.op);
  427. }
  428. else if (this._passthroughs.has(layer.op)) {
  429. callback = (inputs) => [ inputs[0].slice() ];
  430. }
  431. else {
  432. callback = () => [];
  433. }
  434. const parameters = layer.parameters;
  435. const inputs = layer.inputs.map((input) => input.shape);
  436. const outputs = callback(inputs, parameters);
  437. for (let i = 0; i < outputs.length; i++) {
  438. if (i < layer.outputs.length) {
  439. layer.outputs[i].shape = outputs[i];
  440. }
  441. }
  442. }
  443. }
  444. }
  445. };
  446. acuity.Error = class extends Error {
  447. constructor(message) {
  448. super(message);
  449. this.name = 'Error loading Acuity model.';
  450. }
  451. };
  452. if (typeof module !== 'undefined' && typeof module.exports === 'object') {
  453. module.exports.ModelFactory = acuity.ModelFactory;
  454. }