文章目录
1. 计算图
一个深度学习模型是由“计算图”构成的。所谓计算图是一个有向无环图(directed acyclic graph)。数据是这个图的节点(node),运算是这个图的边(edge)。如下图所示:
这张计算图的数学表达式为 y = ( x + w ) ∗ ( w + 1 ) y=(x+w)*(w+1) y=(x+w)∗(w+1)。其中, x x x、 w w w 和 b b b 是由用户定义的,称为“叶子节点”(leaf node),可在 PyTorch 中加以验证:
a = torch.tensor([1.])
b = torch.tensor([2.])
c = a.add(b)
a.is_leaf() # True
c.is_leaf() # False
计算图可以分为动态图与静态图两种。
1.1 动态图
动态图的搭建过程与执行过程可以同时进行。PyTorch 默认采用动态图机制。我们看一个例子:
import torch
first_counter = torch.Tensor([0])
second_counter = torch.Tensor([10])
while (first_counter < second_counter): #[0] 加不加没有影响
first_counter += 2
second_counter += 1
print(first_counter)
print(second_counter)
1.2 静态图
静态图先创建计算图,然后执行计算图。计算图一经定义,无法改变。TensorFlow 2.0 以前以静态图为主。我们看同样的例子在 TensorFlow 2.0 以前是怎么搭建的:
import tensorflow as tf
first_counter = tf.constant(0) # 定义变量
second_counter = tf.constant(10) # 定义变量
def cond(first_counter, second_counter, *args): # 定义条件
return first_counter < second_counter
def body(first_counter, second_counter): # 定义条件
first_counter = tf.add(first_counter, 2)
second_counter = tf.add(second_counter, 1)
return first_counter, second_counter
c1, c2 = tf.while_loop(cond, body, [first_counter, second_counter]) # 定义循环
with tf.Session() as sess: # 建立会话执行计算图
counter_1_res, counter_2_res = sess.run([c1, c2])
print(first_counter)
print(second_counter)
因为静态图在设计好以后不能改变,调试的过程中 debug 实在太痛苦了。所以 TensorFlow 2.0 开始默认使用动态图。
1.3 计算图示例
假如我们想计算上面计算图中 y = ( x + w ) ∗ ( w + 1 ) y=(x+w)*(w+1) y=(x+w)∗(w+1) 在 x = 2 x=2 x=2, w = 1 w=1 w=1 时的导数:
- 首先,我们将上式进行分解:
a = x + w a=x+w a=x+w
b = w + 1 b=w+1 b=w+1
于是我们得
y = a ∗ b y=a*b y=a∗b
对上式求导有:
∂ y ∂ w = ∂ y ∂ a ∂ a ∂ w + ∂ y ∂ b ∂ b ∂ w \frac{\partial y}{\partial w}=\frac{\partial y}{\partial a}\frac{\partial a}{\partial w}+\frac{\partial y}{\partial b}\frac{\partial b}{\partial w} ∂w∂y=∂a∂y∂w∂a+∂b∂y∂w∂b
根据 y = a ∗ b y=a*b y=a∗b, a = x + w a=x+w a=x+w 和 b = w + 1 b=w+1 b=w+1 可知:
∂ y ∂ a = b = w + 1 \frac{\partial y}{\partial a}=b=w+1 ∂a∂y=b=w+1
∂ a ∂ w = 1 \frac{\partial a}{\partial w}=1 ∂w∂a=1
∂ y ∂ b = a = x + w \frac{\partial y}{\partial b}=a=x+w ∂b∂y=a=x+w
∂ b ∂ w = 1 \frac{\partial b}{\partial w}=1 ∂w∂b=1
所以
∂ y ∂ w = ( w + 1 ) + ( x + w ) = 2 ∗ 1 + 2 + 1 = 5 \frac{\partial y}{\partial w}=(w+1)+(x+w)=2*1+2+1=5 ∂w∂y=(w+1)+(x+w)=2∗1+2+1=5
在 PyTorch 中求导数非常简单,使用tensor.backward()
即可:
import torch
x = torch.tensor([2.], requires_grad=True) # 开启导数追踪
w = torch.tensor([1.], requires_grad=True) # 开启导数追踪
a = w.add(x)
b = w.add(1)
y = a.mul(b)
y.backward() # 求导
print(w.grad)
2. 张量的运算
2.1 张量的四则运算
torch.add(input, other, *, alpha=1, out=None) # 相加
torch.sub(input, other, out=None) # 相减
torch.mul(input, other, out=None) # 相乘
torch.div(input, other, out=None) # 相除
torch.add()
比较特殊,它遵循如下公式:
o
u
t
=
i
n
p
u
t
+
a
l
p
h
a
×
o
t
h
e
r
out=input+alpha×other
out=input+alpha×other
所以 torch.add(torch.tensor(1), torch.tensor(2), torch.tensor(3))
的运算实际上是
1
+
2
∗
3
=
7
1+2*3=7
1+2∗3=7。
我们还有
torch.addcdiv(input, tensor1, tensor2, *, value=1, out=None)
对应的运算规则为
o
u
t
=
i
n
p
u
t
+
v
a
l
u
e
∗
t
e
n
s
o
r
1
t
e
n
s
o
r
2
out=input+value*\frac{tensor1}{tensor2}
out=input+value∗tensor2tensor1
torch.addcmul(input, tensor1, tensor2, *, value=1, out=None)
对应运算规则为
o
u
t
=
i
n
p
u
t
+
v
a
l
u
e
∗
t
e
n
s
o
r
1
∗
t
e
n
s
o
r
2
out=input+value*tensor1*tensor2
out=input+value∗tensor1∗tensor2
张量的乘法有点复杂,以后我们会另开一篇。
2.2 对数,指数,幂函数运算
torch.exp(input, out=None)
对应运算规则为
o
u
t
=
e
i
n
p
u
t
out=e^{input}
out=einput
torch.pow(input, exponent, out=None)
对应运算规则为
o
u
t
=
x
e
x
p
o
n
e
n
t
out=x^{exponent}
out=xexponent
四个对数函数:
torch.log(input, out=None)
对应运算规则为
o
u
t
=
l
o
g
e
i
n
p
u
t
out=log_e{input}
out=logeinput
torch.log1p(input, out=None)
对应运算规则为
o
u
t
=
l
o
g
e
(
i
n
p
u
t
+
1
)
out=log_e{(input+1)}
out=loge(input+1)
torch.log2(input, out=None)
对应运算规则为
o
u
t
=
l
o
g
2
i
n
p
u
t
out=log_2{input}
out=log2input
torch.log10(input, out=None)
对应运算规则为
o
u
t
=
l
o
g
10
i
n
p
u
t
out=log_{10}{input}
out=log10input
2.3 三角函数
torch.sin(input, out=None):正弦
torch.cos(input, out=None):余弦
torch.tan(input, out=None):正切
2.4 变换函数
torch.abs(input, out=None)
:返回张量的绝对值。torch.ceil(input, out=None)
:对张量向上取整。torch.floor(input, out=None)
:对张量向下取整。torch.floor_divide(input, other, out=None)
:张量相除后向下取整。torch.fmod(input, other, out=None)
:对张量取余。torch.neg(input, out=None)
:取张量的相反数。torch.round(input, out=None)
:对张量取整。torch.sigmoid(input, out=None)
:对张量进行 sigmoid 计算。torch.sqrt(input, out=None)
:对张量取平方根。torch.square(input, out=None)
:对张量平方。torch.sort(input, dim=-1, descending=False, out=None)
:返回张量的排序结果。
2.5 降维函数
torch.argmax(input, dim, keepdim=False)
:返回张量内最大元素的索引。torch.argmin(input, dim, keepdim=False, out=None)
:返回张量内最小元素的索引。torch.mean(input, dim, keepdim=False, out=None)
:返回张量内张量的平均数。torch.median(input, dim=-1, keepdim=False, out=None)
:返回张量内张量的中位数。torch.prod(input, dim, keepdim=False, dtype=None)
:返回张量内元素的乘积。torch.std(input, dim, unbiased=True, keepdim=False, out=None)
:返回张量内的标准差。torch.sum(input, dim, keepdim=False, dtype=None)
:返回张量内元素的和。torch.var(input, dim, keepdim=False, unbiased=True, out=None)
:返回张量内元素的方差。
如果上述函数中的dim
变量没有显式赋值,则对整个张量进行计算,返回一个值;若dim
被显性赋值,则对该dim
内的每组数据分别进行运算。
2.6 比较函数
torch.eq(input, other, out=None)
:比较张量间的元素是否相等。torch.equal(input, other)
:比较两个张量是否相同。torch.ge(input, other, out=None)
:比较第一个张量内的元素是否小于等于第二个张量内的对应元素。torch.gt(input, other, out=None)
:比较第一个张量内的元素是否小于第二个张量内的对应元素。torch.isnan(input, out=None)
:张量内的元素是否为空。torch.le(input, other, out=None)
:比较第一个张量内的元素是否大于等于第二个张量内的对应元素。torch.lt(input, other, out=None)
:比较第一个张量内的元素是否大于第二个张量内的对应元素。torch.max(input, dim, keepdim=False, out=None)
:返回张量指定轴上的最大元素。torch.min(input, dim, keepdim=False, out=None)
:返回张量指定轴上的最小元素。torch.ne(input, other, out=None)
:比较张量间的元素是否不相同。
3. 张量的索引,变换,拼接与拆分
3.1 张量的索引
- 原生索引
对 tensor 可以采用 Python 的原生索引方式:
>>> a = torch.rand((5, 3))
>>> a[0::2]
tensor([[0.6853, 0.1187, 0.0127],
[0.1146, 0.0663, 0.8147],
[0.9145, 0.5034, 0.3146]])
- torch.index_select(input, dim, index, out=None):沿着指定维度对输入进行切片,取index中指定的相应项(index为一个LongTensor),然后返回到一个新的张量, 返回的张量与原始张量_Tensor_有相同的维度(在指定轴上)。
>>> x = torch.randn(3, 4)
>>> x
1.2045 2.4084 0.4001 1.1372
0.5596 1.5677 0.6219 -0.7954
1.3635 -1.2313 -0.5414 -1.8478
[torch.FloatTensor of size 3x4]
>>> indices = torch.LongTensor([0, 2])
>>> torch.index_select(x, 0, indices)
1.2045 2.4084 0.4001 1.1372
1.3635 -1.2313 -0.5414 -1.8478
[torch.FloatTensor of size 2x4]
>>> torch.index_select(x, 1, indices)
1.2045 0.4001
0.5596 0.6219
1.3635 -0.5414
[torch.FloatTensor of size 3x2]
torch.masked_select(input, mask, out=None)
:根据掩码张量mask
中的二元值,取输入张量中的指定项(mask
为一个 ByteTensor),将取值返回到一个新的1D张量,张量mask
须跟input
张量有相同数量的元素数目,但形状或维度不需要相同。
>>> x = torch.randn(3, 4)
>>> x
1.2045 2.4084 0.4001 1.1372
0.5596 1.5677 0.6219 -0.7954
1.3635 -1.2313 -0.5414 -1.8478
[torch.FloatTensor of size 3x4]
>>> indices = torch.LongTensor([0, 2])
>>> torch.index_select(x, 0, indices)
1.2045 2.4084 0.4001 1.1372
1.3635 -1.2313 -0.5414 -1.8478
[torch.FloatTensor of size 2x4]
>>> torch.index_select(x, 1, indices)
1.2045 0.4001
0.5596 0.6219
1.3635 -0.5414
[torch.FloatTensor of size 3x2]
3.2 张量的变换
torch.squeeze(input, dim=None, out=None)
:将输入张量形状中的 1 去除并返回。 若不指定维度,则去除所有形状为 1 的维度。
>>> x = torch.zeros(2,1,2,1,2)
>>> x.size()
torch.Size([2, 1, 2, 1, 2])
>>> y = torch.squeeze(x)
>>> y.size()
torch.Size([2, 2, 2])
>>> y = torch.squeeze(x, 0)
>>> y.size()
torch.Size([2, 1, 2, 1, 2])
>>> y = torch.squeeze(x, 1)
>>> y.size()
torch.Size([2, 2, 1, 2])
torch.unsqueeze(input, dim, out=None)
:返回一个新的张量,对输入的制定位置插入维度 1。
>>> x = torch.zeros(2,1,2,1,2)
>>> x.size()
>>> torch.unsqueeze(x, 0).size()
torch.Size([1, 2, 1, 2, 1, 2])
torch.reshape(input, shape)
:对张量进行变形。
>>> a = torch.arange(4.)
>>> torch.reshape(a, (2, 2))
tensor([[ 0., 1.],
[ 2., 3.]])
>>> b = torch.tensor([[0, 1], [2, 3]])
>>> torch.reshape(b, (-1,))
tensor([ 0, 1, 2, 3])
如果变形的维度中有 -1
则代表该维度的元素个数通过其他维度的元素个数计算。
torch.t(input)
:对张量进行转置。
>>> a = torch.zeros((3, 4))
>>> torch.t(a).size()
torch.Size([4, 3])
torch.transpose(input, dim0, dim1)
:对张量的某两个维度进行转置。
>>> a = torch.zeros((3, 4, 5))
>>> torch.transpose(a, 0, 1).size()
torch.Size([4, 3, 5])
3.3 张量的拼接
torch.cat(inputs, dimension=0)
:张量的连接操作不引入新的轴。
>>> x = torch.randn(2, 3)
>>> x
tensor([[-0.5741, -0.6404, 1.2253],
[ 0.1389, 1.1403, -0.5611]])
>>> torch.cat((x, x, x), 0)
tensor([[-0.5741, -0.6404, 1.2253],
[ 0.1389, 1.1403, -0.5611],
[-0.5741, -0.6404, 1.2253],
[ 0.1389, 1.1403, -0.5611],
[-0.5741, -0.6404, 1.2253],
[ 0.1389, 1.1403, -0.5611]])
>>> torch.cat((x, x, x), 1)
tensor([[-0.5741, -0.6404, 1.2253, -0.5741, -0.6404, 1.2253, -0.5741, -0.6404,
1.2253],
[ 0.1389, 1.1403, -0.5611, 0.1389, 1.1403, -0.5611, 0.1389, 1.1403,
-0.5611]])
torch.stack(sequence, dim=0)
:沿着一个新的维度对张量进行连接。如果新维度不是最后一维,则该维度后面的维度往后顺延一维。
>>> x = torch.randn(2, 3)
>>> x
tensor([[1.1247, 0.2997, 0.5159],
[0.6374, 0.3810, 0.6746]])
>>> torch.stack((x, x), dim=0)
tensor([[[1.1247, 0.2997, 0.5159],
[0.6374, 0.3810, 0.6746]],
[[1.1247, 0.2997, 0.5159],
[0.6374, 0.3810, 0.6746]]])
3.4 张量的拆分
torch.chunk(tensor, chunks, dim=0)
:在给定维度(轴)上将输入张量进行分块儿。最后一块的元素数量可能少于chunks
。
>>> a = torch.rand((5, 3))
>>> torch.chunk(a, 2, dim=0)
(tensor([[0.6853, 0.1187, 0.0127],
[0.4053, 0.8772, 0.2952],
[0.1146, 0.0663, 0.8147]]),
tensor([[0.4756, 0.5134, 0.2130],
[0.9145, 0.5034, 0.3146]]))
因为 5 不能被 2 整除,所以第一个新元素在 0 维上有 3 个元素,第二个新元素在 0 维上有 2 个元素。
torch.split(tensor, split_size, dim=0)
:将输入张量分割成相等形状的chunks(如果可分)。 如果沿指定维的张量形状大小不能被split_size 整分, 则最后一个分块会小于其它分块。
>>> torch.split(a, [1, 1, 3], dim=0)
(tensor([[0.6853, 0.1187, 0.0127]]),
tensor([[0.4053, 0.8772, 0.2952]]),
tensor([[0.1146, 0.0663, 0.8147],
[0.4756, 0.5134, 0.2130],
[0.9145, 0.5034, 0.3146]]))
欢迎关注我的微信公众号“花解语 NLP”: