1. 提取特征 2. 分类 提取特征,层 在CV领域,提取特征的一种方法是 卷积 在NLP领域,提取特征的一种方法是 循环神经网络 注意力也能提取特征,通用于CV,NLP 分类: 全连接 中间会穿插一系列增加模型拟合能力的方法: BN MAXPOOL RULE DROPOUT
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,并不加载运行,只是为其他程序提供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, 那一层网络作用很大,就将参数一升再降,达到某个值 整个网络上的参数在不断变大变小,不断变换,最后趋于稳定
深度学习竞争点
作为算法工程师:比谁设计的网络结构好 作为厂家:比谁家的算力强,谁的硬件让客户,让算法工程师用着舒服
深度学习目前处于高速发展阶段,各种论文,甚至构架都在不断新生 重在学习论文的思想,解决问题的主方法, 细节与实现的技巧,参考一下就可以了