文章目录
基础
初始化
a = torch.FloatTensor(2,3) #随机生成size为(2,3)的张量
b = torch.FloatTensor([[1,2,3],[4,5,6]])
c = torch.IntTensor([1,2])
d = torch.rand(2,3) #0~1之间的随机数 Tensor可以不用以元组的形式列出,而numpy需要
e = torch.randn(2,3) #均值为0,方差为1的正态分布
f = torch.normal(means,std,size=(H,W)out=None) #正态分布
g = torch.range/arrange(起始值,结束值,步长)
h = torch.zeros(2,3)
i = torch.ones(2,3)
运算
b = torch.abs(a)
c = torch.add(a,b) #其余运算与numpy不一样
d = torch.clamp(a,-0.1,0.1)#对输入参数按照自定义的范围进行裁剪。输入:需要进行剪裁的Tensor数据类型的变量,裁剪的上边界和下边界。具体的裁剪过程是:使用变量中的每个元素分别和裁剪的上边界及裁剪的下边界的值进行比较,如果元素的值小于裁剪的下边界的值,该元素就被重写成裁剪的下边界的值;同理,如果元素的值大于裁剪的上边界的值,该元素就被重写成裁剪的上边界的值
e = torch.div(a,b)#将参数传递到torch.div后返回输入参数的求商结果作为输出,同样,参与运算的参数可以全部是Tensor数据类型的变量,也可以是Tensor数据类型的变量和标量的组合。
f = torch.mul(a,2)#将参数传递到 torch.mul后返回输入参数求积的结果作为输出,参与运算的参数可以全部是Tensor数据类型的变量,也可以是Tensor数据类型的变量和标量的组合。
g = torch.pow(a,2)#将参数传递到torch.pow后返回输入参数的求幂结果作为输出,参与运算的参数可以全部是Tensor数据类型的变量,也可以是Tensor数据类型的变量和标量的组合。
h = torch.mm(a,b)#矩阵之间的乘法规则进行运算
i = torch.mv(a,b)#矩阵与向量之间的乘法规则进行计算,前者为矩阵,后者为向量
搭建简单的神经网络
batch_n = 100
hidden_layer = 100
input_data = 1000
output_data = 10
我们先通过import torch导入必要的包,然后定义4个整型变量,其中:
·batch_n是在一个批次中输入数据的数量,值是100,这意味着我们在一个批次中输入100个数据;
·input_data指每个数据包含的数据特征有input_data个,因为input_data的值是1000,所以每个数据的数据特征就是1000个;
·hidden_layer用于定义经过隐藏层后保留的数据特征的个数,这里有100个,这里我们的模型只考虑一层隐藏层,所以在代码中仅定义了一个隐藏层的参数;
·output_data是输出的数据,值是10表示我们最后要得到10个分类结果值。
一个批次的数据从输入到输出的完整过程是:先输入100个具有1000个特征的数据,经过隐藏层后变成100个具有100个特征的数据,再经过输出层后输出100个具有10个分类结果值的数据,在得到输出结果之后计算损失并进行后向传播,这样一次模型的训练就完成了,然后循环这个流程就可以完成指定次数的训练,并达到优化模型参数的目的。
import torch
import numpy as np
torch.manual_seed(0)
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
print(x.shape)
print(w1.shape)
print(w2.shape)
print(y.shape)
#设定后向传播的次数和梯度下降使用的学习速率。
epoch_n = 20
learning_rate = 1e-6
#训练
for epoch in range(epoch_n):
#前向传播
h1 = x.mm(w1) #[100,100] 每一层独立
h1 = h1.clamp(min = 0) #使用clamp方法进行裁剪,将小于零的值全部重新赋值为0,这就像加上了一个ReLU激活函数的功能。
y_pred = h1.mm(w2) #[100,10] 100个十分类结果 串联起每一层
#计算损失
loss = (y_pred - y ).pow(2).sum() #误差值的计算使用了均方误差函数
print("Epoch:{}, Loss:{:.4f}".format(epoch, loss))
#后向传播,计算梯度使用的是每个节点的链式求导结果
grad_y_pred = 2*(y_pred - y)
grad_w2 = h1.t().mm(grad_y_pred)
grad_h = grad_y_pred.clone()
grad_h = grad_h.mm(w2.t())
grad_h.clamp_(min=0)
grad_w1 = x.t().mm(grad_h)
#权重参数优化
w1 -= learning_rate*grad_w1
w2 -= learning_rate*grad_w2
自动梯度
通过torch.autograd包可使模型参数自动计算在优化过程中需要使用的梯度值。
运行原理
torch.autograd包的主要功能是完成神经网络后向传播中的链式求导,实现自动梯度功能的过程大致为:先通过输入的Tensor数据类型的变量在神经网络的前向传播过程中生成一张计算图,然后根据这个计算图和输出结果准确计算出每个参数需要更新的梯度,并通过完成后向传播完成对参数的梯度更新。
重点:前向传播生成完整的计算图,反向传播进行参数更新。
计算图
实现自动求导功能需要将Tensor变为Variable。计算图中的每一个节点是一个Variable。
用X来表示节点,X.data表示Tensor数据类型的变量;X.grad表示X的梯度,是一个Variable类型;X.grad.data表示X的梯度值。
import torch
from torch.autograd import Variable
batch_n = 100 #the number of`samples
hidden_layer = 100 #the number of reserved features in one sample
input_data = 1000 #the number of features in one sample
output_data = 10 #the number of catagories in one sample
epoch_n = 20
learning_rate = 1e-6
#only one hidden layer
#prepare dataset
x = Variable(torch.randn(batch_n,input_data),requires_grad = False)
y = Variable(torch.randn((batch_n,output_data),requires_grad= False)
#requires_grad是False的时候,计算图中的梯度值不计算。
w1 = Variable(torch.randn(input_data,hidden_layer),requires_grad = True)
w2 = Variable(torch.randn(hidden_layer,output_data),requires_grad = True)
for epoch in range(epoch_n):
#spread forward
x1 = x.mm(w1)
y_pred = x1.mm(w2)
#compute loss
loss = (y_pred - y).pow(2).sum()
#spread backward
loss.backward() #计算并获取梯度,可理解为赋值运算。
#update param
w1.data -= learning_rate * w1.grad.data #只可以获取也叶点的data
w2.data -= learning_rate * w2.grad.data
w1.grad.data.zero_()#梯度置零
w1.grad.data.zero_()#不置零的话,计算的梯度会一直累加。本次与上次累加。
#一般顺序为先清零后backward最后更新。
自定义传播函数
import torch
from torch.autograd import Variable
import torch.optim as optim
import torch.nn as nn
batch_n = 100 #the number of`samples
hidden_layer = 100 #the number of reserved features in one sample
input_data = 1000 #the number of features in one sample
output_data = 10 #the number of catagories in one sample
epoch_n = 20
#only one hidden layer
class model(nn.Module):
def __init__(self,n_batch,hidden_layer,input_data,output_data):
super(model,self).__init__() #必不可少。先类名后self。
self.fc1 = nn.Linear(input_data,hidden_layer)
self.fc2 = nn.Linear(hidden_layer,output_data)
def forward(self,x):
x1 = self.fc1(x)
x2 = self.fc2(x1)
return x2
criterion = nn.MSELoss()
x = Variable(torch.randn(batch_n,input_data))
y = Variable(torch.randn(batch_n,output_data))
Model = model(batch_n,hidden_layer,input_data,output_data)
optimizer = optim.SGD(Model.parameters(),lr=1e-6,momentum=0.9) #__init__需要有参数。
for epoch in range(epoch_n):
out = Model(x) #forward是关键字,直接调用即可。
loss = criterion(y,out)
print(f'epoch={epoch}loss={loss}')
optimizer.zero_grad() #对optimizer进行清零。
loss.backward() #计算至loss中的计算图的梯度
optimizer.step() #loss进入optimizer,开始更新参数。
模型搭建和参数优化
关于我们model中的__init__部分,我们可以进一步优化。
PlanA
self.fc = torch.nn.Sequencial(
nn.Linear(input_data,hidden_layer),
nn.ReLU(),
nn.Linear(hidden_layer,output_data))
PlanB
self.fc = torch.nn.Sequencial(
fc.((nn.Linear(input_data,hidden_layer))
fc.add(nn.ReLU())
fc.add(nn.Linear(hidden_layer,output_data))
nn.Linear()
输入特征数,输出特征数,是否偏置(默认为True)
数据装载——以MNIST为例
我们会用到:
torchvision-datasets
torchvision-transforms
torch.utils.data
datasets
包含多个数据集。
以MNIST为例
datasts.MNIST(root,train=True,transform=None,target_transform=None,download=False)
-root:主目录
-train:True 训练集 False 测试集
-transform:数据预处理
-download:True 从互联网上下载数据,并把数据集放在root目录下,如果之前下载过,放在processed文件夹。
transforms
获取的数据是大小不一的图片,需要先进行归一化等操作。
对图片进行变换
transforms.Compose([]) #将多个transforms连接起来。
transforms.Resize(size) #对载入的图片按照我们的需求进行缩放。
transforms.Scale(size) #同上
transforms.CenterCrop(size) #size可以是元组也可以是一个整数。进行中心切割。
transforms.RandomCrop(size,padding=0)#切割中心点的诶值随机选取
transforms.RandomSizedCrop(size,interpolation=2) #随机切,再resize给定的size大小
transforms.RandomHorizontalFlip() #随机水平翻转给定的图片,概率为0.5.
transforms.RandomHorizontalFlip() #垂直翻转
transforms.Pad(padding,fill=0) #将图片用给定的fill填充,padding决定了填充的像素数量。
Example:
from torchvision import transforms
from PIL import Image
padding_img = transforms.Pad(padding=10, fill=0)
img = Image.open('test.jpg')
print(type(img))
print(img.size)
padded_img=padding_img(img)
print(type(padded_img))
print(padded_img.size)
img.show()
对Tensor进行变换
transfroms.ToTensor() #将一个取值范围为[0,255]的PIL.Image或者Shape为(H,W,C)的numpy.ndarray转化为形状[C,H,W],取值范围为[0,1.0]的torch。FloatTensor。
transforms.ToPILImage() #将shape为(C,H,W)的Tensor或shape为(H,W,C)的numpy.ndarray转换为PIL.Image
torch.utils.data
API:应用程序接口
torch.utils.data.TensorDataset(data_tensor,target_tensor) #包装类,将数据表装为Dataset类,传入Dataloader中,数据和目标的张量。
torch.utils.data.DataLoader(dataset,batch_size,shuffle,sampler=None,num_worker=0)
#dataset 加载数据的数据集
#batch_size 每个batch加载多少个样本,默认为1
#shuffle 设置为True,数据集再每个epoch重新打乱数据
#sampler 定义从数据集中提取样本的策略,如果指定,忽略shuffle
#num_worker 用多少个子进程加载数据。
Dataloader本质上是一个iterable(和list一样),但不能用next()。
Dataloader可以用iter()访问,返回一个迭代器,然后可以用next()访问。
迭代器统一了对容器的访问方式,返回了一种特殊的序列结构。
MNIST数据集
.gz 压缩包文件,在Linux以及Mac文件下可直接解压。
train-images-idx3-ubyte.gz :压缩包 60000个样本
train-labels-idx1-ubyte.gz:压缩包 60000个标签
t10k-images-idx3-ubyte.gz:压缩包 10000个样本
t10k-labels-idx1-ubyte.gz:压缩包 10000个标签
字节文件。
用8-bit(1 byte)表示一个pixel的灰度。
0表示白色,255表示黑色,共256个级别的灰度。
灰度图,单通道。
每张图片有28x28个pixel
我们首先需要将其转化为numpy或者图片。
raw文件夹存储下载下来的基础文件
processed文件夹存储datasets.MNIST制作出的Dataset类。
在train.pt中,每一行是PIL.Image数据和其标签。
我们也可以直接对ubyte文件进行操作。比如转化为jpg图片或者numpy。
DataLoader、Dataset数据类型是DataLoader和Dataset但可以当tuple理解和操作。
对DataLoader的一点思考
#数据预处理
data_loader_train_transforms = transforms.Compose([transforms.ToTensor(),transforms.Normalize(0.5,0.5)])
dataset_train = datasets.MNIST(root = './data',transform = data_loader_train_transforms,train = True,download = False) #(img(Tensor类型) ,label)
dataset_train_loader = DataLoader(dataset_train,batch_size = batch_num,shuffle=True) #batchs_size * 28 * 28 ,是一个整体。
#网络训练
for epoch in range(epoch_num):
for i,(img,label) in enumerate(dataset_train_loader):
img_tackled = Variable(img.reshape(-1,28*28)) #batch_size * features 大小的矩阵
label_tackled = Variable(label)
out = model(img_tackled)
loss = criterion(out,label_tackled)
optimizer.zero_grad()
loss.backward()
optimizer.step
if epoch%100 == 0:
print(f'epoch:{epoch}/{epoch_num},loss:{loss}')
以上是手写体识别的网络部分。
首先Dataloader的作用是,结合batch_size的大小,将数据捆成几份,形成6000个大小为batch_sizex28x28的张量。
接着,每个i循环中,我们都全部训练一遍。
最后,每个epoch中,我们打乱dataset_train_loader再训练一遍。
关于Cuda的调用
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
#模型
model.to(device)
#Loss的比较对象
out.to(device)
label.to(device)
网络结果可视化
loss.item() 得到张量内部的元素值。用于提取并可视化Loss。
plt.figure() #创建画板
plt.title('')
plt.grid() #网格线
plt.plot() #绘图
plt.show()
模型保存
PlanA
保存完整模型
#保存
torch.save(model, 'model.pth')
#加载
model = torch.load('model.pth')
PlanB
保存模型参数
# 保存
torch.save(model.state_dict(), '\parameter.pkl')
# 加载
model = TheModelClass(...)
model.load_state_dict(torch.load('\parameter.pkl'))
pkl和pth没区别。