【翻译】完全新人入门:使用Tensorflow.js进行机器学习

        原文地址:https://blog.usejournal.com/machine-learning-for-dummies-with-tensorflow-js-44795d3d825c

        原文发表日期:2018年6月2日

        最近玩了一下TensorFlow.js库。 因为我平时只用JS来编程,所以当听说出了Tensorflow.js的时候就挺开心。 我先用它做了个简单的小实验,发现这个库的API用起来还是相当简单的,当然前提是你要有一丢丢机器学习的经验。

        我并不对这个小实验能取得丰硕成果抱什么希望,但如果我能够获得一个有效的模型,那么就可以证明Tensorflow.js是可以处理实际数据集的。 但正如我所怀疑的那样,预测新例子的结果不太好,但我仍然认为我的努力足够有效,值得分享,而且我确实学到了一些东西。

        我最初的想法是创建一个能简单区分音乐类型的分类器。只要用户给出一个简单的旋律,它就能够将其分类为四种:蓝调、流行、爵士和金属。 我的方法是将旋律建模为八分音符的序列,其中每个音符由对应音阶1到12的数字表示,0表示在该节拍上没有音符。 这些是我想出的最初的旋律:

const melodies = {
blues: [
    [1, 0, 1, 3, 0, 1, 1, 0],
    [1, 1, 3, 1, 3, 1, 3, 1],
    [5, 5, 6, 5, 7, 5, 6, 5],
    [1, 1, 0, 1, 0, 1, 0, 1],
  ],
pop: [
    [1, 0, 1, 1, 0, 1, 0, 12],
    [1, 3, 1, 3, 1, 5, 5, 5],
    [1, 1, 1, 1, 1, 12, 12, 3],
    [6, 6, 5, 3, 0, 3, 1, 3],
  ],
jazz: [
    [1, 5, 8, 1, 1, 0, 1, 0],
    [8, 7, 6, 5, 4, 3, 1, 5],
    [1, 4, 6, 7, 8, 7, 6, 4],
    [3, 10, 0, 0, 5, 3, 5, 10],
  ],
metal: [
    [1, 1, 2, 1, 11, 4, 1, 2],
    [1, 4, 7, 10, 7, 4, 1, 4],
    [1, 1, 1, 11, 1, 2, 2, 2],
    [1, 4, 0, 4, 6, 6, 0, 9],
  ],
};

        然后我将八分音符序列转换为8x12矩阵,而不是从1到8的数字,所播放的音符在每行的1到12的对应位置将是1。比如第一个蓝调旋律[1, 0, 1, 3, 0, 1, 1, 0]看起来是这样的:

  1. [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  2. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  3. [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  4. [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  5. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  6. [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  7. [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  8. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
//将旋律转化成矩阵
function convertMelody(melody) {
  const converted = [];

  for (let i = 0; i < melody.length; i += 1) {
    const note = melody[i];
    const beat = new Array(12).fill(0);
    if (note) {
      beat[note - 1] = 1;
    }
    converted.push(beat);
  }
  return converted;
}

        下面是机器学习的部分。 TensorFlow.js的关键构建块是张量(tensor)。 张量就像一个数组,但可以推广到任意维度,Tensorflow.js基于此提供了一些抽象操作和转换的接口。 在Tensorflow.js的情况下,张量的高速计算得益于通过WebGL对GPU的使用,因此需要提供一些内存清理的方法来避免内存泄漏。

        因此,对于我的每个旋律,我都创建了一个张量,并将这些张量放在一个数组中:

const convertedMelodies = [];

for (let i = 0; i < 16; i += 1) {
  const genre = Object.keys(melodies)[i % 4];
  const song = melodies[genre][Math.floor(i/4)];

  // 将旋律转换成二维矩阵
  const convertedMelody = convertMelody(song);

  // 将矩阵转换成二维张量
  const tensor = tf.tensor2d(convertedMelody);
  convertedMelodies.push(tensor);
}

        在最初的尝试中,我创建了一个密集的、全连接的输入层,在二维张量中每个元素对应一个神经元。 然后在第二层,我使用压平中间地将其压平成一维张量。 最后一层有4个神经元,每种音乐类型对应一个。 TensorFlow提供了顺序(sequenial)模型,这就是一个堆栈,其中每层按次序进入下一层而中间没有跳过其他层。 代码如下所示:

const firstLayer = tf.layers.dense({
  units: 96,
  inputShape: [8, 12],
  activation: 'relu', //线性整流函数
});

const flatten = tf.layers.flatten();

const thirdLayer = tf.layers.dense({
  units: 4,
  activation: 'softmax',
});

const model = tf.sequential();

model.add(firstLayer);
model.add(flatten);
model.add(thirdLayer);

        Units是多少个神经元,inputShape描述了期望作为输入的张量的尺寸。 这仅在第一层上是必需的。 我在第一层使用'relu'激励函数,relu是线性整流函数(Rectified Linear Units,或修正线性单元)的缩写。 所谓“修正的”激励函数必须是一个合适的函数。 ReLu只意味着输入和输出:神经元的输出与输入成比例。 如果有“5”电荷进入,神经元会在输出中触发0.5。 作为另一个例子,有时神经元会使用某种二进制阈值,其中神经元要么在给定足够输入的情况下触发,要么根本不触发——这与神经元实际的工作方式更相似。

        softmax创建一个分布,其中所有值的总和等于1。在这样做时,这些值对应于每个输出神经元是正确的概率分布。

        接下来训练模型。 如果你按照我上一篇关于梯度下降的文章,你已经知道这是如何工作的了。 我选择学习率为0.2(对应于上一篇文章中的步骤)和分类交叉熵作为损失函数。 最后一部分只是意味着它假定正确类别的值为1,而其他所有类别的值为零,与上面的softmax分布区别。

const LEARNING_RATE = 0.2;

//随机梯度下降
const optimizer = tf.train.sgd(LEARNING_RATE);

model.compile({
  optimizer: optimizer,
  loss: 'categoricalCrossentropy',
  metrics: ['accuracy'], // include accuracy metrics in result
});

        在这种情况下,我将每轮更新权重。 您可以通过批处理操作进行优化,以免使GPU过载。 在这里,我每批做一个数据点。

async function train() {
  for (let i = 0; i < convertedMelodies.length; i += 1) {
    
    // the 1 at the front means this is a batch of size 1
    const batch = convertedMelodies[i].reshape([1, 8, 12]);

    // what is the correct category?
    const labelIndex = i % 4;
    let label = new Array(4).fill(0);
    label[labelIndex] = 1;
    label = tf.tensor1d(label).reshape([1, 4]);

    // 训练
    const hist = await model.fit(
      batch,
      label,
      {
        batchSize: 1,
        epochs: 1,
      }
    );

    // 输出相关参数
    const loss = hist.history.loss[0];
    const accuracy = hist.history.acc[0];
    console.log(loss, accuracy)
  }
}

        然后测试:

const test = [1, 3, 5, 1, 6, 5, 3, 1];
const convertedTest = convertMelody(test);
const tensorTest = tf.tensor2d(convertedTest);
model.predict(tensorTest.reshape([1, 8, 12])).print();

        我的结果是:

Tensor
     [[0.2256236, 0.236722, 0.2450776, 0.2925768],]

        因此,该模型表明上述旋律很可能是金属乐。 可以尝试使用某个乐器来演奏一下,看看是否同意该结论。

总结

        仅仅使用16个样本,如果模型能够准确预测出新的旋律的话,那就太让人开心了。此外,我提供的例子可能不能理想地区分音乐种类,因为需要更大的数据集。

        我还考虑将输入汇集到12个神经元中——每个音符一个。创建这样的中间密集层将具有掩盖关于音符的时间位置的所有信息的效果,而是将关于播放哪些音符的聚合统计。解决这个问题的方法可能是将初始图层和第二图层都连接到最后一层:这种方式都会对结果产生影响。但由于我不知道自己在做什么,所以我选择了更简单的选项。

        我希望这可以让我深入了解如何使用API​​,以及我能够展示这个库使用的简单程度。还有更多——我甚至没有学到皮毛。希望再次尝试更多实际的数据。

你可能感兴趣的:(翻译)