美文网首页我爱编程
使用deeplearn.js预测互补色(译)

使用deeplearn.js预测互补色(译)

作者: 行走的程序猿 | 来源:发表于2017-09-16 22:34 被阅读144次

    本教程将带领读者使用deeplearn.js编写一个预测颜色互补色的模型。

    该模型的超参数可能无法完美优化,但通过构建此模型将使我们了解deeplearn.js的重要概念。

    事实上,添加更多的层数可以更好地预测互补色。 但这只是为演示而提出一个PR,所以没有花费大量时间优化超参数。

    相信读者们应该已经阅读了项目简介给非机器学习专业人员的指南。 尽管原生JavaScript已经足够,本教程选择TypeScript作为编程语言。

    本教程所有代码都位于项目demos / complement-color-predictions目录中。

    根据Edd在Stack Overflow的回答,我们可以得知,计算颜色的互补色需要很多逻辑。

    让我们看看一个小型前馈神经网络能把这个逻辑掌握到什么程度。

    创建输入

    首先,我们用RGB空间中的随机颜色生成训练数据

    const rawInputs = new Array(exampleCount);
    for (let i = 0; i < exampleCount; i++) {
      rawInputs[i] = [
        this.generateRandomChannelValue(), this.generateRandomChannelValue(),
        this.generateRandomChannelValue()
      ];
    }
    

    接着,使用Edd的方法计算其互补色。

    /**
     * This implementation of computing the complementary color came from an
     * answer by Edd https://stackoverflow.com/a/37657940
     */
    computeComplementaryColor(rgbColor: number[]): number[] {
      let r = rgbColor[0];
      let g = rgbColor[1];
      let b = rgbColor[2];
    
      // Convert RGB to HSL
      // Adapted from answer by 0x000f http://stackoverflow.com/a/34946092/4939630
      r /= 255.0;
      g /= 255.0;
      b /= 255.0;
      const max = Math.max(r, g, b);
      const min = Math.min(r, g, b);
      let h = (max + min) / 2.0;
      let s = h;
      const l = h;
    
      if (max === min) {
        h = s = 0;  // achromatic
      } else {
        const d = max - min;
        s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min));
    
        if (max === r && g >= b) {
          h = 1.0472 * (g - b) / d;
        } else if (max === r && g < b) {
          h = 1.0472 * (g - b) / d + 6.2832;
        } else if (max === g) {
          h = 1.0472 * (b - r) / d + 2.0944;
        } else if (max === b) {
          h = 1.0472 * (r - g) / d + 4.1888;
        }
      }
    
      h = h / 6.2832 * 360.0 + 0;
    
      // Shift hue to opposite side of wheel and convert to [0-1] value
      h += 180;
      if (h > 360) {
        h -= 360;
      }
      h /= 360;
    
      // Convert h s and l values into r g and b values
      // Adapted from answer by Mohsen http://stackoverflow.com/a/9493060/4939630
      if (s === 0) {
        r = g = b = l;  // achromatic
      } else {
        const hue2rgb = (p: number, q: number, t: number) => {
          if (t < 0) t += 1;
          if (t > 1) t -= 1;
          if (t < 1 / 6) return p + (q - p) * 6 * t;
          if (t < 1 / 2) return q;
          if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
          return p;
        };
    
        const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        const p = 2 * l - q;
    
        r = hue2rgb(p, q, h + 1 / 3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1 / 3);
      }
    
      return [r, g, b].map(v => Math.round(v * 255));
    }
    

    将每个颜色通道除以255来归一化输入。通常,归一化能有助于模型的训练过程。

    normalizeColor(rgbColor: number[]): number[] {
      return rgbColor.map(v => v / 255);
    }
    

    Array1D是deeplearn.js在GPU上存放数据的数据结构,inputArraytargetArrayArray1D的列表。我们在将每个输入存入到一个Array1D结构中(现在是0到1之间的3个值的列表)。

    const inputArray: Array1D[] =
        rawInputs.map(c => Array1D.new(this.normalizeColor(c)));
    const targetArray: Array1D[] = rawInputs.map(
        c => Array1D.new(
            this.normalizeColor(this.computeComplementaryColor(c))));
    

    之后,我们构建一个ShuffledInputProvider,它会打乱输入数据的顺序(数据包括inputArraytargetArray两个列表)。 ShuffledInputProvider在打乱输入数据时,保持了输入和目标之间的关系(因此两个数组中的对应元素在打乱顺序后仍保持相同的索引)。

    const shuffledInputProviderBuilder =
        new InCPUMemoryShuffledInputProviderBuilder(
            [inputArray, targetArray]);
    const [inputProvider, targetProvider] =
        shuffledInputProviderBuilder.getInputProviders();
    

    利用ShuffledInputProvider创建前馈入口,将数据传递到模型。

    this.feedEntries = [
      {tensor: this.inputTensor, data: inputProvider},
      {tensor: this.targetTensor, data: targetProvider}
    ];
    

    Setting Up the Graph设置图表

    最有意思的部分来了,在接下来的步骤中,我们将构建模型。deeplearn.js是一个基于图形的API,像TensorFlow一样,运行会话前,先设计一个模型。

    创建一个Graph对象和两个张量(Tensor):一个给输入颜色,另一个给目标颜色。仅在训练阶段填充目标颜色(测试阶段不填充) - 在测试过程中,只能利用所给输入颜色来预测目标颜色。

    如上所述,张量(Tensor)用于在前馈入口中将数据传递给模型。

    const graph = new Graph();
    
    // This tensor contains the input. In this case, it is a scalar.
    this.inputTensor = graph.placeholder('input RGB value', [3]);
    
    // This tensor contains the target.
    this.targetTensor = graph.placeholder('output RGB value', [3]);
    

    编写函数createFullyConnectedLayer,用graph.layers.dense生成一个全连接层。

    private createFullyConnectedLayer(
        graph: Graph, inputLayer: Tensor, layerIndex: number,
        sizeOfThisLayer: number, includeRelu = true, includeBias = true) {
      return graph.layers.dense(
          'fully_connected_' + layerIndex, inputLayer, sizeOfThisLayer,
          includeRelu ? (x) => graph.relu(x) : undefined, includeBias);
    }
    

    createFullyConnectedLayer创建三个全连接层,各有64, 32和16个节点。

    // Create 3 fully connected layers, each with half the number of nodes of
    // the previous layer. The first one has 16 nodes.
    let fullyConnectedLayer =
        this.createFullyConnectedLayer(graph, this.inputTensor, 0, 64);
    
    // Create fully connected layer 1, which has 8 nodes.
    fullyConnectedLayer =
        this.createFullyConnectedLayer(graph, fullyConnectedLayer, 1, 32);
    
    // Create fully connected layer 2, which has 4 nodes.
    fullyConnectedLayer =
        this.createFullyConnectedLayer(graph, fullyConnectedLayer, 2, 16);
    

    创建一个有三个输出的网络层,每个输出对应一个颜色通道,用于输出归一化后的预测互补色。

    this.predictionTensor =
        this.createFullyConnectedLayer(graph, fullyConnectedLayer, 3, 3);
    

    添加成本张量(Cost Tensor)指定损失函数(Loss Function)(均方)。

    this.costTensor =
        graph.meanSquaredCost(this.targetTensor, this.predictionTensor);
    

    最后,创建一个运行训练和测试的会话。

    this.session = new Session(graph, this.math);
    

    Train and Predict

    训练和预测

    构建一个初始学习率为0.042优化器训练模型,

    this.optimizer = new SGDOptimizer(this.initialLearningRate);
    

    然后遍写一个能训练一批颜色的函数。需要注意的是,我们将在math.scope回调函数中打包训练的会话调用。

    在这里使用math.scope是必需的(代码的其他部分也是如此),它允许deeplearn.js回收不再需要的资源(如GPU上的数据)。

    还要注意,train1Batch方法接受一个shouldFetchCost参数,让调用train1Batch的外部循环,只能在某些步骤获取成本值。

    从GPU获取成本值要从GPU传输数据,会造成延迟,所以我们偶尔才这样做。

    train1Batch(shouldFetchCost: boolean): number {
      // Every 42 steps, lower the learning rate by 15%.
      const learningRate =
          this.initialLearningRate * Math.pow(0.85, Math.floor(step / 42));
      this.optimizer.setLearningRate(learningRate);
    
      // Train 1 batch.
      let costValue = -1;
      this.math.scope(() => {
        const cost = this.session.train(
            this.costTensor, this.feedEntries, this.batchSize, this.optimizer,
            shouldFetchCost ? CostReduction.MEAN : CostReduction.NONE);
    
        if (!shouldFetchCost) {
          // We only train. We do not compute the cost.
          return;
        }
    
        // Compute the cost (by calling get), which requires transferring data
        // from the GPU.
        costValue = cost.get();
      });
      return costValue;
    }
    

    此外,编写预测任何给定颜色互补色的方法,并创建一个称为映射的前馈入口,将输入颜色传递给模型。

    predict(rgbColor: number[]): number[] {
      let complementColor: number[] = [];
      this.math.scope((keep, track) => {
        const mapping = [{
          tensor: this.inputTensor,
          data: Array1D.new(this.normalizeColor(rgbColor)),
        }];
        const evalOutput = this.session.eval(this.predictionTensor, mapping);
        const values = evalOutput.getValues();
        const colors = this.denormalizeColor(Array.prototype.slice.call(values));
    
        // Make sure the values are within range.
        complementColor = colors.map(
            v => Math.round(Math.max(Math.min(v, 255), 0)));
      });
      return complementColor;
    }
    

    更新UI

    .ts文件中的其余逻辑主要是用于管理UI。

    调用trainAndMaybeRender方法启动循环进行训练和渲染,循环的执行时与浏览器视图刷新率保持同步。

    4242步后停止训练,并在控制台中记录损失值。

    根据一小部分示例颜色,112个(64 + 32 + 16)中间层节点的模型应该就足够了。

    complementary-color-prediction.png

    权重初始化问题

    现在,预测的互补色某一通道在整个训练期间可能始终为0。例如,在下面的截图中,蓝色通道一直为0。

    blue-channel-at-0.png

    这种现象的出现取决于初始化权重对训练是否开始产生多大的影响。

    有时,通道值卡在0的情况可能会随着时间的推移而变化。而有时,这个问题可能需要刷新页面才能解决。

    End
    结语

    目录中的代码和注释提供了一个简单的例子,希望对你了解如何使用deeplearn.js有所帮助。

    相关文章

      网友评论

        本文标题:使用deeplearn.js预测互补色(译)

        本文链接:https://www.haomeiwen.com/subject/bvdysxtx.html