记一次模型debug
记一次模型debug
模型简介
链接
简介
这是一个对AMP(抗菌肽)做二元分类和回归的sota模型,基于pytorch。
训练
拉取
首先从github上拉取代码:
git pull https://github.com/William-Zhanng/SenseXAMP.git
结果喜提command not found。
因为用的是学校的算力平台,确实不太熟,还以为没装git,问了别人才知道原来算力节点不能联网,所有联网操作只能在shell上做……
改到shell,git pull成功。
现在我的文件结构是:
SenseXAMP/
├── Ampmm_base/
│ ├── data/
│ ├── models/
│ ├── runner/
│ └── utils/
├── configs/
│ ├── cls_task/
│ │ ├── benchmark_imblanced_SenseXAMP.py
│ │ ├── benchmark_blanced_SenseXAMP.py
│ │ ├── ...
│ │ └── Your_own_config.py
│ └── reg_task/
│ ├── ecoli_SenseXAMP.py
│ ├── saureus_SenseXAMP.py
│ ├── ...
│ └── Your_own_config.py
├── experiments/
├── tools/
├── utils/
├── requirements.txt
└── run.py
比文档上要求的结构差一个datasets。
准备数据集
根据文档,在执行训练命令前要准备齐全部4种数据:
1.ori_datasets
.csv格式,可以直接从链接下载。
2.esm_embeddings
.h5格式,由于它很大,只能从tools/esm_emb_gen.py脚本生成。
3.stc_info
.h5格式,是通过根据序列计算蛋白质描述符而获得的,既可以直接下载也可以用tools/stc_gen.py脚本生成。
4.stc_datasets
.csv格式,只能通过脚本tools/generate_csv.py获得。
这里边可以直接下载的数据集都很顺利,全程点鼠标就解决了,但是在生成esm_embeddings时反复遇到报错:
max() argument is an empty list.
说我对一个空列表取了最值,于是进文件找到报错行:
...
assert (mode == 'all') or (mode == 'pooling') or (mode == 'cls_token')
os.makedirs(outdir, exist_ok=True)
# 这里self.all_seqs是空的
self.max_len = max(len(seq) for seq in self.all_seqs)
max_len = 64
if mode == 'all':
max_len = self.max_len
print("Max length: {}".format(self.max_len))
...
self.all_seqs一开始被定义为空列表,那么就找它是在哪被填充的:
def get_seqs_from_datasets(self, datasets_list: List[str]):
"""
Get all sequences from list of datasets
Args:
datasets_list: List[String]
"""
for file in datasets_list:
df = pd.read_csv(file)
self.all_seqs.extend(df['Sequence'].values)
# 这里把解析的csv填进了列表
self.all_seqs = set(self.all_seqs)
找到之后可以开始debug,首先检查datasets的路径,没问题。
之后把for循环单拎出来放在结尾挨个输出csv,也能成功输出:
for file in datasets_list:
df = pd.read_csv(file)
self.all_seqs.extend(df['Sequence'].values)
print(df['Sequence'].values)
说明其实内容一开始是能填进来的,后来在某个地方被弄没了。找遍文件发现这里边没有别处在使用这个列表,也就是说里边的内容初次填充后不会再被改动,那就无需投鼠忌器,直接把整个填充逻辑放到末尾,填完马上用:
for file in datasets_list:
df = pd.read_csv(file)
processor.all_seqs.extend(df['Sequence'].values)
print(processor.all_seqs)
processor.generate_embeddings('./datasets/esm_embeddings/all', mode='all', fname=args.fname)
再次执行生成命令,果然不报错了。
执行训练命令
按它开头的说法,我以为只要我准备好数据就可以去训练了,结果我执行完命令后立刻报错no such file or directory. 说我缺少文件cls_benchmark.h5。
我检查了所有数据包,确实没这个文件。但是文档上给的文件树上有,我就奇怪它到底是哪来的,结果在tips里找到了,这个部分的原文是这样的:
他给了一个生成AMPlify.h5的命令,但是却不给更加关键的cls_benchmark.h5怎么生成,甚至把它放到了tips里……只能说以后看文档一句也不能落。
修改这条命令为:
python tools/esm_emb_gen.py --dataset_dir ./datasets/ori_datasets/cls_benchmark_imbalanced/ --fname cls_benchmark.h5
可以生成cls_benchmark.h5。
继续运行训练命令:
CUDA_VISIBLE_DEVICES=0 python -m torch.distributed.launch --nproc_per_node 1 run.py \
--config ./configs/cls_task/benchmark_balanced_SenseXAMP.py --mode train
这次坚持的时间稍微长一点,但是熟悉的Failures:
run.py: error: unrecognized arguments: --local-rank=0
他说local-rank这个参数未识别,我的命令里没有,那就去代码里找,发现有:
parser.add_argument('--local_rank', type=int, default=0)
注释掉还是一样的错误,上stackoverflow寻得解决方案:在命令中加上--use_env参数,
CUDA_VISIBLE_DEVICES=0 python -m torch.distributed.launch --nproc_per_node 1 --use_env run.py \
--config ./configs/cls_task/benchmark_balanced_SenseXAMP.py --mode train
果然可以了。
为什么这样可以?
首先local_rank一般是需要动态获取的(也就是获取当前进程在本机上的rank),而不是由用户设置为固定值。
这个命令行参数“--loacl_rank”是必须声明的,但它不是由用户填写的,而是由pytorch为用户填写,也就是说这个值是会被自动赋值为当前进程在本机上的rank。但是有的新手(比如我),就会把这个参数理解为是需要用户声明,而用户声明的值会覆盖pytorch为用户生成的值,因此就会产生莫名其妙的错误。
问题扩展(内容来自https://zhuanlan.zhihu.com/p/501632575):
现在命令行参数“--loacl_rank”的问题解决了,还以一个问题,就是还有很多大佬的代码在分布式训练中并没有声明命令行参数“--loacl_rank”,但程序同样可以运行,这是为什么呢?
回答这个问题首先需要解释一下pytorch分布式训练的启动方式(当然这种方式官方已经建议废弃,但在很多SOTA论文的代码中都使用这种方式,所以有必要了解):
python -m torch.distributed.launch --nproc_per_node 3 --use_env main.py
其中“--nproc_per_node”是每个节点的进程数量,“main.py”是程序的入口脚本。但“--use_env”这个参数,很多人拿来就用了,并没有注意它是干什么的,官方是这样解释的:
大概意思就是说,声明“--use_env”后,pytorch会将当前进程在本机上的rank添加到环境变量“LOCAL_RANK”中,而不再添加到args.local_rank。
# d.py
import os
import argparse
def main(args):
local_rank = args.local_rank
print(local_rank, os.environ['LOCAL_RANK'])
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("--local_rank", type=int)
args = parser.parse_args()
main(args)
也就是说如果声明“--use_env”那么pytorch就会把当前进程的在本机上的rank放到环境变量中,而不会放在args.local_rank中。
同时上面的输出大家可能也也注意到了,官方现在已经建议废弃使用torch.distributed.launch,转而使用torchrun,而这个torchrun已经把“--use_env”这个参数废弃了,转而强制要求用户从环境变量LOACL_RANK里获取当前进程在本机上的rank(一般就是本机上的gpu编号)。关于这样更改后的新写法大家可以参考: 官方文档,以及github上facebookai实现的**detr**
总结
我从9号下午拿到这个模型,到10号凌晨3点左右才训练成功,我觉得一半原因是学校鬼畜的算力平台(期间我还反复弄了conda和torch、cuda、nccl环境什么的),另一半是因为这个模型没有一个issue,网上根本搜不到,遇到问题根本没处搜,只能看源码,这样确实是对debug能力有极大的提升效果,不过下次我不想提升了。