三大要素支撑起了近十年 Deep Leanring 的发展浪潮: DL algorithm, Big data, Computation power. 从本节起,我们学习一下 PyTorch 的使用。
创建Tensor
首先,什么是Tensor?Tensor是一个任意维度的矩阵 (从标量开始). 而编写神经网络 (NN) 的过程其实就是一个构建一个Tensor的计算图的过程。其中,所有参与计算的变量都要以Tensor的形式存在。 创建一个Tensor也很简单:
- 直接创建 torch.Tensor
import torch
# torch.Tensor(data, dtype=None, device= None, requires_grad = False)
t1 = torch.Tensor([[1,2],[3,4]])
device – 可以看到,这样初始化的tensor是没有指定device的。device取值可以是 ‘cpu’ 或 ‘cuda’ 其中 cuda还可以有不同的设备index 比如 ‘cuda:0’ ‘cuda:1’ 等等. 如果没有设备device那它就放在 torch.cuda.current_device() 中一般是第0个device。而初始化没有指定device那就放在cpu中,我们也可以把它放进cuda中
device1 = torch.device('cuda:0')
t2 = t1.to(device1)
print(t1.device) >>> device(type='cpu')
print(t2.device) >>> device(type='cuda:0')
可以看到, t1 仍然在cpu中,而新生成的 t2 在cuda中。
grad – 一个Tensor由两部分组成,data 和 grad。分别用来存放数据和数据的gradient。初始的Tensor requires_grad = False,因此我们需要把它的 requires_grad = True 才会在back propagation时为它计算gradient。
特别的,t1.data 和 t1.grad 也是一个 Tensor. 因此,如果我们直接取值print的结果都是tensor,而一种拿到标量值的方式就是item() (注意,只能对标量tensor使用):
t1.requires_grad = True
print(t1) >>> tensor
print(t1.data) >>> tensor
print(t1[0]) >>> tensor
print(t1[0][0].item()) >>> scalar = 1
- 从 numpy 转换到 Tensor
data1 = np.array([1,2,3,4])
t2 = torch.from_numpy(data1)
t3 = torch.from_numpy(data1).to(device1)
注意,cpu中的tensor t2 和 data1 是共享内存的,如果修改了其中一个另一个相应也会改变;而 t3 显然和他俩都不共享内存毕竟设备都不同。
- 直接创建特定形式的 Tensor
我们还可以用torch直接产生特定形式的Tensor,比如
torch.zeros(*sizes, out=None, ..) #返回大小为sizes的零矩阵
torch.zeros_like(input, ..) # 返回与input相同size的零矩阵
torch.eye(n, m=None, out=None,…) #返回2-D 的单位对角矩阵
torch.empty_like(input, …) # 返回与input相同size,并被未初始化的数值填充的tensor
torch.normal(mean, std, out=None)
torch.rand(*size, out=None, dtype=None, …) #返回[0,1]之间均匀分布的随机数值
torch.rand_like(input, dtype=None, …) #返回与input相同size的tensor, 填充均匀分布的随机数值
torch.randperm(n, out=None, dtype=torch.int64) # 返回0到n-1的数列的随机排列
可以参考 https://zhuanlan.zhihu.com/p/36233589 中的总结。
一个例子
下面我们来看一个例子,强化一下刚刚的内容(注意其中的注释)
import torch
w = torch.Tensor([0.1]) # 初始化tensor
w.requires_grad = True # 需要设定grad因为等下需要
def forward(x):
return w * x # simple linear model
# 注意,由于 w 是Tensor,input x 标量会被强制转换为Tensor且 * 也变成Tensor乘
def loss(x,y):
predicted = forward(x)
return (predicted-y) ** 2 # MSE loss
x_data = [1.0, 2.0, 3.0]
y_data = [2.0, 4.0, 6.0]
for epoch in range(20):
for x, y in zip(x_data, y_data):
# 这一步对Tensor的操作实际上就是在构建一个计算图
l = loss(x, y) # 给定一组数据先算loss
l.backward() # 这一步就是在计算所有gradients 并存在w里
# 梯度计算完成了计算图就被释放了,下次调用loss就会再重新构建一个计算图
# 取Tensor的data计算不会建立计算图,虽然w.data也是Tensor
w.data = w.data - 0.01 * w.grad.data # 纯数值修改
# 由backward()计算的梯度会持续累积,所以每次必须清零
w.grad.data.zero_()
print("process:", epoch, l.item())
print(forward(3).item())
另一个例子
import torch
# 神经网络
class LineaModel(torch.nn.Module):
def __init__(self):
super(LineaModel, self).__init__()
self.linear = torch.nn.Linear(1, 1)
def forward(self, x):
predicted = self.linear(x)
return predicted
model = LineaModel()
criterion = torch.nn.MSELoss(size_average = loss)
optimizer = torch.optim.SGD(model.parameters(), lr = 0.01)
x_data = torch.Tensor([[1.],[2.],[3.]])
y_data = torch.Tensor([[2.],[4.],[6.]])
for epoch in range(10000):
y_predicted = model(x_data)
loss = criterion(y_predicted, y_data)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print(model(torch.Tensor([0.4])))