Skip to content

Commit

Permalink
More samples
Browse files Browse the repository at this point in the history
  • Loading branch information
DonJayamanne committed Sep 2, 2021
1 parent 0a8b94b commit ce44dcc
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 29 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ However, this extension leverages the power of Notebooks to provide the same ric
* node.js needs to be in the current path

## Roadmap
* [Vega](https://vega.github.io/vega/) plots without having to install vega
* Support user input (node.js `readline` for basic input in scripts)
* Open a plain js/ts file as a notebook & vice versa.
* [Vega](https://vega.github.io/vega/) plots without having to install vega


### Known issues, workarounds and technical details
* See [here](https://github.com/DonJayamanne/typescript-notebook/wiki/Kernel-behaviour-(known-issues-&-workarounds)) for more details
Expand Down
154 changes: 154 additions & 0 deletions resources/docs/tensorflow/carPrice.nnb
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
{
"cells": [
{
"language": "markdown",
"source": [
"# A Non-linear regression model for Used Car price Prediction\nCreation of a simple non linear regression model for used Audi car price from the given data set\n\n[https://medium.com/@swathylenjini/non-linear-regression-for-used-car-price-prediction-model-tensorflow-js-bcce05f970c5](https://medium.com/@swathylenjini/non-linear-regression-for-used-car-price-prediction-model-tensorflow-js-bcce05f970c5)\n\n### SWATHY L"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"# Prerequisites\n\n* Download audi.csvs from https://www.kaggle.com/adityadesai13/used-car-dataset-ford-and-mercedes?select=audi.csv\n* install `@tensorflow/tfjs-node`, `@tensorflow/tfjs-vis`"
],
"outputs": []
},
{
"language": "typescript",
"source": [
"const tf = require('@tensorflow/tfjs-node') as typeof import('@tensorflow/tfjs-node');\nconst tfvis = require('@tensorflow/tfjs-vis') as typeof import('@tensorflow/tfjs-vis');"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"async function plot(pointsArray, featureName) {\n tfvis.render.scatterplot(\n { name: `${featureName} vs Car Price` },\n { values: [pointsArray], series: ['original'] },\n {\n xLabel: featureName,\n yLabel: 'Price',\n }\n )\n}"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"# Case 1: Lets evaluate the model using mileage as a feature."
],
"outputs": []
},
{
"language": "javascript",
"source": [
"// Import data\nconst audi_data = tf.data.csv('file://<fully qualified file path>/audi.csv');\n// Extract x and y values to plot in graph\nconst dataSet = audi_data.map(record => ({\n x: record.mileage,\n y: record.price,\n}));\nconst points = await dataSet.toArray();\nplot(points, 'Mileage');"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"function normalise(tensor) {\n const min = tensor.min();\n const max = tensor.max();\n const normalisedTensor = tensor.sub(min).div(max.sub(min));\n return {\n tensor: normalisedTensor,\n min,\n max\n };\n}\n"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"//Shuffle\ntf.util.shuffle(points);\n// Extract Features (inputs)\nconst featureValues = points.map(p => p.x);\nconst featureTensor = tf.tensor2d(featureValues, [featureValues.length, 1]);\n// Extract Labels (outputs)\nconst labelValues = points.map(p => p.y);\nconst labelTensor = tf.tensor2d(labelValues, [labelValues.length, 1]);\n// Normalise features and labels\nconst normalisedFeature = normalise(featureTensor);\nconst normalisedLabel = normalise(labelTensor);\n"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"const [trainingFeatureTensor, testingFeatureTensor] = tf.split(normalisedFeature.tensor, 2);\nconst [trainingLabelTensor, testingLabelTensor] = tf.split(normalisedLabel.tensor, 2);"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"function createModel() {\n model = tf.sequential();\n model.add(tf.layers.dense({\n units: 1,\n useBias: true,\n activation: 'sigmoid',\n inputDim: 1\n }));\n model.compile({\n loss: 'meanSquaredError',\n optimizer: tf.train.adam()\n });\n return model;\n}"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"//Create Model\nconst model = createModel();"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"async function trainModel(model, trainingFeatureTensor, trainingLabelTensor, trainingLabel) {\n const { onBatchEnd, onEpochEnd } = tfvis.show.fitCallbacks({ name: trainingLabel }, ['loss']);\n return model.fit(trainingFeatureTensor, trainingLabelTensor, {\n batchSize: 32,\n epochs: 20,\n validationSplit: 0.2,\n callbacks: {\n onEpochEnd,\n }\n });\n}"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"//Train Model\nconst result = await trainModel(model, trainingFeatureTensor, trainingLabelTensor, \"Training Performance\");"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"const trainingLoss = result.history.loss.pop();\nconsole.log(`Training loss: ${trainingLoss}`);\nconst validationLoss = result.history.val_loss.pop();\nconsole.log(`Validation loss: ${validationLoss}`);"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"//Evaluate Model\nconst lossTensor = model.evaluate(testingFeatureTensor, testingLabelTensor);\nconst loss = await lossTensor.dataSync();\nconsole.log(`Testing loss: ${loss}`);"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"# Case 2: Let's evaluate the model using mpg as a feature."
],
"outputs": []
},
{
"language": "javascript",
"source": [
"// Import data\nconst audi_data = tf.data.csv('file://<fully qualified file path>/audi.csv');\n// Extract x and y values to plot in graph\nconst dataSet = audi_data.map(record => ({\n x: record.mpg,\n y: record.price,\n}));\nconst points = await dataSet.toArray();\nplot(points, 'MPG');\n"
],
"outputs": []
},
{
"language": "markdown",
"source": [
"# Case 3: Lets evaluate model using both mileage and mpg as features for price prediction model. "
],
"outputs": []
},
{
"language": "javascript",
"source": [
"function createModel() {\n model = tf.sequential();\n model.add(tf.layers.dense({\n units: 10,\n useBias: true,\n activation: 'sigmoid',\n inputDim: 2,\n }));\n model.add(tf.layers.dense({\n units: 1,\n useBias: true,\n activation: 'sigmoid',\n }));\n model.compile({\n loss: 'meanSquaredError',\n optimizer: tf.train.adam()\n });\n return model;\n}"
],
"outputs": []
},
{
"language": "javascript",
"source": [
"const dataSet = audi_data.map((record) => ({\n x1: record.mpg,\n x2: record.mileage,\n y: record.price\n}));\nconst points = await dataSet.toArray();\n//Shuffle\ntf.util.shuffle(points);\n// Extract and normalise features\nconst normalisedFeature1 = normalise(tf.tensor2d(points.map((p) => [p.x1]))).tensor.dataSync();\nconst normalisedFeature2 = normalise(tf.tensor2d(points.map((p) => [p.x2]))).tensor.dataSync();\nconst normFeatureInput = [];\nnormalisedFeature1.forEach((item, i, self) => { normFeatureInput.push([normalisedFeature1[i], normalisedFeature2[i]]) })\n// Extract and normalise Labels (outputs)\nconst labelValues = points.map(p => p.y);\nconst labelTensor = tf.tensor2d(labelValues, [labelValues.length, 1]);\nconst normalisedLabel = normalise(labelTensor);\n//Training and testing data sets\nconst [trainingFeatureTensor, testingFeatureTensor] = tf.split(normFeatureInput, 2);\nconst [trainingLabelTensor, testingLabelTensor] = tf.split(normalisedLabel.tensor, 2);\n//Create Model\nconst model = createModel();\n//Train Model\nconst result = await trainModel(model, trainingFeatureTensor, trainingLabelTensor, \"Training Performance (case 3)\");\nconst trainingLoss = result.history.loss.pop();\nconsole.log(`Training loss: ${trainingLoss}`);\nconst validationLoss = result.history.val_loss.pop();\nconsole.log(`Validation loss: ${validationLoss}`);\n//Evaluate Model\nconst lossTensor = model.evaluate(testingFeatureTensor, testingLabelTensor);\nconst loss = await lossTensor.dataSync();\nconsole.log(`Testing loss: ${loss}`);"
],
"outputs": [],
"metadata": {
"outputCollapsed": true
}
},
{
"language": "javascript",
"source": [
"tfvis.show.history({ name: 'Case 3 Training History' }, result, ['loss'])"
],
"outputs": []
}
]
}
6 changes: 6 additions & 0 deletions src/extension/content/walkThrough.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ samples.push({
description: 'tensorflow.js',
path: path.join('resources', 'docs', 'tensorflow', 'mnist.nnb')
});
samples.push({
command: 'node.notebook.sample.tensorflow.carprice',
label: 'Non-linear regression model for used car price prediction',
description: 'tensorflow.js',
path: path.join('resources', 'docs', 'tensorflow', 'carPrice.nnb')
});
// Plotly
samples.push({
command: 'node.notebook.sample.plotly.generate',
Expand Down
61 changes: 34 additions & 27 deletions src/extension/kernel/cellOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { CellDiagnosticsProvider } from './problems';
import { Compiler } from './compiler';
import { DisplayData, TensorFlowVis } from '../server/types';
import { createDeferred, Deferred, noop, sleep } from '../coreUtils';
import { createDeferred, Deferred, noop } from '../coreUtils';

const taskMap = new WeakMap<NotebookCell, CellOutput>();
const tfVisContainersInACell = new WeakMap<NotebookCell, Set<string>>();
Expand Down Expand Up @@ -62,7 +62,11 @@ export class CellOutput {
}
return this.originalTask;
}
constructor(private originalTask: NotebookCellExecution, private readonly controller: NotebookController) {
constructor(
private originalTask: NotebookCellExecution,
private readonly controller: NotebookController,
private readonly requestId: string
) {
this.cell = originalTask.cell;
this.rendererComms.onDidReceiveMessage((e) => {
if (typeof e.message !== 'object' || !e.message) {
Expand Down Expand Up @@ -105,38 +109,31 @@ export class CellOutput {
}
taskMap.delete(this.cell);
}
public static getOrCreate(task: NotebookCellExecution, controller: NotebookController) {
taskMap.set(task.cell, taskMap.get(task.cell) || new CellOutput(task, controller));
public static getOrCreate(task: NotebookCellExecution, controller: NotebookController, requestId: string) {
taskMap.set(task.cell, taskMap.get(task.cell) || new CellOutput(task, controller, requestId));
const output = taskMap.get(task.cell)!;
output.setTask(task);
return output;
}
private lastOutputStream?: { output: NotebookCellOutput; stream: 'stdout' | 'stderr'; id: string };
public appendStreamOutput(value: string, stream: 'stdout' | 'stderr') {
if (value.length === 0) {
return;
}
this.promise = this.promise
.finally(async () => {
value = Compiler.fixCellPathsInStackTrace(this.cell.notebook, value);
const cell = this.task.cell;
let output: NotebookCellOutput | undefined;
if (cell.outputs.length) {
const lastOutput = cell.outputs[cell.outputs.length - 1];
const expectedMime =
stream === 'stdout'
? 'application/vnd.code.notebook.stdout'
: 'application/vnd.code.notebook.stderr';
if (lastOutput.items.length === 1 && lastOutput.items[0].mime === expectedMime) {
output = lastOutput;
}
}
if (output && this.cell.outputs) {
const output: NotebookCellOutput | undefined =
this.lastOutputStream && this.lastOutputStream.stream === stream
? this.cell.outputs.find((item) => item.metadata?._id === this.requestId)
: undefined;
if (output) {
const newText = `${output.items[0].data.toString()}${value}`;
const item =
stream === 'stderr'
? NotebookCellOutputItem.stderr(newText)
: NotebookCellOutputItem.stdout(newText);
await this.task
this.task
.replaceOutputItems(item, output)
.then(noop, (ex) => console.error('Failed to replace output items in CellOutput', ex));
} else {
Expand All @@ -145,8 +142,16 @@ export class CellOutput {
? NotebookCellOutputItem.stderr(value)
: NotebookCellOutputItem.stdout(value);
await this.task
.appendOutput(new NotebookCellOutput([item]))
.appendOutput(new NotebookCellOutput([item], { _id: this.requestId, stream }))
.then(noop, (ex) => console.error('Failed to append output items in CellOutput', ex));
const output = this.cell.outputs.find(
(item) => item.metadata?._id === this.requestId && item.metadata?.stream === stream
);
if (output) {
this.lastOutputStream = { output, stream, id: this.requestId };
} else {
this.lastOutputStream = undefined;
}
}
})
.finally(() => this.endTempTask());
Expand Down Expand Up @@ -194,13 +199,11 @@ export class CellOutput {
});
return;
}
// If it hasn't been rendered yet, then wait 5s for it to get rendered.
// If still not rendered, then just render a whole new item.
// Its possible the user hit the clear outputs button.
if (!existingInfo.deferred.completed) {
await Promise.race([existingInfo.deferred.promise, sleep(5_000)]);
return;
}
if (
existingInfo.deferred.completed &&
existingInfo.cell.outputs.find(
(item) => item.metadata?.containerId === containerId
)
Expand All @@ -227,13 +230,16 @@ export class CellOutput {
output: tfVisOutputToAppend,
deferred: createDeferred<void>()
});
this.lastOutputStream = undefined;
await this.task.appendOutput(tfVisOutputToAppend).then(noop, noop);
// Wait for output to get created.
if (
this.cell.outputs.find((item) => item.metadata?.containerId === containerId) &&
this.outputsByTfVisContainer.get(containerId)
) {
this.outputsByTfVisContainer.get(containerId)?.deferred.resolve();
} else {
this.outputsByTfVisContainer.delete(containerId);
}
return;
}
Expand All @@ -244,7 +250,7 @@ export class CellOutput {
if (existingInfo) {
// Wait till the UI element is rendered by the renderer.
// & Once rendered, we can send a message instead of rendering outputs.
existingInfo?.deferred.promise.finally(() =>
existingInfo.deferred.promise.finally(() =>
this.rendererComms.postMessage({
...value
})
Expand Down Expand Up @@ -274,7 +280,7 @@ export class CellOutput {
individualOutputItems.push(output);
}
const items: NotebookCellOutputItem[] = [];
await Promise.all(
Promise.all(
individualOutputItems.map(async (value) => {
switch (value.type) {
case 'image': {
Expand Down Expand Up @@ -321,7 +327,8 @@ export class CellOutput {
})
);
if (items.length > 0) {
return this.task.appendOutput(new NotebookCellOutput(items)).then(noop, noop);
this.lastOutputStream = undefined;
this.task.appendOutput(new NotebookCellOutput(items)).then(noop, noop);
}
})
.finally(() => this.endTempTask());
Expand All @@ -338,7 +345,7 @@ export class CellOutput {
newEx.stack = newEx.stack.replace(`${newEx.name}: ${newEx.message}\n`, '');
newEx.stack = Compiler.fixCellPathsInStackTrace(this.cell.notebook, newEx);
const output = new NotebookCellOutput([NotebookCellOutputItem.error(newEx)]);
return this.task.appendOutput(output);
this.task.appendOutput(output);
})
.then(noop, (ex) => console.error('Failed to append the Error output in cellOutput', ex))
.finally(() => this.endTempTask());
Expand Down
2 changes: 1 addition & 1 deletion src/extension/kernel/jsKernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export class JavaScriptKernel implements IDisposable {
deferred: createDeferred<void>()
};
const result = createDeferred<CellExecutionState>();
const stdOutput = CellOutput.getOrCreate(task, this.controller);
const stdOutput = CellOutput.getOrCreate(task, this.controller, requestId);
this.currentTask = { task, requestId, result, stdOutput };
this.lastStdOutput = stdOutput;
this.tasks.set(requestId, { task, requestId, result, stdOutput });
Expand Down

0 comments on commit ce44dcc

Please sign in to comment.