[playground] Tensorflow.Js & Typescript [4]: Quantile Regression

Posted at — Jan 23, 2023

Quantile Regression is one of the practical techniques for many real problems. In this tutorial, we will implement a custom loss for TensorflowJS.

Quantile Loss:

$$\mathcal{L}(y_{\mathtt{true}}, y_{\mathtt{pred}}) = \begin{cases} (y_{\mathtt{true}} - y_{\mathtt{pred}}) \alpha &\text{if } y_{\mathtt{true}} \ge y_{\mathtt{pred}} \\ (y_{\mathtt{true}} - y_{\mathtt{pred}}) (\alpha - 1) &\text{if } y_{\mathtt{true}} < y_{\mathtt{pred}} \end{cases} $$

Or for easy computing:

$$\mathcal{L}(y_{\mathtt{true}}, y_{\mathtt{pred}}) = \mathtt{max}((y_{\mathtt{true}} - y_{\mathtt{pred}}) \alpha, (y_{\mathtt{true}} - y_{\mathtt{pred}}) (\alpha - 1)) $$

Quantile Regression:

Github: https://github.com/quangtiencs/tensorflowjs_typescript_tutorials/tree/main/004_quantile_regression/webbrowser_tensorflowjs_typescript


import { Tensor, Tensor2D, tensor2d } from "@tensorflow/tfjs";
import embed from "vega-embed";
import * as tf from "@tensorflow/tfjs";

try {
  // I know. I'm lazy :-D
  tf.setBackend("webgl");
} catch (err) {
  console.log(err);
}

document.querySelector("#device")!!.innerHTML =
  `Backend by: ${tf.getBackend()}`;

function make_synthetic_data(true_w: number, true_b: number) {
  let x: tf.Tensor1D = tf.randomUniform([500], 0.0, 2.0);
  let noise = tf.randomNormal([500], 0, 0.8);
  let y = x.mul(tf.scalar(true_w)).add(tf.scalar(true_b)).add(noise);

  return { "x": x, "y": y };
}

let data = make_synthetic_data(2, 3);

let vegaData: { x: number; y: number }[] = [];
for (let i = 0; i < data.x.size; i++) {
  vegaData.push({ "x": data.x.dataSync()[i], "y": data.y.dataSync()[i] });
}

function quantileLoss90(yTrue: tf.Tensor, yPred: tf.Tensor) {
  let error = yTrue.sub(yPred);
  let alpha = 0.9;
  return tf.maximum(error.mul(alpha), error.mul(alpha - 1.0)).mean();
}

let model = tf.sequential();
model.add(tf.layers.dense({ inputShape: [1], units: 1 }));
model.add(tf.layers.dense({ units: 1 }));

model.compile({
  optimizer: "sgd",
  loss: quantileLoss90,
});

model.summary();

model.fit(data.x, data.y, { epochs: 100, batchSize: 64 }).then((history) => {
  document.querySelector("#status")!!.innerHTML = "Completed :D";
  document.querySelector("#status")!!.setAttribute("style", "color: green");

  const prediction = (model.predict(data.x) as tf.Tensor)
    .dataSync();
  //   console.log(`data sync ${prediction}`);

  console.log("[Train Model] Completed!");
  console.log("Loss Here :D");
  console.log(history);

  let finalPlotData = [];
  for (let i = 0; i < data.x.size; i++) {
    finalPlotData.push({
      "x": data.x.dataSync()[i],
      "y": data.y.dataSync()[i],
      "y_hat": prediction[i],
    });
  }

  // @ts-ignore
  var specquantile = {
    "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
    "width": 400,
    "height": 200,
    data: {
      values: finalPlotData,
    },
    "layer": [{
      "mark": "circle",
      "encoding": {
        "x": { "field": "x", "type": "quantitative" },
        "y": { "field": "y", "type": "quantitative" },
      },
    }, {
      "mark": "line",
      "encoding": {
        "x": { "field": "x", "type": "quantitative" },
        "y": { "field": "y_hat", "type": "quantitative" },
        "color": { "value": "red" },
      },
    }],
  };

  // @ts-ignore
  embed("#quantileregression", specquantile);
});

var specDataPoint = {
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
  "width": 400,
  "height": 200,
  data: {
    values: vegaData,
  },
  "mark": "circle",
  "encoding": {
    "x": { "field": "x", "type": "quantitative" },
    "y": { "field": "y", "type": "quantitative" },
  },
};

// @ts-ignore
embed("#vis", specDataPoint);

:-) and… don’t forget tf.tidy

Example 1