[ML] TODO resolving/removal
[platform/core/api/webapi-plugins.git] / src / ml / js / ml_trainer.js
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 var MachineLearningTrainer = function () { };
18
19 var OptimizerType = {
20     OPTIMIZER_ADAM: 'OPTIMIZER_ADAM',
21     OPTIMIZER_SGD: 'OPTIMIZER_SGD',
22     OPTIMIZER_UNKNOWN: 'OPTIMIZER_UNKNOWN'
23 };
24
25 var DatasetType = {
26     DATASET_GENERATOR: 'DATASET_GENERATOR',
27     DATASET_FILE: 'DATASET_FILE',
28     DATASET_UNKNOWN: 'DATASET_UNKNOWN'
29 };
30
31 var LayerType = {
32     LAYER_IN: 'LAYER_IN',
33     LAYER_FC: 'LAYER_FC',
34     LAYER_BN: 'LAYER_BN',
35     LAYER_CONV2D: 'LAYER_CONV2D',
36     LAYER_POOLING2D: 'LAYER_POOLING2D',
37     LAYER_FLATTEN: 'LAYER_FLATTEN',
38     LAYER_ACTIVATION: 'LAYER_ACTIVATION',
39     LAYER_ADDITION: 'LAYER_ADDITION',
40     LAYER_CONCAT: 'LAYER_CONCAT',
41     LAYER_MULTIOUT: 'LAYER_MULTIOUT',
42     LAYER_LOSS_MSE: 'LAYER_LOSS_MSE',
43     LAYER_LOSS_CROSS_ENTROPY_SIGMOID: 'LAYER_LOSS_CROSS_ENTROPY_SIGMOID',
44     LAYER_LOSS_CROSS_ENTROPY_SOFTMAX: 'LAYER_LOSS_CROSS_ENTROPY_SOFTMAX',
45     LAYER_BACKBONE_NNSTREAMER: 'LAYER_BACKBONE_NNSTREAMER',
46     LAYER_EMBEDDING: 'LAYER_EMBEDDING',
47     LAYER_RNN: 'LAYER_RNN',
48     LAYER_UNKNOWN: 'LAYER_UNKNOWN'
49 };
50
51 var VerbosityLevel = {
52     SUMMARY_MODEL: 'SUMMARY_MODEL',
53     SUMMARY_LAYER: 'SUMMARY_LAYER',
54     SUMMARY_TENSOR: 'SUMMARY_TENSOR'
55 };
56
57 var SaveFormat = {
58     FORMAT_BIN: 'FORMAT_BIN',
59     FORMAT_INI: 'FORMAT_INI',
60     FORMAT_INI_WITH_BIN: 'FORMAT_INI_WITH_BIN'
61 };
62
63 var DatasetMode = {
64     MODE_TRAIN: 'MODE_TRAIN',
65     MODE_VALID: 'MODE_VALID',
66     MODE_TEST: 'MODE_TEST'
67 };
68
69 var ValidGetNameExceptions = [
70     'InvalidValuesError',
71     'AbortError'
72 ];
73
74 var Layer = function(id, type) {
75     Object.defineProperties(this, {
76         name: {
77             enumerable: true,
78             get: function() {
79                 var result = native_.callSync('MLTrainerLayerGetName', { id: this._id });
80
81                 if (native_.isFailure(result)) {
82                     throw native_.getErrorObjectAndValidate(
83                         result,
84                         ValidGetNameExceptions,
85                         AbortError
86                     );
87                 }
88
89                 return result.name
90             }
91         },
92         type: {
93             enumerable: true,
94             writable: false,
95             value: type
96         },
97         _id: { value: id, writable: false, enumerable: false }
98     });
99 };
100
101 var ValidSetPropertyExceptions = [
102     'InvalidValuesError',
103     'NotSupportedError',
104     'TypeMismatchError',
105     'AbortError'
106 ];
107
108 Layer.prototype.setProperty = function() {
109     var args = validator_.validateArgs(arguments, [
110         {
111             name: 'name',
112             type: types_.STRING
113         },
114         {
115             name: 'value',
116             type: types_.STRING
117         }
118     ]);
119
120     if (!args.has.name || !args.has.value) {
121         throw new WebAPIException(
122             WebAPIException.TYPE_MISMATCH_ERR,
123             'Invalid parameter: ' + (args.has.name ? 'value' : 'name') + ' is undefined'
124         );
125     }
126
127     var callArgs = {
128         id: this._id,
129         name: args.name,
130         value: args.value
131     };
132
133     var result = native_.callSync('MLTrainerLayerSetProperty', callArgs);
134
135     if (native_.isFailure(result)) {
136         throw native_.getErrorObjectAndValidate(
137             result,
138             ValidSetPropertyExceptions,
139             AbortError
140         );
141     }
142 };
143
144 var ValidDisposeExceptions = [
145     'NotFoundError',
146     'NoModificationAllowedError',
147     'AbortError'
148 ];
149
150 Layer.prototype.dispose = function () {
151     var result = native_.callSync('MLTrainerLayerDispose', { id: this._id });
152
153     if (native_.isFailure(result)) {
154         throw native_.getErrorObjectAndValidate(
155             result,
156             ValidDisposeExceptions,
157             AbortError
158         );
159     }
160 };
161
162 var Optimizer = function(id, type) {
163     Object.defineProperties(this, {
164         type: {
165             enumerable: true,
166             writable: false,
167             value: type
168         },
169         _id: { value: id, writable: false, enumerable: false }
170     });
171 };
172
173 Optimizer.prototype.setProperty = function() {
174     var args = validator_.validateArgs(arguments, [
175         {
176             name: 'name',
177             type: types_.STRING
178         },
179         {
180             name: 'value',
181             type: types_.STRING
182         }
183     ]);
184
185     if (!args.has.name || !args.has.value) {
186         throw new WebAPIException(
187             WebAPIException.TYPE_MISMATCH_ERR,
188             'Invalid parameter: ' + (args.has.name ? 'value' : 'name') + ' is undefined'
189         );
190     }
191
192     var callArgs = {
193         id: this._id,
194         name: args.name,
195         value: args.value
196     };
197
198     var result = native_.callSync('MLTrainerOptimizerSetProperty', callArgs);
199
200     if (native_.isFailure(result)) {
201         throw native_.getErrorObjectAndValidate(
202             result,
203             ValidSetPropertyExceptions,
204             AbortError
205         );
206     }
207 };
208
209 Optimizer.prototype.dispose = function () {
210     var result = native_.callSync('MLTrainerOptimizerDispose', { id: this._id });
211
212     if (native_.isFailure(result)) {
213         throw native_.getErrorObjectAndValidate(
214             result,
215             ValidDisposeExceptions,
216             AbortError
217         );
218     }
219 };
220
221 var Dataset = function(id, type) {
222     Object.defineProperties(this, {
223         type: {
224             enumerable: true,
225             writable: false,
226             value: type
227         },
228         _id: { value: id, writable: false, enumerable: false }
229     });
230 };
231
232 Dataset.prototype.setProperty = function() {
233     var args = validator_.validateArgs(arguments, [
234         {
235             name: 'name',
236             type: types_.STRING
237         },
238         {
239             name: 'value',
240             type: types_.STRING
241         },
242         {
243             name: 'mode',
244             type: types_.ENUM,
245             values: Object.values(DatasetMode)
246         }
247     ]);
248
249     if (!args.has.name || !args.has.value || !args.has.mode) {
250         throw new WebAPIException(
251             WebAPIException.TYPE_MISMATCH_ERR,
252             'Invalid parameter: name, value and mode have to be defined'
253         );
254     }
255
256     var callArgs = {
257         id: this._id,
258         name: args.name,
259         value: args.value,
260         mode: args.mode
261     };
262
263     var result = native_.callSync('MLTrainerDatasetSetProperty', callArgs);
264
265     if (native_.isFailure(result)) {
266         throw native_.getErrorObjectAndValidate(
267             result,
268             ValidSetPropertyExceptions,
269             AbortError
270         );
271     }
272 };
273
274
275 Dataset.prototype.dispose = function () {
276     var result = native_.callSync('MLTrainerDatasetDispose', { id: this._id });
277
278     if (native_.isFailure(result)) {
279         throw native_.getErrorObjectAndValidate(
280             result,
281             ValidDisposeExceptions,
282             AbortError
283         );
284     }
285 };
286
287 var Model = function(id) {
288     Object.defineProperties(this, {
289         _id: { value: id, writable: false, enumerable: false }
290     });
291 };
292
293 function ValidateCompileOptions(options) {
294     var args = {};
295     if (options.hasOwnProperty('loss_val')) {
296         args.loss = options.loss_val;
297     }
298     if (options.hasOwnProperty('loss')) {
299         args.loss = options.loss;
300     }
301     if (options.hasOwnProperty('batch_size')) {
302         args.batch_size = options.batch_size;
303     }
304     return args;
305 }
306
307 var ValidModelCompileExceptions = [
308     'InvalidValuesError',
309     'TypeMismatchError',
310     'NoModificationAllowedError',
311     'AbortError'
312 ];
313
314 Model.prototype.compile = function() {
315     var args = validator_.validateArgs(arguments, [
316         {
317             name: 'options',
318             type: validator_.Types.DICTIONARY,
319             optional: true,
320             nullable: true
321         }
322     ]);
323     var options = {};
324     if (args.has.options) {
325         options = ValidateCompileOptions(args.options);
326     }
327
328     var callArgs = {
329         id: this._id,
330         options: options
331     };
332
333     var result = native_.callSync('MLTrainerModelCompile', callArgs);
334
335     if (native_.isFailure(result)) {
336         throw native_.getErrorObjectAndValidate(
337             result,
338             ValidModelCompileExceptions,
339             AbortError
340         );
341     }
342 };
343
344 function ValidateRunOptions(options) {
345     var args = {};
346     if (options.hasOwnProperty('batch_size')) {
347         args.batch_size = options.batch_size;
348     }
349     if (options.hasOwnProperty('epochs')) {
350         args.epochs = options.epochs;
351     }
352     if (options.hasOwnProperty('save_path')) {
353         // produce a global path without "file://" prefix
354         args.save_path = tizen.filesystem.toURI(options.save_path).substring("file://".length);
355     }
356     return args;
357 }
358
359 var ValidModelRunExceptions = [
360     'InvalidValuesError',
361     'InvalidStateError',
362     'TypeMismatchError',
363     'AbortError'
364 ];
365
366 Model.prototype.run = function() {
367     var args = validator_.validateArgs(arguments, [
368         {
369             name: 'options',
370             type: validator_.Types.DICTIONARY,
371             optional: true,
372             nullable: true
373         },
374         {
375             name: 'successCallback',
376             type: types_.FUNCTION
377         },
378         {
379             name: 'errorCallback',
380             type: types_.FUNCTION,
381             optional: true,
382             nullable: true
383         }
384     ]);
385     var runOptions = {};
386     if (args.has.options) {
387         runOptions = ValidateRunOptions(args.options);
388     }
389
390     var callArgs = {
391         id: this._id,
392         options: runOptions
393     };
394
395     var callback = function (result) {
396         if (native_.isFailure(result)) {
397             native_.callIfPossible(
398                 args.errorCallback,
399                 native_.getErrorObjectAndValidate(
400                     result,
401                     ValidModelRunExceptions,
402                     AbortError
403                 )
404             );
405         } else {
406             args.successCallback();
407         }
408     };
409
410     var result = native_.call('MLTrainerModelRun', callArgs, callback);
411     if (native_.isFailure(result)) {
412         throw native_.getErrorObjectAndValidate(
413             result,
414             ValidModelRunExceptions,
415             AbortError
416         );
417     }
418 };
419
420 var ValidBasicExceptions = [
421     'TypeMismatchError',
422     'AbortError'
423 ];
424
425 Model.prototype.summarize = function() {
426     var args = validator_.validateArgs(arguments, [
427         {
428             name: 'level',
429             type: types_.ENUM,
430             values: Object.values(VerbosityLevel),
431             optional: true,
432             nullable: true
433         }
434     ]);
435
436     var callArgs = {
437         id: this._id,
438         level: args.level ? args.level : "SUMMARY_MODEL"
439     }
440
441     var result = native_.callSync('MLTrainerModelSummarize', callArgs);
442
443     if (native_.isFailure(result)) {
444         throw native_.getErrorObjectAndValidate(
445             result,
446             ValidBasicExceptions,
447             AbortError
448         );
449     }
450
451     return result.summary
452 };
453
454 /*
455 Private method used for verification of training results.
456 It returns true if the results match given values (with tolerance 1.0e-5), false otherwise.
457 */
458 Model.prototype._checkMetrics = function (trainLoss, validLoss, validAccuracy) {
459     var callArgs = {
460         trainLoss: trainLoss, validLoss: validLoss, validAccuracy: validAccuracy,
461         id: this._id
462     }
463
464     var result = native_.callSync('MLTrainerModelCheckMetrics', callArgs);
465
466     if (native_.isFailure(result)) {
467         throw native_.getErrorObjectAndValidate(
468             result,
469             ValidBasicExceptions,
470             AbortError
471         );
472     }
473
474     return result.result
475 };
476
477
478 var ValidModelSaveExceptions = [
479     'InvalidValuesError',
480     'TypeMismatchError',
481     "NoModificationAllowedError",
482     'AbortError'
483 ];
484
485 Model.prototype.saveToFile = function () {
486     var args = validator_.validateArgs(arguments, [
487         {
488             name: 'path',
489             type: types_.STRING
490         },
491         {
492             name: 'format',
493             type: types_.ENUM,
494             values: Object.values(SaveFormat)
495         }
496     ]);
497
498     var callArgs = {
499         id: this._id,
500         savePath: args.path,
501         saveFormat: args.format
502     }
503
504     try {
505         callArgs.savePath = tizen.filesystem.toURI(args.path);
506     } catch (e) {
507         throw new WebAPIException(WebAPIException.INVALID_VALUES_ERR, 'Invalid file path given');
508     }
509
510     if (tizen.filesystem.pathExists(callArgs.savePath)) {
511         throw new WebAPIException(WebAPIException.NO_MODIFICATION_ALLOWED_ERR, 'Path already exists - overwriting is not allowed');
512     }
513
514     var result = native_.callSync('MLTrainerModelSave', callArgs);
515
516     if (native_.isFailure(result)) {
517         throw native_.getErrorObjectAndValidate(
518             result,
519             ValidModelSaveExceptions,
520             AbortError
521         );
522     }
523 };
524
525 Model.prototype.load = function () {
526     var args = validator_.validateArgs(arguments, [
527         {
528             name: 'path',
529             type: types_.STRING
530         },
531         {
532             name: 'format',
533             type: types_.ENUM,
534             values: Object.values(SaveFormat)
535         }
536     ]);
537
538     var callArgs = {
539         id: this._id,
540         savePath: args.path,
541         saveFormat: args.format
542     }
543
544     try {
545         callArgs.savePath = tizen.filesystem.toURI(args.path);
546     } catch (e) {
547         throw new WebAPIException(WebAPIException.InvalidValuesError, 'Invalid file path given');
548     }
549
550     if (!tizen.filesystem.pathExists(callArgs.savePath)) {
551         throw new WebAPIException(WebAPIException.NotFoundError, 'Path not found');
552     }
553
554     var result = native_.callSync('MLTrainerModelLoad', callArgs);
555
556     if (native_.isFailure(result)) {
557         throw native_.getErrorObjectAndValidate(
558             result,
559             ValidModelSaveExceptions,
560             AbortError
561         );
562     }
563 };
564
565 Model.prototype.addLayer = function() {
566     var args = validator_.validateArgs(arguments, [
567         {
568             name: 'layer',
569             type: types_.PLATFORM_OBJECT,
570             values: Layer
571         }
572     ]);
573
574     if (!args.has.layer) {
575         throw new WebAPIException(
576             WebAPIException.TYPE_MISMATCH_ERR, 'Invalid parameter: layer is undefined'
577         );
578     }
579
580     var callArgs = {
581         id: this._id,
582         layerId: args.layer._id
583     };
584
585     var result = native_.callSync('MLTrainerModelAddLayer', callArgs);
586
587     if (native_.isFailure(result)) {
588         throw native_.getErrorObjectAndValidate(
589             result,
590             ValidSetObjectExceptions,
591             AbortError
592         );
593     }
594 };
595
596 var ValidSetObjectExceptions = [
597     'InvalidValuesError',
598     'TypeMismatchError',
599     'NoModificationAllowedError',
600     'NotSupportedError',
601     'NotFound',
602     'AbortError'
603 ];
604
605 Model.prototype.setDataset = function() {
606     var args = validator_.validateArgs(arguments, [
607         {
608             name: 'dataset',
609             type: types_.PLATFORM_OBJECT,
610             values: Dataset
611         }
612     ]);
613
614     if (!args.has.dataset) {
615         throw new WebAPIException(
616             WebAPIException.TYPE_MISMATCH_ERR, 'Invalid parameter: dataset is undefined'
617         );
618     }
619
620     var callArgs = {
621         id: this._id,
622         datasetId: args.dataset._id
623     };
624
625     var result = native_.callSync('MLTrainerModelSetDataset', callArgs);
626
627     if (native_.isFailure(result)) {
628         throw native_.getErrorObjectAndValidate(
629             result,
630             ValidSetObjectExceptions,
631             AbortError
632         );
633     }
634 };
635
636 Model.prototype.setOptimizer = function() {
637     var args = validator_.validateArgs(arguments, [
638         {
639             name: 'optimizer',
640             type: types_.PLATFORM_OBJECT,
641             values: Optimizer
642         }
643     ]);
644
645     if (!args.has.optimizer) {
646         throw new WebAPIException(
647             WebAPIException.TYPE_MISMATCH_ERR, 'Invalid parameter: optimizer is undefined'
648         );
649     }
650
651     var callArgs = {
652         id: this._id,
653         optimizerId: args.optimizer._id
654     };
655
656     var result = native_.callSync('MLTrainerModelSetOptimizer', callArgs);
657
658     if (native_.isFailure(result)) {
659         throw native_.getErrorObjectAndValidate(
660             result,
661             ValidSetObjectExceptions,
662             AbortError
663         );
664     }
665 };
666
667 Model.prototype.dispose = function () {
668     var result = native_.callSync('MLTrainerModelDispose', { id: this._id });
669
670     if (native_.isFailure(result)) {
671         throw native_.getErrorObjectAndValidate(
672             result,
673             ValidDisposeExceptions,
674             AbortError
675         );
676     }
677 };
678
679 var ValidCreateLayerExceptions = ['NotSupportedError', 'TypeMismatchError', 'AbortError'];
680
681 var NO_ID = -1;
682 MachineLearningTrainer.prototype.createLayer = function() {
683     var args = validator_.validateArgs(arguments, [
684         {
685             name: 'type',
686             type: types_.ENUM,
687             values: Object.values(LayerType),
688             optional: false
689         }
690     ]);
691
692     var nativeArgs = {
693         type: args.type
694     };
695
696     var result = native_.callSync('MLTrainerLayerCreate', nativeArgs);
697     if (native_.isFailure(result)) {
698         throw native_.getErrorObjectAndValidate(
699             result,
700             ValidCreateLayerExceptions,
701             AbortError
702         );
703     }
704
705     var nLay = new Layer(result.id, args.type);
706
707     nLay.setProperty("name", args.type.toString() + result.id.toString());
708
709     return nLay;
710 };
711
712 function ValidateAndReturnDatasetPaths(train, valid, test) {
713     try {
714         var args = {
715             train: tizen.filesystem.toURI(train),
716             valid: valid ? tizen.filesystem.toURI(valid) : '',
717             test: test ? tizen.filesystem.toURI(test) : ''
718         };
719         return args;
720     } catch (e) {
721         throw new WebAPIException(WebAPIException.NOT_FOUND_ERR, 'Path is invalid');
722     }
723 }
724
725 MachineLearningTrainer.prototype.createFileDataset = function() {
726     var args = validator_.validateArgs(arguments, [
727         {
728             name: 'train',
729             type: types_.STRING
730         },
731         {
732             name: 'valid',
733             type: types_.STRING,
734             optional: true,
735             nullable: true
736         },
737         {
738             name: 'test',
739             type: types_.STRING,
740             optional: false,
741             nullable: true
742         }
743     ]);
744     if (!args.has.train) {
745         throw new WebAPIException(
746             WebAPIException.TYPE_MISMATCH_ERR,
747             'Invalid parameter: training set path is undefined'
748         );
749     }
750
751     var nativeArgs = ValidateAndReturnDatasetPaths(
752         args.train,
753         args.has.valid ? args.valid : undefined,
754         args.has.test ? args.test : undefined
755     );
756
757     var result = native_.callSync('MLTrainerDatasetCreateFromFile', nativeArgs);
758     if (native_.isFailure(result)) {
759         throw native_.getErrorObjectAndValidate(
760             result,
761             ValidCreateLayerExceptions,
762             AbortError
763         );
764     }
765
766     return new Dataset(result.id, 'DATASET_FILE');
767 };
768
769 var ValidCreateOptimizerExceptions = [
770     'NotSupportedError',
771     'TypeMismatchError',
772     'AbortError'
773 ];
774
775 MachineLearningTrainer.prototype.createOptimizer = function() {
776     var args = validator_.validateArgs(arguments, [
777         {
778             name: 'type',
779             type: types_.ENUM,
780             values: Object.values(OptimizerType),
781             optional: false
782         }
783     ]);
784
785     var nativeArgs = {
786         type: args.type
787     };
788
789     var result = native_.callSync('MLTrainerOptimizerCreate', nativeArgs);
790     if (native_.isFailure(result)) {
791         throw native_.getErrorObjectAndValidate(
792             result,
793             ValidCreateOptimizerExceptions,
794             AbortError
795         );
796     }
797
798     return new Optimizer(result.id, args.type);
799 };
800
801 var ValidCreateModelWithConfigurationExceptions = [
802     'InvalidValuesError',
803     'NotFoundError',
804     'SecurityError',
805     'AbortError'
806 ];
807
808 MachineLearningTrainer.prototype.createModelWithConfiguration = function() {
809     var args = validator_.validateArgs(arguments, [
810         {
811             name: 'configPath',
812             type: types_.STRING,
813             optional: true
814         }
815     ]);
816     if (args.has.configPath) {
817         try {
818             args.configPath = tizen.filesystem.toURI(args.configPath);
819         } catch (e) {
820             throw new WebAPIException(WebAPIException.NOT_FOUND_ERR, 'Path is invalid');
821         }
822     }
823     var nativeArgs = {
824         configPath: args.configPath
825     };
826
827     var result = native_.callSync('MLTrainerModelCreate', nativeArgs);
828     if (native_.isFailure(result)) {
829         throw native_.getErrorObjectAndValidate(
830             result,
831             ValidCreateModelWithConfigurationExceptions,
832             AbortError
833         );
834     }
835
836     return new Model(result.id);
837 };
838
839 MachineLearningTrainer.prototype.createModel = function() {
840     var result = native_.callSync('MLTrainerModelCreate', {});
841     if (native_.isFailure(result)) {
842         throw new WebAPIException(WebAPIException.AbortError, 'Could not create model');
843     }
844
845     return new Model(result.id);
846 };