cntk.js 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421
  1. /* jshint esversion: 6 */
  2. var cntk = cntk || {};
  3. var protobuf = protobuf || require('./protobuf');
  4. var cntk_v1 = {};
  5. var cntk_v2 = null;
  6. cntk.ModelFactory = class {
  7. match(context) {
  8. const stream = context.stream;
  9. // CNTK v1
  10. const signature = [ 0x42, 0x00, 0x43, 0x00, 0x4e, 0x00, 0x00, 0x00 ];
  11. if (signature.length <= stream.length && stream.peek(signature.length).every((value, index) => value === signature[index])) {
  12. return 'cntk.v1';
  13. }
  14. // CNTK v2
  15. const tags = context.tags('pb');
  16. if (tags.get(1) === 0 && tags.get(2) === 2) {
  17. return 'cntk.v2';
  18. }
  19. return undefined;
  20. }
  21. open(context, match) {
  22. return cntk.Metadata.open(context).then((metadata) => {
  23. switch (match) {
  24. case 'cntk.v1': {
  25. let obj = null;
  26. try {
  27. const stream = context.stream;
  28. const buffer = stream.peek();
  29. obj = new cntk_v1.ComputationNetwork(buffer);
  30. }
  31. catch (error) {
  32. const message = error && error.message ? error.message : error.toString();
  33. throw new cntk.Error('File format is not CNTK v1 (' + message.replace(/\.$/, '') + ').');
  34. }
  35. return new cntk.Model(metadata, 1, obj);
  36. }
  37. case 'cntk.v2': {
  38. return context.require('./cntk-proto').then(() => {
  39. let obj = null;
  40. try {
  41. cntk_v2 = protobuf.get('cntk').CNTK.proto;
  42. cntk_v2.PoolingType = { 0: 'Max', 1: 'Average' };
  43. const stream = context.stream;
  44. const reader = protobuf.BinaryReader.open(stream);
  45. const dictionary = cntk_v2.Dictionary.decode(reader);
  46. obj = cntk.ModelFactory._convertDictionary(dictionary);
  47. }
  48. catch (error) {
  49. const message = error && error.message ? error.message : error.toString();
  50. throw new cntk.Error('File format is not cntk.Dictionary (' + message.replace(/\.$/, '') + ').');
  51. }
  52. return new cntk.Model(metadata, 2, obj);
  53. });
  54. }
  55. default: {
  56. throw new cntk.Error("Unknown CNTK format '" + match + "'.");
  57. }
  58. }
  59. });
  60. }
  61. static _convertDictionary(dictionary) {
  62. const target = {};
  63. for (const key of Object.keys(dictionary.data).filter((key) => key != 'version')) {
  64. target[key] = cntk.ModelFactory._convertDictionaryValue(dictionary.data[key]);
  65. }
  66. return target;
  67. }
  68. static _convertDictionaryValue(dictionaryValue) {
  69. switch (dictionaryValue.value_type) {
  70. case cntk_v2.DictionaryValue.Type.Bool:
  71. return dictionaryValue.bool_value;
  72. case cntk_v2.DictionaryValue.Type.Int:
  73. return dictionaryValue.int_value;
  74. case cntk_v2.DictionaryValue.Type.SizeT:
  75. return dictionaryValue.size_t_value;
  76. case cntk_v2.DictionaryValue.Type.Float:
  77. return dictionaryValue.float_value;
  78. case cntk_v2.DictionaryValue.Type.Double:
  79. return dictionaryValue.double_value;
  80. case cntk_v2.DictionaryValue.Type.String:
  81. return dictionaryValue.string_value;
  82. case cntk_v2.DictionaryValue.Type.Vector:
  83. return cntk.ModelFactory._convertVectorValue(dictionaryValue.vector_value);
  84. case cntk_v2.DictionaryValue.Type.NDShape:
  85. return dictionaryValue.nd_shape_value;
  86. case cntk_v2.DictionaryValue.Type.Axis:
  87. return dictionaryValue.axis_value;
  88. case cntk_v2.DictionaryValue.Type.Dictionary:
  89. return cntk.ModelFactory._convertDictionary(dictionaryValue.dictionary_value);
  90. case cntk_v2.DictionaryValue.Type.NDArrayView:
  91. return dictionaryValue.nd_array_view_value;
  92. }
  93. throw new cntk.Error("Unknown dictionary value type '" + dictionaryValue.value_type.toString() + "'.");
  94. }
  95. static _convertVectorValue(vectorValue) {
  96. return vectorValue.value.map((item) => {
  97. return cntk.ModelFactory._convertDictionaryValue(item);
  98. });
  99. }
  100. };
  101. cntk.Model = class {
  102. constructor(metadata, version, obj) {
  103. switch (version) {
  104. case 1:
  105. this._format = 'CNTK v1' + (obj.version ? ('.' + obj.version.toString()) : '');
  106. break;
  107. case 2:
  108. this._format = 'CNTK v2';
  109. break;
  110. }
  111. this._graphs = [];
  112. this._graphs.push(new cntk.Graph(metadata, version, obj));
  113. }
  114. get graphs() {
  115. return this._graphs;
  116. }
  117. get format() {
  118. return this._format;
  119. }
  120. };
  121. cntk.Graph = class {
  122. constructor(metadata, version, obj) {
  123. metadata = new cntk.GraphMetadata(metadata);
  124. this._inputs = [];
  125. this._outputs = [];
  126. this._nodes = [];
  127. const args = new Map();
  128. const arg = (name, version, obj) => {
  129. if (obj && args.has(name)) {
  130. throw new cntk.Error("Duplicate argument identifier '" + name + "'.");
  131. }
  132. if (!args.has(name)) {
  133. switch (version) {
  134. case 1:
  135. args.set(name, new cntk.Argument(version, obj ? obj : { name: name }));
  136. break;
  137. case 2:
  138. args.set(name, new cntk.Argument(version, obj ? obj : { uid: name }));
  139. break;
  140. }
  141. }
  142. return args.get(name);
  143. };
  144. switch (version) {
  145. case 1: {
  146. for (const name of Object.keys(obj.nodes)) {
  147. const node = obj.nodes[name];
  148. switch (node.__type__) {
  149. case 'InputValue':
  150. this._inputs.push(new cntk.Parameter(node.name, [
  151. new cntk.Argument(version, node)
  152. ]));
  153. break;
  154. case 'LearnableParameter':
  155. arg(node.name, version, node);
  156. break;
  157. }
  158. }
  159. for (const name of Object.keys(obj.nodes)) {
  160. const node = obj.nodes[name];
  161. if (node.__type__ != 'InputValue' && node.__type__ != 'LearnableParameter') {
  162. this._nodes.push(new cntk.Node(metadata, version, node, arg));
  163. }
  164. }
  165. if (obj.output) {
  166. for (const output of obj.output) {
  167. this._outputs.push(new cntk.Parameter(output, [ arg(output, version) ]));
  168. }
  169. }
  170. break;
  171. }
  172. case 2: {
  173. const map = new Map(obj.primitive_functions.map((node) => [ node.uid, node ]));
  174. for (const input of obj.inputs) {
  175. const argument = arg(input.uid, version, input);
  176. // VariableKind { 0: 'input', 1: 'output', 2: 'parameter', 3: 'constant', 4: 'placeholder' }
  177. if (input.kind == 0) {
  178. const inputName = input.name || input.uid;
  179. this._inputs.push(new cntk.Parameter(inputName, [ argument ]));
  180. }
  181. }
  182. for (const block of obj.primitive_functions) {
  183. if (block.op == 57 && block.block_function_composite) {
  184. const list = [ block.block_function_composite.root ];
  185. const output = map.get(block.block_function_composite.root);
  186. const keys = block.block_function_composite_arguments_map_keys;
  187. const values = block.block_function_composite_arguments_map_values;
  188. block.inputs = values;
  189. if (!Array.isArray(keys) || !Array.isArray(values) || keys.length !== values.length) {
  190. throw new cntk.Error('Invalid block function composite arguments.');
  191. }
  192. const inputs = keys.map((key) => new cntk.Parameter(key, [ arg(key, version) ]));
  193. const outputs = [ new cntk.Parameter('output', [ arg(output.uid + '_Output_0', version) ]) ];
  194. const nodes = [];
  195. while (list.length > 0) {
  196. const name = list.shift();
  197. if (map.has(name)) {
  198. const node = map.get(name);
  199. nodes.push(new cntk.Node(metadata, version, node, arg));
  200. map.delete(name);
  201. for (let i = 0; i < node.inputs.length; i++) {
  202. const parts = node.inputs[i].split('_');
  203. if (parts.length >= 3) {
  204. parts.pop();
  205. if (parts.pop() == 'Output') {
  206. list.push(parts.join('_'));
  207. }
  208. }
  209. }
  210. }
  211. }
  212. const func = new cntk.Function(block.block_function_op_name, nodes, inputs, outputs);
  213. metadata.add(block.uid, func);
  214. }
  215. }
  216. for (const node of map.values()) {
  217. this._nodes.push(new cntk.Node(metadata, version, node, arg));
  218. }
  219. break;
  220. }
  221. default: {
  222. throw new cntk.Error("Unsupported graph version '" + version + "'.");
  223. }
  224. }
  225. }
  226. get nodes() {
  227. return this._nodes;
  228. }
  229. get inputs() {
  230. return this._inputs;
  231. }
  232. get outputs() {
  233. return this._outputs;
  234. }
  235. };
  236. cntk.Parameter = class {
  237. constructor(name, args) {
  238. this._name = name;
  239. this._arguments = args;
  240. }
  241. get name() {
  242. return this._name;
  243. }
  244. get visible() {
  245. return true;
  246. }
  247. get arguments() {
  248. return this._arguments;
  249. }
  250. };
  251. cntk.Argument = class {
  252. constructor(version, obj) {
  253. switch (version) {
  254. case 1:
  255. switch (obj.__type__) {
  256. case 'InputValue':
  257. this._name = obj.name;
  258. this._type = new cntk.TensorType(version, obj.precision, obj.sampleLayout);
  259. this._initializer = null;
  260. break;
  261. case 'LearnableParameter':
  262. this._name = obj.name;
  263. this._type = null;
  264. this._initializer = new cntk.Tensor(version, obj);
  265. break;
  266. default:
  267. this._name = obj.name;
  268. this._type = null;
  269. this._initializer = null;
  270. break;
  271. }
  272. break;
  273. case 2:
  274. if (obj.value) {
  275. this._name = obj.name || obj.uid;
  276. this._type = null;
  277. this._initializer = new cntk.Tensor(version, obj);
  278. }
  279. else {
  280. this._name = obj.uid;
  281. if (obj.data_type && obj.shape) {
  282. this._type = new cntk.TensorType(version, obj.data_type, obj.shape);
  283. }
  284. this._initializer = null;
  285. }
  286. break;
  287. }
  288. }
  289. get name() {
  290. return this._name;
  291. }
  292. get type() {
  293. if (this._type) {
  294. return this._type;
  295. }
  296. if (this._initializer) {
  297. return this._initializer.type;
  298. }
  299. return null;
  300. }
  301. get description() {
  302. return '';
  303. }
  304. get initializer() {
  305. return this._initializer;
  306. }
  307. };
  308. cntk.Node = class {
  309. constructor(metadata, version, obj, arg) {
  310. this._attributes = [];
  311. this._inputs = [];
  312. this._outputs = [];
  313. let inputs = [];
  314. let outputs = [];
  315. switch (version) {
  316. case 1: {
  317. const type = obj.__type__;
  318. this._type = metadata.type(type) || { name: type };
  319. this._name = obj.name;
  320. for (const entry of Object.entries(obj)) {
  321. const name = entry[0];
  322. const value = entry[1];
  323. if (name != '__type__' && name != 'name' && name != 'inputs' && name != 'precision') {
  324. this._attributes.push(new cntk.Attribute(metadata.attribute(type, name), name, value));
  325. }
  326. }
  327. inputs = obj.inputs.map((input) => arg(input, version));
  328. outputs = [ arg(this._name, version) ];
  329. break;
  330. }
  331. case 2: {
  332. this._name = obj.name || obj.uid || null;
  333. const output = obj.uid;
  334. if (obj.op == 57) {
  335. this._type = metadata.type(obj.uid) || { name: obj.uid };
  336. }
  337. else if (Object.prototype.hasOwnProperty.call(obj, 'op')) {
  338. this._type = metadata.name(obj.op.toNumber()) || { name: obj.op ? obj.op.toString() : '?' };
  339. }
  340. else {
  341. const type = obj.type;
  342. this._type = metadata.type(type) || { name: type };
  343. if (obj.user_defined_state) {
  344. for (const attributeName of Object.keys(obj.user_defined_state)) {
  345. this._attributes.push(new cntk.Attribute(metadata.attribute(type, attributeName), attributeName, obj.user_defined_state[attributeName]));
  346. }
  347. }
  348. }
  349. if (obj.attributes) {
  350. for (const entry of Object.entries(obj.attributes)) {
  351. this._attributes.push(new cntk.Attribute(metadata.attribute(this._type, entry[0]), entry[0], entry[1]));
  352. }
  353. }
  354. inputs = obj.inputs.map((input) => arg(input, version));
  355. outputs.push(arg(output + '_Output_0', version));
  356. break;
  357. }
  358. }
  359. let inputIndex = 0;
  360. if (this._type && this._type.inputs) {
  361. for (const inputSchema of this._type.inputs) {
  362. if (inputIndex < inputs.length || inputSchema.option != 'optional') {
  363. const inputCount = inputSchema.list ? (inputs.length - inputIndex) : 1;
  364. const inputArguments = [];
  365. for (const inputArgument of inputs.slice(inputIndex, inputIndex + inputCount)) {
  366. if (inputArgument.name != '' || inputSchema.option != 'optional') {
  367. inputArguments.push(inputArgument);
  368. }
  369. }
  370. this._inputs.push(new cntk.Parameter(inputSchema.name, inputArguments));
  371. inputIndex += inputCount;
  372. }
  373. }
  374. }
  375. this._inputs.push(...inputs.slice(inputIndex).map((argument, index) => {
  376. return new cntk.Parameter((inputIndex + index).toString(), [ argument ]);
  377. }));
  378. let outputIndex = 0;
  379. if (this._type && this._type.outputs) {
  380. for (const outputSchema of this._type.outputs) {
  381. if (outputIndex < outputs.length || !outputSchema.optional) {
  382. const outputCount = outputSchema.list ? (outputs.length - outputIndex) : 1;
  383. this._outputs.push(new cntk.Parameter(outputSchema.name, outputs.slice(outputIndex, outputIndex + outputCount)));
  384. outputIndex += outputCount;
  385. }
  386. }
  387. }
  388. this._outputs.push(...outputs.slice(outputIndex).map((argument) => {
  389. return new cntk.Parameter(outputIndex.toString(), [ argument ]);
  390. }));
  391. }
  392. get type() {
  393. return this._type;
  394. }
  395. get name() {
  396. return this._name;
  397. }
  398. get attributes() {
  399. return this._attributes;
  400. }
  401. get inputs() {
  402. return this._inputs;
  403. }
  404. get outputs() {
  405. return this._outputs;
  406. }
  407. };
  408. cntk.Attribute = class {
  409. constructor(schema, name, value) {
  410. this._name = name;
  411. this._value = value;
  412. this._type = null;
  413. if (cntk_v1 && this._value instanceof cntk_v1.TensorShape) {
  414. this._value = new cntk.TensorShape(1, value);
  415. this._type = 'shape';
  416. }
  417. if (cntk_v2 && this._value instanceof cntk_v2.NDShape) {
  418. this._value = new cntk.TensorShape(2, value);
  419. this._type = 'shape';
  420. }
  421. if (cntk_v2 && this._value instanceof cntk_v2.Axis) {
  422. const axis = { __type__: 'Axis' };
  423. for (const key of Object.keys(value).filter((key) => key !== 'name')) {
  424. axis[key] = value[key];
  425. }
  426. this._value = axis;
  427. }
  428. if (schema) {
  429. if (schema.type) {
  430. this._type = schema.type;
  431. const type = cntk_v1[this._type] || cntk_v2[this._type];
  432. if (type && type[this._value]) {
  433. this._value = type[this._value];
  434. }
  435. }
  436. if (Object.prototype.hasOwnProperty.call(schema, 'visible') && !schema.visible) {
  437. this._visible = false;
  438. }
  439. else if (Object.prototype.hasOwnProperty.call(schema, 'default')) {
  440. let defaultValue = schema.default;
  441. value = this._value;
  442. if (typeof value == 'function') {
  443. value = value();
  444. }
  445. if (this._type == 'shape') {
  446. value = value.dimensions;
  447. }
  448. if (value == defaultValue) {
  449. this._visible = false;
  450. }
  451. else if (Array.isArray(value) && Array.isArray(defaultValue)) {
  452. defaultValue = defaultValue.slice(0, defaultValue.length);
  453. if (defaultValue.length > 1 && defaultValue[defaultValue.length - 1] == null) {
  454. defaultValue.pop();
  455. while (defaultValue.length < value.length) {
  456. defaultValue.push(defaultValue[defaultValue.length - 1]);
  457. }
  458. }
  459. if (value.every((item, index) => { return item == defaultValue[index]; })) {
  460. this._visible = false;
  461. }
  462. }
  463. }
  464. }
  465. }
  466. get name() {
  467. return this._name;
  468. }
  469. get type() {
  470. return this._type;
  471. }
  472. get value() {
  473. return this._value;
  474. }
  475. get visible() {
  476. return this._visible == false ? false : true;
  477. }
  478. };
  479. cntk.Tensor = class {
  480. constructor(version, tensor) {
  481. switch (version) {
  482. case 1:
  483. if (tensor.__type__ == 'LearnableParameter') {
  484. this._name = tensor.name || null;
  485. this._type = new cntk.TensorType(version, tensor.precision, tensor.sampleLayout);
  486. }
  487. break;
  488. case 2:
  489. this._name = tensor.name || tensor.uid || null;
  490. this._type = new cntk.TensorType(version, tensor.data_type, tensor.shape);
  491. this._value = tensor.value;
  492. break;
  493. }
  494. }
  495. get name() {
  496. return this._name;
  497. }
  498. get type() {
  499. return this._type;
  500. }
  501. get state() {
  502. return this._context().state || null;
  503. }
  504. get value() {
  505. const context = this._context();
  506. if (context.state) {
  507. return null;
  508. }
  509. context.limit = Number.MAX_SAFE_INTEGER;
  510. return this._decode(context, 0);
  511. }
  512. toString() {
  513. const context = this._context();
  514. if (context.state) {
  515. return '';
  516. }
  517. context.limit = 10000;
  518. const value = this._decode(context, 0);
  519. return JSON.stringify(value, null, 4);
  520. }
  521. _context() {
  522. const context = {};
  523. context.index = 0;
  524. context.count = 0;
  525. context.state = null;
  526. if (this._type.dataType == '?') {
  527. context.state = 'Tensor has unknown data type.';
  528. return context;
  529. }
  530. if (!this._type.shape) {
  531. context.state = 'Tensor has no dimensions.';
  532. return context;
  533. }
  534. const value = this._value;
  535. if (!value) {
  536. context.state = 'Tensor data is empty.';
  537. return context;
  538. }
  539. switch (this._type.dataType) {
  540. case 'float32':
  541. if (value.float_values && value.float_values.value && value.float_values.value.length > 0) {
  542. context.data = value.float_values.value;
  543. }
  544. else {
  545. context.state = 'Tensor data is empty.';
  546. }
  547. break;
  548. default:
  549. context.state = 'Tensor data type is not implemented.';
  550. break;
  551. }
  552. context.dataType = this._type.dataType;
  553. context.shape = this._type.shape.dimensions;
  554. return context;
  555. }
  556. _decode(context, dimension) {
  557. let shape = context.shape;
  558. if (context.shape.length == 0) {
  559. shape = [ 1 ];
  560. }
  561. const results = [];
  562. const size = shape[dimension];
  563. if (dimension == shape.length - 1) {
  564. for (let i = 0; i < size; i++) {
  565. if (context.count > context.limit) {
  566. results.push('...');
  567. return results;
  568. }
  569. results.push(context.data[context.index++]);
  570. context.count++;
  571. }
  572. }
  573. else {
  574. for (let j = 0; j < size; j++) {
  575. if (context.count > context.limit) {
  576. results.push('...');
  577. return results;
  578. }
  579. results.push(this._decode(context, dimension + 1));
  580. }
  581. }
  582. if (context.shape.length == 0) {
  583. return results[0];
  584. }
  585. return results;
  586. }
  587. };
  588. cntk.TensorType = class {
  589. constructor(version, dataType, shape) {
  590. this._dataType = '?';
  591. switch (version) {
  592. case 1:
  593. switch (dataType) {
  594. case 'float': this._dataType = 'float32'; break;
  595. case 'double': this._dataType = 'float64'; break;
  596. case 'half': this._dataType = 'float16'; break;
  597. case '': this._dataType = 'float32'; break;
  598. }
  599. this._shape = new cntk.TensorShape(version, shape);
  600. break;
  601. case 2:
  602. dataType = dataType.toNumber();
  603. switch (dataType) {
  604. case 1: this._dataType = 'float32'; break;
  605. }
  606. this._shape = new cntk.TensorShape(version, shape);
  607. break;
  608. }
  609. }
  610. get dataType() {
  611. return this._dataType;
  612. }
  613. get shape() {
  614. return this._shape;
  615. }
  616. toString() {
  617. return this._dataType + this._shape.toString();
  618. }
  619. };
  620. cntk.TensorShape = class {
  621. constructor(version, shape) {
  622. switch (version) {
  623. case 1:
  624. this._dimensions = shape.dims;
  625. break;
  626. case 2:
  627. this._dimensions = shape.shape_dim.map((dimension) => dimension.toNumber());
  628. break;
  629. }
  630. }
  631. get dimensions() {
  632. return this._dimensions;
  633. }
  634. toString() {
  635. return (this._dimensions && this._dimensions.length) ? ('[' + this._dimensions.join(',') + ']') : '';
  636. }
  637. };
  638. cntk.Function = class {
  639. constructor(name, nodes, inputs, outputs) {
  640. this._name = name;
  641. this._inputs = inputs;
  642. this._outputs = outputs;
  643. this._nodes = nodes;
  644. }
  645. get type() {
  646. return 'function';
  647. }
  648. get name() {
  649. return this._name;
  650. }
  651. get category() {
  652. switch (this._name) {
  653. case 'PReLU':
  654. case 'Softmax':
  655. return 'Activation';
  656. case 'Dropout':
  657. return 'Dropout';
  658. case 'Convolution':
  659. case 'ConvolutionTranspose':
  660. case 'Dense':
  661. case 'linear':
  662. case 'LSTM':
  663. return 'Layer';
  664. case 'BatchNormalization':
  665. case 'lrn':
  666. return 'Normalization';
  667. case 'AveragePooling':
  668. case 'MaxPooling':
  669. return 'Pool';
  670. }
  671. return null;
  672. }
  673. get description() {
  674. return '';
  675. }
  676. get inputs() {
  677. return this._inputs;
  678. }
  679. get outputs() {
  680. return this._outputs;
  681. }
  682. get nodes() {
  683. return this._nodes;
  684. }
  685. };
  686. cntk.GraphMetadata = class {
  687. constructor(metadata) {
  688. this._metadata = metadata;
  689. this._functions = new Map();
  690. this._attributes = new Map();
  691. }
  692. add(name, func) {
  693. if (this._functions.has(name)) {
  694. throw new cntk.Error("Duplicate function identifier '" + func.name + "'.");
  695. }
  696. this._functions.set(name, func);
  697. }
  698. name(code) {
  699. // cntk/Source/CNTKv2LibraryDll/API/Internals/PrimitiveOpType.h
  700. return this._metadata.name(code);
  701. }
  702. type(name) {
  703. if (this._functions.has(name)) {
  704. return this._functions.get(name);
  705. }
  706. return this._metadata.type(name);
  707. }
  708. attribute(type, name) {
  709. const key = type + ':' + name;
  710. if (!this._attributes.has(key)) {
  711. const metadata = this.type(type);
  712. if (metadata && metadata.attributes && metadata.attributes.length > 0) {
  713. for (const attribute of metadata.attributes) {
  714. this._attributes.set(type + ':' + attribute.name, attribute);
  715. }
  716. }
  717. if (!this._attributes.has(key)) {
  718. this._attributes.set(key, null);
  719. }
  720. }
  721. return this._attributes.get(key);
  722. }
  723. };
  724. cntk.Metadata = class {
  725. static open(context) {
  726. if (cntk.Metadata._metadata) {
  727. return Promise.resolve(cntk.Metadata._metadata);
  728. }
  729. return context.request('cntk-metadata.json', 'utf-8', null).then((data) => {
  730. cntk.Metadata._metadata = new cntk.Metadata(data);
  731. return cntk.Metadata._metadata;
  732. }).catch(() => {
  733. cntk.Metadata._metadata = new cntk.Metadata(null);
  734. return cntk.Metadata._metadata;
  735. });
  736. }
  737. constructor(data) {
  738. this._map = new Map();
  739. this._typeMap = new Map();
  740. if (data) {
  741. const metadata = JSON.parse(data);
  742. this._types = new Map(metadata.map((item) => [ item.name, item ]));
  743. this._codes = new Map(metadata.map((item) => [ item.operator, item ]));
  744. }
  745. }
  746. name(code) {
  747. // cntk/Source/CNTKv2LibraryDll/API/Internals/PrimitiveOpType.h
  748. return this._codes.get(code);
  749. }
  750. type(name) {
  751. return this._types.get(name);
  752. }
  753. };
  754. cntk_v1.ComputationNetwork = class {
  755. constructor(buffer) {
  756. const reader = new cntk_v1.Reader(buffer);
  757. reader.assert('BCN');
  758. reader.assert('BVersion');
  759. this.version = reader.uint64();
  760. reader.assert('EVersion');
  761. const numNodes = reader.uint64();
  762. reader.assert('BNodeList');
  763. const op = {};
  764. op.Minus = function() {};
  765. op.Plus = function() {};
  766. op.GreaterEqual = function() {};
  767. op.Equal = function() {};
  768. op.NotEqual = function() {};
  769. op.GreaterEqual = function() {};
  770. op.Exp = function() {};
  771. op.Log = function() {};
  772. op.Reciprocal = function() {};
  773. op.ElementTimes = function() {};
  774. op.ClassificationError = function() {};
  775. op.RectifiedLinear = function() {};
  776. op.InputValue = function(reader, version) {
  777. this.rows = reader.uint64();
  778. this.cols = reader.uint64();
  779. this.sampleLayout = new cntk_v1.TensorShape(reader, true);
  780. this.dynamicAxisNodeName = '';
  781. if (version >= 8) {
  782. const nrAxes = reader.uint32();
  783. if (nrAxes == 1) {
  784. this.dynamicAxisNodeName = reader.string();
  785. }
  786. }
  787. this.learningRateMultiplier = 0;
  788. if (version >= 10) {
  789. this.learningRateMultiplier = reader.float32();
  790. }
  791. };
  792. op.LearnableParameter = function(reader, version) {
  793. if (version >= 3) {
  794. this.learningRateMultiplier = reader.float32();
  795. this.sampleLayout = new cntk_v1.TensorShape(reader);
  796. }
  797. else {
  798. throw new cntk.Error('LeanableParameter reader implemented.');
  799. }
  800. this.value = new cntk_v1.Matrix(reader);
  801. };
  802. op.CrossEntropyWithSoftmax = function(reader) {
  803. this.evalMode = reader.uint32();
  804. if (this.evalMode > 2) {
  805. this.evalMode = 0;
  806. reader.skip(-4);
  807. }
  808. };
  809. op.Times = function(reader, version) {
  810. this.outputRank = (version >= 3) ? reader.uint64() : 1;
  811. this.inferInputRankToMap = (version >= 12) ? reader.int32() : -1;
  812. };
  813. op.Dropout = function(reader, version) {
  814. if (version >= 16) {
  815. this.rngSeed = (version == 16) ? reader.uint32() : reader.uint64();
  816. this.rngOffset = reader.uint64();
  817. }
  818. };
  819. op.ConvolutionBase = function(reader, version) {
  820. if (version >= 5) {
  821. this.kernelShape = new cntk_v1.TensorShape(reader);
  822. this.mapCount = new cntk_v1.TensorShape(reader);
  823. this.strides = new cntk_v1.TensorShape(reader);
  824. this.sharing = reader.booleans(reader.uint64());
  825. this.autoPadding = reader.booleans(reader.uint64());
  826. this.lowerPad = new cntk_v1.TensorShape(reader);
  827. this.upperPad = new cntk_v1.TensorShape(reader);
  828. this.poolKind = reader.enum();
  829. this.imageLayoutKind = reader.enum();
  830. this.maxTempMemSizeInSamples = reader.uint64();
  831. }
  832. if (version >= 9) {
  833. this.transpose = reader.boolean();
  834. }
  835. if (version >= 20) {
  836. this.outputShape = new cntk_v1.TensorShape(reader);
  837. }
  838. if (version >= 21) {
  839. this.ceilOutDim = reader.boolean();
  840. }
  841. if (version >= 23) {
  842. this.includePad = reader.boolean();
  843. }
  844. };
  845. op.Convolution = function(reader, version) {
  846. op.ConvolutionBase.apply(this, [ reader, version ]);
  847. if (version < 5) {
  848. this.kernelShape = new cntk_v1.TensorShape([ reader.uint64(), reader.uint64(), 1 ]);
  849. this.strides = new cntk_v1.TensorShape([ reader.uint64(), reader.uint64(), 1 ]);
  850. this.mapCount = new cntk_v1.TensorShape([ reader.uint32() ]);
  851. this.imageLayoutKind = reader.enum();
  852. this.autoPadding = [ reader.boolean() ];
  853. this.maxTempMemSizeInSamples = reader.uint64();
  854. this.poolKind = 'None';
  855. this.convolution2D = true;
  856. this.sharing = [ true ];
  857. this.lowerPad = new cntk_v1.TensorShape([ 0 ]);
  858. this.upperPad = new cntk_v1.TensorShape([ 0 ]);
  859. }
  860. else {
  861. this.convolution2D = reader.boolean();
  862. if (version >= 18) {
  863. this.dilation = new cntk_v1.TensorShape(reader);
  864. }
  865. else {
  866. this.dilation = new cntk_v1.TensorShape([ 1 ]);
  867. }
  868. }
  869. };
  870. op.Pooling = function(reader, version) {
  871. op.ConvolutionBase.apply(this, [ reader, version ]);
  872. };
  873. op.PoolingBase = function(reader) {
  874. this.imageLayoutKind = reader.enum();
  875. this.windowWidth = reader.uint32();
  876. this.windowHeight = reader.uint64();
  877. this.horizontalSubsample = reader.uint64();
  878. this.verticalSubsample = reader.uint64();
  879. };
  880. op.MaxPooling = function(reader, version) {
  881. op.PoolingBase.apply(this, [ reader, version ]);
  882. };
  883. op.ROIPooling = function(reader, version) {
  884. this.roiOutputShape = new cntk_v1.TensorShape(reader);
  885. this.poolKind = (version < 26) ? 'Max' : reader.enum();
  886. this.spatialScale = (version < 26) ? 0.0625 : reader.float64();
  887. };
  888. op.Reshape = function(reader) {
  889. this.beginDimParameter = reader.uint32();
  890. this.endDimParameter = reader.uint32();
  891. this.replacementSampleLayout = new cntk_v1.TensorShape(reader);
  892. };
  893. op.ReduceElements = function(reader, version) {
  894. let num_axes = 1;
  895. if (version >= 27) {
  896. num_axes = reader.uint32();
  897. }
  898. this.axes = [];
  899. for (let i = 0; i < num_axes; i++) {
  900. this.axes.push(reader.uint32());
  901. }
  902. this.operation = reader.string();
  903. if (version >= 24) {
  904. this.keepDimensions = reader.boolean();
  905. }
  906. };
  907. op.BatchNormalization = function(reader, version) {
  908. let mbCount = 0;
  909. if (version >= 6) {
  910. this.spatial = reader.boolean();
  911. this.normalizationTimeConstant = reader.float64();
  912. this.blendTimeConstant = reader.float64();
  913. this.imageLayoutKind = reader.enum();
  914. if (version >= 13) {
  915. if (version != 19) {
  916. this.runCountUntied = reader.uint64();
  917. }
  918. else {
  919. this.runCountUntied = reader.boolean() ? 0 : 'SIZE_MAX'; // TODO
  920. }
  921. }
  922. else {
  923. mbCount = reader.uint64();
  924. }
  925. this.epsilon = reader.float64();
  926. this.useCntkEngine = reader.boolean();
  927. }
  928. else {
  929. const verWritten = reader.int32();
  930. const verReadable = reader.int32();
  931. if (verReadable > verWritten || verWritten < 0x00010001 || verReadable > 0x00010004) {
  932. throw new cntk.Error('BatchNormalization version not supported.');
  933. }
  934. this.eval = reader.boolean();
  935. this.spatial = reader.boolean();
  936. if (verWritten >= 0x00010004) {
  937. this.normalizationTimeConstant = reader.float64();
  938. }
  939. else {
  940. reader.float64(); // expAvgFactor
  941. }
  942. if (verWritten >= 0x00010002) {
  943. this.imageLayoutKind = reader.enum();
  944. mbCount = reader.uint64();
  945. }
  946. if (verWritten >= 0x00010003) {
  947. this.epsilon = reader.float64();
  948. this.useCntkEngine = reader.boolean();
  949. }
  950. }
  951. if (version < 13) {
  952. this.runCountUntied = 16 * mbCount;
  953. this.convertRunningVariancePending = true;
  954. }
  955. };
  956. op.Tanh = function() {};
  957. op.Sigmoid = function() {};
  958. op.Logistic = function() {};
  959. op.SquareError = function() {};
  960. op.ErrorPrediction = function() {};
  961. op.RowStack = function(reader, version) {
  962. this.spliceDim = (version >= 3) ? reader.int32() : 1;
  963. };
  964. op.Slice = function(reader, version) {
  965. let num = 1;
  966. if (version >= 22) {
  967. num = reader.int32();
  968. }
  969. this.index = [];
  970. this.axis = [];
  971. this.strideMultiplier = [];
  972. for (let i = 0; i < num; i++) {
  973. this.index.push([ [ reader.uint64(), reader.uint64() ] ]);
  974. if (version >= 3) {
  975. this.axis.push(reader.int32());
  976. }
  977. if (version >= 27) {
  978. this.strideMultiplier.push(reader.int32());
  979. }
  980. }
  981. };
  982. op.PastValue = function(reader, version) {
  983. this.timeStep = reader.int32();
  984. if (version > 3) {
  985. this.sampleLayout = new cntk_v1.TensorShape(reader, false);
  986. }
  987. else {
  988. const rows = reader.uint64();
  989. reader.uint64();
  990. this.sampleLayout = new cntk_v1.TensorShape([ rows ], true);
  991. }
  992. if (version >= 2) {
  993. this.initialStateValue = reader.int32();
  994. }
  995. };
  996. op.FutureValue = function(reader, version) {
  997. this.timeStep = reader.int32();
  998. if (version > 3) {
  999. this.sampleLayout = new cntk_v1.TensorShape(reader, false);
  1000. }
  1001. else {
  1002. const rows = reader.uint64();
  1003. reader.uint64();
  1004. this.sampleLayout = new cntk_v1.TensorShape([ rows ], true);
  1005. }
  1006. if (version >= 2) {
  1007. this.initialStateValue = reader.int32();
  1008. }
  1009. };
  1010. op.TransposeDimensions = function(reader, version) {
  1011. if (version >= 3) {
  1012. this.axis1 = reader.int32();
  1013. this.axis2 = reader.int32();
  1014. if (version >= 25 && this.axis1 == 0 && this.axis2 == 0) {
  1015. const size = reader.uint64();
  1016. this.perm = [];
  1017. for (let i = 0; i < size; i++) {
  1018. this.perm.push(reader.uint64());
  1019. }
  1020. }
  1021. }
  1022. else {
  1023. this.axis1 = 1;
  1024. this.axis2 = 2;
  1025. }
  1026. };
  1027. op.AveragePooling = function(reader, version) {
  1028. op.PoolingBase.apply(this, [ reader, version ]);
  1029. };
  1030. op.InvStdDev = function(reader) {
  1031. this.hasComputed = reader.boolean();
  1032. this.value = new cntk_v1.Matrix(reader);
  1033. };
  1034. op.Mean = function(reader) {
  1035. this.hasComputed = reader.boolean();
  1036. this.value = new cntk_v1.Matrix(reader);
  1037. };
  1038. op.PerDimMeanVarNormalization = function() {};
  1039. op.Softmax = function() {};
  1040. op.DynamicAxis = function() {};
  1041. const nodes = [];
  1042. this.nodes = {};
  1043. for (let i = 0; i < numNodes; i++) {
  1044. const precision = this.version >= 7 ? reader.string() : '';
  1045. if (precision != 'float' && precision != 'double' && precision != 'half' && precision != '') {
  1046. throw new cntk.Error("Invalid precision format '" + precision + "'.");
  1047. }
  1048. const obj = { __type__: reader.string() };
  1049. obj.name = reader.string();
  1050. obj.precision = precision;
  1051. const constructor = op[obj.__type__];
  1052. if (!constructor) {
  1053. throw new cntk.Error("Unknown node type '" + obj.__type__ + "'.");
  1054. }
  1055. constructor.apply(obj, [ reader, this.version ]);
  1056. nodes.push(obj);
  1057. this.nodes[obj.name] = obj;
  1058. }
  1059. reader.assert('ENodeList');
  1060. reader.assert('BRelation');
  1061. for (let j = 0; j < numNodes; j++) {
  1062. const nodeName = reader.string();
  1063. const node = this.nodes[nodeName];
  1064. const numChildren = reader.uint64();
  1065. const children = [];
  1066. for (let k = 0; k < numChildren; k++) {
  1067. children.push(reader.string());
  1068. }
  1069. if (this.version < 19 && node.__type__ == 'BatchNormalization') {
  1070. const runSampleCount = {
  1071. __type__: 'LearnableParameter',
  1072. name: nodeName + '.run_sample_count',
  1073. precision: node.precision,
  1074. sampleLayout: new cntk_v1.TensorShape([ 1 ]), // TODO set value = 0
  1075. learningRateMultiplier: 0
  1076. };
  1077. nodes.push(runSampleCount);
  1078. this.nodes[runSampleCount.name] = runSampleCount;
  1079. children.push(runSampleCount.name);
  1080. }
  1081. if (node.__type__ == 'Convolution' && children.length > 1) {
  1082. children.splice(0, 0, children.pop());
  1083. }
  1084. node.inputs = children;
  1085. }
  1086. reader.assert('ERelation');
  1087. reader.assert('BRootNodes');
  1088. if (reader.match('BFeatureNodes')) {
  1089. this.feature = reader.strings(reader.uint64());
  1090. reader.assert('EFeatureNodes');
  1091. }
  1092. if (reader.match('BLabelNodes')) {
  1093. this.label = reader.strings(reader.uint64());
  1094. reader.assert('ELabelNodes');
  1095. }
  1096. if (reader.match('BCriterionNodes')) {
  1097. this.criterion = reader.strings(reader.uint64());
  1098. reader.assert('ECriterionNodes');
  1099. }
  1100. if (this.criterion.length == 0) {
  1101. if (reader.match('BCriteriaNodes')) {
  1102. this.criterion = reader.strings(reader.uint64());
  1103. reader.assert('ECriteriaNodes');
  1104. }
  1105. }
  1106. if (reader.match('BNodesReqMultiSeqHandling')) {
  1107. reader.strings(reader.uint64());
  1108. reader.assert('ENodesReqMultiSeqHandling');
  1109. }
  1110. if (reader.match('BEvalNodes')) {
  1111. this.eval = reader.strings(reader.uint64());
  1112. reader.assert('EEvalNodes');
  1113. }
  1114. if (reader.match('BOutputNodes')) {
  1115. this.output = reader.strings(reader.uint64());
  1116. reader.assert('EOutputNodes');
  1117. }
  1118. if (reader.match('BPairNodes')) {
  1119. this.pair = reader.strings(reader.uint64());
  1120. reader.assert('EPairNodes');
  1121. }
  1122. reader.assert('ERootNodes');
  1123. reader.assert('ECN');
  1124. }
  1125. };
  1126. cntk_v1.Reader = class {
  1127. constructor(buffer) {
  1128. this._buffer = buffer;
  1129. this._dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
  1130. this._position = 0;
  1131. }
  1132. match(text) {
  1133. const position = this._position;
  1134. for (let i = 0; i < text.length; i++) {
  1135. if (this.uint16() != text.charCodeAt(i)) {
  1136. this._position = position;
  1137. return false;
  1138. }
  1139. }
  1140. if (this.uint16() != 0) {
  1141. this._position = position;
  1142. return false;
  1143. }
  1144. return true;
  1145. }
  1146. assert(text) {
  1147. if (!this.match(text)) {
  1148. throw new cntk_v1.Error("Invalid '" + text + "' signature.");
  1149. }
  1150. }
  1151. skip(offset) {
  1152. this._position += offset;
  1153. if (this._position > this._buffer.length) {
  1154. throw new cntk.Error('Expected ' + (this._position - this._buffer.length) + ' more bytes. The file might be corrupted. Unexpected end of file.');
  1155. }
  1156. }
  1157. boolean() {
  1158. return this.byte() != 0 ? true : false;
  1159. }
  1160. booleans(count) {
  1161. const array = [];
  1162. for (let i = 0; i < count; i++) {
  1163. array.push(this.boolean());
  1164. }
  1165. return array;
  1166. }
  1167. byte() {
  1168. const position = this._position;
  1169. this.skip(1);
  1170. return this._dataView.getUint8(position);
  1171. }
  1172. bytes(length) {
  1173. const position = this._position;
  1174. this.skip(length);
  1175. return this._buffer.subarray(position, this._position);
  1176. }
  1177. uint16() {
  1178. const position = this._position;
  1179. this.skip(2);
  1180. return this._dataView.getUint16(position, true);
  1181. }
  1182. int32() {
  1183. const position = this._position;
  1184. this.skip(4);
  1185. return this._dataView.getInt32(position, true);
  1186. }
  1187. uint32() {
  1188. const position = this._position;
  1189. this.skip(4);
  1190. return this._dataView.getUint32(position, true);
  1191. }
  1192. uint64() {
  1193. const low = this.uint32();
  1194. const hi = this.uint32();
  1195. if (hi > 65536) {
  1196. throw new cntk_v1.Error('Value not in 48-bit range.');
  1197. }
  1198. return (hi << 32) | low;
  1199. }
  1200. float32() {
  1201. const position = this._position;
  1202. this.skip(4);
  1203. return this._dataView.getFloat32(position, true);
  1204. }
  1205. float64() {
  1206. const position = this._position;
  1207. this.skip(8);
  1208. return this._dataView.getFloat64(position, true);
  1209. }
  1210. string() {
  1211. let content = '';
  1212. let c = this.uint16();
  1213. while (c != 0) {
  1214. content += String.fromCharCode(c);
  1215. c = this.uint16();
  1216. }
  1217. return content;
  1218. }
  1219. strings(count) {
  1220. const array = [];
  1221. for (let i = 0; i < count; i++) {
  1222. array.push(this.string());
  1223. }
  1224. return array;
  1225. }
  1226. enum() {
  1227. return this.int32();
  1228. }
  1229. };
  1230. cntk_v1.TensorShape = class {
  1231. constructor(reader, acceptLegacyFormat = false) {
  1232. if (reader && Array.isArray(reader)) {
  1233. this.dims = reader;
  1234. return;
  1235. }
  1236. this.dims = [];
  1237. const rank = reader.uint32();
  1238. let dim0 = 0;
  1239. if (rank > 0) {
  1240. dim0 = reader.uint32();
  1241. }
  1242. if (!acceptLegacyFormat || dim0 != 0) {
  1243. if (rank > 0) {
  1244. this.dims.push(dim0);
  1245. }
  1246. for (let i = 1; i < rank; i++) {
  1247. this.dims.push(reader.uint32());
  1248. }
  1249. }
  1250. else {
  1251. const dim = reader.uint32();
  1252. this.dims.push(reader.uint32());
  1253. this.dims.push(rank);
  1254. this.dims.push(dim);
  1255. }
  1256. }
  1257. };
  1258. cntk_v1.Matrix = class {
  1259. constructor(reader) {
  1260. const type = reader.byte();
  1261. switch (type) {
  1262. case 100: {
  1263. // dense
  1264. reader.assert('BMAT');
  1265. const elsize = reader.uint64();
  1266. this.name = reader.string();
  1267. this.format = reader.uint32();
  1268. this.rows = reader.uint64();
  1269. this.columns = reader.uint64();
  1270. reader.bytes(elsize * this.rows * this.columns);
  1271. reader.assert('EMAT');
  1272. break;
  1273. }
  1274. case 115: // sparse
  1275. throw new cntk_v1.Error('Matrix sparse type not implemented.');
  1276. default:
  1277. throw new cntk_v1.Error("Matrix type '" + type.toString() + "' not implemented.");
  1278. }
  1279. }
  1280. };
  1281. cntk_v1.ImageLayoutKind = {
  1282. 0: 'CHW',
  1283. 1: 'HWC'
  1284. };
  1285. cntk_v1.PoolKind = {
  1286. 0: 'None',
  1287. 1: 'Max',
  1288. 2: 'Average'
  1289. };
  1290. cntk_v1.Error = class extends Error {
  1291. constructor(message) {
  1292. super(message);
  1293. this.name = 'Error loading CNTK v1 model.';
  1294. }
  1295. };
  1296. cntk.Error = class extends Error {
  1297. constructor(message) {
  1298. super(message);
  1299. this.name = 'Error loading CNTK model.';
  1300. }
  1301. };
  1302. if (typeof module !== 'undefined' && typeof module.exports === 'object') {
  1303. module.exports.ModelFactory = cntk.ModelFactory;
  1304. }