神经网络总体架构

 
1. 提取特征
2. 分类

提取特征,层
在CV领域,提取特征的一种方法是 卷积
在NLP领域,提取特征的一种方法是 循环神经网络
注意力也能提取特征,通用于CV,NLP 

分类:
全连接

中间会穿插一系列增加模型拟合能力的方法:
BN 
MAXPOOL
RULE
DROPOUT

使用pytorch自定义网络

call方法

 
python类的init方法可以在调用类生成对象时传递参数
call方法可以为对象传递参数

import numpy as np
class MyModel(object):
    def __init__(self, in_feature,out_feature) -> None:
        self.in_feature = in_feature
        self.out_feature = out_feature
        
    def __call__(self, X) :
        x = np.array(X)
        return x.mean()

# 模板定义,经过参数初始化,将一个通用模板类转化一个具有特定业务含义的模板
# 就是定义一个参数网络
model = MyModel(in_feature=2,out_feature=1)
X = np.array([1,2,3])

# 让数据流过参数网络
y = model(X)
print(y)   # 2.0

深度学习的模型结构

 
继承nn.Module类,调用super().__init__()完成父类初始化
forward方法实现类似call的功能
总之,深度学习的模型定义通常只有两部分:
1. 定义一个参数网络(言外之意:不含数据)
2. 调用网络:让数据流过参数网络,通过重写forward方法实现

import torch
from torch import nn 
import numpy as np

class MyModel(nn.Module):
    def __init__(self, in_feature,out_feature) -> None:
        super().__init__()
        self.in_feature = in_feature
        self.out_feature = out_feature
        
    def forward(self, X) :
        x = np.array(X)
        return x.mean()

# 模板定义,经过参数初始化,将一个通用模板类转化一个具有特定业务含义的模板
# 就是定义一个参数网络
model = MyModel(in_feature=2,out_feature=1)
X = np.array([1,2,3])

# 让数据流过参数网络
y = model(X)
print(y)   # 2.0

回归问题:预测得到一个数

 
import torch 
from torch import nn 
import torch.nn.functional as F 

# torch 批次处理
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split

# 因为加入了1%的噪声,所以,不管怎么训练,模型的精度上限是 99%,会有稍许浮动,但不会达到100%
# 这也说明影响精度的两个重要因素:一是模型算法不够好,二是数据噪声太多(这个决定了上限)
X,y = make_regression(n_samples=10000,n_features=100,noise=0.01,random_state=73)
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.1,random_state=73)

class MyDataSet(Dataset):
    
    def __init__(self,X,y):
        """
        构建数据集
        """
        self.X = X
        self.y = y.reshape(-1,1)  # 将标签转为2维,与模型输出维度一致
        print(f"seq_len={len(X[0])}")
    
    def __len__(self):
        return len(self.X)
    
    def __getitem__(self, idx):
        x = self.X[idx]
        y = self.y[idx]

        return torch.tensor(data=x).float(), torch.tensor(data=y).float()

# 自定义数据加载器
train_dataset = MyDataSet(X=X_train,y=y_train)
test_dataset = MyDataSet(X=X_test,y=y_test)


# -----------------------------
# 模型定义
# -----------------------------
class LinearModel(nn.Module):
    def __init__(self, in_features=100, out_features=1):
        """初始化参数网络
        两层网络相比一层网络,加速了模型收敛的速度
        """
        super().__init__()
        # 全连接网络1
        self.linear1 = nn.Linear(in_features=in_features, out_features=8)

        # 最后一层全连接,将数据维度映射到业务需要的维度,
        # 因为是回归问题,得到的是一个数
        # 这在神经网络中用1维表示,
        self.linear2 = nn.Linear(in_features=8, out_features=out_features)

    def forward(self, X):
        """让数据流过参数网络"""
        x = self.linear1(X) 

        # 加了一层激活函数,结果验证它可以提升模型的表达能力
        # 激活函数没有参数,或者说它的参数是固定的,也没有梯度,不参与梯度运算
        x = F.relu(x)
        
        x = self.linear2(x)
        return x

model = LinearModel(in_features=100, out_features=1)

# 回归问题常用损失函数MSE
loss_fn = nn.MSELoss()

# 自适应优化法
optim = torch.optim.Adam(params=model.parameters(), lr=1e-3)

# 从数据集中批次取数据
train_dataloader = DataLoader(dataset=train_dataset, shuffle=True, batch_size = 128)

# 训练一行数据,测试代码是否有误
for X,y in train_dataloader:
    print(X.shape,X.ndim,y.shape,y.ndim)  # torch.Size([128, 100]) 2 torch.Size([128, 1]) 2
    y_out = model(X)
    # print(y_out.shape,y_out.ndim)  # torch.Size([128, 1]) 2
    if y_out.ndim != y.ndim:
        print('模型输出数据维度与标签维度不一致,无法进行损失计算...')
        break
    loss = loss_fn(y_out,y)
    optim.zero_grad()
    loss.backward()
    optim.step()  # 完成一次模型参数更新,w = w - x.grad
    break 
# ------------------
# 重要部分到这就结束,总结一下:
# 参数网络定义(模型定义),如何让参数逼近标签(损失函数设计及优化方法)
# ------------------

from ai.dl import T
from ai.params import DATA_ROOT
import os 

model_param_path = os.path.join(DATA_ROOT,'linear_model/model2_params2.h5')
log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)),"train.log")

# 自定义训练器,重要不在这里,所以就封装一下
T.train(model=model,loss_fn=loss_fn,optimizer="adam",
train_dataset=train_dataset,
train_dataloader=train_dataloader,
epochs=10,
learning_rate=1e-3,
model_param_path=model_param_path,
test_dataset=test_dataset,
auto_save=True,
continuation=True,
is_regression=True,
log_file=log_file)

回归问题,模型输出是一个与对标签对应的向量
 
回归模型一次输出一个数,比如[3.14] 
DL模型都是批次计算,一个批次如下
[
[3.14] 
[3.15] 
[3.16] 
]
所以模型的输出是个2维矩阵 
第2维只有一个数,其结果就是我们需要的数值
回归问题的损失函数通常使用MSE 
分类问题

分类问题标签设计

 
分类模型一次输出n个数,n=3,就是3分类问题,比如[0.1, 0.3, 0.7] 
DL模型都是批次计算,一个批次如下
[
[0.1, 0.3, 0.7] 
[0.1, 0.3, 0.7] 
[0.1, 0.3, 0.7] 
]
所以模型的输出是个2维矩阵 
第2维的3个数对应着3个类别,
我们的标签是这样的
[
[0, 0, 1] 
[0, 0, 1] 
[0, 0, 1] 
]
随着模型参数不断逼近标签,
模型输出的第2维数据只有一个元素会逼近1(因为标签是1)
模型输出的第2维数据    其他元素会逼近0(因为标签是0)
于是,模型输出是哪个类型我们就清楚了
也就是说,你怎么设计标签很重要,
遇到其他的分类问题,修改标签的表示方法就能解决不同的问题

分类问题损失计算


明白了模型输出与真实标签的shape后,那怎么计算它们之间的损失?
这实际上是要计算两个分布之间的距离
分解之后就是如何计算两个向量之间的距离,
接上面的例子,就是要计算
模型输出向量 [0.1, 0.3, 0.7] 与能表示标签的向量[0, 0, 1] 之间的距离
这就要引入 交叉熵 的概念

另外要注意,不管是分类还是回归,在深度学习都是批量计算,损失值最后求的是平均值 

pytorch模型转onnx

将pytorch模型保存为onnx

 
仅将pytorch模型保存为onnx,并不加载运行,只是为其他程序提供onnx格式的模型,
这时仅安装pytorch包就可以

import torch
import torchvision
    
model = torchvision.models.resnet18()
model.eval()


#[B,C,H,W],trace需要通过实际运行一遍模型导出其静态图,故需要一个输入数据
h0 = torch.zeros(1, 3, 32, 32)

# trace方式,在模型设计时不要用for循环,
# 能在模型外完成的数据操作不要在模型中写,
# 不用inplace等高大上的语法,保持简单,简洁,否则onnx可能无法完全转换过去
torch.onnx.export(
    model=model, 

    # model的参数,就是原来y_out = model(args)的args在这里指定了
    # 有其shape能让模型运行一次就行,不需要真实数据
    args=(h0,), 

    # 储存的文件路径
    f="model02.onnx",  
    
    # 导出模型参数,默认为True
    export_params = True, 
    
    # eval推理模式,dropout,BatchNorm等超参数固定或不生效
    training=torch.onnx.TrainingMode.EVAL,  

    # 打印详细信息
    verbose=True, 

    # 为输入和输出节点指定名称,方便后面查看或者操作
    input_names=["input1"], 
    output_names=["output1"], 

    # 这里的opset,指各类算子以何种方式导出,对应于symbolic_opset11
    opset_version=11, 

    # batch维度是动态的,其他的避免动态
    dynamic_axes={
        "input1": {0: "batch"},
        "output1": {0: "batch"},
    }
)

加载onnx

 
import onnx
model_onnx = onnx.load("model02.onnx")  # 加载onnx模型
onnx.checker.check_model(model_onnx)  # 验证onnx模型是否加载成功

调用onnx

 
import onnxruntime
import numpy as np

# 创建会话
session = onnxruntime.InferenceSession("model02.onnx",providers=[ 'CPUExecutionProvider'])
# session = onnxruntime.InferenceSession("model02.onnx",providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
x = np.random.randn(1, 3, 32, 32)
ort_input = {session.get_inputs()[0].name: x.astype(np.float32)}
ort_output = session.run(None, ort_input)

示例1

 

#[B,C,H,W],trace需要通过实际运行一遍模型导出其静态图,故需要一个输入数据
h0 = torch.zeros(1, 1350)

# trace方式,在模型设计时不要用for循环,
# 能在模型外完成的数据操作不要在模型中写,
# 不用inplace等高大上的语法,保持简单,简洁,否则onnx可能无法完全转换过去
torch.onnx.export(
    model=model, 

    # model的参数,就是原来y_out = model(args)的args在这里指定了
    # 有其shape能让模型运行一次就行,不需要真实数据
    args=(h0,), 

    # 储存的文件路径
    f=f"model/{pm.model_name}_{pm.model_version}.onnx",  
    
    # 导出模型参数,默认为True
    export_params = True, 
    
    # eval推理模式,dropout,BatchNorm等超参数固定或不生效
    training=torch.onnx.TrainingMode.EVAL,  

    # 打印详细信息
    verbose=True, 

    # 为输入和输出节点指定名称,方便后面查看或者操作
    input_names=["input"], 
    output_names=["output"], 

    # 这里的opset,指各类算子以何种方式导出,对应于symbolic_opset11
    opset_version=11, 

    # batch维度是动态的,其他的避免动态
    dynamic_axes={
        "input": {0: "batch"},
        "output": {0: "batch"},
    }
)


import onnxruntime
import numpy as np

# 创建会话
session = onnxruntime.InferenceSession(f"model/{pm.model_name}_{pm.model_version}.onnx",providers=[ 'CPUExecutionProvider'])
# session = onnxruntime.InferenceSession("model02.onnx",providers=['CUDAExecutionProvider', 'CPUExecutionProvider'])
x = np.random.randn(1, 1350)
ort_input = {session.get_inputs()[0].name: x.astype(np.float32)}
ort_output = session.run(None, ort_input)


ort_output
[array([0.48087886, 0.51912105], dtype=float32)]


session.get_inputs()[0].name
'input'

深度学习主流程

第一步:大方向

 
面临的业务问题是什么?

总体上,有哪些模型可供参考 

要使用哪些/哪类思想?

要解决什么问题?

第二步:数据流转过程

 
数据是如何从业务转到模型的?
模型的输入,输出是什么 ?

标签的shape逆向决定了模型输出的shape 

损失函数的目标是什么?
如何设计损失函数,才会让 当损失函数越来越小时,模型输出逼近目标 ?
有没有现成的可供调用,还是需要自己手工设计?

数据从业务开始,最后又返回业务,得到一个业务能看明白的结果,
这是个一体化的过程,
比如前面进行的很顺利,但损失函数找不到现成的,自己又设计不出来合适的,
前面做那么多工作又有什么用!!!

数据到模型,模型设计,损失函数设计,训练设计,预测设计,返回业务等任何一个环节搞不定,
整体就不会有好结果!

只有将整个过程弄个七七八八,才可以开始这个工程... 

第三步:具体实现

 
就是将第二步思考的内容实现出来,

先定下一个baseline,
然后对细节进行优化,做了什么使精度或速度提升了多少... 

再加上日志,

考虑灰度上线,

如何对外提供服务,

如何架框转换可以提速,

日后运行维护的工程化问题等等

第四步:看看别人如何做的

 
前三步可能是你一个人在做,

做完后,如果有机会看看别人怎么处理类似的问题

若只顾着自己,那不就是“闭门造车”嘛

向外看看,总会有些收获

AI的算法思想很多本身就是参考其他领域解决问题的方法来的...

高级算法工程师 都是自己设计网络,
现成的API内部如何实现的都是明白的,
没有现成可供调用时,自己是有能力设计一个的

深度学习核心

深度学习中计算机干了什么

 
计算机在暴力计算参数,暴力求导,根据你的目标(损失函数要求的方向),计算机在进行全遍历计算,
换句话,计算机依指令行事... 你让它干什么它就干什么

深度学习中算法工程师在干什么

 
算法工程师在设计网络,
算法工程师面前有一个问题,比如,图片一个像素有三色,每色0-255,
多个像素组合出来的视角信息太多了,

上面有个花,
现在你想单把朵花取下来放到一张新图片上,
于是算法工程师发话了,
将这三色 “微分” 一下,拆分成1000个不同的成份,每份都是图像的一部分,
怎么分呢,计算机开始随机就给它拆分成了1000份,

如此做法,猜想一下,这朵花大概...大概会在这1000某一些部分上,
这里就假设这朵花在这1000张新图像上的其中100张上吧
于是算法工程师又发话了,将这1000转成100,
计算机对这1000张图像进行计算,融合,
主要就是看看每张图片上哪个像素重要,哪个不重要,
这样的过程重复了100次,每次得到一个新的维度,100次得到了100维

算法工程师指定了参数的网络结构,
计算机让数据流过所有的网络,
根据损失函数梯度下降反馈的结果不断在调整着网络上的参数,
某一层网络的某个区域重要,就将这个区域的参数升一升,
这一层网络作用不大,就将参数一降再降,然后归于0,
那一层网络作用很大,就将参数一升再降,达到某个值

整个网络上的参数在不断变大变小,不断变换,最后趋于稳定

深度学习竞争点

 
作为算法工程师:比谁设计的网络结构好

作为厂家:比谁家的算力强,谁的硬件让客户,让算法工程师用着舒服

深度学习论文

 
深度学习目前处于高速发展阶段,各种论文,甚至构架都在不断新生 

重在学习论文的思想,解决问题的主方法,
细节与实现的技巧,参考一下就可以了 

参考