https://blog.tensorflowcn.cn/2018/07/train-model-in-tfkeras-with-colab-and-run-in-browser-tensorflowjs.html
https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi9Y9ngL4ZATBjhq70_nUKh79SrPmEhR8UJqwSi_H7pHYefN33smGSoBWRF-ZUTtoSvbLzrDVAqZhSvlN6tzadE64JeH51VM2-T-MeRlUhtCnEyzSsCOIa6TSWENSYmGLekG-1Z8PzSYOE/s1600/cupmodel.gif
发布者:Zaid Alyafeai
我们将创建一个简单的工具,可以识别绘图并输出当前绘图的名称。此应用程序将直接在浏览器上运行,无需任何安装。我们将使用 Google Colab 训练模型,并将使用 TensorFlow.js 在浏览器上部署它。

代码和演示
在 GitHub 上找到实时
演示 和
代码。 另外,请务必在 Google Colab 上测试笔记本
这里。
数据集
我们将使用 CNN 来识别不同类型的绘图。CNN 将在 Quick Draw
数据集 上进行训练。该数据集包含大约 5000 万张 345 个类别的绘图。
 |
类别的子集 |
管道
我们将使用 Keras 在 Google Colab 上的 GPU 上免费训练模型,然后使用
TensorFlow.js(tfjs) 直接在浏览器上运行它。 我创建了关于 TensorFlow.js 的
教程。 在继续之前,请务必阅读它。 以下是项目的管道
 |
管道 |
在 Colab 上训练
Google 在 GPU 上提供免费的处理能力。 你可以看看这个关于如何创建笔记本并激活 GPU 编程的
教程。
导入
我们将使用 keras,tensorflow 作为后端
import os
import glob
import numpy as np
from tensorflow.keras import layers
from tensorflow import keras
import tensorflow as tf
加载数据
由于我们的内存有限,我们将不会对所有类别进行训练。 我们将只使用
100 个类别的数据集。 每个类别的可用数据都在
Google Cloud 上,作为形状为
[N,784]
的 numpy 数组,其中
N
是该特定类别的图像数量。 我们首先下载数据集
import urllib.request
def download():
base = 'https://storage.googleapis.com/quickdraw_dataset/full/numpy_bitmap/'
for c in classes:
cls_url = c.replace('_', '%20')
path = base+cls_url+'.npy'
print(path)
urllib.request.urlretrieve(path, 'data/'+c+'.npy')
由于我们的内存有限,我们只加载每个类别的 5000 张图像到内存中。 我们还保留了 20% 的未见数据用于测试
def load_data(root, vfold_ratio=0.2, max_items_per_class= 5000 ):
all_files = glob.glob(os.path.join(root, '*.npy'))
#initialize variables
x = np.empty([0, 784])
y = np.empty([0])
class_names = []
#load a subset of the data to memory
for idx, file in enumerate(all_files):
data = np.load(file)
data = data[0: max_items_per_class, :]
labels = np.full(data.shape[0], idx)
x = np.concatenate((x, data), axis=0)
y = np.append(y, labels)
class_name, ext = os.path.splitext(os.path.basename(file))
class_names.append(class_name)
data = None
labels = None
#separate into training and testing
permutation = np.random.permutation(y.shape[0])
x = x[permutation, :]
y = y[permutation]
vfold_size = int(x.shape[0]/100*(vfold_ratio*100))
x_test = x[0:vfold_size, :]
y_test = y[0:vfold_size]
x_train = x[vfold_size:x.shape[0], :]
y_train = y[vfold_size:y.shape[0]]
return x_train, y_train, x_test, y_test, class_names
预处理数据
我们预处理数据,为训练做准备。 模型将接受形状为
[N, 28, 28, 1]
的批次,并输出形状为
[N, 100]
的概率
# Reshape and normalize
x_train = x_train.reshape(x_train.shape[0], image_size, image_size, 1).astype('float32')
x_test = x_test.reshape(x_test.shape[0], image_size, image_size, 1).astype('float32')
x_train /= 255.0
x_test /= 255.0
# Convert class vectors to class matrices
y_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
创建模型
我们将创建一个简单的 CNN。 注意,模型越简单,参数越少越好。 事实上,我们将转换后在浏览器上运行模型,并且希望模型能够快速运行以进行预测。 以下模型包含 3 个卷积层和 2 个密集层。
# Define model
model = keras.Sequential()
model.add(layers.Convolution2D(16, (3, 3),
padding='same',
input_shape=x_train.shape[1:], activation='relu'))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Convolution2D(32, (3, 3), padding='same', activation= 'relu'))
model.add(layers.MaxPooling2D(pool_size=(2, 2)))
model.add(layers.Convolution2D(64, (3, 3), padding='same', activation= 'relu'))
model.add(layers.MaxPooling2D(pool_size =(2,2)))
model.add(layers.Flatten())
model.add(layers.Dense(128, activation='relu'))
model.add(layers.Dense(100, activation='softmax'))
# Train model
adam = tf.train.AdamOptimizer()
model.compile(loss='categorical_crossentropy',
optimizer=adam,
metrics=['top_k_categorical_accuracy'])
print(model.summary())
拟合、验证和测试
之后,我们将模型训练
5
个 epoch,
256
个批次,并使用
10%
的验证分割
#fit the model
model.fit(x = x_train, y = y_train, validation_split=0.1, batch_size = 256, verbose=2, epochs=5)
#evaluate on unseen data
score = model.evaluate(x_test, y_test, verbose=0)
print('Test accuarcy: {:0.2f}%'.format(score[1] * 100))

测试精度为
92.20%
,前
5
个精度。
为 Web 格式准备模型
在我们对模型的精度感到满意后,我们将保存它以进行转换
model.save('keras.h5')
我们安装 tfjs 包以进行转换
!pip install tensorflowjs
然后我们转换模型
!mkdir model
!tensorflowjs_converter --input_format keras keras.h5 model/
这将创建一些权重文件和包含模型架构的 json 文件。
压缩模型,准备下载到本地计算机
!zip -r model.zip model
最后,下载模型
from google.colab import files
files.download('model.zip')
在浏览器上推断
在本节中,我们将展示如何加载模型并进行推断。 我将假设我们有一个大小为
300 x 300
的画布。 我将不会详细介绍界面,而是专注于 TensorFlow.js 部分。
加载模型
为了使用 TensorFlow.js,首先使用以下脚本
<script src="https://cdn.jsdelivr.net.cn/npm/@tensorflow/tfjs@latest"> </script>
您将需要在本地机器上运行一个服务器来托管权重文件。 您可以创建一个 apache 服务器或在
GitHub 上托管页面,就像我在我的
项目 中做的那样。
之后,使用以下方法将模型加载到浏览器中
model = await tf.loadLayersModel('model/model.json')
await
关键字将等待浏览器加载模型。
预处理
在进行预测之前,我们需要预处理数据。 首先从画布中获取图像数据
//the minimum boudning box around the current drawing
const mbb = getMinBox()
//cacluate the dpi of the current window
const dpi = window.devicePixelRatio
//extract the image data
const imgData = canvas.contextContainer.getImageData(mbb.min.x * dpi, mbb.min.y * dpi,
(mbb.max.x - mbb.min.x) * dpi, (mbb.max.y - mbb.min.y) * dpi);
getMinBox()
将在后面解释。 变量
dpi
用于根据屏幕像素密度拉伸画布的裁剪。
我们获取画布的当前图像数据,将其转换为张量,调整大小并进行归一化
function preprocess(imgData)
{
return tf.tidy(()=>{
//convert the image data to a tensor
let tensor = tf.browser.fromPixels(imgData, numChannels= 1)
//resize to 28 x 28
const resized = tf.image.resizeBilinear(tensor, [28, 28]).toFloat()
// Normalize the image
const offset = tf.scalar(255.0);
const normalized = tf.scalar(1.0).sub(resized.div(offset));
//We add a dimension to get a batch shape
const batched = normalized.expandDims(0)
return batched
})
}
为了进行预测,我们使用
model.predict
,这将返回形状为
[N, 100]
的概率
const pred = model.predict(preprocess(imgData)).dataSync()
然后,我们可以使用简单的函数来查找前 5 个概率。
提高精度
请记住,我们的模型接受形状为
[N, 28, 28,1]
的张量。 我们拥有的绘图画布大小为
300 x 300
,对于绘图来说可能太大,或者用户可能绘制了一个小图形。 最好只裁剪包含当前绘图的框。 为此,我们通过查找左上角和右下角点来提取绘图周围的最小边界框
//record the current drawing coordinates
function recordCoor(event)
{
//get current mouse coordinate
var pointer = canvas.getPointer(event.e);
var posX = pointer.x;
var posY = pointer.y;
//record the point if withing the canvas and the mouse is pressed
if(posX >=0 && posY >= 0 && mousePressed)
{
coords.push(pointer)
}
}
//get the best bounding box by finding the top left and bottom right cornders
function getMinBox(){
var coorX = coords.map(function(p) {return p.x});
var coorY = coords.map(function(p) {return p.y});
//find top left corner
var min_coords = {
x : Math.min.apply(null, coorX),
y : Math.min.apply(null, coorY)
}
//find right bottom corner
var max_coords = {
x : Math.max.apply(null, coorX),
y : Math.max.apply(null, coorY)
}
return {
min : min_coords,
max : max_coords
}
}
测试绘图
以下是一些第一次绘图及其最高百分比类别。 我用鼠标绘制了所有的绘图。 你应该使用笔得到更好的精度......