【花书笔记|PyTorch版】手动学深度学习5: 线性神经模型:代码部分(下)

3.5 图像分类数据集

这章主要是讲对数据的一些处理和准备

数据集:使用类似但更复杂的Fashion-MNIST数据集

【花书笔记|PyTorch版】手动学深度学习5: 线性神经模型:代码部分(下)_第1张图片

%matplotlib inline
import torch

# torchvision:pytorch视觉实现的一个库
import torchvision
#from torchvision import datasets
from torch.utils import data

# transforms对数据进行操作的模组
from torchvision import transforms
from d2l import torch as d2l

# 这个函数也是画图的函数
d2l.use_svg_display()

① 将数据从torch库中下载下来,并转变成pytorch的数据类型torchvision.datasets

torchvision:https://blog.csdn.net/wohu1104/article/details/107743290

知识点1; 我们使用是配套的

  • 如果导入的是import torchvision,那么使用你面的函数就是torchvision.datasets.XXX;
  • 如果导入的是from torchvision import datasets,那么使用你面的函数就是datasets.XXX;

同理torchvision下的主要函数transform

# 通过ToTensor实例将图像数据从PIL类型变换成float32格式,
# 并除以255使得所有像素的数值均在0到1之间
trans = transforms.ToTensor()

# 准备训练集 和测试机
# 训练集下载的地方;是否是训练集;下载时候转变成32位浮点数格式;是否下载
mnist_train = torchvision.datasets.FashionMNIST(root="../data", train=True, transform=trans, download=True)
mnist_test = torchvision.datasets.FashionMNIST(root="../data", train=False, transform=trans, download=True)
  • 【观察数据】

Fashion-MNIST由10个类别的图像组成, 每个类别由训练数据集(train dataset)中的6000张图像 和测试数据集(test dataset)中的1000张图像组成。 因此,训练集和测试集分别包含60000和10000张图像。 测试数据集不会用于训练,只用于评估模型性能。

# 看看训练集 测试集长度
len(mnist_train), len(mnist_test)
(60000, 10000)

个输入图像的高度和宽度均为28像素。 数据集由灰度图像组成,其通道数为1。 为了简洁起见,本书将高度h像素、宽度w像素图像的形状记为或(h,w)

mnist_train[0][0].shape
torch.Size([1, 28, 28])
  • mnist_train[0][0]第一个是取第几张照片,范围是0~59999;60000会报错,因为一共只有60000张图
  • 第二个取值0图片数据1标签数据

下面是第1张图属于第9类;第60000张图属于第5

mnist_train[0][1]
9
mnist_train[59999][1]
5

② 处理分类,用get_fashion_mnist_labels将分类数字0-9与具体名称一一对应

Fashion-MNIST中包含的10个类别,分别为t-shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。 以下函数用于在数字标签索引及其文本名称之间进行转换。

def get_fashion_mnist_labels(labels):  #@save
    """返回Fashion-MNIST数据集的文本标签"""
    text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
                   'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
    return [text_labels[int(i)] for i in labels]

③ 名称有了,用show_images 来呈现图像

参数:图片 展示成几行几列 标题默认没有 规模(尺寸大小)

def show_images(imgs, num_rows, num_cols, titles=None, scale=1.5):  #@save

    # 设置图片尺寸:scale就是每张图的大小(基数),这里是1.5;改大图像就变大
    figsize = (num_cols * scale, num_rows * scale)
    
     # 这里的 _ 表示忽略不使用的变量、即fig;
     # d2l.plt.subplots()把多张图拼成一张,其中figsize把上面尺寸传下来
    _, axes = d2l.plt.subplots(num_rows, num_cols, figsize=figsize)
    
    #把一张图的数据拉直(成):在用plt.subplots画多个子图中,ax = ax.flatten()将ax由n*m的Axes组展平成1*nm的Axes组
    # 这一步就是 不用在二维数组来定位第几个图了 直接第几个图,会自动落位?
    axes = axes.flatten()
    #print("axes",axes[1][0])
    
    for i, (ax, img) in enumerate(zip(axes, imgs)):
        
        
        # i:第几个图
        # imgs:是一个图的数据,是个28*28的二维数组

        # 判断传入的图片是否为张量
        if torch.is_tensor(img):
            # 图片张量
            ax.imshow(img.numpy())
        else:
            # PIL图片
            ax.imshow(img)
            
            # 设取消横纵坐标上的刻度(横、纵轴均为28)
        ax.axes.get_xaxis().set_visible(False)
        ax.axes.get_yaxis().set_visible(False)
        if titles:
            ax.set_title(titles[i])
    return axes
  • 把多张图拼成一张图的函数:fig, ax = plt.subplots():https://www.cnblogs.com/komean/p/10670619.html

    fig, ax = plt.subplots(1,3): 其中参数1和3分别代表子图的行数和列数,一共有 1x3 个子图像。函数返回一个figure图像和子图ax的array列表。

    fig, ax = plt.subplots(1,3,1): 最后一个参数1代表第一个子图。
    如果想要设置子图的宽度和高度可以在函数内加入figsize值

    fig, ax = plt.subplots(1,3,figsize=(15,7)): 这样就会有1行3个15x7大小的子图。【本文用法】

  • axes = axes.flatten():https://blog.csdn.net/weixin_38314865/article/details/84785141

  • zip():https://blog.csdn.net/lanmy_dl/article/details/124216431

  • enumerate()遍历: https://www.runoob.com/python/python-func-enumerate.html

  • 结合使用;https://blog.csdn.net/weixin_43408110/article/details/87731547

④ 来设计上一步的X:imagsy:titles

# 取出X y 用next(iter)搞了第一组数据,数据一组18个
X, y = next(iter(data.DataLoader(mnist_train, batch_size=18)))

#我们一个`X:torch.Size([18, 1, 28, 28])`传进来18张照片 每张[1*28*28]
X.shape

torch.Size([18, 1, 28, 28])
 # 不要色彩通道了 直接这组张数 和 每张照片的尺寸;一排9张 一o共2排
show_images(X.reshape(18,28, 28), 2,9, titles=get_fashion_mnist_labels(y));

【花书笔记|PyTorch版】手动学深度学习5: 线性神经模型:代码部分(下)_第2张图片

3.5.2 读取小批量

# 准备训练数据
batch_size = 256

def get_dataloader_workers():  #@save
    """使用4个进程来读取数据"""
    return 4

# batch_size一组256个,shuffle随机取,几个进程做
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
                             num_workers=get_dataloader_workers())
# 测试了一下训练的时间
timer = d2l.Timer()
for X, y in train_iter:
    continue
f'{timer.stop():.2f} sec'
'5.81 sec'

3.5.3. 整合所有组件

# resize 就是我们输入照片是28*28,如果想把尺寸变大 用到resize

def load_data_fashion_mnist(batch_size, resize=None):  #@save
    """下载Fashion-MNIST数据集,然后将其加载到内存中"""
    trans = [transforms.ToTensor()]
    if resize:
        trans.insert(0, transforms.Resize(resize))
    trans = transforms.Compose(trans)
    mnist_train = torchvision.datasets.FashionMNIST(
        root="../data", train=True, transform=trans, download=True)
    mnist_test = torchvision.datasets.FashionMNIST(
        root="../data", train=False, transform=trans, download=True)
    return (data.DataLoader(mnist_train, batch_size, shuffle=True,
                            num_workers=get_dataloader_workers()),
            data.DataLoader(mnist_test, batch_size, shuffle=False,
                            num_workers=get_dataloader_workers()))

讲解了一下resize,原本图像是28*28 ;通过resize=64 变成了64*64
transforms.Resize(resize)

train_iter, test_iter = load_data_fashion_mnist(32, resize=64)
for X, y in train_iter:
    print(X.shape, X.dtype, y.shape, y.dtype)
    break
torch.Size([32, 1, 64, 64]) torch.float32 torch.Size([32]) torch.int64

3.6. softmax回归的从零开始实现

【花书笔记|PyTorch版】手动学深度学习5: 线性神经模型:代码部分(下)_第3张图片

import torch
from IPython import display
from d2l import torch as d2l


#设置训练集 和测试集迭代器 每组256个
# load_data_fashion_mnist这个函数 是上一章整合的一个函数,下载Fashion-MNIST数据集,打乱顺序
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)

3.6.1. 初始化模型参数

#始数据集中的每个样本都是的28*28图像。 在本节中,我们将展平每个图像,把它们看作长度为784的向量
# 10个分类 输出10
num_inputs = 784
num_outputs = 10


# 注意 W的形状size=(num_inputs, num_outputs) X是256*num_inputs W是num_inputs*10;b是1*10
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)

3.6.2. 定义softmax操作

  • 我们简要回顾一下sum运算符如何沿着张量中的特定维度工作

keepdim:保持原来的维度数,此题为2

0是结果1*n;1n*1;几就是哪个维度是1

X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
X.shape
X.sum(0, keepdim=True), X.sum(1, keepdim=True)
(tensor([[5., 7., 9.]]),
 tensor([[ 6.],
         [15.]]))
# keepdim=True 保留 行列两个维度
X.sum(0, keepdim=False), X.sum(1, keepdim=False)
(tensor([5., 7., 9.]), tensor([ 6., 15.]))
  • 定义softmax:
    s o f t m a x ( X ) i j = e x p ( X i j ) ∑ k e x p ( X i k ) softmax(X)_{ij}=\frac{exp(X_{ij})}{\sum_k exp(X_{ik})} softmax(X)ij=kexp(Xik)exp(Xij)
def softmax(X):
    #X是256*10
    X_exp = torch.exp(X)
    
    #X_exp.sum(1, keepdim=True) 就是256*1 也就是把10个分类结果exp加了起来
    partition = X_exp.sum(1, keepdim=True)
    
    #输出的是概率
    return X_exp / partition  # 这里应用了广播机制
X = torch.tensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])
X/X.sum(1, keepdim=True)
tensor([[0.1667, 0.3333, 0.5000],
        [0.2667, 0.3333, 0.4000]])
  • 这点我是举了一个例子 这里面有三个结果 每一行是一个数据 所以每一行加起来都是1

3.6.3. 定义模型

我们使用reshape函数将每张原始图像展平为向量

def net(X):
    return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)

3.6.4. 定义损失函数

然后使用y作为y_hat中概率的索引, 我们选择第一个样本中第一个类的概率和第二个样本中第三个类的概率。

y = torch.tensor([0, 2])
y_hat = torch.tensor([[0.1, 0.3, 0.6], [0.3, 0.2, 0.5]])
y_hat[[0, 1], y]
tensor([0.1000, 0.5000])

交叉熵: H ( y , y ^ ) = − y l o g y ^ H(y,\hat y)=- ylog{\hat y} H(yy^)=ylogy^

  • 因为有是个分类,所以就是【0,0,1,0,0】是哪类哪类是1
  • 原式子化简:假如真实y是第i类,交叉熵化简为;

交叉熵: H ( y , y ^ ) = − l o g y ^ i H(y,\hat y)=- log{\hat y_i} H(yy^)=logy^i

所以有了下面方法,y_hat[range(len(y_hat)), y]:y就是第几类(比如i),所以就是y_hat里拿出第i

所以,现在我们只需一行代码就可以实现交叉熵损失函数。

#交叉熵 所以有了下面方法,y就是第几类(比如i),所以就是y_hat里拿出
def cross_entropy(y_hat, y):
    print("ss:",y_hat[range(len(y_hat)), y])
    return - torch.log(y_hat[range(len(y_hat)), y])

cross_entropy(y_hat, y)
ss: tensor([0.1000, 0.5000])





tensor([2.3026, 0.6931])

len(y_hat)=2 那么就是有几列。
取0时 那么就是第一行

3.6.5. 分类精度

分 类 精 度 = 正 确 预 测 数 量 总 预 测 数 量 分类精度= \frac{正确预测数量}{总预测数量} =

def accuracy(y_hat, y):  #@save
    """计算预测正确的数量"""

    #张量是否大于1  且 张量第二个维度:分类大于1个
    # 就是看是否是矩阵
    if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
        
        #argmax获得每行中最大元素的索引(下标)
        y_hat = y_hat.argmax(axis=1)
        
        #预测第三类,第三类
    #print("yhat:",y_hat)
    # y_hat的数据类型转换为与y的数据类型一致
    # 这是一个判断:y_hat.type(y.dtype) == y 对是1 错是0
    cmp = y_hat.type(y.dtype) == y
    
    return float(cmp.type(y.dtype).sum())
 accuracy(y_hat, y) / len(y)
0.5

知识点

y_hat.shape,y_hat.shape[1]
(torch.Size([2, 3]), 3)

那么这里里面。len(y_hat.shape) 其实就相当于 len(y_hat) 或者y_hat.shape[0],指的列的数量

  • y_hat.argmax(axis=1):argmax获得每行中最大元素的索引(下标);

之所有是axis=1 可以理解为 mn最后形式是m1 ,那么存的这个数就是每行最大的

  • y_hat.type(y.dtype) == y:
    y_hat的数据类型转换为与y的数据类型一致;
    这是一个判断:y_hat.type(y.dtype) == y 对是1 错是0;
    它是按行来判断的

第一个样本的预测类别是2,这与实际标签0不一致。 第二个样本的预测类别是2,这与实际标签2一致。 因此,这两个样本的分类精度率为0.5。
同样,对于任意数据迭代器data_iter可访问的数据集, 我们可以评估在任意模型net的精度。

def evaluate_accuracy(net, data_iter):  #@save
    """计算在指定数据集上模型的精度"""
    if isinstance(net, torch.nn.Module):
        # 这里只计算 不调整参数
        net.eval()  # 将模型设置为评估模式
        
    # 我们在Accumulator实例中创建了2个变量, 分别用于存储正确预测的数量和预测的总数量
    metric = Accumulator(2)  # 正确预测数、预测总数
    
    # # 当我们遍历数据集时,两者都将随着时间的推移而累加
    with torch.no_grad():
        for X, y in data_iter:
            metric.add(accuracy(net(X), y), y.numel())
    return metric[0] / metric[1]
  • isinstance(object, type):如果指定的对象拥有指定的类型,则 isinstance() 函数返回 True,否则返回 False。
  • 这里就是net是不是torch.nn.Module类型,如果是,net.eval()
  • net.train()和net.eval():https://www.jianshu.com/p/5c6e67719678
  • y.numel()我们看y有多少个元素
class Accumulator:  #@save
    """在n个变量上累加"""
    def __init__(self, n):
        self.data = [0.0] * n

    def add(self, *args):
        self.data = [a + float(b) for a, b in zip(self.data, args)]

    def reset(self):
        self.data = [0.0] * len(self.data)

    def __getitem__(self, idx):
        return self.data[idx]
evaluate_accuracy(net, test_iter)
0.0947

3.6.6. 训练

# updater是更新模型参数的常用函数,它接受批量大小作为参数
def train_epoch_ch3(net, train_iter, loss, updater):  #@save
    """训练模型一个迭代周期(定义见第3章)"""
    # 将模型设置为训练模式
    if isinstance(net, torch.nn.Module):
        net.train()
        
    # 训练损失总和、训练准确度总和、样本数
    metric = Accumulator(3)
    for X, y in train_iter:
        # 计算梯度并更新参数 
        y_hat = net(X)
        l = loss(y_hat, y)
        
        
        if isinstance(updater, torch.optim.Optimizer):
            # 使用PyTorch内置的优化器和损失函数
            updater.zero_grad()
            l.mean().backward()
            updater.step()
        else:
            # 使用定制的优化器和损失函数
            l.sum().backward()
            updater(X.shape[0])
        metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
    # 返回训练损失和训练精度
    return metric[0] / metric[2], metric[1] / metric[2]
- 进行参数更新,所以要训练模式,拿出X,y X通过net(X)训练处y_hat
- 然后计算损失`l`,要么使用内置优化参数,要么使用定制的优化器和损失函数
class Animator:  #@save
    """在动画中绘制数据"""
    def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None,
                 ylim=None, xscale='linear', yscale='linear',
                 fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1,
                 figsize=(3.5, 2.5)):
        # 增量地绘制多条线
        if legend is None:
            legend = []
        d2l.use_svg_display()
        self.fig, self.axes = d2l.plt.subplots(nrows, ncols, figsize=figsize)
        if nrows * ncols == 1:
            self.axes = [self.axes, ]
        # 使用lambda函数捕获参数
        self.config_axes = lambda: d2l.set_axes(
            self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend)
        self.X, self.Y, self.fmts = None, None, fmts

    def add(self, x, y):
        # 向图表中添加多个数据点
        if not hasattr(y, "__len__"):
            y = [y]
        n = len(y)
        if not hasattr(x, "__len__"):
            x = [x] * n
        if not self.X:
            self.X = [[] for _ in range(n)]
        if not self.Y:
            self.Y = [[] for _ in range(n)]
        for i, (a, b) in enumerate(zip(x, y)):
            if a is not None and b is not None:
                self.X[i].append(a)
                self.Y[i].append(b)
        self.axes[0].cla()
        for x, y, fmt in zip(self.X, self.Y, self.fmts):
            self.axes[0].plot(x, y, fmt)
        self.config_axes()
        display.display(self.fig)
        display.clear_output(wait=True)
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):  #@save
    """训练模型(定义见第3章)"""
    animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
                        legend=['train loss', 'train acc', 'test acc'])
    for epoch in range(num_epochs):
        train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
        test_acc = evaluate_accuracy(net, test_iter)
        animator.add(epoch + 1, train_metrics + (test_acc,))
    train_loss, train_acc = train_metrics
    assert train_loss < 0.5, train_loss
    assert train_acc <= 1 and train_acc > 0.7, train_acc
    assert test_acc <= 1 and test_acc > 0.7, test_acc
lr = 0.1

def updater(batch_size):
    return d2l.sgd([W, b], lr, batch_size)
num_epochs = 10
train_ch3(net, train_iter, test_iter, cross_entropy, num_epochs, updater)

【花书笔记|PyTorch版】手动学深度学习5: 线性神经模型:代码部分(下)_第4张图片

3.6.7. 预测

def predict_ch3(net, test_iter, n=6):  #@save
    """预测标签(定义见第3章)"""
    for X, y in test_iter:
        break
    trues = d2l.get_fashion_mnist_labels(y)
    preds = d2l.get_fashion_mnist_labels(net(X).argmax(axis=1))
    titles = [true +'\n' + pred for true, pred in zip(trues, preds)]
    d2l.show_images(
        X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n])

predict_ch3(net, test_iter)

【花书笔记|PyTorch版】手动学深度学习5: 线性神经模型:代码部分(下)_第5张图片

3.7 softmax回归的简洁实现

导入需要的包,把训练数据和测试下载好准备好

import torch
from torch import nn
from d2l import torch as d2l

batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
train_iter.shape
---------------------------------------------------------------------------

AttributeError                            Traceback (most recent call last)

Input In [43], in ()
----> 1 train_iter.shape


AttributeError: 'DataLoader' object has no attribute 'shape'

我们发现DataLoader不支持用shape.钱么使用的X,y的for循环

3.7.1 初始化模型参数

# PyTorch不会隐式地调整输入的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整网络输入的形状
# 784=28*28 与输入图片大小有关
# nn.Linear线性网络,输入784,输出10

net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))

def init_weights(m):
    # 初始化权重
    if type(m) == nn.Linear:
        #以均值0和标准差0.01随机初始化权重
        nn.init.normal_(m.weight, std=0.01)

net.apply(init_weights);

传进来一个网络,判断是不是线性网络,如果是那么初始化参数

net是我们给写的神经网络定义的类实例。apply函数会递归地搜索网络内的所有module并把参数表示的函数应用到所有的module上。也就是说apply函数,会一层一层的去拜访Generator网络层。

3.7.2 实现SOFTMAX

loss = nn.CrossEntropyLoss(reduction='none')

交叉熵损失函数,默认情况,reduction = 'elementwise_mean'就是所有损失均值
reduction = 'sum':损失之和
`reduction = ‘None’:损失不做处理,输出就是一个和样本数相等的向量了

3.7.3 优化算法

trainer = torch.optim.SGD(net.parameters(), lr=0.1)

SGD就是梯度下降。lr是学习率

3.7.4 训练

num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)

【花书笔记|PyTorch版】手动学深度学习5: 线性神经模型:代码部分(下)_第6张图片

这里 可能已经会一个批量一个的进入训练了



你可能感兴趣的:(【花书笔记】手动学深度学习,python,深度学习,人工智能)