AI-2

干货|卷出顶级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)
  • 现在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可能需要考虑更换源,例如
    • 有些时候因为网络不畅而导致下载很慢,可以手动在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
      • 组织训练、测试和验证数据最常用的类,可以定义批大小、是否随机打乱等等,还支持继承该类进行自定义
  • 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在某些时候(例如二维矩阵点积)可能比对应的函数慢,但有时(例如高维矩阵点乘求和)却可能比相应的函数快