干货|卷出顶级AI实验室(2)
进阶
(本人已授权小红书转载)
一、对AI的认知:
深度学习是个很强大的工具,万能近似定理(universal approximation theorem)[Hornik, 1991]理论证明具有隐含层(最少一层)感知机神经网络在激励函数(也称激活函数)为非常数函数的情况下具有逼近任何函数的作用。
-
然而理论只是证明了其可能性,通常难以得到这种理想的状态,因此对于一般的深度学习模型来说,通常会设计比较复杂的结构,一方面增大其表达能力,从而得到更好的性能;另一方面增大其对参数的容错能力,使其更容易达到较好的性能。
-
从万能近似定理也可以看出来,神经网络一般都由线性结构和激励函数(非线性结构)组成,最简单的神经网络结构就是由激励函数分开的全连接层(多层感知机,MLP)。
-
然而,全连接层参数众多,需要的资源太多、计算速度太慢,因此,将全连接层替换为卷积层,将激励函数变为池化层(maxpool或者avgpool),就变成了最基本的卷积神经网络(CNN),著名的LeNet和AlexNet就是CNN,只不过它们用的是多channel的卷积核。
-
以上的网络都是从前往后依次序的结构,在反向传播的时候容易产生遗忘(梯度消失)的现象,越靠近输入的参数梯度的信号越弱,导致模型容易忽视底层所提取到的特征,例如边缘、纹理等等。因此,ResNet加上一个shortcut连接,即x’=x+F(x),这样,梯度就能以更快的速度传回前面的模块,从而使得模型能够更好地注意底层特征。而DenseNet进一步推广,在每两个模块之间都加上shortcut连接。
从以上的例子可以看出来,深度学习的研究的一种思路就是看到某个问题需要解决,就设计出一个巧妙的结构去解决它。例如,看到RNN容易导致遗忘问题,就让输入的序列两两之间交互,于是便有了attention。而如果是QA,则需要让输入和输出序列之间、输出序列各token之间都有交互,于是就有了Transformer。
然而,深度学习虽然是万能的工具,但通常需要极其大的数据量去训练(例如GPT4、Kimi Chat等目前较好的大语言模型需要上TB的tokens),而对于一些数据比较少的较简单的任务(比如我之前做的多组学探究只有数百个样本),深度学习往往难以奏效,这时需要考虑一些其它的机器学习算法,例如XGBoost、KNN、SVM等。通常这些算法能带来相当令人满意的精度,其中XGBoost结合SHAP[S. Lundberg&Su-In Lee, 2017]等工具还可以做出比较好的可解释性分析。
对于不同的领域,有不同的研究风格,例如NLP领域,目前LLM就是在Transformer结构上不断进行扩大、修改结构(如pre-norm和post-norm、positional embedding、修改激活函数),而传统NLP则将机器学习算法、数据库等技术融入到BERT等语言模型中,用来实现即插即用、个性化等功能。计算机视觉领域则是在基本CNN上不断推陈出新,如diffusion模型(大家可以想一下为什么diffusion模型用在文本生成任务上表现不尽人意)。另外,对于AI4Science这个方向,因为数据类型和数据量各异,则是要用到多种算法,甚至需要用到一些传统机器学习算法,例如像DNA序列、蛋白质序列等,可以用Transformer(例如Alphafold);对于像核磁共振等图形,可以用CNN、diffusion等模型;而像组学研究,特征离散且数量巨多,则可以用XGBoost算法。
二、常用工具Pytorch介绍
作为AI相关的研究者,最痛苦的莫过于安装环境。一般会去官网下载安装Anaconda或者Miniconda,而对于AI研究者来说,最重要的包莫过于PyTorch了。
- 所有与PyTorch相关的问题都最好先在https://pytorch.org/上面查询一遍,尤其是查询PyTorch文档(上边栏的Docs下拉菜单里面的PyTorch,直接点搜索符号出来的是Blog)
- 对于英文不太好的uu,可以查找PyTorch中文文档:https://pytorch-cn.readthedocs.io/zh/latest/,不过要想深入科研,还是得提高英语能力,学会适应英文文档
- 现在PyTorch的版本已经更新到2.1.x了,理论上兼容1.x的版本,但是会有一些接口上的不同。
- 安装PyTorch
- 可以到官网https://pytorch.org/get-started/locally/查找对应的安装命令,请注意,官网安装命令默认安装最新版本的PyTorch,实际情况因为算力太高、CUDA版本太低或者python版本较老,可能需要寻找较老版本的PyTorch
- 安装PyTorch需要先考虑显卡的算力,有些高端显卡(例如GeRorce RTX3090、4090),算力太高,低版本的PyTorch不支持。一些极端情况,高端显卡配上低版本的CUDA,没有合适的PyTorch版本,因此,这种情况需要更新CUDA(总不可能换块显卡吧QAQ)。CUDA版本可以通过nvidia-smi命令查看右上角CUDA处或者nvcc -V查询(但需要注意的是,这两者查到的版本可能是不同的。通常后者版本不大于前者的版本就行)
- 根据对应的CUDA版本,查询对应的PyTorch适合的版本(https://pytorch.org/get-started/previous-versions/上cudatoolkit的可选范围)
- 有时国内的一些服务器不能访问torch官网,因此安装pytorch可能需要考虑更换源,例如
- torch==1.9.0 -f https://download.pytorch.org/whl/cu110/torch_stable.html
- 有些时候因为网络不畅而导致下载很慢,可以手动在https://download.pytorch.org/whl/cu1xx/torch_stable.html(例如https://download.pytorch.org/whl/cu110/torch_stable.html)上面寻找到合适的安装包
- 用torch.cuda.is_available() (记得先import torch)命令查看你的pytorch版本是不是装正确了(你显卡能不能用)
- PyTorch的一些重要包
- torch.nn.Module和torch.nn.functional
- 前者是一个类,除了要进行对应的计算,还要对模块中的参数等进行管理,而后者只是单纯的计算,需要手动提供对应的参数。
- 例如nn.Linear包含了一个全连接层,内含了全连接层的weight和bias(如果设定bias=True)参数,而nn.functional.linear需要手动提供weight和bias(默认None)
- nn.Module其实是一大堆常见模块,例如n维卷积层、池化层(最大池化Maxpool、平均池化Avgpool等)、非线性层、归一化层(组归一化、批归一化、例归一化)、整合结构(GRU、RNN、LSTM、Transformer(含Encoder、Decoder和单独的layer))、全连接层、dropout、损失函数等等,其中除整合结构外,绝大部分都有对应的nn.functional函数。
- 一般各种神经网络用到的就是nn.Module下面的各种模块(为了适配nn.sequential()),而nn.functional一般会用于loss的计算以及模块间和最终输出前单独的非线性函数。
- 这两个再配合torch.view/reshape等函数,就能搭建一个比较复杂的神经网络了
- torch.optim
- 这里面包含了一些常见的optimizer,如SGD、Adam、AdamW、RMSprop;以及常用的学习率调节函数(torch.optim.lr_scheduler),如LinearLR,OneCycleLR等等
- torch.distributed
- 对于大型项目(如LLM),可能会涉及到多张显卡,因此显卡之间的通讯就非常重要,这里面包含了许多分布式操作函数,例如DDP
- torch的梯度机制
- 一般的流程:
- for epoch in range(num_epoch):
- model.train(); for x, target in train_dataset: model.zero_grad(); y = model(x); loss = loss_function(y, target), loss.backward(); optimizer.step(); scheduler.step()
- model.eval(); total_loss = 0; for x, target in eval_dataset: model.zero_grad(); y = model(x); total_loss += loss_function(y, target); total_loss = total_loss / len(eval_dataset)
- if total_loss < min_loss: torch.save(model)
- model.train()和model.eval():启停模型其中的batch norm和dropout
- loss.backward():传入反向传播命令
- optimizer.step():有时需要手动调用optimizer和scheduler去调整训练梯度的参数和学习率
- with torch.no_grad()
- pytorch有自动求导机制,梯度计算是累加式的,每推算一次就会累加一次梯度相关的参数,因此在evaluation推理期间,需要用这个语句停止自动求导
- 取消自动求导可以降低存储需要,提高计算速度
- 一般的流程:
- torch.nn.init
- 在神经网络初始化时,通常是随机的参数,而用Xavier、Kaiming等初始化策略,可以使得神经网络在初始时就能让反向传播各层方差几乎相等,从而降低梯度收缩和梯度爆炸的可能性。
- torch.utils.data.Dataset
- 组织训练、测试和验证数据最常用的类,可以定义批大小、是否随机打乱等等,还支持继承该类进行自定义
- torch.nn.Module和torch.nn.functional
- PyTorch一些容易被忽略的功能
- torchsummary
- 可以直接打印函数每一层输出的size,还可以统计每一层的参数数量和总参数量方便写函数的时候debug
- 用pip install torchsummary
- from torchsummary import summary
- summary(your_model, input_size=(bsz, channels, H, W)),其中input size根据设计模型的输入变化,不一定是4维
- torch.einsum
- 对于形象能力强但抽象能力弱的同学,能够形象描述对应的操作,但很难写对应代码的uu,可以试着用torch.einsum
- 这是基于爱因斯坦求和约定:如果两个相同的指标出现在指标符号公式的同一项中,则表示对该指标遍历整个取值范围求和。
- 一般形式:torch.einsum(用符号描述你需求的字符串,输入的list),字符串的格式为:“矩阵1的shape、矩阵2的shape…->输出矩阵的shape”。
- 例如:
- torch.einsum(‘ij, jk->ik’, [a, b])就是求a与b的矩阵乘法;
- torch.einsum(‘jj’, a)就是求矩阵的迹;
- torch.einsum(‘…ij-> …ji’, A)就是把A(不管有几维)的最后两维进行转置;
- torch.einsum(‘ij->’, a)就是矩阵求和
- torch.einsum(‘ij->j’, a)就是按列求和
- einsum在某些时候(例如二维矩阵点积)可能比对应的函数慢,但有时(例如高维矩阵点乘求和)却可能比相应的函数快
- torchsummary