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