np&torch:sum运算符,高级索引,.detach()和.detach_()
python-numpy and torch
1. sum运算符
1.1 sum简介
按指定维求和, keepdim/keepdims(numpy中array数组只能用keepdims):保持维数不变
eg:形状为(a,b,c)的张量:
通过sum(0),指定第0维求和,shape变为(b,c)
通过sum(0, keepdim= True),shape变为(1,b,c)
通过sum(1),指定第0维求和,shape变为(a,c)
通过sum(1, keepdim= True),shape变为(a,1,c)
…
X = torch.arange(12)
X = X.reshape(2, 3, 2)
print(X)
# tensor([[[ 0, 1],
# [ 2, 3],
# [ 4, 5]],
#
# [[ 6, 7],
# [ 8, 9],
# [10, 11]]])
print(X.sum(0).shape, '\n', X.sum(0))
print(X.sum(0, keepdims=True).shape, '\n', X.sum(0, keepdims=True))
# torch.Size([3, 2])
# tensor([[ 6, 8], [[ 0, 1] [[ 6, 7]
# [10, 12], = [ 2, 3] + [ 8, 9]
# [14, 16]]) [ 4, 5]] [10, 11]]
# torch.Size([1, 3, 2])
# tensor([[[ 6, 8],
# [10, 12],
# [14, 16]]])
1.2 简单实现softmax(sum运算符举例)
def Softmax(X) # shape为(batch_size, num_class)
X_e = torch.exp(X)
Sum = X_e.sum(1, keepdim=True) # 保持维度是为了后面做除法,shape为(batch_size, 1)
return X_e / Sum # 这里使用了广播机制(维数相同,shape不同:可以自动补齐,使得shape一样,然后做运算)
2.高级索引
2.1切片索引 :
‘ ,’ : 分开维度,从第0维开始,一刀一刀往下切
eg:
y = np.arange(24).reshape(2, 3, 4)
print(y)
# [[[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
#
# [[12 13 14 15]
# [16 17 18 19]
# [20 21 22 23]]]
(以下“第n个”就是index=n:)
y[0] :(0)第0维的第0个元素,shape为(3,4) : [[ 0 1 2 3],[ 4 5 6 7],[ 8 9 10 11]]
y[1, 0] : 第0维的第1个元素,第1维的第0个元素,shape为(4,): [12 13 14 15]
y[0,1,3]:第0维的第1个元素,第1维的第1个元素,第2维的第3个元素,shape为():7
y[:,1:3,0:4:2]:第0维的所有元素,第1维的第1个到第3个元素(不包括第3个),第2维的第0个到第4个元 素(不包括第4个,步长为2),shape为(2,2,2):[ [ [ 4 6],[ 8 10] ],[ [16 18],[20 22] ] ]
小结:索引从第0维开始,以 “ ,” 为分开维度的符号
2.2索引数组(整型):与切片不要混淆!!!
[注]:
切片是从第0维开始,依次执行下去,“一刀一刀切下去”
索引数组是每个维度同时选取(一一对应),进行索引
NumPy数组可以使用其他数组(或任何其他可以转换为数组的类似序列的对象,如列表,除元组之外的索引)
!!!每个维度数组对应的位置(可以用广播机制)做组合!!!:
y[np.array([0,1])] = y[ [0,1] ] : 约等于 [ y[0],y[1] ] ,结果为 y
y[ [0,1],2,[0,3] ]:用到了广播机制,约等于 [ y [0,2,0],y [ 1,2,3 ] ] ,结果为 [ 8,23 ]
y[ [0,1],[0,2 ] ]:约等于 [ y[0,0],y[1,2 ] ],结果为 [ [ 0 1 2 3],[20 21 22 23 ] ]
应用:
(1):依次找出矩阵的对角线值,对于3维数据,我们可以认为第一维是矩阵个数
y = np.arange(27).reshape(3, 3, 3)
print(y)
# [[[ 0 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]]]
print(y[range(len(y)), 1, 1]) # == y[[0, 1, 2], 1, 1]
# [ 4 13 22]
print(y[[range(len(y))], 1, 1]) # 或者 print(y[[range(y.shape[0])], 1, 1])
# [[ 4 13 22]]
(2):对于问题(1),依次取出第一个矩阵的第一行,第二个矩阵的第二行,第三个矩阵的第三行,并构成一个新的矩阵
new = y[range(len(y)), range(len(y))]
print(new)
# [[ 0 1 2]
# [12 13 14]
# [24 25 26]]
小结:索引数组可以和切片混合使用
2.3交叉熵损失的简单实现
y_true:[0,2,1],一共3个样本,第一个样本的类别为0,第二个样本的类别为2,第二个样本的类别为1
y_pre:[ [ 0.1, 0.2, 0.7],[0.35, 0.45, 0.2],[0.7, 0.2, 0.1 ] ],3个样本,第一个样本的预测概率为[ 0.1, 0.2, 0.7],类别为0的概率为0.1,类别为1的概率为0.2,类别为2的概率为0.7,
从y_pre的每个样本中取出正确类别的概率值!!!
print(y_pre[range(len(y_pre)), y_true])
# [0.1 0.2 0.2]
def Cross_Entropy(y_pre, y_true):
y_ = y_pre[range(len(y_pre)), y_true]
cross_entropy = -torch.log(y_)
return cross_entropy
3. .detach()和.detach_()
Variable 中的两个方法:主要是为了从计算图中分离出来,即不参与梯度计算(backward)。在有些想要某些参数不变(不参与网络训练),我们就需要用到这两个方法。
eg1: .detach( )
经过detach()的变量, requires_grad会变成 false,即不追踪梯度,也就变成了常量。z = y * (w.detach()),可以认为只是将 w 变量中的tensor取出,作为常量参与运算,w本身是变量并没有改变。对于没有经过backward()反向传播的变量,grad=None,即w.grad=None。
def func():
x = torch.ones(size=(2, 2), requires_grad=True)
w = torch.ones(size=(2, 2), requires_grad=True)
y = x ** 2
print(w)
print(w.detach())
z = y * (w.detach())
k = (z ** 2).sum()
k.backward()
print(w.grad)
print(x.grad)
print(w)
# tensor([[1., 1.],
# [1., 1.]], requires_grad=True)
# tensor([[1., 1.],
# [1., 1.]])
# None
# tensor([[4., 4.],
# [4., 4.]])
# tensor([[1., 1.],
# [1., 1.]], requires_grad=True)
eg2: .detach_( )
注意, .detach_() 将会改变w本身,w的requires_grad变为了False。
def func():
x = torch.ones(size=(2, 2), requires_grad=True)
w = torch.ones(size=(2, 2), requires_grad=True)
y = x ** 2
print(w)
print(w.detach_())
print(w)
z = y * w
k = (z ** 2).sum()
k.backward()
print(w.grad)
print(x.grad)
# tensor([[1., 1.],
# [1., 1.]], requires_grad=True)
# tensor([[1., 1.],
# [1., 1.]])
# tensor([[1., 1.],
# [1., 1.]])
# None
# tensor([[4., 4.],
# [4., 4.]])
在神经网络中,我们可以利用这两个方法对变量进行固定,即固定某些网络。
eg3: net中参数被固定,不参与训练(利用 .named_parameters() 可以利用名字灵活的固定指定网络 / 操作)
def eg3():
net = nn.Sequential(nn.Conv2d(3, 1, 1, 1, 1), nn.ReLU())
Net = nn.Sequential(nn.Conv2d(3, 1, 3, 1, 1), nn.ReLU(), nn.MaxPool2d(3), net, nn.MaxPool2d(3))
# 固定 net
for i in net.parameters():
print(i)
i.detach_()
print(i)
# Parameter containing:
# tensor([[[[-0.5519]],
#
# [[ 0.5120]],
#
# [[ 0.3357]]]], requires_grad=True)
# Parameter containing:
# tensor([[[[-0.5519]],
#
# [[ 0.5120]],
#
# [[ 0.3357]]]])
# Parameter containing:
# tensor([-0.5076], requires_grad=True)
# Parameter containing:
# tensor([-0.5076])
注:
.detach(): 从变量分离出的 tensor 变化,原变量中的 tensor 也会跟着变化,说明都是同一块数据,只是将变量从计算图中分离出来, detach_()同理。
vector = torch.tensor([1., 2., 3.], requires_grad=True)
a = vector.detach()
print(a)
print(vector)
a += 1 # 注: a = a + 1 和 a += 1 的区别!(不懂评论区问,我再答!)
print(a)
print(vector)
# tensor([1., 2., 3.])
# tensor([1., 2., 3.], requires_grad=True)
# tensor([2., 3., 4.])
# tensor([2., 3., 4.], requires_grad=True)