使用 Tensorflow.js 和 tf.Tensor 处理大数据的最佳方法是什么?
What is the best way to handle large data with Tensorflow.js and tf.Tensor?
问题
我正在使用 tf.Tensor
和 tf.concat()
来处理大量训练数据,
我发现连续使用 tf.concat()
会变慢。
将大数据从文件加载到 tf.Tensor
的最佳方法是什么?
背景
我认为在Javascript中按数组处理数据是常见的方式。
要实现这一目标,请执行以下粗略步骤。
从文件加载数据到数组的步骤
- 从文件中读取行
- 将行解析为 Javascript 的对象
- 通过
Array.push()
将该对象添加到数组
- 读完一行后,我们可以用for循环使用那个数组。
所以我想我可以像上面那样使用 tf.concat()
。
将数据从文件加载到 tf.Tensor
的步骤
- 从文件中读取行
- 将行解析为 Javascript 的对象
- 将对象解析为 tf.Tensor
- 将张量添加到原始张量
tf.concat()
- 读完一行后,我们可以使用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
虽然 tf.concat
和 Array.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
是一个新的张量,而 a
和 b
没有改变。
Array.push
const a = [1,2];
a.push(3);
console.log(a); // Result: [1,2,3]
这里直接改变量a
对运行时的影响
对于运行速度,这意味着 tf.concat
在添加输入之前将所有张量值复制到一个新的张量。显然,需要复制的数组越大,花费的时间就越多。与此相反,Array.push
不会创建数组的副本,因此无论数组有多大,运行时都或多或少相同。
请注意,这是 "by design",因为张量是不可变的,因此对现有张量的每次操作都会创建一个新的张量。引自 docs:
Tensors are immutable, so all operations always return new Tensors and never modify input Tensors.
因此,如果您需要从输入数据创建一个大张量,建议首先从您的文件中读取所有数据并将其与 "vanilla" JavaScript 函数合并,然后再从中创建张量.
处理内存太大的数据
如果您的数据集太大以至于由于内存限制需要分块处理,您有两个选择:
- 使用
trainOnBatch
函数
- 使用数据集生成器
选项 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
函数来训练您的模型。
虽然没有一种方法可以创建张量,但问题的答案在于如何处理创建的张量。
性能
张量是不可变的,因此每次调用 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
调用期间需要时批量创建的。
问题
我正在使用 tf.Tensor
和 tf.concat()
来处理大量训练数据,
我发现连续使用 tf.concat()
会变慢。
将大数据从文件加载到 tf.Tensor
的最佳方法是什么?
背景
我认为在Javascript中按数组处理数据是常见的方式。 要实现这一目标,请执行以下粗略步骤。
从文件加载数据到数组的步骤
- 从文件中读取行
- 将行解析为 Javascript 的对象
- 通过
Array.push()
将该对象添加到数组
- 读完一行后,我们可以用for循环使用那个数组。
所以我想我可以像上面那样使用 tf.concat()
。
将数据从文件加载到 tf.Tensor
的步骤- 从文件中读取行
- 将行解析为 Javascript 的对象
- 将对象解析为 tf.Tensor
- 将张量添加到原始张量
tf.concat()
- 读完一行后,我们可以使用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
虽然 tf.concat
和 Array.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
是一个新的张量,而 a
和 b
没有改变。
Array.push
const a = [1,2];
a.push(3);
console.log(a); // Result: [1,2,3]
这里直接改变量a
对运行时的影响
对于运行速度,这意味着 tf.concat
在添加输入之前将所有张量值复制到一个新的张量。显然,需要复制的数组越大,花费的时间就越多。与此相反,Array.push
不会创建数组的副本,因此无论数组有多大,运行时都或多或少相同。
请注意,这是 "by design",因为张量是不可变的,因此对现有张量的每次操作都会创建一个新的张量。引自 docs:
Tensors are immutable, so all operations always return new Tensors and never modify input Tensors.
因此,如果您需要从输入数据创建一个大张量,建议首先从您的文件中读取所有数据并将其与 "vanilla" JavaScript 函数合并,然后再从中创建张量.
处理内存太大的数据
如果您的数据集太大以至于由于内存限制需要分块处理,您有两个选择:
- 使用
trainOnBatch
函数 - 使用数据集生成器
选项 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
函数来训练您的模型。
虽然没有一种方法可以创建张量,但问题的答案在于如何处理创建的张量。
性能
张量是不可变的,因此每次调用 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
调用期间需要时批量创建的。