深度学习核心框架PyTorch

一、神经网络分类与回归任务

学习方法

  1. 边用边学,torch只是个工具,真正用,查的才是学习的过程
  2. 直接上框架

首先我们要明白PyTorch 和 NumPy 中的数据结构与设备之间的关系

GPU/CPU 数据结构
torch GPU Tensor张量
numpy CPU ndarray

1、神经网络分类任务

分类任务是将输入数据分配到预定义类别中的任务。模型的目标是根据输入特征预测类别标签。

(1)数据集及任务概述

        MNIST(Modified National Institute of Standards and Technology)是一个广泛使用的手写数字识别数据集,常用于机器学习和深度学习的入门教程。它包含了 70,000 张手写数字的灰度图像,每个图像的尺寸为 28x28 像素,包含数字 0 到 9。

(2)torch.nn.functional模块

  • torch.nn.Module 是 PyTorch 中用于构建神经网络模型的基类。你通常会继承这个类来定义自定义的神经网络层或整个模型。nn.Module 提供了一个结构化的方式来定义模型的前向传播(forward)和其他相关操作。
  • torch.nn.functional 是一个包含许多函数式操作的模块,这些操作通常没有状态(即不涉及可训练的参数)。这些函数是为了完成某些计算或操作,而不需要定义完整的模型类。

   什么时候使用nn.Module,什么时候使用nn.functional呢? 一般情况下,如果模型有可学习的参数,最好用nn.Module,其他情况nn.functional相对更简单一些

(3)网络结构说明

 以数字九为例神经网络的输入为(784,)784是mnist数据集每个样本的像素点个数,经过神经网络训练得到数字9为每个类别(0,1,2,3,4,5,6,7,8,9)的概率,概率最大的即为预测结果。

神经网络训练总流程
神经网络训练流程
Neural Network结构
其中结构Mnist_NN(
  (hidden1): Linear(in_features=784, out_features=128, bias=True)
  (hidden2): Linear(in_features=128, out_features=256, bias=True)
  (hidden3): Linear(in_features=256, out_features=512, bias=True)
  (out): Linear(in_features=512, out_features=10, bias=True)
  (dropout): Dropout(p=0.5, inplace=False)
)

输入为(784,),经过hidden1变成(128,),经过hidden2变成(256,),经过out层变成(10,)。hidden层数可以自己来设定,out是最后输出层、dropout层可有可无。

Dropout 是一种正则化技术,通过在训练过程中随机“丢弃”一部分神经元(使它们在前向传播中不参与计算),Dropout 能够有效地提高模型的泛化能力。例如,如果 dropout rate 是 0.5,那么在每次前向传播时,约 50% 的神经元会被随机选择并且不被激活。这意味着在每个训练步骤中,模型的结构都会有所不同。

import torch.nn.functional as F
import torch
from torch import nn
from pathlib import Path
import requests
import pickle
import gzip
DATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"

PATH.mkdir(parents=True, exist_ok=True)

URL = "http://deeplearning.net/data/mnist/"
FILENAME = "mnist.pkl.gz"

if not (PATH / FILENAME).exists():
        content = requests.get(URL + FILENAME).content
        (PATH / FILENAME).open("wb").write(content)

with gzip.open((PATH / FILENAME).as_posix(), "rb") as f:
        ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")

#将 NumPy 数组转换为 PyTorch 张量
#注意数据需转换成tensor才能参与后续建模训练!!
x_train, y_train, x_valid, y_valid = map(
    torch.tensor, (x_train, y_train, x_valid, y_valid)
)
#其中 n 是样本数量,c 是特征数量
n, c = x_train.shape
x_train, x_train.shape, y_train.min(), y_train.max()
loss_func = F.cross_entropy

#x*w+b
def model(xb):
    return xb.mm(weights) + bias
bs = 64
xb = x_train[0:bs]  # a mini-batch from x
yb = y_train[0:bs]
weights = torch.randn([784, 10], dtype = torch.float,  requires_grad = True) 
bs = 64
bias = torch.zeros(10, requires_grad=True)

#定义一个类继承nn.moudle
class Mnist_NN(nn.Module):
    #构造函数
    def __init__(self):
        super().__init__()
        self.hidden1 = nn.Linear(784, 128)
        self.hidden2 = nn.Linear(128, 256)
        self.hidden3 = nn.Linear(256,512)
        self.out  = nn.Linear(512, 10)
        self.dropout = nn.Dropout(0.5)
    def forward(self, x):
        x = F.relu(self.hidden1(x))
        x=self.dropout(x)
        x = F.relu(self.hidden2(x))
        x=self.dropout(x)
        x=F.relu(self.hidden3(x))
        x=self.dropout(x)
        x = self.out(x)
        return x
net = Mnist_NN()
print(net)

(4)使用TensorDataset和DataLoader来简化

  • TensorDataset 是一个简单的 Dataset 类,用于将多个 tensor 组合在一起,使得它们可以在迭代时一起返回。通常在需要将特征 tensor 和标签 tensor 组合在一起时使用。
  • DataLoader 是一个非常强大的工具,能够方便地加载数据集,进行批处理、打乱数据、并行加载等操作。

 model.train()与model.eval()

  • 一般在训练模型时加上model.train(),这样会正常使用Batch Normalization和 Dropout
  • 测试的时候一般选择model.eval(),这样就不会使用Batch Normalization和 Dropout
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader
#创建训练数据集和数据加载器
train_ds = TensorDataset(x_train, y_train)
train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)
#shuffle=True: 在每个训练周期开始时打乱数据,增加训练的随机性,有助于提高模型的泛化能力
#创建验证数据集和数据加载器
valid_ds = TensorDataset(x_valid, y_valid)
valid_dl = DataLoader(valid_ds, batch_size=bs * 2)
def get_data(train_ds, valid_ds, bs):
    return (
        DataLoader(train_ds, batch_size=bs, shuffle=True),
        DataLoader(valid_ds, batch_size=bs * 2),
    )
import numpy as np

def fit(steps, model, loss_func, opt, train_dl, valid_dl):
    for step in range(steps):
        model.train()
        #训练模型更新w与b
        for xb, yb in train_dl:
            loss_batch(model, loss_func, xb, yb, opt)

        model.eval()#关闭梯度计算以节省内存
        with torch.no_grad():
            #zip(*[...]) 将 loss_batch 返回的 (损失, 样本数量) 分别打包成 losses 和 nums
            
            losses, nums = zip(
                *[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl]
            )
        val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)
        #求平均损失
        print('当前step:'+str(step), '验证集损失:'+str(val_loss))

from torch import optim
def get_model():
    model = Mnist_NN()
    return model, optim.Adam(model.parameters(), lr=0.001)

def loss_batch(model, loss_func, xb, yb, opt=None):
    loss = loss_func(model(xb), yb)

    if opt is not None:
        loss.backward() # 执行反向传播
        opt.step()  # 更新模型参数
        opt.zero_grad()# 清除梯度除模型参数的梯度,以便下一次迭代时能正确计算新的梯度。梯度在每次反向传播后必须清零,因为 PyTorch 的梯度是累加的。

    return loss.item(), len(xb)
train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
model, opt = get_model()
fit(20, model, loss_func, opt, train_dl, valid_dl)

2、神经网络回归任务 

回归任务是预测一个连续值或实数的任务。模型的目标是根据输入特征输出一个连续的数值。

下表为回归任务与分类任务的区别

特点 回归任务 分类任务
输出类型 连续值(实数) 离散类别标签
损失函数 均方误差(MSE),均绝对误差(MAE) 交叉熵损失(Cross Entropy Loss)
模型输出 通常是线性激活函数或没有激活函数 Softmax(多类)或 Sigmoid(二类)激活函数
应用场景 房价预测、温度预测等 图像分类、文本分类等

具体步骤懒得写了,想要完整的可以后台私信我

二、卷积网络

1、相关知识

(1)、卷积神经网络与全连接网络的对比

全连接网络将图像视为二维矩阵,每个像素点独立处理,不考虑像素间的关系。 卷积神经网络通过卷积操作选择图像窗口,综合考虑窗口内像素点的关系,提取特征。

(2)、卷积神经网络中的2D、3D和1D的区别

2D卷积主要用于处理图像数据,通过滑动窗口在图像上执行卷积操作。 3D卷积适用于处理视频数据,通过引入时间维度进行特征提取。 1D卷积用于处理结构化数据,如时间序列或文本数据。

2、网络构建

  • 一般卷积层,relu层,池化层可以写成一个套餐
  • 注意卷积最后结果还是一个特征图,需要把图转换成向量才能做分类或者回归任务

(1)卷积网络模块构建 

# 定义超参数 

input_size = 28  #图像的总尺寸28*28
num_classes = 10  #标签的种类数
num_epochs = 3  #训练的总循环周期
batch_size = 64  #一个撮(批次)的大小,64张图片
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
        self.conv1 = nn.Sequential(         # 输入大小 (1, 28, 28)
            nn.Conv2d(
                in_channels=1,              # 灰度图
                out_channels=16,            # 要得到几多少个特征图
                kernel_size=5,              # 卷积核大小通常与pad对应 5x5对应pad=2,3x3 pad=1
                
                stride=1,                   # 步长
                padding=2,                  # 如果希望卷积后大小跟原来一样,需要设置padding=(kernel_size-1)/2 if stride=1
            ),                              # 输出的特征图为 (16, 28, 28)
            nn.ReLU(),                      # relu层
            nn.MaxPool2d(kernel_size=2),    # 进行池化操作(2x2 区域), 输出结果为: (16, 14, 14)
        )
        self.conv2 = nn.Sequential(         # 下一个套餐的输入 (16, 14, 14)
            nn.Conv2d(16, 32, 5, 1, 2),     # 输出 (32, 14, 14)
            nn.ReLU(),                      # relu层
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(2),                # 输出 (32, 7, 7)
        )
        
        self.conv3 = nn.Sequential(         # 下一个套餐的输入 (16, 14, 14)
            nn.Conv2d(32, 64, 5, 1, 2),     # 输出 (32, 14, 14)
            nn.ReLU(),             # 输出 (64, 7, 7)
        )
        
        self.out = nn.Linear(64 * 7 * 7, 10)   # 全连接层得到的结果

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = x.view(x.size(0), -1)           # flatten操作 展平操作,结果为:(batch_size, 32 * 7 * 7) -1即自动计算
        
        output = self.out(x)
        return output

(2)准确率作为评估标准

def accuracy(predictions, labels):
    pred = torch.max(predictions.data, 1)[1] #会返回每个样本在所有类别中的最大值及其索引 取索引
    rights = pred.eq(labels.data.view_as(pred)).sum() 
    return rights, len(labels) 

(3)训练网络模型

# 实例化
net = CNN() 
#损失函数
criterion = nn.CrossEntropyLoss() 
#优化器
optimizer = optim.Adam(net.parameters(), lr=0.001) #定义优化器,普通的随机梯度下降算法

#开始训练循环
for epoch in range(num_epochs):
    #当前epoch的结果保存下来
    train_rights = [] 
    
    for batch_idx, (data, target) in enumerate(train_loader):  #针对容器中的每一个批进行循环
        net.train()                             
        output = net(data) 
        loss = criterion(output, target) 
        optimizer.zero_grad() #清空梯度 每次迭代之前需要手动清空。
        loss.backward() 
        optimizer.step() 
        right = accuracy(output, target) 
        train_rights.append(right) 

    
        if batch_idx % 100 == 0: 
            
            net.eval() 
            val_rights = [] 
            
            for (data, target) in test_loader:
                output = net(data) 
                right = accuracy(output, target) 
                val_rights.append(right)
                
            #准确率计算
            train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))
            val_r = (sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))

            print('当前epoch: {} [{}/{} ({:.0f}%)]\t损失: {:.6f}\t训练集准确率: {:.2f}%\t测试集正确率: {:.2f}%'.format(
                epoch, batch_idx * batch_size, len(train_loader.dataset),
                100. * batch_idx / len(train_loader), 
                loss.data, 
                100. * train_r[0].numpy() / train_r[1], 
                100. * val_r[0].numpy() / val_r[1]))

三、数据集Dataloader制作

1、如何自定义数据集:

  • 1.数据和标签的目录结构先搞定(得知道到哪读数据)
  • 2.写好读取数据和标签路径的函数(根据自己数据集情况来写)
  • 3.完成单个数据与标签读取函数(给dataloader举一个例子

2、 步骤拆解

(1)读取txt文件中的路径和标签

  • 从标注文件中读取数据和标签
  • 存成什么格式,都可以的,一会能取出来东西就行
    def load_annotations(ann_file):
        data_infos = {}
        with open(ann_file) as f:
            samples = [x.strip().split(' ') for x in f.readlines()]
            for filename, gt_label in samples:
                data_infos[filename] = np.array(gt_label, dtype=np.int64)
        return data_infos

(2)分别把数据和标签都存在list里

img_label = load_annotations('./flower_data/train.txt')
image_name = list(img_label.keys())
label = list(img_label.values())

(3)图像数据路径得完整

  • 因为一会咱得用这个路径去读数据,所以路径得加上前缀
  • 以后大家任务不同,数据不同,怎么加你看着来就行,反正得能读到图像
data_dir = './flower_data/'
train_dir = data_dir + '/train_filelist'
valid_dir = data_dir + '/val_filelist'
image_path = [os.path.join(train_dir,img) for img in image_name]

(4)Dataset实现

  1. 注意要使用from torch.utils.data import Dataset, DataLoader
  2. 类名定义class FlowerDataset(Dataset),其中FlowerDataset可以改成自己的名字
  3. def init(self, root_dir, ann_file, transform=None):咱们要根据自己任务重写
  4. def getitem(self, idx):根据自己任务,返回图像数据和标签数据
from torch.utils.data import Dataset, DataLoader
class FlowerDataset(Dataset):
    def __init__(self, root_dir, ann_file, transform=None):
        self.ann_file = ann_file
        self.root_dir = root_dir
        self.img_label = self.load_annotations()
        self.img = [os.path.join(self.root_dir,img) for img in list(self.img_label.keys())]
        self.label = [label for label in list(self.img_label.values())]
        self.transform = transform
 
    def __len__(self):
        return len(self.img)
 
    def __getitem__(self, idx):
        image = Image.open(self.img[idx])
        label = self.label[idx]
        #检查是否定义了任何数据变换操作(如数据增强)。
        if self.transform:
            image = self.transform(image)
        label = torch.from_numpy(np.array(label))
        return image, label
    def load_annotations(self):
        data_infos = {}  # 存储文件名和标签的字典
        with open(self.ann_file) as f:
            samples = [x.strip().split(' ') for x in f.readlines()]
            for filename, gt_label in samples:
                data_infos[filename] = np.array(gt_label, dtype=np.int64)
        return data_infos

(5)数据预处理(transform)

data_transforms = {
    'train': 
        transforms.Compose([
        transforms.Resize(64),
        transforms.RandomRotation(45),#随机旋转,-45到45度之间随机选
        transforms.CenterCrop(64),#从中心开始裁剪
        transforms.RandomHorizontalFlip(p=0.5),#随机水平翻转 选择一个概率概率
        transforms.RandomVerticalFlip(p=0.5),#随机垂直翻转
        transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),#参数1为亮度,参数2为对比度,参数3为饱和度,参数4为色相
        transforms.RandomGrayscale(p=0.025),#概率转换成灰度率,3通道就是R=G=B
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])#均值,标准差
    ]),
    'valid': 
        transforms.Compose([
        transforms.Resize(64),
        transforms.CenterCrop(64),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

(6)根据写好的class FlowerDataset(Dataset):来实例化咱们的dataloader

train_dataset = FlowerDataset(root_dir=train_dir, ann_file = './flower_data/train.txt', transform=data_transforms['train'])
val_dataset = FlowerDataset(root_dir=valid_dir, ann_file = './flower_data/val.txt', transform=data_transforms['valid'])
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=True)

(7)用之前先试试,整个数据和标签对应下,看看对不对

  • 1.别着急往模型里传,对不对都不知道呢
  • 2.用这个方法:iter(train_loader).next()来试试,得到的数据和标签是啥
  • 3.看不出来就把图画出来,标签打印出来,确保自己整的数据集没啥问题
    image, label = iter(train_loader).next()#取一个batch数据
    sample = image[0].squeeze()
    sample = sample.permute((1, 2, 0)).numpy()
    sample *= [0.229, 0.224, 0.225]
    sample += [0.485, 0.456, 0.406]
    plt.imshow(sample)
    plt.show()
    print('Label is: {}'.format(label[0].numpy()))
    

自己制作的dataloader就做好了

四、对抗生成网络(GAN)架构原理与实战解析

1、(GAN)架构原理

(1)GAN 主要由两个部分组成:

  • 生成器(Generator):负责生成新的数据样本。它接受随机噪声(通常是从标准正态分布中采样)作为输入,并生成伪造的数据样本。

  • 判别器(Discriminator):负责判断输入的数据样本是真实的还是伪造的。它接受真实数据和生成器生成的数据作为输入,并输出一个概率值,表示输入样本是来自真实数据的概率。

(2) GAN 的训练过程包括以下几个步骤:

stp1:生成器生成数据:生成器从随机噪声中生成伪造数据。

stp2:判别器评估数据:判别器接收真实数据和伪造数据,并试图判断它们的来源。

stp3:损失计算

判别器的目标是最大化对真实数据的正确识别率,并最小化对伪造数据的识别率。

生成器的目标是最大化判别器对伪造数据的错误识别率(即使判别器认为这些伪造数据是真实的)。

stp4反向传播:计算梯度并更新生成器和判别器的参数,使得生成器生成的数据越来越逼近真实数据。

2、GAN实战

数据集选用mnist

(1)生成器生成数据

class Generator(nn.Module):
    def __init__(self):
        super(Generator, self).__init__()

        def block(in_feat, out_feat, normalize=True):
            layers = [nn.Linear(in_feat, out_feat)]
            if normalize:
                layers.append(nn.BatchNorm1d(out_feat, 0.8))
            layers.append(nn.LeakyReLU(0.2, inplace=True))
            return layers

        self.model = nn.Sequential(
            *block(opt.latent_dim, 128, normalize=False),
            *block(128, 256),
            *block(256, 512),
            *block(512, 1024),
#最后一个层:将 1024 维特征映射到图像的特征数量(在 MNIST 中为 784,即 28*28)
            nn.Linear(1024, int(np.prod(img_shape))),
            nn.Tanh()
        )

#输入 z:是生成器的输入,从正态分布中采样的随机噪声。
    def forward(self, z):
        img = self.model(z)
        img = img.view(img.size(0), *img_shape)
        return img

(2)判别器

class Discriminator(nn.Module):
    def __init__(self):
        super(Discriminator, self).__init__()

        self.model = nn.Sequential(
            nn.Linear(int(np.prod(img_shape)), 512),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(512, 256),
            nn.LeakyReLU(0.2, inplace=True),
            nn.Linear(256, 1),#将 256 个特征映射到 1 个输出,表示判别器对输入图像的有效性。
            nn.Sigmoid(),
        )

    def forward(self, img):
        img_flat = img.view(img.size(0), -1)
        validity = self.model(img_flat)

        return validity

(3)数据加载

dataloader = torch.utils.data.DataLoader(
    datasets.MNIST(
        "./data/mnist",
        train=True,
        download=True,
        transform=transforms.Compose(
            [transforms.Resize(opt.img_size), transforms.ToTensor(), transforms.Normalize([0.5], [0.5])]
        ),
    ),
    batch_size=opt.batch_size,
    shuffle=True,
)

(4)模型训练

# Optimizers
optimizer_G = torch.optim.Adam(generator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
optimizer_D = torch.optim.Adam(discriminator.parameters(), lr=opt.lr, betas=(opt.b1, opt.b2))
# ----------
#  Training
# ----------
for epoch in range(opt.n_epochs):
    for i, (imgs, _) in enumerate(dataloader):

        #  创建真实标签 (valid)值为 1 和伪造标签 (fake)值为 0
        valid = Variable(Tensor(imgs.size(0), 1).fill_(1.0), requires_grad=False)
        fake = Variable(Tensor(imgs.size(0), 1).fill_(0.0), requires_grad=False)

        #  配置输入图像
        real_imgs = Variable(imgs.type(Tensor))

        # -----------------
        #  Train Generator
        # -----------------
        # 生成器优化器梯度清零
        optimizer_G.zero_grad()

        # 噪声生成从正态分布的随机噪声 z,其形状为 (batch_size, latent_dim)
        z = Variable(Tensor(np.random.normal(0, 1, (imgs.shape[0], opt.latent_dim))))

        # 生成伪造数据输出一个形状为 (batch_size, channels, height, width) 的图像张量,通常与真实图像的形状相同。
        gen_imgs = generator(z)

        # 计算生成器的损失
        g_loss = adversarial_loss(discriminator(gen_imgs), valid)
        
        # 反向传播更新生成器参数
        g_loss.backward()
        optimizer_G.step()

        # ---------------------
        #  Train Discriminator
        # ---------------------

        optimizer_D.zero_grad()

        # Measure discriminator's ability to classify real from generated samples
        real_loss = adversarial_loss(discriminator(real_imgs), valid)
        fake_loss = adversarial_loss(discriminator(gen_imgs.detach()), fake)
        d_loss = (real_loss + fake_loss) / 2

        d_loss.backward()
        optimizer_D.step()

        print(
            "[Epoch %d/%d] [Batch %d/%d] [D loss: %f] [G loss: %f]"
            % (epoch, opt.n_epochs, i, len(dataloader), d_loss.item(), g_loss.item())
        )

        batches_done = epoch * len(dataloader) + i
        if batches_done % opt.sample_interval == 0:
            save_image(gen_imgs.data[:25], "images/%d.png" % batches_done, nrow=5, normalize=True)

猜你喜欢

转载自blog.csdn.net/ikeke_zhang/article/details/142059284