神经网络Pytorch实现

上次我们通过“神经网络简介”这篇文章介绍了神经网络的基本原理,主要是多层感知机,并用numpy进行了实现。不过现在主流的深度学习都是采用框架来进行开发的,最主流的是:Tensorflow,Keras(对Tensorflow做了封装,本质上还是Tensorflow),Mxnet,Pytorch,以及国内百度的PaddlePaddle。不过最近用Pytorch实现的深度学习算法越来越多,所以我们采用Pytorch来讨论深度学习算法。

PyTorch前身Torch是用Lua写的机器学习框架,后来受到Facebook、NVIDIA(著名显卡生产厂商)、Uber等大公司以及斯坦福大学、卡内基·梅隆大学等著名高校的支持。按照官方的说法,PyTorch具有如下3个最关键的特性:与Python完美融合(Pythonic);支持张量计算(tensor computation);动态计算图(dynamic computation graph)。

PyTorch的运算单元叫作张量(tensor)。我们可以将张量理解为一个多维数组。

下面我们来看看张量的基本用法:

import torch
import numpy as np

a = torch.Tensor([[2,3],[4,8],[7,9]]) # 定义一个三行二列的矩阵
print(a)
print(a.size())
# 输出:
tensor([[2., 3.],
        [4., 8.],
        [7., 9.]])
torch.Size([3, 2])

b = torch.zeros((3,2)) # 定义一个三行二列的全零矩阵
print(b)
print(b.size())
# 输出:
tensor([[0., 0.],
        [0., 0.],
        [0., 0.]])
torch.Size([3, 2])

c = torch.randn((3,2)) # 定义一个三行二列的正态分布矩阵
print(c)
print(c.size())
# 输出:
tensor([[ 0.5061,  0.4601],
        [-1.3891, -0.1940],
        [ 1.3784,  0.8395]])
torch.Size([3, 2])

a[0, 1] = 100 # 改变矩阵a的一个元素
print(a)
# 输出:
tensor([[  2., 100.],
        [  4.,   8.],
        [  7.,   9.]])

pytorch的Tensor和numpy的ndarray可以非常方便的互相转换:

a_n = a.numpy() # 将Tensor转成numpy数组
print(a_n)
print(type(a))
print(type(a_n))
# 输出:
[[  2. 100.]
 [  4.   8.]
 [  7.   9.]]
<class 'torch.Tensor'>
<class 'numpy.ndarray'>

d = np.array([[2,3],[4,5]]) # 将numpy数组转成Tensor
d_t = torch.from_numpy(d)
print(d_t)
print(type(d))
print(type(d_t))
# 输出:
tensor([[2, 3],
        [4, 5]], dtype=torch.int32)
<class 'numpy.ndarray'>
<class 'torch.Tensor'>

做深度学习离不开GPU,在有GPU的条件下,我们尽量把数据和深度学习模型迁移到GPU上去进行计算,这样可以大大提高效率。

if torch.cuda.is_available():
    a_cuda = a.cuda()
    print(a_cuda)
# 输出:
tensor([[  2., 100.],
        [  4.,   8.],
        [  7.,   9.]], device='cuda:0')

Variable,叫作变量,是神经网络计算图里特有的一个概念。Variable提供了自动求导的功能。Variable和Tensor本质上没有区别,不过Variable会被放入一个计算图中,然后进行前向传播,反向传播,自动求导。

要将一个tensor变成Variable很简单,只需要Variable(a)就可以了。Variable有三个比较重要的组成属性:data,grad和grad_fn。

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 = 2x + 3
y.backward() # 反向传播
print(x.grad) # x的梯度
print(w.grad) # w的梯度
print(b.grad) # b的梯度
# 输出:
tensor([2.])
tensor([1.])
tensor([1.])

构建Variable,有一个传入的参数requires_grad=True,这个参数表示是否对这个变量求梯度,默认的是False,也就是不对这个变量求梯度,这里我们希望得到这些变量的梯度,所以需要传入这个参数。y.backward()就是自动求导,等价于y.backward(torch.FloatTensor([1])),对于标量求导这里面的参数就可以不用写了,但是如果用一个多维向量做自动求导就需要带上参数了,否则会报错。

pytorch主要通过Dataset和DataLoader两个类来读取训练和测试数据集。Dataset定义了数据集,我们自定义的Dataset继承torch.utils.data.Dataset,只要定义__len__和__getitem__这两个函数就行了。

DataLoader主要定义了数据加载方式,如按批次读取的批次大小,是否需要打乱数据,是否需要多线程去读取等等

下面我们通过一个数据读取的例子来看一下:

from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
import os
from PIL import Image

def make_data(root):
    data = []
    cat_path = root + '/cat/'
    for f in os.listdir(cat_path):
        data.append((cat_path+f, 0))
        data = data[:100]
        #print(f)
    dog_path = root + '/dog/'
    for f in os.listdir(dog_path):
        data.append((dog_path+f, 1))
        #print(f)
    return data[:200]
    


class myDataset(Dataset):
    def __init__(self, root, transform=None):
        data = make_data(root)
        self.data = data
        self.transform = transform
        self.root = root
        
    def __len__(self):
        return len(self.data)
    
    def __getitem__(self, idx):
        img_path, label = self.data[idx]
        image = Image.open(img_path).convert('RGB')
        if self.transform:
            image = self.transform(image)
        return image, label
# 把数据格式转成Tensor,否则DataLoader无法加载
transform = transforms.Compose([transforms.RandomResizedCrop(224), transforms.ToTensor()]) 
# 同时,也把图片数据都裁剪成224大小,否则大小不同DataLoader加载也会报错
trainDataset = myDataset(r'D:\zj\自己项目\Python机器学习实践\样本库\dogcat\zip\train',transform=transform)
train_loader = DataLoader(dataset=trainDataset, batch_size=4, shuffle=True)
for i,data in enumerate(train_loader):
    print(i)
    trainData, trainLabel = data
    print(trainLabel)
# 输出:
0
tensor([1, 2, 1, 1])
1
tensor([1, 1, 1, 1])
2
tensor([1, 1, 1, 1]) ...

在pytorch里面编写神经网络,所有的层和损失函数都来自于torch.nn包,所有的模型都是从nn.Module这个类继承的。一般在__init__函数中定义网络结构,在forward函数中定义网络的前向传播流程。

定义完模型之后,我们需要通过nn这个包来定义损失函数。常见的损失函数包括均方误差,多分类的交叉熵损失函数,以及二分类交叉熵等等。如下所示:

criterion = nn.CrossEntropyLoss()

loss = criterion(output, target)

在深度学习中,我们需要通过修改参数使得损失函数最小化,优化算法是一种调整模型参数更新的策略。在pytorch中,优化算法主要是封装在torch.optim包中的。

如:optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

代表了我们设定学习率为0.01,动量是0.9的随机梯度下降策略。在优化之前需要先将梯度归零,用optimeizer.zeros()实现,进行前向传播,计算损失值loss,然后通过loss.backward()做反向传播,自动求导得到每个参数的梯度,最后只需要调用optimizer.step()就可以通过梯度做参数更新。

训练结束的模型,我们用torch.save保存,用torch.load重新调用和加载。

下面,我们通过一个猫狗识别的案例来实际应用一下pytorch。我们有两种图片,一种是猫的图片,一种是狗的图片,希望训练一个模型,使得它可以对输入的猫狗图片区分。

首先,读取train文件夹下的图片,从cat文件夹中读取的图片是猫,标签赋值为0,从dog文件夹中读取的图片的狗,标签赋值为1。

import torch
import torch.nn as nn
import numpy as np
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
import os
from PIL import Image
import torchvision.models as models
from torch.autograd import Variable

def make_data(root):
    data = []
    cat_path = root + '/cat/'
    for f in os.listdir(cat_path):
        data.append((cat_path+f, 0))
        data = data[:100]
        #print(f)
    dog_path = root + '/dog/'
    for f in os.listdir(dog_path):
        data.append((dog_path+f, 1))
        #print(f)
    return data[:200]

构建Dataset,主要是对读取图片,并转换成Tensor,裁剪到224*224的大小。

class myDataset(Dataset):
    def __init__(self, root, transform=None):
        data = make_data(root)
        self.data = data
        self.transform = transform
        self.root = root
        
    def __len__(self):
        return len(self.data)
    
    def __getitem__(self, idx):
        img_path, label = self.data[idx]
        image = Image.open(img_path).convert('RGB')
        if self.transform:
            image = self.transform(image)
        return image, label

下面我们构建了一个训练函数,采用了预训练的VGG16模型,因为VGG是在Imagenet上做的预训练,最后输出是1000类的,而我们这里是两类的,所以需要改造一下最后一层。

def train():
     # 把数据格式转成Tensor,否则DataLoader无法加载
    transform = transforms.Compose([transforms.RandomResizedCrop(224), transforms.ToTensor()])
    # 同时,也把图片数据都裁剪成224大小,否则大小不同DataLoader加载也会报错
    trainDataset = myDataset(r'D:\zj\自己项目\Python机器学习实践\样本库\dogcat\zip\train',transform=transform)
    train_loader = DataLoader(dataset=trainDataset, batch_size=4, shuffle=True)
    lr = 1e-5

    net = models.vgg16(pretrained=True) # 获取预训练的VGG模型
    dim_in = net.classifier[-1].in_features # 最后一个分类层的输入维度
    net.classifier[-1] = nn.Linear(dim_in, 2) # 网络最后一层改为分类到两个类别
    print(net)

    loss_fn = torch.nn.CrossEntropyLoss() # 定义交叉熵分类损失函数
    optimizer = torch.optim.SGD(net.parameters(), lr=lr, momentum=0.9) # 定义优化函数
    
    for epoch in range(10):
        print(epoch)
        train_loss = 0.
        train_acc = 0.
        for i,data in enumerate(train_loader):
            trainData, trainLabel = data
            if torch.cuda.is_available():
                trainData = Variable(trainData).cuda()
                trainLabel = Variable(trainLabel).cuda()
                net.cuda()
            else:
                trainData = Variable(trainData)
                trainLabel = Variable(trainLabel)
            out = net(trainData)
            #print(out.shape)
            #print(trainLabel.shape)
            #print("out: ",out)
            #print("train label: ",trainLabel)
            #print("torch.max===",torch.max(out, 1))
            pred = torch.max(out, 1)[1] # 获取到预测的结果,[1]代表的是预测出来的索引值,也就是标签的值
            #print("pred===",pred)
            train_acc += (pred==trainLabel).sum()
            loss = loss_fn(out, trainLabel)
            train_loss += loss.item()
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
        print("train loss: {:.6f}".format(train_loss/len(trainDataset)))
        print("train acc: {:.6f}".format(train_acc/len(trainDataset)))
    
    torch.save(net, './dogcat.pth') # 存储训练后的模型
    
 # 输出:
out:  tensor([[-0.4860, -0.0374],
    [-0.6848,  0.0859],
    [-0.5021,  0.0744],
    [-0.7800,  0.6378]], device='cuda:0', grad_fn=<AddmmBackward0>)
torch.max=== torch.return_types.max(
values=tensor([-0.0374,  0.0859,  0.0744,  0.6378], device='cuda:0',
       grad_fn=<MaxBackward0>),
indices=tensor([1, 1, 1, 1], device='cuda:0'))
pred=== tensor([1, 1, 1, 1], device='cuda:0')
0
train loss: 0.179478
train acc: 0.525000
1
train loss: 0.154265
train acc: 0.640000
2
train loss: 0.135757
train acc: 0.770000
3
train loss: 0.127150
train acc: 0.775000
4
train loss: 0.112834
train acc: 0.845000
5
train loss: 0.105011
train acc: 0.830000
6
train loss: 0.093956
train acc: 0.875000
7
train loss: 0.082467
train acc: 0.895000
8
train loss: 0.087010
train acc: 0.880000
9
train loss: 0.079594
train acc: 0.895000

可以看到训练精度不断提升,损失值不断下降。下面定义一个验证函数,对验证集的数据进行预测,并和标签比对,验证精度。

def validation():
     # 把数据格式转成Tensor,否则DataLoader无法加载
    transform = transforms.Compose([transforms.RandomResizedCrop(224), transforms.ToTensor()])
    # 同时,也把图片数据都裁剪成224大小,否则大小不同DataLoader加载也会报错
    valDataset = myDataset(r'D:\zj\自己项目\Python机器学习实践\样本库\dogcat\zip\val',transform=transform)
    val_loader = DataLoader(dataset=valDataset, batch_size=4, shuffle=True)
    loss_fn = torch.nn.CrossEntropyLoss() # 定义交叉熵分类损失函数
    net = torch.load('./dogcat.pth')
    net.eval() # 验证和预测模式
    val_loss = 0.
    val_acc = 0.
    for i,data in enumerate(val_loader):
        valData, valLabel = data
        if torch.cuda.is_available():
            valData = Variable(valData).cuda()
            valLabel = Variable(valLabel).cuda()
            net.cuda()
        else:
            valData = Variable(valData)
            valLabel = Variable(valLabel)
        #print(valData.shape)
        out = net(valData)
        pred = torch.max(out, 1)[1]
        val_acc += (pred==valLabel).sum()
        loss = loss_fn(out, valLabel)
        val_loss += loss.item()
    print("val loss: {:.6f}".format(val_loss/len(valDataset)))
    print("val acc: {}".format(val_acc/len(valDataset)))
    
#输出:
val loss: 0.072276
val acc: 0.9049999713897705

可以看到,模型在验证集上的精度也达到了90%,毕竟才迭代了10次,效果还是可以的。最后定义一个测试函数,输入一副未知的猫狗图片,让系统判断结果。

def test():
    # 把数据格式转成Tensor,否则DataLoader无法加载
    transform = transforms.Compose([transforms.RandomResizedCrop(224), transforms.ToTensor()]) 
    image = Image.open(r'D:\zj\自己项目\Python机器学习实践\样本库\dogcat\zip\test\111.jpg').convert('RGB')
    img = transform(image)
    print(img.shape) # [3,224,224]
    img.unsqueeze_(0) # 再第0维上提升一个维度,否则和训练的时候shape不同,会报错
    print(img.shape) # [1, 3, 224, 224]
    net = torch.load('./dogcat.pth')
    net.eval() # 验证和预测模式

    if torch.cuda.is_available():
        img_data = Variable(img).cuda()
        net.cuda()
    else:
        img_data = Variable(img)
    out = net(img_data)
    pred = torch.max(out, 1)[1]
    print("result:", pred)
    r = pred.item()
    if r==1:
        print("This is a dog")
    elif r==0:
        print("This is a cat")

if __name__ == "__main__":
    #train()
    #validation()
    test()
# 输出:
torch.Size([3, 224, 224])
torch.Size([1, 3, 224, 224])
result: tensor([0], device='cuda:0')
This is a cat

要注意的一个地方就是,输入图像是三维的,但是训练出的模型输入是四维的,还有一个批量batch_size的维度,所以需要把图像数据提升一个维度再输入到模型中。

从输入来看,结果是猫,预测正确。

可以使用 PyTorch 中的 torch.optim 模块来实现 PSO 优化神经网络。具体实现方法可以参考以下代码: ```python import torch import torch.nn as nn import torch.optim as optim import numpy as np # 定义神经网络模型 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.fc1 = nn.Linear(10, 5) self.fc2 = nn.Linear(5, 1) def forward(self, x): x = torch.relu(self.fc1(x)) x = self.fc2(x) return x # 定义 PSO 优化器 class PSO(optim.Optimizer): def __init__(self, params, lr=0.01, momentum=0.9, weight_decay=0.0): defaults = dict(lr=lr, momentum=momentum, weight_decay=weight_decay) super(PSO, self).__init__(params, defaults) self.particles = [] self.velocities = [] for param in self.param_groups[0]['params']: self.particles.append(param.data.clone()) self.velocities.append(torch.zeros_like(param.data)) self.best_particles = self.particles.copy() self.best_scores = [np.inf] * len(self.particles) def step(self, closure=None): loss = None if closure is not None: loss = closure() for i, param in enumerate(self.param_groups[0]['params']): velocity = self.velocities[i] particle = self.particles[i] best_particle = self.best_particles[i] best_score = self.best_scores[i] # 更新粒子速度和位置 r1 = torch.rand_like(param.data) r2 = torch.rand_like(param.data) velocity = self.param_groups[0]['momentum'] * velocity + \ self.param_groups[0]['lr'] * r1 * (best_particle - particle) + \ self.param_groups[0]['lr'] * r2 * (param.data - particle) particle = particle + velocity # 更新粒子最优解 score = loss.item() if score < best_score: best_particle = particle.clone() best_score = score # 更新参数 param.data = particle.clone() # 保存粒子状态 self.particles[i] = particle.clone() self.velocities[i] = velocity.clone() self.best_particles[i] = best_particle.clone() self.best_scores[i] = best_score return loss # 使用 PSO 优化神经网络 net = Net() criterion = nn.MSELoss() optimizer = PSO(net.parameters(), lr=0.01, momentum=0.9, weight_decay=0.0) inputs = torch.randn(1, 10) labels = torch.randn(1, 1) for i in range(100): optimizer.zero_grad() outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() print(net(inputs)) ``` 这段代码实现了一个简单的神经网络模型,使用 PSO 优化器来训练模型。其中 PSO 优化器继承自 PyTorch 的 Optimizer 类,实现了粒子群算法的更新规则。在每次迭代中,PSO 优化器会更新神经网络模型的参数,并保存每个粒子的状态和最优解。最后输出模型的预测结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值