1. 张量
张量概念是矢量概念的推广,矢量是一阶张量。张量是一个可用来表示在一些矢量、标量和其他张量之间的线性关系的多线性函数。[1] 张量 - 百度百科
张量是数据的一种存储方式,向量、矩阵等可以称为1阶或2阶张量,同理张量也可以表示n阶多维numpy数组。
下图为 [2]不同维度的张量可视化表示 。
2.算子
算子是一个函数空间到函数空间上的映射O:X→X。广义上的算子可以推广到任何空间,如内积空间等。[3] 算子 - 百度百科
如果我们实现每个基础函数的前向函数和反向函数,就可以非常方便地通过这些基础函数组合出复杂函数,并通过链式法则反向计算复杂函数的偏导数。 在深度学习框架中,这些基本函数的实现称为算子(Operator,Op)。有了算子,就可以像搭积木一样构建复杂的模型。[4]
进行实验前,我们需要先导入torch包。
import torch
# 一维张量
a = torch.tensor([2.0, 3.0, 4.0])
print("一维张量:", a)
# 二维张量
b = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
print("二维张量:", b)
# 多维张量
c = torch.tensor([[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]],
[[11, 12, 13, 14, 15], [16, 17, 18, 19, 20]]])
print("多维张量:", c)
代码执行结果:
一维张量: tensor([2., 3., 4.])
二维张量: tensor([[1., 2., 3.],
[4., 5., 6.]])
多维张量: tensor([[[ 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10]],
[[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20]]])
显然,通过给定Python列表数据,可以创建任意维度的张量。需要注意的是,张量在任何一个维度上的元素数量必须相等。
下面尝试定义一个在不同维度上元素数量不等的张量。
# 尝试定义在不同维度上元素数量不等的张量
d = torch.tensor([[1.0, 2.0], [4.0, 5.0, 6.0]])
print(d)
代码执行结果(抛出异常):
Traceback (most recent call last):
File "C:\Users\...\DeepLearningExperiment\EX2.py", line ..., in <module>
d = torch.tensor([[1.0, 2.0],[4.0, 5.0, 6.0]])
ValueError: expected sequence of length 2 at dim 1 (got 3)
显然,读取时读取到第二维有3个元素,与第一维不符,所以抛出元素数量不等的异常。
如果要创建一个指定形状、元素数据相同的张量,可以调用函数zeros, ones, full进行张量创建。
m, n = 2, 3
# 创建数据全为0,形状为[m, n]的张量
a = torch.zeros([2, 3])
print('zeros Tensor:', a)
# 创建数据全为1,形状为[m, n]的张量
b = torch.ones([2, 3])
print('ones Tensor:', b)
# 创建数据全为指定值,形状为[m, n]的张量,此处我们指定数据为10
c = torch.full([2, 3], 10)
print('full Tensor:', c)
代码执行结果:
zeros Tensor: tensor([[0., 0., 0.],
[0., 0., 0.]])
ones Tensor: tensor([[1., 1., 1.],
[1., 1., 1.]])
full Tensor: tensor([[10, 10, 10],
[10, 10, 10]])
如果要在指定区间内创建张量,可以调用函数arange, linspace进行张量创建。
# 创建以步长step均匀分隔数值区间[start, end)的一维张量
a = torch.arange(start=1, end=5, step=1)
print('arange Tensor:', a)
# 创建以元素个数num均匀分隔数值区间[start, stop]的张量
b = torch.linspace(start=1, end=5, steps=5)
print('linspace Tensor:', b)
代码执行结果:
arange Tensor: tensor([1, 2, 3, 4])
linspace Tensor: tensor([1., 2., 3., 4., 5.])
属性 | 功能 |
---|---|
ndim | 张量的维度 |
shape | 张量每个维度上元素的数量 |
shape[n] | 张量第n维的大小(n=-1时表示最后一维的大小) |
numel | 张量中全部元素的个数 |
a = torch.ones([2, 3, 4, 5])
print("维度大小:", a.ndim)
print("张量中各个维度上元素的数量:", a.shape)
print("张量第一维的大小:", a.shape[0])
print("张量最后一维的大小:", a.shape[-1])
print("张量中元素的个数:", a.numel())
上述实验代码中的四维张量([5]形状为[2, 3, 4, 5])如下图所示。
代码执行结果:
维度大小: 4
张量中各个维度上元素的数量: torch.Size([2, 3, 4, 5])
张量第一维的大小: 2
张量最后一维的大小: 5
张量中元素的个数: 120
调用reshape函数可以保持在输入数据不变的情况下,改变数据形状。
# 定义一个shape为[3,2,5]的三维张量
A = torch.tensor([[[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]],
[[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20]],
[[21, 22, 23, 24, 25],
[26, 27, 28, 29, 30]]])
print("the shape of a:", A.shape)
# 设置reshape为[2,5,3]
b = A.reshape(2, 5, 3)
print("After reshape:", b)
# 将原张量reshape为一维张量并输出张量的大小
c = A.reshape([-1])
print("After reshape:", c.shape[0])
代码执行结果:
the shape of A: torch.Size([3, 2, 5])
After reshape: tensor([[[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9],
[10, 11, 12],
[13, 14, 15]],
[[16, 17, 18],
[19, 20, 21],
[22, 23, 24],
[25, 26, 27],
[28, 29, 30]]])
After reshape: 30
调用dtype函数来查看张量的数据类型。
a = torch.tensor([1, 2, 3])
print("张量a的数据类型:", a.type())
b = torch.tensor([4.0, 5.0, 6.0])
print("张量b的数据类型:", b.type())
代码执行结果:
张量a的数据类型: torch.LongTensor
张量b的数据类型: torch.FloatTensor
使用device属性来查看张量的设备位置。
a = torch.tensor([1, 2, 3])
print("张量a的设备位置:", a.device)
代码执行结果:
张量a的设备位置: cpu
该实验使用CPU版本的pytorch运行,故输出设备位置为cpu.
import numpy
a = torch.Tensor([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = a.numpy()
print("张量a转化为数组:", b)
代码执行结果:
张量a转化为数组: [[1. 2. 3.]
[4. 5. 6.]
[7. 8. 9.]]
import numpy
a = numpy.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
b = torch.Tensor(a)
print("数组a转化为张量:", b)
代码执行结果:
数组a转化为张量: tensor([[1., 2., 3.],
[4., 5., 6.],
[7., 8., 9.]])
我们可以通过索引或切片方便地访问或修改张量。飞桨使用标准的Python索引规则与Numpy索引规则,具有以下特点:
1 基于0−n的下标进行索引,如果下标为负数,则从尾部开始计算。
2 通过冒号“:”分隔切片参数start:stop:step来进行切片操作,也就是访问start到stop范围内的部分元素并生成一个新的序列。其中start为切片的起始位置,stop为切片的截止位置,step是切片的步长,这三个参数均可缺省。[4]
与numpy相同,按位置访问即可。
(以二维张量为例)
a = torch.tensor([[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11]])
print("张量a:", a)
print("第一行:", a[0])
print("第一行:", a[0, :])
print("第一列:", a[:, 0])
print("最后一列:", a[:, -1])
print("张量中所有元素:", a[:])
print("第一行第二列的元素:", a[0, 1])
代码执行结果:
张量a: tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
第一行: tensor([0, 1, 2, 3])
第一行: tensor([0, 1, 2, 3])
第一列: tensor([0, 4, 8])
最后一列: tensor([ 3, 7, 11])
张量中所有元素: tensor([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
第一行第二列的元素: tensor(1)
与访问张量类似,可以在单个或多个轴上通过索引或切片操作来修改张量。
提醒
慎重通过索引或切片操作来修改张量,此操作仅会原地修改该张量的数值,且原值不会被保存。如果被修改的张量参与梯度计算,将仅会使用修改后的数值,这可能会给梯度计算引入风险。[4]
a = torch.zeros([2, 3])
print('张量a:', a)
# 修改第1维为1
a[0] = 1
print('修改第1维为1:', a)
# 修改第1维为2.1
a[0:1] = 2.1
print('修改第1维为2.1:', a)
# 修改全部Tensor
a[...] = 3
print('修改全部张量元素为3:', a)
代码执行结果:
张量a: tensor([[0., 0., 0.],
[0., 0., 0.]])
修改第1维为1: tensor([[1., 1., 1.],
[0., 0., 0.]])
修改第1维为2.1: tensor([[2.1000, 2.1000, 2.1000],
[0.0000, 0.0000, 0.0000]])
修改全部张量元素为3: tensor([[3., 3., 3.],
[3., 3., 3.]])
张量类的基础数学函数如下:
函数 | 功能 |
---|---|
x + y 和 x.add(y) | 逐元素加 |
x - y 和 x.subtract(y) | 逐元素减 |
x * y 和 x.multiply(y) | 逐元素乘 |
x / y 和 x.divide(y) | 逐元素除 |
x % y 和 x.mod(y) | 逐元素取余 |
x ** y 和 x.pow(y) | 逐元素幂 |
x.abs() | 逐元素取绝对值 |
x.ceil() | 逐元素向上取整 |
x.floor() | 逐元素向下取整 |
x.round() | 逐元素四舍五入 |
x.exp() | 逐元素计算自然常数为底的指数 |
x.log() | 逐元素计算x的自然对数 |
x.reciprocal() | 逐元素求倒数 |
x.square() | 逐元素计算平方 |
x.sqrt() | 逐元素计算平方根 |
x.sin() | 逐元素计算正弦 |
x.cos() | 逐元素计算余弦 |
x.max() | 指定维度上元素最大值,默认为全部维度 |
x.min() | 指定维度上元素最小值,默认为全部维度 |
x.prod() | 指定维度上元素累乘,默认为全部维度 |
x.sum() | 指定维度上元素的和,默认为全部维度 |
代码以其中一部分函数举例
a = torch.tensor([-1.0, -2, -4, -8])
print("a =", a)
print("|a| =", a.abs())
print("a + 1 =", a + 1)
print("a - 1 =", a - 1)
print("a * 2 =", a * 2)
print("a / 2 =", a / 2)
print("a % 2 =", a % 2)
print("a ** 2 =", a ** 2)
print("(-a) ** 0.5 =", (-a) ** 0.5)
代码执行结果:
a = tensor([-1., -2., -4., -8.])
|a| = tensor([1., 2., 4., 8.])
a + 1 = tensor([ 0., -1., -3., -7.])
a - 1 = tensor([-2., -3., -5., -9.])
a * 2 = tensor([ -2., -4., -8., -16.])
a / 2 = tensor([-0.5000, -1.0000, -2.0000, -4.0000])
a % 2 = tensor([1., -0., -0., -0.])
a ** 2 = tensor([ 1., 4., 16., 64.])
(-a) ** 0.5 = tensor([1.0000, 1.4142, 2.0000, 2.8284])
张量类的逻辑运算函数如下:
函数 | 功能 |
---|---|
x.isfinite() | 判断张量中元素是否是有限的数字,即不包括inf与nan |
x.equal_all(y) | 判断两个张量的全部元素是否相等,并返回形状为[1]的布尔类张量 |
x.equal(y) | 判断两个张量的每个元素是否相等,并返回形状相同的布尔类张量 |
x.not_equal(y) | 判断两个张量的每个元素是否不相等 |
x.less_than(y) | 判断张量x的元素是否小于张量y的对应元素 |
x.less_equal(y) | 判断张量x的元素是否小于或等于张量y的对应元素 |
x.greater_than(y) | 判断张量x的元素是否大于张量y的对应元素 |
x.greater_equal(y) | 判断张量x的元素是否大于或等于张量y的对应元素 |
x.allclose(y) | 判断两个张量的全部元素是否接近 |
张量类还包含了矩阵运算相关的函数,如矩阵的转置、范数计算和乘法等。
函数 | 功能 |
---|---|
x.t() | 矩阵转置 |
x.transpose([1, 0]) | 交换第0维与第1维的顺序 |
x.norm(‘fro’) | 矩阵的弗罗贝尼乌斯范数 |
x.dist(y, p=2) | 矩阵(x-y)的2范数 |
x.matmul(y) | 矩阵乘法 |
有些矩阵运算中也支持大于两维的张量,比如matmul函数,对最后两个维度进行矩阵乘。比如x是形状为[j,k,n,m]的张量,另一个y是[j,k,m,p]的张量,则x.matmul(y)输出的张量形状为[j,k,n,p]。
以下内容标记为本人尚未完全理解的内容,因此所有文字均摘自 神经网络与深度学习:案例与实践 - 第1章:实验基础
飞桨的一些API在计算时支持广播(Broadcasting)机制,允许在一些运算时使用不同形状的张量。通常来讲,如果有一个形状较小和一个形状较大的张量,会希望多次使用较小的张量来对较大的张量执行某些操作,看起来像是形状较小的张量首先被扩展到和较大的张量形状一致,然后再做运算。
飞桨的广播机制主要遵循如下规则(参考Numpy广播机制):
1)每个张量至少为一维张量。
2)从后往前比较张量的形状,当前维度的大小要么相等,要么其中一个等于1,要么其中一个不存在。[4]
x = torch.ones([2, 3, 4])
y = torch.ones([2, 3, 4])
z = x + y
print('广播两个相同形状的张量:', z.shape)
x = torch.ones([2, 3, 1, 5])
y = torch.ones([3, 4, 1])
# 从后往前依次比较:
# 第一次:y的维度大小是1
# 第二次:x的维度大小是1
# 第三次:x和y的维度大小相等,都为3
# 第四次:y的维度不存在
# 所以x和y是可以广播的
z = x + y
print('广播两个不同形状的张量:', z.shape)
代码执行结果:
广播两个相同形状的张量: torch.Size([2, 3, 4])
广播两个不同形状的张量: torch.Size([2, 3, 4, 5])
现在我们知道在什么情况下两个张量是可以广播的。两个张量进行广播后的结果张量的形状计算规则如下:
1)如果两个张量shape的长度不一致,那么需要在较小长度的shape前添加1,直到两个张量的形状长度相等。
2)保证两个张量形状相等之后,每个维度上的结果维度就是当前维度上较大的那个。
以张量x和y进行广播为例,x的shape为[2, 3, 1, 5],张量y的shape为[3, 4, 1]。首先张量y的形状长度较小,因此要将该张量形状补齐为[1, 3, 4, 1],再对两个张量的每一维进行比较。从第一维看,x在一维上的大小为2,y为1,因此,结果张量在第一维的大小为2。以此类推,对每一维进行比较,得到结果张量的形状为[2, 3, 4, 5]。
由于矩阵乘法函数paddle.matmul在深度学习中使用非常多,这里需要特别说明一下它的广播规则:
1)如果两个张量均为一维,则获得点积结果。
2)如果两个张量都是二维的,则获得矩阵与矩阵的乘积。
3)如果张量x是一维,y是二维,则将x的shape转换为[1, D],与y进行矩阵相乘后再删除前置尺寸。
4)如果张量x是二维,y是一维,则获得矩阵与向量的乘积。
5)如果两个张量都是N维张量(N > 2),则根据广播规则广播非矩阵维度(除最后两个维度外其余维度)。比如:如果输入x是形状为[j,1,n,m]的张量,另一个y是[k,m,p]的张量,则输出张量的形状为[j,k,n,p]。
此外,计算张量乘积时会使用到广播机制。[4]
进行实验前,我们需要先导入pandas包。
import pandas as pd
使用read_csv函数实现csv文档的读取。
house_tiny = pd.read_csv('./house_tiny.csv')
boston_house_prices = pd.read_csv('./boston_house_prices.csv')
iris = pd.read_csv('./Iris.csv')
print(house_tiny)
代码执行结果:
NumRooms Alley Price
0 NaN Pave 127500
1 2.0 NaN 106000
2 4.0 NaN 178100
3 NaN NaN 140000
使用get_dummies函数实现Alley的分类表示。
get_dummies函数官方文档:
pandas.get_dummies(data, prefix=None, prefix_sep=’_’, dummy_na=False, columns=None, sparse=False, drop_first=False)[source]
house_tiny = house_tiny.fillna(house_tiny.iloc[:, 0:2].mean())
house_tiny = pd.get_dummies(house_tiny, dummy_na=True).iloc[:, [0, 2, 3, 1]] # 价格移动到最后一列
print(house_tiny)
代码执行结果:
NumRooms Alley_Pave Alley_nan Price
0 3.0 1 0 127500
1 2.0 0 1 106000
2 4.0 0 1 178100
3 3.0 0 1 140000
直接用前文的torch.Tensor转换即可,转换前需要先用values属性获取数据。
a = torch.Tensor(house_tiny.values)
print(a)
代码执行结果:
tensor([[3.0000e+00, 1.0000e+00, 0.0000e+00, 1.2750e+05],
[2.0000e+00, 0.0000e+00, 1.0000e+00, 1.0600e+05],
[4.0000e+00, 0.0000e+00, 1.0000e+00, 1.7810e+05],
[3.0000e+00, 0.0000e+00, 1.0000e+00, 1.4000e+05]])
本次实验主要对张量和算子进行学习和应用,其中广播机制的内容…还需要再学习巩固一下。有关于收获的话…paddle包中的size属性对应在torch中是numel,在torch中size和shape的作用类似。数据预处理部分主要回忆了数据集的读取、处理,其中张量转换部分直接按照矩阵处理即可。
看来我还需要趁着周末缓缓。人真的快要寄了(悲)