posted at 2025.3.11 10:38 by Administrator
一、张量概述
张量是一个任意维度的结构化类型的数据集合,是信息表示的首选格式,对于非数值类型甚至还有一些抽象,它们就像是从物理世界传递到AI大脑的电信号。可以理解为一个优化的数据分组,从数学上讲,传统的JavaScript数组是一个张量,2D数组是一个张量,多维数组也是一个张量。
张量是数据的容器。每个张量都有两个基本属性:数据类型(dtype)和形状(shape)。dtype 负责控制每个张量可以存储什么样的值,并且每个张量只能存储一种类型的值。shape 是由整数组成的数组,它表示张量中有多少元素,以及这些元素的组织结构。因此,可以将属性看作张量这个数据容器的“形状和尺寸”。
形状数组的长度叫作张量的阶(rank)。例如,一维张量为一阶,它又叫作向量。一维张量的形状为包含一个数的数组,可以从中看出一维张量的长度。
如果将形状的阶加 1,就会得到一个二维张量。可以将二维张量看作二维平面上一个由数组成的网格(就像灰度图像一样)。二维张量的形状数组由两个数组成,可以从中看出网格高和宽。
继续增加形状的阶,就会得到一个三维张量。如图 B-1 中的示例所示,可以将三维张量看作由数组成的三维网格。三维张量的形状数 组由三个整数组成,可以从中看出三维网格在三个维度上的尺寸。你应该已经看出了阶的变化规律。
因为我们所在的现实世界只有三个维度,所以很难直观地想象四阶张量(即四维张量)是什么样子的。四维张量是很多模型(例如深度 convnet)的常用张量类型。TensorFlow.js 支持的最 高阶数为六。事实上,只有极少数场景(例如表示视频数据时)会用到五阶张量,而六阶张量就更少见了。
数据与张量的关系:
1、标量数据是形状为空数组(即[])的张量。它没有任何轴,并总是有且只有一个值。
2、向量数据:形状为[numExamples, features]的二维张量。
3、时间序列或一般的序列数据:形状为[numExamples, timesteps, features]的三维 张量。
4、图像数据:形状为[numExamples, height, width, channels]的四维张量。
5、视频数据:形状为[numExamples, frame, height, width, channels]的五维张量。
二、张量创建
//以导入TensorFlow.js库为基础。
1、创建一个一阶张量
> const myVector = tf.tensor1d([-1.2, 0, 19, 78]);> myVector.shape; [4]> myVector.rank; 1> await myVector.data();Float32Array(4) [-1.2, 0, 19, 78]
2、创建一个二阶张量
> const myMatrix = tf.tensor2d([[1, 2, 3], [40, 50, 60]]);> myMatrix.shape; [2, 3]> myMatrix.rank; 2
3、创建一个三阶张量
> const myRank3Tensor = tf.tensor3d([[[1, 2, 3], [4, 5, 6]], [[10, 20, 30], [40, 50, 60]]]);> myRank3Tensor.shape; [2, 2, 3]> myRank3Tensor.rank; 3
4、创建一个元素值都为0的张量
> const x = tf.zeros([2, 3, 3]);> const y = tf.zerosLike(x);
5、创建元素值全为1的张量
使用tf.ones()函数和tf.onesLike()函数。
6、创建随机值张量
有很多应用场景,比如权重初始化,使用tf.randomNormal()和tf.randomUniform()函数。
> const x = tf.randomNormal([2, 3]);> x.print():Tensor [[-0.2772508, 0.63506 , 0.3080665], [0.7655841 , 2.5264773, 1.142776 ]]
7、创建图像张量
你可能认为,图像转换为一个张量时,得到的张量秩为2,二阶张量只适用于灰度图像。对于彩色像素,最常见的做法是将其表示为3个单独的值(RGB值)。存储彩色图像至少需要一个3D数组。
例如,如果你想创建一个4*3像素的方格图案,可以利用一个形状为[3,4,3]的3D数组手动创建。
const checky = tf.tensor([ [1,1,1], [0,0,0], [1,1,1], [0,0,0]],[ [ 0,0,0], [1,1,1], [0,0,0],[1,1,1]],[ [1,1,1], [0,0,0], [1,1,1], [0,0,0]]
在 TensorFlow.js 中,如果你想直接处理张量对象,那么同时还需要负责对它们进行内存管理。
具体而言,在创建并使用完张量后,必须记得对它们进行垃圾回收,否则它们会持续占用起初为它们分配的内存。如果未回收的张量过多或总体积过大,它们最终会导致浏览器标签页耗尽 WebGL 内存,或使 Node.js 进程耗尽系统或 GPU 显存。
幸运的是所有的张量和模型都有一个.dispose()方法,用于从内存中清除张量。还有一个名为.tidy()的自动清理函数,它会清除所有未返回或者未用keep()函数保留的张量。
// Start at zero tensors console.log('start', tf.memory().numTensors) let keeper, chaser, seeker, beater tf.tidy(() => { keeper = tf.tensor([1, 2, 3]) chaser = tf.tensor([1, 2, 3]) seeker = tf.tensor([1, 2, 3]) beater = tf.tensor([1, 2, 3]) // Now we're at four tensors in memory console.log('inside tidy', tf.memory().numTensors) tf.keep(keeper) return chaser }) console.log('after tidy', tf.memory().numTensors) keeper.dispose() //显式撤销tf.keep保留下来的一个张量 chaser.dispose() //显式撤销返回的张量 // Back to zero console.log('end', tf.memory().numTensors)
三、张量运算
(一)张量的基本运算
对张量进行运算才能真正发挥张量的作用。TensorFlow.js 支持大量的张量运算,可以在 TensorFlow 官方文档中查看相关介绍。
最常用的运算,可分为两个类别:一元运算(unary operation)和二元运算(binary operation)。一元运算的输入为一个张量,返回的是一个新张量。二元运算的输入是两个张量,返回的也是一个新张量。
和一元运算不同,二元运算有两个参数。
1、tf.add()是最常用也最简单的二元运算,它的作用是将两个张量相加。
> const x = tf.tensor2d([[0, 2], [4, 6]]);> const y = tf.tensor2d([[10, 20], [30, 46]]);> tf.add(x, y).print(); Tensor [[10, 22], [34, 52]]
2、tf.sub()可以计算两个张量的差。
3、tf.mul()可以计算两个张量的积。
4、tf.matMul()可以计算两个矩阵的积。
5、tf.logicalAnd()、tf.logicalOr()和 tf.logicaXor()适用于布尔类型的张量,它们
分别可以进行与(AND)、或(OR)和异或(XOR)运算。
(二)张量的特殊运算
张量的特殊运算包括张量的拼接、提取或拆分。
常用的拼接和拆分张量的方法:tf.concat()、tf.slice()、tf.unstack()和tf.stack()。
tf.concat()可能是拼接运算中最常用的一个,作用是将多个形状匹配的张量拼接(concatenate)成单个张量。只有当张量的形状满足某种约束条件时, 拼接才可行。比如,沿着第一个轴拼接形状为[5, 3]的张量和形状为[4, 3]的张量是可行的,并且可以得到形状为[9, 3]的输出张量。但是,如果它们的形状是[5, 3]和[4, 2],那么拼接就是不可行的。
除了将多个张量拼接成一个,有时我们还想做拼接的“逆运算”,即提取或拆分出张量的一部分。
例如,假设你创建了一个形状为[3, 2]的二维张量(矩阵):
> const x = tf.randomNormal([3, 2]);> x.print();Tensor[[1.2366893 , 0.6011682 ],[-1.0172369, -0.5025602],[-0.6265425, -0.0009868]]
并且想提取出矩阵的第二行。为此,你可以使用tf.slice()的链式写法。
> x.slice([1, 0], [1, 2]).print();Tensor[[-1.0172369, -0.5025602],]
slice()的第一个参数表示想要提取的张量始于第一个维度的索引1和第二个维度的索引0位置。第二个参数表示想要的输出形状,即[1, 2]。在矩阵的语境下,它代表1行2列。
由此得到的结果就是矩阵的第二行。
如果想要的是矩阵的列而不是行,那该怎么办呢?可以使用如下的写法。
> x.slice([0, 1], [-1, 1]).print();Tensor[[0.6011682 ],[-0.5025602],[-0.0009868]]
此处的第一个参数([0, 1])代表的是待提取数据的起始索引,即第一维度的第一个索引和第二维度的第二个索引。第二个参数([-1, 1])表示切片的尺寸。该数组中的第一个数(-1)表示我们想要第一维度中的所有索引(即所有的行),第二个数(1)表示我们只想要第二维度的一个索引(即一列元素)。
由此得到的结果就是矩阵的第二列。
四、结束语
我们生活在一个充满数据的世界,归根到底,我们都知道数据最终都是1和0。对我们很多人来说,这是非常神奇的。你用手机拍一张照片,就会创建一个复杂的二进制文件。然后,你上下滑动几下屏幕,二进制文件就会瞬间从JPG变成PNG。调整文件大小时间,重新格式化文件时,还有对照片加滤镜时,会在几微秒时间内生成或撤销成千上万的数据字节。当你大胆地去真正触摸、感受和输入数据时,必须抛弃“无知便是福”的想法。
引用1998年的电影《刀锋战士》:
“你最好醒醒。你生活的世界只不过是一层糖衣。在它下面还有另一个世界。”
好吧,确实是这样,但没有那么紧张。要训练一个AI,你需要确保你的数据是统一的,而且你需要理解并看到数据。并不是训练AI来完成PNG和JPG的解码任务,而是要在已解码的具体照片内容上训练AI。
这意味着图像、音乐、统计数据以及你在TensorFlow.js模型(本文的默认张量生存环境为TensorFlow.js模型)中使用的任何其他数据都需要一个统一、优化的数据格式。在理想情况下,我们的数据将转换为数字容器,能够快速伸缩,并且可以直接在GPU或Web Assembly中进行计算优化。我们需要一些简洁明了的结构来存放信息数据。这个容器应当没有特定限制,从而能存放任何数据。这就是使用张量的原因。
拥抱张量吧!
966186c7-375e-40b0-8755-5a6a107871d0|0|.0|96d5b379-7e1d-4dac-a6ba-1e50db561b04
Tags: 3D, 方法, 数据, 类, 模, 手机, 物理
IT技术