learning_model 1.0.38 → 1.0.40
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 +5 -0
- package/dist/index.bundle.js +1 -1
- package/dist/learning/base.d.ts +0 -1
- package/dist/learning/mobilenet.d.ts +5 -9
- package/dist/learning/mobilenet.js +120 -136
- package/dist/lib/learning/base.d.ts +0 -1
- package/dist/lib/learning/mobilenet.d.ts +5 -9
- package/dist/lib/utils/canvas.d.ts +3 -0
- package/dist/lib/utils/dataset.d.ts +6 -0
- package/dist/lib/utils/tf.d.ts +7 -0
- package/dist/utils/canvas.d.ts +3 -0
- package/dist/utils/canvas.js +47 -0
- package/dist/utils/dataset.d.ts +6 -0
- package/dist/utils/dataset.js +21 -0
- package/dist/utils/tf.d.ts +7 -0
- package/dist/utils/tf.js +124 -0
- package/lib/learning/base.ts +0 -2
- package/lib/learning/mobilenet.ts +150 -163
- package/lib/utils/canvas.ts +49 -0
- package/lib/utils/dataset.ts +24 -0
- package/lib/utils/tf.ts +94 -0
- package/package.json +1 -1
- package/dist/learning/util.d.ts +0 -2
- package/dist/learning/util.js +0 -40
- package/dist/lib/learning/util.d.ts +0 -2
- package/lib/learning/util.ts +0 -16
package/dist/learning/base.d.ts
CHANGED
|
@@ -7,21 +7,18 @@ declare class LearningMobilenet implements LearningInterface {
|
|
|
7
7
|
batchSize: number;
|
|
8
8
|
learningRate: number;
|
|
9
9
|
validateRate: number;
|
|
10
|
-
labels: string[];
|
|
11
|
-
modelURL: string;
|
|
12
10
|
isRunning: boolean;
|
|
13
11
|
isReady: boolean;
|
|
14
12
|
isTrainedDone: boolean;
|
|
15
13
|
limitSize: number;
|
|
16
14
|
mobilenetModule: tf.LayersModel | null;
|
|
17
|
-
|
|
15
|
+
imageExamples: Float32Array[][];
|
|
16
|
+
classNumber: string[];
|
|
18
17
|
readonly MOBILE_NET_INPUT_WIDTH = 224;
|
|
19
18
|
readonly MOBILE_NET_INPUT_HEIGHT = 224;
|
|
20
19
|
readonly MOBILE_NET_INPUT_CHANNEL = 3;
|
|
21
20
|
readonly IMAGE_NORMALIZATION_FACTOR = 255;
|
|
22
|
-
constructor({
|
|
23
|
-
epochs, batchSize, limitSize, learningRate, validateRate, }?: {
|
|
24
|
-
modelURL?: string;
|
|
21
|
+
constructor({ epochs, batchSize, limitSize, learningRate, validateRate, }?: {
|
|
25
22
|
epochs?: number;
|
|
26
23
|
batchSize?: number;
|
|
27
24
|
limitSize?: number;
|
|
@@ -38,6 +35,8 @@ declare class LearningMobilenet implements LearningInterface {
|
|
|
38
35
|
jsonURL: string;
|
|
39
36
|
labels: Array<string>;
|
|
40
37
|
}): Promise<void>;
|
|
38
|
+
private registerClassNumber;
|
|
39
|
+
private _convertToTfDataset;
|
|
41
40
|
addData(label: string, data: any): Promise<void>;
|
|
42
41
|
init(): Promise<void>;
|
|
43
42
|
train(): Promise<tf.History>;
|
|
@@ -45,9 +44,6 @@ declare class LearningMobilenet implements LearningInterface {
|
|
|
45
44
|
saveModel(handlerOrURL: io.IOHandler | string, config?: io.SaveConfig): Promise<void>;
|
|
46
45
|
running(): boolean;
|
|
47
46
|
ready(): boolean;
|
|
48
|
-
private _preprocessedTargetData;
|
|
49
|
-
private _preprocessedInputData;
|
|
50
|
-
private loadModel;
|
|
51
47
|
private _createModel;
|
|
52
48
|
}
|
|
53
49
|
export default LearningMobilenet;
|
|
@@ -38,10 +38,14 @@ 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
|
+
const tfjs_1 = require("@tensorflow/tfjs");
|
|
42
|
+
const tf_1 = require("../utils/tf");
|
|
43
|
+
const canvas_1 = require("../utils/canvas");
|
|
44
|
+
const dataset_1 = require("../utils/dataset");
|
|
41
45
|
class LearningMobilenet {
|
|
42
|
-
constructor({
|
|
43
|
-
|
|
44
|
-
this.
|
|
46
|
+
constructor({ epochs = 50, batchSize = 16, limitSize = 2, learningRate = 0.001, validateRate = 0.15, } = {}) {
|
|
47
|
+
this.imageExamples = [];
|
|
48
|
+
this.classNumber = [];
|
|
45
49
|
this.MOBILE_NET_INPUT_WIDTH = 224;
|
|
46
50
|
this.MOBILE_NET_INPUT_HEIGHT = 224;
|
|
47
51
|
this.MOBILE_NET_INPUT_CHANNEL = 3;
|
|
@@ -58,14 +62,12 @@ class LearningMobilenet {
|
|
|
58
62
|
this.batchSize = batchSize;
|
|
59
63
|
this.learningRate = learningRate;
|
|
60
64
|
this.validateRate = validateRate;
|
|
61
|
-
this.labels = [];
|
|
62
|
-
this.modelURL = modelURL;
|
|
63
65
|
this.isRunning = false;
|
|
64
66
|
this.isReady = false;
|
|
65
67
|
this.isTrainedDone = false;
|
|
66
68
|
this.limitSize = limitSize;
|
|
67
69
|
this.mobilenetModule = null;
|
|
68
|
-
this.
|
|
70
|
+
this.classNumber = [];
|
|
69
71
|
}
|
|
70
72
|
//
|
|
71
73
|
// 기존의 모델 로드
|
|
@@ -76,7 +78,9 @@ class LearningMobilenet {
|
|
|
76
78
|
}
|
|
77
79
|
try {
|
|
78
80
|
this.model = yield tf.loadLayersModel(jsonURL);
|
|
79
|
-
|
|
81
|
+
for (var i = 0; i < labels.length; i++) {
|
|
82
|
+
this.registerClassNumber(labels[i]);
|
|
83
|
+
}
|
|
80
84
|
this.isReady = true;
|
|
81
85
|
this.model.summary();
|
|
82
86
|
}
|
|
@@ -86,33 +90,69 @@ class LearningMobilenet {
|
|
|
86
90
|
}
|
|
87
91
|
});
|
|
88
92
|
}
|
|
93
|
+
registerClassNumber(value) {
|
|
94
|
+
// 중복 값의 인덱스를 찾습니다.
|
|
95
|
+
const existingIndex = this.classNumber.indexOf(value);
|
|
96
|
+
// 중복 값이 있다면 해당 인덱스를 반환합니다.
|
|
97
|
+
if (existingIndex !== -1) {
|
|
98
|
+
return existingIndex;
|
|
99
|
+
}
|
|
100
|
+
// 중복 값이 없다면 새로운 항목을 추가하고 그 인덱스를 반환합니다.
|
|
101
|
+
this.classNumber.push(value);
|
|
102
|
+
return this.classNumber.length - 1;
|
|
103
|
+
}
|
|
104
|
+
_convertToTfDataset() {
|
|
105
|
+
for (let i = 0; i < this.imageExamples.length; i++) {
|
|
106
|
+
this.imageExamples[i] = (0, dataset_1.fisherYates)(this.imageExamples[i]);
|
|
107
|
+
}
|
|
108
|
+
const trainDataset = [];
|
|
109
|
+
const validationDataset = [];
|
|
110
|
+
for (let i = 0; i < this.imageExamples.length; i++) {
|
|
111
|
+
const classLength = this.imageExamples[i].length;
|
|
112
|
+
// 클래스의 전체 데이터 수를 사용하여 학습 및 검증 데이터 수 계산
|
|
113
|
+
const numValidation = Math.ceil(this.validateRate * classLength);
|
|
114
|
+
const numTrain = classLength - numValidation;
|
|
115
|
+
// One-Hot 인코딩을 사용하여 라벨 생성
|
|
116
|
+
const y = (0, dataset_1.flatOneHot)(i, this.classNumber.length);
|
|
117
|
+
// numTrain과 numValidation에 따라 데이터를 학습 및 검증 데이터로 분할
|
|
118
|
+
const classTrain = this.imageExamples[i].slice(0, numTrain).map(dataArray => ({ data: dataArray, label: y }));
|
|
119
|
+
trainDataset.push(...classTrain);
|
|
120
|
+
const classValidation = this.imageExamples[i].slice(numTrain, numTrain + numValidation).map(dataArray => ({ data: dataArray, label: y }));
|
|
121
|
+
validationDataset.push(...classValidation);
|
|
122
|
+
}
|
|
123
|
+
// Shuffle entire datasets
|
|
124
|
+
const shuffledTrainDataset = (0, dataset_1.fisherYates)(trainDataset);
|
|
125
|
+
const shuffledValidationDataset = (0, dataset_1.fisherYates)(validationDataset);
|
|
126
|
+
// Convert to tf.data.Dataset
|
|
127
|
+
const trainX = tf.data.array(shuffledTrainDataset.map(sample => sample.data));
|
|
128
|
+
const validationX = tf.data.array(shuffledValidationDataset.map(sample => sample.data));
|
|
129
|
+
const trainY = tf.data.array(shuffledTrainDataset.map(sample => sample.label));
|
|
130
|
+
const validationY = tf.data.array(shuffledValidationDataset.map(sample => sample.label));
|
|
131
|
+
return {
|
|
132
|
+
trainDataset: tf.data.zip({ xs: trainX, ys: trainY }),
|
|
133
|
+
validationDataset: tf.data.zip({ xs: validationX, ys: validationY })
|
|
134
|
+
};
|
|
135
|
+
}
|
|
89
136
|
// 학습 데이타 등록
|
|
90
137
|
addData(label, data) {
|
|
91
138
|
return __awaiter(this, void 0, void 0, function* () {
|
|
92
139
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const expandedTensor = normalizedTensor.expandDims(0);
|
|
102
|
-
console.log('predict extend', expandedTensor);
|
|
103
|
-
const predict = this.mobilenetModule.predict(expandedTensor);
|
|
104
|
-
console.log('predict', predict);
|
|
105
|
-
return predict;
|
|
140
|
+
if (this.mobilenetModule !== null) {
|
|
141
|
+
const croppedImage = (0, canvas_1.cropTo)(data, 224, false);
|
|
142
|
+
const cap = (0, tf_1.isTensor)(data) ? data : (0, tf_1.capture)(croppedImage, false);
|
|
143
|
+
const predict = this.mobilenetModule.predict(cap);
|
|
144
|
+
const activation = predict.dataSync();
|
|
145
|
+
const classIndex = this.registerClassNumber(label);
|
|
146
|
+
if (!this.imageExamples[classIndex]) {
|
|
147
|
+
this.imageExamples[classIndex] = [];
|
|
106
148
|
}
|
|
107
|
-
|
|
108
|
-
|
|
149
|
+
this.imageExamples[classIndex].push(activation);
|
|
150
|
+
if (this.classNumber.length >= this.limitSize) {
|
|
151
|
+
this.isReady = true;
|
|
109
152
|
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
this.labels.push(label);
|
|
114
|
-
if (this.labels.length >= this.limitSize) {
|
|
115
|
-
this.isReady = true;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
throw new Error('mobilenetModule is null');
|
|
116
156
|
}
|
|
117
157
|
return Promise.resolve();
|
|
118
158
|
}
|
|
@@ -125,7 +165,7 @@ class LearningMobilenet {
|
|
|
125
165
|
init() {
|
|
126
166
|
return __awaiter(this, void 0, void 0, function* () {
|
|
127
167
|
try {
|
|
128
|
-
this.mobilenetModule = yield
|
|
168
|
+
this.mobilenetModule = yield (0, tf_1.loadModel)();
|
|
129
169
|
}
|
|
130
170
|
catch (error) {
|
|
131
171
|
console.log('init Error', error);
|
|
@@ -144,27 +184,23 @@ class LearningMobilenet {
|
|
|
144
184
|
onTrainBegin: (log) => {
|
|
145
185
|
this.isTrainedDone = false;
|
|
146
186
|
this.onTrainBegin(log);
|
|
147
|
-
console.log('Training has started.');
|
|
148
187
|
},
|
|
149
188
|
onTrainEnd: (log) => {
|
|
150
189
|
this.isTrainedDone = true;
|
|
151
190
|
this.onTrainEnd(log);
|
|
152
|
-
console.log('Training has ended.');
|
|
153
191
|
this.isRunning = false;
|
|
154
192
|
},
|
|
155
193
|
onBatchBegin: (batch, logs) => {
|
|
156
|
-
console.log(`Batch ${batch} is starting.`);
|
|
194
|
+
//console.log(`Batch ${batch} is starting.`);
|
|
157
195
|
},
|
|
158
196
|
onBatchEnd: (batch, logs) => {
|
|
159
|
-
console.log(`Batch ${batch} has ended.`);
|
|
197
|
+
//console.log(`Batch ${batch} has ended.`);
|
|
160
198
|
},
|
|
161
199
|
onEpochBegin: (epoch, logs) => {
|
|
162
|
-
console.log(`Epoch ${epoch
|
|
200
|
+
//console.log(`Epoch ${epoch+1} is starting.`, logs);
|
|
163
201
|
},
|
|
164
202
|
onEpochEnd: (epoch, logs) => {
|
|
165
203
|
this.onEpochEnd(epoch, logs);
|
|
166
|
-
console.log(`Epoch ${epoch + 1} has ended.`);
|
|
167
|
-
console.log('Loss:', logs);
|
|
168
204
|
this.onLoss(logs.loss);
|
|
169
205
|
this.onProgress(epoch + 1);
|
|
170
206
|
this.onEvents({
|
|
@@ -175,21 +211,24 @@ class LearningMobilenet {
|
|
|
175
211
|
};
|
|
176
212
|
try {
|
|
177
213
|
this.isRunning = true;
|
|
178
|
-
if (this.
|
|
214
|
+
if (this.classNumber.length < this.limitSize) {
|
|
179
215
|
return Promise.reject(new Error('Please train Data need over 2 data length'));
|
|
180
216
|
}
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
const history = yield
|
|
217
|
+
const datasets = this._convertToTfDataset();
|
|
218
|
+
const trainData = datasets.trainDataset.batch(this.batchSize);
|
|
219
|
+
const validationData = datasets.validationDataset.batch(this.batchSize);
|
|
220
|
+
const optimizer = tf.train.adam(this.learningRate);
|
|
221
|
+
const trainModel = yield this._createModel(optimizer);
|
|
222
|
+
const history = yield trainModel.fitDataset(trainData, {
|
|
187
223
|
epochs: this.epochs,
|
|
188
|
-
|
|
189
|
-
validationSplit: this.validateRate,
|
|
224
|
+
validationData: validationData,
|
|
190
225
|
callbacks: customCallback
|
|
191
226
|
});
|
|
192
|
-
|
|
227
|
+
const jointModel = tf.sequential();
|
|
228
|
+
jointModel.add(this.mobilenetModule);
|
|
229
|
+
jointModel.add(trainModel);
|
|
230
|
+
this.model = jointModel;
|
|
231
|
+
optimizer.dispose();
|
|
193
232
|
return history;
|
|
194
233
|
}
|
|
195
234
|
catch (error) {
|
|
@@ -206,36 +245,18 @@ class LearningMobilenet {
|
|
|
206
245
|
return Promise.reject(new Error('Model is Null'));
|
|
207
246
|
}
|
|
208
247
|
try {
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const resizedTensor = tf.image.resizeBilinear(t, [224, 224]);
|
|
215
|
-
const normalizedTensor = resizedTensor.div(255.0);
|
|
216
|
-
// Add an extra dimension to the tensor
|
|
217
|
-
const expandedTensor = normalizedTensor.expandDims(0);
|
|
218
|
-
console.log('predict extend', expandedTensor);
|
|
219
|
-
const predict = this.mobilenetModule.predict(expandedTensor);
|
|
220
|
-
console.log('predict', predict);
|
|
221
|
-
return predict;
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
throw new Error('mobilenetModule is null');
|
|
225
|
-
}
|
|
248
|
+
const classProbabilities = new Map();
|
|
249
|
+
const croppedImage = (0, canvas_1.cropTo)(data, 224, false);
|
|
250
|
+
const logits = tf.tidy(() => {
|
|
251
|
+
const captured = (0, tf_1.capture)(croppedImage, false);
|
|
252
|
+
return this.model.predict(captured);
|
|
226
253
|
});
|
|
227
|
-
const
|
|
228
|
-
const predictedClass = predictions.as1D().argMax();
|
|
229
|
-
const classId = (yield predictedClass.data())[0];
|
|
230
|
-
console.log('classId', classId, this.labels[classId]);
|
|
231
|
-
const predictionsData = yield predictions.data(); // 예측 텐서의 데이터를 비동기로 가져옴
|
|
232
|
-
const classProbabilities = new Map(); // 클래스별 확률 누적값을 저장할 맵
|
|
233
|
-
console.log('predictionsData', predictionsData);
|
|
254
|
+
const values = yield logits.data();
|
|
234
255
|
const EPSILON = 1e-6; // 매우 작은 값을 표현하기 위한 엡실론
|
|
235
|
-
for (let i = 0; i <
|
|
236
|
-
let probability = Math.max(0, Math.min(1,
|
|
256
|
+
for (let i = 0; i < values.length; i++) {
|
|
257
|
+
let probability = Math.max(0, Math.min(1, values[i])); // 확률 값을 0과 1 사이로 조정
|
|
237
258
|
probability = probability < EPSILON ? 0 : probability; // 매우 작은 확률 값을 0으로 간주
|
|
238
|
-
const className = this.
|
|
259
|
+
const className = this.classNumber[i]; // 클래스 이름
|
|
239
260
|
const existingProbability = classProbabilities.get(className);
|
|
240
261
|
if (existingProbability !== undefined) {
|
|
241
262
|
classProbabilities.set(className, existingProbability + probability);
|
|
@@ -244,9 +265,8 @@ class LearningMobilenet {
|
|
|
244
265
|
classProbabilities.set(className, probability);
|
|
245
266
|
}
|
|
246
267
|
}
|
|
247
|
-
console.log('
|
|
248
|
-
|
|
249
|
-
predictions.dispose();
|
|
268
|
+
console.log('classProbabilities', classProbabilities);
|
|
269
|
+
(0, tfjs_1.dispose)(logits);
|
|
250
270
|
return classProbabilities;
|
|
251
271
|
}
|
|
252
272
|
catch (error) {
|
|
@@ -258,7 +278,6 @@ class LearningMobilenet {
|
|
|
258
278
|
saveModel(handlerOrURL, config) {
|
|
259
279
|
var _a;
|
|
260
280
|
return __awaiter(this, void 0, void 0, function* () {
|
|
261
|
-
console.log('saved model');
|
|
262
281
|
if (!this.isTrainedDone) {
|
|
263
282
|
return Promise.reject(new Error('Train is not done status'));
|
|
264
283
|
}
|
|
@@ -272,71 +291,36 @@ class LearningMobilenet {
|
|
|
272
291
|
ready() {
|
|
273
292
|
return this.isReady;
|
|
274
293
|
}
|
|
275
|
-
// target 라벨 데이타
|
|
276
|
-
_preprocessedTargetData() {
|
|
277
|
-
// 라벨 unique 처리 & 배열 리턴
|
|
278
|
-
console.log('uniqueLabels.length', this.labels, this.labels.length);
|
|
279
|
-
const labelIndices = this.labels.map((label) => this.labels.indexOf(label));
|
|
280
|
-
console.log('labelIndices', labelIndices);
|
|
281
|
-
const oneHotEncode = tf.oneHot(tf.tensor1d(labelIndices, 'int32'), this.labels.length);
|
|
282
|
-
console.log('oneHotEncode', oneHotEncode);
|
|
283
|
-
return oneHotEncode;
|
|
284
|
-
}
|
|
285
|
-
// 입력 이미지 데이타
|
|
286
|
-
_preprocessedInputData(model) {
|
|
287
|
-
// 이미지 배열을 배치로 변환 - [null, 224, 224, 3]
|
|
288
|
-
const inputShape = model.inputs[0].shape;
|
|
289
|
-
console.log('inputShape', inputShape);
|
|
290
|
-
// inputShape를 이와 같이 포멧 맞춘다. for reshape to [224, 224, 3]
|
|
291
|
-
const inputShapeArray = inputShape.slice(1);
|
|
292
|
-
console.log('inputShapeArray', inputShapeArray);
|
|
293
|
-
const inputBatch = tf.stack(this.imageTensors.map((image) => {
|
|
294
|
-
return tf.reshape(image, inputShapeArray);
|
|
295
|
-
}));
|
|
296
|
-
return inputBatch;
|
|
297
|
-
}
|
|
298
|
-
loadModel() {
|
|
299
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
300
|
-
const load_model = yield tf.loadLayersModel(this.modelURL);
|
|
301
|
-
load_model.summary();
|
|
302
|
-
const layer = load_model.getLayer('conv_pw_13_relu');
|
|
303
|
-
const truncatedModel = tf.model({
|
|
304
|
-
inputs: load_model.inputs,
|
|
305
|
-
outputs: layer.output
|
|
306
|
-
});
|
|
307
|
-
return truncatedModel;
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
294
|
// 모델 저장
|
|
311
|
-
_createModel(
|
|
295
|
+
_createModel(optimizer) {
|
|
312
296
|
return __awaiter(this, void 0, void 0, function* () {
|
|
313
297
|
try {
|
|
314
|
-
const truncatedModel = yield this.loadModel();
|
|
315
298
|
// 입력 이미지 크기에 맞게 모델 구조 수정
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
299
|
+
let varianceScaling;
|
|
300
|
+
varianceScaling = tf.initializers.varianceScaling({});
|
|
301
|
+
const trainModel = tf.sequential({
|
|
302
|
+
layers: [
|
|
303
|
+
tf.layers.dense({
|
|
304
|
+
inputShape: this.mobilenetModule.outputs[0].shape.slice(1),
|
|
305
|
+
units: 128,
|
|
306
|
+
activation: 'relu',
|
|
307
|
+
kernelInitializer: varianceScaling,
|
|
308
|
+
useBias: true
|
|
309
|
+
}),
|
|
310
|
+
tf.layers.dense({
|
|
311
|
+
kernelInitializer: varianceScaling,
|
|
312
|
+
useBias: false,
|
|
313
|
+
activation: 'softmax',
|
|
314
|
+
units: this.classNumber.length,
|
|
315
|
+
})
|
|
316
|
+
]
|
|
317
|
+
});
|
|
318
|
+
trainModel.compile({
|
|
319
|
+
loss: 'categoricalCrossentropy',
|
|
335
320
|
optimizer: optimizer,
|
|
336
|
-
metrics: ['accuracy'
|
|
321
|
+
metrics: ['accuracy']
|
|
337
322
|
});
|
|
338
|
-
|
|
339
|
-
return model;
|
|
323
|
+
return trainModel;
|
|
340
324
|
}
|
|
341
325
|
catch (error) {
|
|
342
326
|
console.error('Failed to load model', error);
|
|
@@ -7,21 +7,18 @@ declare class LearningMobilenet implements LearningInterface {
|
|
|
7
7
|
batchSize: number;
|
|
8
8
|
learningRate: number;
|
|
9
9
|
validateRate: number;
|
|
10
|
-
labels: string[];
|
|
11
|
-
modelURL: string;
|
|
12
10
|
isRunning: boolean;
|
|
13
11
|
isReady: boolean;
|
|
14
12
|
isTrainedDone: boolean;
|
|
15
13
|
limitSize: number;
|
|
16
14
|
mobilenetModule: tf.LayersModel | null;
|
|
17
|
-
|
|
15
|
+
imageExamples: Float32Array[][];
|
|
16
|
+
classNumber: string[];
|
|
18
17
|
readonly MOBILE_NET_INPUT_WIDTH = 224;
|
|
19
18
|
readonly MOBILE_NET_INPUT_HEIGHT = 224;
|
|
20
19
|
readonly MOBILE_NET_INPUT_CHANNEL = 3;
|
|
21
20
|
readonly IMAGE_NORMALIZATION_FACTOR = 255;
|
|
22
|
-
constructor({
|
|
23
|
-
epochs, batchSize, limitSize, learningRate, validateRate, }?: {
|
|
24
|
-
modelURL?: string;
|
|
21
|
+
constructor({ epochs, batchSize, limitSize, learningRate, validateRate, }?: {
|
|
25
22
|
epochs?: number;
|
|
26
23
|
batchSize?: number;
|
|
27
24
|
limitSize?: number;
|
|
@@ -38,6 +35,8 @@ declare class LearningMobilenet implements LearningInterface {
|
|
|
38
35
|
jsonURL: string;
|
|
39
36
|
labels: Array<string>;
|
|
40
37
|
}): Promise<void>;
|
|
38
|
+
private registerClassNumber;
|
|
39
|
+
private _convertToTfDataset;
|
|
41
40
|
addData(label: string, data: any): Promise<void>;
|
|
42
41
|
init(): Promise<void>;
|
|
43
42
|
train(): Promise<tf.History>;
|
|
@@ -45,9 +44,6 @@ declare class LearningMobilenet implements LearningInterface {
|
|
|
45
44
|
saveModel(handlerOrURL: io.IOHandler | string, config?: io.SaveConfig): Promise<void>;
|
|
46
45
|
running(): boolean;
|
|
47
46
|
ready(): boolean;
|
|
48
|
-
private _preprocessedTargetData;
|
|
49
|
-
private _preprocessedInputData;
|
|
50
|
-
private loadModel;
|
|
51
47
|
private _createModel;
|
|
52
48
|
}
|
|
53
49
|
export default LearningMobilenet;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as tf from '@tensorflow/tfjs';
|
|
2
|
+
export declare function isTensor(c: any): c is tf.Tensor;
|
|
3
|
+
export declare function loadModel(): Promise<tf.LayersModel>;
|
|
4
|
+
export declare function mobileNetURL(version: number): string;
|
|
5
|
+
export declare function imageToTensor(data: any): tf.Tensor3D;
|
|
6
|
+
export declare function capture(rasterElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement, grayscale?: boolean): tf.Tensor<tf.Rank>;
|
|
7
|
+
export declare function cropTensor(img: tf.Tensor3D, grayscaleModel?: boolean, grayscaleInput?: boolean): tf.Tensor3D;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cropTo = void 0;
|
|
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
|
+
}
|
|
47
|
+
exports.cropTo = cropTo;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fisherYates = exports.flatOneHot = void 0;
|
|
4
|
+
function flatOneHot(label, numClasses) {
|
|
5
|
+
const labelOneHot = new Array(numClasses).fill(0);
|
|
6
|
+
labelOneHot[label] = 1;
|
|
7
|
+
return labelOneHot;
|
|
8
|
+
}
|
|
9
|
+
exports.flatOneHot = flatOneHot;
|
|
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
|
+
}
|
|
21
|
+
exports.fisherYates = fisherYates;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import * as tf from '@tensorflow/tfjs';
|
|
2
|
+
export declare function isTensor(c: any): c is tf.Tensor;
|
|
3
|
+
export declare function loadModel(): Promise<tf.LayersModel>;
|
|
4
|
+
export declare function mobileNetURL(version: number): string;
|
|
5
|
+
export declare function imageToTensor(data: any): tf.Tensor3D;
|
|
6
|
+
export declare function capture(rasterElement: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement, grayscale?: boolean): tf.Tensor<tf.Rank>;
|
|
7
|
+
export declare function cropTensor(img: tf.Tensor3D, grayscaleModel?: boolean, grayscaleInput?: boolean): tf.Tensor3D;
|