张量及张量运算

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像素的方格图案,可以利用一个形状为[343]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)。一元运算的输入为一个张量,返回的是一个新张量。二元运算的输入是两个张量,返回的也是一个新张量。

和一元运算不同,二元运算有两个参数。

1tf.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]]

2tf.sub()可以计算两个张量的差。

3tf.mul()可以计算两个张量的积。

4tf.matMul()可以计算两个矩阵的积。

5tf.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]。在矩阵的语境下,它代表12列。

由此得到的结果就是矩阵的第二行。 

如果想要的是矩阵的列而不是行,那该怎么办呢?可以使用如下的写法。

> x.slice([0, 1], [-1, 1]).print();
Tensor
[[0.6011682 ],
[-0.5025602],
[-0.0009868]]

此处的第一个参数([0, 1])代表的是待提取数据的起始索引,即第一维度的第一个索引和第二维度的第二个索引。第二个参数([-1, 1])表示切片的尺寸。该数组中的第一个数(-1)表示我们想要第一维度中的所有索引(即所有的行),第二个数(1)表示我们只想要第二维度的一个索引(即一列元素)。

由此得到的结果就是矩阵的第二列。

  四、结束语 

我们生活在一个充满数据的世界,归根到底,我们都知道数据最终都是10。对我们很多人来说,这是非常神奇的。你用手机拍一张照片,就会创建一个复杂的二进制文件。然后,你上下滑动几下屏幕,二进制文件就会瞬间从JPG变成PNG。调整文件大小时间,重新格式化文件时,还有对照片加滤镜时,会在几微秒时间内生成或撤销成千上万的数据字节。当你大胆地去真正触摸、感受和输入数据时,必须抛弃“无知便是福”的想法。 

引用1998年的电影《刀锋战士》:

“你最好醒醒。你生活的世界只不过是一层糖衣。在它下面还有另一个世界。” 

好吧,确实是这样,但没有那么紧张。要训练一个AI,你需要确保你的数据是统一的,而且你需要理解并看到数据。并不是训练AI来完成PNGJPG的解码任务,而是要在已解码的具体照片内容上训练AI

这意味着图像、音乐、统计数据以及你在TensorFlow.js模型(本文的默认张量生存环境为TensorFlow.js模型)中使用的任何其他数据都需要一个统一、优化的数据格式。在理想情况下,我们的数据将转换为数字容器,能够快速伸缩,并且可以直接在GPUWeb Assembly中进行计算优化。我们需要一些简洁明了的结构来存放信息数据。这个容器应当没有特定限制,从而能存放任何数据。这就是使用张量的原因。

拥抱张量吧!

 

 

Tags: , , , , , ,

IT技术

添加评论

  Country flag

biuquote
  • 评论
  • 在线预览
Loading