learning_model 1.0.16 → 1.0.18
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/index.js +2 -2
- package/dist/types/learning/image.js +167 -229
- package/dist/types/learning/mobilenet_image.js +162 -229
- package/package.json +2 -2
- package/tsconfig.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.LearningMobilenetImage = exports.LearningImage = void 0;
|
|
7
|
-
|
|
7
|
+
const image_1 = __importDefault(require("./types/learning/image"));
|
|
8
8
|
exports.LearningImage = image_1.default;
|
|
9
|
-
|
|
9
|
+
const mobilenet_image_1 = __importDefault(require("./types/learning/mobilenet_image"));
|
|
10
10
|
exports.LearningMobilenetImage = mobilenet_image_1.default;
|
|
@@ -31,48 +31,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
31
31
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
32
|
});
|
|
33
33
|
};
|
|
34
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
35
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
36
|
-
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
37
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
38
|
-
function step(op) {
|
|
39
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
40
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
41
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
42
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
43
|
-
switch (op[0]) {
|
|
44
|
-
case 0: case 1: t = op; break;
|
|
45
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
46
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
47
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
48
|
-
default:
|
|
49
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
50
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
51
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
52
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
53
|
-
if (t[2]) _.ops.pop();
|
|
54
|
-
_.trys.pop(); continue;
|
|
55
|
-
}
|
|
56
|
-
op = body.call(thisArg, _);
|
|
57
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
58
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
34
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
var _b = _a === void 0 ? {} : _a, _c = _b.epochs, epochs = _c === void 0 ? 10 : _c, _d = _b.batchSize, batchSize = _d === void 0 ? 16 : _d, _e = _b.limitSize, limitSize = _e === void 0 ? 2 : _e, _f = _b.learningRate, learningRate = _f === void 0 ? 0.001 : _f;
|
|
35
|
+
const tf = __importStar(require("@tensorflow/tfjs"));
|
|
36
|
+
class LearningImage {
|
|
37
|
+
constructor({ epochs = 10, batchSize = 16, limitSize = 2, learningRate = 0.001, } = {}) {
|
|
66
38
|
this.trainImages = [];
|
|
67
39
|
this.MOBILE_NET_INPUT_WIDTH = 224;
|
|
68
40
|
this.MOBILE_NET_INPUT_HEIGHT = 224;
|
|
69
41
|
this.MOBILE_NET_INPUT_CHANNEL = 3;
|
|
70
42
|
this.IMAGE_NORMALIZATION_FACTOR = 255.0;
|
|
71
|
-
this.onProgress =
|
|
72
|
-
this.onLoss =
|
|
73
|
-
this.onEvents =
|
|
74
|
-
this.onTrainBegin =
|
|
75
|
-
this.onTrainEnd =
|
|
43
|
+
this.onProgress = () => { };
|
|
44
|
+
this.onLoss = () => { };
|
|
45
|
+
this.onEvents = () => { };
|
|
46
|
+
this.onTrainBegin = () => { };
|
|
47
|
+
this.onTrainEnd = () => { };
|
|
76
48
|
this.model = null;
|
|
77
49
|
this.epochs = epochs;
|
|
78
50
|
this.batchSize = batchSize;
|
|
@@ -83,188 +55,159 @@ var LearningImage = /** @class */ (function () {
|
|
|
83
55
|
this.limitSize = 2;
|
|
84
56
|
}
|
|
85
57
|
// 학습 데이타 등록
|
|
86
|
-
|
|
87
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
this.
|
|
95
|
-
if (this.labels.length >= this.limitSize) {
|
|
96
|
-
this.isReady = true;
|
|
97
|
-
}
|
|
98
|
-
return [2 /*return*/, Promise.resolve()];
|
|
58
|
+
addData(label, data) {
|
|
59
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
60
|
+
try {
|
|
61
|
+
const tensor = tf.browser.fromPixels(data);
|
|
62
|
+
console.log('addData', tensor);
|
|
63
|
+
this.trainImages.push(tensor);
|
|
64
|
+
this.labels.push(label);
|
|
65
|
+
if (this.labels.length >= this.limitSize) {
|
|
66
|
+
this.isReady = true;
|
|
99
67
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
68
|
+
return Promise.resolve();
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error('Model training failed', error);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
106
74
|
});
|
|
107
|
-
}
|
|
75
|
+
}
|
|
108
76
|
// 모델 학습 처리
|
|
109
|
-
|
|
110
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
_b.label = 1;
|
|
147
|
-
case 1:
|
|
148
|
-
_b.trys.push([1, 4, , 5]);
|
|
149
|
-
this.isRunning = true;
|
|
150
|
-
if (this.labels.length < this.limitSize) {
|
|
151
|
-
return [2 /*return*/, Promise.reject(new Error('Please train Data need over 2 data length'))];
|
|
152
|
-
}
|
|
153
|
-
_a = this;
|
|
154
|
-
return [4 /*yield*/, this._createModel(this.labels.length)];
|
|
155
|
-
case 2:
|
|
156
|
-
_a.model = _b.sent();
|
|
157
|
-
inputData = this._preprocessedInputData(this.model);
|
|
158
|
-
console.log('inputData', inputData);
|
|
159
|
-
targetData = this._preprocessedTargetData();
|
|
160
|
-
console.log('targetData', targetData);
|
|
161
|
-
return [4 /*yield*/, this.model.fit(inputData, targetData, {
|
|
162
|
-
epochs: this.epochs,
|
|
163
|
-
batchSize: this.batchSize,
|
|
164
|
-
callbacks: customCallback
|
|
165
|
-
})];
|
|
166
|
-
case 3:
|
|
167
|
-
history_1 = _b.sent();
|
|
168
|
-
console.log('Model training completed', history_1);
|
|
169
|
-
return [2 /*return*/, history_1];
|
|
170
|
-
case 4:
|
|
171
|
-
error_1 = _b.sent();
|
|
172
|
-
this.isRunning = false;
|
|
173
|
-
console.error('Model training failed', error_1);
|
|
174
|
-
throw error_1;
|
|
175
|
-
case 5: return [2 /*return*/];
|
|
77
|
+
train() {
|
|
78
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
if (this.isRunning) {
|
|
80
|
+
return Promise.reject(new Error('Training is already in progress.'));
|
|
81
|
+
}
|
|
82
|
+
// 콜백 정의
|
|
83
|
+
const customCallback = {
|
|
84
|
+
onTrainBegin: (log) => {
|
|
85
|
+
this.onTrainBegin(log);
|
|
86
|
+
console.log('Training has started.');
|
|
87
|
+
},
|
|
88
|
+
onTrainEnd: (log) => {
|
|
89
|
+
this.onTrainEnd(log);
|
|
90
|
+
console.log('Training has ended.');
|
|
91
|
+
this.isRunning = false;
|
|
92
|
+
},
|
|
93
|
+
onBatchBegin: (batch, logs) => {
|
|
94
|
+
console.log(`Batch ${batch} is starting.`);
|
|
95
|
+
},
|
|
96
|
+
onBatchEnd: (batch, logs) => {
|
|
97
|
+
console.log(`Batch ${batch} has ended.`);
|
|
98
|
+
},
|
|
99
|
+
onEpochBegin: (epoch, logs) => {
|
|
100
|
+
console.log(`Epoch ${epoch + 1} is starting.`, logs);
|
|
101
|
+
},
|
|
102
|
+
onEpochEnd: (epoch, logs) => {
|
|
103
|
+
console.log(`Epoch ${epoch + 1} has ended.`);
|
|
104
|
+
this.onLoss(logs.loss);
|
|
105
|
+
console.log('Loss:', logs);
|
|
106
|
+
this.onEvents(logs);
|
|
107
|
+
this.onProgress(epoch + 1);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
this.isRunning = true;
|
|
112
|
+
if (this.labels.length < this.limitSize) {
|
|
113
|
+
return Promise.reject(new Error('Please train Data need over 2 data length'));
|
|
176
114
|
}
|
|
177
|
-
|
|
115
|
+
this.model = yield this._createModel(this.labels.length);
|
|
116
|
+
const inputData = this._preprocessedInputData(this.model);
|
|
117
|
+
console.log('inputData', inputData);
|
|
118
|
+
const targetData = this._preprocessedTargetData();
|
|
119
|
+
console.log('targetData', targetData);
|
|
120
|
+
const history = yield this.model.fit(inputData, targetData, {
|
|
121
|
+
epochs: this.epochs,
|
|
122
|
+
batchSize: this.batchSize,
|
|
123
|
+
callbacks: customCallback
|
|
124
|
+
});
|
|
125
|
+
console.log('Model training completed', history);
|
|
126
|
+
return history;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
this.isRunning = false;
|
|
130
|
+
console.error('Model training failed', error);
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
178
133
|
});
|
|
179
|
-
}
|
|
134
|
+
}
|
|
180
135
|
// 추론하기
|
|
181
|
-
|
|
182
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
probability = predictionsData[i];
|
|
204
|
-
existingProbability = classProbabilities.get(className);
|
|
205
|
-
if (existingProbability !== undefined) {
|
|
206
|
-
classProbabilities.set(className, existingProbability + probability);
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
classProbabilities.set(className, probability);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
console.log('Class Probabilities:', classProbabilities);
|
|
213
|
-
return [2 /*return*/, classProbabilities];
|
|
214
|
-
case 3:
|
|
215
|
-
error_2 = _a.sent();
|
|
216
|
-
throw error_2;
|
|
217
|
-
case 4: return [2 /*return*/];
|
|
136
|
+
infer(data) {
|
|
137
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
138
|
+
if (this.model === null) {
|
|
139
|
+
throw new Error('Model is null');
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const tensor = tf.browser.fromPixels(data);
|
|
143
|
+
const resizedTensor = tf.image.resizeBilinear(tensor, [this.MOBILE_NET_INPUT_WIDTH, this.MOBILE_NET_INPUT_HEIGHT]);
|
|
144
|
+
const reshapedTensor = resizedTensor.expandDims(0); // 배치 크기 1을 추가하여 4차원으로 변환
|
|
145
|
+
const predictions = this.model.predict(reshapedTensor);
|
|
146
|
+
const predictionsData = yield predictions.data(); // 예측 텐서의 데이터를 비동기로 가져옴
|
|
147
|
+
const classProbabilities = new Map(); // 클래스별 확률 누적값을 저장할 맵
|
|
148
|
+
for (let i = 0; i < predictionsData.length; i++) {
|
|
149
|
+
const className = this.labels[i]; // 클래스 이름
|
|
150
|
+
const probability = predictionsData[i];
|
|
151
|
+
const existingProbability = classProbabilities.get(className);
|
|
152
|
+
if (existingProbability !== undefined) {
|
|
153
|
+
classProbabilities.set(className, existingProbability + probability);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
classProbabilities.set(className, probability);
|
|
157
|
+
}
|
|
218
158
|
}
|
|
219
|
-
|
|
159
|
+
console.log('Class Probabilities:', classProbabilities);
|
|
160
|
+
return classProbabilities;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
220
165
|
});
|
|
221
|
-
}
|
|
166
|
+
}
|
|
222
167
|
// 모델 저장
|
|
223
|
-
|
|
168
|
+
saveModel() {
|
|
224
169
|
console.log('saved model');
|
|
225
|
-
}
|
|
170
|
+
}
|
|
226
171
|
// 진행중 여부
|
|
227
|
-
|
|
172
|
+
running() {
|
|
228
173
|
return this.isRunning;
|
|
229
|
-
}
|
|
230
|
-
|
|
174
|
+
}
|
|
175
|
+
ready() {
|
|
231
176
|
return this.isReady;
|
|
232
|
-
}
|
|
177
|
+
}
|
|
233
178
|
// target 라벨 데이타
|
|
234
|
-
|
|
235
|
-
var _this = this;
|
|
179
|
+
_preprocessedTargetData() {
|
|
236
180
|
// 라벨 unique 처리 & 배열 리턴
|
|
237
181
|
console.log('uniqueLabels.length', this.labels, this.labels.length);
|
|
238
|
-
|
|
182
|
+
const labelIndices = this.labels.map((label) => this.labels.indexOf(label));
|
|
239
183
|
console.log('labelIndices', labelIndices);
|
|
240
|
-
|
|
184
|
+
const oneHotEncode = tf.oneHot(tf.tensor1d(labelIndices, 'int32'), this.labels.length);
|
|
241
185
|
console.log('oneHotEncode', oneHotEncode);
|
|
242
186
|
return oneHotEncode;
|
|
243
|
-
}
|
|
187
|
+
}
|
|
244
188
|
// 입력 이미지 데이타
|
|
245
|
-
|
|
246
|
-
var _this = this;
|
|
189
|
+
_preprocessedInputData(model) {
|
|
247
190
|
// 이미지 배열을 배치로 변환 - [null, 224, 224, 3]
|
|
248
|
-
|
|
191
|
+
const inputShape = model.inputs[0].shape;
|
|
249
192
|
console.log('inputShape', inputShape);
|
|
250
193
|
// inputShape를 이와 같이 포멧 맞춘다. for reshape to [224, 224, 3]
|
|
251
|
-
|
|
194
|
+
const inputShapeArray = inputShape.slice(1);
|
|
252
195
|
console.log('inputShapeArray', inputShapeArray);
|
|
253
|
-
|
|
196
|
+
const inputBatch = tf.stack(this.trainImages.map((image) => {
|
|
254
197
|
// 이미지 전처리 및 크기 조정 등을 수행한 후에
|
|
255
198
|
// 모델의 입력 형태로 변환하여 반환
|
|
256
|
-
|
|
199
|
+
const xs = this._preprocessData(image); // 전처리 함수는 사용자 정의해야 함
|
|
257
200
|
return tf.reshape(xs, inputShapeArray);
|
|
258
201
|
}));
|
|
259
202
|
return inputBatch;
|
|
260
|
-
}
|
|
203
|
+
}
|
|
261
204
|
// 모델 학습하기 위한 데이타 전처리 단계
|
|
262
|
-
|
|
205
|
+
_preprocessData(tensor) {
|
|
263
206
|
try {
|
|
264
207
|
// mobilenet model summary를 하면 위와 같이 224,224 사이즈의 입력값 설정되어 있다. ex) input_1 (InputLayer) [null,224,224,3]
|
|
265
|
-
|
|
208
|
+
const resizedImage = tf.image.resizeBilinear(tensor, [this.MOBILE_NET_INPUT_WIDTH, this.MOBILE_NET_INPUT_HEIGHT]);
|
|
266
209
|
// 이미지를 [0,1] 범위로 정규화 255로 나뉜 픽셀값
|
|
267
|
-
|
|
210
|
+
const normalizedImage = resizedImage.div(this.IMAGE_NORMALIZATION_FACTOR);
|
|
268
211
|
// expandDims(0)을 하여 차원을 추가하여 4D텐서 반환
|
|
269
212
|
return normalizedImage.expandDims(0);
|
|
270
213
|
}
|
|
@@ -272,50 +215,45 @@ var LearningImage = /** @class */ (function () {
|
|
|
272
215
|
console.error('Failed to _preprocessData data', error);
|
|
273
216
|
throw error;
|
|
274
217
|
}
|
|
275
|
-
}
|
|
218
|
+
}
|
|
276
219
|
// 모델 저장
|
|
277
|
-
|
|
278
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
throw error;
|
|
314
|
-
}
|
|
315
|
-
return [2 /*return*/];
|
|
316
|
-
});
|
|
220
|
+
_createModel(numClasses) {
|
|
221
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
222
|
+
try {
|
|
223
|
+
const inputShape = [this.MOBILE_NET_INPUT_WIDTH, this.MOBILE_NET_INPUT_HEIGHT, this.MOBILE_NET_INPUT_CHANNEL];
|
|
224
|
+
const model = tf.sequential();
|
|
225
|
+
model.add(tf.layers.conv2d({
|
|
226
|
+
inputShape,
|
|
227
|
+
filters: 32,
|
|
228
|
+
kernelSize: 3,
|
|
229
|
+
activation: 'relu'
|
|
230
|
+
}));
|
|
231
|
+
model.add(tf.layers.maxPooling2d({ poolSize: 2 }));
|
|
232
|
+
model.add(tf.layers.conv2d({
|
|
233
|
+
filters: 64,
|
|
234
|
+
kernelSize: 3,
|
|
235
|
+
activation: 'relu'
|
|
236
|
+
}));
|
|
237
|
+
model.add(tf.layers.maxPooling2d({ poolSize: 2 }));
|
|
238
|
+
model.add(tf.layers.flatten());
|
|
239
|
+
model.add(tf.layers.dense({
|
|
240
|
+
units: numClasses,
|
|
241
|
+
activation: 'softmax'
|
|
242
|
+
}));
|
|
243
|
+
const optimizer = tf.train.adam(this.learningRate); // Optimizer를 생성하고 학습률을 설정합니다.
|
|
244
|
+
model.compile({
|
|
245
|
+
loss: (numClasses === 2) ? 'binaryCrossentropy' : 'categoricalCrossentropy',
|
|
246
|
+
optimizer: optimizer,
|
|
247
|
+
metrics: ['accuracy', 'acc']
|
|
248
|
+
});
|
|
249
|
+
model.summary();
|
|
250
|
+
return model;
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
console.error('Failed to load model', error);
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
317
256
|
});
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
}());
|
|
257
|
+
}
|
|
258
|
+
}
|
|
321
259
|
exports.default = LearningImage;
|