在 tf.keras 中训练模型,并在浏览器中使用 TensorFlow.js 运行它
2018 年 7 月 2 日
发布者:Zaid Alyafeai

我们将创建一个简单的工具,可以识别绘图并输出当前绘图的名称。此应用程序将直接在浏览器上运行,无需任何安装。我们将使用 Google Colab 训练模型,并将使用 TensorFlow.js 在浏览器上部署它。

代码和演示

在 GitHub 上找到实时 演示代码。 另外,请务必在 Google Colab 上测试笔记本 这里

数据集

我们将使用 CNN 来识别不同类型的绘图。CNN 将在 Quick Draw 数据集 上进行训练。该数据集包含大约 5000 万张 345 个类别的绘图。
A subset of the classes
类别的子集

管道

我们将使用 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
   }
}

测试绘图

以下是一些第一次绘图及其最高百分比类别。 我用鼠标绘制了所有的绘图。 你应该使用笔得到更好的精度......
下一篇文章
Train a model in tf.keras with Colab, and run it in the browser with TensorFlow.js

发布者:Zaid Alyafeai

我们将创建一个简单的工具,可以识别绘图并输出当前绘图的名称。 此应用程序将直接在浏览器上运行,无需任何安装。 我们将使用 Google Colab 训练模型,并将使用 TensorFlow.js 在浏览器上部署它。
代码和演示在 GitHub 上找到实时 演示代码。 另外,请务必在 Google 上测试笔记本...