Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
} catch (error) {
console.error("Error loading embedding cache.\nWill create a new one.");
console.error(error);
embeddingCache = {};
}
} else {
embeddingCache = {};
}
// Useful for making the embedding smaller.
// This did not change the accuracy by much.
if (EMB_SIZE % EMB_REDUCTION_FACTOR !== 0) {
throw new Error("The embedding reduction factor is not a multiple of the embedding size.");
}
const EMB_MAPPER =
tf.tidy(_ => {
const mapper = tf.fill([EMB_SIZE / EMB_REDUCTION_FACTOR, EMB_SIZE], 0, 'int32');
const buffer = mapper.bufferSync();
for (let i = 0; i < mapper.shape[0]; ++i) {
for (let j = 0; j < EMB_REDUCTION_FACTOR; ++j) {
buffer.set(1, i, 2 * i + j);
}
}
return buffer.toTensor();
});
/**
* @param {string} sample The relative path from `dataPath` for the image.
* @returns The embedding for the image. Shape has 1 dimension.
*/
async function getEmbedding(sample) {
let result = embeddingCache[sample];
samples.push(...samplesForClass);
});
const model = {
bias: 0,
}
// Initialize the weights.
console.log(` Training with ${EMB_SIZE} weights.`);
model.weights = new Array(EMB_SIZE);
for (let j = 0; j < model.weights.length; ++j) {
// Can initialize randomly with `Math.random() - 0.5` but it doesn't seem to make much of a difference.
// model.weights[j] = 0;
model.weights[j] = Math.random() - 0.5;
}
model.weights = tf.tidy(_ => {
return normalize1d(tf.tensor1d(model.weights));
});
let numUpdates, bestNumUpdatesBeforeLearningRateChange;
let epoch = 0;
let stabilityCount = 0;
do {
if (model.weights !== undefined && NORMALIZE_PERCEPTRON_WEIGHTS) {
// Sort of like regularization.
model.weights = normalize1d(model.weights);
}
numUpdates = 0;
shuffle(samples);
for (let i = 0; i < samples.length; ++i) {
if (i % Math.round(samples.length / 4) == 0) {
embeddingsShape.unshift(numImages);
const embeddings = new Float32Array(
tf.util.sizeFromShape(embeddingsShape)
);
const labels = new Int32Array(numImages);
// Loop through the files and populate the 'images' and 'labels' arrays
let embeddingsOffset = 0;
let labelsOffset = 0;
console.log("Loading Training Data");
console.time("Loading Training Data");
for (const element of this.labelsAndImages) {
let labelIndex = this.labelIndex(element.label);
for (const image of element.images) {
let t = await fileToTensor(image);
tf.tidy(() => {
let prediction = model.predict(t);
embeddings.set(prediction.dataSync(), embeddingsOffset);
labels.set([labelIndex], labelsOffset);
});
t.dispose();
embeddingsOffset += embeddingsFlatSize;
labelsOffset += 1;
}
console.timeLog("Loading Training Data", {
label: element.label,
count: element.images.length
});
}
this.dataset = {
break;
default:
throw new Error(`Unrecognized classifierType: "${CLASSIFIER_TYPE}"`);
}
await evaluate(model);
fs.writeFileSync(embeddingCachePath, JSON.stringify(embeddingCache));
console.debug(`Wrote embedding cache to \"${embeddingCachePath}\" with ${Object.keys(embeddingCache).length} cached embeddings.`);
if (PERCEPTRON_NUM_FEATS !== EMB_SIZE) {
console.log(`Reducing weights to ${PERCEPTRON_NUM_FEATS} dimensions.`)
model.featureIndices = tf.tidy(_ => {
return tf.abs(model.weights).topk(PERCEPTRON_NUM_FEATS).indices;
});
model.weights = tf.tidy(_ => {
return model.weights.gather(model.featureIndices);
});
const modelPath = path.join(__dirname, `classifier-perceptron-${PERCEPTRON_NUM_FEATS}.json`);
console.log(`Saving Perceptron with ${PERCEPTRON_NUM_FEATS} weights to "${modelPath}".`);
fs.writeFileSync(modelPath, JSON.stringify({
type: 'perceptron',
classifications: [NEGATIVE_CLASS, POSITIVE_CLASS],
featureIndices: model.featureIndices.arraySync(),
weights: model.weights.arraySync(),
bias: model.bias
}));
await evaluate(model);
}
};
async function predictPerceptron(model, sample) {
let result;
let emb = sample;
if (typeof sample === 'string') {
emb = await getEmbedding(sample);
}
tf.tidy(() => {
if (model.featureIndices !== undefined) {
emb = emb.gather(model.featureIndices);
}
let prediction = model.weights.dot(emb);
prediction = prediction.add(model.bias);
if (prediction.greater(0).dataSync()[0]) {
result = POSITIVE_CLASS;
} else {
result = NEGATIVE_CLASS;
}
});
if (typeof sample === 'string') {
emb.dispose();
}
return result;
}
async function predict(tfModel, tensor) {
const batched = await tf.tidy(() => tensor.expandDims())
const result = await tfModel.executeAsync(batched)
const scores = result[0].arraySync()[0]
const boxes = result[1].dataSync()
batched.dispose()
tf.dispose(result)
return { scores, boxes }
}
async function getEmbedding(sample) {
let result = embeddingCache[sample];
if (result !== undefined) {
result = tf.tensor1d(result);
} else {
const img = await loadImage(path.join(dataPath, sample));
const canvas = createCanvas(img.width, img.height);
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
const emb = await encoder.infer(canvas, { embedding: true });
if (emb.shape[1] !== EMB_SIZE) {
throw new Error(`Expected embedding to have ${EMB_SIZE} dimensions. Got shape: ${emb.shape}.`);
}
result = tf.tidy(_ => {
let result = emb.gather(0);
embeddingCache[sample] = result.arraySync();
if (REDUCE_EMBEDDINGS) {
result = EMB_MAPPER.dot(result);
}
return result;
});
emb.dispose();
}
if (NORMALIZE_EACH_EMBEDDING) {
const normalizedResult = normalize1d(result);
result.dispose();
result = normalizedResult;
}
return result;
}
const imageToTensor = (pixelData, imageInfo) => {
const outShape = [1, imageInfo.height, imageInfo.width, imageInfo.channels];
return tf.tidy(() =>
tf
.tensor4d(pixelData, outShape, "int32")
.toFloat()
.resizeBilinear([224, 224])
.div(tf.scalar(127))
.sub(tf.scalar(1))
);
};
async function imgToTensor(imgBuffer) {
const img = new Image()
const imgOnLoad = new Promise(resolve => {
img.onload = resolve
})
img.src = imgBuffer
await imgOnLoad
const width = img.width
const height = img.height
const canvas = createCanvas(width, height)
const ctx = canvas.getContext('2d')
ctx.drawImage(img, 0, 0, width, height)
const tensor = await tf.tidy(() => tf.fromPixels(canvas))
return { tensor, width, height }
}