learning_model 1.0.28 → 1.0.29
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.
- package/dist/index.bundle.js +1 -1
- package/dist/learning/base.d.ts +3 -2
- package/dist/learning/image.d.ts +3 -1
- package/dist/learning/image.js +23 -12
- package/dist/learning/mobilenet.d.ts +49 -0
- package/dist/learning/mobilenet.js +317 -0
- package/dist/learning/mobilenet.test.d.ts +1 -0
- package/dist/learning/mobilenet.test.js +77 -0
- package/dist/learning/mobilenet_image.d.ts +3 -1
- package/dist/learning/mobilenet_image.js +5 -6
- package/dist/learning/util.js +1 -0
- package/dist/lib/learning/base.d.ts +3 -2
- package/dist/lib/learning/image.d.ts +3 -1
- package/dist/lib/learning/mobilenet.d.ts +49 -0
- package/dist/lib/learning/mobilenet.test.d.ts +1 -0
- package/dist/lib/learning/mobilenet_image.d.ts +3 -1
- package/lib/learning/base.ts +4 -4
- package/lib/learning/image.ts +26 -13
- package/lib/learning/mobilenet.test.ts +44 -0
- package/lib/learning/mobilenet.ts +322 -0
- package/lib/learning/mobilenet_image.ts +7 -6
- package/lib/learning/util.ts +1 -0
- package/package.json +1 -1
- package/yarn-error.log +10 -10
package/dist/learning/base.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as tf from '@tensorflow/tfjs';
|
|
2
|
+
import { io } from '@tensorflow/tfjs-core';
|
|
2
3
|
interface LearningInterface {
|
|
3
4
|
model: tf.LayersModel | null;
|
|
4
5
|
labels: string[];
|
|
@@ -6,13 +7,13 @@ interface LearningInterface {
|
|
|
6
7
|
isReady: boolean;
|
|
7
8
|
onProgress(progress: number): void;
|
|
8
9
|
onLoss(loss: number): void;
|
|
9
|
-
onEvents(logs: any): void;
|
|
10
10
|
onTrainBegin(log: any): void;
|
|
11
11
|
onTrainEnd(log: any): void;
|
|
12
|
+
onEpochEnd(epoch: number, logs: any): void;
|
|
12
13
|
addData(label: string, data: any): Promise<void>;
|
|
13
14
|
train(): Promise<tf.History>;
|
|
14
15
|
infer(data: any): Promise<any>;
|
|
15
|
-
saveModel(): Promise<void>;
|
|
16
|
+
saveModel(handlerOrURL: io.IOHandler | string, config?: io.SaveConfig): Promise<void>;
|
|
16
17
|
running(): boolean;
|
|
17
18
|
ready(): boolean;
|
|
18
19
|
}
|
package/dist/learning/image.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as tf from '@tensorflow/tfjs';
|
|
2
|
+
import { io } from '@tensorflow/tfjs-core';
|
|
2
3
|
import LearningInterface from './base';
|
|
3
4
|
declare class LearningImage implements LearningInterface {
|
|
4
5
|
model: tf.LayersModel | null;
|
|
@@ -29,10 +30,11 @@ declare class LearningImage implements LearningInterface {
|
|
|
29
30
|
onEvents: (logs: any) => void;
|
|
30
31
|
onTrainBegin: (log: any) => void;
|
|
31
32
|
onTrainEnd: (log: any) => void;
|
|
33
|
+
onEpochEnd: (epoch: number, logs: any) => void;
|
|
32
34
|
addData(label: string, data: any): Promise<void>;
|
|
33
35
|
train(): Promise<tf.History>;
|
|
34
36
|
infer(data: any): Promise<Map<string, number>>;
|
|
35
|
-
saveModel(): Promise<void>;
|
|
37
|
+
saveModel(handlerOrURL: io.IOHandler | string, config?: io.SaveConfig): Promise<void>;
|
|
36
38
|
running(): boolean;
|
|
37
39
|
ready(): boolean;
|
|
38
40
|
private _preprocessedTargetData;
|
package/dist/learning/image.js
CHANGED
|
@@ -45,6 +45,7 @@ class LearningImage {
|
|
|
45
45
|
this.onEvents = () => { };
|
|
46
46
|
this.onTrainBegin = () => { };
|
|
47
47
|
this.onTrainEnd = () => { };
|
|
48
|
+
this.onEpochEnd = () => { };
|
|
48
49
|
this.model = null;
|
|
49
50
|
this.epochs = epochs;
|
|
50
51
|
this.batchSize = batchSize;
|
|
@@ -140,7 +141,6 @@ class LearningImage {
|
|
|
140
141
|
// 추론하기
|
|
141
142
|
infer(data) {
|
|
142
143
|
return __awaiter(this, void 0, void 0, function* () {
|
|
143
|
-
const threshold = 1e-20; // 임계값 설정
|
|
144
144
|
if (this.model === null) {
|
|
145
145
|
throw new Error('Model is null');
|
|
146
146
|
}
|
|
@@ -151,17 +151,15 @@ class LearningImage {
|
|
|
151
151
|
const predictions = this.model.predict(reshapedTensor);
|
|
152
152
|
const predictionsData = yield predictions.data(); // 예측 텐서의 데이터를 비동기로 가져옴
|
|
153
153
|
const classProbabilities = new Map(); // 클래스별 확률 누적값을 저장할 맵
|
|
154
|
-
console.log('predictionsData', predictionsData);
|
|
155
154
|
for (let i = 0; i < predictionsData.length; i++) {
|
|
156
155
|
const className = this.labels[i]; // 클래스 이름
|
|
157
156
|
const probability = predictionsData[i];
|
|
158
|
-
const result = Math.abs(probability) < threshold ? 0 : probability;
|
|
159
157
|
const existingProbability = classProbabilities.get(className);
|
|
160
158
|
if (existingProbability !== undefined) {
|
|
161
|
-
classProbabilities.set(className, existingProbability +
|
|
159
|
+
classProbabilities.set(className, existingProbability + probability);
|
|
162
160
|
}
|
|
163
161
|
else {
|
|
164
|
-
classProbabilities.set(className,
|
|
162
|
+
classProbabilities.set(className, probability);
|
|
165
163
|
}
|
|
166
164
|
}
|
|
167
165
|
console.log('Class Probabilities:', classProbabilities);
|
|
@@ -173,14 +171,14 @@ class LearningImage {
|
|
|
173
171
|
});
|
|
174
172
|
}
|
|
175
173
|
// 모델 저장
|
|
176
|
-
saveModel() {
|
|
174
|
+
saveModel(handlerOrURL, config) {
|
|
177
175
|
var _a;
|
|
178
176
|
return __awaiter(this, void 0, void 0, function* () {
|
|
179
177
|
console.log('saved model');
|
|
180
178
|
if (!this.isTrainedDone) {
|
|
181
179
|
return Promise.reject(new Error('Train is not done status'));
|
|
182
180
|
}
|
|
183
|
-
yield ((_a = this.model) === null || _a === void 0 ? void 0 : _a.save(
|
|
181
|
+
yield ((_a = this.model) === null || _a === void 0 ? void 0 : _a.save(handlerOrURL, config));
|
|
184
182
|
});
|
|
185
183
|
}
|
|
186
184
|
// 진행중 여부
|
|
@@ -238,19 +236,32 @@ class LearningImage {
|
|
|
238
236
|
const inputShape = [this.MOBILE_NET_INPUT_WIDTH, this.MOBILE_NET_INPUT_HEIGHT, this.MOBILE_NET_INPUT_CHANNEL];
|
|
239
237
|
const model = tf.sequential();
|
|
240
238
|
model.add(tf.layers.conv2d({
|
|
241
|
-
|
|
239
|
+
activation: "relu",
|
|
242
240
|
filters: 32,
|
|
241
|
+
inputShape: inputShape,
|
|
243
242
|
kernelSize: 3,
|
|
244
|
-
activation: 'relu'
|
|
245
243
|
}));
|
|
246
|
-
model.add(tf.layers.maxPooling2d({ poolSize: 2 }));
|
|
247
244
|
model.add(tf.layers.conv2d({
|
|
245
|
+
activation: "relu",
|
|
246
|
+
filters: 32,
|
|
247
|
+
kernelSize: 3,
|
|
248
|
+
}));
|
|
249
|
+
model.add(tf.layers.maxPooling2d({ poolSize: [2, 2] }));
|
|
250
|
+
model.add(tf.layers.conv2d({
|
|
251
|
+
activation: "relu",
|
|
252
|
+
filters: 64,
|
|
253
|
+
kernelSize: 3,
|
|
254
|
+
}));
|
|
255
|
+
model.add(tf.layers.conv2d({
|
|
256
|
+
activation: "relu",
|
|
248
257
|
filters: 64,
|
|
249
258
|
kernelSize: 3,
|
|
250
|
-
activation: 'relu'
|
|
251
259
|
}));
|
|
252
|
-
model.add(tf.layers.maxPooling2d({ poolSize: 2 }));
|
|
260
|
+
model.add(tf.layers.maxPooling2d({ poolSize: [2, 2] }));
|
|
253
261
|
model.add(tf.layers.flatten());
|
|
262
|
+
model.add(tf.layers.dropout({ rate: 0.25 }));
|
|
263
|
+
model.add(tf.layers.dense({ units: 512, activation: "relu" }));
|
|
264
|
+
model.add(tf.layers.dropout({ rate: 0.5 }));
|
|
254
265
|
model.add(tf.layers.dense({
|
|
255
266
|
units: numClasses,
|
|
256
267
|
activation: 'softmax'
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as tf from '@tensorflow/tfjs';
|
|
2
|
+
import { io } from '@tensorflow/tfjs-core';
|
|
3
|
+
import LearningInterface from './base';
|
|
4
|
+
declare class LearningMobilenet implements LearningInterface {
|
|
5
|
+
model: tf.LayersModel | null;
|
|
6
|
+
epochs: number;
|
|
7
|
+
batchSize: number;
|
|
8
|
+
learningRate: number;
|
|
9
|
+
validateRate: number;
|
|
10
|
+
labels: string[];
|
|
11
|
+
modelURL: string;
|
|
12
|
+
isRunning: boolean;
|
|
13
|
+
isReady: boolean;
|
|
14
|
+
isTrainedDone: boolean;
|
|
15
|
+
limitSize: number;
|
|
16
|
+
mobilenetModule: tf.LayersModel | null;
|
|
17
|
+
imageTensors: tf.Tensor<tf.Rank>[];
|
|
18
|
+
readonly MOBILE_NET_INPUT_WIDTH = 224;
|
|
19
|
+
readonly MOBILE_NET_INPUT_HEIGHT = 224;
|
|
20
|
+
readonly MOBILE_NET_INPUT_CHANNEL = 3;
|
|
21
|
+
readonly IMAGE_NORMALIZATION_FACTOR = 255;
|
|
22
|
+
constructor({ modelURL, // 디폴트 mobilenet 이미지
|
|
23
|
+
epochs, batchSize, limitSize, learningRate, validateRate, }?: {
|
|
24
|
+
modelURL?: string;
|
|
25
|
+
epochs?: number;
|
|
26
|
+
batchSize?: number;
|
|
27
|
+
limitSize?: number;
|
|
28
|
+
learningRate?: number;
|
|
29
|
+
validateRate?: number;
|
|
30
|
+
});
|
|
31
|
+
onProgress: (progress: number) => void;
|
|
32
|
+
onLoss: (loss: number) => void;
|
|
33
|
+
onEvents: (logs: any) => void;
|
|
34
|
+
onTrainBegin: (log: any) => void;
|
|
35
|
+
onTrainEnd: (log: any) => void;
|
|
36
|
+
onEpochEnd: (epoch: number, logs: any) => void;
|
|
37
|
+
addData(label: string, data: any): Promise<void>;
|
|
38
|
+
init(): Promise<void>;
|
|
39
|
+
train(): Promise<tf.History>;
|
|
40
|
+
infer(data: any): Promise<Map<string, number>>;
|
|
41
|
+
saveModel(handlerOrURL: io.IOHandler | string, config?: io.SaveConfig): Promise<void>;
|
|
42
|
+
running(): boolean;
|
|
43
|
+
ready(): boolean;
|
|
44
|
+
private _preprocessedTargetData;
|
|
45
|
+
private _preprocessedInputData;
|
|
46
|
+
private loadModel;
|
|
47
|
+
private _createModel;
|
|
48
|
+
}
|
|
49
|
+
export default LearningMobilenet;
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
///////////////////////////////////////////////////////////////////////////
|
|
3
|
+
///////////////////////////////////////////////////////////////////////////
|
|
4
|
+
///////////////////////////////////////////////////////////////////////////
|
|
5
|
+
// mobilenet 모델을 이용한 전이학습 방법
|
|
6
|
+
///////////////////////////////////////////////////////////////////////////
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
24
|
+
if (mod && mod.__esModule) return mod;
|
|
25
|
+
var result = {};
|
|
26
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
27
|
+
__setModuleDefault(result, mod);
|
|
28
|
+
return result;
|
|
29
|
+
};
|
|
30
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
31
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
32
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
33
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
34
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
35
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
36
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const tf = __importStar(require("@tensorflow/tfjs"));
|
|
41
|
+
const util_1 = require("./util");
|
|
42
|
+
class LearningMobilenet {
|
|
43
|
+
constructor({ modelURL = 'https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_0.25_224/model.json', // 디폴트 mobilenet 이미지
|
|
44
|
+
epochs = 10, batchSize = 16, limitSize = 2, learningRate = 0.001, validateRate = 0.2, } = {}) {
|
|
45
|
+
this.imageTensors = [];
|
|
46
|
+
this.MOBILE_NET_INPUT_WIDTH = 224;
|
|
47
|
+
this.MOBILE_NET_INPUT_HEIGHT = 224;
|
|
48
|
+
this.MOBILE_NET_INPUT_CHANNEL = 3;
|
|
49
|
+
this.IMAGE_NORMALIZATION_FACTOR = 255.0;
|
|
50
|
+
// 진행 상태를 나타내는 이벤트를 정의합니다.
|
|
51
|
+
this.onProgress = () => { };
|
|
52
|
+
this.onLoss = () => { };
|
|
53
|
+
this.onEvents = () => { };
|
|
54
|
+
this.onTrainBegin = () => { };
|
|
55
|
+
this.onTrainEnd = () => { };
|
|
56
|
+
this.onEpochEnd = () => { };
|
|
57
|
+
this.model = null;
|
|
58
|
+
this.epochs = epochs;
|
|
59
|
+
this.batchSize = batchSize;
|
|
60
|
+
this.learningRate = learningRate;
|
|
61
|
+
this.validateRate = validateRate;
|
|
62
|
+
this.labels = [];
|
|
63
|
+
this.modelURL = modelURL;
|
|
64
|
+
this.isRunning = false;
|
|
65
|
+
this.isReady = false;
|
|
66
|
+
this.isTrainedDone = false;
|
|
67
|
+
this.limitSize = limitSize;
|
|
68
|
+
this.mobilenetModule = null;
|
|
69
|
+
this.imageTensors = [];
|
|
70
|
+
this.init();
|
|
71
|
+
}
|
|
72
|
+
// 학습 데이타 등록
|
|
73
|
+
addData(label, data) {
|
|
74
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
75
|
+
try {
|
|
76
|
+
const tensor = (0, util_1.ImageToTensor)(data);
|
|
77
|
+
const imgTensor = tf.tidy(() => {
|
|
78
|
+
if (this.mobilenetModule !== null) {
|
|
79
|
+
console.log('predict before', data);
|
|
80
|
+
const t = tf.browser.fromPixels(data);
|
|
81
|
+
// Resize the image to match the model's input shape
|
|
82
|
+
const resizedTensor = tf.image.resizeBilinear(t, [224, 224]);
|
|
83
|
+
const normalizedTensor = resizedTensor.div(255.0);
|
|
84
|
+
// Add an extra dimension to the tensor
|
|
85
|
+
const expandedTensor = normalizedTensor.expandDims(0);
|
|
86
|
+
console.log('predict extend', expandedTensor);
|
|
87
|
+
const predict = this.mobilenetModule.predict(expandedTensor);
|
|
88
|
+
console.log('predict', predict);
|
|
89
|
+
return predict;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
throw new Error('mobilenetModule is null');
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
console.log('imgTensor', imgTensor);
|
|
96
|
+
this.imageTensors.push(imgTensor);
|
|
97
|
+
this.labels.push(label);
|
|
98
|
+
if (this.labels.length >= this.limitSize) {
|
|
99
|
+
this.isReady = true;
|
|
100
|
+
}
|
|
101
|
+
return Promise.resolve();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error('Model training failed', error);
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
init() {
|
|
110
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
111
|
+
const model = yield this.loadModel();
|
|
112
|
+
// const load_model = await tf.loadLayersModel(this.modelURL);
|
|
113
|
+
this.mobilenetModule = model;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// 모델 학습 처리
|
|
117
|
+
train() {
|
|
118
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
119
|
+
if (this.isRunning) {
|
|
120
|
+
return Promise.reject(new Error('Training is already in progress.'));
|
|
121
|
+
}
|
|
122
|
+
// 콜백 정의
|
|
123
|
+
const customCallback = {
|
|
124
|
+
onTrainBegin: (log) => {
|
|
125
|
+
this.isTrainedDone = false;
|
|
126
|
+
this.onTrainBegin(log);
|
|
127
|
+
console.log('Training has started.');
|
|
128
|
+
},
|
|
129
|
+
onTrainEnd: (log) => {
|
|
130
|
+
this.isTrainedDone = true;
|
|
131
|
+
this.onTrainEnd(log);
|
|
132
|
+
console.log('Training has ended.');
|
|
133
|
+
this.isRunning = false;
|
|
134
|
+
},
|
|
135
|
+
onBatchBegin: (batch, logs) => {
|
|
136
|
+
console.log(`Batch ${batch} is starting.`);
|
|
137
|
+
},
|
|
138
|
+
onBatchEnd: (batch, logs) => {
|
|
139
|
+
console.log(`Batch ${batch} has ended.`);
|
|
140
|
+
},
|
|
141
|
+
onEpochBegin: (epoch, logs) => {
|
|
142
|
+
console.log(`Epoch ${epoch + 1} is starting.`, logs);
|
|
143
|
+
},
|
|
144
|
+
onEpochEnd: (epoch, logs) => {
|
|
145
|
+
this.onEpochEnd(epoch, logs);
|
|
146
|
+
console.log(`Epoch ${epoch + 1} has ended.`);
|
|
147
|
+
console.log('Loss:', logs);
|
|
148
|
+
this.onLoss(logs.loss);
|
|
149
|
+
this.onProgress(epoch + 1);
|
|
150
|
+
this.onEvents({
|
|
151
|
+
epoch: epoch,
|
|
152
|
+
logs: logs,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
try {
|
|
157
|
+
this.isRunning = true;
|
|
158
|
+
if (this.labels.length < this.limitSize) {
|
|
159
|
+
return Promise.reject(new Error('Please train Data need over 2 data length'));
|
|
160
|
+
}
|
|
161
|
+
this.model = yield this._createModel(this.labels.length);
|
|
162
|
+
const inputData = this._preprocessedInputData(this.model);
|
|
163
|
+
console.log('this.imageTensors', this.imageTensors, inputData);
|
|
164
|
+
const targetData = this._preprocessedTargetData();
|
|
165
|
+
const history = yield this.model.fit(inputData, targetData, {
|
|
166
|
+
epochs: this.epochs,
|
|
167
|
+
batchSize: this.batchSize,
|
|
168
|
+
validationSplit: this.validateRate,
|
|
169
|
+
callbacks: customCallback
|
|
170
|
+
});
|
|
171
|
+
console.log('Model training completed', history);
|
|
172
|
+
return history;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
this.isRunning = false;
|
|
176
|
+
console.error('Model training failed', error);
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// 추론하기
|
|
182
|
+
infer(data) {
|
|
183
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
184
|
+
if (this.model === null) {
|
|
185
|
+
return Promise.reject(new Error('Model is Null'));
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const imgTensor = tf.tidy(() => {
|
|
189
|
+
if (this.mobilenetModule !== null) {
|
|
190
|
+
console.log('predict before', data);
|
|
191
|
+
const t = tf.browser.fromPixels(data);
|
|
192
|
+
// Resize the image to match the model's input shape
|
|
193
|
+
const resizedTensor = tf.image.resizeBilinear(t, [224, 224]);
|
|
194
|
+
const normalizedTensor = resizedTensor.div(255.0);
|
|
195
|
+
// Add an extra dimension to the tensor
|
|
196
|
+
const expandedTensor = normalizedTensor.expandDims(0);
|
|
197
|
+
console.log('predict extend', expandedTensor);
|
|
198
|
+
const predict = this.mobilenetModule.predict(expandedTensor);
|
|
199
|
+
console.log('predict', predict);
|
|
200
|
+
return predict;
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
throw new Error('mobilenetModule is null');
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
const predictions = this.model.predict(imgTensor);
|
|
207
|
+
const predictedClass = predictions.as1D().argMax();
|
|
208
|
+
const classId = (yield predictedClass.data())[0];
|
|
209
|
+
console.log('classId', classId, this.labels[classId]);
|
|
210
|
+
const predictionsData = yield predictions.data(); // 예측 텐서의 데이터를 비동기로 가져옴
|
|
211
|
+
const classProbabilities = new Map(); // 클래스별 확률 누적값을 저장할 맵
|
|
212
|
+
const EPSILON = 1e-6; // 매우 작은 값을 표현하기 위한 엡실론
|
|
213
|
+
for (let i = 0; i < predictionsData.length; i++) {
|
|
214
|
+
let probability = Math.max(0, Math.min(1, predictionsData[i])); // 확률 값을 0과 1 사이로 조정
|
|
215
|
+
probability = probability < EPSILON ? 0 : probability; // 매우 작은 확률 값을 0으로 간주
|
|
216
|
+
const className = this.labels[i]; // 클래스 이름
|
|
217
|
+
const existingProbability = classProbabilities.get(className);
|
|
218
|
+
if (existingProbability !== undefined) {
|
|
219
|
+
classProbabilities.set(className, existingProbability + probability);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
classProbabilities.set(className, probability);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
console.log('Class Probabilities:', classProbabilities);
|
|
226
|
+
predictedClass.dispose();
|
|
227
|
+
predictions.dispose();
|
|
228
|
+
return classProbabilities;
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
// 모델 저장
|
|
236
|
+
saveModel(handlerOrURL, config) {
|
|
237
|
+
var _a;
|
|
238
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
239
|
+
console.log('saved model');
|
|
240
|
+
if (!this.isTrainedDone) {
|
|
241
|
+
return Promise.reject(new Error('Train is not done status'));
|
|
242
|
+
}
|
|
243
|
+
yield ((_a = this.model) === null || _a === void 0 ? void 0 : _a.save(handlerOrURL, config));
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// 진행중 여부
|
|
247
|
+
running() {
|
|
248
|
+
return this.isRunning;
|
|
249
|
+
}
|
|
250
|
+
ready() {
|
|
251
|
+
return this.isReady;
|
|
252
|
+
}
|
|
253
|
+
// target 라벨 데이타
|
|
254
|
+
_preprocessedTargetData() {
|
|
255
|
+
// 라벨 unique 처리 & 배열 리턴
|
|
256
|
+
console.log('uniqueLabels.length', this.labels, this.labels.length);
|
|
257
|
+
const labelIndices = this.labels.map((label) => this.labels.indexOf(label));
|
|
258
|
+
console.log('labelIndices', labelIndices);
|
|
259
|
+
const oneHotEncode = tf.oneHot(tf.tensor1d(labelIndices, 'int32'), this.labels.length);
|
|
260
|
+
console.log('oneHotEncode', oneHotEncode);
|
|
261
|
+
return oneHotEncode;
|
|
262
|
+
}
|
|
263
|
+
// 입력 이미지 데이타
|
|
264
|
+
_preprocessedInputData(model) {
|
|
265
|
+
// 이미지 배열을 배치로 변환 - [null, 224, 224, 3]
|
|
266
|
+
const inputShape = model.inputs[0].shape;
|
|
267
|
+
console.log('inputShape', inputShape);
|
|
268
|
+
// inputShape를 이와 같이 포멧 맞춘다. for reshape to [224, 224, 3]
|
|
269
|
+
const inputShapeArray = inputShape.slice(1);
|
|
270
|
+
console.log('inputShapeArray', inputShapeArray);
|
|
271
|
+
const inputBatch = tf.stack(this.imageTensors.map((image) => {
|
|
272
|
+
return tf.reshape(image, inputShapeArray);
|
|
273
|
+
}));
|
|
274
|
+
return inputBatch;
|
|
275
|
+
}
|
|
276
|
+
loadModel() {
|
|
277
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
278
|
+
const load_model = yield tf.loadLayersModel(this.modelURL);
|
|
279
|
+
load_model.summary();
|
|
280
|
+
const layer = load_model.getLayer('conv_pw_13_relu');
|
|
281
|
+
const truncatedModel = tf.model({
|
|
282
|
+
inputs: load_model.inputs,
|
|
283
|
+
outputs: layer.output
|
|
284
|
+
});
|
|
285
|
+
return truncatedModel;
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
// 모델 저장
|
|
289
|
+
_createModel(numClasses) {
|
|
290
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
291
|
+
try {
|
|
292
|
+
const truncatedModel = yield this.loadModel();
|
|
293
|
+
console.log('truncatedModel', truncatedModel, truncatedModel.outputs[0].shape.slice(1));
|
|
294
|
+
// 입력 이미지 크기에 맞게 모델 구조 수정
|
|
295
|
+
const model = tf.sequential();
|
|
296
|
+
model.add(tf.layers.flatten({
|
|
297
|
+
inputShape: truncatedModel.outputs[0].shape.slice(1)
|
|
298
|
+
}));
|
|
299
|
+
model.add(tf.layers.dense({ units: 100, activation: 'relu' }));
|
|
300
|
+
model.add(tf.layers.dense({ units: this.labels.length, activation: 'softmax' }));
|
|
301
|
+
const optimizer = tf.train.adam(this.learningRate); // Optimizer를 생성하고 학습률을 설정합니다.
|
|
302
|
+
model.compile({
|
|
303
|
+
loss: (numClasses === 2) ? 'binaryCrossentropy' : 'categoricalCrossentropy',
|
|
304
|
+
optimizer: optimizer,
|
|
305
|
+
metrics: ['accuracy', 'acc']
|
|
306
|
+
});
|
|
307
|
+
model.summary();
|
|
308
|
+
return model;
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
console.error('Failed to load model', error);
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
exports.default = LearningMobilenet;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
35
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const tf = __importStar(require("@tensorflow/tfjs"));
|
|
40
|
+
const mobilenet_1 = __importDefault(require("./mobilenet"));
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const { createCanvas, loadImage } = require('canvas');
|
|
43
|
+
let imageTensor1;
|
|
44
|
+
let imageTensor2;
|
|
45
|
+
// 이미지경로를 기준으로
|
|
46
|
+
function ImagePathToTensor(imagePath) {
|
|
47
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
49
|
+
const image = yield loadImage(imageBuffer);
|
|
50
|
+
const canvas = createCanvas(image.width, image.height);
|
|
51
|
+
const ctx = canvas.getContext('2d');
|
|
52
|
+
ctx.drawImage(image, 0, 0);
|
|
53
|
+
const imageData = ctx.getImageData(0, 0, image.width, image.height);
|
|
54
|
+
return tf.tensor3d(imageData.data, [imageData.height, imageData.width, 4], 'int32');
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
describe('LearningMobilenetImage', () => {
|
|
58
|
+
const learning = new mobilenet_1.default({});
|
|
59
|
+
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');
|
|
62
|
+
imageTensor1 = yield ImagePathToTensor(image1Path);
|
|
63
|
+
imageTensor2 = yield ImagePathToTensor(image2Path);
|
|
64
|
+
}));
|
|
65
|
+
test('loads an image and converts it to a tensor', () => {
|
|
66
|
+
expect(imageTensor1).toBeDefined();
|
|
67
|
+
expect(imageTensor1 instanceof tf.Tensor).toBe(true);
|
|
68
|
+
expect(imageTensor2).toBeDefined();
|
|
69
|
+
expect(imageTensor2 instanceof tf.Tensor).toBe(true);
|
|
70
|
+
});
|
|
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
|
+
});
|
|
77
|
+
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as tf from '@tensorflow/tfjs';
|
|
2
|
+
import { io } from '@tensorflow/tfjs-core';
|
|
2
3
|
import LearningInterface from './base';
|
|
3
4
|
declare class LearningMobilenetImage implements LearningInterface {
|
|
4
5
|
model: tf.LayersModel | null;
|
|
@@ -31,10 +32,11 @@ declare class LearningMobilenetImage implements LearningInterface {
|
|
|
31
32
|
onEvents: (logs: any) => void;
|
|
32
33
|
onTrainBegin: (log: any) => void;
|
|
33
34
|
onTrainEnd: (log: any) => void;
|
|
35
|
+
onEpochEnd: (epoch: number, logs: any) => void;
|
|
34
36
|
addData(label: string, data: any): Promise<void>;
|
|
35
37
|
train(): Promise<tf.History>;
|
|
36
38
|
infer(data: any): Promise<Map<string, number>>;
|
|
37
|
-
saveModel(): Promise<void>;
|
|
39
|
+
saveModel(handlerOrURL: io.IOHandler | string, config?: io.SaveConfig): Promise<void>;
|
|
38
40
|
running(): boolean;
|
|
39
41
|
ready(): boolean;
|
|
40
42
|
private _preprocessedTargetData;
|
|
@@ -53,6 +53,7 @@ class LearningMobilenetImage {
|
|
|
53
53
|
this.onEvents = () => { };
|
|
54
54
|
this.onTrainBegin = () => { };
|
|
55
55
|
this.onTrainEnd = () => { };
|
|
56
|
+
this.onEpochEnd = () => { };
|
|
56
57
|
this.model = null;
|
|
57
58
|
this.epochs = epochs;
|
|
58
59
|
this.batchSize = batchSize;
|
|
@@ -147,7 +148,6 @@ class LearningMobilenetImage {
|
|
|
147
148
|
// 추론하기
|
|
148
149
|
infer(data) {
|
|
149
150
|
return __awaiter(this, void 0, void 0, function* () {
|
|
150
|
-
const threshold = 1e-20; // 임계값 설정
|
|
151
151
|
if (this.model === null) {
|
|
152
152
|
return Promise.reject(new Error('Model is Null'));
|
|
153
153
|
}
|
|
@@ -161,13 +161,12 @@ class LearningMobilenetImage {
|
|
|
161
161
|
for (let i = 0; i < predictionsData.length; i++) {
|
|
162
162
|
const className = this.labels[i]; // 클래스 이름
|
|
163
163
|
const probability = predictionsData[i];
|
|
164
|
-
const result = Math.abs(probability) < threshold ? 0 : probability;
|
|
165
164
|
const existingProbability = classProbabilities.get(className);
|
|
166
165
|
if (existingProbability !== undefined) {
|
|
167
|
-
classProbabilities.set(className, existingProbability +
|
|
166
|
+
classProbabilities.set(className, existingProbability + probability);
|
|
168
167
|
}
|
|
169
168
|
else {
|
|
170
|
-
classProbabilities.set(className,
|
|
169
|
+
classProbabilities.set(className, probability);
|
|
171
170
|
}
|
|
172
171
|
}
|
|
173
172
|
console.log('Class Probabilities:', classProbabilities);
|
|
@@ -179,14 +178,14 @@ class LearningMobilenetImage {
|
|
|
179
178
|
});
|
|
180
179
|
}
|
|
181
180
|
// 모델 저장
|
|
182
|
-
saveModel() {
|
|
181
|
+
saveModel(handlerOrURL, config) {
|
|
183
182
|
var _a;
|
|
184
183
|
return __awaiter(this, void 0, void 0, function* () {
|
|
185
184
|
console.log('saved model');
|
|
186
185
|
if (!this.isTrainedDone) {
|
|
187
186
|
return Promise.reject(new Error('Train is not done status'));
|
|
188
187
|
}
|
|
189
|
-
yield ((_a = this.model) === null || _a === void 0 ? void 0 : _a.save(
|
|
188
|
+
yield ((_a = this.model) === null || _a === void 0 ? void 0 : _a.save(handlerOrURL, config));
|
|
190
189
|
});
|
|
191
190
|
}
|
|
192
191
|
// 진행중 여부
|