Title

Pytorch计算机视觉实战(更新中)

第一章 人工神经网络基础

1.1 人工智能与传统机器学习

学习心得:

传统机器学习(ML):需要专业的主题专家人工提取特征,并通过一个编写良好的算法来破译给定的特征,从而判断这幅图像中的内容。

输入-->人工提取特征-->特征-->具有浅层结构的分类器-->输出

当存在欺骗性的图片出现时可能会失效,我们需要创建能够精细分类多种类型的人为规则数量可能是指数级的,,因此传统方法在非常受限的环境中很有效。
我们可以将同样的思路扩展到任何领域比如:文本或结构化数据,在过去如果想通过编程解决现实世界的任务,那么必须理解关于输入数据的所有内容,并且需要编写尽可能多的规则来覆盖每个场景,不仅乏味,而且不能保证所有的新场景都会遵循上述规则。

然而,通过人工神经网络,神经网络提供了特征提取和决策分类的模块,几乎不需要手工提取特征,只需要标记数据(如:哪些是人,哪些不是人)和神经网络架构,这样就消除了传统技术(如何创造规则)带来的负担

输入-->特征学习+分类器(端到端选择)-->输出

但是在这里,我们需要提供大量样本,如:大量人和不含人的图片输入给模型,以便它学习特征

1.2 人工神经网络的构建模块

可以将人工神经网络看作一个数学函数,输入一个或者多个张量(权重),输出一个或者多个张量(权重)连接这些输入和输出的运算排列称为神经网络的架构,我们可以根据手头任务定制。

输入层:自变量的输入。

隐藏(中间)层:连接输入和输出层,并对输入数据进行转换,此外隐藏层包含节点, 用于将输入值修改为更高/更低维度的值,可以通过修改中间层节点的激活函数实现更复杂的表示。

输出层:输入变量后产生期望的值,树输出层节点数量取决于手头任务,及试图预测的是连续还是分类变量,若是连续的则输出层只有一个节点,若是n个分类则输出层就有n个节点。

激活函数:

深度学习指的是具有更多隐藏层

1.3 前向传播

在开始前使用随机权重初始化

  1. 计算隐藏层的值

  2. 进行非线性激活

    sigmoid = 1/(1 + e^-x)
    ReLU = x if x > 0 else 0
    tanh = (e^x - e-x)/(ex + e^-x)

  3. 估算输出层的值

  4. 计算与期望值对应的损失值
    计算连续变量的损失一般用MSE(均方误差的平方可以保证正误差和负误差不相互抵消)
    np.mean(np.square(pred - y))

    计算分类变量的损失一般用二元交叉熵损失(当只有两个类别的时候)
    -np.mean((y*np.log(p))+(1-y)*np.log(1-p))
    一般多分类用分类交叉熵
    (图)

数据集的最终损失是所有单个损失的平均值。

补:平均绝对误差
np.mean(np.abs(pred - y))
# 在预测输出小于1的时候,使用均方误差可以大大降低损失量
np.mean(np.square(pred - y))

前向传播(Numpy)

def feed_forward(inputs, outputs, weights):       
    pre_hidden = np.dot(inputs,weights[0])+ weights[1]
    hidden = 1/(1+np.exp(-pre_hidden))
    pred_out = np.dot(hidden, weights[2]) + weights[3]
    mean_squared_error = np.mean(np.square(pred_out - outputs))
    return mean_squared_error

1.4 反向传播

通过更新权重来减小误差的过程称为梯度下降

Numpy实现每次对一个参数少量更新,实现梯度下降

from copy import deepcopy
import numpy as np
import matplotlib.pyplot as plt

 # 更新权重
def update_weights(inputs, outputs, weights, lr):
    original_weights = deepcopy(weights)
    updated_weights = deepcopy(weights)
    original_loss = feed_forward(inputs, outputs, original_weights)
    # 先计算一次损失
    for i, layer in enumerate(original_weights):
        for index, weight in np.ndenumerate(layer):
            temp_weights = deepcopy(weights)
            temp_weights[i][index] += 0.0001 
            # 每次将权重和偏置的一个增加很小的量
            _loss_plus = feed_forward(inputs, outputs, temp_weights)
            # 再计算一次损失
            grad = (_loss_plus - original_loss)/(0.0001)
            # 计算梯度(参数值改变引起的损失的改变)
            updated_weights[i][index] -= grad*lr
            # 更新参数,使用学习率慢慢建立信任
    return updated_weights, original_loss

# 输入输出
x = np.array([[1,1]])
y = np.array([[0]])

# 参数随机初始化
W = [
np.array([[-0.0053, 0.3793],
          [-0.5820, -0.5204],
          [-0.2723, 0.1896]], dtype=np.float32).T, 
np.array([-0.0140, 0.5607, -0.0628], dtype=np.float32), 
np.array([[ 0.1528, -0.1745, -0.1135]], dtype=np.float32).T, 
np.array([-0.5516], dtype=np.float32)
]

# 绘图
losses = []
for epoch in range(100):
    W, loss = update_weights(x,y,W,0.01)
    losses.append(loss)
plt.plot(losses)
plt.title('Loss over increasing number of epochs')

批大小:当有成千上万的数据的时候,如果一起输入到网络计算损失收益不是很高,因此使用批大小进行模型训练

1.5 链式法则的反向传播

利用偏导数就可以仅仅通过利用前向传播计算出的损失来更新参数。

1.6 学习率的影响

当学习率很小的时候,权值向最优值的移动比较慢。
当学习率稍大的时候,权值先是震荡,然后快速向最优值收敛。
当学习率很大的时候,权值会达到一个很大的值,无法达到最优值。
(权值小于最优值的时候梯度为负,反之梯度为正)
一般来说学习率越小越好(0.0001-0.01)

第二章 Pytorch基础

2.1 Pytorch 张量

学习心得:

标量是0维张量
向量可以表示一维张量(轴0)
形状(4,)
二维矩阵表示二维张量(上到下轴0,左到右轴1)
形状(4,3)
三维维矩阵表示三维张量(上到下轴0,左到右轴1,外到内轴2)
形状(4,3,2)

初始化张量

import torch
x = torch.tensor([[1,2]])
y = torch.tensor([[1],[2]])

print(x.shape)
# torch.Size([1,2]) # one entity of two items
print(y.shape)
# torch.Size([2,1]) # two entities of one item each

torch.zeros(3, 4)
# tensor([[0., 0., 0., 0.],
        [0., 0., 0., 0.],
        [0., 0., 0., 0.]])
torch.ones(3, 4)
# tensor([[1., 1., 1., 1.],
        [1., 1., 1., 1.],
        [1., 1., 1., 1.]])
torch.randint(low=0, high=10, size=(3,4))
# tensor([[8, 2, 5, 9],
        [6, 1, 6, 0],
        [5, 6, 9, 5]])
torch.rand(3, 4) # 0-1之间随机
# tensor([[0.3196, 0.9387, 0.9268, 0.1246],
        [0.6700, 0.7529, 0.8687, 0.3948],
        [0.2279, 0.2309, 0.0151, 0.0339]])
torch.randn(3,4) # 服从正态分布的随机
# tensor([[-0.4039, -1.8015,  0.9784, -1.5263],
        [ 0.9577, -1.2826,  0.2746, -0.2621],
        [-1.4713,  0.6437,  0.3326, -1.0703]])

x = np.array([[10,20,30],[2,3,4]])
# np.ndarrary
y = torch.tensor(x)
# 将numpy转换为张量
print(type(x), type(y))
# <class 'numpy.ndarray'> <class 'torch.Tensor'>

张量运算

x = torch.tensor([[1,2,3,4], [5,6,7,8]]) 
print(x * 10)
# tensor([[10, 20, 30, 40],
#         [50, 60, 70, 80]])

x = torch.tensor([[1,2,3,4], [5,6,7,8]]) 
y = x.add(10)
print(y)
#tensor([[11, 12, 13, 14],
#        [15, 16, 17, 18]])

重塑张量

y = torch.tensor([2, 3, 1, 0]) 
y = y.view(4,1)
y 

# tensor([[2],
    [3],
    [1],
    [0]])

#另一种重塑方法squeeze方法,只适合在某个轴上数值为1才行
x = torch.randn(10,1,10)# 三维张量轴0,轴1 轴2
z1 = torch.squeeze(x, 1) # 1表示轴为1
# z1 = x.squeeze(1)
z1.shape
# torch.Size([10, 10])

x = torch.randn(10,1,10)# 三维张量轴0,轴1 轴2
z1 = torch.unsqueeze(x, 0) # 1表示轴为1
# z1 = x.unsqueeze(0)
# torch.Size([1,10,10])

除了unsqueeze也可以用None见下

z2, z3, z4 = x[None,:,:], x[:,None,:], x[:,:,None]
# torch.Size([1, 10, 10]) 
torch.Size([10, 1, 10]) 
torch.Size([10, 10, 1])

张量的矩阵乘法

y = torch.tensor([2, 3, 1, 0])
x = torch.tensor([[1,2,3,4], [5,6,7,8]])
print(torch.matmul(x, y))
# 或者 print(x@y)

张量的连接

x = torch.randn(10,10,10)
z = torch.cat([x,x], axis=0) # np.concatenate()
print('Cat axis 0:', z.shape)
# torch.Size([20, 10, 10])

提取张量最大值

x = torch.arange(25).reshape(5,5)
print('Max:', x.shape, x.max())    
# Max: torch.Size([5, 5]) tensor(24)

x.max(dim=0)# 轴0上的最大值
# torch.return_types.max(
values=tensor([20, 21, 22, 23, 24]),
indices=tensor([4, 4, 4, 4, 4]))

x.max(dim=1)# 轴1上的最大值
# torch.return_types.max(
values=tensor([ 4,  9, 14, 19, 24]),
indices=tensor([4, 4, 4, 4, 4]))

置换张量维数(不要通过重塑张量来交换维数)

x = torch.randn(10,20,30)# (0,1,2)
z = x.permute(2,0,1) # np.permute()
print('Permute dimensions:',z.shape)
# torch.Size([30, 10, 20])

dir(torch.Tensor)

查看张量方法

help(torch.Tensor.<method>)

对某个方法查看如何使用

2.2 张量的自动梯度

x = torch.tensor([[2., -1.], [1., 1.]], requires_grad=True)
# requires_grad=True 参数指定为张量对象计算梯度
# tensor([[ 2., -1.],
    [ 1.,  1.]], requires_grad=True)
out = x.pow(2).sum()
out.backward()
# 计算out的梯度
x.grad
# 得到out 关于 x 的梯度
# tensor([[ 4., -2.],
          [ 2.,  2.]])

一般来说,在CPU上使用Torch张量运算仍然比Numpy要快
torch的GPU最快

2.3 Pytorch构建神经网络

import torch
x = [[1,2],[3,4],[5,6],[7,8]]
y = [[3],[7],[11],[15]]

# 转换为浮点对象
X = torch.tensor(x).float()
Y = torch.tensor(y).float()
print(X,Y)

# 将数据注册到GPU
device = 'cuda' if torch.cuda.is_available() else 'cpu'
X = X.to(device)
Y = Y.to(device)

# 定义网络架构
import torch.nn as nn

# 对nn.Module的继承是强制的因为是神经网络的基类
# 必须利用super().__init__()来确保继承nn.Module就可以利用事先编写好的功能
class MyNeuralNet(nn.Module):
def __init__(self):
    super().__init__()
    self.input_to_hidden_layer = nn.Linear(2,8)#等价于 nn.Parameter(torch.rand(2,8))
    # 具体为Linear(in_features = 2 , out_features = 8 , bias = True)
    self.hidden_layer_activation = nn.ReLU()
    self.hidden_to_output_layer = nn.Linear(8,1)# nn.Parameter(torch.rand(8,1))
# 必须使用forward作为方法名,因为Pytorch保留了,用其他的会报错!!!
def forward(self, x):
    x = self.input_to_hidden_layer(x)
    #若 nn.Parameter(torch.rand(2,8)),则x = x @ self.input_to_hidden_layer(x)
    x = self.hidden_layer_activation(x)
    x = self.hidden_to_output_layer(x)
    #若 nn.Parameter(torch.rand(8,1)),则x = x @ self.hidden_to_output_layer(x)
    return x

# 创建实例并注册到GPU上
mynet = MyNeuralNet().to(device)

# 获取参数权重看一下
mynet.input_to_hidden_layer.weight

# 更详细的看下参数
mynet.parameters()
for i in  mynet.parameters():
    print(i)

# 定义MSE损失
loss_func = nn.MSELoss()
# CrossEntropyLoss() # 多分类损失
# BCELoss # 二分类损失

# 计算损失
_Y = mynet(X)
loss_value = loss_func(_Y,Y)
# 在pytorch中约定先传预测,再传真实数据
print(loss_value)

# 优化器随机梯度下降
from torch.optim import SGD
opt = SGD(mynet.parameters(), lr = 0.001)   

loss_history = []
for _ in range(50):
    opt.zero_grad()# 刷新上一步计算的梯度
    loss_value = loss_func(mynet(X),Y)
    loss_value.backward()# 计算梯度
    opt.step()# 根据梯度更新权重
    loss_history.append(loss_value.item())

# 绘图
import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(loss_history)
plt.title('Loss variation over increasing epochs')
plt.xlabel('epochs')
plt.ylabel('loss value')

2.4 数据集、数据加载器和批大小

批大小:用于计算损失或更新权重的数据点的数量
在有数百万数据点的情况下很有用,

class MyDataset(Dataset):
    def __init__(self,x,y):
        self.x = torch.tensor(x).float()
        self.y = torch.tensor(y).float()
    def __len__(self):
        return len(self.x)
    def __getitem__(self, ix):
        return self.x[ix], self.y[ix]
ds = MyDataset(X, Y)

# 从ds中获取两个数据点
dl = DataLoader(ds, batch_size=2, shuffle=True)

# 其余没变可套用上方代码
# 变化见下
import time
loss_history = []
start = time.time()
for _ in range(50):
    for data in dl:
        x, y = data
        opt.zero_grad()
        loss_value = loss_func(mynet(x),y)
        loss_value.backward()
        opt.step()
        loss_history.append(loss_value)
end = time.time()
print(end - start)

预测数据点

val_x = [[10,11]]
val_xf = torch.tensor(val_x).float().to('device')
mynet(val_x)

自定义损失函数

def my_mean_squared_error(_y, y):
    loss = (_y-y)**2
    loss = loss.mean()
    return loss

my_mean_squared_error(mynet(X),Y)
# 和这个效果一样 
loss_func = nn.MSELoss()
loss_value = loss_func(mynet(X),Y)
print(loss_value)

获取中间层的值(获取参数上面讲了)

# 种子
torch.random.manual_seed(10)

# 法1
input_to_hidden = mynet.input_to_hidden_layer(X)
hidden_activation = mynet.hidden_layer_activation(input_to_hidden)
x = mynet.hidden_to_output_layer(hidden_activation)
x

# 法2 改下类的forward函数
class MyNeuralNet(nn.Module):
def __init__(self):
    super().__init__()
    self.input_to_hidden_layer = nn.Linear(2,8)
    self.hidden_layer_activation = nn.ReLU()
    self.hidden_to_output_layer = nn.Linear(8,1)
def forward(self, x):
    hidden1 = self.input_to_hidden_layer(x)
    hidden2 = self.hidden_layer_activation(hidden1)
    x = self.hidden_to_output_layer(hidden2)
    return x, hidden1

# 调用
_Y, _Y_hidden = mynet(X)

2.5 使用Sequential类构建神经网络

之前是通过定义一个类来构建神经网络,

model = nn.Sequential(
nn.Linear(2, 8),
nn.ReLU(),
nn.Linear(8, 1)
).to(device)

# 这个包可以输出一个模型的摘要(总体架构)
!pip install torch_summary
from torchsummary import summary

# 输入模型和模型大小
summary(model, torch.zeros(2,2))

# 其余没变化除了模型名称
loss_func = nn.MSELoss()
from torch.optim import SGD
opt = SGD(model.parameters(), lr = 0.001)
import time
loss_history = []
start = time.time()
for _ in range(50):
    for ix, iy in dl:
        opt.zero_grad()
        loss_value = loss_func(model(ix),iy)
        loss_value.backward()
        opt.step()
        loss_history.append(loss_value)
end = time.time()
print(end - start)

# 预测新数据
val = [[8,9],[10,11],[1.5,2.5]]
val = torch.tensor(val).float().to(device)
model(val)

2.6 保存并加载Pytorch模型

保存模型

save_path = 'mymodel.pth'
torch.save(model.state_dict(), save_path)
# torch.save(model.state_dict(), 'mymodel.pth')
# state指的是模型的当前快照
# model.state_dict()返回一个字典

# 注:一个良好的保存做法是在调用torch.save()
之前将模型注册到CPU中,有助于模型加载到任何机器上(助人为乐)
# 所以可以torch.save(model.to('cpu').state_dict(), save_path)

加载模型

# 加载模型前需要事先对模型进行权重初始化
model = nn.Sequential(
        nn.Linear(2, 8),
        nn.ReLU(),
        nn.Linear(8, 1)
).to(device)

# 然后再加载参数
state_dict = torch.load('mymodel.pth')
model.load_state_dict(state_dict)

# 预测数据
val = [[8,9],[10,11],[1.5,2.5]]
val = torch.tensor(val).float().to(device)
model(val)

第三章 使用Pytorch构建深度神经网络

3.1 将图像转化为结构化数组和标量

一幅图像包含H×W×C,C为通道数,若是3则代表彩色图像

%matplotlib inline
import cv2, matplotlib.pyplot as plt
# 读取图像为像素值数组
img = cv2.imread('xxx.jpeg')
# 输出
array([[[206, 232, 249],
        [205, 231, 248],
        [204, 230, 247],
        ...,
        [205, 214, 218],
        [162, 169, 178],
        [165, 174, 183]],
        ...,
        [186, 215, 230],
        [186, 215, 230],
        [185, 214, 229]]], dtype=uint8)
# rgb -> gray
img = img[50:250,40:240]
# CV2导入图象时通道顺序是B G R,而我们通常习惯于观看基于R G B
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) 
plt.imshow(img_gray, cmap='gray')
# 改变数组大小
img_gray_small = cv2.resize(img_gray,(25,25))

# 加载彩色图像
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 
img1 = img[-3:,-3:]
# 展示图像
plt.imshow(img1)
# 查看对应数组
img1
# 输出    R    G     B
array([[[242, 149, 141], # 对应第一行三个像素
        [249, 161, 151],
        [239, 161, 148]],

       [[239, 145, 135], # 对应第二行三个像素
        [244, 150, 140],
        [247, 159, 149]],

       [[239, 145, 135], # 对应第三行三个像素
        [242, 148, 138],
        [245, 153, 142]]], dtype=uint8)

在结构化数组上进行数学运算,可以实现如:分类,检测,分割等任务

3.2 为什么要是用神经网络进行图像分析

传统的手工提取的特征如:直方图特征、边角特征、色彩分离特征、图像梯度特征等等,创建这些特征需要你是图像和信号处理的专家(不然这些特征一般人可能真想不到)且应该充分了解什么样的特征用来解决什么样的问题,其实满足了这两个条件,也不一定某个任务能保证专家就能找到正确特征组合,其实找到了也不一定能在未见到的场景中正确发挥作用
神经网络缺可以自动寻找特征并分类(即:既可以做特征提取器,也可以做分类器,可以直接实现把输入放入神经网络得到输出(端到端)极其方便)

3.3准备数据

from torchvision import datasets
import torch
data_folder = './data/FMNIST' # This can be any directory you want 
# to download FMNIST to
fmnist = datasets.FashionMNIST(data_folder, download=True, train=True)
# 只想下载训练数据

tr_images = fmnist.data
tr_targets = fmnist.targets
# 训练数据尺寸
torch.Size([60000, 28, 28]) # torch显示的是CHW,np等为hwc(28,28,6000)
torch.Size([60000])
# 提供了每个类别的对应名称
fmnist.classes

3.4 训练神经网络

# 导包
from torch.utils.data import Dataset, DataLoader
import torch
import torch.nn as nn
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
device = "cuda" if torch.cuda.is_available() else "cpu"
from torchvision import datasets
data_folder = './data/FMNIST' # This can be any directory you want to 
# download FMNIST to
fmnist = datasets.FashionMNIST(data_folder, download=True, train=True)
tr_images = fmnist.data
tr_targets = fmnist.targets

# 派生自Dataset类
class FMNISTDataset(Dataset):
    def __init__(self, x, y):
        x = x.float()
        x = x.view(-1,28*28) # 图像扁平化
        self.x, self.y = x, y 
    def __getitem__(self, ix):
        x, y = self.x[ix], self.y[ix] 
        return x.to(device), y.to(device)
    def __len__(self): 
        return len(self.x)

def get_data(): 
    train = FMNISTDataset(tr_images, tr_targets) 
    trn_dl = DataLoader(train, batch_size=32, shuffle=True)
    return trn_dl

# 隐藏层1000个神经元,输出层10个神经元
from torch.optim import SGD
def get_model():
    model = nn.Sequential(
        nn.Linear(28 * 28, 1000),
        nn.ReLU(),
        nn.Linear(1000, 10)
    ).to(device)
    loss_fn = nn.CrossEntropyLoss()
    optimizer = SGD(model.parameters(), lr=1e-2)
    return model, loss_fn, optimizer

def train_batch(x, y, model, opt, loss_fn):
    model.train() # 模型可以更新梯度
    prediction = model(x)
    batch_loss = loss_fn(prediction, y)
    batch_loss.backward() # 计算梯度
    opt.step() # 更新梯度
    opt.zero_grad() # 梯度清零
    return batch_loss.item()

@torch.no_grad()
def accuracy(x, y, model):
    model.eval() # 模型不更新梯度
    prediction = model(x)
    max_values, argmaxes = prediction.max(-1)
    is_correct = argmaxes == y
    return is_correct.cpu().numpy().tolist()

trn_dl = get_data()
model, loss_fn, optimizer = get_model()

losses, accuracies = [], []
i = 0
for epoch in range(5):
    print(epoch)
    epoch_losses, epoch_accuracies = [], []
    for ix, batch in enumerate(iter(trn_dl)):
        print(list(zip(*batch)))
        x, y = batch
        # x为torch.Size([32, 784]) y为torch.Size([32])
        batch_loss = train_batch(x, y, model, optimizer, loss_fn)
        epoch_losses.append(batch_loss)
    epoch_loss = np.array(epoch_losses).mean()
    for ix, batch in enumerate(iter(trn_dl)):
        x, y = batch
        is_correct = accuracy(x, y, model)
        epoch_accuracies.extend(is_correct)
    epoch_accuracy = np.mean(epoch_accuracies)
    
    losses.append(epoch_loss)
    accuracies.append(epoch_accuracy)

# 绘图 
epochs = np.arange(5)+1
# 输出[1 2 3 4 5]
print(epochs)
plt.figure(figsize=(20,5))
plt.subplot(121)
plt.title('Loss value over increasing epochs')
plt.plot(epochs, losses, label='Training Loss')
plt.legend()

plt.subplot(122)
plt.title('Accuracy value over increasing epochs')
plt.plot(epochs, accuracies, label='Training Accuracy')
plt.gca().set_yticklabels(['{:.0f}%'.format(x*100) for x in plt.gca().get_yticks()]) 
plt.legend()

# 可以看到,精度并没有上升多少
# 这就要了解超参数

3.5 缩放数据集

改变之处相对于上面的代码
# 派生自Dataset类
class FMNISTDataset(Dataset):
    def __init__(self, x, y):
        x = x.float()/255
# 除以最大的数据,把数据缩放到0-1之间
# 训练精度已经可以达到85%左右

3.6 不同批大小的影响

若60000个数据批大小为32,则需要进行6万除以32=1875次权重更新,5epoch,验证集精度已经可以达到85%左右  

若60000个数据批大小为10000,则需要进行6万除以1万=6次权重更新,5epoch,验证集精度已经可以达到80%左右

当轮数减少时,较小的批大小有助于达到最佳准确度

3.7 不同损失优化器的影响

SGD 批大小为32,10epoch 感觉SGD好一些
Adam 批大小为32,10epoch    Adam比较震荡

某些优化器能够更快的实现最佳准确度

3.8 不同学习率的影响

对缩放数据集

学习率大1e-1,验证集准确率为25% 10epoch(欠拟合)(1e-2验证集准确率为85%)
学习率中1e-3,验证集准确率为89% 10epoch
学习率小1e-5,验证集准确率为89% 100epoch (此时训练准确率91%)
学习率较小时,训练集损失和验证损失之间的差距小,因为权重更新慢

缩放数据集不同学习率不同层参数分布

学习率大的时候,参数分布(权重和参数最小值与最大值之间的范围)比中和小要大得多
参数分布大,出现过拟合

对非缩放数据集

学习率大1e-1,验证集准确率为2% 10epoch(欠拟合)(1e-2验证集准确率为85%)
学习率中1e-3,验证集准确率为82% 10epoch
学习率小1e-5,验证集准确率为89% 100epoch (相对缩放数据集过拟合了(此时训练准确率100%))
学习率较小时,训练集损失和验证损失之间的差距小,因为权重更新慢

非缩放数据集不同学习率不同层参数分布

与缩放数据集类似,注:在非缩放数据集上学习率为1e-5等价于在缩放数据集上学习率为1e-3

学习率小则意味着需要很长时间训练模型,而学习率过大则会导致模型不稳定。
可否一开始使用中等学习率,之后慢慢变小呢?
of course!!

3.9 不同学习率衰减的影响

持续监督验证损失,如果验证损失(x轮x可设置)不减少,就对学习率减半
from torch import optim
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, 
                                                factor=0.5, 
                                                patience=0, 
                                                threshold = 0.001, 
                                                verbose=True, 
                                                min_lr = 1e-5, 
                                                threshold_mode = 'abs')
# patience = 0 表示只要验证损失增加超过threshold =0.001,那么就将lr减少factor=0.5,注不能小于min_lr = 1e-5

for ix, batch in enumerate(iter(val_dl)):
    x, y = batch
    val_is_correct = accuracy(x, y, model)
    validation_loss = val_loss(x, y, model)
    scheduler.step(validation_loss)
val_epoch_accuracy = np.mean(val_is_correct)
# 全代码暂时不放,为了给自己做笔记的,如果需要完整代码可以私信我

3.10 不同隐层深度的影响

# 没有隐层(逻辑回归)
model = nn.Sequential(
    nn.Linear(28 * 28, 10)
).to(device)
# 一个隐层
model = nn.Sequential(
    nn.Linear(28 * 28, 1000),
    nn.ReLU(),
    nn.Linear(1000, 10)
).to(device)
# 两个隐层
model = nn.Sequential(
    nn.Linear(28 * 28, 1000),
    nn.ReLU(),
    nn.Linear(1000, 1000),
    nn.ReLU(),
    nn.Linear(1000, 10)
).to(device)

两个隐层,过拟合量大于,单个隐层(双验证损失比单多)
非缩放数据(除了表示数据过大,也可以表示数据过小)也有可能出现在隐层当中,下面介绍如何在中间层处理未缩放数据。

3.11 不同批归一化的影响

输入data既不能过大也不能过小,中间层的输入亦是

批均值=,
批方差=,
归一化输入=,
批归一化输入=,
可以归一化到一个固定的范围,由网络确定归一化参数
下面对比

没有批归一化的非常小输入值

100epoch
class FMNISTDataset(Dataset):
def __init__(self, x, y):
    x = x.float()/(255*10000)# 模拟非常小输入值
    x = x.view(-1,28*28)

def get_model():
class neuralnet(nn.Module):
    def __init__(self):
        super().__init__()
        self.input_to_hidden_layer = nn.Linear(784,1000)
        self.hidden_layer_activation = nn.ReLU()
        self.hidden_to_output_layer = nn.Linear(1000,10)
验证精度为85%,没有前面10轮就到90%好,
隐藏层值的分布很小,所以权重(参数分布大)需要较大的范围

经过批归一化的非常小输入值

def get_model():
    class neuralnet(nn.Module):
        def __init__(self):
            super().__init__()
            self.input_to_hidden_layer = nn.Linear(784,1000)
            self.batch_norm = nn.BatchNorm1d(1000)
            self.hidden_layer_activation = nn.ReLU()
            self.hidden_to_output_layer = nn.Linear(1000,10)
        def forward(self, x):
            x = self.input_to_hidden_layer(x)
            x0 = self.batch_norm(x)
            x1 = self.hidden_layer_activation(x0)
            x2= self.hidden_to_output_layer(x1)
            return x2, x1
验证精度为86%,但是可以比不BN的更快达到较高的精确度,可以帮助避免梯度变得太小以至于不能更新权重
隐藏层值的分布变大了,所以权重(参数分布小了)需要较小的范围

3.12 过拟合

训练集精度95%以上,但是验证集精度却仅仅89%,说明模型泛化能力不高,也说明模型在学习训练数据的边缘情况。
减少过拟合的策略

dropout

虽然大多数参数的调整有助于实现合理的训练,但是也有某些参数可能仅仅是对训练数据的微调,所以虽然这些权重对训练数据有较高精度,但是不能够泛化到验证数据集,dropout随机选取特定百分比的神经元不激活(关闭),使得没那么多机会对边缘进行优化,预测过程中不需要dropout,需要指定模型model.train()为训练模式,model.eval()为评估模式
def get_model():
model = nn.Sequential(
nn.Dropout(0.25),
nn.Linear(28 * 28, 1000),
nn.ReLU(),
nn.Dropout(0.25),
nn.Linear(1000, 10)
).to(device)
训练集精度90%,验证集精度为87%左右有效降低了过拟合

正则化

除了模型的训练精度大于验证精度外,过拟合的另一个特征是某些权重值会远远高于其他权重,可能是模型在训练数据上学习很好的表现,dropout是使权重不会频繁的更新,而正则化则是对模型中中高权重施加惩罚,双重优化目标即:最小化训练数据的损失和权重,结论L1和L2都将参数的分布变得很小,减小了在边缘更新权重的机会。

L1正则化

def train_batch(x, y, model, opt, loss_fn):
    prediction = model(x)
    l1_regularization = 0
    for param in model.parameters():
    l1_regularization += torch.norm(param,1)
    batch_loss = loss_fn(prediction, y) + 0.0001*l1_regularization
    batch_loss.backward()
    optimizer.step()
    optimizer.zero_grad()
    return batch_loss.item()
#torch.norm(param,1) 提供了权重和偏置的绝对值

L2正则化

def train_batch(x, y, model, opt, loss_fn):
    prediction = model(x)
    l2_regularization = 0
    for param in model.parameters():
    l2_regularization += torch.norm(param,2)
    batch_loss = loss_fn(prediction, y) + 0.01*l2_regularization
    batch_loss.backward()
    optimizer.step()
    optimizer.zero_grad()
    return batch_loss.item()
之所以取0.01是因为,权重通常是-1到1,平方后会更小

第四章 卷积神经网络

4.1 传统神经网络问题

在传统神经网络中,如果测试某一个图像经过左右平移-5到5个像素的图像的时候,模型只能识别左右平移+-2个像素的图像,原因可能是,训练图像和预测图像主要都在图像中间,当遇到在两边的时候则几乎失效

4.2 CNN构建模块

卷积:一种运算方法,对应相乘,最后累加

滤波器:一个权重矩阵,数量越多学到的特征越多,有几个滤波器就有几个偏置,
图像有多个滤波器的时候输出通道数为滤波器的数量。

步长和填充:用来避免信息丢失的可能性,可以保持尺寸不变,(尺寸不变就可以resnet相加啦)
为了确保滤波器的每个元素都可以乘到图像中的每个元素,需要边界零填充。

池化:最大池化用的多,有助于聚合信息,从而减少扁平层节点的数量,池化可以看作某一小块的抽象,在上述的平移的时候很有用

4.3 实现CNN

(Pytorch期望输入形状是(N×C×H×W)N是批大小,C是通道数)
# 导库
import torch
from torch import nn
from torch.utils.data import TensorDataset, Dataset, DataLoader
from torch.optim import SGD, Adam
device = 'cuda' if torch.cuda.is_available() else 'cpu'
from torchvision import datasets
import numpy as np

import matplotlib.pyplot as plt
%matplotlib inline

# 创建数据集
X_train = torch.tensor([[[[1,2,3,4],
                      [2,3,4,5],
                      [5,6,7,8],
                      [1,3,4,5]]],
                    [[[-1,2,3,-4],
                      [2,-3,4,5],
                      [-5,6,-7,8],
                      [-1,-3,-4,-5]]]]).to(device).float()
# 缩放数据集
X_train /= 8
y_train = torch.tensor([0,1]).to(device).float().unsqueeze(1)
# x的torch.Size([2, 1, 4, 4])
# y的torch.Size([2, 1])

# 定义模型架构
def get_model():
model = nn.Sequential(
    nn.Conv2d(1, 1, kernel_size=3),
    nn.MaxPool2d(2),
    nn.ReLU(),
    nn.Flatten(),
    nn.Linear(1, 1),
    nn.Sigmoid(),
).to(device)
loss_fn = nn.BCELoss()
optimizer = Adam(model.parameters(), lr=1e-3)
return model, loss_fn, optimizer

# 获取模型摘要
model, loss_fn, optimizer = get_model()
summary(model, X_train)

# 训练批数据
def train_batch(x, y, model, opt, loss_fn):
    model.train()
    prediction = model(x)
    batch_loss = loss_fn(prediction, y)
    batch_loss.backward()
    optimizer.step()
    optimizer.zero_grad()
    return batch_loss.item()
trn_dl = DataLoader(TensorDataset(X_train, y_train))

# 训练
for epoch in range(2000):
for ix, batch in enumerate(iter(trn_dl)):
    x, y = batch
    batch_loss = train_batch(x, y, model, optimizer, loss_fn)

# 预测
model(X_train)

4.4 深度CNN分类图像

class FMNISTDataset(Dataset):
def __init__(self, x, y):
    x = x.float()/255
    x = x.view(-1,1,28,28)

# 模型
def get_model():
model = nn.Sequential(
    nn.Conv2d(1, 64, kernel_size=3),
    nn.MaxPool2d(2),
    nn.ReLU(),
    nn.Conv2d(64, 128, kernel_size=3),
    nn.MaxPool2d(2),
    nn.ReLU(),
    nn.Flatten(),
    nn.Linear(3200, 256),
    nn.ReLU(),
    nn.Linear(256, 10)
).to(device)

训练5epoch验证集准确度为92%(已经优于前面各种技术的结果)
在验证-5到+5平移像素的影响时,已经可以预测到-4到+4的像素了,但没完全解决!
下面介绍数据增强

4.5 实现数据增强

图像增强

旋转
放大缩小
添加噪声
亮度调整
翻转
剪切
扭曲
等等……
库:pytorch 的torchvision.transforms,但是另外一个库更强大imgaug

import imgaug.augmenters as iaa
# 仿射变换Affine方法,如
aug = iaa.Affine(scale=2)
plt.imshow(aug.augment_image(to_numpy(tr_images[0])))

aug = iaa.Affine(rotate=(-45,45), fit_output=True, cval=0, mode='constant')
plt.imshow(aug.augment_image(to_numpy(tr_images[0])), cmap='gray')

# 防止信息丢失fit_output参数
aug = iaa.Affine(scale=2,fit_output=True)

# 扩展部分填充颜色cval参数
aug = iaa.Affine(scale=2,fit_output=True,cval=255)#填充白色
# 填充模式mode参数

# 改变亮度Multiply所有像素都×0.5
aug = iaa.Multiply(0.5)
plt.imshow(aug.augment_image(to_numpy(tr_images[0])), cmap='gray',vmin = 0, vmax = 255)

# 改变亮度LinearContrast
aug = iaa.LinearContrast(0.5) # α
plt.imshow(aug.augment_image(to_numpy(tr_images[0])), cmap='gray',vmin = 0, vmax = 255)
# 根据公式:127 + α × (像素值 - 127)调整
# α<1时,高像素减少,低像素增加

# 添加模糊GaussianBlur
aug = iaa.GaussianBlur(sigma=1) # sigma越大越模糊
plt.imshow(aug.augment_image(to_numpy(tr_images[0])), cmap='gray',vmin = 0, vmax = 255)

# 添加噪声dropout随机变黑0
aug = iaa.Dropout(p=0.2)
plt.imshow(aug.augment_image(to_numpy(tr_images[0])), cmap='gray',vmin = 0, vmax = 255)
# 添加噪声saltandpepper随机取0-255然后随机在图像上赋值
aug = iaa.SaltAndPepper(0.2)
plt.imshow(aug.augment_image(to_numpy(tr_images[0])), cmap='gray',vmin = 0, vmax = 255)

# random_order参数,增强过程在二者之间随即进行
seq = iaa.Sequential([
  iaa.Dropout(p=0.2,),
iaa.Affine(rotate=(-30,30))], random_order= True)
# 可同时也可单个

对一批图像数据增强collate_fn函数

对一批图像进行增强比单个增强快
class FMNISTDataset(Dataset):
def init(self, x, y, aug=None):
self.x, self.y = x, y
self.aug = aug
def getitem(self, ix):
x, y = self.x[ix], self.y[ix]
return x, y
def len(self): return len(self.x)

    def collate_fn(self, batch):
        'logic to modify a batch of images'
        ims, classes = zip(*batch)
        print(ims)
        print('*****************************8')
        # transform a batch of images at once
        ims = torch.stack(ims)
        print(ims)
        print(classes)
        # 对验证数据不增强
        if self.aug: ims=self.aug.augment_images(images=to_numpy(ims))
        print(ims.shape)
        ims = torch.tensor(ims)[:,None,:,:].to(device)/255.
        print(ims.shape)
        classes = torch.tensor(classes).to(device)
        return ims, classes

最后-5到+5都识别出来了

图像样本数量的影响

样本数量越多效果越好,那如果数据没那么多怎么办?迁移学习,可以利用少量数据获得较高的准确度

posted @   denngamin  阅读(231)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示