Commit 84909181 by vincent

weight loading of quantized tiny-yolov2-seperable-conv2d + use separable conv…

weight loading of quantized tiny-yolov2-seperable-conv2d + use separable conv model by default + testcases
parent 4f45297b
...@@ -20,17 +20,17 @@ import { NetParams, PostProcessingParams, TinyYolov2ForwardParams } from './type ...@@ -20,17 +20,17 @@ import { NetParams, PostProcessingParams, TinyYolov2ForwardParams } from './type
export class TinyYolov2 extends NeuralNetwork<NetParams> { export class TinyYolov2 extends NeuralNetwork<NetParams> {
private _hasSeparableConvs: boolean private _withSeparableConvs: boolean
private _anchors: Point[] private _anchors: Point[]
constructor(hasSeparableConvs: boolean = false) { constructor(withSeparableConvs: boolean = true) {
super('TinyYolov2') super('TinyYolov2')
this._hasSeparableConvs = hasSeparableConvs this._withSeparableConvs = withSeparableConvs
this._anchors = hasSeparableConvs ? BOX_ANCHORS_SEPARABLE : BOX_ANCHORS this._anchors = withSeparableConvs ? BOX_ANCHORS_SEPARABLE : BOX_ANCHORS
} }
public get hasSeparableConvs(): boolean { public get withSeparableConvs(): boolean {
return this._hasSeparableConvs return this._withSeparableConvs
} }
public get anchors(): Point[] { public get anchors(): Point[] {
...@@ -48,7 +48,7 @@ export class TinyYolov2 extends NeuralNetwork<NetParams> { ...@@ -48,7 +48,7 @@ export class TinyYolov2 extends NeuralNetwork<NetParams> {
const out = tf.tidy(() => { const out = tf.tidy(() => {
let batchTensor = input.toBatchTensor(inputSize, false) let batchTensor = input.toBatchTensor(inputSize, false)
batchTensor = this.hasSeparableConvs batchTensor = this.withSeparableConvs
? normalize(batchTensor, MEAN_RGB) ? normalize(batchTensor, MEAN_RGB)
: batchTensor : batchTensor
batchTensor = batchTensor.div(tf.scalar(256)) as tf.Tensor4D batchTensor = batchTensor.div(tf.scalar(256)) as tf.Tensor4D
...@@ -132,7 +132,7 @@ export class TinyYolov2 extends NeuralNetwork<NetParams> { ...@@ -132,7 +132,7 @@ export class TinyYolov2 extends NeuralNetwork<NetParams> {
const numCells = outputTensor.shape[1] const numCells = outputTensor.shape[1]
const [boxesTensor, scoresTensor] = tf.tidy(() => { const [boxesTensor, scoresTensor] = tf.tidy(() => {
const reshaped = outputTensor.reshape([numCells, numCells, NUM_BOXES, this.hasSeparableConvs ? 5 : 6]) const reshaped = outputTensor.reshape([numCells, numCells, NUM_BOXES, this.withSeparableConvs ? 5 : 6])
const boxes = reshaped.slice([0, 0, 0, 0], [numCells, numCells, NUM_BOXES, 4]) const boxes = reshaped.slice([0, 0, 0, 0], [numCells, numCells, NUM_BOXES, 4])
const scores = reshaped.slice([0, 0, 0, 4], [numCells, numCells, NUM_BOXES, 1]) const scores = reshaped.slice([0, 0, 0, 4], [numCells, numCells, NUM_BOXES, 1])
...@@ -172,10 +172,10 @@ export class TinyYolov2 extends NeuralNetwork<NetParams> { ...@@ -172,10 +172,10 @@ export class TinyYolov2 extends NeuralNetwork<NetParams> {
} }
protected loadQuantizedParams(uri: string | undefined) { protected loadQuantizedParams(uri: string | undefined) {
return loadQuantizedParams(uri) return loadQuantizedParams(uri, this.withSeparableConvs)
} }
protected extractParams(weights: Float32Array) { protected extractParams(weights: Float32Array) {
return extractParams(weights, this.hasSeparableConvs) return extractParams(weights, this.withSeparableConvs)
} }
} }
\ No newline at end of file
...@@ -56,7 +56,7 @@ function extractorsFactory(extractWeights: ExtractWeightsFunction, paramMappings ...@@ -56,7 +56,7 @@ function extractorsFactory(extractWeights: ExtractWeightsFunction, paramMappings
} }
export function extractParams(weights: Float32Array, hasSeparableConvs: boolean): { params: NetParams, paramMappings: ParamMapping[] } { export function extractParams(weights: Float32Array, withSeparableConvs: boolean): { params: NetParams, paramMappings: ParamMapping[] } {
const { const {
extractWeights, extractWeights,
...@@ -71,8 +71,8 @@ export function extractParams(weights: Float32Array, hasSeparableConvs: boolean) ...@@ -71,8 +71,8 @@ export function extractParams(weights: Float32Array, hasSeparableConvs: boolean)
extractSeparableConvParams extractSeparableConvParams
} = extractorsFactory(extractWeights, paramMappings) } = extractorsFactory(extractWeights, paramMappings)
const extractConvFn = hasSeparableConvs ? extractSeparableConvParams : extractConvWithBatchNormParams const extractConvFn = withSeparableConvs ? extractSeparableConvParams : extractConvWithBatchNormParams
const numAnchorEncodings = hasSeparableConvs ? 5 : 6 const numAnchorEncodings = withSeparableConvs ? 5 : 6
const conv0 = extractConvFn(3, 16, 'conv0',) const conv0 = extractConvFn(3, 16, 'conv0',)
const conv1 = extractConvFn(16, 32, 'conv1') const conv1 = extractConvFn(16, 32, 'conv1')
......
...@@ -2,8 +2,8 @@ import { TinyYolov2 } from './TinyYolov2'; ...@@ -2,8 +2,8 @@ import { TinyYolov2 } from './TinyYolov2';
export * from './TinyYolov2'; export * from './TinyYolov2';
export function createTinyYolov2(weights: Float32Array) { export function createTinyYolov2(weights: Float32Array, withSeparableConvs: boolean = true) {
const net = new TinyYolov2() const net = new TinyYolov2(withSeparableConvs)
net.extractWeights(weights) net.extractWeights(weights)
return net return net
} }
\ No newline at end of file
...@@ -4,9 +4,10 @@ import { disposeUnusedWeightTensors } from '../commons/disposeUnusedWeightTensor ...@@ -4,9 +4,10 @@ import { disposeUnusedWeightTensors } from '../commons/disposeUnusedWeightTensor
import { extractWeightEntryFactory } from '../commons/extractWeightEntryFactory'; import { extractWeightEntryFactory } from '../commons/extractWeightEntryFactory';
import { loadWeightMap } from '../commons/loadWeightMap'; import { loadWeightMap } from '../commons/loadWeightMap';
import { ConvParams, ParamMapping } from '../commons/types'; import { ConvParams, ParamMapping } from '../commons/types';
import { BatchNorm, ConvWithBatchNorm, NetParams } from './types'; import { BatchNorm, ConvWithBatchNorm, NetParams, SeparableConvParams } from './types';
const DEFAULT_MODEL_NAME = 'tiny_yolov2_model' const DEFAULT_MODEL_NAME = 'tiny_yolov2_model'
const DEFAULT_MODEL_NAME_SEPARABLE_CONV = 'tiny_yolov2_separable_conv_model'
function extractorsFactory(weightMap: any, paramMappings: ParamMapping[]) { function extractorsFactory(weightMap: any, paramMappings: ParamMapping[]) {
...@@ -30,35 +31,51 @@ function extractorsFactory(weightMap: any, paramMappings: ParamMapping[]) { ...@@ -30,35 +31,51 @@ function extractorsFactory(weightMap: any, paramMappings: ParamMapping[]) {
return { conv, bn } return { conv, bn }
} }
function extractSeparableConvParams(prefix: string): SeparableConvParams {
const depthwise_filter = extractWeightEntry<tf.Tensor4D>(`${prefix}/depthwise_filter`, 4)
const pointwise_filter = extractWeightEntry<tf.Tensor4D>(`${prefix}/pointwise_filter`, 4)
const bias = extractWeightEntry<tf.Tensor1D>(`${prefix}/bias`, 1)
return new SeparableConvParams(
depthwise_filter,
pointwise_filter,
bias
)
}
return { return {
extractConvParams, extractConvParams,
extractConvWithBatchNormParams extractConvWithBatchNormParams,
extractSeparableConvParams
} }
} }
export async function loadQuantizedParams( export async function loadQuantizedParams(
uri: string | undefined uri: string | undefined,
withSeparableConvs: boolean
): Promise<{ params: NetParams, paramMappings: ParamMapping[] }> { ): Promise<{ params: NetParams, paramMappings: ParamMapping[] }> {
const weightMap = await loadWeightMap(uri, DEFAULT_MODEL_NAME) const weightMap = await loadWeightMap(uri, withSeparableConvs ? DEFAULT_MODEL_NAME_SEPARABLE_CONV : DEFAULT_MODEL_NAME)
const paramMappings: ParamMapping[] = [] const paramMappings: ParamMapping[] = []
const { const {
extractConvParams, extractConvParams,
extractConvWithBatchNormParams extractConvWithBatchNormParams,
extractSeparableConvParams
} = extractorsFactory(weightMap, paramMappings) } = extractorsFactory(weightMap, paramMappings)
const extractConvFn = withSeparableConvs ? extractSeparableConvParams : extractConvWithBatchNormParams
const params = { const params = {
conv0: extractConvWithBatchNormParams('conv0'), conv0: extractConvFn('conv0'),
conv1: extractConvWithBatchNormParams('conv1'), conv1: extractConvFn('conv1'),
conv2: extractConvWithBatchNormParams('conv2'), conv2: extractConvFn('conv2'),
conv3: extractConvWithBatchNormParams('conv3'), conv3: extractConvFn('conv3'),
conv4: extractConvWithBatchNormParams('conv4'), conv4: extractConvFn('conv4'),
conv5: extractConvWithBatchNormParams('conv5'), conv5: extractConvFn('conv5'),
conv6: extractConvWithBatchNormParams('conv6'), conv6: extractConvFn('conv6'),
conv7: extractConvWithBatchNormParams('conv7'), conv7: extractConvFn('conv7'),
conv8: extractConvParams('conv8') conv8: extractConvParams('conv8')
} }
......
...@@ -29,6 +29,15 @@ export const expectedTinyYolov2Boxes = [ ...@@ -29,6 +29,15 @@ export const expectedTinyYolov2Boxes = [
{ x: 87, y: 30, width: 92, height: 93 } { x: 87, y: 30, width: 92, height: 93 }
] ]
export const expectedTinyYolov2SeparableConvBoxes = [
{ x: 42, y: 257, width: 111, height: 121 },
{ x: 454, y: 175, width: 104, height: 121 },
{ x: 230, y: 45, width: 94, height: 104 },
{ x: 574, y: 62, width: 88, height: 113 },
{ x: 260, y: 233, width: 82, height: 104 },
{ x: 83, y: 24, width: 85, height: 111 }
]
export const expectedMtcnnFaceLandmarks = [ export const expectedMtcnnFaceLandmarks = [
[new Point(117, 58), new Point(156, 63), new Point(141, 86), new Point(109, 98), new Point(147, 104)], [new Point(117, 58), new Point(156, 63), new Point(141, 86), new Point(109, 98), new Point(147, 104)],
[new Point(82, 292), new Point(134, 304), new Point(104, 330), new Point(72, 342), new Point(120, 353)], [new Point(82, 292), new Point(134, 304), new Point(104, 330), new Point(72, 342), new Point(120, 353)],
...@@ -38,7 +47,6 @@ export const expectedMtcnnFaceLandmarks = [ ...@@ -38,7 +47,6 @@ export const expectedMtcnnFaceLandmarks = [
[new Point(489, 224), new Point(534, 223), new Point(507, 250), new Point(493, 271), new Point(530, 270)] [new Point(489, 224), new Point(534, 223), new Point(507, 250), new Point(493, 271), new Point(530, 270)]
] ]
export function expectMtcnnResults( export function expectMtcnnResults(
results: { faceDetection: faceapi.FaceDetection, faceLandmarks: faceapi.FaceLandmarks5 }[], results: { faceDetection: faceapi.FaceDetection, faceLandmarks: faceapi.FaceLandmarks5 }[],
boxOrder: number[], boxOrder: number[],
......
import * as faceapi from '../../../src'; import * as faceapi from '../../../src';
import { SizeType } from '../../../src/tinyYolov2/types'; import { SizeType } from '../../../src/tinyYolov2/types';
import { describeWithNets, expectAllTensorsReleased, expectRectClose } from '../../utils'; import { describeWithNets, expectAllTensorsReleased, expectRectClose } from '../../utils';
import { expectedTinyYolov2Boxes } from './expectedResults'; import { expectedTinyYolov2Boxes, expectedTinyYolov2SeparableConvBoxes } from './expectedResults';
describe('tinyYolov2', () => { describe('tinyYolov2', () => {
...@@ -13,11 +12,63 @@ describe('tinyYolov2', () => { ...@@ -13,11 +12,63 @@ describe('tinyYolov2', () => {
imgEl = await faceapi.bufferToImage(img) imgEl = await faceapi.bufferToImage(img)
}) })
describe('with separable convolutions', () => {
describeWithNets('quantized weights', { withTinyYolov2: { quantized: true } }, ({ tinyYolov2 }) => { describeWithNets('quantized weights', { withTinyYolov2: { quantized: true } }, ({ tinyYolov2 }) => {
it('inputSize lg, finds all faces', async () => { it('inputSize lg, finds all faces', async () => {
const detections = await tinyYolov2.locateFaces(imgEl, { inputSize: SizeType.LG }) const detections = await tinyYolov2.locateFaces(imgEl, { inputSize: SizeType.LG })
const expectedScores = [0.9, 0.9, 0.89, 0.85, 0.85, 0.85]
const maxBoxDelta = 1
const boxOrder = [0, 1, 2, 3, 4, 5]
expect(detections.length).toEqual(6)
detections.forEach((det, i) => {
expect(det.getScore()).toBeCloseTo(expectedScores[i], 2)
expectRectClose(det.getBox(), expectedTinyYolov2SeparableConvBoxes[boxOrder[i]], maxBoxDelta)
})
})
it('inputSize md, finds all faces', async () => {
const detections = await tinyYolov2.locateFaces(imgEl, { inputSize: SizeType.MD })
const expectedScores = [0.85, 0.85, 0.84, 0.83, 0.8, 0.8]
const maxBoxDelta = 17
const boxOrder = [5, 1, 4, 3, 2, 0]
expect(detections.length).toEqual(6)
detections.forEach((det, i) => {
expect(det.getScore()).toBeCloseTo(expectedScores[i], 2)
expectRectClose(det.getBox(), expectedTinyYolov2SeparableConvBoxes[boxOrder[i]], maxBoxDelta)
})
})
it('inputSize custom, finds all faces', async () => {
const detections = await tinyYolov2.locateFaces(imgEl, { inputSize: 416 })
const expectedScores = [0.85, 0.85, 0.84, 0.83, 0.8, 0.8]
const maxBoxDelta = 17
const boxOrder = [5, 1, 4, 3, 2, 0]
expect(detections.length).toEqual(6)
detections.forEach((det, i) => {
expect(det.getScore()).toBeCloseTo(expectedScores[i], 2)
expectRectClose(det.getBox(), expectedTinyYolov2SeparableConvBoxes[boxOrder[i]], maxBoxDelta)
})
})
})
})
describe('without separable convolutions', () => {
describeWithNets('quantized weights', { withTinyYolov2: { quantized: true, withSeparableConv: false } }, ({ tinyYolov2 }) => {
it('inputSize lg, finds all faces', async () => {
const detections = await tinyYolov2.locateFaces(imgEl, { inputSize: SizeType.LG })
const expectedScores = [0.86, 0.86, 0.85, 0.83, 0.81, 0.81] const expectedScores = [0.86, 0.86, 0.85, 0.83, 0.81, 0.81]
const maxBoxDelta = 3 const maxBoxDelta = 3
const boxOrder = [0, 1, 2, 3, 4, 5] const boxOrder = [0, 1, 2, 3, 4, 5]
...@@ -59,7 +110,7 @@ describe('tinyYolov2', () => { ...@@ -59,7 +110,7 @@ describe('tinyYolov2', () => {
}) })
describeWithNets('uncompressed weights', { withTinyYolov2: { quantized: false } }, ({ tinyYolov2 }) => { describeWithNets('uncompressed weights', { withTinyYolov2: { quantized: false, withSeparableConv: false } }, ({ tinyYolov2 }) => {
it('inputSize lg, finds all faces', async () => { it('inputSize lg, finds all faces', async () => {
const detections = await tinyYolov2.locateFaces(imgEl, { inputSize: SizeType.LG }) const detections = await tinyYolov2.locateFaces(imgEl, { inputSize: SizeType.LG })
...@@ -105,13 +156,17 @@ describe('tinyYolov2', () => { ...@@ -105,13 +156,17 @@ describe('tinyYolov2', () => {
}) })
})
describe('no memory leaks', () => { describe('no memory leaks', () => {
describe('with separable convolutions', () => {
describe('NeuralNetwork, uncompressed model', () => { describe('NeuralNetwork, uncompressed model', () => {
it('disposes all param tensors', async () => { it('disposes all param tensors', async () => {
await expectAllTensorsReleased(async () => { await expectAllTensorsReleased(async () => {
const res = await fetch('base/weights_uncompressed/tiny_yolov2_model.weights') const res = await fetch('base/weights_uncompressed/tiny_yolov2_separable_conv_model.weights')
const weights = new Float32Array(await res.arrayBuffer()) const weights = new Float32Array(await res.arrayBuffer())
const net = faceapi.createTinyYolov2(weights) const net = faceapi.createTinyYolov2(weights)
net.dispose() net.dispose()
...@@ -134,4 +189,35 @@ describe('tinyYolov2', () => { ...@@ -134,4 +189,35 @@ describe('tinyYolov2', () => {
}) })
describe('without separable convolutions', () => {
describe('NeuralNetwork, uncompressed model', () => {
it('disposes all param tensors', async () => {
await expectAllTensorsReleased(async () => {
const res = await fetch('base/weights_uncompressed/tiny_yolov2_model.weights')
const weights = new Float32Array(await res.arrayBuffer())
const net = faceapi.createTinyYolov2(weights, false)
net.dispose()
})
})
})
describe('NeuralNetwork, quantized model', () => {
it('disposes all param tensors', async () => {
await expectAllTensorsReleased(async () => {
const net = new faceapi.TinyYolov2(false)
await net.load('base/weights')
net.dispose()
})
})
})
})
})
}) })
\ No newline at end of file
...@@ -5,8 +5,7 @@ import * as faceapi from '../src/'; ...@@ -5,8 +5,7 @@ import * as faceapi from '../src/';
import { NeuralNetwork } from '../src/commons/NeuralNetwork'; import { NeuralNetwork } from '../src/commons/NeuralNetwork';
import { IPoint } from '../src/'; import { IPoint } from '../src/';
import { allFacesFactory, allFacesMtcnnFactory } from '../src/allFacesFactory'; import { allFacesFactory, allFacesMtcnnFactory } from '../src/allFacesFactory';
import { allFacesMtcnnFunction, allFacesFunction, tinyYolov2 } from '../src/globalApi'; import { allFacesMtcnnFunction, allFacesFunction } from '../src/globalApi';
import { TinyYolov2 } from '../src/tinyYolov2/TinyYolov2';
export function zeros(length: number): Float32Array { export function zeros(length: number): Float32Array {
return new Float32Array(length) return new Float32Array(length)
...@@ -55,6 +54,10 @@ export type WithNetOptions = { ...@@ -55,6 +54,10 @@ export type WithNetOptions = {
quantized?: boolean quantized?: boolean
} }
export type WithTinyYolov2Options = WithNetOptions & {
withSeparableConv?: boolean
}
export type InjectNetArgs = { export type InjectNetArgs = {
allFaces: allFacesFunction allFaces: allFacesFunction
allFacesMtcnn: allFacesMtcnnFunction allFacesMtcnn: allFacesMtcnnFunction
...@@ -73,7 +76,7 @@ export type DescribeWithNetsOptions = { ...@@ -73,7 +76,7 @@ export type DescribeWithNetsOptions = {
withFaceLandmarkNet?: WithNetOptions withFaceLandmarkNet?: WithNetOptions
withFaceRecognitionNet?: WithNetOptions withFaceRecognitionNet?: WithNetOptions
withMtcnn?: WithNetOptions withMtcnn?: WithNetOptions
withTinyYolov2?: WithNetOptions withTinyYolov2?: WithTinyYolov2Options
} }
async function loadNetWeights(uri: string): Promise<Float32Array> { async function loadNetWeights(uri: string): Promise<Float32Array> {
...@@ -102,7 +105,7 @@ export function describeWithNets( ...@@ -102,7 +105,7 @@ export function describeWithNets(
let faceLandmarkNet: faceapi.FaceLandmarkNet = new faceapi.FaceLandmarkNet() let faceLandmarkNet: faceapi.FaceLandmarkNet = new faceapi.FaceLandmarkNet()
let faceRecognitionNet: faceapi.FaceRecognitionNet = new faceapi.FaceRecognitionNet() let faceRecognitionNet: faceapi.FaceRecognitionNet = new faceapi.FaceRecognitionNet()
let mtcnn: faceapi.Mtcnn = new faceapi.Mtcnn() let mtcnn: faceapi.Mtcnn = new faceapi.Mtcnn()
let tinyYolov2: faceapi.TinyYolov2 = new faceapi.TinyYolov2() let tinyYolov2: faceapi.TinyYolov2 = new faceapi.TinyYolov2(options.withTinyYolov2 && options.withTinyYolov2.withSeparableConv)
let allFaces = allFacesFactory(faceDetectionNet, faceLandmarkNet, faceRecognitionNet) let allFaces = allFacesFactory(faceDetectionNet, faceLandmarkNet, faceRecognitionNet)
let allFacesMtcnn = allFacesMtcnnFactory(mtcnn, faceRecognitionNet) let allFacesMtcnn = allFacesMtcnnFactory(mtcnn, faceRecognitionNet)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment