learning_model 1.0.51 → 1.0.53
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/Makefile +7 -1
- package/dist/learning/mobilenet.d.ts +0 -1
- package/dist/learning/mobilenet.js +3 -3
- package/dist/lib/index.js +10 -0
- package/dist/lib/learning/base.js +2 -0
- package/dist/lib/learning/data_model.js +215 -0
- package/dist/lib/learning/data_model.test.js +56 -0
- package/dist/lib/learning/mobilenet.d.ts +0 -1
- package/dist/lib/learning/mobilenet.js +386 -0
- package/dist/lib/learning/mobilenet.test.js +89 -0
- package/dist/lib/utils/canvas.js +46 -0
- package/dist/lib/utils/data_manager.d.ts +2 -3
- package/dist/lib/utils/data_manager.js +72 -0
- package/dist/lib/utils/dataset.d.ts +1 -1
- package/dist/lib/utils/dataset.js +20 -0
- package/dist/lib/utils/tf.d.ts +1 -1
- package/dist/lib/utils/tf.js +135 -0
- package/lib/learning/mobilenet.ts +3 -3
- package/package.json +2 -3
- package/tsconfig.json +1 -0
- package/dist/index.bundle.js +0 -2
- package/dist/index.bundle.js.LICENSE.txt +0 -352
- package/dist/index.html +0 -1
|
@@ -0,0 +1,386 @@
|
|
|
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 () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
41
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
42
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
43
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
44
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
45
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
46
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
const tf = __importStar(require("@tensorflow/tfjs"));
|
|
51
|
+
const tfjs_backend_wasm_1 = require("@tensorflow/tfjs-backend-wasm");
|
|
52
|
+
const tfjs_1 = require("@tensorflow/tfjs");
|
|
53
|
+
const tf_1 = require("../utils/tf");
|
|
54
|
+
const canvas_1 = require("../utils/canvas");
|
|
55
|
+
const dataset_1 = require("../utils/dataset");
|
|
56
|
+
class LearningMobilenet {
|
|
57
|
+
constructor({ epochs = 50, batchSize = 16, limitSize = 2, learningRate = 0.001, validateRate = 0.15, } = {}) {
|
|
58
|
+
this.imageExamples = [];
|
|
59
|
+
this.classNumber = [];
|
|
60
|
+
this.MOBILE_NET_INPUT_WIDTH = 224;
|
|
61
|
+
this.MOBILE_NET_INPUT_HEIGHT = 224;
|
|
62
|
+
this.MOBILE_NET_INPUT_CHANNEL = 3;
|
|
63
|
+
this.IMAGE_NORMALIZATION_FACTOR = 255.0;
|
|
64
|
+
// 진행 상태를 나타내는 이벤트를 정의합니다.
|
|
65
|
+
this.onProgress = () => { };
|
|
66
|
+
this.onLoss = () => { };
|
|
67
|
+
this.onEvents = () => { };
|
|
68
|
+
this.onTrainBegin = () => { };
|
|
69
|
+
this.onTrainEnd = () => { };
|
|
70
|
+
this.onEpochEnd = () => { };
|
|
71
|
+
this.model = null;
|
|
72
|
+
this.epochs = epochs;
|
|
73
|
+
this.batchSize = batchSize;
|
|
74
|
+
this.learningRate = learningRate;
|
|
75
|
+
this.validateRate = validateRate;
|
|
76
|
+
this.isRunning = false;
|
|
77
|
+
this.isReady = false;
|
|
78
|
+
this.isTrainedDone = false;
|
|
79
|
+
this.limitSize = limitSize;
|
|
80
|
+
this.mobilenetModule = null;
|
|
81
|
+
this.classNumber = [];
|
|
82
|
+
}
|
|
83
|
+
//
|
|
84
|
+
// 기존의 모델 로드
|
|
85
|
+
load(_a) {
|
|
86
|
+
return __awaiter(this, arguments, void 0, function* ({ jsonURL, labels }) {
|
|
87
|
+
if (labels.length <= 0) {
|
|
88
|
+
return Promise.reject(new Error('Labels length is 0'));
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
this.model = yield tf.loadLayersModel(jsonURL);
|
|
92
|
+
for (var i = 0; i < labels.length; i++) {
|
|
93
|
+
this.registerClassNumber(labels[i]);
|
|
94
|
+
}
|
|
95
|
+
this.isReady = true;
|
|
96
|
+
this.model.summary();
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
console.error('Model load failed', error);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
registerClassNumber(value) {
|
|
105
|
+
// 중복 값의 인덱스를 찾습니다.
|
|
106
|
+
const existingIndex = this.classNumber.indexOf(value);
|
|
107
|
+
// 중복 값이 있다면 해당 인덱스를 반환합니다.
|
|
108
|
+
if (existingIndex !== -1) {
|
|
109
|
+
return existingIndex;
|
|
110
|
+
}
|
|
111
|
+
// 중복 값이 없다면 새로운 항목을 추가하고 그 인덱스를 반환합니다.
|
|
112
|
+
this.classNumber.push(value);
|
|
113
|
+
return this.classNumber.length - 1;
|
|
114
|
+
}
|
|
115
|
+
_convertToTfDataset() {
|
|
116
|
+
for (let i = 0; i < this.imageExamples.length; i++) {
|
|
117
|
+
this.imageExamples[i] = (0, dataset_1.fisherYates)(this.imageExamples[i]);
|
|
118
|
+
}
|
|
119
|
+
const trainDataset = [];
|
|
120
|
+
const validationDataset = [];
|
|
121
|
+
for (let i = 0; i < this.imageExamples.length; i++) {
|
|
122
|
+
const classLength = this.imageExamples[i].length;
|
|
123
|
+
// 클래스의 전체 데이터 수를 사용하여 학습 및 검증 데이터 수 계산
|
|
124
|
+
const numValidation = Math.ceil(this.validateRate * classLength);
|
|
125
|
+
const numTrain = classLength - numValidation;
|
|
126
|
+
// One-Hot 인코딩을 사용하여 라벨 생성
|
|
127
|
+
const y = (0, dataset_1.flatOneHot)(i, this.classNumber.length);
|
|
128
|
+
// numTrain과 numValidation에 따라 데이터를 학습 및 검증 데이터로 분할
|
|
129
|
+
const classTrain = this.imageExamples[i].slice(0, numTrain).map(dataArray => ({ data: dataArray, label: y }));
|
|
130
|
+
trainDataset.push(...classTrain);
|
|
131
|
+
const classValidation = this.imageExamples[i].slice(numTrain, numTrain + numValidation).map(dataArray => ({ data: dataArray, label: y }));
|
|
132
|
+
validationDataset.push(...classValidation);
|
|
133
|
+
}
|
|
134
|
+
// Shuffle entire datasets
|
|
135
|
+
const shuffledTrainDataset = (0, dataset_1.fisherYates)(trainDataset);
|
|
136
|
+
const shuffledValidationDataset = (0, dataset_1.fisherYates)(validationDataset);
|
|
137
|
+
// Convert to tf.data.Dataset
|
|
138
|
+
const trainX = tf.data.array(shuffledTrainDataset.map(sample => sample.data));
|
|
139
|
+
const validationX = tf.data.array(shuffledValidationDataset.map(sample => sample.data));
|
|
140
|
+
const trainY = tf.data.array(shuffledTrainDataset.map(sample => sample.label));
|
|
141
|
+
const validationY = tf.data.array(shuffledValidationDataset.map(sample => sample.label));
|
|
142
|
+
return {
|
|
143
|
+
trainDataset: tf.data.zip({ xs: trainX, ys: trainY }),
|
|
144
|
+
validationDataset: tf.data.zip({ xs: validationX, ys: validationY })
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// 학습 데이타 등록
|
|
148
|
+
addData(label, data) {
|
|
149
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
150
|
+
try {
|
|
151
|
+
if (this.mobilenetModule !== null) {
|
|
152
|
+
const cap = (0, tf_1.isTensor)(data) ? data : yield (0, tf_1.capture)(data, false);
|
|
153
|
+
const predict = tf.tidy(() => {
|
|
154
|
+
return this.mobilenetModule.predict(cap);
|
|
155
|
+
});
|
|
156
|
+
const activation = yield predict.data();
|
|
157
|
+
const classIndex = this.registerClassNumber(label);
|
|
158
|
+
if (!this.imageExamples[classIndex]) {
|
|
159
|
+
this.imageExamples[classIndex] = [];
|
|
160
|
+
}
|
|
161
|
+
this.imageExamples[classIndex].push(activation);
|
|
162
|
+
if (this.classNumber.length >= this.limitSize) {
|
|
163
|
+
this.isReady = true;
|
|
164
|
+
}
|
|
165
|
+
// Dispose of the prediction tensor to free memory
|
|
166
|
+
predict.dispose();
|
|
167
|
+
// If cap is not a tensor, we don't need to dispose it. Otherwise, we should.
|
|
168
|
+
if (!(0, tf_1.isTensor)(data)) {
|
|
169
|
+
cap.dispose();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
throw new Error('mobilenetModule is null');
|
|
174
|
+
}
|
|
175
|
+
return Promise.resolve();
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error('Model training failed', error);
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
init() {
|
|
184
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
185
|
+
try {
|
|
186
|
+
console.log('init call');
|
|
187
|
+
yield this.setupBackend();
|
|
188
|
+
this.mobilenetModule = yield (0, tf_1.loadModel)();
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
console.log('init Error', error);
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
setupBackend() {
|
|
197
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
198
|
+
const isWasmSupported = yield this.checkWasmSupport();
|
|
199
|
+
if (isWasmSupported) {
|
|
200
|
+
console.log('Backend is set to WebAssembly');
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
yield tf.setBackend('cpu');
|
|
204
|
+
yield tf.ready();
|
|
205
|
+
console.log('Backend is set to CPU');
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
checkWasmSupport() {
|
|
210
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
211
|
+
try {
|
|
212
|
+
const wasmVersion = tf.version['tfjs-backend-wasm'] || tf.version['tfjs-core'];
|
|
213
|
+
(0, tfjs_backend_wasm_1.setWasmPaths)(`https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-wasm@${wasmVersion}/dist/`);
|
|
214
|
+
yield tf.setBackend('wasm');
|
|
215
|
+
yield tf.ready();
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
console.warn('WASM backend is not supported in this environment.', error);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
// 모델 학습 처리
|
|
225
|
+
train() {
|
|
226
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
227
|
+
if (this.isRunning) {
|
|
228
|
+
return Promise.reject(new Error('Training is already in progress.'));
|
|
229
|
+
}
|
|
230
|
+
// 콜백 정의
|
|
231
|
+
const customCallback = {
|
|
232
|
+
onTrainBegin: (log) => {
|
|
233
|
+
this.isTrainedDone = false;
|
|
234
|
+
this.onTrainBegin(log);
|
|
235
|
+
},
|
|
236
|
+
onTrainEnd: (log) => {
|
|
237
|
+
this.isTrainedDone = true;
|
|
238
|
+
this.onTrainEnd(log);
|
|
239
|
+
this.isRunning = false;
|
|
240
|
+
},
|
|
241
|
+
onBatchBegin: (batch, logs) => {
|
|
242
|
+
//console.log(`Batch ${batch} is starting.`);
|
|
243
|
+
},
|
|
244
|
+
onBatchEnd: (batch, logs) => {
|
|
245
|
+
//console.log(`Batch ${batch} has ended.`);
|
|
246
|
+
},
|
|
247
|
+
onEpochBegin: (epoch, logs) => {
|
|
248
|
+
//console.log(`Epoch ${epoch+1} is starting.`, logs);
|
|
249
|
+
},
|
|
250
|
+
onEpochEnd: (epoch, logs) => {
|
|
251
|
+
this.onEpochEnd(epoch, logs);
|
|
252
|
+
this.onLoss(logs.loss);
|
|
253
|
+
this.onProgress(epoch + 1);
|
|
254
|
+
this.onEvents({
|
|
255
|
+
epoch: epoch,
|
|
256
|
+
logs: logs,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
try {
|
|
261
|
+
this.isRunning = true;
|
|
262
|
+
if (this.classNumber.length < this.limitSize) {
|
|
263
|
+
return Promise.reject(new Error('Please train Data need over 2 data length'));
|
|
264
|
+
}
|
|
265
|
+
const datasets = this._convertToTfDataset();
|
|
266
|
+
const trainData = datasets.trainDataset.batch(this.batchSize);
|
|
267
|
+
const validationData = datasets.validationDataset.batch(this.batchSize);
|
|
268
|
+
const optimizer = tf.train.adam(this.learningRate);
|
|
269
|
+
const trainModel = yield this._createModel(optimizer);
|
|
270
|
+
const jointModel = tf.sequential();
|
|
271
|
+
jointModel.add(this.mobilenetModule);
|
|
272
|
+
jointModel.add(trainModel);
|
|
273
|
+
this.model = jointModel;
|
|
274
|
+
const history = yield trainModel.fitDataset(trainData, {
|
|
275
|
+
epochs: this.epochs,
|
|
276
|
+
validationData: validationData,
|
|
277
|
+
callbacks: customCallback
|
|
278
|
+
});
|
|
279
|
+
optimizer.dispose();
|
|
280
|
+
return history;
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
this.isRunning = false;
|
|
284
|
+
console.error('Model training failed', error);
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
// 추론하기
|
|
290
|
+
infer(data) {
|
|
291
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
292
|
+
if (this.model === null) {
|
|
293
|
+
return Promise.reject(new Error('Model is Null'));
|
|
294
|
+
}
|
|
295
|
+
try {
|
|
296
|
+
const classProbabilities = new Map();
|
|
297
|
+
const croppedImage = (0, canvas_1.cropTo)(data, 224, false);
|
|
298
|
+
const captured = yield (0, tf_1.capture)(croppedImage, false);
|
|
299
|
+
const logits = tf.tidy(() => {
|
|
300
|
+
return this.model.predict(captured);
|
|
301
|
+
});
|
|
302
|
+
const values = yield logits.data();
|
|
303
|
+
const EPSILON = 1e-6; // 매우 작은 값을 표현하기 위한 엡실론
|
|
304
|
+
for (let i = 0; i < values.length; i++) {
|
|
305
|
+
let probability = Math.max(0, Math.min(1, values[i])); // 확률 값을 0과 1 사이로 조정
|
|
306
|
+
probability = probability < EPSILON ? 0 : probability; // 매우 작은 확률 값을 0으로 간주
|
|
307
|
+
const className = this.classNumber[i]; // 클래스 이름
|
|
308
|
+
const existingProbability = classProbabilities.get(className);
|
|
309
|
+
if (existingProbability !== undefined) {
|
|
310
|
+
classProbabilities.set(className, existingProbability + probability);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
classProbabilities.set(className, probability);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
console.log('classProbabilities', classProbabilities);
|
|
317
|
+
(0, tfjs_1.dispose)(logits);
|
|
318
|
+
return classProbabilities;
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
// 모델 저장
|
|
326
|
+
saveModel(handlerOrURL, config) {
|
|
327
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
328
|
+
var _a;
|
|
329
|
+
try {
|
|
330
|
+
console.log('saveModel try', this.isTrainedDone);
|
|
331
|
+
if (!this.isTrainedDone) {
|
|
332
|
+
return Promise.reject(new Error('Train is not done status'));
|
|
333
|
+
}
|
|
334
|
+
yield ((_a = this.model) === null || _a === void 0 ? void 0 : _a.save(handlerOrURL, config));
|
|
335
|
+
}
|
|
336
|
+
catch (e) {
|
|
337
|
+
console.log('saveModel Error', e);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
// 진행중 여부
|
|
342
|
+
running() {
|
|
343
|
+
return this.isRunning;
|
|
344
|
+
}
|
|
345
|
+
ready() {
|
|
346
|
+
return this.isReady;
|
|
347
|
+
}
|
|
348
|
+
// 모델 저장
|
|
349
|
+
_createModel(optimizer) {
|
|
350
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
351
|
+
try {
|
|
352
|
+
// 입력 이미지 크기에 맞게 모델 구조 수정
|
|
353
|
+
let varianceScaling;
|
|
354
|
+
varianceScaling = tf.initializers.varianceScaling({});
|
|
355
|
+
const trainModel = tf.sequential({
|
|
356
|
+
layers: [
|
|
357
|
+
tf.layers.dense({
|
|
358
|
+
inputShape: this.mobilenetModule.outputs[0].shape.slice(1),
|
|
359
|
+
units: 128,
|
|
360
|
+
activation: 'relu',
|
|
361
|
+
kernelInitializer: varianceScaling, // 'varianceScaling'
|
|
362
|
+
useBias: true
|
|
363
|
+
}),
|
|
364
|
+
tf.layers.dense({
|
|
365
|
+
kernelInitializer: varianceScaling, // 'varianceScaling'
|
|
366
|
+
useBias: false,
|
|
367
|
+
activation: 'softmax',
|
|
368
|
+
units: this.classNumber.length,
|
|
369
|
+
})
|
|
370
|
+
]
|
|
371
|
+
});
|
|
372
|
+
trainModel.compile({
|
|
373
|
+
loss: 'categoricalCrossentropy',
|
|
374
|
+
optimizer: optimizer,
|
|
375
|
+
metrics: ['accuracy']
|
|
376
|
+
});
|
|
377
|
+
return trainModel;
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
console.error('Failed to load model', error);
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
exports.default = LearningMobilenet;
|
|
@@ -0,0 +1,89 @@
|
|
|
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 () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const tf = __importStar(require("@tensorflow/tfjs"));
|
|
50
|
+
const mobilenet_1 = __importDefault(require("./mobilenet"));
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const { createCanvas, loadImage } = require('canvas');
|
|
53
|
+
let imageTensor1;
|
|
54
|
+
let imageTensor2;
|
|
55
|
+
// 이미지경로를 기준으로
|
|
56
|
+
function ImagePathToTensor(imagePath) {
|
|
57
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
58
|
+
const imageBuffer = fs.readFileSync(imagePath);
|
|
59
|
+
const image = yield loadImage(imageBuffer);
|
|
60
|
+
const canvas = createCanvas(image.width, image.height);
|
|
61
|
+
const ctx = canvas.getContext('2d');
|
|
62
|
+
ctx.drawImage(image, 0, 0);
|
|
63
|
+
const imageData = ctx.getImageData(0, 0, image.width, image.height);
|
|
64
|
+
return tf.tensor3d(imageData.data, [imageData.height, imageData.width, 4], 'int32');
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
describe('LearningMobilenetImage', () => {
|
|
68
|
+
const learning = new mobilenet_1.default({});
|
|
69
|
+
const image1Path = path.join(__dirname, '../../public/images/image1.jpeg');
|
|
70
|
+
const image2Path = path.join(__dirname, '../../public/images/image2.jpeg');
|
|
71
|
+
console.log('Resolved path for image1:', image1Path);
|
|
72
|
+
beforeAll(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
73
|
+
learning.init();
|
|
74
|
+
imageTensor1 = yield ImagePathToTensor(image1Path);
|
|
75
|
+
imageTensor2 = yield ImagePathToTensor(image2Path);
|
|
76
|
+
}));
|
|
77
|
+
it('loads an image and converts it to a tensor', () => {
|
|
78
|
+
expect(imageTensor1).toBeDefined();
|
|
79
|
+
expect(imageTensor1 instanceof tf.Tensor).toBe(true);
|
|
80
|
+
expect(imageTensor2).toBeDefined();
|
|
81
|
+
expect(imageTensor2 instanceof tf.Tensor).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
// test('mobilenet add data', () => {
|
|
84
|
+
// learning.addData("라벨1", imageTensor1);
|
|
85
|
+
// learning.addData("라벨1", imageTensor1);
|
|
86
|
+
// learning.addData("라벨2", imageTensor2);
|
|
87
|
+
// learning.addData("라벨2", imageTensor2);
|
|
88
|
+
// });
|
|
89
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cropTo = cropTo;
|
|
4
|
+
const newCanvas = () => document.createElement('canvas');
|
|
5
|
+
function cropTo(image, size, flipped = false, canvas = newCanvas()) {
|
|
6
|
+
let width;
|
|
7
|
+
let height;
|
|
8
|
+
// If ImageData
|
|
9
|
+
if (image instanceof ImageData) {
|
|
10
|
+
width = image.width;
|
|
11
|
+
height = image.height;
|
|
12
|
+
}
|
|
13
|
+
// If image, bitmap, or canvas
|
|
14
|
+
else if (image instanceof HTMLImageElement || image instanceof HTMLCanvasElement) {
|
|
15
|
+
width = image.width;
|
|
16
|
+
height = image.height;
|
|
17
|
+
}
|
|
18
|
+
// If video element
|
|
19
|
+
else if (image instanceof HTMLVideoElement) {
|
|
20
|
+
width = image.videoWidth;
|
|
21
|
+
height = image.videoHeight;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
throw new Error("Unsupported Drawable type");
|
|
25
|
+
}
|
|
26
|
+
const min = Math.min(width, height);
|
|
27
|
+
const scale = size / min;
|
|
28
|
+
const scaledW = Math.ceil(width * scale);
|
|
29
|
+
const scaledH = Math.ceil(height * scale);
|
|
30
|
+
const dx = scaledW - size;
|
|
31
|
+
const dy = scaledH - size;
|
|
32
|
+
canvas.width = canvas.height = size;
|
|
33
|
+
const ctx = canvas.getContext('2d');
|
|
34
|
+
// Handle ImageData separately
|
|
35
|
+
if (image instanceof ImageData) {
|
|
36
|
+
ctx.putImageData(image, ~~(dx / 2) * -1, ~~(dy / 2) * -1); // Adjust this if needed
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
ctx.drawImage(image, ~~(dx / 2) * -1, ~~(dy / 2) * -1, scaledW, scaledH);
|
|
40
|
+
}
|
|
41
|
+
if (flipped) {
|
|
42
|
+
ctx.scale(-1, 1);
|
|
43
|
+
ctx.drawImage(canvas, size * -1, 0);
|
|
44
|
+
}
|
|
45
|
+
return canvas;
|
|
46
|
+
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import * as tf from '@tensorflow/tfjs';
|
|
2
1
|
declare class DataManager {
|
|
3
2
|
private labelMap;
|
|
4
3
|
private labelIndex;
|
|
5
4
|
private data;
|
|
6
5
|
addData(label: string, values: number[]): Promise<void>;
|
|
7
6
|
convertToTensors(): {
|
|
8
|
-
xs:
|
|
9
|
-
ys:
|
|
7
|
+
xs: any;
|
|
8
|
+
ys: any;
|
|
10
9
|
};
|
|
11
10
|
getLabelMap(): {
|
|
12
11
|
[key: string]: number;
|
|
@@ -0,0 +1,72 @@
|
|
|
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 () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
const tf = __importStar(require("@tensorflow/tfjs"));
|
|
46
|
+
class DataManager {
|
|
47
|
+
constructor() {
|
|
48
|
+
this.labelMap = {};
|
|
49
|
+
this.labelIndex = 0;
|
|
50
|
+
this.data = [];
|
|
51
|
+
}
|
|
52
|
+
addData(label, values) {
|
|
53
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
if (!(label in this.labelMap)) {
|
|
55
|
+
this.labelMap[label] = this.labelIndex++;
|
|
56
|
+
}
|
|
57
|
+
const numericLabel = this.labelMap[label];
|
|
58
|
+
this.data.push({ label: numericLabel, values });
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
convertToTensors() {
|
|
62
|
+
const xs = this.data.map(d => d.values);
|
|
63
|
+
const ys = this.data.map(d => d.label);
|
|
64
|
+
const xsTensor = tf.tensor2d(xs, [xs.length, xs[0].length], 'float32');
|
|
65
|
+
const ysTensor = tf.tensor1d(ys, 'float32');
|
|
66
|
+
return { xs: xsTensor, ys: ysTensor };
|
|
67
|
+
}
|
|
68
|
+
getLabelMap() {
|
|
69
|
+
return this.labelMap;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
exports.default = DataManager;
|
|
@@ -3,4 +3,4 @@ export interface Sample {
|
|
|
3
3
|
label: number[];
|
|
4
4
|
}
|
|
5
5
|
export declare function flatOneHot(label: number, numClasses: number): number[];
|
|
6
|
-
export declare function fisherYates(array: Float32Array[] | Sample[]): Float32Array[] | Sample[];
|
|
6
|
+
export declare function fisherYates(array: Float32Array[] | Sample[]): Float32Array<ArrayBuffer>[] | Sample[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.flatOneHot = flatOneHot;
|
|
4
|
+
exports.fisherYates = fisherYates;
|
|
5
|
+
function flatOneHot(label, numClasses) {
|
|
6
|
+
const labelOneHot = new Array(numClasses).fill(0);
|
|
7
|
+
labelOneHot[label] = 1;
|
|
8
|
+
return labelOneHot;
|
|
9
|
+
}
|
|
10
|
+
function fisherYates(array) {
|
|
11
|
+
const length = array.length;
|
|
12
|
+
// need to clone array or we'd be editing original as we goo
|
|
13
|
+
const shuffled = array.slice();
|
|
14
|
+
for (let i = (length - 1); i > 0; i -= 1) {
|
|
15
|
+
let randomIndex;
|
|
16
|
+
randomIndex = Math.floor(Math.random() * (i + 1));
|
|
17
|
+
[shuffled[i], shuffled[randomIndex]] = [shuffled[randomIndex], shuffled[i]];
|
|
18
|
+
}
|
|
19
|
+
return shuffled;
|
|
20
|
+
}
|
package/dist/lib/utils/tf.d.ts
CHANGED
|
@@ -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): Promise<
|
|
6
|
+
export declare function capture(rasterElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement, grayscale?: boolean): Promise<any>;
|
|
7
7
|
export declare function cropTensor(img: tf.Tensor3D, grayscaleModel?: boolean, grayscaleInput?: boolean): tf.Tensor3D;
|