Tensorflow.js项目实战(7):手写数字识别1
JS Coding 纯手敲JS
项目背景
该项目是在浏览器端 使用 TensorFlow.js 构建一个卷积神经网络(CNN)模型,使用 MNIST 数据集进行训练,用于识别 在浏览器端手写输入的数字。MNIST 数据集是一个手写数字识别任务的常用数据集,本次训练任务所用图片包含65,000张图像,每张图像都是28x28像素的灰度图。
以下代码定义了一个基于 TensorFlow.js 构建的卷积神经网络(CNN)模型,用于识别 MNIST 数据集的手写数字。以下是代码各部分的功能说明:
- 导入 TensorFlow.js 库并设置超参数,如学习率、批大小和训练步数。
- 定义数据常量,如图像尺寸和标签数量。
- 创建一个顺序模型(Sequential model),这是 Keras API 中的一种模型类型,允许按顺序添加多个层。
- 添加卷积层和最大池化层:
- 卷积层(Conv2D)用于提取图像特征,
strides
参数控制卷积核在输入矩阵上滑动时每一步跨越的像素数。步长为 1 表示每个方向每次移动一个像素。 - 最大池化层(MaxPooling2D)降低空间维度,减少计算量,同时保留最重要的特征。
- 添加第二组卷积层和最大池化层,进一步抽取和降维特征。
- 使用 Flatten 层将多维特征映射转换为一维向量,便于与全连接层(Dense)配合。
- 添加一个全连接层,激活函数为 softmax,用于生成针对各个类别的概率分布。
- 定义损失函数,使用 softmax 交叉熵损失函数,适用于多分类问题。
- 定义训练函数,首先编译模型,设置优化器和损失函数。然后进入训练循环,每次迭代获取一批训练样本,对输入数据进行适当的重塑,以适应模型的输入格式,并执行拟合(fit)操作。训练过程中显示每个周期的平均损失。
- 训练完成后,将模型保存到浏览器的本地存储(localStorage),键名为 'mnist-model'。
- 定义预测函数,接受输入图像,重塑数据后通过模型进行预测,并返回预测出的概率最高的类别索引。
- 提供一个辅助函数,用于从给定的逻辑回归输出或标签向量中提取类别索引。
整个模型是专门为处理28x28像素的单通道灰度手写数字图像设计的,用于实现MNIST数据集上的手写数字识别任务。
import * as tf from"@tensorflow/tfjs";
// 定义超参数
// 学习率:模型在训练过程中更新权重的速度
const LEARNING_RATE = 0.1;
// 批大小:每次训练使用的样本数
const BATCH_SIZE = 64;
// 总训练步骤数
const TRAIN_STEPS = 400;
// 数据相关的常量
// 图像尺寸,这里假设是28x28像素
const IMAGE_SIZE = 28;
// 类别数目,对于MNIST数据集是10个数字
const LABELS_SIZE = 10;
// 初始化优化器,采用随机梯度下降法(SGD),学习率为0.1
const optimizer = tf.train.sgd(LEARNING_RATE);
// 创建一个顺序模型(Sequential model)
// 这是一种可以按顺序添加多层的模型结构
const model = tf.sequential();
// 添加第一个卷积层
// 卷积层参数说明:
// - inputShape: 输入数据形状,这里是[28, 28, 1],即28x28像素的单通道图像
// - kernelSize: 卷积核大小为5x5
// - filters: 输出通道数为8
// - strides: 卷积步长为1,意味着卷积核每次在图像上滑动时,水平和垂直方向都移动1个像素
// - activation: 激活函数选用ReLU
// - kernelInitializer: 初始化权重的方法为变方差缩放初始化
model.add(tf.layers.conv2d({
inputShape: [IMAGE_SIZE, IMAGE_SIZE, 1],
kernelSize: 5,
filters: 8,
strides: 1,//解释一下strides的作用,就是卷积核的移动步长,步长为1的话,卷积核每次只移动一个像素点,步长为2的话,卷积核每次移动两个像素点,以此类推。
activation: 'relu',
kernelInitializer: 'varianceScaling'
}));
// 添加第一个最大池化层
// 参数poolSize指定池化窗口大小为2x2
// strides也为2,这意味着每次池化后图像尺寸会减半
model.add(tf.layers.maxPooling2d({ poolSize: 2, strides: 2 }));
// 添加第二个卷积层,结构类似第一个卷积层但通道数更多
model.add(tf.layers.conv2d({
kernelSize: 5,
filters: 16,
strides: 1,
activation: 'relu',
kernelInitializer: 'varianceScaling'
}));
// 添加第二个最大池化层,同样使图像尺寸减半
model.add(tf.layers.maxPooling2d({ poolSize: 2, strides: 2 }));
// 将多维的卷积特征展平成一维向量
model.add(tf.layers.flatten());
// 添加全连接层(Dense layer),输出节点数等于类别数
// 使用softmax作为激活函数,使得输出可以表示每个类别的概率分布
model.add(tf.layers.dense({
units: LABELS_SIZE,
kernelInitializer: 'varianceScaling',
activation: 'softmax'
}));
// 训练模型
exportasyncfunction train(data) {
// 编译模型,设置优化器和损失函数
model.compile({
optimizer: optimizer,
loss: 'categoricalCrossentropy'
});
// 开始训练循环
for (let i = 0; i < TRAIN_STEPS; i++) {
const batch = data.nextTrainBatch(BATCH_SIZE);
//reshape -1,28,28,1 第一个参数-1 表示不确定
const history = await model.fit(batch.xs.reshape([-1, IMAGE_SIZE, IMAGE_SIZE, 1]), batch.labels, {
batchSize: BATCH_SIZE,
epochs: 1
});
// 打印当前批次的平均损失值以及训练步数
console.log(history.history.loss[0], i);
await tf.nextFrame();
}
//保存模型
model.save('localstorage://mnist-model');
}
// 预测模型
exportfunction predict(x) {
const predictions = model.predict(x.reshape([-1, IMAGE_SIZE, IMAGE_SIZE, 1]));
returnArray.from(predictions.argMax(-1).dataSync());
}
// 辅助函数,用于从标签向量中获取类别索引
exportfunction classesFromLabel(y) {
returnArray.from(y.argMax(-1).dataSync());
}
使用 tf.Sequential
的模型的层次结构非常清晰,只需按顺序添加层,无需在单独的步骤中定义输入和输出。
下期预告
- 在浏览器端加载 MNIST 数据集,并将其分为训练集和测试集。
- canvas 元素用于获取用户输入的手写数字图像,并将其转换为张量。
- 将张量输入到训练好的模型中,获取预测结果,并将结果打印。
- 优化模型,使其在更多的训练数据上取得更好的效果。
- 加载已训练好的模型,并在浏览器端进行实时手写数字识别。
本文使用 文章同步助手 同步