normal.js

'use strict';

/**
 * Analyzer module.
 * @exports analyzer
 */

let tfAnalyzer = require('./tensorflow');
let errorCheck = require('./errorCheck');

/**
 * Calculate statistics for convolution layer
 * 
 * @param  {Array}	input - 2 or 3 dimension array ([height, width] or [height, width, channel])
 * @param  {Array}	filter - 3 dimension array ([height, width, out-channel])
 * @param  {Array}	strides - 2 dimension array ([height, width])
 * @param  {Array}	padding - 2 or 4 dimension array ([right, down] or [up, right, down, left])
 * @param  {Object} options
 * @return {Object}	statistics of the convolution layer
 *
 * @example
 * let input = [5, 5, 3];
 * let filter = [2, 2, 8];
 * let strides = [1, 1];
 * let padding = [0, 0];
 * let stats = analyzer.analyzeConv(input, filter, strides, padding);
 */
function analyzeConv(input, filter, strides, padding, options) {
	options = options || {};
	padding = padding || [0, 0];

	input = errorCheck.check2Or3DInput(input);
	filter = errorCheck.check3DFilter(filter);
	strides = errorCheck.check2Dstrides(strides);
	padding = errorCheck.check2DPadding(padding);

	let width = Math.floor((input[0] - filter[0] + 2 * padding[0]) / strides[0] + 1);
	let height = Math.floor((input[1] - filter[1] + 2 * padding[1]) / strides[1] + 1);
	let channel = filter[2];

	let output = [
		width,
		height,
		channel
	];

	let connectionsPerNeuron =  filter[0] * filter[1] * input[2] + 1;
	let reducedWeights = channel * filter[0] * filter[1] * input[2] + channel;

	if (options['noBias'] === true) {
		connectionsPerNeuron -= 1;
		reducedWeights -= channel;
	}

	let neurons = width * height * channel;
	let weights = neurons * connectionsPerNeuron;

	return {
		output, connectionsPerNeuron, neurons, weights, reducedWeights
	}
}

/**
 * Calculate statistics for pooling layer
 * 
 * @param  {Array}	input - 2 or 3 dimension array ([height, width] or [height, width, channel])
 * @param  {Array}	filter - 2 dimension array ([height, width])
 * @param  {Array}	strides - 2 dimension array ([height, width])
 * @param  {String}	padding - The padding algorithm. Either "VALID" or "SAME".
 * @param  {Object} options
 * @return {Object}	statistics of the pooling layer
 */
function analyzePool(input, filter, strides, padding, options) {
	options = options || {};
	padding = padding || [0, 0];

	input = errorCheck.check2Or3DInput(input);
	filter = errorCheck.check2DFilter(filter);
	strides = errorCheck.check2Dstrides(strides);
	padding = errorCheck.check2DPadding(padding);

	let width = Math.floor((input[0] - filter[0] + 2 * padding[0]) / strides[0] + 1);
	let height = Math.floor((input[1] - filter[1] + 2 * padding[1]) / strides[1] + 1);
	let channel = input[2];

	let output = [
		width,
		height,
		channel
	];

	let neurons = width * height * channel;

	return {
		output, 
		connectionsPerNeuron: 0, 
		neurons, 
		weights: 0
	}
}

/**
 * Calculate statistics for fully-connected layer.
 * 
 * @param  {Number}	input - number of neurons in input layer.
 * @param  {Number}	hidden - number of neurons in hidden layer.
 * @return {Object} statistics
 */
function analyzeFC(input, hidden, options) {
	options = options || {};

	return {
		output: hidden,
		neurons: hidden,
		weights: input * hidden
	}
}

/**
 * Calculate statistics for flatten layer.
 * 
 * @param  {Array}	input - number of neurons in input layer.
 * @param  {Number}	hidden - number of neurons in hidden layer.
 * @return {Object} statistics
 */
function analyzeFlatten(input, hidden, options) {
	options = options || {};

	input = errorCheck.check2Or3DInput(input);

	let connectionsPerNeuron =  0;
	let neurons = 0;
	let weights = input[0] * input[1] * input[2] * hidden;
	let output = hidden;

	return {
		output, connectionsPerNeuron, neurons, weights
	}
}

/**
 * Calculate statistics of a network
 * 
 * @param  {Object} networkJson - network's architecture in json format
 * @param  {Array}	input - 2 or 3 dimension array ([height, width] or [height, width, channel])
 * @param  {Object}	options
 * @return {Object}	statistics
 */
function analyzeNetwork(networkJson, input, options) {
	options = options || {};

	input = errorCheck.check2Or3DInput(input);

	let layers = [];
	let stat;
	let neurons = 0;
	let weights = 0;
	let memory = 0;
	let reducedWeights = 0;

	for (var i = 0; i < networkJson.length; i++) {
		let layer = networkJson[i];
		stat = {};

		if (layer.type === 'conv') {
			// If tensorflow flag is set, user TF analyzer instead
			if (options.tensorflow)
				stat = tfAnalyzer.analyzeConvTF(input, layer.filter, layer.strides, layer.padding, options);
			else
				stat = analyzeConv(input, layer.filter, layer.strides, layer.padding, options);
		} else if (layer.type === 'pool') {
			// If tensorflow flag is set, user TF analyzer instead
			if (options.tensorflow)
				stat = tfAnalyzer.analyzePoolTF(input, layer.filter, layer.strides, layer.padding, options);
			else
				stat = analyzePool(input, layer.filter, layer.strides, layer.padding, options);
		} else if (layer.type === 'fc') {
			if (i > 0 && (networkJson[i - 1].type === 'pool' || networkJson[i - 1].type === 'conv'))
				stat = analyzeFlatten(input, layer.hidden, options);
			else
				stat = analyzeFC(input, layer.hidden, options);
		}

		if (layer.datasize > 0) {
			stat.memory = layer.datasize * stat.neurons;
			memory += stat.memory;
		}

		stat.datasize = layer.datasize;
		stat.type = layer.type;
		stat.name = layer.name || layer.type + (i + 1);
		neurons += stat.neurons;
		weights += stat.weights;

		if (stat.reducedWeights) {
			reducedWeights += stat.reducedWeights;
		} else {
			stat.reducedWeights = stat.weights;
			reducedWeights += stat.weights;
		}

		layers.push(stat);
		input = stat.output;
	}
	return {
		neurons,
		weights,
		reducedWeights,
		layers,
		memory,
		output: stat.output
	};
}

module.exports = {
	analyzeFC,
	analyzeFlatten,
	analyzePool,
	analyzeConv,
	analyzeNetwork
};