UNPKG

mnist-dataset

Version:

✍️ Yann LeCun's MNIST handwritten digit dataset, made available to Node.js.

110 lines (96 loc) 3.08 kB
import fs from "fs"; import http from "http"; import { existsSync, mkdirSync } from "fs"; import { tmpdir } from "os"; import { sep } from "path"; import { createGunzip } from "zlib"; import { createHash } from "crypto"; const mnistTmpDir = `${tmpdir()}${sep}mnist-dataset`; export const gunzipUrl = url => Promise.resolve([]) .then( buf => new Promise((resolve, reject) => http .get(url, res => res .pipe(createGunzip()) .on("data", data => buf.push(data)) .on("end", () => resolve(buf)) .on("error", reject) ) .on("error", reject) ) ) .then(arr => [].concat(...arr)) .then(arr => Buffer.concat(arr)); export const getTempPath = key => { if (!existsSync(mnistTmpDir)) mkdirSync(mnistTmpDir); if (typeof key === "string") { return `${mnistTmpDir}${sep}${createHash("md5") .update(key) .digest("hex")}`; } throw new Error(`Expected string key, encountered ${key}.`); }; const magicNumbers = Object.freeze([2051, 2049]); const [imagesMagicNumber, labelsMagicNumber] = magicNumbers; const images = buf => Promise.resolve().then(() => { const [magicNumber, numberOfImages, numberOfRows, numberOfColumns] = [ buf.readInt32BE(0), buf.readInt32BE(4), buf.readInt32BE(8), buf.readInt32BE(12) ]; const { data } = buf.slice(16).toJSON(); const pixels = numberOfRows * numberOfColumns; return [...Array(numberOfImages)].map((_, i) => data.slice(i * pixels, (i + 1) * pixels) ); }); const labels = buf => Promise.resolve().then(() => { const [magicNumber, numberOfLabels] = [ buf.readInt32BE(0), buf.readInt32BE(4) ]; const { data } = buf.slice(8).toJSON(); return [...Array(numberOfLabels)].map((_, i) => data.slice(i, i + 1)); }); const parse = buf => Promise.resolve().then(() => { const magicNumber = buf.readInt32BE(0); if (magicNumber === imagesMagicNumber) { return images(buf); } else if (magicNumber === labelsMagicNumber) { return labels(buf); } return Promise.reject( new Error( `Expected one of magic numbers ${magicNumbers}, encountered ${magicNumber}.` ) ); }); const maybeDownload = url => Promise.resolve().then(() => { const path = getTempPath(url); if (!fs.existsSync(path)) { return gunzipUrl(url) .then(data => fs.promises.writeFile(path, data)) .then(() => path); } return path; }); export const trainingImagesUrl = "http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz"; export const trainingLabelsUrl = "http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz"; export const testImagesUrl = "http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz"; export const testLabelsUrl = "http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz"; export const cache = url => maybeDownload(url) .then(path => fs.promises.readFile(path)) .then(parse);