1.tensor类型数据操作
随机生成5*3的tensor
y = torch.rand(5, 3)
print(y)
print(x + y)
全为0,且定义元素类型为长整型
x = torch.zeros(5, 3, dtype=torch.long)
print(x)
Torch Tensor与NumPy数组共享底层内存地址,修改一个会导致另一个的变化。
a = torch.zeros(5, dtype=torch.long)#没了后面定义数据类型这句话,就默认float类型的元素了
print(a)
b = a.numpy()#numpy类型和tensor可以自动转换
print(b)
a = np.ones(5)
b = torch.from_numpy(a)#numpy类型和tensor可以自动转换
np.add(a, 1, out=a)
b=b.long()#数据类型转换
print(a)
print(b)
print(b)
高维数据的提取
a = torch.rand(4,3,28,28)
1.提取
a[0].shape #提取数第0个元素
Out:
torch.Size([3, 28, 28]) #输出的是第0列元素的维度
2.第一维和第二维全部提取,第三四维度0~28号元素隔行提取
a[:,:,0:28:2,0:28:2].shape
Out:
torch.Size([4, 3, 14, 14])
3.另一种对某一列元素直接提提取要求
a.index_select(0,torch.tensor([0,2])).shape
Out:
torch.Size([2, 3, 28, 28])
4.用mask掩码提取
x = torch.randn(3,4)
out:
tensor([[-0.5798, 1.8884, -1.5138, 0.4819],
[ 2.1086, -1.2168, 0.0635, 2.3809],
[ 0.8980, -0.9256, -1.1818, -0.3602]])
mask = x.ge(0.5)
out:
tensor([[False, True, False, False],
[ True, False, False, True],
[ True, False, False, False]])
反向查找:
torch.masked_select(x,mask)
out:
tensor([1.8884, 2.1086, 2.3809, 0.8980])
5.打平,适合于全连接层
'''x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8) # size -1 从其他维度推断
print(x.size(),'\n', y.size(),'\n' ,z.size(),end='')
print(" ")
print(x[0, 0])
print(x,'\n',y,'\n',z,end=" ")'''
a = torch.rand(4,1,28,28)
a.view(4,28*28)
转换成4*784的数组,把第二维抹掉,第三四维度融合
OUT:
tensor([[0.3119, 0.5369, 0.0716, ..., 0.5901, 0.9709, 0.0840],
[0.1729, 0.7027, 0.0882, ..., 0.2998, 0.8972, 0.1559],
[0.6869, 0.0621, 0.2235, ..., 0.8772, 0.4899, 0.0951],
[0.8927, 0.9689, 0.1014, ..., 0.0775, 0.7951, 0.6934]])
6.压缩和反向压缩squeeze
在第一维度之前插入一个维度
a.unsqueeze(0).shape
out:
torch.Size([1, 4, 1, 28, 28])
不输入参数,把默认可以压缩的都压缩了
b.shape=torch.Size([1, 32, 1, 1])
b.squeeze().shape
out:
torch.Size([32])
只能压缩维度为一的列
b.squeeze(0).shape
Out:
torch.Size([32, 1, 1])
b.squeeze(1).shape
out:
torch.Size([1,32, 1, 1])
7.扩展expand
1->n不需要写策略,32->n要写策略,就会报错。
b=torch.Size([1, 32, 1, 1])
a = torch.rand(4,32,14,14)
b.expand(4,32,14,14).shape
8.复制repeat
b=torch.Size([1, 32, 1, 1])
repeat参数写的是某一维元素要重复的次数,expand是最终需要他某一维度要多少个元素
b.repeat(4,32,1,1).shape
out:
torch.Size([4, 1024, 1, 1])
repeat和expand的区别:不建议使用repeat,他是重写划分一块money,然后每次调用都是直接写数据。expand只有有必要的时候才写内容。
9.数据操作
a+b==torch.add(a,b)
a-b==torch.sub(a,b)
a*b==torch.mul(a,b)#对应的元素相乘
a/b==torch.div(a,b)
a@b==torch.matmul(a,b)#矩阵乘法
a.pow(2)==a**2#平方
aa.sqrt()==a.pow(1/2)==a**(1/2)#开方
torch.exp(torch.ones(2,2))==tensor([[2.7183, 2.7183],[2.7183, 2.7183]])
torch.log(a)==torch.ones(2,2)#对数
grad.max()
grad.median()
grad.clamp(0,10)#将数据大小限制在0~10之间
例如:
tensor([[ 7.6886, 0.3353, 10.0000],
[ 5.2982, 7.9627, 10.0000]])
10.范数
向量范数和矩阵范数稍微有点区别
b=tensor([[1., 1., 1., 1.],
[1., 1., 1., 1.]])
b.norm(1,dim =1)==tensor([4., 4.])#在一维度对b做一范数操作
c==tensor([[[1., 1.], [1., 1.]],
[[1., 1.], [1., 1.]]])
c.norm(1,dim = 0)==tensor([[2., 2.], [2., 2.]])
11.找特殊数字的数值和位置
a==tensor([[0., 1., 2., 3.],[4., 5., 6., 7.]])
a.min(),a.max(),a.mean(),a.prod()#最小值,最大值,平均值,累乘
==(tensor(0.), tensor(7.), tensor(3.5000), tensor(0.))#这个操作是打平成一维,再操作的
a.argmax()=7#也是打平了再返回的
a.max(dim = 1)=(3,7)#这也就是返回规定维度下的最大值和最大值所在的位置
a.argmax(dim = 1)=(3,3)
a = torch.rand(4,10)
a.topk(3,dim = 1)#在一维度上找到前三个最大的数字
torch.return_types.topk(
values=tensor([[0.9999, 0.9809, 0.9776],
[0.8233, 0.6239, 0.5821],
[0.9214, 0.9111, 0.9012],
[0.9411, 0.8931, 0.7642]]),
indices=tensor([[2, 7, 3],
[5, 0, 7],
[8, 6, 2],
[7, 4, 6]]))
a.kthvalue(8,dim = 1)#在一维度上找到排序排第八个的数字
torch.return_types.kthvalue(
values=tensor([0.9776, 0.5821, 0.9012, 0.7642]),
indices=tensor([3, 7, 2, 6]))
12.比较数值大小
a = torch.rand(2,3)==tensor([[0.4765, 0.8942, 0.2954],
[0.2414, 0.1131, 0.6429]])
a > 0
tensor([[True, True, True],
[True, True, True]])
torch.eq(a,a)
tensor([[True, True, True],
[True, True, True]])
13.模型建立
nn.Sequential
一个有序的容器,神经网络模块将按照在传入构造器的顺序依次被添加到计算图中执行,同时以神经网络模块为元素的有序字典也可以作为传入参数。
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)
y = Variable(torch.randn(batch_n , output_data) , requires_grad = False)
models = torch.nn.Sequential(
# 首先通过其完成从输入层到隐藏层的线性变换
torch.nn.Linear(input_data,hidden_layer),
# 经过激活函数
torch.nn.ReLU(),
# 最后完成从隐藏层到输出层的线性变换
torch.nn.Linear(hidden_layer,output_data)
)
print(models)
输出:
Sequential(
(0): Linear(in_features=1000, out_features=100, bias=True)
(1): ReLU()
(2): Linear(in_features=100, out_features=10, bias=True)
)
也可以这样来自定义每一层网络的名称
models = torch.nn.Sequential(OrderedDict([
("Linel",torch.nn.Linear(input_data,hidden_layer)),
("ReLU1",torch.nn.ReLU()),
("Line2",torch.nn.Linear(hidden_layer,output_data))
])
第三种;
model = nn.Sequential()
model.add_module("conv1",nn.Conv2d(1,20,5))
model.add_module('relu1', nn.ReLU())
model.add_module('conv2', nn.Conv2d(20,64,5))
model.add_module('relu2', nn.ReLU())
14. torch.nn.Linear
线性层,其实就是全连接层
torch.nn.Linear(in_features, out_features, bias=True)
15. torch.nn.RelU
非线性激活分类、torch.nn包中还有许多非线性激活函数类可供选择,比如之前讲到的PReLU、LeakyReLU、Tanh、Sigmoid、Softmax等。
16. 几种损失函数
torch.nn.L1Loss:平均绝对误差,是指模型预测值f(x)和真实值y之间距离的平均值
CrossEntropyLoss():交叉熵损失函数
熵:
用于描述一个系统中的不确定性。放在信息论的语境里来说,就是一个事件所包含的信息量。变量的不确定性越大,熵也就越大,把它搞清楚所需要的信息量也就越大。
交叉熵:
它主要刻画的是实际输出(概率)与期望输出(概率)的距离,也就是交叉熵的值越小,两个概率分布就越接近。假设概率分布p为期望输出,概率分布q为实际输出
18.综合练习
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)
y = Variable(torch.randn(batch_n , output_data) , requires_grad = False)
models = torch.nn.Sequential(
# 首先通过其完成从输入层到隐藏层的线性变换
torch.nn.Linear(input_data,hidden_layer),
# 经过激活函数
torch.nn.ReLU(),
# 最后完成从隐藏层到输出层的线性变换
torch.nn.Linear(hidden_layer,output_data)
)
# print(models)
epoch_n = 10000
learning_rate = 1e-4
loss_fn = torch.nn.MSELoss()
for epoch in range(epoch_n):
y_pred = models(x)
loss = loss_fn(y_pred,y)
if epoch%1000 == 0:
print("Epoch:{},Loss:{:.4f}".format(epoch,loss.data[0]))
models.zero_grad()
loss.backward()
for param in models.parameters():
param.data -= param.grad.data*learning_rate
out:
Epoch:0,Loss:1.0140
Epoch:1000,Loss:0.9409
Epoch:2000,Loss:0.8776
Epoch:3000,Loss:0.8216
Epoch:4000,Loss:0.7716
Epoch:5000,Loss:0.7263
Epoch:6000,Loss:0.6850
Epoch:7000,Loss:0.6468
Epoch:8000,Loss:0.6109
Epoch:9000,Loss:0.5773
收敛的比较慢,但oss值被控制在相对较小的范围之内
19.自动化的优化函数adam
Adam(Adaptive Moment Estimation)本质上是带有动量项的RMSprop,它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。它的优点主要在于经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。
使用torch.optim,需先构造一个优化器对象Optimizer,用来保存当前的状态,并能够根据计算得到的梯度来更新参数。
所有的优化器Optimizer都实现了step()方法来对所有的参数进行更新
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)
y = Variable(torch.randn(batch_n , output_data) , requires_grad = False)
models = torch.nn.Sequential(
# 首先通过其完成从输入层到隐藏层的线性变换
torch.nn.Linear(input_data,hidden_layer),
# 经过激活函数
torch.nn.ReLU(),
# 最后完成从隐藏层到输出层的线性变换
torch.nn.Linear(hidden_layer,output_data)
)
# print(models)
epoch_n = 20
learning_rate = 1e-4
loss_fn = torch.nn.MSELoss()
optimzer = torch.optim.Adam(models.parameters(),lr = learning_rate)
optimzer = torch.optim.Adam(models.parameters(),lr = learning_rate)
for epoch in range(epoch_n):
y_pred = models(x)
loss = loss_fn(y_pred,y)
print("Epoch:{}, Loss:{:.4f}".format(epoch, loss.data[0]))
optimzer.zero_grad()
loss.backward()
#进行梯度更新
optimzer.step()
在以上代码中有几处代码和之前的训练代码不同,这是因为我们引入了优化算法,所以通过直接调optimzer.zero_grad来完成对模型参数梯度的归零;并且在以上代码中增加了optimzer.step,它的主要功能是使用计算得到的梯度值对各个节点的参数进行梯度更新
out:
Epoch:17, Loss:0.8209
Epoch:18, Loss:0.8061
Epoch:19, Loss:0.7917
进行了20次训练,得到的loss值就已经远远低于之前进行10000次优化训练的结果。
20.randn和rand
torch.rand(*sizes, out=None) → Tensor
返回一个张量,包含了从区间[0,1)的 均匀分布中抽取的一组随机数,形状由可变参数sizes 定义。
torch.randn(*sizes, out=None) → Tensor
返回一个张量,包含了从 标准正态分布(Normal distribution)(均值为0,方差为 1,即高斯白噪声)中抽取一组随机数,形状由可变参数sizes定义。
21.torch.nn.Module
我们在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数init构造函数和forward这两个方法:
(1)一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数init()中,当然我也可以吧不具有参数的层也放在里面;
(2)一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数init里面,则在forward方法里面可以使用nn.functional来代替
(3)前向传播函数forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
例子
import torch
class MyNet(torch.nn.Module):
def __init__(self):
super(MyNet, self).__init__() # 第一句话,调用父类的构造函数
self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.relu1=torch.nn.ReLU()
self.max_pooling1=torch.nn.MaxPool2d(2,1)
self.conv2 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.relu2=torch.nn.ReLU()
self.max_pooling2=torch.nn.MaxPool2d(2,1)
self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)
self.dense2 = torch.nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = self.relu1(x)
x = self.max_pooling1(x)
x = self.conv2(x)
x = self.relu2(x)
x = self.max_pooling2(x)
x = self.dense1(x)
x = self.dense2(x)
return x
model = MyNet
()print(model)
结果:
MyNet(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu1): ReLU()
(max_pooling1): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu2): ReLU()
(max_pooling2): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
(dense1): Linear(in_features=288, out_features=128, bias=True)
(dense2): Linear(in_features=128, out_features=10, bias=True)
上面的是将所有的层都放在了构造函数init里面,但是只是定义了一系列的层,各个层之间到底是什么连接关系并没有,而是在forward里面实现所有层的连接关系,当然这里依然是顺序连接的。
例子2:
import torch
import torch.nn.functional as F
class MyNet(torch.nn.Module):
def __init__(self):
super(MyNet, self).__init__() # 第一句话,调用父类的构造函数
self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.conv2 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)
self.dense2 = torch.nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = F.max_pool2d(x)
x = self.conv2(x)
x = F.relu(x)
x = F.max_pool2d(x)
x = self.dense1(x)
x = self.dense2(x)
return x
model = MyNet()
print(model)
结果:
MyNet(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(conv2): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(dense1): Linear(in_features=288, out_features=128, bias=True)
(dense2): Linear(in_features=128, out_features=10, bias=True)
此时,将没有训练参数的层没有放在构造函数里面了,所以这些层就不会出现在model里面,但是运行关系是在forward里面通过functional的方法实现的。
例子3:
import torch.nn as nn
from collections import OrderedDict
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.conv_block = nn.Sequential(
nn.Conv2d(3, 32, 3, 1, 1),
nn.ReLU(),
nn.MaxPool2d(2))
self.dense_block = nn.Sequential(
nn.Linear(32 * 3 * 3, 128),
nn.ReLU(),
nn.Linear(128, 10)
)
# 在这里实现层之间的连接关系,其实就是所谓的前向传播
def forward(self, x):
conv_out = self.conv_block(x)
res = conv_out.view(conv_out.size(0), -1)
out = self.dense_block(res)
return out
model = MyNet()
print(model)
结果
MyNet(
(conv_block): Sequential(
(0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(1): ReLU()
(2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(dense_block): Sequential(
(0): Linear(in_features=288, out_features=128, bias=True)
(1): ReLU()
(2): Linear(in_features=128, out_features=10, bias=True)
)
之前学过容器Sequential,他可以填充各种层。即将几个层包装在一起作为一个大的层
例子4:
import torch.nn as nn
from collections import OrderedDict
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.conv_block=torch.nn.Sequential()
self.conv_block.add_module("conv1",torch.nn.Conv2d(3, 32, 3, 1, 1))
self.conv_block.add_module("relu1",torch.nn.ReLU())
self.conv_block.add_module("pool1",torch.nn.MaxPool2d(2))
self.dense_block = torch.nn.Sequential()
self.dense_block.add_module("dense1",torch.nn.Linear(32 * 3 * 3, 128))
self.dense_block.add_module("relu2",torch.nn.ReLU())
self.dense_block.add_module("dense2",torch.nn.Linear(128, 10))
def forward(self, x):
conv_out = self.conv_block(x)
res = conv_out.view(conv_out.size(0), -1)
out = self.dense_block(res)
return out
model = MyNet()
print(model)
运行结果为:
MyNet(
(conv_block): Sequential(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu1): ReLU()
(pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)
(dense_block): Sequential(
(dense1): Linear(in_features=288, out_features=128, bias=True)
(relu2): ReLU()
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
)
现在每层都有名字了
另一种打印方法:model.children()、model.modules()、model.named_modules():
for i in model.children():
print(i)
print(type(i))
for i in model.named_modules():
print(i)
for i in model.modules():
print(i)
children:
(1)model.children()和model.named_children()方法返回的是迭代器iterator;
(2)model.children():每一次迭代返回的每一个元素实际上是 Sequential 类型,而Sequential类型又可以使用下标index索引来获取每一个Sequenrial 里面的具体层,比如conv层、dense层等;
(3)model.named_children():每一次迭代返回的每一个元素实际上是 一个元组类型,元组的第一个元素是名称,第二个元素就是对应的层或者是Sequential。
modules:
(1)model.modules()和model.named_modules()方法返回的是迭代器iterator;
(2)model的modules()方法和named_modules()方法都会将整个模型的所有构成(包括包装层、单独的层、自定义层等)由浅入深依次遍历出来,只不过modules()返回的每一个元素是直接返回的层对象本身,而named_modules()返回的每一个元素是一个元组,第一个元素是名称,第二个元素才是层对象本身。
(3)如何理解children和modules之间的这种差异性。注意pytorch里面不管是模型、层、激活函数、损失函数都可以当成是Module的拓展,所以modules和named_modules会层层迭代,由浅入深,将每一个自定义块block、然后block里面的每一个层都当成是module来迭代。而children就比较直观,就表示的是所谓的“孩子”,所以没有层层迭代深入。
综合练习;
class Model(torch.nn.Module):
def __init__(self):
super(Model,self).__init__()
self.conv1 = torch.nn.Sequential(
torch.nn.Conv2d(1,64,kernel_size=3,stride=1,padding=1),
torch.nn.ReLU(),
torch.nn.Conv2d(64,128,kernel_size=3,stride=1,padding=1),
torch.nn.ReLU(),
torch.nn.MaxPool2d(stride=2,kernel_size=2))
self.dense = torch.nn.Sequential(
torch.nn.Linear(14*14*128,1024),
torch.nn.ReLU(),
torch.nn.Dropout(p = 0.5),
torch.nn.Linear(1024,10)
)
def forward(self, x):
x = self.conv1(x)
x = x.view(-1,14*14*128)
x = self.dense(x)
return x
简化的卷积神经网络模型,在结构上使用了两个卷积层:一个最大池化层和两个全连接层
22.torch.nn.Conv2d
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
in_channels:在文本应用中,即为词向量的维度
out_channels:卷积产生的通道数,有多少个out_channels,就需要多少个一维卷积(也就是卷积核的数量)
kernel_size:卷积核的尺寸;卷积核的第二个维度由in_channels决定,所以实际上卷积核的大小为kernel_size * in_channels
padding:对输入的每一条边,补充0的层数
输入参数有输入通道数、输出通道数、卷积核大小、卷积核移动步长和Paddingde值
23.torch.nn.MaxPool2d
lass torch.nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, ceil_mode=False)
kernel_size(int or tuple) - max pooling的窗口大小
stride(int or tuple, optional) - max pooling的窗口移动的步长。默认值是kernel_size
padding(int or tuple, optional) - 输入的每一条边补充0的层数
dilation(int or tuple, optional) – 一个控制窗口中元素步幅的参数
return_indices - 如果等于True,会返回输出最大值的序号,对于上采样操作会有帮助
ceil_mode - 如果等于True,计算输出信号大小的时候,会使用向上取整,代替默认的向下取整的操作
用于实现卷积神经网络中的最大池化层,主要的输入参数是池化窗口大小、池化窗口移动步长和Padding的值。同样,池化窗口大小的数据类型是整型,用于确定池化窗口的大小。池化窗口步长的数据类型也是整型,用于确定池化窗口每次移动的步长。
24.torch.nn.Dropout
torch.nn.Dropout(keep_prob)
keep_prob:不保留节点数的比例
防止卷积神经网络在训练的过程中发生过拟合,其工作原理简单来说就是在模型训练的过程中,以一定的随机概率将卷积神经网络模型的部分参数归零(按照一定的概率将其暂时从网络中丢弃),以达到减少相邻两层神经连接的目的。
a = torch.randn(10,1)
>>> tensor([[ 0.0684],
[-0.2395],
[ 0.0785],
[-0.3815],
[-0.6080],
[-0.1690],
[ 1.0285],
[ 1.1213],
[ 0.5261],
[ 1.1664]])
torch.nn.Dropout(0.5)(a)
>>> tensor([[ 0.0000],
[-0.0000],
[ 0.0000],
[-0.7631],
[-0.0000],
[-0.0000],
[ 0.0000],
[ 0.0000],
[ 1.0521],
[ 2.3328]])