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

本教程将带领读者使用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有所帮助。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容