pytorch教程

1 pytorch的安装

库名作用
torchvision图像视频处理
torchaudio音频处理
torchtext自然语言处理
pip install -i https://mirrors.aliyun.com/pypi/simple/ torch torchvision torchaudio torchtext
  • 出现以下界面说明安装成功
    在这里插入图片描述
  • 验证是否安装成功
    在这里插入图片描述

2 PyTorch基础知识

2.1 张量简介

  • 分类:0维张量(标量)、1维张量(向量)、2维张量(矩阵)、3维张量(时间序列)、4维张量(图像)、5维张量(视频)
  • 概念:一个数据容器,可以包含数据、字符串等
  • 常见的构造Tensor的函数
函数功能
Tensor(*sizes)基础构造函数
tensor(data)类似于np.array
ones(*sizes)全1
zeros(*sizes)全0
eye(*sizes)对角为1,其余为0
arange(s,e,step)从s到e,步长为step
linspace(s,e,steps)从s到e,均匀分成step份
rand/randn(*sizes)rand是[0,1)均匀分布;randn是服从N(0,1)的正态分布
normal(mean,std)正态分布(均值为mean,标准差是std)
randperm(m)随机排列
  • Tensor的运算
函数作用
torch.abs(A)绝对值
torch.add(A,B)相加,A和B既可以是Tensor也可以是标量
torch.clamp(A,max,min)裁剪,A中的数据若小于min或大于max,则变成min或max,即保证范围在[min,max]
torch.div(A,B)相除,A%B,A和B既可以是Tensor也可以是标量
torch.mul(A,B)点乘,A*B,A和B既可以是Tensor也可以是标量
torch.pow(A,n)求幂,A的n次方
torch.mm(A,B.T)矩阵叉乘,注意与torch.mul之间的区别
torch.mv(A,B)矩阵与向量相乘,A是矩阵,B是向量,这里的B需不需要转置都是可以的
A.item()将Tensor转化为基本数据类型,注意Tensor中只有一个元素的时候才可以使用,一般用于在Tensor中取出数值
A.numpy()将Tensor转化为Numpy类型
A.size()查看尺寸
A.shape查看尺寸
A.dtype查看数据类型
A.view()重构张量尺寸,类似于Numpy中的reshape
A.transpose(0,1)行列交换
A[1:]A[-1,-1]=100切面,类似Numpy中的切面
A.zero_()归零化
torch.stack((A,B),sim=-1)拼接,升维
torch.diag(A)取A对角线元素形成一个一维向量
torch.diag_embed(A)将一维向量放到对角线中,其余数值为0的Tensor

2.2 初始化

  • 张量可以直接从数据中创建。数据类型是自动推断的
import torch

# 直接从数据创建张量
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)
print(f"Tensor from Data:\n {x_data} \n")
  • 从 NumPy 数组创建
import numpy as np

# 从numpy创建张量
data = [[1, 2], [3, 4]]
np_array = np.array(data)
x_np = torch.from_numpy(np_array)
print(f"Tensor from Numpy:\n {x_np} \n")
  • 根据另一个张量创建
import torch

# 根据另一个张量创建
data = [[1, 2], [3, 4]]
x_data = torch.tensor(data)

x_ones = torch.ones_like(x_data)  # 保留原有张量的形状和数据类型
print(f"Ones Tensor: \n {x_ones} \n")

x_rand = torch.rand_like(x_data, dtype=torch.float)  # 显式更改张量的数据类型
  • 使用随机或恒定值创建.
import torch

shape = (2, 3,)  # 创建2行3列的张量
rand_tensor = torch.rand(shape)
ones_tensor = torch.ones(shape)
zeros_tensor = torch.zeros(shape)

print(f"Random Tensor: \n {rand_tensor} \n")
print(f"Ones Tensor: \n {ones_tensor} \n")
print(f"Zeros Tensor: \n {zeros_tensor}")

2.3 张量的属性

  • 张量属性包括形状、数据类型和存储设备等
import torch

tensor = torch.rand(3,4)
print(f"Shape of tensor: {tensor.shape}")
print(f"Datatype of tensor: {tensor.dtype}")
print(f"Device tensor is stored on: {tensor.device}")

2.4 ndarray与tensor互转

import numpy as np
import torch

print(np.__version__)
print(torch.__version__)

print("tensor转ndarray")
a = torch.ones(5)
print(type(a))
b = a.numpy()
print(type(b))

print("ndarray转tensor")
a1 = np.ones(5)
print(type(a1))
b2 = torch.from_numpy(a1)
print(type(b2))

2.5 索引、切片、变形、聚合、矩阵拼接、切割、转置

import torch
import numpy as np

# 随机生成0-10的形状为4行5列的tensor
t = torch.randint(0, 10, (4, 5))
print(t)

# 查看形状
print(t.shape)

# 一 索引和切片
# 取第0行0列的数据
print(t[0, 0])

# 取第0行的数据
print(t[0])

# 取中间两行的数据
print(t[1:3])

# 取中间两列的数据
print(t[:, 1:3])

# 取中间两行和两列的数据
print(t[1:3, 1:3])

# 增加维度 变为 (4,5,1)
# 方法一
print(t.reshape(4, 5, 1))
# 方法二 None表示占个位置
print(t[:, :, None])
# 方法三 ...代表前面所有维度,None代表最后一个维度
print(t[..., None])
# 方法四 在中间插一个维度 (4,1,5)
print(t[:, None, :])

# 去除空白的维度
print(t.reshape(4, 5, 1).squeeze())
print(t[:, None, :].squeeze())

# 拉伸维度
print(t.unsqueeze(dim=0).shape)

tensor = torch.tensor([[1], [2], [3]])
print(tensor.expand(3, 4))

# 模拟卷积神经网络只取宽度和高度
# 32个通道,224宽,224高,3个卷积核(batch_size,W,H,C)
# numpy操作
n = np.random.random((32, 224, 224, 3))
print(n[0, :, :, 0].shape)
# torch操作
t = torch.tensor(n)
print(t.shape)
print(t[0, :, :, 0].shape)

# 二、变形 reshape和view
t = torch.randint(0, 10, (4, 5))
print(t)
# print(t.reshape(5,4))
# print(t.view(5,4))

# 三、聚合
# pytorch聚合的时候不指定维度和numpy表现是一样的,回把所有的维度聚合成一个数字
print(t.sum())
# dim(dimension)指定维度
# 行相加
print(t.sum(dim=0))
# 保持聚合掉的维度继续存在
print(t.sum(dim=0,keepdim=True))
# 列相加
print(t.sum(dim=1))
# 保持聚合掉的维度继续存在
print(t.sum(dim=1,keepdim=True))
# 聚合函数 max,min,mean,median,sum,argmin(最小值索引),argmax(最大值索引),std(标准偏差)
# 第一个维度最大值得索引(每列的最大值的索引)
print(t.argmax(dim=0))
# 第二个维度最大值得索引(每行的最大值的索引)
print(t.argmax(dim=1))

# 四、矩阵的拼接
t1 = torch.randint(0,10,size=(4,5))
t2 = torch.randint(0,10,size=(4,5))
print(t1)
print(t2)
# pytorch也有dot,但是,仅限于向量(一维)之间的运算。(numpy中不限于一维)
# 一维向量的点乘
# t1[0,0]*t2[0,0] + t1[0,1]*t2[0,1] + t1[0,2]*t2[0,2] + t1[0,3]*t2[0,3] + t1[0,4]*t2[0,4]
print(t1[0].dot(t2[0]))
# 默认也是按照dim=0,增加了行数
print(torch.concat((t1,t2)))
# 同样增加行数
print(torch.vstack((t1,t2)))
# 增加列
print(torch.concat((t1,t2),dim=1))
# 同样增加列数
print(torch.hstack((t1,t2)))

# 五、切割
print("--------------切割----------------")
t = torch.randint(0,10,size=(4,5))
print(t)
# 1+2+1=4,必须等于维度
print(t.split([1,2,1]))

# 六、转置
# 0维度和1维度互换
print(torch.permute(t,[1,0]).shape)
print(t.transpose(1,0).shape)

3 pytorch自动微分

  • 自动微分其实就是求导
import torch

# requires_grad=True 表示对x求导
x = torch.ones(1, 1, requires_grad=True)

# 写一个计算公式,倒数是2
y = 2 * x + 2

# 反向传播求导
y.backward()

# 这里也是2
print(x.grad)

4 线性回归

from torch import nn, optim, tensor

# y = 2*x + 1
X = tensor([[1.0], [2.0], [3.0], [4.0]])
Y = tensor([[3.0], [5.0], [7.0], [9.0]])

# 训练模型(线性),可以获得预测结果。
model = nn.Linear(1, 1)

# 定义损失函数(均方误差损失(Mean Square Error Loss)),传入实际值和预测值,就可以获得损失函数。
# 这是常用于回归问题的损失函数
loss_fn = nn.MSELoss()

# 需要更新的参数-这里是一个生成器,可以节约内存
# model.parameters()

# 优化器(训练器) lr是学习率,可以梯度清零和更新参数。
optimizer = optim.SGD(model.parameters(), 0.001)

# 训练
for epoch in range(1000):
    for x, y in zip(X, Y):
        # 获得预测结果
        y_pred = model(x)
        # 获得损失函数,使用损失函数做反向传播
        loss = loss_fn(y, y_pred)
        # 梯度清零
        optimizer.zero_grad()
        # 反向传播
        loss.backward()
        # 更新参数
        optimizer.step()

# w的值
weight = model.weight
print(weight)
# b的值
bias = model.bias
print(bias)

5 分类

5.1 写法一

import torch
from torch import nn,float

# 1代表基数 2代表偶数
data = [[1, 3, 5, 7, 9, 1],
        [2, 4, 6, 8, 10, 0],
        [11, 13, 15, 17, 19, 1],
        [12, 14, 16, 18, 20, 0],
        [21, 23, 25, 27, 29, 1],
        [22, 24, 26, 28, 30, 0],
        [31, 33, 35, 37, 39, 1],
        [32, 34, 36, 38, 40, 0],
        [41, 43, 45, 47, 49, 1],
        [42, 44, 46, 48, 50, 0], ]
t_data = torch.tensor(data,dtype=float)

# 行都要,列不要最后一行
X = t_data[:, :-1]
print(type(X))
print(X)
# 行都要,列只要最后一样
Y = t_data[:, -1]
Y = Y.reshape(-1, 1)
print(type(X))
print(Y)

# 查看特征数,也就是有多少列
print(X.shape)

# 建一个两层的模型
model = nn.Sequential(
    # 输入是5个特征(X的列数),输出50个神经元
    nn.Linear(5, 50),
    # 输如的神经元数就是上一层的输出50个,输出就是Y的列数
    nn.Linear(50, 1),
    # 激活函数(分类),将这个输出转化为一个在0和1之间的值。这个值可以看作是模型对输入数据的预测结果。
    nn.Sigmoid()
)

state_dict = model.state_dict()
print(state_dict)

# 二分类交叉熵(适用于二分类问题的一种损失函数)
loss_fn = nn.BCELoss()

# 优化器-梯度下降的优化算法,传入模型的参数为权重(w)和偏执(b),学习率为0.001
optim_sgd = torch.optim.SGD(model.parameters(), 0.001)

# 正常神经网络是一批一批跑的
batch_size = 2
# 整除 根据批次大小计算步长,一共10行数据,每次跑两行,正好步长是5
steps = 10 // 2

# 开始训练
# 训练100次
for epoch in range(100):
    # 防止数据量过大,按照步长跑,把大数据切成一段一段的跑。
    for batch in range(steps):
        # 每一批的起始位置
        start = batch * batch_size
        end = start + batch_size
        x = X[start:end]
        y = Y[start:end]

        # 拿到预测结果
        y_pred = model(x)
        # 获取损失函数
        loss = loss_fn(y_pred, y)
        # 梯度清零
        optim_sgd.zero_grad()
        # 反向传播
        loss.backward()
        # 更新
        optim_sgd.step()

# 查看权重
print(model.state_dict())

# 计算准确率
# >=0.5 就是正样本
acc_rate = ((model(X).data.numpy() >= 0.5) == Y.numpy()).mean()

5.2 写法二

import torch
from torch import nn, float
import numpy as np

# 1代表基数 2代表偶数
data = [[1, 1],
        [2, 0],
        [11, 1],
        [12, 0],
        [21, 1],
        [22, 0],
        [31, 1],
        [32, 0],
        [41, 1],
        [42, 0], ]
t_data = torch.tensor(data, dtype=float)

# 行都要,列不要最后一行
X = t_data[:, :-1]

# 行都要,列只要最后一样
Y = t_data[:, -1]
Y = Y.reshape(-1, 1)

# 查看特征数,也就是有多少列
print(X.shape)


class DemoModl(nn.Module):
    def __init__(self):
        super().__init__()
        # 定义网络中会用到的东西
        # 输入是5个特征(X的列数),输出50个神经元
        self.lin_1 = nn.Linear(1, 50)
        # 输如的神经元数就是上一层的输出50个,输出就是Y的列数
        self.lin_2 = nn.Linear(50, 1)
        # 激活函数(分类),将这个输出转化为一个在0和1之间的值。这个值可以看作是模型对输入数据的预测结果。
        self.sigmod = nn.Sigmoid()
        # 拐弯函数
        self.activate = nn.ReLU()

    def forward(self, input):
        # forward中写前向传播
        x = self.lin_1(input)
        x = self.activate(x)
        x = self.lin_2(x)
        x = self.sigmod(x)
        return x


# 学习率
lr = 0.001

# 获取模型函数
def get_model():
    model = DemoModl()
    # 返回模型和优化器
    return model, torch.optim.Adam(model.parameters(),lr=lr)

# 损失函数
loss_fn = nn.BCELoss()

# 获取模型和优化器
model,opt = get_model()
# 超参数初始化
batch_size = 2
steps = 10 // 2

# 训练100次
for epoch in range(1000):
    # 防止数据量过大,按照步长跑,把大数据切成一段一段的跑。
    for batch in range(steps):
        # 每一批的起始位置
        start = batch * batch_size
        end = start + batch_size
        x = X[start:end]
        y = Y[start:end]

        # 拿到预测结果
        y_pred = model(x)
        # 获取损失函数
        loss = loss_fn(y_pred, y)
        # 梯度清零
        opt.zero_grad()
        # 反向传播
        loss.backward()
        # 更新
        opt.step()

print('loss=========',loss_fn(model(X),Y))

acc_rate = ((model(X).data.numpy() >= 0.5) == Y.numpy()).mean()
print(acc_rate)

print(np.unique(model(X).data.numpy()))

6 CNN手写数字识别

import matplotlib.pyplot as plt
import torch
from torchvision import datasets, transforms
from torch import nn
import torch.nn.functional as F
from tqdm import tqdm

# ToTensor有三个作用
# 1、把数据转化为tensor
# 2、把数据转化成0到1之间的小数
# 3、会把图片你的通道数放到第一个维度
transformation = transforms.Compose([transforms.ToTensor(), ])

# 训练数据
train_ds = datasets.MNIST(root="./../data/", train=True, download=True, transform=transformation)
print(f"训练数据条数{len(train_ds)}")
# 测试数据
test_ds = datasets.MNIST(root="./../data/", train=False, download=True, transform=transformation)
print(f"测试数据条数{len(test_ds)}")

x, y = train_ds[0]
print(f"y值是{y}")
# 对图片进行数据处理
print(x.shape)
# squeeze变成二维的(1, 28, 28)-->(28,28)
plt.imshow(x.squeeze().numpy(), cmap='gray')
# 显示图片  图片显示也是5
plt.show()

# 变成data loadr
train_dl = torch.utils.data.DataLoader(train_ds, batch_size=64, shuffle=True)
test_dl = torch.utils.data.DataLoader(test_ds, batch_size=256)


# 创建模型
class Model(nn.Module):
    def __init__(self):
        super().__init__()
        # 写init按照模型结果顺序去写
        # 黑白图片,输入通道为1,输出通道为32,卷积核3*3
        self.conv1 = nn.Conv2d(1, 32, 3)  # in: 64(batch_size),1(黑白),28(width),28(height) -> out: 64,32,26,26
        # 池化 卷积2*2,步长2
        self.pool = nn.MaxPool2d(2, 2)  # out: 64,32,13,13
        # 第二个卷积核
        self.conv2 = nn.Conv2d(32, 64,
                               3)  # out: 64,64,11,11 -> maxpool: 64,64,5,5   (W(宽)-F(核数)+1)/S(步长) = 11-3+1/2=5.5(向上取整)
        # 全链接
        self.linear_1 = nn.Linear(64 * 5 * 5, 256)
        self.linear_2 = nn.Linear(256, 10)
        # 将二维或三维的张量展平为一维的向量。
        # 例如,如果你有一个 2D 张量,其形状为 [batch_size, height * width],那么使用 nn.Flatten() 后,它将变成一个 1D 张量,形状为 [batch_size, height * width]。
        # 这个函数通常在将卷积层的输出送入全连接层或其他需要将数据展平为一维向量的层之前使用
        self.flatten = nn.Flatten()

    def forward(self, input1):
        # conv1卷积 + relu激活(引入非线性,解决梯度消失,提高模型性能)
        x = F.relu(self.conv1(input1))
        # 池化 (降低特征图的维度,减少信息冗余,提高训练速度,提高所提取特征的鲁棒性)
        x = self.pool(x)
        # 卷积
        x = F.relu(self.conv2(x))
        # 池化
        x = self.pool(x)
        # 批次保持不变,转变为一维向量 ,变成二维 [batch_size, height * width]
        x = self.flatten(x)
        # 全连接
        x = F.relu(self.linear_1(x))
        x = self.linear_2(x)
        return x


# 创建模型
model = Model()
# 损失函数(多分类的损失函数)
loss_fn = torch.nn.CrossEntropyLoss()
# 优化器
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)


def train_one_epoch(epoch, model, train_dl, test_dl):
    correct = 0
    total = 0
    running_loss = 0
    for x, y in tqdm(train_dl,desc="训练"):
        # 预测值
        y_pred = model(x)
        # 损失
        loss = loss_fn(y_pred, y)
        # 梯度清零
        optimizer.zero_grad()
        # 反向传播
        loss.backward()
        # 调整
        optimizer.step()

        # 计算一下训练的损失和准确率
        with torch.no_grad():
            y_ = torch.argmax(y_pred, dim=1)
            correct += (y_ == y).sum().item()
            total += y.size(0)
            running_loss += loss.item()

    epoch_loss = running_loss / len(train_dl.dataset)
    epoch_acc = correct / total

    # 测试部分代码
    test_correct = 0
    test_total = 0
    test_running_loss = 0

    with torch.no_grad():
        for x, y in tqdm(test_dl,desc="测试"):
            # 预测值
            y_pred = model(x)
            # 损失
            loss = loss_fn(y_pred, y)
            y_ = torch.argmax(y_pred, dim=1)
            test_correct += (y_ == y).sum().item()
            test_total += y.size(0)
            test_running_loss += loss.item()

    test_epoch_loss = test_running_loss / len(test_dl.dataset)
    test_epoch_acc = test_correct / test_total

    print("epoch===", epoch,"loss===", round(epoch_loss, 3),"accuracy===", round(epoch_acc, 3),"test_loss===", round(test_epoch_loss, 3),"test_accuracy===", round(test_epoch_acc, 3))
    return epoch_loss, epoch_acc, test_epoch_loss, test_epoch_acc


epochs = 20
train_loss = []
train_acc = []
test_loss = []
test_acc = []

for epoch in range(epochs):
    epoch_loss, epoch_acc, test_epoch_loss, test_epoch_acc = train_one_epoch(epoch, model, train_dl, test_dl)
    train_loss.append(epoch_loss)
    train_acc.append(epoch_acc)
    test_loss.append(test_epoch_loss)
    test_acc.append(test_epoch_acc)

7 自定义数据集

自定义数据集要求实现两个方法

  • _len_返回数据集的长度
  • _getitem_返回数据集中的一项内容
  • 自定义的数据集需要返回图片,以及图片对应的标记
from torchvision import transforms
import torch.nn.functional as F
import torch
from torch import nn
import torchvision
import os
import numpy as np
from sklearn.model_selection import train_test_split
from PIL import Image
import matplotlib.pyplot as plt

# 自定义的数据集的图片标记
species = ["男", "女"]
# 使用字典推导式,建立类别与索引之间的映射关系
species_to_idx = dict((i, c) for i, c in enumerate(species))
print(species_to_idx)
# 获取指定路径下的文件名称
filenames = os.listdir("./../dataset")
print(filenames)

# 生成所有图片的label
all_labels = []
for img in filenames:
    for i, c in enumerate(species):
        # 如果包含label
        if c in img:
            all_labels.append(i)
print(all_labels)

# 打乱数据
index = np.random.permutation(len(all_labels))
filenames = np.array(filenames)[index]
all_labels = np.array(all_labels)[index]
print(filenames)
print(all_labels)

# (方法一)手动划分训练集和测试集 (80%为训练集20%为测试集)
# split = int((len(filenames) * 0.8))
# print(split)
# 训练集
# train_imgs = filenames[:split]
# train_labels = all_labels[:split]
# print(train_imgs)
# 测试集
# test_imgs = filenames[split:]
# test_labels = all_labels[split:]
# print(test_imgs)
# (方法二) 使用train_test_split(参数一:X,参数二:Y,参数三:分割值)
x_train, x_test, y_train, y_test = train_test_split(filenames, all_labels, test_size=0.25)
print(x_train, x_test, y_train, y_test)

# 对图片的操作
transform = transforms.Compose([
    transforms.Resize((96, 96)),
    transforms.ToTensor(),
    # 正则化
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])


class MyDataset(torch.utils.data.Dataset):
    def __init__(self, img_paths, labels, transform):
        # super.__init__()
        self.imgs = img_paths
        self.labels = labels
        # transform处理的图片要求是正常的颜色空间即RGB颜色空间的图片
        self.transforms = transform

    def __getitem__(self, index):
        # 返回指定index的样本
        base_dir = "./../dataset"
        img_path = os.path.join(base_dir, self.imgs[index])
        label = self.labels[index]

        # 通过路径获取图像(类型RGB)
        img = Image.open(img_path)
        # 检查一下图片的通道数

        # 自定义数据集的时候,需要对图片做一些检查.有可能读进来的图片,不是符合要求.
        # #检查一下图片的通道数
        if np.array(img).shape[-1] == 3:
            data = self.transforms(img)
            return data, label
        else:
            # 图片有问题.去访问下一张图片
            # return self.__getitem__(index + 1)
            return None
        # if np.array(img).shape[-1] != 3:
        #     print("图片有损坏")
        #     print(img_path)
        # 对图片处理(变成tensor,变换大小,正则化)
        # data = self.transforms(img)
        # print(data)
        # print(data.shape)
        # print(label)
        # return data, label

    def __len__(self):
        # 返回样本长度
        return len(self.imgs)

    # collate_fn是个独立的函数,一般写成静态函数,放到自定义的dataset类中
    # 实例函数,类函数,静态函数
    @staticmethod
    def collate_fn(batch):
        # batch是个列表,长度是batch_size
        # 列表里面每一个元素是一个元组(x,y)
        # [(x1,y1),(x2,y2)... (xn,yn)]
        # 需要把所有的x取出来,拼到一起,需要把所有的y取出来,拼接到一起.#[(x1, y1).. None,..(xn,yn)]
        # 对batch中的None做了过滤.
        batch = [sample for sample in batch if sample is not None]
        from torch.utils.data.dataloader import default_collate
        return default_collate(batch)


train_ds = MyDataset(x_train, y_train, transform)
test_ds = MyDataset(x_test, y_test, transform)
# img, label = train_ds[0]

# 查看图片是否正确,permute变换维度,从(3,96,96)-->(96,96,3)
# plt.imshow(img.permute(1, 2, 0).numpy())
# plt.show()

# 将数据集放到加载器中
train_dl = torch.utils.data.DataLoader(train_ds, batch_size=1, shuffle=True, drop_last=True,
                                       collate_fn=MyDataset.collate_fn)
test_dl = torch.utils.data.DataLoader(test_ds, batch_size=1, shuffle=True, drop_last=True,
                                      collate_fn=MyDataset.collate_fn)

for x, y in train_dl:
    print(x.shape, y.shape)

8 学习迁移

  • 这里利用了上面的自定义数据集
from numpy.compat import long
from torchvision import transforms
import torch.nn.functional as F
import torch
from torch import nn
import torchvision
import os
import numpy as np
from sklearn.model_selection import train_test_split
from PIL import Image
import matplotlib.pyplot as plt
from tqdm import tqdm

# 自定义的数据集的图片标记
species = ["nan", "nv"]
# 使用字典推导式,建立类别与索引之间的映射关系
species_to_idx = dict((i, c) for i, c in enumerate(species))
print(species_to_idx)
# 获取指定路径下的文件名称
filenames = os.listdir("./../dataset")
print(filenames)

# 生成所有图片的label
all_labels = []
for img in filenames:
    for i, c in enumerate(species):
        # 如果包含label
        if c in img:
            all_labels.append(i)
print(all_labels)

# 打乱数据
index = np.random.permutation(len(all_labels))
filenames = np.array(filenames)[index]
all_labels = np.array(all_labels)[index]
print(filenames)
print(all_labels)

# (方法一)手动划分训练集和测试集 (80%为训练集20%为测试集)
# split = int((len(filenames) * 0.8))
# print(split)
# 训练集
# train_imgs = filenames[:split]
# train_labels = all_labels[:split]
# print(train_imgs)
# 测试集
# test_imgs = filenames[split:]
# test_labels = all_labels[split:]
# print(test_imgs)
# (方法二) 使用train_test_split(参数一:X,参数二:Y,参数三:分割值)
x_train, x_test, y_train, y_test = train_test_split(filenames, all_labels, test_size=0.25)
print(x_train, x_test, y_train, y_test)

# 对图片的操作
transform = transforms.Compose([
    transforms.Resize((96, 96)),
    transforms.ToTensor(),
    # 正则化
    transforms.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])
])


class MyDataset(torch.utils.data.Dataset):
    def __init__(self, img_paths, labels, transform):
        # super.__init__()
        self.imgs = img_paths
        self.labels = labels
        # transform处理的图片要求是正常的颜色空间即RGB颜色空间的图片
        self.transforms = transform

    def __getitem__(self, index):
        # 返回指定index的样本
        base_dir = "./../dataset"
        img_path = os.path.join(base_dir, self.imgs[index])
        label = self.labels[index]

        # 通过路径获取图像(类型RGB)
        img = Image.open(img_path)
        # 检查一下图片的通道数

        # 自定义数据集的时候,需要对图片做一些检查.有可能读进来的图片,不是符合要求.
        # #检查一下图片的通道数
        if np.array(img).shape[-1] == 3:
            data = self.transforms(img)
            # 转换为int64类型
            label = label.astype(np.int64)
            return data, label
        else:
            # 图片有问题.去访问下一张图片
            return self.__getitem__(index + 1)
            # return None
        # if np.array(img).shape[-1] != 3:
        #     print("图片有损坏")
        #     print(img_path)
        # 对图片处理(变成tensor,变换大小,正则化)
        # data = self.transforms(img)
        # print(data)
        # print(data.shape)
        # print(label)
        # return data, label

    def __len__(self):
        # 返回样本长度
        return len(self.imgs)

    # collate_fn是个独立的函数,一般写成静态函数,放到自定义的dataset类中
    # 实例函数,类函数,静态函数
    @staticmethod
    def collate_fn(batch):
        # batch是个列表,长度是batch_size
        # 列表里面每一个元素是一个元组(x,y)
        # [(x1,y1),(x2,y2)... (xn,yn)]
        # 需要把所有的x取出来,拼到一起,需要把所有的y取出来,拼接到一起.#[(x1, y1).. None,..(xn,yn)]
        # 对batch中的None做了过滤.
        batch = [sample for sample in batch if sample is not None]
        from torch.utils.data.dataloader import default_collate
        return default_collate(batch)


train_ds = MyDataset(x_train, y_train, transform)
test_ds = MyDataset(x_test, y_test, transform)
# img, label = train_ds[0]

# 查看图片是否正确,permute变换维度,从(3,96,96)-->(96,96,3)
# plt.imshow(img.permute(1, 2, 0).numpy())
# plt.show()

# 将数据集放到加载器中
train_dl = torch.utils.data.DataLoader(train_ds, batch_size=1, shuffle=True, drop_last=True)
test_dl = torch.utils.data.DataLoader(test_ds, batch_size=1, shuffle=True, drop_last=True)

for x, y in train_dl:
    print(x.shape, y.shape)

# pytorch放模型的地方
# pretained=True,代表使用迁移学习,progress=True,下载模型(进度条)
# model = torchvision.models.resnet34(pretrained=True, progress=True)
model = torchvision.models.vgg16(pretrained=True, progress=True)
print(model)
# 把features层的参数全部固定住.
for param in model.features.parameters():
    param.requires_grad = False
# 改造最后一层,输入特征还是原来的,输出特征为2(男,女)
model.classifier[-1] = nn.Linear(model.classifier[-1].in_features, 2)
# 另一种改法,这是针对1000个类别的,不适用
# model.classifier[-1].out_features = 4

# 创建损失函数
loss_fn = torch.nn.CrossEntropyLoss()
# 优化器
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)


# 训练
def train_one_epoch(epoch, model, train_dl, test_dl):
    correct = 0
    total = 0
    running_loss = 0
    for x, y in tqdm(train_dl, desc="训练"):
        # 预测值
        y_pred = model(x)
        # 损失
        loss = loss_fn(y_pred, y)
        # 梯度清零
        optimizer.zero_grad()
        # 反向传播
        loss.backward()
        # 调整
        optimizer.step()

        # 计算一下训练的损失和准确率
        with torch.no_grad():
            y_ = torch.argmax(y_pred, dim=1)
            correct += (y_ == y).sum().item()
            total += y.size(0)
            running_loss += loss.item()

    epoch_loss = running_loss / len(train_dl.dataset)
    epoch_acc = correct / total

    # 测试部分代码
    test_correct = 0
    test_total = 0
    test_running_loss = 0

    with torch.no_grad():
        for x, y in tqdm(test_dl, desc="测试"):
            # 预测值
            y_pred = model(x)
            # 损失
            loss = loss_fn(y_pred, y)
            y_ = torch.argmax(y_pred, dim=1)
            test_correct += (y_ == y).sum().item()
            test_total += y.size(0)
            test_running_loss += loss.item()

    test_epoch_loss = test_running_loss / len(test_dl.dataset)
    test_epoch_acc = test_correct / test_total

    print("epoch===", epoch, "loss===", round(epoch_loss, 3), "accuracy===", round(epoch_acc, 3), "test_loss===",
          round(test_epoch_loss, 3), "test_accuracy===", round(test_epoch_acc, 3))
    return epoch_loss, epoch_acc, test_epoch_loss, test_epoch_acc


epochs = 20
train_loss = []
train_acc = []
test_loss = []
test_acc = []

for epoch in range(epochs):
    epoch_loss, epoch_acc, test_epoch_loss, test_epoch_acc = train_one_epoch(epoch, model, train_dl, test_dl)
    train_loss.append(epoch_loss)
    train_acc.append(epoch_acc)
    test_loss.append(test_epoch_loss)
    test_acc.append(test_epoch_acc)

# 查看参数
params = model.state_dict()
print(params)

9 保存模型和加载模型

9.1 保存模型

# 保存模型
save_path = "./../model/my_vgg16.pth"
torch.save(model.state_dict(), save_path)

9.2 加载模型

# 加载模型
# 得先把网络结构定义好.
# 然后把网络的实例对象创建出来.
# pretrained=False,表示只要模型,不要参数,不会下载
vgg16 = torchvision.models.vgg16(pretrained=False, progress=True)
#没有训练过的参数
vgg16.state_dict()
# 修改最后一层
vgg16.classifier[-1] = nn.Linear(vgg16.classifier[-1].in_features,2)
# 加载自己的模型参数
vgg16.load_state_dict(torch.load('./../model/my_vgg16.pth'))
print("已完成")

9.3 保存最佳模型

import copy

# 最佳参数
best_model_weight = model.state_dict()
epochs = 2
train_loss = []
train_acc = []
test_loss = []
test_acc = []
# 最佳准确率
best_acc = 0.0

for epoch in range(epochs):
    epoch_loss, epoch_acc, test_epoch_loss, test_epoch_acc = train_one_epoch(epoch, model, train_dl, test_dl)
    train_loss.append(epoch_loss)
    train_acc.append(epoch_acc)
    test_loss.append(test_epoch_loss)
    test_acc.append(test_epoch_acc)

    if test_epoch_acc > best_acc:
        best_acc = test_epoch_acc
        # 深拷贝
        best_model_weight = copy.deepcopy(model.state_dict())
        
model.load_state_dict(best_model_weight)     

9.4 保存完整模型

#完整模型包括模型结构和参数
torch. save (model,'./../model/whole_model.pth')

9.5 加载完整模型

#加载完整模型
model2 = torch.load('./../model/whole_model.pth')

10 数据增强

transforms.CenterCrop 中间位置裁剪
transforms.RandomCrop 随机位置裁剪
transforms.RandomHorizontalFlip 水平翻转
transforms.RandomVerticalFlip垂直翻转
transforms. RandomRotation旋转
transforms.ColorJitter(brigtness,contrast对比度,saturation饱和度, hue色调)(调整颜色的相关属性.transforms.RandomGrayscale随机灰度化)

11 学习率衰减

# 学习率衰减
from torch.optim import lr_scheduler
# setp_size步长为7,每7步学习率减少0.1
step_lr_scheduler = lr_scheduler.StepLR(optimizer,step_size=7,gamma=0.1)

# 在每批循环后添加
step_lr_scheduler.step()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逻辑峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值