深度学习基础(一)-张量创建,计算与自动微分

张量创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import torch

x = torch.arrange(12)
x = torch.arrange(12,dtype=torch.float32)

x.shape

x.numel() # 求元素个数

x.reshape(3,4)

torch.ones(a,b)
torch.zeros(a,b)
torch.randn(a,b)
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True) # 正态分布

torch.tensor([])

# numpy 到 tensor的转换
a, b, c, d = [torch.tensor(x, dtype=torch.float32) for x in [a, b, c, d]]

torch.normal(均值,标准差,x.shape)
# 要梯度更新的化torch.normal(均值,标准差,x.shape,require_grad=True)

张量计算

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import torch
import random
# 注意以下操作建立在x是torch张量的基础下
torch.exp(x)

torch.cat((a,b),dim = 0)

x.sum() # 求所有元素之和
'''
默认情况,下,会沿所有轴降低维度,变为一个标量。
可以指定维度,例如x.sum(axis=0),可以按行求和,即把所有行相加成为一行
'''

# 对于二维tensor(可看作列表套列表) x[0]即表示第一行,x[1:3]表示第二到三行
# 对于三维tensor(可看作列表套列表套列表)
# 例如x = torch.tensor([[[0,1,2],
# [3,4,5],
# [6,7,8]],

# [[0,1,2],
# [3,4,5],
# [6,7,8]])
# x[0] = tensor[[0,1,2],
# [3,4,5],
# [6,7,8]]

X = X + Y
# 新的X与原先的X的地址不同,分配了新的内存
X += Y
# 新的X与原先的X的地址相同,未分配新的内存

A = X.numpy()
B = torch.tensor(A) # 把numpy数组转换为tensor张量

len(x)

x.T # 转置

A*B # 按矩阵对应元素相乘,也叫哈达玛积

B=A.clone() # 克隆A

A.mean() # 求平均值,也降维度,成为标量
A.mean(axis=0) # 按行求平均值,返回每列的平均值,返回到一个行向量
A.mean(axis=0,keepdims=True)

torch.dot(x,y) # 向量点积,返回标量
x*y # 对应元素相乘

torch.mv(A,x) # 矩阵乘以向量
torch.matmul(A,x) # 矩阵乘以向量
torch.mm(A,B) # 矩阵相乘

torch.norm(x) # L2范数
torch.norm(A) # 矩阵元素平方和的平方根
torch.abs(x).sum() # L1范数

random.shuffle(list) # 打乱列表

exp = torch.arange(2.0, 6.0)
a = torch.arange(1., 5.)
torch.pow(a,exp) #实现张量和标量之间逐元素求指数操作。a,exp是标量或者张量,一般a是底数,exp是指数

torch.stack()

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
T1 = torch.tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
T2 = torch.tensor([[10, 20, 30],
[40, 50, 60],
[70, 80, 90]])

R0 = torch.stack((T1, T2), dim=0)
print("R0:\n", R0)
print("R0.shape:\n", R0.shape)
"""
R0:
tensor([[[ 1, 2, 3],
[ 4, 5, 6],
[ 7, 8, 9]],

[[10, 20, 30],
[40, 50, 60],
[70, 80, 90]]])
R0.shape:
torch.Size([2, 3, 3])
"""

R1 = torch.stack((T1, T2), dim=1)
print("R1:\n", R1)
print("R1.shape:\n", R1.shape)
"""
R1:
tensor([[[ 1, 2, 3],
[10, 20, 30]],

[[ 4, 5, 6],
[40, 50, 60]],

[[ 7, 8, 9],
[70, 80, 90]]])
R1.shape:
torch.Size([3, 2, 3])

"""

R2 = torch.stack((T1, T2), dim=2)
print("R2:\n", R2)
print("R2.shape:\n", R2.shape)
"""
R2:
tensor([[[ 1, 10],
[ 2, 20],
[ 3, 30]],

[[ 4, 40],
[ 5, 50],
[ 6, 60]],

[[ 7, 70],
[ 8, 80],
[ 9, 90]]])
R2.shape:
torch.Size([3, 3, 2])

"""

R3 = torch.stack((T1, T2), dim=3)
print("R3:\n", R3)
print("R3.shape:\n", R3.shape)
"""
IndexError: Dimension out of range (expected to be in range of [-3, 2], but got 3)
"""

torch.meshgrid()

生成网格,可以用于生成坐标。函数输入两个数据类型相同的一维张量,两个输出张量的行数为第一个输入张量的元素个数,列数为第二个输入张量的元素个数,当两个输入张量数据类型不同或维度不是一维时会报错。

其中第一个输出张量填充第一个输入张量中的元素,各行元素相同;第二个输出张量填充第二个输入张量中的元素各列元素相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import torch
a = torch.tensor([1, 2, 3, 4])
print(a)
b = torch.tensor([4, 5, 6])
print(b)
x, y = torch.meshgrid(a, b)
print(x)
print(y)

结果显示:
tensor([1, 2, 3, 4])
tensor([4, 5, 6])
tensor([[1, 1, 1],
[2, 2, 2],
[3, 3, 3],
[4, 4, 4]])
tensor([[4, 5, 6],
[4, 5, 6],
[4, 5, 6],


自动微分

一个标量对列向量求导的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import torch

x = torch.arange(4.0)
x.requires_grad_(True) # 等价于x=torch.arange(4.0,requires_grad=True)
#创建一个张量x,并设置其 requires_grad参数为True,程序将会追踪所有对于该张量的操作,当完成计算后通过调用 .backward(),自动计算所有的梯度, 这个张量的所有梯度将会自动积累到 .grad 属性

x.grad # 默认值是None
y = 2 * torch.dot(x, x)
y.backward() # 反向传播
x.grad #计算梯度
# 输出tensor([ 0., 4., 8., 12.])

# 在默认情况下,PyTorch会累积梯度,在对新的函数求导之前我们需要清除之前的值
x.grad.zero_()
y = x.sum()
y.backward()
x.grad



非标量求导与分离计算

y.backward(grad_tensors = None, retain_graph = None, create_graph = False, grid_variables = None):

1
2
3
4
5
6
import torch

# 如果你的Y值是个标量,那么直接使用:
y.backward()
# 如果你的Y是个向量或矩阵,那么就不一样:
y.backward(torch.ones_like(x))

pytorch是动态图机制,在训练模型时候,每迭代一次都会构建一个新的计算图.pytorch在利用计算图求导的过程中根节点都是一个标量,即一个数。

当根节点即函数的因变量为一个向量的时候,会构建多个计算图对该向量中的每一个元素分别进行求导。pytoch构建的计算图是动态图,为了节约内存,所以每次一轮迭代完也即是进行了一次backward函数计算之后计算图就被在内存释放,因此如果你需要多次backward只需要在第一次反向传播时候添加一个retain_graph=True标识,让计算图不被立即释放。

实际上文档中retain_graphcreate_graph两个参数作用相同,因为前者是保持计算图不释放,而后者是创建计算图,因此如果我们不想要计算图释放掉,将任意一个参数设置为True都行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import torch

# 计算一批训练样本中每个组成部分的损失函数的导数
# 对非标量调用backward需要传入一个gradient参数,该参数指定微分函数关于self的梯度。
# 本例只想求偏导数的和,所以传递一个1的梯度是合适的
x.grad.zero_()
y = x * x
# 等价于y.backward(torch.ones(len(x)))
y.sum().backward()
x.grad

# 分离计算,z=yx,但是把y当作常数计算z对x的导数
u = y.detach()
z = u * x
z.sum().backward()