深度学习概述

 
总体:AI从历史数据中学习规律

1. 数据加载器,准备数据与标签,并进行批次处理

2. 损失函数,分析问题类型,选择或设计一个损失函数,以求模型输出与标签的偏差

3. 模型层设计,设计模型的参数网络,使得数据流过该参数网络后,
   得到一个与标签shape一致的输出,以便损失函数计算

4. 优化器,使用梯度下降法修改模型参数,试图得到一组参数,使得损失函数在该数据集上,值最小 

 

    

 

    

 
正向传播 求损失,求两个分布的平均损失

反向传播 求梯度,求每个参数变量的梯度

梯度下降 求极值,优化参数找一个极小值

 
---------------------------------------------------------
    

 

    

 
深度学习中所有数据皆以批次的形式,即矩阵的形式进行运算 

数据,输入模型的数据,或者从模型中输出的数据,至少是2维的 

第1维,即index=0的维度上,永远是批次的维度,代表样本的个数 

 
对于数据X,第1维,机器学习与深度学习皆是一样的,代表一个样本

对于标签y,
- 机器学习中是1维,即一个列表
- 深度学习中是2维,第1维代表批次,第2维代表样本对应的数值/类别

在深度学习,一个样本对应的是一个标签向量

 


标签不做标准化

 
标签不需要做归一化
- 深度学习中 标签通常不做标准化/归一化处理
- 标签是损失函数的参数,参与损失函数计算

标签可以做归一化,但需要反解
- 不方便,或者说多此一举
- 对于小于1的数,反解之后会与原来的数有差异 


数据必须做标准化

 

输入模型之前的标准化
- 统一量纲 


模型内每一层的标准化
- 深度学习中层较多,为了防止数据爆炸导致无法计算,每一层都会进行一次归一化处理
- 批归一化 
  

 

  

 

  

 


数据的维度

一切数据都是tensor对象

 
在torch中一切数都是张量,包含单个数字的标量,多维矩阵等,
张量就是tensor

所有的数据都是一个tensor对象,它的浮点类型默认是float32, 
numpy的浮点类型默认是float64
float32在进行大数据量运算时,性能消耗比float64低一些 

具体可以看一下pytorch部分的知识 

 

    

 
如果你之前不是这行的,那么看数据没有维度的概念

但这行,看数据全看维度,即shape 

通常说的矩阵是二维 tensor, 

import torch
a=torch.tensor([[1,1,2],[1,2,3]])
a.ndim
2

a.shape
torch.Size([2, 3])

这个要反复地提醒自己,直至本能化,

看所有事物都是向量,就是看问题,想问题 要从多个角度/维度去观察思考/分析 

看一类事物都是矩阵,
就是任何事物都不是单独存在的,
有很多同类的,
多看几个才更容易找出本质特征 ... 

这是个艰难且缓慢的过程,
技术易学,思维方式难改,
没有几年的反反复复,改变不过来的 ... 

 

    

 


 

  

 


DL(深度学习)基本步骤

概述

 
数据处理

模型定义:期望有个算法能近似表示数据中的规律

学习训练:损失函数计算模型输出与目标的距离大小/差异,优化器让这个距离越来越小 

预测处理

深度学习发展之初大致就这四步,
损失函数因为比较通用,就没有在此过程描述,但损失函数的作用依然是核心 

数据处理

 

数据决定了模型的上限 
数据有批次,即矩阵的形式

数据--向量--矩阵--批次化 

数据集

 

from torch.utils.data import Dataset
from torch.utils.data import DataLoader

数据通常制作成数据集,并以批次的形式加载

模型定义:输入,输出,网络结构

 

完成一堆参数的初始化,

主要工作是 设计参数的网络结构,

数据会 流过 这个网络结构, 

调用时,主要看输入,输出的shape 

模型结构

 

继承nn.Module类 
init函数中初始化参数
forward方法中计算逻辑 

损失函数:要能够衡量模型输出与标签之间的差异

 

差异越小,代表模型设计越接近标签中隐藏的规律
- 这里直接使用数据代表其规律
- 但要知道,数据是残缺且有噪声的 
- 因此,所谓的规律只是真实规律的一个近似的片面


损失函数,常用的有五六个,它的输入参数有两个:
模型输出:可自动求导,求导工作torch后台自动完成,开发者几乎感受不到这个过程  
目标数据:常量 

学习训练

 

损失函数定义/设计 

优化器:w = w - 学习率×模型输出的梯度

每调用一次优化器,就会更新一次参数,
让模型的参数网络 整体/大致 朝着损失函数更小的方向调整

学习率用于控制w的变化幅度,
比如,学习率设置为一个很小的数0.005,那么一次改变量只有梯度的千分之五

梯度
函数曲线梯度越大,越陡峭,梯度越小,越平缓
陡峭时w变化大一点,平缓时,变化小一点 
最核心的还是正负 

还有个附加操作,就是torch这个框架,梯度默认累加,
每次损失函数梯度下降计算后,得到的梯度是历史累加的结果,
为了防止与上次的计算结果叠加,
每次损失计算后都要手动清空一次梯度

预测处理

 
将数据代入模型,得到一个结果;2维的数表/矩阵 

如果有需要,再对该结果进行一番业务逻辑处理,让业务人员能明白其含义

这就是预测处理 

 

    

最简DL模型

 
import torch
from torch import nn 

class DLModel(nn.Module):
    """模型定义
    """
    
    def __init__(self, in_features, out_features):
        """参数网络设计
        - 总体来说,做的事件是将数据从一个维度转换到另外一个维度
        """
        super(DLModel, self).__init__()
        
        self.linear = nn.Linear(in_features=in_features, out_features=out_features)
        
    def forward(self, X):
        """正向传播
        - 调用定义的参数网络
        - 让数据流过参数网络,常量数据流过不过的参数产生不同的值
        - 这个过程参数本身不会变
        - 让参数变化的是后面的优化器 
        """
        out = self.linear(X)
        
        return out

查看模型结构

 
model = DLModel(in_features=13, out_features=1)
model
DLModel(
    (linear): Linear(in_features=13, out_features=1, bias=True)
)

查看模型参数

 
for param in model.parameters():
print(param)
Parameter containing:
tensor([[ 0.0875, -0.1299,  0.1730,  0.2226, -0.1479, -0.1526, -0.0852,  0.0231,
            -0.2111,  0.0999,  0.2695,  0.2554,  0.0442]], requires_grad=True)
Parameter containing:
tensor([-0.0163], requires_grad=True)

参数分为两部分W,b 

xW+b 

W是参数,b是偏置

 

    

 


 
模型定义和网络参数分开保存

模型参数保存示例

 
import torch
from torch import nn

class Model1(nn.Module):
    def __init__(self,in_features,out_features):
        super().__init__()
        self.layer1 = nn.Linear(in_features=in_features,out_features=out_features)
        
    def forward(self,X):
        x = self.layer1(X)
        return x
    
model = Model1(in_features=200,out_features=2)

torch.save(model.state_dict(),"model/m1_param.pkl")

model_dict_params=torch.load("model/m1_param.pkl")

model.load_state_dict(model_dict_params)

torch对参数的保存就是save,加载就是load,保存的是参数,加载的也是参数

模型使用指定参数时,调用model.load_state_dict方法,model中所有方法以load开头的只有load_state_dict这一个方法,
所以,只需要记住save,load这两个单词就可以了

 

  

 


一句话描述DL

使用神经网络建模从历史数据中学习规律

 
算法工程师出一个模型,

这里面有神经网络,也可能会有一些其他的处理技巧,

用于学习/模拟 现有历史数据的规律

简单说,就是以线性变换为主设计一个网络模型,
以此为核心,再不断地展开,

比如,怎么设计数据的结构使之方便设计网络

怎么设计一个损失函数,
是使用已有的还是需要自己单独设计一个,
使之能更好地衡量 模型输出与目标数据 之间的差异 

怎么设计日志,以方便观察损失值的变化 

怎么设计一个方法,可以更好地反映预测结果的精度
... 
... 
... 
参数网络结构设计 才是深度学习的根,其他都是枝
要把主要精力放在这个方向上  
当然了,其他的也需要会用... 

模型的嵌套

自定义模型嵌套torch模板类

 
class DLModel(nn.Module):
"""模型定义
"""

def __init__(self, in_features, out_features):
    """参数网络设计
    - 总体来说,做的事件是将数据从一个维度转换到另外一个维度
    """
    super(DLModel, self).__init__()
    
    self.linear = nn.Linear(in_features=in_features, out_features=out_features)
    
nn.Linear是一个模板类,实现一个功能,可以认为它嵌套进了自定义的模型DLModel中
这个init方法中也可以嵌套自定义的模板类,

自定义模型嵌套自定义模板类

 
import torch
from torch import nn 

class DLModelBase(nn.Module):
    """模型定义
    """
    
    def __init__(self, in_features, out_features):
        """参数网络设计
        - 总体来说,做的事件是将数据从一个维度转换到另外一个维度
        """
        super().__init__()
        
        self.linear = nn.Linear(in_features=in_features, out_features=out_features)
        
    def forward(self, X):
        """正向传播
        - 调用定义的参数网络
        - 让数据流过参数网络,常量数据流过不过的参数产生不同的值
        - 这个过程参数本身不会变
        - 让参数变化的是后面的优化器 
        """
        out = self.linear(X)
        
        return out


class DLModel(nn.Module):
    """模型定义
    """
    
    def __init__(self, in_features, out_features):
        """参数网络设计
        - 总体来说,做的事件是将数据从一个维度转换到另外一个维度
        """
        super().__init__()
        
        self.linear = DLModelBase(in_features=in_features, out_features=out_features)
        
    def forward(self, X):
        """正向传播
        - 调用定义的参数网络
        - 让数据流过参数网络,常量数据流过不过的参数产生不同的值
        - 这个过程参数本身不会变
        - 让参数变化的是后面的优化器 
        """
        out = self.linear(X)
        
        return out
    
X = torch.randn(3,13)
model = DLModel(in_features=13,out_features=2)
y_out = model(X)
y_out
tensor([[ 0.0909, -0.4304],
        [ 1.0879,  1.4130],
        [-0.1355,  0.1395]], grad_fn=)

模型forward

forward 是实现了类似__call__方法

 
class Model(object):
    """调用对象即调用方法
    """
    def __init__(self):
        pass
    
    def __call__(self, x):
        return x+1
    
model = Model()
y_out = model(x=3)
y_out
4

模型类的便利

模型类的核心功能

 
模型类定义了参数网络,
主要就是参数网络/结构,

目的在于让数据流过一个怎样的参数结构

甚至说,参数的初始化都不必在类中完成,
当然了,在类中完成时,写代码会方便一点 

forward

 
model = 模板类(参数网络需要的初始化值...)
y_out = model(数据X)

forward就实现这么一个功能,功效就是让人写代码舒服点

model.parameters

 
torch中所有自带的模板类实现有parameters()方法,
继承nn.Module的类也会有个parameters()方法

pytorch 把不同模板类的参数都集中到这个方法中,后续调用模型对象的参数就方便多了

分割开的功能设计

 
本来,参数网络定义(模型),参数梯度求解(损失函数反向传播), 参数更新(优化器),
这三者是紧密相连的,

为了省事,很容易将它们设计成一个类的不同方法
但Pytorch没有这么设计,而是拆分开来,
拆分成一个个小的组件,让 用户/开发者 自已去组装

就像现在的电脑一样,有人做主板,有人做内存,有人做显卡,有人做磁盘
还有一部分人,做电脑组装...
给打游戏的装,给办公的人装,给做深度学习的人装... 
不同群体的需求不一样,就给它装不同的配置

参考