Commit 7cbd7500 authored by vincent's avatar vincent

init AgeGenderNet

parent 2d885bb7
import * as tf from '@tensorflow/tfjs-core';
import { NetInput, NeuralNetwork, TNetInput, toNetInput } from 'tfjs-image-recognition-base';
import { fullyConnectedLayer } from '../common/fullyConnectedLayer';
import { seperateWeightMaps } from '../faceProcessor/util';
import { TinyXception } from '../xception/TinyXception';
import { extractParams } from './extractParams';
import { extractParamsFromWeigthMap } from './extractParamsFromWeigthMap';
import { NetOutput, NetParams } from './types';
export class AgeGenderNet extends NeuralNetwork<NetParams> {
private _faceFeatureExtractor: TinyXception
constructor(faceFeatureExtractor: TinyXception = new TinyXception(2)) {
super('AgeGenderNet')
this._faceFeatureExtractor = faceFeatureExtractor
}
public get faceFeatureExtractor(): TinyXception {
return this._faceFeatureExtractor
}
public runNet(input: NetInput | tf.Tensor4D): NetOutput {
const { params } = this
if (!params) {
throw new Error(`${this._name} - load model before inference`)
}
return tf.tidy(() => {
const bottleneckFeatures = input instanceof NetInput
? this.faceFeatureExtractor.forwardInput(input)
: input
const bottleneckFeatures2d = bottleneckFeatures.as2D(bottleneckFeatures.shape[0], -1)
const age = fullyConnectedLayer(bottleneckFeatures2d, params.fc.age).as1D()
const gender = fullyConnectedLayer(bottleneckFeatures2d, params.fc.gender)
return { age, gender }
})
}
public forwardInput(input: NetInput | tf.Tensor4D): NetOutput {
const { age, gender } = this.runNet(input)
return tf.tidy(() => ({ age, gender: tf.softmax(gender) }))
}
public async forward(input: TNetInput): Promise<NetOutput> {
return this.forwardInput(await toNetInput(input))
}
public async predictAgeAndGender(input: TNetInput): Promise<{ age: number, gender: string, genderProbability: number }> {
const netInput = await toNetInput(input)
const out = await this.forwardInput(netInput)
const age = await out.age.data()[0] as number
const probMale = await out.gender.data()[0] as number
const isMale = probMale > 0.5
const gender = isMale ? 'male' : 'female'
const genderProbability = isMale ? probMale : (1 - probMale)
return { age, gender, genderProbability }
}
protected getDefaultModelName(): string {
return 'age_gender_model'
}
public dispose(throwOnRedispose: boolean = true) {
this.faceFeatureExtractor.dispose(throwOnRedispose)
super.dispose(throwOnRedispose)
}
public loadClassifierParams(weights: Float32Array) {
const { params, paramMappings } = this.extractClassifierParams(weights)
this._params = params
this._paramMappings = paramMappings
}
public extractClassifierParams(weights: Float32Array) {
return extractParams(weights)
}
protected extractParamsFromWeigthMap(weightMap: tf.NamedTensorMap) {
const { featureExtractorMap, classifierMap } = seperateWeightMaps(weightMap)
this.faceFeatureExtractor.loadFromWeightMap(featureExtractorMap)
return extractParamsFromWeigthMap(classifierMap)
}
protected extractParams(weights: Float32Array) {
const classifierWeightSize = (512 * 1) + (512 * 2)
const featureExtractorWeights = weights.slice(0, weights.length - classifierWeightSize)
const classifierWeights = weights.slice(weights.length - classifierWeightSize)
this.faceFeatureExtractor.extractWeights(featureExtractorWeights)
return this.extractClassifierParams(classifierWeights)
}
}
\ No newline at end of file
import { TfjsImageRecognitionBase } from 'tfjs-image-recognition-base';
import { NetParams } from './types';
export function extractParams(weights: Float32Array): { params: NetParams, paramMappings: TfjsImageRecognitionBase.ParamMapping[] } {
const paramMappings: TfjsImageRecognitionBase.ParamMapping[] = []
const {
extractWeights,
getRemainingWeights
} = TfjsImageRecognitionBase.extractWeightsFactory(weights)
const extractFCParams = TfjsImageRecognitionBase.extractFCParamsFactory(extractWeights, paramMappings)
const age = extractFCParams(512, 1, 'fc_age')
const gender = extractFCParams(512, 2, 'fc_gender')
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`)
}
return {
paramMappings,
params: { fc: { age, gender } }
}
}
\ No newline at end of file
import * as tf from '@tensorflow/tfjs-core';
import { TfjsImageRecognitionBase } from 'tfjs-image-recognition-base';
import { NetParams } from './types';
export function extractParamsFromWeigthMap(
weightMap: tf.NamedTensorMap
): { params: NetParams, paramMappings: TfjsImageRecognitionBase.ParamMapping[] } {
const paramMappings: TfjsImageRecognitionBase.ParamMapping[] = []
const extractWeightEntry = TfjsImageRecognitionBase.extractWeightEntryFactory(weightMap, paramMappings)
function extractFcParams(prefix: string): TfjsImageRecognitionBase.FCParams {
const weights = extractWeightEntry<tf.Tensor2D>(`${prefix}/weights`, 2)
const bias = extractWeightEntry<tf.Tensor1D>(`${prefix}/bias`, 1)
return { weights, bias }
}
const params = {
fc: {
age: extractFcParams('fc_age'),
gender: extractFcParams('fc_gender')
}
}
TfjsImageRecognitionBase.disposeUnusedWeightTensors(weightMap, paramMappings)
return { params, paramMappings }
}
\ No newline at end of file
export * from './AgeGenderNet';
export * from './types';
\ No newline at end of file
import * as tf from '@tensorflow/tfjs-core';
import { TfjsImageRecognitionBase } from 'tfjs-image-recognition-base';
export type NetOutput = { age: tf.Tensor1D, gender: tf.Tensor2D }
export type NetParams = {
fc: {
age: TfjsImageRecognitionBase.FCParams
gender: TfjsImageRecognitionBase.FCParams
}
}
\ No newline at end of file
import * as tf from '@tensorflow/tfjs-core';
import { TfjsImageRecognitionBase } from 'tfjs-image-recognition-base';
import { depthwiseSeparableConv } from './depthwiseSeparableConv';
import { depthwiseSeparableConv } from '../common/depthwiseSeparableConv';
import { DenseBlock3Params, DenseBlock4Params } from './types';
export function denseBlock3(
......
import * as tf from '@tensorflow/tfjs-core';
import { TfjsImageRecognitionBase } from 'tfjs-image-recognition-base';
import { DenseBlock3Params, DenseBlock4Params } from './types';
export function extractorsFactory(extractWeights: TfjsImageRecognitionBase.ExtractWeightsFunction, paramMappings: TfjsImageRecognitionBase.ParamMapping[]) {
function extractSeparableConvParams(channelsIn: number, channelsOut: number, mappedPrefix: string): TfjsImageRecognitionBase.SeparableConvParams {
const depthwise_filter = tf.tensor4d(extractWeights(3 * 3 * channelsIn), [3, 3, channelsIn, 1])
const pointwise_filter = tf.tensor4d(extractWeights(channelsIn * channelsOut), [1, 1, channelsIn, channelsOut])
const bias = tf.tensor1d(extractWeights(channelsOut))
paramMappings.push(
{ paramPath: `${mappedPrefix}/depthwise_filter` },
{ paramPath: `${mappedPrefix}/pointwise_filter` },
{ paramPath: `${mappedPrefix}/bias` }
)
return new TfjsImageRecognitionBase.SeparableConvParams(
depthwise_filter,
pointwise_filter,
bias
)
}
const extractConvParams = TfjsImageRecognitionBase.extractConvParamsFactory(extractWeights, paramMappings)
const extractSeparableConvParams = TfjsImageRecognitionBase.extractSeparableConvParamsFactory(extractWeights, paramMappings)
function extractDenseBlock3Params(channelsIn: number, channelsOut: number, mappedPrefix: string, isFirstLayer: boolean = false): DenseBlock3Params {
......
......@@ -6,6 +6,7 @@ export {
export * from 'tfjs-image-recognition-base';
export * from './ageGenderNet/index';
export * from './classes/index';
export * from './dom/index'
export * from './faceExpressionNet/index';
......
import * as tf from '@tensorflow/tfjs-core';
import {
NetInput,
NeuralNetwork,
normalize,
range,
TfjsImageRecognitionBase,
TNetInput,
toNetInput,
} from 'tfjs-image-recognition-base';
import { depthwiseSeparableConv } from '../common/depthwiseSeparableConv';
import { extractParams } from './extractParams';
import { extractParamsFromWeigthMap } from './extractParamsFromWeigthMap';
import { MainBlockParams, ReductionBlockParams, TinyXceptionParams } from './types';
function conv(x: tf.Tensor4D, params: TfjsImageRecognitionBase.ConvParams, stride: [number, number]): tf.Tensor4D {
return tf.add(tf.conv2d(x, params.filters, stride, 'same'), params.bias)
}
function reductionBlock(x: tf.Tensor4D, params: ReductionBlockParams, isActivateInput: boolean = true): tf.Tensor4D {
let out = isActivateInput ? tf.relu(x) : x
out = depthwiseSeparableConv(out, params.separable_conv0, [1, 1])
out = depthwiseSeparableConv(tf.relu(out), params.separable_conv1, [1, 1])
out = tf.maxPool(out, [3, 3], [2, 2], 'same')
out = tf.add(out, conv(x, params.expansion_conv, [2, 2]))
return out
}
function mainBlock(x: tf.Tensor4D, params: MainBlockParams): tf.Tensor4D {
let out = depthwiseSeparableConv(tf.relu(x), params.separable_conv0, [1, 1])
out = depthwiseSeparableConv(tf.relu(out), params.separable_conv1, [1, 1])
out = depthwiseSeparableConv(tf.relu(out), params.separable_conv2, [1, 1])
out = tf.add(out, x)
return out
}
export class TinyXception extends NeuralNetwork<TinyXceptionParams> {
private _numMainBlocks: number
constructor(numMainBlocks: number) {
super('TinyXception')
this._numMainBlocks = numMainBlocks
}
public forwardInput(input: NetInput): tf.Tensor4D {
const { params } = this
if (!params) {
throw new Error('TinyXception - load model before inference')
}
return tf.tidy(() => {
const batchTensor = input.toBatchTensor(112, true)
const meanRgb = [122.782, 117.001, 104.298]
const normalized = normalize(batchTensor, meanRgb).div(tf.scalar(255)) as tf.Tensor4D
let out = tf.relu(conv(normalized, params.entry_flow.conv_in, [2, 2]))
out = reductionBlock(out, params.entry_flow.reduction_block_0, false)
out = reductionBlock(out, params.entry_flow.reduction_block_1)
range(this._numMainBlocks, 0, 1).forEach((idx) => {
out = mainBlock(out, params.middle_flow[`main_block_${idx}`])
})
out = reductionBlock(out, params.exit_flow.reduction_block)
out = tf.relu(depthwiseSeparableConv(out, params.exit_flow.separable_conv, [1, 1]))
return out
})
}
public async forward(input: TNetInput): Promise<tf.Tensor4D> {
return this.forwardInput(await toNetInput(input))
}
protected getDefaultModelName(): string {
return 'tiny_xception_model'
}
protected extractParamsFromWeigthMap(weightMap: tf.NamedTensorMap) {
return extractParamsFromWeigthMap(weightMap)
}
protected extractParams(weights: Float32Array) {
return extractParams(weights, this._numMainBlocks)
}
}
\ No newline at end of file
import { range, TfjsImageRecognitionBase } from 'tfjs-image-recognition-base';
import { MainBlockParams, ReductionBlockParams, TinyXceptionParams } from './types';
function extractorsFactory(extractWeights: TfjsImageRecognitionBase.ExtractWeightsFunction, paramMappings: TfjsImageRecognitionBase.ParamMapping[]) {
const extractConvParams = TfjsImageRecognitionBase.extractConvParamsFactory(extractWeights, paramMappings)
const extractSeparableConvParams = TfjsImageRecognitionBase.extractSeparableConvParamsFactory(extractWeights, paramMappings)
function extractReductionBlockParams(channelsIn: number, channelsOut: number, mappedPrefix: string): ReductionBlockParams {
const separable_conv0 = extractSeparableConvParams(channelsIn, channelsOut, `${mappedPrefix}/separable_conv0`)
const separable_conv1 = extractSeparableConvParams(channelsOut, channelsOut, `${mappedPrefix}/separable_conv1`)
const expansion_conv = extractConvParams(channelsIn, channelsOut, 1, `${mappedPrefix}/expansion_conv`)
return { separable_conv0, separable_conv1, expansion_conv }
}
function extractMainBlockParams(channels: number, mappedPrefix: string): MainBlockParams {
const separable_conv0 = extractSeparableConvParams(channels, channels, `${mappedPrefix}/separable_conv0`)
const separable_conv1 = extractSeparableConvParams(channels, channels, `${mappedPrefix}/separable_conv1`)
const separable_conv2 = extractSeparableConvParams(channels, channels, `${mappedPrefix}/separable_conv2`)
return { separable_conv0, separable_conv1, separable_conv2 }
}
return {
extractConvParams,
extractSeparableConvParams,
extractReductionBlockParams,
extractMainBlockParams
}
}
export function extractParams(weights: Float32Array, numMainBlocks: number): { params: TinyXceptionParams, paramMappings: TfjsImageRecognitionBase.ParamMapping[] } {
const paramMappings: TfjsImageRecognitionBase.ParamMapping[] = []
const {
extractWeights,
getRemainingWeights
} = TfjsImageRecognitionBase.extractWeightsFactory(weights)
const {
extractConvParams,
extractSeparableConvParams,
extractReductionBlockParams,
extractMainBlockParams
} = extractorsFactory(extractWeights, paramMappings)
const entry_flow_conv_in = extractConvParams(3, 32, 3, 'entry_flow/conv_in')
const entry_flow_reduction_block_0 = extractReductionBlockParams(32, 64, 'entry_flow/reduction_block_0')
const entry_flow_reduction_block_1 = extractReductionBlockParams(64, 128, 'entry_flow/reduction_block_1')
const entry_flow = {
conv_in: entry_flow_conv_in,
reduction_block_0: entry_flow_reduction_block_0,
reduction_block_1: entry_flow_reduction_block_1
}
const middle_flow = {}
range(numMainBlocks, 0, 1).forEach((idx) => {
middle_flow[`main_block_${idx}`] = extractMainBlockParams(128, `middle_flow/main_block_${idx}`)
})
const exit_flow_reduction_block = extractReductionBlockParams(128, 256, 'exit_flow/reduction_block')
const exit_flow_separable_conv = extractSeparableConvParams(256, 512, 'exit_flow/separable_conv')
const exit_flow = {
reduction_block: exit_flow_reduction_block,
separable_conv: exit_flow_separable_conv
}
if (getRemainingWeights().length !== 0) {
throw new Error(`weights remaing after extract: ${getRemainingWeights().length}`)
}
return {
paramMappings,
params: { entry_flow, middle_flow, exit_flow }
}
}
\ No newline at end of file
import * as tf from '@tensorflow/tfjs-core';
import { TfjsImageRecognitionBase } from 'tfjs-image-recognition-base';
import { TinyXceptionParams } from './types';
export function extractParamsFromWeigthMap(
weightMap: tf.NamedTensorMap
): { params: TinyXceptionParams, paramMappings: TfjsImageRecognitionBase.ParamMapping[] } {
throw "extractParamsFromWeigthMap not implemented";
const paramMappings: TfjsImageRecognitionBase.ParamMapping[] = []
TfjsImageRecognitionBase.disposeUnusedWeightTensors(weightMap, paramMappings)
return { params: {} as any, paramMappings }
}
\ No newline at end of file
export * from './TinyXception';
\ No newline at end of file
import { TfjsImageRecognitionBase } from "tfjs-image-recognition-base";
export type ReductionBlockParams = {
separable_conv0: TfjsImageRecognitionBase.SeparableConvParams
separable_conv1: TfjsImageRecognitionBase.SeparableConvParams
expansion_conv: TfjsImageRecognitionBase.ConvParams
}
export type MainBlockParams = {
separable_conv0: TfjsImageRecognitionBase.SeparableConvParams
separable_conv1: TfjsImageRecognitionBase.SeparableConvParams
separable_conv2: TfjsImageRecognitionBase.SeparableConvParams
}
export type TinyXceptionParams = {
entry_flow: {
conv_in: TfjsImageRecognitionBase.ConvParams
reduction_block_0: ReductionBlockParams
reduction_block_1: ReductionBlockParams
}
middle_flow: any,
exit_flow: {
reduction_block: ReductionBlockParams
separable_conv: TfjsImageRecognitionBase.SeparableConvParams
}
}
\ No newline at end of file
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