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