【senta-skep从git拉源码部署】

本文详细记录了如何在git上部署senta-skep项目,包括环境设置(Python库版本、CUDA路径等)、路径配置(LD_LIBRARY_PATH、CUDA_VISIBLE_DEVICES等)、参数配置(run_train.sh与lanch.py的参数)以及训练和预测过程的解决步骤。遇到的问题如内存不足和GPU使用情况也做了分享。
摘要由CSDN通过智能技术生成

内容:关于git上senta项目的复现

首先友情提示,可以选择飞桨框架进行部署会更方便一些
本文的方法是直接在git上的项目源码拉下来的
感觉项目的参数说明太简陋了,有一些是直接或猜或摸索的结果,记录一下
其实直接跟着官网命令就可以了

配置环境

1.环境搭建

python包:

nltk==3.4.5
numpy==1.14.5
six==1.11.0
scikit-learn==0.20.4
sentencepiece==0.1.83

要求的几个库按照git上提示一起安装也行,我的基本都有

总体来说:
因为项目是感觉很久没有更新了,其实我的一些版本不一样但是也可以,有的存在版本冲突,我调高调低一些也没有影响,这个自己试一下(然后看一下后面项目的提问也基本没有回复,可能不做维护了吧)
#如果存在scipy版本冲突,你可以选择scipy==1.4.0的,这个版本可以和上面那些共存

2.路径配置

env.sh

set -x
#在LD_LIBRARY_PATH中添加cuda库的路径
export LD_LIBRARY_PATH=/home/hhhhhhhh/opt/cuda/cuda-10.2/lib64:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/home/hhhhhhhh/opt/cuda/cuda-10.2/extras/CUPTI/lib64:$LD_LIBRARY_PATH
#在LD_LIBRARY_PATH中添加cudnn库的路径
export LD_LIBRARY_PATH=/home/hhhhhhhh/opt/cudnn/cuda/lib64:$LD_LIBRARY_PATH
#需要先下载NCCL,然后在LD_LIBRARY_PATH中添加NCCL库的路径
export LD_LIBRARY_PATH=/home/hhhhhhhh/opt/nccl/lib:$LD_LIBRARY_PATH
#如果FLAGS_sync_nccl_allreduce为1,则会在allreduce_op_handle中调用cudaStreamSynchronize(nccl_stream),这种模式在某些情况下可以获得更好的性能
export FLAGS_sync_nccl_allreduce=1
#表示分配的显存块占GPU总可用显存大小的比例,范围[0,1]
export FLAGS_fraction_of_gpu_memory_to_use=1
#选择要使用的GPU
export CUDA_VISIBLE_DEVICES=0,1,2,3
#表示是否使用垃圾回收策略来优化网络的内存使用,<0表示禁用,>=0表示启用
export FLAGS_eager_delete_tensor_gb=1.0
#是否使用快速垃圾回收策略
export FLAGS_fast_eager_deletion_mode=1.0
#垃圾回收策略释放变量的内存大小百分比,范围为[0.0, 1.0]
export FLAGS_memory_fraction_of_eager_deletion=1.0
#设置fluid路径
export PATH=fluid=/home/hhhhhhhh/miniconda3/envs/py37/lib/python3.7/site-packages/paddle/fluid/:$PATH
#设置python
alias python=/home/hhhhhhhh/miniconda3/envs/py37/bin/python
set +x

暂时是这个样子

3.参数配置

调用顺序:
run_train.sh-------->>lanch.py---------->>train.py

#以下主要是记录自己的探索过程,如果只是想跑通的话可以直接按照官网readme进行,基本是没有问题的,也可以直接跳到4.运行过程

run_train.sh:

#!/bin/sh
source ./env.sh
#env.sh包含环境路径,如果bashrc都指定过了,可以直接把几个非路径的export拿进来去掉这句
set -eux
#这里设置了两种ip,这个不用动会自动读取的
#node_ips包含了所有你分布式训练的current_node_ip
#current_node_ip是一个字符串
export node_ips=`hostname -i`
export current_node_ip=`hostname -i`
selected_gpus="0"
train_json=$1
log_dir="./log"
output_dir="./output"

rm -rf $log_dir
 #这里涉及的就是log和输出,每一次run都会覆盖上次结果,如果你想留下了之前的model或者log进行分析,可以训练完了mv output/ _output/另存,不然就会被清掉,血泪教训
rm -rf $output_dir
mkdir -p $log_dir
mkdir -p $output_dir
#这里设置一起传入lanch.py的参数,具体的含义在lanch文件最上面的参数定义都有说明
distributed_args="--node_ips ${node_ips} \
                --node_id 0 \
                --current_node_ip ${current_node_ip} \
                --nproc_per_node 1 \
                --selected_gpus ${selected_gpus} \
                --split_log_path ${log_dir} \
                --log_prefix train"
python -u ./lanch.py ${distributed_args} ./train.py  --param_path ${train_json} --log_dir ${log_dir}

run_train.sh是shell脚本,设置了一些参数,最后一行命令比较重要:

python -u ./lanch.py ${distributed_args} ./train.py --param_path ${train_json} --log_dir ${log_dir}

是这个意思:
python + -u + ./lanch.py + lanch的参数
lanch的参数包含: distributed_args 和 (./train.py + train.py 的参数)
train.py 的参数包含:–param_path ${train_json} 和 --log_dir ${log_dir}

其中${train_json} 提供上面语句要的:
train_json=$1
输入,表示输入的奇异果参数
${log_dir}是上面直接定义了的

这个地方就是sh调用lanch,然后lanch里面再调用一个子进程

插入说一个点:

这里面有一个逻辑,就是lanch.py是为了分布式训练的,如果不需要,直接可以跳过lanch,用run_train.sh调用train就可以,类似命令改成了:

python -u ./train.py  --param_path ${train_json} --log_dir ${log_dir}

反正就是按上面说明的参数意义往里套,如果做自己的框架这里可以自己改

接下来是lanch.py:

"""lanch.py"""
# 前面是有关参数定义,解释的比较清楚就不再说了

def start_procs(args):
    """start process"""
    procs = []
    log_fns = []
    default_env = os.environ.copy() #这里是直接copy了系统环境做初始值
    node_id = args.node_id
    node_ips = [x.strip() for x in args.node_ips.split(',')]#所有ip切分成列表成员
    current_ip = args.current_node_ip
    num_nodes = len(node_ips)
    selected_gpus = [x.strip() for x in args.selected_gpus.split(',')]#所有gpu切分成列表成员
    selected_gpu_num = len(selected_gpus)#计算现有gpu的个数

    all_trainer_endpoints = ""#记录上一次训练结束的位置
    for ip in node_ips:
        for i in range(args.nproc_per_node):
            if all_trainer_endpoints != "":
                all_trainer_endpoints += ","
            all_trainer_endpoints += "%s:618%d" % (ip, i) #比如说=127.0.0.1:6180

    nranks = num_nodes * args.nproc_per_node #节点数*每个节点上要使用的进程数
    gpus_per_proc = args.nproc_per_node % selected_gpu_num 
    #每个gpu处理进程数 = 每个节点上要使用的进程数 %整除 你选择使用的gpu个数
    #整除干净就刚刚好,不然还要多一个,这个不明白的纸上画一画
    if gpus_per_proc == 0:
        gpus_per_proc =  selected_gpu_num // args.nproc_per_node
    else:
        gpus_per_proc =  selected_gpu_num // args.nproc_per_node + 1

    selected_gpus_per_proc = [selected_gpus[i:i + gpus_per_proc] for i in range(0, len(selected_gpus), gpus_per_proc)]
    #log的部分也不需要说直接跳过了
    current_env = copy.copy(default_env)
    procs = [] #这个应该是写忘了多出来了吧
    cmds = []#这个是命令列表
    log_fns = []
    #这里开始分布式执行
    for i in range(0, args.nproc_per_node):
        trainer_id = node_id * args.nproc_per_node + i
        #trainer_id=node_id*每个节点上的进程+i,也就是说0*8+0,0*8+1,0*8+2,0*8+i...1*8+i...
        #对环境做更新,每一轮都在上一轮的基础上修改
        current_env.update({
            "FLAGS_selected_gpus": "%s" % ",".join([str(s) for s in selected_gpus_per_proc[i]]),
            "PADDLE_TRAINER_ID": "%d" % trainer_id,
            "PADDLE_CURRENT_ENDPOINT": "%s:618%d" % (current_ip, i),
            "PADDLE_TRAINERS_NUM": "%d" % nranks,
            "PADDLE_TRAINER_ENDPOINTS": all_trainer_endpoints,
            "PADDLE_NODES_NUM": "%d" % num_nodes
        })

        cmd = [sys.executable, "-u",
               args.training_script] + args.training_script_args #后面把这里cmd命令改了
        cmds.append(cmd)
        #cmd命令有时候是“python test.py”,有时候是列表["命令","test.py"]
        #这个地方的cmd=[],之后还+了的就是train的参数,因为cmd一开始只对应python -u ./lanch&{xxxxx, ./train.py} 这部分,使用需要把train的参数再加上去
        #我单步执行的时候cmd打印出来:
        #['/home/hhhhhhhh/miniconda3/envs/py37/bin/python', '-u', './train.py', '--param_path', './config/roberta_skep_large_en.SST-2.cls.json', '--log_dir', './log']
        if args.split_log_path:
            fn = open("%s/%s.job.log.%d" % (args.split_log_path, args.log_prefix, trainer_id), "a")
            log_fns.append(fn)
            # subprocess是开始执行一个子进程,执行刚刚组合的cmd命令
            process = subprocess.Popen(cmd, env=current_env, stdout=fn, stderr=fn) #bug点
        else:
            process = subprocess.Popen(cmd, env=current_env)
        procs.append(process)
    #下面就是对返回结果做处理,正常返回是0
    for i in range(len(procs)):
        proc = procs[i]
        proc.wait() #这个地方如果去掉的话执行的终端上不会显示结束
        if len(log_fns) > 0:
            log_fns[i].close()
        if proc.returncode != 0:    
            logging.info("proc %d run failed" % i)
            raise subprocess.CalledProcessError(returncode=procs[i].returncode,
                                                cmd=cmds[i])
        else:
            logging.info("proc %d run success" % i)
#后面是做了一个入参的解析,把结果传入主函数,主函数调用start_procs并传入参数

关于subprocess这个地方就比较有说头,如果lanch.py中执行调用train过程中train结果出错了,会在lanch这里直接执行会报错:

Traceback (most recent call last):
  File "./lanch.py", line 131, in <module>
    main(lanch_args)
  File "./lanch.py", line 124, in main
    start_procs(args)
  File "./lanch.py", line 115, in start_procs
    cmd=cmds[i])
subprocess.CalledProcessError: Command '['/home/hhhhhhhh/miniconda3/envs/py37/bin/python', '-u', './train.py', '--param_path', './config/roberta_skep_large_en.SST-2.cls.json', '--log_dir', './log']' died with <Signals.SIGABRT: 6>.

错误定位在subprocess这个地方,但是查log要看train.log和train.job.log.0,最后说回到这句的函数本体,去查了subprocess.popen函数本身的定义:

class subprocess.Popen(args, bufsize=-1, executable=None, stdin=None, stdout=None, stderr=None, 
preexec_fn=None, close_fds=True, shell=False, cwd=None, env=None, universal_newlines=False, 
startupinfo=None, creationflags=0,restore_signals=True, start_new_session=False, pass_fds=(),
*, encoding=None, errors=None)

这就是插入的下一个知识点:
两种cmd表达方式:

cmd = ['python', 'test.py']
cmd = "python test.py"

但是执行subprocess的时候稍有不同,使用shell=True这个参数,字符串的cmd就可以被识别了
直接看一个例子:

import subprocess
	def cmd(command):
	subp = subprocess.Popen(command)
	subp.wait(2)
	print("成功")
cmd(["python","1train.py"])

#------------enter test.py------------
#OK-----------
#成功

再看

import subprocess
	def cmd(command):
	subp = subprocess.Popen(command, shell=True)
	subp.wait(2)
	print("成功")
cmd("python 1train.py")

#------------enter test.py------------
#OK-----------
#成功
#shell:如果该参数为 True,将通过操作系统的 shell 执行指定的命令

但是如果你的cmd是列表,但是你在process里面加了shell=True这个参数,会报下面的错
在这里插入图片描述
修正过后:
在这里插入图片描述
#这里我为了方便直接把train的内容注掉了,就是为了看一下cmd命令用法
#把train.py内容注掉,只保留参数的入口,写一个print打印一句话,看看命令参数交互过程有没有问题:

cmd = "python train.py --mode=train --param_path=./config/ernie_1.0_skep_large_ch.Chnsenticorp.cls.json --log_dir=./log --paddle_version=1.5.2"

结果可以在fn指定的文件里面看到:
在这里插入图片描述
(是的,我属于完全自学,很多东西都是看定义+瞎试出来的,因此有些时候会有理解错误,或者有时候观察到现象但是没有联系到知识点本质,和本专业的还是有差距)

#然后我又把命令改一下,看着更明显:

cmd = "cat ./i.txt"          #内容是print(”------------------i.py success---------------“)
process = subprocess.Popen(cmd,env=current_env, stdout=fn, stderr=fn, shell=True)

这样是没有错的,执行cat命令,
在这里插入图片描述
打开log文件夹里面的train.log.job.0可以看到:
在这里插入图片描述
这里就是可以看到,如果不指定fn的话,这句话会在终端直接输出:
process = subprocess.Popen(cmd,env=current_env, shell=True)
这个时候就可以直接输出cat i.txt的内容:
在这里插入图片描述

运行过程

1.训练运行

可以直接跳到到这里跟着跑程序了



我按照正常的来,执行

sh ./script/run_train.sh ./config/roberta_skep_large_en.SST-2.cls.json

这一步爆出一个错误:

Traceback (most recent call last):
  File "./lanch.py", line 131, in <module>
    main(lanch_args)
  File "./lanch.py", line 124, in main
    start_procs(args)
  File "./lanch.py", line 115, in start_procs
    cmd=cmds[i])
subprocess.CalledProcessError: Command '['/home/hhhhhhhh/miniconda3/envs/py37/bin/python', '-u', './train.py', '--param_path', './config/roberta_skep_large_en.SST-2.cls.json', '--log_dir', './log']' died with <Signals.SIGABRT: 6>.

(记住这里,这个错误显示在lanch中,但是定位语句是在子进程,因此直接去看train的log内容,lanch本身没啥内容基本不会有错误)
然后查看train.job.log.0发现是个memory error:
在这里插入图片描述
是我指定gpu内存不够了,然后查看一下换一个可用的仔细看了一下报错之后的说明,其实解释的还是很详细的:

Out of memory error on GPU 0. Cannot allocate 76.000244MB memory on GPU 0, available memory is only 38.187500MB.
Please check whether there is any other process using GPU 0.

1. If yes, please stop them, or start PaddlePaddle on another GPU.
#如果你是gpu0上还有其他进程,把他关闭(这个不可能)
2. If no, please try one of the following suggestions:
   1) Decrease the batch size of your model.
   2) FLAGS_fraction_of_gpu_memory_to_use is 1.00 now, please set it to a higher value but less than 1.0.
      The command is `export FLAGS_fraction_of_gpu_memory_to_use=xxx`.
#或者把batch size缩小
#或者在参数FLAGS_fraction_of_gpu_memory_to_use上设置比例修改一下,这个在env.sh

我把selected_gpu=“0,1,2,3”,这次是打开显存空间看着:
在这里插入图片描述
惊呆

因此应该是真的是内存不够用,然后我只能将我的batchsize该到了1
但是发现了一个问题,4个显卡中只有一个在计算,另外3个顶满之后不动了,然后后三个之中有一个爆出memory error之后本次训练停止,我将改回来:
单个selected_gpu="2"配合batchsize=1(2也可,4就不行了)
这样就可以执行成功了:

在这里插入图片描述
然后可以在output里面看到成果:
(这个是中文的训练结果,都一样)
在这里插入图片描述
这个就是你想要的模型,里面可以看一下:
在这里插入图片描述
这里的参数模型文件都是在一起的,还有一种是会有一个params的文件夹,然后里面是分层存放你的各种参数,这是对输出的格式设置决定的,需要的可以自己改一下

补充一下:
batch_size的修改在./config/ernie_1.0_skep_large_ch.Chnsenticorp.cls.json
在这里插入图片描述
dataset_reader包含了reader+encoder的功能
在这里插入图片描述

2.预测过程

预测就按照:

sh ./script/run_infer.sh ./config/ernie_1.0_skep_large_ch.Chnsenticorp.infer.json

但是./config/ernie_1.0_skep_large_ch.Chnsenticorp.infer.json参数应该修改
你可以看到output里面有一个文件resultbest,这个是保存了你训练过程中最好模型的信息(只是信息不是模型)
在这里插入图片描述
可以看到训练过程中最好的是4090这个,对于的acc是0.9425
最好单独把模型保存一份,好不容易训出来的,一下搞没了哭都没地方哭
再次提醒output会覆盖
下一步预测就要用这个最好模型:
./config/ernie_1.0_skep_large_ch.Chnsenticorp.infer.json:
在这里插入图片描述
主要就是把你的最好模型换进来,然后执行命令开始预测:
(注意,不论训练还是预测,output都是覆盖的,自己有需要最好保存了)
在这里插入图片描述
此时的output就会
在这里插入图片描述
或者你嫌麻烦的话可以给train和infer设置不同输出路径,然后对应config也记得要改成你对应的
比如:
“test_save”: “./output_infer/inference/test_out.tsv”,
这样也不用你每次把前一个结果挪出去了,无所谓咋都行
test_out.tsv里面是这样的:
在这里插入图片描述
好了,也没啥了,基本就是顺一遍过程,也没啥高深内容,就当是笔记而已

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值