Testing TensorFlow.js with Jasmine
Jasmine is a popular testing library for JavaScript that enables Behavior Driven Development (BDD).
In the following snippet there is a “Tensor suite” with a single spec that tests two scalars for equality with the toEqual()
matcher:
describe('Tensor suite', () => {
it('Tensors should be equal', () => {
const a = tf.scalar(1);
const b = tf.scalar(1);
expect(a).toEqual(b);
// > Expected $.dataId.id = 2 to equal 3.
// > Expected $.id = 2 to equal 3.
});
});
The toEqual()
matcher recursively checks the properties of the two objects for equality.
The test above fails as the unique IDs and DataID of the two tf.Tensor objects differ.
For that reason a custom equality tester for tf.Tensor must be implemented.
Currently, there is no tf.assertEqual()
function for tensors in TensorFlow.js as the authors would have had to make the decission to make it either synchronous or asynchronous.
For test cases we can implement the custom equality tester like this:
import {Tensor} from "@tensorflow/tfjs-core/dist/tensor";
function tensorTester(a: any, b: any): boolean | void {
if (a instanceof Tensor && b instanceof Tensor) {
return a.equal(b).all().dataSync()[0] === 1;
}
}
beforeEach(() => {
jasmine.addCustomEqualityTester(tensorTester);
});
Now our “Tensors should be equal” spec passes.
The next problem I encountered when I tried to run a spec in a GitHub Actions CI environment. While the test I executed locally with Chrome didn’t fail, it failed with an assertion error that a tensor had a wrong value. Upon closer inspection I realized that my local test environment with Chrome ran on a WebGL backend while the GitHub Action used a headless Chrome browser that has a CPU backend:
const numerator = tf.scalar(0.004609584808349609);
const denominator = tf.scalar(0.007243633270263672);
numerator.div(denominator).print();
console.log(getBackend());
// > 0.6363636255264282 -> for 'cpu' backend
// > 0.636363685131073 -> for 'webgl' backend
The precision problem above can be reproduced when running a spec with both, Chrome and a headless Chrome (Karma flag --browsers=ChromeHeadlessCI,Chrome
).
TensorFlow.js might only support 16 bit floating point textures in some environments which can lead to problems with models that are trained with 32 bit floating point weights and activations.
To check if 32 bit floating point textures are supported the flags WEBGL_RENDER_FLOAT32_CAPABLE
and WEBGL_RENDER_FLOAT32_ENABLED
can be queried with tf.ENV.getBool()
.
For spec execution the CPU backend can also be forced with setBackend('cpu')
so there are the same results for headful and headless browsers.