2 计算图,张量的操作与线性回归
目录
计算图
计算图是用来描述运算的有向无环图。计算图有两个主要元素:结点(Node)和边(Edge)。结点代表数据,如向量,矩阵,张量;边表示运算,如加减乘除卷积等。
比如用计算图表示(y = (x + w) * (w + 1)):
代码表示为:
x = torch.tensor([1.])
w = torch.tensor([2.])
a = x.add(w)
b = w.add(1.)
y = a.mul(b)
x.is_leaf() # True
w.is_leaf() # True
b.is_leaf() # False
其中叶子结点没有grad_func
,非叶子节点没有grad
(因为都可以用叶子节点的grad
表示,计算完后就会被释放)。当然,也就意味着,我们可以用函数retain_grad()
保存下来非叶子结点的grad
。
根据搭建与执行图的区别,计算图又可以分为动态图和静态图。
对于使用者来说,两种形式的计算图有着非常大的区别,同时静态图和动态图都有他们各自的优点,比如动态图比较方便debug,使用者能够用任何他们喜欢的方式进行debug,同时非常直观,而静态图是通过先定义后运行的方式,之后再次运行的时候就不再需要重新构建计算图,所以速度会比动态图更快。
接下来看一下TensorFlow1.x时代的动态图和PyTorch的静态图定义一个循环语句的差别:
TensorFlow:
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)
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)
可以看到 TensorFlow 需要将整个图构建成静态的,换句话说,每次运行的时候图都是一样的,是不能够改变的,所以不能直接使用 Python 的 while 循环语句,需要使用辅助函数 tf.while_loop
写成 TensorFlow 内部的形式。
而PyTorch 的写法跟 Python 的写法是完全一致的,没有任何额外的学习成本。
张量的操作:拼接、切分、索引和变换
张量的拼接
torch.cat(tensors, dim=0)
:将张量按维度dim进行拼接
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).shape
# torch.Size([6, 3])
torch.cat((x, x, x), 1).shape
# torch.Size([2, 9])
torch.stack(tensors, dim=0)
:沿着一个新的维度对张量进行连接。如果不是新的维度,则插入到该维度(原先维度一次后移)
x = torch.randn(2, 3)
x
# tensor([[-0.5741, -0.6404, 1.2253],
# [ 0.1389, 1.1403, -0.5611]])
# 新维度拼接
torch.stack((x, x, x), 2).shape
# torch.Size([2, 3, 3])
# 插入到第0维度
torch.stack((x, x, x), 0).shape
# torch.Size([3, 2, 3])
张量的拆分
torch.chunk(input, chunks, dim=0)
:在给定维度上对张量进行平均切分,返回一个张量列表。注意,如果不能整除的话,最后一份张量小于其他张量。
x = torch.ones((2, 5))
tensors_list = torch.chunk(x, 1, 2)
for t in tensors_list:
print(t.shape)
# torch.Size([2, 3])
# torch.Size([2, 2])
torch.split(tensor, split_size_or_sections, dim=0)
:将张量按维度进行切分,返回一个张量列表。与torch.chunk
不同的是第二个参数,第二个参数如果为整数,则表示切分后该维度每一维的长度不能整除时最后一维减小); 如果为list,则按list指定的维度进行切分
x = torch.ones((2, 5, 3))
# 切成指定大小
tensor_list1 = torch.split(x, 2, 1)
for i in tensor_list1:
print(i.shape)
# torch.Size([2, 2, 3])
# torch.Size([2, 2, 3])
# torch.Size([2, 1, 3])
# 切成指定维度
tensor_list2 = torch.split(x, [2, 1, 2], 1)
for i in tensor_list2:
print(i.shape)
# torch.Size([2, 2, 3])
# torch.Size([2, 1, 3])
# torch.Size([2, 2, 3])
注意:list的维度之和必须等于该维度长度,否则抛异常
torch.index_select(input, dim, index, out=None)
在维度dim上,按index索引数据,返回一个依index索引拼接起来的张量。
t = torch.randint(0, 9, size = (3, 3))
indices = torch.tensor([0, 2], dtype = torch.long)
t_selected = torch.index_select(t, dim = 1, index = indices)
print(t, t_selected, sep = "
")
# tensor([[5, 8, 0],
# [5, 0, 0],
# [8, 7, 6]])
# tensor([[5, 0],
# [5, 0],
# [8, 6]])
注意:indices
必须为torch.long
torch.masked_select(input, mask, out=None)
按mask中的True进行索引,返回一个一维张量
t = torch.randint(0, 9, size = (3, 3))
mask = t.ge(5) # ge greater than or equal, le, lt, gt
t_selected = torch.masked_select(t, mask)
print(t, t_selected, sep = "
")
# tensor([[4, 5, 0],
# [5, 7, 1],
# [2, 5, 8]])
# tensor([5, 5, 7, 5, 8])
学过MIPS系统的应该很懂ge
, le
, lt
, gt
这些缩写的意思,不多做说明。
此外,还可以通过Python切片的方式对tensor进行索引。
x = torch.randn(2, 5)
print(x, x[:][0::2], sep = "
")
# tensor([[ 0.6614, 0.2669, 0.0617, 0.6213, -0.4519],
# [-0.1661, -1.5228, 0.3817, -1.0276, -0.5631]])
# tensor([[ 0.6614, 0.2669, 0.0617, 0.6213, -0.4519]])
张量的变换
torch.reshape(input, shape)
:变换张量的形状,返回一个新张量。注意:如果张量在内存中是连续的,新张量与input共享数据内存
x = torch.randperm(8)
x_reshape = torch.reshape(x, (2, 4))
print(x, x.shape, x_reshape.shape, sep = "
")
# tensor([5, 4, 2, 6, 7, 3, 1, 0])
# torch.Size([8])
# torch.Size([2, 4])
x[7] = 1
print(x, x_reshape, sep = "
")
# tensor([5, 4, 2, 6, 7, 3, 1, 1])
# tensor([[5, 4, 2, 6],
# [7, 3, 1, 1]])
注意:reshape大小计算后必须与原张量相同,或者写作-1,代表缺省,可通过其他维度计算出来。
torch.transpose(input, dim0, dim1)
:对张量的某两个维度进行转置。
x = torch.zeros((3, 4, 5))
torch.transpose(x, 0, 1).size() # 图像预处理 c*h*w => h*w*c
# torch.Size([4, 3, 5])
torch.t(input)
:对二维张量进行转置。等效于torch.transpose(input, 0, 1)
a = torch.zeros((3, 4))
torch.t(a).size()
# torch.Size([4, 3])
torch.squeeze(input, dim=None, out=None)
压缩长度为一的维度,其中dim为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)
在指定位置依据生成方式插入坐标轴
x = torch.zeros(2,1,2,1,2)
x.size()
torch.unsqueeze(x, 0).size()
# torch.Size([1, 2, 1, 2, 1, 2])
张量的数学运算
四则运算
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()
运算时遵循:
其余的向量化运算。
还有:
torch.addcdiv(input, tensor1, tensor2, *, value=1, out=None)
torch.addcdiv(input, tensor1, tensor2, *, value=1, out=None)
前者遵循:
后者同理。
对数,指数, 幂函数运算
torch.exp(input, out=None) # e^input
torch.pow(input, exponent, out=None) #幂函数
# 对数
torch.log(input, out=None)
torch.log1p(input, out=None)
torch.log2(input, out=None)
torch.log10(input, out=None)
显然,这些运算都遵循向量式运算。
几个对数函数的运算规则分别是:
三角函数
torch.sin(input, out=None):正弦
torch.cos(input, out=None):余弦
torch.tan(input, out=None):正切
torhc.asin(input, out=None)
torch.cosh(input, out=None)
torch.atan2(input, out=None)
用法就是普通三角函数的向量运算。
其他
其他数学运算
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)
:返回张量排序后的结果
降维函数
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
内的数据进行计算。
比较函数
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)
:比较张量间的元素是否不相同。
线性回归
接下来我们用之前学到的东西来做一个线性回归。即用线性关系去拟合一些离散的点。
求解步骤
-
确定模型 $ y = w*x + b$,目标即求解w和b
-
选则均方差损失函数
[loss = frac{1}{m}sum_{i=1}^{m}(y_{i} - hat{y_{i}})^2 ] -
求解梯度并更新w, b
[egin{align} w &= w - LR * w.grad \ b &= b - LR * b.grad end{align} ]
代码
import torch
import matplotlib.pyplot as plt
torch.manual_seed(10)
lr = 0.05 # 学习率
# 创建训练数据
x = torch.rand(20, 1) * 10 # x data (tensor), shape=(20, 1)
y = 2*x + (5 + torch.randn(20, 1)) # y data (tensor), shape=(20, 1)
# 构建线性回归参数
w = torch.randn((1, ), requires_grad=True)
b = torch.zeros((1, ), requires_grad=True)
for iteration in range(1600):
# 前向传播
wx = torch.mul(w, x)
y_pred = torch.add(wx, b)
# 计算 MSE loss
loss = (0.5 * (y - y_pred) ** 2).mean()
# 反向传播
loss.backward()
# 更新参数
b.data.sub_(lr * b.grad)
w.data.sub_(lr * w.grad)
# 清零张量的梯度
w.grad.zero_()
b.grad.zero_()
# 绘图
if iteration % 40 == 0:
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), y_pred.data.numpy(), 'r-', lw=5)
plt.text(2, 20, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})
plt.xlim(1.5, 10)
plt.ylim(8, 28)
plt.title("Iteration: {}
w: {} b: {}".format(iteration, w.data.numpy(), b.data.numpy()))
plt.pause(0.5)
if loss.data.numpy() < 1e-3:
break
结果
参考
CSDN博客:[DL]PyTorch 学习总结(2)
Github:PyTorch_Tutorial