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

ya9*_*9do 5 javascript node.js deep-learning tensorflow 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)
}
Run Code Online (Sandbox Code Playgroud)

测量

我们可以在 上看到稳定的过程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
Run Code Online (Sandbox Code Playgroud)

对于 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
Run Code Online (Sandbox Code Playgroud)

Tho*_*orf 5

虽然tf.concatandArray.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]
Run Code Online (Sandbox Code Playgroud)

结果变量c是一个新的张量,而ab没有改变。

Array.push

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

console.log(a); // Result: [1,2,3]
Run Code Online (Sandbox Code Playgroud)

a这里直接改变变量。

对运行时的影响

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

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

张量是不可变的,因此所有操作始终返回新的张量,并且永远不会修改输入张量。

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

处理内存太大的数据

如果您的数据集太大,由于内存限制而需要分块处理,您有两种选择:

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

选项 1:trainOnBatch

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

选项 2:数据集生成器

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

示例(取自文档):

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

const ds = tf.data.generator(dataGenerator);
Run Code Online (Sandbox Code Playgroud)

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


edk*_*ked 3

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

表现

张量是不可变的,因此每次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, 
Run Code Online (Sandbox Code Playgroud)
<html>
  <head>
    <!-- Load TensorFlow.js -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@0.14.1"> </script>
  </head>

  <body>
  </body>
</html>
Run Code Online (Sandbox Code Playgroud)

正如我们从上面的代码片段中看到的,调用 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)
Run Code Online (Sandbox Code Playgroud)

虽然这是一个简单的解决方案,但并不总是可行。因为创建数组会将数据保存在内存中,而大数据条目很容易耗尽内存。因此,有时,最好不要创建整个 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);
Run Code Online (Sandbox Code Playgroud)

使用 tf.data 的优点是整个数据集是在model.fit调用过程中需要时批量创建的。