使用Tensorflow.js和tf.Tensor处理大数据的最佳方法是什么?

时间:2019-04-30 23:38:55

标签: javascript node.js tensorflow deep-learning tensorflow.js

问题

我正在使用tf.Tensortf.concat()处理大量的训练数据, 而且我发现连续使用tf.concat()会很慢。 将大数据从文件加载到tf.Tensor的最佳方法是什么?

背景

我认为这是Javascript中按数组处理数据的常用方法。 要实现这一目标,这是要执行的粗略步骤。

将数据从文件加载到阵列的步骤

  1. 从文件中读取行
  2. 将行解析为Javascript对象
  3. 通过Array.push()将该对象添加到数组中
  4. 读完一行后,我们可以将该数组与for循环一起使用。

所以我想我可以像上面一样使用tf.concat()

将数据从文件加载到tf.Tensor

  1. 从文件中读取行
  2. 将行解析为Javascript对象
  3. 将对象解析为tf.Tensor
  4. 通过tf.concat()将张量添加到原始张量
  5. 读完一行后,我们可以使用该tf.Tensor

某些代码

这里有一些代码可以同时测量Array.push()tf.concat()的速度

import * as tf from "@tensorflow/tfjs"

let t = tf.tensor1d([1])
let addT = tf.tensor1d([2])

console.time()
for (let idx = 0; idx < 50000; idx++) {
    if (idx % 1000 == 0) {
        console.timeEnd()
        console.time()
        console.log(idx)
    }
    t = tf.tidy(() => t.concat(addT))
}


let arr = []
let addA = 1
console.time()
for (let idx = 0; idx < 50000; idx++) {
    if (idx % 1000 == 0) {
        console.timeEnd()
        console.time()
        console.log(idx)
    }
    arr.push(addA)
}

测量

我们可以看到Array.push()上的流程稳定, 但在tf.concat()

上变慢

对于tf.concat()

default: 0.150ms
0
default: 68.725ms
1000
default: 62.922ms
2000
default: 23.199ms
3000
default: 21.093ms
4000
default: 27.808ms
5000
default: 39.689ms
6000
default: 34.798ms
7000
default: 45.502ms
8000
default: 94.526ms
9000
default: 51.996ms
10000
default: 76.529ms
11000
default: 83.662ms
12000
default: 45.730ms
13000
default: 89.119ms
14000
default: 49.171ms
15000
default: 48.555ms
16000
default: 55.686ms
17000
default: 54.857ms
18000
default: 54.801ms
19000
default: 55.312ms
20000
default: 65.760ms

对于Array.push()

default: 0.009ms
0
default: 0.388ms
1000
default: 0.340ms
2000
default: 0.333ms
3000
default: 0.317ms
4000
default: 0.330ms
5000
default: 0.289ms
6000
default: 0.299ms
7000
default: 0.291ms
8000
default: 0.320ms
9000
default: 0.284ms
10000
default: 0.343ms
11000
default: 0.327ms
12000
default: 0.317ms
13000
default: 0.329ms
14000
default: 0.307ms
15000
default: 0.218ms
16000
default: 0.193ms
17000
default: 0.234ms
18000
default: 1.943ms
19000
default: 0.164ms
20000
default: 0.148ms

2 个答案:

答案 0 :(得分:1)

虽然tf.concatArray.push函数的外观和行为相似,但有一个很大的区别:

  • tf.concat从输入中创建一个新张量
  • Array.push将输入添加到第一个数组

示例

tf.concat

const a = tf.tensor1d([1, 2]);
const b = tf.tensor1d([3]);
const c = tf.concat([a, b]);

a.print(); // Result: Tensor [1, 2]
b.print(); // Result: Tensor [3]
c.print(); // Result: Tensor [1, 2, 3]

结果变量c是新的张量,而ab不变。

Array.push

const a = [1,2];
a.push(3);

console.log(a); // Result: [1,2,3]

在这里,变量a被直接更改。

对运行时的影响

对于运行时速度,这意味着tf.concat在添加输入之前将所有张量值复制到新的张量。显然,需要复制的数组越大,花费的时间就越多。与此相反,Array.push不会创建数组的副本,因此无论数组有多大,运行时间都将大致相同。

请注意,这是“设计使然”的,因为张量是不可变的,因此对现有张量的每个操作始终会创建一个新的张量。引用docs

  

张量是不可变的,因此所有操作总是返回新的张量,而从不修改输入张量。

因此,如果需要根据输入数据创建大张量,建议先从文件中读取所有数据,然后将其与“普通” JavaScript函数合并,然后再从中创建张量。

处理数据太大而导致内存不足

如果数据集太大,由于内存限制,需要分块处理它,则有两个选择:

  1. 使用trainOnBatch函数
  2. 使用数据集生成器

选项1:trainOnBatch

trainOnBatch函数允许训练一批数据,而不是使用全部数据集。因此,您可以在训练它们之前将代码分成合理的批次,这样就不必一次将所有数据合并在一起。

选项2:数据集生成器

另一个答案已经超出了基础知识。这将允许您使用JavaScript generator function来准备数据。我建议使用生成器语法而不是迭代器工厂(用于其他答案),因为它是更现代的JavaScript语法。

示例(摘自docs):

function* dataGenerator() {
  const numElements = 10;
  let index = 0;
  while (index < numElements) {
    const x = index;
    index++;
    yield x;
  }
}

const ds = tf.data.generator(dataGenerator);

然后您可以使用fitDataset函数来训练模型。

答案 1 :(得分:0)

尽管没有单一的方法来创建张量,但问题的答案在于如何处理创建的张量。

性能

张量是不可变的,因此每次tf.concat被称为创建新张量。

let x = tf.tensor1d([2]);
console.log(tf.memory()) // "numTensors": 1
const y = tf.tensor1d([3])
x = tf.concat([x, y])
console.log(tf.memory()) // "numTensors": 3, 
<html>
  <head>
    <!-- Load TensorFlow.js -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.14.1"> </script>
  </head>

  <body>
  </body>
</html>

从上面的片段中我们可以看到,调用tf.concat时创建的张量数量为 3 而不是 2 。的确,tf.tidy将处理未使用的张量。但是,随着创建的张量越来越大,这种创建和处理张量的操作将变得最昂贵。这既是内存消耗又是计算的问题,因为创建新的张量将始终委托给后端。


从大数据创建张量

现在了解性能问题了,最好的处理方法是什么?

  • 在js中创建整个数组,并在完成整个数组后创建张量。
for (i= 0; i < data.length; i++) {
  // fill array x
  x.push(dataValue)
}
// create the tensor
tf.tensor(x)

尽管这是微不足道的解决方案,但并非总是可行。因为创建数组会将数据保留在内存中,所以我们很容易用大数据条目耗尽内存。因此有时,最好不要创建整个javascript数组来创建数组块,并从这些数组块中创建张量,并在创建后立即开始处理这些张量。如有必要,可以再次使用tf.concat合并块张量。但这并不总是必需的。

例如,我们可以使用张量块重复调用model.fit(),而不必使用可能需要很长时间才能创建的大张量调用一次。在这种情况下,不需要连接块张量。

  • 如果可能,请使用tf.data创建数据集。如果我们接下来要用数据拟合模型,这是理想的解决方案。
function makeIterator() {

  const iterator = {
    next: () => {
      let result;
      if (index < data.length) {
        result = {value: dataValue, done: false};
        index++;
        return result;
      }
      return {value: dataValue, done: true};
    }
  };
  return iterator;
}
const ds = tf.data.generator(makeIterator);

使用tf.data的优点是,整个数据集是在model.fit调用期间在需要时分批创建的。