[javascript] nodejs (tensorflow.js)에서 모델을 훈련시키는 방법?
이미지 분류기를 만들고 싶지만 파이썬을 모르겠습니다. Tensorflow.js는 내가 익숙한 자바 스크립트와 함께 작동합니다. 모델을 학습 할 수 있으며 그렇게하는 단계는 무엇입니까? 솔직히 나는 어디서부터 시작할지 전혀 모른다.
내가 알아 낸 유일한 방법은 “모바일 넷”을로드하는 방법인데, 이것은 사전 훈련 된 모델 집합으로, 이미지를 분류합니다.
const tf = require('@tensorflow/tfjs'),
mobilenet = require('@tensorflow-models/mobilenet'),
tfnode = require('@tensorflow/tfjs-node'),
fs = require('fs-extra');
const imageBuffer = await fs.readFile(......),
tfimage = tfnode.node.decodeImage(imageBuffer),
mobilenetModel = await mobilenet.load();
const results = await mobilenetModel.classify(tfimage);
작동하지만 레이블을 사용하여 이미지를 사용하여 내 모델을 훈련시키고 싶기 때문에 나에게는 쓸모가 없습니다.
========================
이미지와 레이블이 많이 있다고 가정 해보십시오. 모델을 훈련시키는 데 어떻게 사용합니까?
const myData = JSON.parse(await fs.readFile('files.json'));
for(const data of myData){
const image = await fs.readFile(data.imagePath),
labels = data.labels;
// how to train, where to pass image and labels ?
}
답변
우선 이미지를 텐서로 변환해야합니다. 첫 번째 방법은 모든 기능을 포함하는 텐서 (각각 모든 레이블을 포함하는 텐서)를 만드는 것입니다. 데이터 세트에 이미지가 거의없는 경우에만 진행해야합니다.
const imageBuffer = await fs.readFile(feature_file);
tensorFeature = tfnode.node.decodeImage(imageBuffer) // create a tensor for the image
// create an array of all the features
// by iterating over all the images
tensorFeatures = tf.stack([tensorFeature, tensorFeature2, tensorFeature3])
레이블은 각 이미지의 유형을 나타내는 배열입니다.
labelArray = [0, 1, 2] // maybe 0 for dog, 1 for cat and 2 for birds
이제 라벨의 핫 인코딩을 만들어야합니다.
tensorLabels = tf.oneHot(tf.tensor1d(labelArray, 'int32'), 3);
텐서가 있으면 훈련을위한 모델을 만들어야합니다. 다음은 간단한 모델입니다.
const model = tf.sequential();
model.add(tf.layers.conv2d({
inputShape: [height, width, numberOfChannels], // numberOfChannels = 3 for colorful images and one otherwise
filters: 32,
kernelSize: 3,
activation: 'relu',
}));
model.add(tf.layers.flatten()),
model.add(tf.layers.dense({units: 3, activation: 'softmax'}));
그런 다음 모델을 훈련시킬 수 있습니다
model.fit(tensorFeatures, tensorLabels)
데이터 세트에 많은 이미지가 포함 된 경우 대신 tfDataset을 작성해야합니다. 이 답변 은 이유를 설명합니다.
const genFeatureTensor = image => {
const imageBuffer = await fs.readFile(feature_file);
return tfnode.node.decodeImage(imageBuffer)
}
const labelArray = indice => Array.from({length: numberOfClasses}, (_, k) => k === indice ? 1 : 0)
function* dataGenerator() {
const numElements = numberOfImages;
let index = 0;
while (index < numFeatures) {
const feature = genFeatureTensor(imagePath) ;
const label = tf.tensor1d(labelArray(classImageIndex))
index++;
yield {xs: feature, ys: label};
}
}
const ds = tf.data.generator(dataGenerator);
그리고 model.fitDataset(ds)
모델 훈련에 사용
위의 nodejs 훈련입니다. 브라우저에서 이러한 처리를 수행하려면 다음과 genFeatureTensor
같이 작성할 수 있습니다.
function load(url){
return new Promise((resolve, reject) => {
const im = new Image()
im.crossOrigin = 'anonymous'
im.src = 'url'
im.onload = () => {
resolve(im)
}
})
}
genFeatureTensor = image => {
const img = await loadImage(image);
return tf.browser.fromPixels(image);
}
주의해야 할 한 가지 사항은 과도한 처리를 수행하면 브라우저의 기본 스레드가 차단 될 수 있다는 것입니다. 여기에서 웹 워커가 시작됩니다.
답변
예 https://codelabs.developers.google.com/codelabs/tfjs-training-classfication/#0을 고려 하십시오.
그들이하는 일은 :
- 큰 png 이미지를 가져옵니다 (이미지의 수직 연결)
- 몇 가지 레이블을
- 데이터 세트 구축 (data.js)
다음 훈련
데이터 세트의 구축은 다음과 같습니다.
- 이미지
큰 이미지는 n 개의 수직 청크로 나뉩니다. (n 청크 크기)
크기가 2 인 chunkSize를 고려하십시오.
이미지 1의 픽셀 매트릭스가 주어진 경우 :
1 2 3
4 5 6
이미지 2의 픽셀 매트릭스는
7 8 9
1 2 3
결과 배열은
1 2 3 4 5 6 7 8 9 1 2 3
(1D 연결)
기본적으로 처리가 끝나면 큰 버퍼가 나타납니다.
[...Buffer(image1), ...Buffer(image2), ...Buffer(image3)]
- 라벨
이러한 종류의 서식은 분류 문제에 대해 많이 수행됩니다. 숫자로 분류하는 대신 부울 배열을 사용합니다. 10 개의 클래스 중 7 개를 예측하려면
[0,0,0,0,0,0,0,1,0,0] // 1 in 7e position, array 0-indexed
시작하기 위해 할 수있는 일
- 이미지 및 관련 레이블을 가져옵니다.
- 이미지를 캔버스에로드
- 관련 버퍼 추출
- 모든 이미지 버퍼를 큰 버퍼로 연결하십시오. 그것이 xs입니다.
- 연결된 모든 레이블을 가져 와서 부울 배열로 매핑 한 다음 연결합니다.
아래에서 나는 서브 클래스 MNistData::load
(나머지 클래스 는 인스턴스화해야하는 script.js를 제외하고)
나는 여전히 28×28 이미지를 생성하고 그 위에 숫자를 쓰고 잡음이나 자발적으로 잘못된 라벨을 포함하지 않기 때문에 완벽한 정확성을 얻습니다.
import {MnistData} from './data.js'
const IMAGE_SIZE = 784;// actually 28*28...
const NUM_CLASSES = 10;
const NUM_DATASET_ELEMENTS = 5000;
const NUM_TRAIN_ELEMENTS = 4000;
const NUM_TEST_ELEMENTS = NUM_DATASET_ELEMENTS - NUM_TRAIN_ELEMENTS;
function makeImage (label, ctx) {
ctx.fillStyle = 'black'
ctx.fillRect(0, 0, 28, 28) // hardcoded, brrr
ctx.fillStyle = 'white'
ctx.fillText(label, 10, 20) // print a digit on the canvas
}
export class MyMnistData extends MnistData{
async load() {
const canvas = document.createElement('canvas')
canvas.width = 28
canvas.height = 28
let ctx = canvas.getContext('2d')
ctx.font = ctx.font.replace(/\d+px/, '18px')
let labels = new Uint8Array(NUM_DATASET_ELEMENTS*NUM_CLASSES)
// in data.js, they use a batch of images (aka chunksize)
// let's even remove it for simplification purpose
const datasetBytesBuffer = new ArrayBuffer(NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);
for (let i = 0; i < NUM_DATASET_ELEMENTS; i++) {
const datasetBytesView = new Float32Array(
datasetBytesBuffer, i * IMAGE_SIZE * 4,
IMAGE_SIZE);
// BEGIN our handmade label + its associated image
// notice that you could loadImage( images[i], datasetBytesView )
// so you do them by bulk and synchronize after your promises after "forloop"
const label = Math.floor(Math.random()*10)
labels[i*NUM_CLASSES + label] = 1
makeImage(label, ctx)
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// END you should be able to load an image to canvas :)
for (let j = 0; j < imageData.data.length / 4; j++) {
// NOTE: you are storing a FLOAT of 4 bytes, in [0;1] even though you don't need it
// We could make it with a uint8Array (assuming gray scale like we are) without scaling to 1/255
// they probably did it so you can copy paste like me for color image afterwards...
datasetBytesView[j] = imageData.data[j * 4] / 255;
}
}
this.datasetImages = new Float32Array(datasetBytesBuffer);
this.datasetLabels = labels
//below is copy pasted
this.trainIndices = tf.util.createShuffledIndices(NUM_TRAIN_ELEMENTS);
this.testIndices = tf.util.createShuffledIndices(NUM_TEST_ELEMENTS);
this.trainImages = this.datasetImages.slice(0, IMAGE_SIZE * NUM_TRAIN_ELEMENTS);
this.testImages = this.datasetImages.slice(IMAGE_SIZE * NUM_TRAIN_ELEMENTS);
this.trainLabels =
this.datasetLabels.slice(0, NUM_CLASSES * NUM_TRAIN_ELEMENTS);// notice, each element is an array of size NUM_CLASSES
this.testLabels =
this.datasetLabels.slice(NUM_CLASSES * NUM_TRAIN_ELEMENTS);
}
}
답변
기존 모델을 사용하여 새로운 클래스를 학습하는 방법을 튜토리얼 [1]에서 찾았습니다. 주요 코드 부분은 다음과 같습니다.
index.html 헤드 :
<script src="https://unpkg.com/@tensorflow-models/knn-classifier"></script>
index.html 본문 :
<button id="class-a">Add A</button>
<button id="class-b">Add B</button>
<button id="class-c">Add C</button>
index.js :
const classifier = knnClassifier.create();
....
// Reads an image from the webcam and associates it with a specific class
// index.
const addExample = async classId => {
// Capture an image from the web camera.
const img = await webcam.capture();
// Get the intermediate activation of MobileNet 'conv_preds' and pass that
// to the KNN classifier.
const activation = net.infer(img, 'conv_preds');
// Pass the intermediate activation to the classifier.
classifier.addExample(activation, classId);
// Dispose the tensor to release the memory.
img.dispose();
};
// When clicking a button, add an example for that class.
document.getElementById('class-a').addEventListener('click', () => addExample(0));
document.getElementById('class-b').addEventListener('click', () => addExample(1));
document.getElementById('class-c').addEventListener('click', () => addExample(2));
....
주요 아이디어는 기존 네트워크를 사용하여 예측 한 다음 찾은 레이블을 자신의 레이블로 대체하는 것입니다.
전체 코드는 튜토리얼에 있습니다. 또 다른 유망한, 더 진보 된 것 [2]. 엄격한 사전 처리가 필요하므로 여기에만 남겨 두십시오.
출처 :
[1] https://codelabs.developers.google.com/codelabs/tensorflowjs-teachablemachine-codelab/index.html#6
답변
TL; DR
MNIST는 이미지 인식 Hello World입니다. 마음으로 그것을 배우고 나면, 당신의 마음에있는이 질문들은 쉽게 풀 수 있습니다.
질문 설정 :
당신의 주요 질문은
// how to train, where to pass image and labels ?
코드 블록 내부. 그런 사람들을 위해 Tensorflow.js 예제 섹션의 예제에서 완벽한 대답을 찾았습니다 : MNIST 예제. 아래 링크에는 순수한 javascript 및 node.js 버전과 Wikipedia 설명이 있습니다. 나는 당신의 마음에있는 주요 질문에 대답하는 데 필요한 수준에서 그것들을 살펴보고, 자신의 이미지와 레이블이 MNIST 이미지 세트와 어떤 관련이 있는지와 그것을 사용하는 예제에 대한 관점을 추가 할 것입니다.
먼저 첫 번째 것들:
코드 스 니펫.
이미지를 전달할 위치 (Node.js 샘플)
async function loadImages(filename) {
const buffer = await fetchOnceAndSaveToDiskWithBuffer(filename);
const headerBytes = IMAGE_HEADER_BYTES;
const recordBytes = IMAGE_HEIGHT * IMAGE_WIDTH;
const headerValues = loadHeaderValues(buffer, headerBytes);
assert.equal(headerValues[0], IMAGE_HEADER_MAGIC_NUM);
assert.equal(headerValues[2], IMAGE_HEIGHT);
assert.equal(headerValues[3], IMAGE_WIDTH);
const images = [];
let index = headerBytes;
while (index < buffer.byteLength) {
const array = new Float32Array(recordBytes);
for (let i = 0; i < recordBytes; i++) {
// Normalize the pixel values into the 0-1 interval, from
// the original 0-255 interval.
array[i] = buffer.readUInt8(index++) / 255;
}
images.push(array);
}
assert.equal(images.length, headerValues[1]);
return images;
}
노트:
MNIST 데이터 세트는 하나의 파일에 x와 y 좌표 테이블의 상자와 같이 같은 크기, 나란히, 같은 크기의 퍼즐 타일과 같은 여러 이미지가있는 거대한 이미지입니다. 각 상자에는 하나의 샘플이 있으며 labels 배열의 해당 x 및 y에는 레이블이 있습니다. 이 예제에서 여러 파일 형식으로 변환하는 것은 큰 문제가 아니므로 실제로 한 번에 하나의 그림 만 처리 할 while 루프에 제공됩니다.
라벨 :
async function loadLabels(filename) {
const buffer = await fetchOnceAndSaveToDiskWithBuffer(filename);
const headerBytes = LABEL_HEADER_BYTES;
const recordBytes = LABEL_RECORD_BYTE;
const headerValues = loadHeaderValues(buffer, headerBytes);
assert.equal(headerValues[0], LABEL_HEADER_MAGIC_NUM);
const labels = [];
let index = headerBytes;
while (index < buffer.byteLength) {
const array = new Int32Array(recordBytes);
for (let i = 0; i < recordBytes; i++) {
array[i] = buffer.readUInt8(index++);
}
labels.push(array);
}
assert.equal(labels.length, headerValues[1]);
return labels;
}
노트:
여기서 레이블은 파일의 바이트 데이터이기도합니다. Javascript 세계에서, 그리고 시작점에서 접근 한 방식으로 레이블은 json 배열 일 수도 있습니다.
모델 훈련 :
await data.loadData();
const {images: trainImages, labels: trainLabels} = data.getTrainData();
model.summary();
let epochBeginTime;
let millisPerStep;
const validationSplit = 0.15;
const numTrainExamplesPerEpoch =
trainImages.shape[0] * (1 - validationSplit);
const numTrainBatchesPerEpoch =
Math.ceil(numTrainExamplesPerEpoch / batchSize);
await model.fit(trainImages, trainLabels, {
epochs,
batchSize,
validationSplit
});
노트:
다음 model.fit
은 작업을 수행하는 실제 코드 줄입니다. 모델을 훈련시킵니다.
모든 것의 결과 :
const {images: testImages, labels: testLabels} = data.getTestData();
const evalOutput = model.evaluate(testImages, testLabels);
console.log(
`\nEvaluation result:\n` +
` Loss = ${evalOutput[0].dataSync()[0].toFixed(3)}; `+
`Accuracy = ${evalOutput[1].dataSync()[0].toFixed(3)}`);
노트 :
이번에는 데이터 사이언스에서 가장 매혹적인 부분은 모델이 새로운 데이터의 테스트에서 얼마나 잘 살아남 았는지, 레이블이 없는지, 레이블을 붙일 수 있는지를 아는 것입니다. 그것은 이제 우리에게 숫자를 인쇄하는 평가 부분입니다.
손실 및 정확도 : [4]
손실이 적을수록 모델이 향상됩니다 (모델이 훈련 데이터에 과도하게 적합하지 않은 경우). 손실은 훈련 및 검증에 의해 계산되며 그 상호 작용은 모델이이 두 세트에 대해 얼마나 잘 수행되고 있는지를 나타냅니다. 정확도와 달리 손실은 백분율이 아닙니다. 훈련 또는 검증 세트에서 각 예에 대해 발생한 오류를 요약 한 것입니다.
..
모델의 정확도는 일반적으로 모델 매개 변수를 학습하고 수정 한 후 학습이 수행되지 않은 후에 결정됩니다. 그런 다음 테스트 샘플을 모델에 공급하고 실제 목표와 비교 한 후 모델의 실수 횟수 (0 대 1 손실)를 기록합니다.
추가 정보:
github 페이지의 README.md 파일에는 github 예제의 모든 내용이 자세히 설명되어있는 튜토리얼 링크가 있습니다.
[1] https://github.com/tensorflow/tfjs-examples/tree/master/mnist
[2] https://github.com/tensorflow/tfjs-examples/tree/master/mnist-node