tfjs-tiny-yolov2
Version:
Tiny YOLO v2 object detection with tensorflow.js.
127 lines (99 loc) • 3.93 kB
text/typescript
import * as tf from '@tensorflow/tfjs-core';
import {
computeReshapedDimensions,
Dimensions,
getMediaDimensions,
imageToSquare,
Rect,
toNetInput,
} from 'tfjs-image-recognition-base';
import { TinyYolov2TrainableConfig, validateTrainConfig } from './config';
import { getDefaultBackwardOptions } from './getDefaultBackwardOptions';
import { TinyYolov2 } from './TinyYolov2';
import { TinyYolov2LossFunction } from './TinyYolov2LossFunction';
import { GroundTruth, TinyYolov2BackwardOptions } from './types';
export class TinyYolov2Trainable extends TinyYolov2 {
private _trainableConfig: TinyYolov2TrainableConfig
private _optimizer: tf.Optimizer
constructor(trainableConfig: TinyYolov2TrainableConfig, optimizer: tf.Optimizer) {
super(trainableConfig)
this._trainableConfig = validateTrainConfig(trainableConfig)
this._optimizer = optimizer
}
public get trainableConfig(): TinyYolov2TrainableConfig {
return this._trainableConfig
}
public get optimizer(): tf.Optimizer {
return this._optimizer
}
public async backward(
img: HTMLImageElement | HTMLCanvasElement,
groundTruth: GroundTruth[],
inputSize: number,
options: TinyYolov2BackwardOptions = {}
): Promise<tf.Tensor<tf.Rank.R0> | null> {
const { minBoxSize, reportLosses } = getDefaultBackwardOptions(options)
const reshapedImgDims = computeReshapedDimensions(getMediaDimensions(img), inputSize)
const filteredGroundTruthBoxes = this.filterGroundTruthBoxes(groundTruth, reshapedImgDims, minBoxSize)
if (!filteredGroundTruthBoxes.length) {
return null
}
// square input images before creating tensor to prevent gpu memory overflow bug
const netInput = await toNetInput(imageToSquare(img, inputSize))
const loss = this.optimizer.minimize(() => {
const {
noObjectLoss,
objectLoss,
coordLoss,
classLoss,
totalLoss
} = this.computeLoss(
this.forwardInput(netInput, inputSize),
filteredGroundTruthBoxes,
reshapedImgDims
)
if (reportLosses) {
const losses = {
totalLoss: totalLoss.dataSync()[0],
noObjectLoss: noObjectLoss.dataSync()[0],
objectLoss: objectLoss.dataSync()[0],
coordLoss: coordLoss.dataSync()[0],
classLoss: classLoss.dataSync()[0]
}
const report = {
losses,
numBoxes: filteredGroundTruthBoxes.length,
inputSize
}
reportLosses(report)
}
return totalLoss
}, true)
return loss
}
public computeLoss(outputTensor: tf.Tensor4D, groundTruth: GroundTruth[], reshapedImgDims: Dimensions) {
const config = validateTrainConfig(this.config)
const inputSize = Math.max(reshapedImgDims.width, reshapedImgDims.height)
if (!inputSize) {
throw new Error(`computeLoss - invalid inputSize: ${inputSize}`)
}
const predictedBoxes = this.extractBoxes(outputTensor, reshapedImgDims)
return tf.tidy(() => {
const lossFunction = new TinyYolov2LossFunction(outputTensor, groundTruth, predictedBoxes, reshapedImgDims, config)
return lossFunction.computeLoss()
})
}
public filterGroundTruthBoxes(groundTruth: GroundTruth[], imgDims: Dimensions, minBoxSize: number) {
const { height: imgHeight, width: imgWidth } = imgDims
return groundTruth.filter(({ x, y, width, height }) => {
const box = (new Rect(x, y, width, height))
.rescale({ height: imgHeight, width: imgWidth })
const isTooTiny = box.width < minBoxSize || box.height < minBoxSize
return !isTooTiny
})
}
public async load(weightsOrUrl: Float32Array | string | undefined): Promise<void> {
await super.load(weightsOrUrl)
this.variable()
}
}