✅作者简介:人工智能专业本科在读,喜欢计算机与编程,写博客记录自己的学习历程。
个人主页:小嗷犬的个人主页
个人网站:小嗷犬的技术小站
个人信条:为天地立心,为生民立命,为往圣继绝学,为万世开太平。
Python优先的深度学习框架,支持GPU加速和动态神经网络。
Tensor 相当于多维的矩阵。
Tensor 的数据类型有:(32位浮点型)torch.FloatTensor
,(64位浮点型)torch.DoubleTensor
,(16位整型)torch.ShortTensor
,(32位整型)torch.IntTensor
,(64位整型)torch.LongTensor
。
导入 PyTorch
import torch
创建一个没有初始化的 5×3 矩阵
x = torch.empty(5, 3)
print(x)
创建一个随机初始化的矩阵
#均匀分布[0,1],rand
x = torch.rand(5, 3)
print(x)
#正态分布,randn
x = torch.randn(5, 3)
print(x)
创建一个全0,全1,或者对角线为1的矩阵,且数据类型为 long
#全0
x = torch.zeros(5, 3, dtype=torch.long)
print(x)
#全1
x = torch.ones(5, 3, dtype=torch.long)
print(x)
#对角线为1
x = torch.eye(5, 3, dtype=torch.long)
print(x)
从数据中直接创建 Tensor
#从列表中创建
x = torch.tensor([5.5, 3])
print(x)
#从numpy数组中创建
import numpy as np
x = torch.from_numpy(np.ones(5))
print(x)
创建一个和已有 Tensor 一样大小的 Tensor
x = torch.randn_like(x)
print(x)
获取 Tensor 的形状
print(x.size())
torch.Size
本质上是一个元组,所以支持元组的各种操作。
转化为 numpy 数组
x = x.numpy()
print(x)
绝对值
x = torch.abs(x)
print(x)
加法
y = torch.rand(5, 3)
# 形式一
print(x + y)
# 形式二
print(torch.add(x, y))
# 形式三
result = torch.empty(5, 3)
torch.add(x, y, out=result)
print(result)
# 形式四
y.add_(x)
print(y)
add_
是原地操作,即改变了y
的值。所有的inplace
操作都有一个_
后缀,表示原地操作。
其他运算:sub
减法,mul
乘法,div
除法,pow
幂运算,mm
矩阵乘法,mv
矩阵向量乘法。
限定范围
x = torch.clamp(x, min=0, max=1)
print(x)
改变形状
x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8) # -1 表示自适应
print(x.size(), y.size(), z.size())
获取单元素 Tensor 中的值
x = torch.randn(1)
print(x.item())
使用GPU
# 判断是否有GPU
print(torch.cuda.is_available())
# 创建一个 Tensor 并放到 GPU 上
x = torch.randn(5, 3)
x = x.cuda()
print(x)
创建一个张量并设置 requires_grad = True 用来追踪其计算历史
x = torch.ones(2, 2, requires_grad=True)
print(x)
对这个张量做一次运算
y = x + 2
print(y)
# y 是计算结果,所以它有 grad_fn 属性
print(y.grad_fn)
# 对 y 做更多操作
z = y * y * 3
out = z.mean()
print(z, out)
.requires_grad_()
会改变现有张量的 requires_grad
标志。如果没有指定的话,默认输入的这个标志是 False
。
a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
x = torch.ones(2,2,requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean()
# 现在开始反向传播,因为 out 是一个标量,则 out.backward() 和 out.backward(torch.tensor(1.)) 等价。
out.backward()
# 打印梯度 d(out)/dx
print(x.grad)
即
o u t = 1 4 ∑ i z i out=\frac{1}{4}\sum_iz_i out=41i∑zi
z i = 3 ( x i + 2 ) 2 z_i=3(x_i+2)^2 zi=3(xi+2)2
因为 z i ∣ x i = 1 = 27 z_i\bigr\rvert_{x_i=1}=27 zi xi=1=27,所以
∂ o u t ∂ x i = 3 2 ( x i + 2 ) \frac{\partial_{out}}{\partial_{x_i}}=\frac{3}{2}(x_i+2) ∂xi∂out=23(xi+2)
所以
∂ o u t ∂ x i ∣ x i = 1 = 9 2 = 4.5 \frac{\partial_{out}}{\partial_{x_i}}\bigr\rvert_{x_i=1}=\frac{9}{2}=4.5 ∂xi∂out xi=1=29=4.5
雅可比矩阵
数学上,若有向量值函数 y = f(x)
,那么 y
相对于 x
的梯度是一个雅可比矩阵。
J = [ ∂ y 1 ∂ x 1 ⋯ ∂ y 1 ∂ x n ⋮ ⋱ ⋮ ∂ y m ∂ x 1 ⋯ ∂ y m ∂ x n ] J=\begin{bmatrix}\frac{\partial y_1}{\partial x_1} & \cdots & \frac{\partial y_1}{\partial x_n}\\\vdots & \ddots & \vdots\\\frac{\partial y_m}{\partial x_1} & \cdots & \frac{\partial y_m}{\partial x_n}\end{bmatrix} J= ∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym
通常来说,torch.autograd
是计算雅可比向量积的一个引擎。也就是说,给定任意向量 v
,计算乘积 J ⋅ v J·v J⋅v。如果 v
恰好是标量函数 l = g(y)
的梯度,也即 v = ( ∂ l ∂ y 1 , ⋯ , ∂ l ∂ y m ) T v=(\frac{\partial l}{\partial y_1}, \cdots ,\frac{\partial l}{\partial y_m})^T v=(∂y1∂l,⋯,∂ym∂l)T,那么根据链式法则,雅可比向量积的计算刚好就是 l
对 x
的导数:
J ⋅ v = [ ∂ y 1 ∂ x 1 ⋯ ∂ y 1 ∂ x n ⋮ ⋱ ⋮ ∂ y m ∂ x 1 ⋯ ∂ y m ∂ x n ] [ ∂ l ∂ y 1 ⋮ ∂ l ∂ y m ] = [ ∂ l ∂ x 1 ⋮ ∂ l ∂ x n ] J·v=\begin{bmatrix}\frac{\partial y_1}{\partial x_1} & \cdots & \frac{\partial y_1}{\partial x_n}\\\vdots & \ddots & \vdots\\\frac{\partial y_m}{\partial x_1} & \cdots & \frac{\partial y_m}{\partial x_n}\end{bmatrix}\begin{bmatrix}\frac{\partial l}{\partial y_1}\\\vdots\\\frac{\partial l}{\partial y_m}\end{bmatrix}=\begin{bmatrix}\frac{\partial l}{\partial x_1}\\\vdots\\\frac{\partial l}{\partial x_n}\end{bmatrix} J⋅v= ∂x1∂y1⋮∂x1∂ym⋯⋱⋯∂xn∂y1⋮∂xn∂ym ∂y1∂l⋮∂ym∂l = ∂x1∂l⋮∂xn∂l
雅可比向量积的这一特性使得将外部梯度输入到具有非标量输出的模型中变得非常方便。
x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
y = y * 2
print(y)
下面这种情况,y
不再是标量。torch.autograd
不能直接计算完整的雅可比矩阵,但是如果我们只想要雅可比向量积,只需将这个向量作为参数传给 backward
。
v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(v)
print(x.grad)
也可以通过将代码块包装在 with torch.no_grad():
中,来阻止 autograd
跟踪设置了 .requires_grad=True
的张量的历史记录。
print(x.requires_grad)
print((x ** 2).requires_grad)
with torch.no_grad():
print((x ** 2).requires_grad)
Variable
和 Tensor
的区别是 Variable
会被放入计算图中,然后进行前向传播,反向传播,自动求导。
Variable
是在 torch.autograd.Variable
中的,使用Variable
需要导入 torch.autograd.Variable
。
from torch.autograd import Variable
x = Variable(torch.Tensor([1]),requires_grad=True)
w = Variable(torch.Tensor([2]),requires_grad=True)
b = Variable(torch.Tensor([3]),requires_grad=True)
y = w * x + b
y.backward()
print(x.grad)
print(w.grad)
print(b.grad)
搭建一个简单的神经网络
import torch
batch_n = 100 # 一个批次中输入数据的数量
hidden_layer = 100 # 经过隐藏层后保留的数据特征的个数
input_data = 1000 # 每个数据包含的数据量
output_data = 10 # 每个输出的数据包含的数据量
x = torch.randn(batch_n, input_data) # 100*1000
y = torch.randn(batch_n, output_data) # 100*10
w1 = torch.randn(input_data, hidden_layer) # 1000*100
w2 = torch.randn(hidden_layer, output_data) # 100*10
epoch_n = 20 # 训练的次数
learning_rate = 1e-6 # 学习率
for epoch in range(epoch_n):
h1 = x.mm(w1) # 100*100
h1 = h1.clamp(min=0) # if x < 0 ,x=0
y_pred = h1.mm(w2) # 100*10,前向传播预测结果
loss = (y_pred - y).pow(2).sum() # 损失函数,即均方误差
print("Epoch:{}, Loss:{:.4f}".format(epoch, loss))
grad_y_pred = 2*(y_pred-y) # dloss/dy
grad_w2 = h1.t().mm(grad_y_pred) # dloss/dy * dy/dw2
grad_h = grad_y_pred.clone() # 复制
grad_h = grad_h.mm(w2.t()) # dloss/dy * dy/dh1
grad_h.clamp_(min=0) # if x < 0, x = 0
grad_w1 = x.t().mm(grad_h)
w1 -= learning_rate*grad_w1 # 梯度下降
w2 -= learning_rate*grad_w2
使用 Variable
搭建一个自动计算梯度的神经网络
import torch
from torch.autograd import Variable
batch_n = 100 # 一个批次中输入数据的数量
hidden_layer = 100 # 经过隐藏层后保留的数据特征的个数
input_data = 1000 # 每个数据包含的数据量
output_data = 10 # 每个输出的数据包含的数据量
x = Variable(torch.randn(batch_n, input_data),
requires_grad=False) # requires_grad = False不保留梯度
y = Variable(torch.randn(batch_n, output_data), requires_grad=False)
w1 = Variable(torch.randn(input_data, hidden_layer),
requires_grad=True) # requires_grad = True自动保留梯度
w2 = Variable(torch.randn(hidden_layer, output_data), requires_grad=True)
epoch_n = 20
learning_rate = 1e-6
for epoch in range(epoch_n):
y_pred = x.mm(w1).clamp(min=0).mm(w2) # y_pred = w2 * (w1 * x)
loss = (y_pred-y).pow(2).sum() # 损失函数
print("Epoch:{},Loss:{:.4f}".format(epoch, loss))
loss.backward() # 后向传播计算
w1.data -= learning_rate*w1.grad.data
w2.data -= learning_rate*w2.grad.data
w1.grad.data.zero_() # 置0
w2.grad.data.zero_()
使用 nn.Module
自定义传播函数来搭建神经网络
import torch
from torch.autograd import Variable
batch_n = 100
hidden_layer = 100
input_data = 1000
output_data = 10
class Model(torch.nn.Module):
def __init__(self):
super(Model, self).__init__()
def forward(self, input_n, w1, w2):
x = torch.mm(input_n, w1)
x = torch.clamp(x, min=0)
x = torch.mm(x, w2)
return x
def backward(self):
pass
model = Model()
x = Variable(torch.randn(batch_n, input_data),
requires_grad=False) # requires_grad = False不保留梯度
y = Variable(torch.randn(batch_n, output_data), requires_grad=False)
w1 = Variable(torch.randn(input_data, hidden_layer),
requires_grad=True) # requires_grad = True自动保留梯度
w2 = Variable(torch.randn(hidden_layer, output_data), requires_grad=True)
epoch_n = 20
learning_rate = 1e-6
for epoch in range(epoch_n):
y_pred = model(x, w1, w2)
loss = (y_pred-y).pow(2).sum()
print("Epoch:{},Loss:{:.4f}".format(epoch, loss))
loss.backward() # 后向传播计算
w1.data -= learning_rate*w1.grad.data
w2.data -= learning_rate*w2.grad.data
w1.grad.data.zero_() # 置0
w2.grad.data.zero_()
torch.utils.data.Dataset
是代表这一数据的抽象类,可以自己定义数据类继承和重写这个抽象类,只需要定义__len__
和__getitem__
函数即可。
from torch.utils.data import Dataset
class myDataset(Dataset):
def __init__(self, csv_file, txt_file, root_dir, other_file):
self.csv_data = pd.read_csv(csv_file)
with open(txt_file, 'r') as f:
data_list = f.readlines()
self.txt_data = data_list
self.root_dir = root_dir
def __len__(self):
return len(self.csv_data)
def __getitem__(self, idx):
data = (self.csv_data[idx], self.txt_data[idx])
return data
通过上面的方式,可以定义需要的数据类,可以通过迭代的方法取得每一个数据,但是这样很难实现取 batch
,shuffle
或者多线程去读取数据,所以 Pytorch
中提供了 torch.utils.data.DataLoader
来定义一个新迭代器。
from torch.utils.data import DataLoader
dataiter = DataLoader(myDataset, batch_size=32)
所有的层结构和损失函数来自 torch.nn
。
from torch import nn
class net_name(nn.Module):
def __init__(self, other_arguments):
super(net_name, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size)
def forward(self, x):
x = self.conv1(x)
return x
一个神经网络的典型训练过程如下:
loss
(输出和正确答案的距离)weight = weight - learning_rate * gradient
使用 torch.nn
内的序列容器 Sequential
import torch
batch_n = 100
hidden_layer = 100
input_data = 1000
output_data = 10
model = torch.nn.Sequential(
torch.nn.Linear(input_data, hidden_layer),
torch.nn.ReLU(),
torch.nn.Linear(hidden_layer, output_data)
)
print(model)
使用 nn.Module
定义一个神经网络
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 输入图像channel:1, 输出channel:6, 5x5卷积核
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# an affine operation: y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# 2x2 Max pooling
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# 如果是方阵,则可以只使用一个数字进行定义
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(-1, self.num_flat_features(x))
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flat_features(self, x):
size = x.size()[1:] # 除去批处理维度的其他所有维度
num_features = 1
for s in size:
num_features *= s
return num_features
net = Net()
print(net)
优化算法分为两大类:
(1)一阶优化算法
使用各个参数的梯度值来更新参数,最常用的是梯度下降。梯度下降的功能是通过寻找最小值,控制方差,更新模型参数,最终使模型收敛,网络的参数更新公式:
w = w − η ∂ L ∂ w w = w - \eta \frac{\partial L}{\partial w} w=w−η∂w∂L
其中, η \eta η是学习率, ∂ L ∂ w \frac{\partial L}{\partial w} ∂w∂L是损失函数关于参数 w w w的梯度。
(2)二阶优化算法
二阶优化算法使用了二阶导数(Hessian方法)来最小化或最大化损失函数,主要是基于牛顿法:
w = w − η H − 1 ∂ L ∂ w w = w - \eta H^{-1} \frac{\partial L}{\partial w} w=w−ηH−1∂w∂L
其中, H H H是损失函数关于参数 w w w的Hessian矩阵。
# 优化器(SGD)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
#保存完整模型
torch.save(model,path)
#保存模型的状态
torch.save(model.state_dict(),path)
#加载完整模型
model = torch.load(path)
#加载模型的状态,需要先定义模型结构
model.load_state_dict(torch.load(path))