Distillation is another shrinkage technique that is also pretty foolproof, but allows you to pick an arbitrary architecture - this way, you can make full use of whatever hardware you're deploying too.
Distillation is another shrinkage technique that is also pretty foolproof, but allows you to pick an arbitrary architecture - this way, you can make full use of whatever hardware you're deploying too.
I would love to be able to model each artificial neuron as a separate object in code, making it trivial to group/ungroup neurons into irregularly shaped and/or overlapping groups and easily shrink/expand/alter such groups on the fly without incurring a performance penalty. Easy pruning would be just one application.
As you point out, right now, those things not practical with frameworks like PyTorch and TensorFlow if we want decent performance. Currently we have little choice but to pigeonhole all our designs explicitly into layers that operate on grid-like structures of fixed size and resort to things like masking so we can have fast multiply-sum operations.
By coincidence I was just talking about this on an unrelated thread: https://news.ycombinator.com/item?id=23118348
I hope you don't mean object in the OOP sense, but in a more general way, because that would be the slowest thing ever, no matter how smart your compiler/framework.
You'd probably be better off to just have each group as a copy of the original one.
> not practical with frameworks like PyTorch and TensorFlow
The problem is not the frameworks, the problem is hardware, and physics.
We've hit the wall with clock speeds, we've hit the wall with pipeline depth, we've hit the wall with branch prediction.
The only thing we have left is specialised hardware, parallel hardware, and bigger caches. But all of these only work with SIMD, because it makes data fetches predictable, it makes the cores simpler and it makes adding more compute cheaper (no separate fetch decode silicon required).
For the memory hierarchy to work you want stuff packed like in ECS systems for games, or Matrices for ML.
Even with specialised hardware, changing your neural architecture on the fly would be ridiculously expensive because of all the communication overhead.
[a] https://dl.acm.org/doi/pdf/10.1145/3317550.3321441?download=...
There's a big "impedance mismatch" between (1) "programmability" (by which I mean, being able to write high-level code that under-the-hood requires dynamic modification, reshaping, and/or combination-optimization of those adjacency matrices you mention, without worrying about performance) and (2) existing infrastructure (frameworks that rely on highly optimized computation "kernels" that cleverly exploit the memory layout and other features of accelerated hardware, i.e., GPUs/TPUs).
Then you can calculate the results of applying the weights in the first batch to every example in a minibatch in parallel. Then you move on to calculate the results of applying the activations in the second batch to the results of the first batch, and so on until you have finished all the batches.
The hard part is making the batches, although it feels like the kind of classical algorithms 101 problem that has some great solutions out there already. Maybe it is an application of graph coloring or something.
I have not really pursued this because I'm not sure if there is a benefit to breaking the neurons out of their orderly layers. It would be interesting to just try training a few randomly generated networks, but I haven't had the time so far.
Email me at hi at jehan dot email if you want to talk about this.
Say we want to build a CNN that accepts batches of images of different size without having to resize and/or pad them to the same size. Or say we want to build a convolutional layer in which kernels need not be rectangular and of the same shape and size but can be of different shapes and sizes (e.g., circular kernels that vary in size as we move away from the center of attention). Or say we want to remove connections with low weights (as proposed in this paper) and randomly add new connections between arbitrary individual neurons in different layers between backprop updates.
These are just a few examples off the top of my head of things that in principle would be easy to code in any modern high-level language (e.g., Python, Swift) but look very hard to get to run efficiently using today's software + hardware infrastructure (PyTorch/TensorFlow + GPUs/TPUs).
I am fortunate enough to have a friend that's fairly accomplished in ML and a really nice guy. He spent a video chat + code session with me where he helped me build an NN from scratch in Typescript, we replicated the network in the paper here:
https://mattmazur.com/2015/03/17/a-step-by-step-backpropagat...
Here's the Codesandbox link, if you notice the console output it (approximately) matches the figures from the paper above:
https://codesandbox.io/s/node-typescript-vqszi?file=/example...
I don't really understand much about ML or neural networks, but from a code perspective, if you have these data structures:
class Neuron {
id: number | void = neuronUidGen.next().value;
incoming: Connection[];
outgoing: Connection[];
rate: number;
bias: number;
output: number;
error: number;
}
class Connection {
id: number | void = connectionUidGen.next().value;
from: Neuron;
to: Neuron;
weight: number;
}
Why couldn't you manipulate these however you see fit? There's a Layer implementation in there that's commented out because it doesn't function right that could roughly accomplish something like this theoretically, right?Surely in PyTorch/TF, the ND-Arrays of NN's must also be associated to an object reference?
> existing frameworks (PyTorch/TensorFlow) and accelerated hardware (GPUs/TPUs) which operate on neurons arranged as arrays of the same shape.
const dot = (inputs: number[], weights: number[]) => {
let total = 0;
const length = Math.min(inputs.length, weights.length);
for (let i = 0; i < length; i++) {
total += inputs[i] * weights[i];
}
return total;
};
activate(input?: number): number {
if (!this.incoming.length) {
this.output = input;
} else {
const inputs = this.incoming.map(connection => connection.from.output)
const weights = this.incoming.map(connection => connection.weight);
this.output = squash(dot(inputs, weights) + this.bias);
}
return this.output;
}
This is on principle the same concept right?Only then would it make sense to consider a different computational approach.
Edited: This is the paper:https://news.ycombinator.com/item?id=22502131
Perhaps both techniques can be used together, since the paper take advantage of sparcity to beat gpu when computation are on a sparse subset.
I you parallelize your computation along the training data dimension, the problem is embarrassingly parallel and the structure of your neural network does not matter on bit.
Could you elaborate a little on this aspect?