# 识别手写数字(CNN 卷积神经网络)
# 为什么要使用卷积神经网络?
需要处理的数据量太大,特征太多,普通神经网络效率太低
卷积神经网络模拟人类对的视觉处理流程,高效提取特征,大幅度减少运算量
# 卷积神经网络的层
- 卷积层(通过卷积运算提取特征)
卷积层包含了多个卷积核对图像进行卷积操作,根据不同的卷积核(矩阵)计算出特征
一个卷积核可能只会提取某一种特征,比如 outline,多个卷积核通过逐轮提取并组合使得结果更加准确
卷积层是有权重需要训练的,卷积核就是权重
- 池化层(属于优化的一个层,没有它也行)
作用是用于提取最强的特征
扩大感受野,减少计算量
池化层是没有权重需要训练的
- 全连接层
作为输出层
作为分类器
全连接层是有权重需要训练的
# 代码展示
import * as tf from '@tensorflow/tfjs';
import * as tfvis from '@tensorflow/tfjs-vis';
import { MnistData } from './data';
import { accuracy } from '@tensorflow/tfjs-vis/dist/util/math';
window.onload = async () => {
/*加载变量*/
const data = new MnistData();
await data.load(); // 加载图片和二进制文件的过程-load之后就可以拿到图片的数据了
const TEST_COUNT = 20;
const example = data.nextTestBatch(TEST_COUNT); // 加载一些(20个)验证集tensor数据
const surface = tfvis.visor().surface({
// 船舰一个tfvis的绘画面板
name: '输入示例',
});
console.log(example);
/*将tensors数据转换成图片展示*/
for (let i = 0; i < TEST_COUNT; i++) {
const imagetensor = tf.tidy(() => {
// 释放内存
/*切割出每一张图并且转换成三维的tensor*/
return example.xs.slice([i, 0], [1, 784]).reshape([28, 28, 1]); // 784是图片为28*28*1的黑白图
});
/*船舰canvas对象*/
const canvas = document.createElement('canvas');
canvas.width = 28;
canvas.height = 28;
canvas.style = 'margin : 4px';
/*渲染图片*/
await tf.browser.toPixels(imagetensor, canvas); // 将tensor转化为像素,将数据渲染到canvas上面去
surface.drawArea.append(canvas); // 在visor的绘画区域插入canvas
}
/*定义模型*/
const model = tf.sequential();
/*添加一个卷积层*/
model.add(
tf.layers.conv2d({
inputShape: [28, 28, 1], // 输入数据shape
kernelSize: 5, // 设置卷积核大小(奇数可以更好地提取中心点)
filters: 8, // 使用的卷积核个数(类型)(超参数)
strides: 1, // 每次扫描步长
activation: 'relu',
kernelInitializer: 'varianceScaling', // 设置初始化方法(选填)可以提升收敛速度
})
);
/*添加(最大)池化层*/
model.add(
tf.layers.maxPool2d({
poolSize: [2, 2], // 池化尺寸
strides: [2, 2], // 步数
})
);
/*重复上诉步骤提取特征组合--比如第一轮提取横竖,第二轮提取撇捺等*/
model.add(
tf.layers.conv2d({
kernelSize: 5,
filters: 16, // 需要更加多的卷积核,因为要提取更加复杂的组合特征
strides: 1,
activation: 'relu',
kernelInitializer: 'varianceScaling', // 设置初始化方法(选填)可以提升收敛速度
})
);
model.add(
tf.layers.maxPool2d({
poolSize: [2, 2], // 池化尺寸
strides: [2, 2], // 步数
})
);
/*将高维数据转换成一维*/
model.add(tf.layers.flatten());
/*添加输出层(一维)*/
model.add(
tf.layers.dense({
// 全连接层
units: 10, // 输出0-9共10个结果
activation: 'softmax', // 多分类的激活函数
kernelInitializer: 'varianceScaling',
})
);
/*设置损失函数和优化器*/
model.compile({
loss: 'categoricalCrossentropy', // 交叉熵
optimizer: tf.train.adam(),
metrics: 'accuracy', // 度量单位(准确度)
});
/*准备训练集和验证集*/
const [tranXs, trainYs] = tf.tidy(() => {
const d = data.nextTestBatch(2000);
return [d.xs.reshape([2000, 28, 28, 1]), d.labels]; // 2000个数据,每个是28*28*1
});
const [testXs, testYs] = tf.tidy(() => {
const d = data.nextTestBatch(400);
// console.log('d', d);
// console.log('d', d.xs.reshape([400, 28, 28, 1]).print());
return [d.xs.reshape([400, 28, 28, 1]), d.labels];
});
/*进行训练*/
model.fit(tranXs, trainYs, {
validationData: [testXs, testYs], // 验证集数据
epochs: 50,
batchSize: 1000,
callbacks: tfvis.show.fitCallbacks(
{ name: '训练效果' },
['loss', 'val_loss', 'acc', 'val_acc'],
{ callbacks: ['onEpochEnd'] }
),
});
/*画布操作*/
const canvas = document.querySelector('canvas');
/*手写*/
canvas.addEventListener('mousemove', (e) => {
if (e.buttons === 1) {
// 左键画东西
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(255,255,255)';
ctx.fillRect(e.offsetX, e.offsetY, 20, 20);
}
});
/*清除事件*/
window.clear = () => {
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(0,0,0)';
ctx.fillRect(0, 0, 300, 300);
};
clear();
/*预测*/
window.predict = () => {
const input = tf.tidy(() => {
return tf.image
.resizeBilinear(
// canvas转tensor并转大小
tf.browser.fromPixels(canvas),
[28, 28], // 尺寸
true // 边角
)
.slice([0, 0, 0], [28, 28, 1]) // 切成黑白图片
.toFloat() // 归一化
.div(255) // 归一化(转化成0-1之间)
.reshape([1, 28, 28, 1]); // 保持与训练数据形状一致
});
const pred = model.predict(input).argMax(1);
console.log('预测结果', model.predict(input));
console.log('预测结果', model.predict(input).dataSync());
alert(`预测结果为 ${pred.dataSync()[0]}`);
};
};
# 重点笔记
需要使用 http-server 在本地建立一个静态服务器以加载/data/mnist 下的图片和标签, http-server(hs) 目录 --cors(防止跨域--端口不一样也算跨域)
加载的数据是一个 tensor,里面有 lables 标签---一个 shape 是[20,10]的数据,具体是[[0,1,0,0,0,0,0,0,0,0],...,[]]。 另外一个是 xs,是一个 shape 为[20,784]的数据(784 是因为这个图片是 28 像素28 像素的,所以为 28281)1 为黑白照片,如果是彩色照片 rgb 三个通道的花就要3
tf.tidy()的使用,可以清除内存(中间张量),使用此方法有助于避免内存泄漏。 tf.tidy
其实涉及到 tensor 操作都应该放在 tidy 中
tf.slice()的使用 tf.slice
tf.Tensor.reshape()的使用 tf.Tensor.reshape
tf.browser.toPixels()的使用,将 tensor 转化成像素。第一个参数接收一个图片的 tensor(必须是二维或者三维的) tf.browser.toPixels
使用 tfvis.visor().surface()加载图片数据。tfvis.visor().surface()会返回一个 drawArea 绘图区域,将这个 drawArea 给 append 到 canvas 即可显示 tfvis.visor().surface()
卷积神经网络如何提取特征? 卷积神经网络提取特征示例网站
添加层的顺序
卷积层 - 最大池化层 -
- tf.layers.flatten 层的使用--把高纬数据转换成一维数据
把高纬数据放在最后一层 dense 层去做分类的非常普遍的一个操作
了解使用 canvas 手绘数字图片并将其转换为 tensor 数据(详见代码)
tf.image.resizeBilinear()的使用--将 tensor 的 image 数据转换成新的形状 将 tensor 的 image 数据转换成新的形状
tf.browser.fromPixels(canvas)的使用--canvas 转换成 tensor canvas 转换成 tensor
← 过拟合和欠拟合 图片分类(预训练模型) →