learning_model 1.0.49 → 1.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -105,6 +105,23 @@
105
105
  * =============================================================================
106
106
  */
107
107
 
108
+ /**
109
+ * @license
110
+ * Copyright 2019 Google LLC. All Rights Reserved.
111
+ * Licensed under the Apache License, Version 2.0 (the 'License');
112
+ * you may not use this file except in compliance with the License.
113
+ * You may obtain a copy of the License at
114
+ *
115
+ * http://www.apache.org/licenses/LICENSE-2.0
116
+ *
117
+ * Unless required by applicable law or agreed to in writing, software
118
+ * distributed under the License is distributed on an 'AS IS' BASIS,
119
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
120
+ * See the License for the specific language governing permissions and
121
+ * limitations under the License.
122
+ * =============================================================================
123
+ */
124
+
108
125
  /**
109
126
  * @license
110
127
  * Copyright 2020 Google Inc. All Rights Reserved.
@@ -1,4 +1,5 @@
1
1
  import * as tf from '@tensorflow/tfjs';
2
+ import '@tensorflow/tfjs-backend-wasm';
2
3
  import { io } from '@tensorflow/tfjs-core';
3
4
  import LearningInterface from './base';
4
5
  declare class LearningMobilenet implements LearningInterface {
@@ -39,6 +40,8 @@ declare class LearningMobilenet implements LearningInterface {
39
40
  private _convertToTfDataset;
40
41
  addData(label: string, data: any): Promise<void>;
41
42
  init(): Promise<void>;
43
+ private setupBackend;
44
+ private checkWasmSupport;
42
45
  train(): Promise<tf.History>;
43
46
  infer(data: any): Promise<Map<string, number>>;
44
47
  saveModel(handlerOrURL: io.IOHandler | string, config?: io.SaveConfig): Promise<void>;
@@ -38,6 +38,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  const tf = __importStar(require("@tensorflow/tfjs"));
41
+ require("@tensorflow/tfjs-backend-wasm"); // WebAssembly 백엔드 추가
41
42
  const tfjs_1 = require("@tensorflow/tfjs");
42
43
  const tf_1 = require("../utils/tf");
43
44
  const canvas_1 = require("../utils/canvas");
@@ -138,8 +139,10 @@ class LearningMobilenet {
138
139
  return __awaiter(this, void 0, void 0, function* () {
139
140
  try {
140
141
  if (this.mobilenetModule !== null) {
141
- const cap = (0, tf_1.isTensor)(data) ? data : (0, tf_1.capture)(data, false);
142
- const predict = this.mobilenetModule.predict(cap);
142
+ const cap = (0, tf_1.isTensor)(data) ? data : yield (0, tf_1.capture)(data, false);
143
+ const predict = tf.tidy(() => {
144
+ return this.mobilenetModule.predict(cap);
145
+ });
143
146
  const activation = yield predict.data();
144
147
  const classIndex = this.registerClassNumber(label);
145
148
  if (!this.imageExamples[classIndex]) {
@@ -149,6 +152,12 @@ class LearningMobilenet {
149
152
  if (this.classNumber.length >= this.limitSize) {
150
153
  this.isReady = true;
151
154
  }
155
+ // Dispose of the prediction tensor to free memory
156
+ predict.dispose();
157
+ // If cap is not a tensor, we don't need to dispose it. Otherwise, we should.
158
+ if (!(0, tf_1.isTensor)(data)) {
159
+ cap.dispose();
160
+ }
152
161
  }
153
162
  else {
154
163
  throw new Error('mobilenetModule is null');
@@ -164,6 +173,8 @@ class LearningMobilenet {
164
173
  init() {
165
174
  return __awaiter(this, void 0, void 0, function* () {
166
175
  try {
176
+ console.log('init call');
177
+ yield this.setupBackend();
167
178
  this.mobilenetModule = yield (0, tf_1.loadModel)();
168
179
  }
169
180
  catch (error) {
@@ -172,6 +183,34 @@ class LearningMobilenet {
172
183
  }
173
184
  });
174
185
  }
186
+ setupBackend() {
187
+ return __awaiter(this, void 0, void 0, function* () {
188
+ const isWasmSupported = yield this.checkWasmSupport();
189
+ if (isWasmSupported) {
190
+ yield tf.setBackend('wasm');
191
+ yield tf.ready();
192
+ console.log('Backend is set to WebAssembly');
193
+ }
194
+ else {
195
+ yield tf.setBackend('cpu');
196
+ yield tf.ready();
197
+ console.log('Backend is set to CPU');
198
+ }
199
+ });
200
+ }
201
+ checkWasmSupport() {
202
+ return __awaiter(this, void 0, void 0, function* () {
203
+ try {
204
+ yield tf.setBackend('wasm');
205
+ yield tf.ready();
206
+ return true;
207
+ }
208
+ catch (error) {
209
+ console.warn('WASM backend is not supported in this environment.', error);
210
+ return false;
211
+ }
212
+ });
213
+ }
175
214
  // 모델 학습 처리
176
215
  train() {
177
216
  return __awaiter(this, void 0, void 0, function* () {
@@ -246,8 +285,8 @@ class LearningMobilenet {
246
285
  try {
247
286
  const classProbabilities = new Map();
248
287
  const croppedImage = (0, canvas_1.cropTo)(data, 224, false);
288
+ const captured = yield (0, tf_1.capture)(croppedImage, false);
249
289
  const logits = tf.tidy(() => {
250
- const captured = (0, tf_1.capture)(croppedImage, false);
251
290
  return this.model.predict(captured);
252
291
  });
253
292
  const values = yield logits.data();
@@ -56,22 +56,24 @@ function ImagePathToTensor(imagePath) {
56
56
  }
57
57
  describe('LearningMobilenetImage', () => {
58
58
  const learning = new mobilenet_1.default({});
59
+ const image1Path = path.join(__dirname, '../../public/images/image1.jpeg');
60
+ const image2Path = path.join(__dirname, '../../public/images/image2.jpeg');
61
+ console.log('Resolved path for image1:', image1Path);
59
62
  beforeAll(() => __awaiter(void 0, void 0, void 0, function* () {
60
- const image1Path = path.join(__dirname, '../../public/images/image1.jpeg');
61
- const image2Path = path.join(__dirname, '../../public/images/image2.jpeg');
63
+ learning.init();
62
64
  imageTensor1 = yield ImagePathToTensor(image1Path);
63
65
  imageTensor2 = yield ImagePathToTensor(image2Path);
64
66
  }));
65
- test('loads an image and converts it to a tensor', () => {
67
+ it('loads an image and converts it to a tensor', () => {
66
68
  expect(imageTensor1).toBeDefined();
67
69
  expect(imageTensor1 instanceof tf.Tensor).toBe(true);
68
70
  expect(imageTensor2).toBeDefined();
69
71
  expect(imageTensor2 instanceof tf.Tensor).toBe(true);
70
72
  });
71
- test('mobilenet add data', () => {
72
- learning.addData("라벨1", imageTensor1);
73
- learning.addData("라벨1", imageTensor1);
74
- learning.addData("라벨2", imageTensor2);
75
- learning.addData("라벨2", imageTensor2);
76
- });
73
+ // test('mobilenet add data', () => {
74
+ // learning.addData("라벨1", imageTensor1);
75
+ // learning.addData("라벨1", imageTensor1);
76
+ // learning.addData("라벨2", imageTensor2);
77
+ // learning.addData("라벨2", imageTensor2);
78
+ // });
77
79
  });
@@ -1,4 +1,5 @@
1
1
  import * as tf from '@tensorflow/tfjs';
2
+ import '@tensorflow/tfjs-backend-wasm';
2
3
  import { io } from '@tensorflow/tfjs-core';
3
4
  import LearningInterface from './base';
4
5
  declare class LearningMobilenet implements LearningInterface {
@@ -39,6 +40,8 @@ declare class LearningMobilenet implements LearningInterface {
39
40
  private _convertToTfDataset;
40
41
  addData(label: string, data: any): Promise<void>;
41
42
  init(): Promise<void>;
43
+ private setupBackend;
44
+ private checkWasmSupport;
42
45
  train(): Promise<tf.History>;
43
46
  infer(data: any): Promise<Map<string, number>>;
44
47
  saveModel(handlerOrURL: io.IOHandler | string, config?: io.SaveConfig): Promise<void>;
@@ -3,5 +3,5 @@ export declare function isTensor(c: any): c is tf.Tensor;
3
3
  export declare function loadModel(): Promise<tf.LayersModel>;
4
4
  export declare function mobileNetURL(version: number): string;
5
5
  export declare function imageToTensor(data: any): tf.Tensor3D;
6
- export declare function capture(rasterElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement, grayscale?: boolean): tf.Tensor<tf.Rank>;
6
+ export declare function capture(rasterElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement, grayscale?: boolean): Promise<tf.Tensor<tf.Rank>>;
7
7
  export declare function cropTensor(img: tf.Tensor3D, grayscaleModel?: boolean, grayscaleInput?: boolean): tf.Tensor3D;
@@ -3,5 +3,5 @@ export declare function isTensor(c: any): c is tf.Tensor;
3
3
  export declare function loadModel(): Promise<tf.LayersModel>;
4
4
  export declare function mobileNetURL(version: number): string;
5
5
  export declare function imageToTensor(data: any): tf.Tensor3D;
6
- export declare function capture(rasterElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement, grayscale?: boolean): tf.Tensor<tf.Rank>;
6
+ export declare function capture(rasterElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement, grayscale?: boolean): Promise<tf.Tensor<tf.Rank>>;
7
7
  export declare function cropTensor(img: tf.Tensor3D, grayscaleModel?: boolean, grayscaleInput?: boolean): tf.Tensor3D;
package/dist/utils/tf.js CHANGED
@@ -90,15 +90,17 @@ function imageToTensor(data) {
90
90
  }
91
91
  exports.imageToTensor = imageToTensor;
92
92
  function capture(rasterElement, grayscale) {
93
- return tf.tidy(() => {
94
- const pixels = tf.browser.fromPixels(rasterElement);
95
- // crop the image so we're using the center square
96
- const cropped = cropTensor(pixels, grayscale);
97
- // Expand the outer most dimension so we have a batch size of 1
98
- const batchedImage = cropped.expandDims(0);
99
- // Normalize the image between -1 and a1. The image comes in between 0-255
100
- // so we divide by 127 and subtract 1.
101
- return batchedImage.toFloat().div(tf.scalar(127)).sub(tf.scalar(1));
93
+ return __awaiter(this, void 0, void 0, function* () {
94
+ return tf.tidy(() => {
95
+ const pixels = tf.browser.fromPixels(rasterElement);
96
+ // crop the image so we're using the center square
97
+ const cropped = cropTensor(pixels, grayscale);
98
+ // Expand the outer most dimension so we have a batch size of 1
99
+ const batchedImage = cropped.expandDims(0);
100
+ // Normalize the image between -1 and a1. The image comes in between 0-255
101
+ // so we divide by 127 and subtract 1.
102
+ return batchedImage.toFloat().div(tf.scalar(127)).sub(tf.scalar(1));
103
+ });
102
104
  });
103
105
  }
104
106
  exports.capture = capture;
package/jest.config.js CHANGED
@@ -2,7 +2,7 @@ module.exports = {
2
2
  preset: 'ts-jest',
3
3
  testEnvironment: 'node',
4
4
  // 기타 Jest 구성 옵션
5
- testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
6
- //testMatch: ['/lib/learning/data_model_test.ts'],
5
+ //testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
6
+ testMatch: ['**/mobile(*.)+(spec|test).[jt]s?(x)'],
7
7
  testPathIgnorePatterns: ['/node_modules/'],
8
8
  };
@@ -20,25 +20,29 @@ async function ImagePathToTensor(imagePath: string): Promise<tf.Tensor3D> {
20
20
 
21
21
  describe('LearningMobilenetImage', () => {
22
22
  const learning = new LearningMobilenetImage({});
23
-
23
+
24
+ const image1Path = path.join(__dirname, '../../public/images/image1.jpeg');
25
+ const image2Path = path.join(__dirname, '../../public/images/image2.jpeg');
26
+ console.log('Resolved path for image1:', image1Path);
24
27
  beforeAll(async () => {
25
- const image1Path = path.join(__dirname, '../../public/images/image1.jpeg');
26
- const image2Path = path.join(__dirname, '../../public/images/image2.jpeg');
28
+ learning.init();
27
29
  imageTensor1 = await ImagePathToTensor(image1Path);
28
30
  imageTensor2 = await ImagePathToTensor(image2Path);
29
31
  });
30
32
 
31
- test('loads an image and converts it to a tensor', () => {
33
+
34
+ it('loads an image and converts it to a tensor', () => {
32
35
  expect(imageTensor1).toBeDefined();
33
36
  expect(imageTensor1 instanceof tf.Tensor).toBe(true);
34
37
  expect(imageTensor2).toBeDefined();
35
38
  expect(imageTensor2 instanceof tf.Tensor).toBe(true);
36
39
  });
37
40
 
38
- test('mobilenet add data', () => {
39
- learning.addData("라벨1", imageTensor1);
40
- learning.addData("라벨1", imageTensor1);
41
- learning.addData("라벨2", imageTensor2);
42
- learning.addData("라벨2", imageTensor2);
43
- });
41
+ // test('mobilenet add data', () => {
42
+ // learning.addData("라벨1", imageTensor1);
43
+ // learning.addData("라벨1", imageTensor1);
44
+ // learning.addData("라벨2", imageTensor2);
45
+ // learning.addData("라벨2", imageTensor2);
46
+ // });
44
47
  });
48
+
@@ -5,6 +5,7 @@
5
5
  ///////////////////////////////////////////////////////////////////////////
6
6
 
7
7
  import * as tf from '@tensorflow/tfjs';
8
+ import '@tensorflow/tfjs-backend-wasm'; // WebAssembly 백엔드 추가
8
9
  import { dispose } from '@tensorflow/tfjs';
9
10
  import { io } from '@tensorflow/tfjs-core';
10
11
  import LearningInterface from './base';
@@ -150,17 +151,32 @@ class LearningMobilenet implements LearningInterface {
150
151
  public async addData(label: string, data: any): Promise<void> {
151
152
  try {
152
153
  if (this.mobilenetModule !== null) {
153
- const cap = isTensor(data) ? data : capture(data, false);
154
- const predict = this.mobilenetModule.predict(cap) as tf.Tensor;
154
+ const cap = isTensor(data) ? data : await capture(data, false);
155
+
156
+ const predict = tf.tidy(() => {
157
+ return this.mobilenetModule!.predict(cap) as tf.Tensor;
158
+ });
159
+
155
160
  const activation = await predict.data() as Float32Array;
161
+
156
162
  const classIndex = this.registerClassNumber(label);
157
163
  if (!this.imageExamples[classIndex]) {
158
164
  this.imageExamples[classIndex] = [];
159
165
  }
160
166
  this.imageExamples[classIndex].push(activation);
167
+
161
168
  if(this.classNumber.length >= this.limitSize) {
162
169
  this.isReady = true;
163
170
  }
171
+
172
+ // Dispose of the prediction tensor to free memory
173
+ predict.dispose();
174
+
175
+ // If cap is not a tensor, we don't need to dispose it. Otherwise, we should.
176
+ if (!isTensor(data)) {
177
+ cap.dispose();
178
+ }
179
+
164
180
  } else {
165
181
  throw new Error('mobilenetModule is null');
166
182
  }
@@ -173,6 +189,8 @@ class LearningMobilenet implements LearningInterface {
173
189
 
174
190
  public async init() {
175
191
  try {
192
+ console.log('init call')
193
+ await this.setupBackend();
176
194
  this.mobilenetModule = await loadModel();
177
195
  } catch(error) {
178
196
  console.log('init Error', error);
@@ -180,6 +198,29 @@ class LearningMobilenet implements LearningInterface {
180
198
  }
181
199
  }
182
200
 
201
+ private async setupBackend() {
202
+ const isWasmSupported = await this.checkWasmSupport();
203
+ if (isWasmSupported) {
204
+ await tf.setBackend('wasm');
205
+ await tf.ready();
206
+ console.log('Backend is set to WebAssembly');
207
+ } else {
208
+ await tf.setBackend('cpu');
209
+ await tf.ready();
210
+ console.log('Backend is set to CPU');
211
+ }
212
+ }
213
+
214
+ private async checkWasmSupport(): Promise<boolean> {
215
+ try {
216
+ await tf.setBackend('wasm');
217
+ await tf.ready();
218
+ return true;
219
+ } catch (error) {
220
+ console.warn('WASM backend is not supported in this environment.', error);
221
+ return false;
222
+ }
223
+ }
183
224
 
184
225
  // 모델 학습 처리
185
226
  public async train(): Promise<tf.History> {
@@ -227,7 +268,9 @@ class LearningMobilenet implements LearningInterface {
227
268
  const trainData = datasets.trainDataset.batch(this.batchSize);
228
269
  const validationData = datasets.validationDataset.batch(this.batchSize);
229
270
  const optimizer = tf.train.adam(this.learningRate);
271
+
230
272
  const trainModel = await this._createModel(optimizer);
273
+
231
274
  const jointModel = tf.sequential();
232
275
  jointModel.add(this.mobilenetModule!);
233
276
  jointModel.add(trainModel);
@@ -257,16 +300,19 @@ class LearningMobilenet implements LearningInterface {
257
300
  try {
258
301
  const classProbabilities = new Map<string, number>();
259
302
  const croppedImage = cropTo(data, 224, false);
303
+ const captured = await capture(croppedImage, false);
260
304
 
261
305
  const logits = tf.tidy(() => {
262
- const captured = capture(croppedImage, false);
263
306
  return this.model!.predict(captured);
264
307
  });
308
+
265
309
  const values = await (logits as tf.Tensor<tf.Rank>).data();
266
310
  const EPSILON = 1e-6; // 매우 작은 값을 표현하기 위한 엡실론
311
+
267
312
  for (let i = 0; i < values.length; i++) {
268
313
  let probability = Math.max(0, Math.min(1, values[i])); // 확률 값을 0과 1 사이로 조정
269
314
  probability = probability < EPSILON ? 0 : probability; // 매우 작은 확률 값을 0으로 간주
315
+
270
316
  const className = this.classNumber[i]; // 클래스 이름
271
317
  const existingProbability = classProbabilities.get(className);
272
318
  if (existingProbability !== undefined) {
@@ -275,6 +321,7 @@ class LearningMobilenet implements LearningInterface {
275
321
  classProbabilities.set(className, probability);
276
322
  }
277
323
  }
324
+
278
325
  console.log('classProbabilities', classProbabilities);
279
326
  dispose(logits);
280
327
  return classProbabilities;
package/lib/utils/tf.ts CHANGED
@@ -54,7 +54,7 @@ export function imageToTensor(data: any): tf.Tensor3D {
54
54
  return tensor;
55
55
  }
56
56
 
57
- export function capture(rasterElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement, grayscale?: boolean) {
57
+ export async function capture(rasterElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement, grayscale?: boolean) {
58
58
  return tf.tidy(() => {
59
59
  const pixels = tf.browser.fromPixels(rasterElement);
60
60
 
@@ -70,6 +70,7 @@ export function capture(rasterElement: HTMLImageElement | HTMLVideoElement | HTM
70
70
  });
71
71
  }
72
72
 
73
+
73
74
  export function cropTensor( img: tf.Tensor3D, grayscaleModel?: boolean, grayscaleInput?: boolean ) : tf.Tensor3D {
74
75
  const size = Math.min(img.shape[0], img.shape[1]);
75
76
  const centerHeight = img.shape[0] / 2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "learning_model",
3
- "version": "1.0.49",
3
+ "version": "1.0.51",
4
4
  "description": "learning model develop",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -20,6 +20,7 @@
20
20
  "@tensorflow-models/mobilenet": "^2.1.0",
21
21
  "@tensorflow/tfjs": "^4.6.0",
22
22
  "@tensorflow/tfjs-layers": "^4.6.0",
23
+ "@tensorflow/tfjs-backend-wasm": "^4.20.0",
23
24
  "canvas": "^2.11.2",
24
25
  "learning_model": "^1.0.0"
25
26
  },