使用javascript/node.js读取MNIST数据集

Reading MNIST dataset with javascript/node.js

本文关键字:读取 MNIST 数据集 js node javascript 使用      更新时间:2023-09-26

我正在尝试从这个来源解码数据集:http://yann.lecun.com/exdb/mnist/

底部有一个"非常简单"的IDX文件类型的描述,但是我看不清楚。

我想要达到的是:

var imagesFileBuffer = fs.readFileSync(__dirname + '/train-images-idx3-ubyte');
var labelFileBuffer  = fs.readFileSync(__dirname + '/train-labels-idx1-ubyte');
var pixelValues      = {};

施展魔法

pixelValues现在像:

// {
//   "0": [0,0,200,190,79,0... for all 784 pixels ... ],
//   "4": [0,0,200,190,79,0... for all 784 pixels ... ],

等对于数据集中的所有图像条目。我试图找出二进制文件的结构,但失败了。

我意识到在我的pixelValues对象结构中会有重复的键,所以我创建了一个它的对象数组。下面的代码将创建我所追求的结构:

var dataFileBuffer  = fs.readFileSync(__dirname + '/train-images-idx3-ubyte');
var labelFileBuffer = fs.readFileSync(__dirname + '/train-labels-idx1-ubyte');
var pixelValues     = [];
// It would be nice with a checker instead of a hard coded 60000 limit here
for (var image = 0; image <= 59999; image++) { 
    var pixels = [];
    for (var x = 0; x <= 27; x++) {
        for (var y = 0; y <= 27; y++) {
            pixels.push(dataFileBuffer[(image * 28 * 28) + (x + (y * 28)) + 15]);
        }
    }
    var imageData  = {};
    imageData[JSON.stringify(labelFileBuffer[image + 8])] = pixels;
    pixelValues.push(imageData);
}

像素值的结构现在是这样的:

[
    {5: [28,0,0,0,0,0,0,0,0,0...]},
    {0: [0,0,0,0,0,0,0,0,0,0...]},
    ...
]

有28x28=784个像素值,范围从0到255。

要渲染像素,像上面一样使用for循环,渲染左上角的第一个像素,然后向右工作。

只是一个小小的改进:

for (var image = 0; image <= 59999; image++) {

带有60000的

pixelValues的末尾有一个null的"条目"。

编辑:

我有点痴迷于细节,因为我想把MNIST数据集转换回真实的和独立的图像文件。所以我在你的代码中发现了更多的错误。

  1. 它肯定是+16,因为你必须跳过16字节的头数据。这个小错误反映在你的答案中,第一个数字的第一个像素值(是5)是"28"。这实际上是告诉图像有多少列的值-而不是图像的第一个像素。

  2. 你的嵌套for循环必须翻转到外面,以获得正确的像素顺序-假设你将"重建"你的图像从左上角到右下角。使用您的代码,图像将沿着从左上角到右下角的轴翻转。

所以你的代码应该是:
var dataFileBuffer  = fs.readFileSync(__dirname + '/train-images-idx3-ubyte');
var labelFileBuffer = fs.readFileSync(__dirname + '/train-labels-idx1-ubyte');
var pixelValues     = [];
// It would be nice with a checker instead of a hard coded 60000 limit here
for (var image = 0; image <= 59999; image++) { 
    var pixels = [];
    for (var y = 0; y <= 27; y++) {
        for (var x = 0; x <= 27; x++) {
            pixels.push(dataFileBuffer[(image * 28 * 28) + (x + (y * 28)) + 16]);
        }
    }
    var imageData  = {};
    imageData[JSON.stringify(labelFileBuffer[image + 8])] = pixels;
    pixelValues.push(imageData);
}

如果你保持一致并使用这些提取的数据来训练神经网络,这些小细节就不会成为问题,因为你将对测试数据集做同样的事情。但是如果你想用MNIST训练的神经网络来验证真实的手写数字,你会得到不好的结果,因为真实的图像没有翻转。

@Lilleman的代码非常有帮助。这是一个经过清理的版本,更新到ES6,并作为函数实现。如前所述,原始代码以错误的顺序将字节放在一起,因此图像逆时针旋转90度。这段代码修复了这个问题,并且循环直到读取所有数据,而不是硬编码最大图像计数。

const os = require('os');
const fs = require('fs');
function load(labelsFilePath, imagesFilePath) {
    const labelsFileBuffer = fs.readFileSync(labelsFilePath);
    const dataFileBuffer   = fs.readFileSync(imagesFilePath);
    const pixelValues      = [];
    let image = 0;
    while(true) {
        const imageData  = {};
        const label = labelsFileBuffer[image + 8];
        const pixels = [];
        if(typeof label == 'undefined') {
            break;
        }
        
        for(let x=0; x<=27; x++) {
            for(let y=0; y<=27; y++) {
                pixels.push(dataFileBuffer[(image * 28 * 28) + (x * 28  + y) + 15]);
            }
        }
        imageData[label] = pixels;
        pixelValues.push(imageData);
        image += 1;
    }
    return pixelValues;
}
// sample usage:
const mnist = load(
    `${os.homedir()}/Downloads/train-labels.idx1-ubyte`,
    `${os.homedir()}/Downloads/train-images.idx3-ubyte`
);
console.log(mnist);

希望这能帮助到一些人,我添加了将图像保存为png格式的功能。请注意,您需要有一个图像目录

var fs = require('fs');
const {createCanvas} = require('canvas');
function readMNIST(start, end)
{
    var dataFileBuffer = fs.readFileSync(__dirname + '''test_images_10k.idx3-ubyte');
    var labelFileBuffer = fs.readFileSync(__dirname + '''test_labels_10k.idx1-ubyte');
    var pixelValues = [];
    
    for (var image = start; image < end; image++)
    { 
        var pixels = [];
        for (var y = 0; y <= 27; y++)
        {
            for (var x = 0; x <= 27; x++)
            {
                pixels.push(dataFileBuffer[(image * 28 * 28) + (x + (y * 28)) + 16]);
            }
        }
        var imageData  = {};
        imageData["index"] = image;
        imageData["label"] = labelFileBuffer[image + 8];
        imageData["pixels"] = pixels;
        pixelValues.push(imageData);
    }
    return pixelValues;
}
function saveMNIST(start, end)
{
    const canvas = createCanvas(28, 28);
    const ctx = canvas.getContext('2d');
    var pixelValues = readMNIST(start, end);
    pixelValues.forEach(function(image)
    { 
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        for (var y = 0; y <= 27; y++)
        {
            for (var x = 0; x <= 27; x++)
            {
                var pixel = image.pixels[x + (y * 28)];
                var colour = 255 - pixel;
                ctx.fillStyle = `rgb(${colour}, ${colour}, ${colour})`;
                ctx.fillRect(x, y, 1, 1);
            }
        }
        const buffer = canvas.toBuffer('image/png')
        fs.writeFileSync(__dirname + `''images''image${image.index}-${image.label}.png`, buffer)
    })
}
saveMNIST(0, 5);