在这篇文章中,我们将介绍描述神经网络数学的核心元素之一:张量

虽然通常情况下,你不会直接与张量打交道(通常它们在引擎盖下运行),但了解幕后发生的事情是很重要的。此外,你可能经常希望检查张量,这样你就可以直接查看数据,或者查看权重和偏置的阵列,所以能够与张量一起工作是很重要的。
注意:本文假设你熟悉神经网络的工作原理。
理论上,我们可以使用纯 Python 来实现神经网络。
然而,这有几个问题。Python,特别是列表数据类型,执行得相当慢。另外,用嵌套的for 循环,代码的可读性就不高了。
相反,在PyTorch等软件包中实现神经网络的库使用了张量,它们的运行速度比纯Python要快得多。另外,正如你将看到的,张量允许对网络及其数据进行更可读的描述。
张量
张量本质上是值的数组。由于神经网络本质上是神经元的数组,所以张量是描述它们的自然选择。它们可以用来描述数据,描述网络连接权重,以及其他东西。
一个一维的张量被称为矢量。这里有一个例子。

向量也可以用水平方式书写。下面是同一个向量的水平书写。

将一个向量从垂直方向转换为水平方向,或反之亦然,称为转置,有时需要转置,这取决于数学上的具体情况。在这篇文章中,我们将不详细讨论这个问题。
矢量通常用于表示网络中的数据。例如,向量中的每个单独元素可以代表网络中每个单独输入神经元的输入值。
二维张量矩阵
一个二维张量被称为矩阵。这里有一个例子。

对于一个完全连接的网络,其中一层的每个神经元都连接到下一层的每个神经元,通常用一个矩阵来表示所有的连接权重。如果有m 神经元连接到n 神经元,你将需要一个n x m 矩阵来描述所有的连接权重。
这里有一个例子,两个神经元连接到三个神经元。这里是网络,包括连接权重。

这里是连接权重矩阵。

我们为什么使用张量
在我们完成介绍张量之前,让我们用我们到目前为止所看到的东西来看看为什么它们在为神经网络建模时如此重要。
让我们引入一个两元素的数据向量,并通过我们刚才展示的网络运行它。
信息:回顾一下神经元把它们的加权输入加在一起,然后通过激活函数运行结果。
在这个例子中,我们忽略了激活函数,以便在演示时保持简单。
这里是我们的数据向量。

这是一个描述操作的图示。

让我们用手来计算操作(神经元的计算)。

最后的结果是一个3元素的向量。

如果你在小学时学过矩阵,记得做过**矩阵乘法**,你可能会注意到我们刚才的计算与矩阵乘法是一样的。

注:记得矩阵乘法是将第一矩阵的行与第二矩阵的列按元素相乘,然后将元素相加。
这就是为什么张量对神经网络如此重要:张量数学精确地描述了神经网络的操作。
作为一个额外的好处,上面显示矩阵乘法的方程比嵌套的for 循环的描述要简洁得多。
如果我们用黑体小写表示矢量,黑体大写表示矩阵,那么矢量数据通过神经网络权重矩阵的运行就可以用这个非常紧凑的方程来描述。

我们将在后面看到,PyTorch内部的矩阵乘法也是一个类似的紧凑代码方程。
高维标量
三维(3D)张量被简单地称为张量。正如你所看到的,张量 一词泛指任何尺寸的数字阵列。只是一维和二维张量分别有 "向量 "和 "矩阵 "的独特名称。
你可能认为没有必要使用三维和更大的张量,但这并不完全正确。
一个灰度图像显然是一个二维张量,换句话说,是一个矩阵。但一幅彩色图像实际上是三个二维数组,红、绿、蓝三色通道各占一个。因此,彩色图像本质上是一个三维的张量。
此外,我们通常以小批量的方式处理数据。因此,如果我们要处理一个迷你批次的彩色图像,我们已经注意到了三维的方面,再加上迷你批次中的图像列表的一个维度。因此,一个迷你批次的彩色图像可以用一个四维的张量来表示。
神经网络库中的张量
一个非常适合处理数组的Python库是NumPy。事实上,NumPy被一些用户用来实现神经网络。一个例子是scikit-learn机器学习库,它与NumPy一起工作。
然而,PyTorch实现的张量比NumPy数组更强大。PyTorch张量的设计考虑到了神经网络。PyTorch的张量有这些优势。
- PyTorch的张量包括集成在其中的梯度计算。
- PyTorch张量还支持GPU计算,大大加快了神经网络的计算速度。
然而,如果你习惯于使用NumPy,你应该对PyTorch张量感到相当自如。虽然创建PyTorch张量的命令略有不同,但它们会让你感到相当熟悉。在本文的其余部分,我们将专门讨论PyTorch的张量。
PyTorch中的张量。创建它们,并进行数学运算
好了,让我们最后做一些编码工作吧

首先,确保你有PyTorch,可以通过安装在你的系统上或通过在线Jupyter笔记本服务器访问它。
在这篇文章中,我们将使用谷歌提供的在线Jupyter笔记本服务,名为Colab。PyTorch已经安装在Colab中,我们只需将其作为一个模块导入即可使用。
import torch
在PyTorch中,有许多创建张量的方法。
通常情况下,你会通过从PyTorch提供的数据集中导入数据来创建张量,或者将你自己的数据转换为张量。
目前,由于我们只是想演示一下张量的使用,我们将使用基本命令来创建非常简单的张量。
你可以从一个列表中创建一个张量。
t_list = torch.tensor([[1,2], [3,4]])
t_list
输出。
tensor([[1, 2],
[3, 4]])
请注意,当我们对张量变量进行评估时,输出被标记为表示它是一个张量。这意味着它是一个PyTorch张量对象,所以在PyTorch中的一个对象,执行起来就像数学张量一样,另外还有PyTorch提供的各种功能(比如支持梯度计算,支持GPU处理)。
你可以创建充满零、充满一或充满随机数的张量。
t_zeros = torch.zeros(2,3)
t_zeros
输出。
tensor([[0., 0., 0.],
[0., 0., 0.]])
t_ones = torch.ones(3,2)
t_ones
输出。
tensor([[1., 1.],
[1., 1.],
[1., 1.]])
t_rand = torch.rand(3,2,4)
t_rand
输出:输出。
tensor([[[0.9661, 0.3915, 0.0263, 0.2753],
[0.7866, 0.0503, 0.3963, 0.1334]],
[[0.4085, 0.1816, 0.2827, 0.3428],
[0.9923, 0.4543, 0.0872, 0.0771]],
[[0.2451, 0.6048, 0.8686, 0.8148],
[0.7930, 0.4150, 0.6125, 0.3401]]])
要了解张量的形状,需要熟悉的一个重要属性是被恰当地命名为 **[shape](https://blog.finxter.com/how-to-get-shape-of-array/)**属性。
t_rand.shape
# Output: torch.Size([3, 2, 4])
这表明张量 "t_rand" 是一个三维张量,由两行四列的三个元素组成。
注:张量的维度被称为其 rank.一个一维的张量,或矢量,是一个秩1张量;一个二维的张量,或矩阵,是一个秩2张量;一个三维的张量是一个秩3张量,以此类推。
让我们用张量做一些数学运算--让我们把两个张量加在一起。

请注意,张量是按元素相加的。现在在PyTorch中是这样的。
t_first = torch.tensor([[1,2], [3,4]])
t_second = torch.tensor([[5,6],[7,8]])
t_sum = t_first + t_second
t_sum
输出。
tensor([[ 6, 8],
[10, 12]])
让我们把一个标量,也就是一个独立的数字(或者一个0级张量!)加到一个张量上。
t_add3 = t_first + 3
t_add3
输出。
tensor([[4, 5],
[6, 7]])
请注意,标量被添加到张量的每个元素中。同样的情况也适用于标量乘以张量的时候。
t_times3 = t_first * 3
t_times3
输出。
tensor([[ 3, 6],
[ 9, 12]])
同样的事情也适用于将张量提高到一个幂,也就是幂运算是逐个元素应用的。
t_squared = t_first ** 2
t_squared
输出。
tensor([[ 1, 4],
[ 9, 16]])
回顾一下,在加权输入相加后,神经元通过激活函数处理结果。请注意,同样的性能也适用于此:当一个向量通过激活函数处理时,该操作是逐元地应用于向量。
早些时候,我们指出,矩阵乘法是神经网络计算的一个重要部分。
在PyTorch中,有两种方法可以做到这一点:你可以使用 [matmul](https://blog.finxter.com/python-__matmul__-magic-method/)函数。
t_matmul1 = torch.matmul(t_first, t_second)
t_matmul1
输出。
tensor([[19, 22],
[43, 50]])
或者你可以使用矩阵乘法符号"[@](https://blog.finxter.com/numpy-matmul-operator/)":
t_matmul2 = t_first @ t_second
t_matmul2
输出。
tensor([[19, 22],
[43, 50]])
回顾之前,我们展示了通过神经网络运行输入信号,其中输入信号的向量被乘以连接权重的矩阵。
下面是PyTorch中的情况。
x = torch.tensor([[7],[8]])
x
输出。
tensor([[7],
[8]])
W = torch.tensor([[1,4], [2,5], [3,6]])
W
输出。
tensor([[1, 4],
[2, 5],
[3, 6]])
y = W @ x
y
输出。
tensor([[39],
[54],
[69]])
请注意,这比起做嵌套的for 循环是多么紧凑和可读。
其他数学运算也可以用张量来完成,但我们已经涵盖了与神经网络相关的大多数情况。如果你发现你需要用你的张量做额外的数学运算,请查看PyTorch文档或进行网络搜索。
索引和切分张量
分片允许你检查数据的子集,并更好地理解数据集是如何构建的。你可能会发现你会经常使用这个。
索引切片 PyTorch vs NumPy vs Python Lists
索引和切分张量的工作方式与NumPy数组的工作方式相同。请注意,其语法与Python列表不同。对于 Python 列表,每一层嵌套的列表都有一对单独的括号。相反,在Pytorch中,一对括号包含了所有的维度,用逗号分隔。
让我们在张量 "t_rand" 中找到第二元素,第一行,第三列的项目。首先,这里又是 "t_rand"。
t_rand
输出。
tensor([[[0.9661, 0.3915, 0.0263, 0.2753],
[0.7866, 0.0503, 0.3963, 0.1334]],
[[0.4085, 0.1816, 0.2827, 0.3428],
[0.9923, 0.4543, 0.0872, 0.0771]],
[[0.2451, 0.6048, 0.8686, 0.8148],
[0.7930, 0.4150, 0.6125, 0.3401]]])
这里是第二元素、第一行、第三列的项目(别忘了索引从零开始)。
t_rand[1, 0, 2]
# Output: tensor(0.2827)
让我们看一下第二元素的切片,第一行,第二到第三列。
t_rand[1, 0, 1:3]
# tensor([0.1816, 0.2827])
让我们看一下整个第三列。
t_rand[:, :, 2]
输出。
tensor([[0.0263, 0.3963],
[0.2827, 0.0872],
[0.8686, 0.6125]])
重要的切片提示:在上面,我们使用了标准的Python惯例,即在 " "之前的空白表示 "从头开始",而在 " "之后的空白表示 "一直到最后"。所以单独的 " "意味着 "包括从头到尾的所有内容"。:``:``:
切片的一个可能用途是在一组数组中查看一个完整的数组(即一个矩阵),即在一组图像中查看一个图像。
让我们假设我们的 "t_rand" 张量是一个图像列表。我们可能希望只对一些 "图像 "进行采样,以了解它们是什么样子的。
让我们检查一下我们的张量中的第一个 "图像"("图像列表")。
t_rand[0]
输出。
tensor([[0.9661, 0.3915, 0.0263, 0.2753],
[0.7866, 0.0503, 0.3963, 0.1334]])
这里是张量 "t_rand "中的最后一个数组("图像")。
t_rand[-1]
输出。
tensor([[0.2451, 0.6048, 0.8686, 0.8148],
[0.7930, 0.4150, 0.6125, 0.3401]])
使用小张量来演示索引是很有启发性的,但让我们看看它的实际效果。让我们来看看一些有真实图像的真实数据集。
真实的例子
我们不会详细描述下面的内容,只是注意到我们正在导入各种库,使我们能够下载和处理数据集。最后一行创建一个函数,将张量转换为PIL图像。
import torch
from torch.utils.data import Dataset
from torchvision import datasets
from torchvision.transforms import ToTensor
import matplotlib.pyplot as plt
import torchvision.transforms as T
conv_to_PIL = T.ToPILImage()
以下是下载Caltech 101数据集,它是101个类别的8000多张图像的集合。
caltech101_data = datasets.Caltech101(
root="data",
download=True,
transform=ToTensor()
)
Extracting data/caltech101/101_ObjectCategories.tar.gz to data/caltech101
Extracting data/caltech101/Annotations.tar to data/caltech101
这已经创建了一个数据集对象,它是数据的容器。这些对象可以像列表一样被索引。
len(caltech101_data)
# 8677
type(caltech101_data[0])
# tuple
len(caltech101_data[0])
# 2
上面的代码显示该数据集包含8677个项目。看一下这个集合的第一个项目,我们可以看到它们是每个项目都有2个的tuples。下面是图元中项目的种类。
type(caltech101_data[0][0])
# torch.Tensor
type(caltech101_data[0][1])
# int
元组中的两个项目是作为张量的图像,以及对应于图像类别的整数代码。
Colab有一个方便的函数 **display()**这将显示图像。首先,我们使用之前创建的转换函数将我们的张量转换成PIL图像,然后显示图像。
img = conv_to_PIL(caltech101_data[0][0])
display(img)

我们可以使用索引来取样并显示集合中的其他一些图像。
img = conv_to_PIL(caltech101_data[1234][0])
display(img)

img = conv_to_PIL(caltech101_data[4321][0])
display(img)

总结
我们已经学到了一些东西。
- 张量是什么
- 为什么张量是描述和实现神经网络的关键数学对象?
- 在PyTorch中创建张量
- 在PyTorch中用张量做数学运算
- 在PyTorch中对张量进行索引和切片,特别是检查数据集中的图像
我们希望你能发现这篇文章的信息。我们祝您编码愉快!