0.预备知识 收先我们知道,一般大型公司都有一台机器才能够外网访问,其他机器都只能访问内网,我们把进行外网访问的机器称为跳板机,他的作用就是,我们通过这个机器来远程俩连接到内网的跳板机,然后再这个跳板机上进行连接到真正的内网机器。我们可以使用端口转发,ssh -N来进行转发内网得到机器端口到本地的机器端口,可以转发内网机器node5的端口准发到我自己的windows的 10086端口,字样,我们如果需要连接到内网的机器node5,就不需要通过跳板机连接。注意,如果需要使用之恩重方案,一般都需要把自己的公钥上传到跳板机还有内网机器上的authority——key’才可以,等待我们自己的机器进行重亲就可以 。以后我们只需要下面代码就可以连接node5机器
1 ssh -p 1008600 root@localhost
本文是首先讲解的是如何进行远程连接,所以作为预备知识。目前远程连接主要是是使用以下两种方案,一种是vscode的 remote ssh还有一种是使用jetbrain 的gateway。下面介绍这两种方案:
1.vscode进行连接.我们只需要在上面我们已近做好的内网转发上面,进行新建一个vscode远程连接,输入我们 远程连接的上述代码,然后选择我们需要的项目目录就可以了。此时,我们还需要进行设置python解释器,我们一般都是使用自己的虚拟环境,我们我们找到自己虚拟环境所在的vene包里面,然后进行选择到python,就可以了,
主要优点:连接消耗的内存就比较少,但是代码补全还有debug能力不如gateway。一般我们可以直接在vscode进行修改,然后直接在windterm里面来进行执行代码,使用screen
2.使用gateway,这个就是jetbrain推出的远程pycharm,可以这么理解,相当于,我们前端只是进行修改 代码,但是真正的操作都是在服务器的后端服务器上,只不过,这个gateway也还是需要授权才可以使用的,我使用的是教育邮箱,所以可以进行使用。
优点:和我们之前在自己电脑使用pycharm一样,但是由于使用的是远程连接,会造成服务器不稳定,偶尔出现多次断链的情况,也会出现卡死服务器cpu的情况。但是一般都是比较正常(推荐使用gateway)
1.datasets方面 深度学习的第一步就是学习如何写数据集,对于深度学习现在已经变成搭积木的游戏,我们只需要根据之前的官网文件,来对我们的自定义我们的数据集,我们只有知道我们的输入输出后,才可以定制自己的模型。由于我们的是分类模型,所以我们的输入是图片的tensor,输出是图片的类型,使用数字就可以。
对于这个数据集获取器,我们可以使用继承nn的datasets来实现,需要我们写的只有三个函数,一个init,一个len,还有一个getitem。
下面是通用得到实现方法:
在此之前我们自己制作一个通用的csv,奇艺列是图片的地址,第二列就是属性值,我们的dataset就只需要从csv来读取图片还有表示,就可以了,主要使用load——csv来进行读取csv
首先写init,需要传入的参数有,resize,mode,path,三个是图片的大小,当前是什么模式,还有一个是csv存在的地址,之后我们就进行切割数据集,使用6:2:2来进行切分,主要思路,就是把从loadcsv得到完整的列表,然后进行切分
之后就是getitem,我们只需要按照给定的idx,返回对应的图片还有属性值就可以,一般transform可以在外面写好,也可以在dataset你写好,使用transform来进行组合所需要的操作,然后对图片来进行执行就可以了,最后返回图片还有属性值
返回len只需要返回图片列表长度就可以
这是最简单的dataset,我们只需要想明白我们需要的输入还有输出到底是什么就可以了,然后重点就是如何得到这个csv表格,我们可以使用os还有glob这些包来进行对文件夹操作,然后放入到csv里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 def __init__ (self ): self .resize = resize self .mode = mode self .cur_images ,self .syn_images,self .labels = self .load_csv('./p2p.csv' ) if mode == "train" : self .cur_images = self .cur_images[:int (0.6 * len (self .cur_images))] self .syn_images = self .syn_images[:int (0.6 * len (self .syn_images))] self .labels = self .labels[:int (0.6 * len (self .labels))] elif mode == 'val' : self .cur_images = self .cur_images[int (0.6 * len (self .cur_images)):int (0.8 * len (self .cur_images))] self .syn_images = self .syn_images[int (0.6 * len (self .syn_images)):int (0.8 * len (self .syn_images))] self .labels = self .labels[int (0.6 * len (self .labels)):int (0.8 * len (self .labels))] elif mode == 'test' : self .cur_images = self .cur_images[int (0.8 * len (self .cur_images)):int (0.99999 * len (self .cur_images))] self .syn_images = self .syn_images[int (0.8 * len (self .syn_images)):int (0.99999 * len (self .syn_images))] self .labels = self .labels[int (0.8 * len (self .labels)):int (0.99999 * len (self .labels))] def load_csv (self, data_csv ): cur_img = [] syn_img = [] labels = [] with open (data_csv, 'r' ) as f: reader = csv.reader(f) for row in reader: old, new, label = row label = int (label) cur_img.append(old) syn_img.append(new) labels.append(label) assert len (syn_img) == len (cur_img) assert len (labels) == len (syn_img) return cur_img, syn_img, labels def __len__ (self ): return len (self .labels) def __getitem__ (self, idx ): img, syn_image,label = self .cur_images[idx],self .syn_images[idx], self .labels[idx] tf_cnn = transforms.Compose([ lambda x:Image.open (x).convert('RGB' ), transforms.Resize((int (self .resize*1.25 ), int (self .resize*1.25 ))), transforms.RandomRotation(15 ), transforms.CenterCrop(self .resize), transforms.ToTensor(), transforms.Normalize(mean=[0.485 , 0.456 , 0.406 ], std=[0.229 , 0.224 , 0.225 ]) ]) tf_swin = transforms.Compose([ lambda x: Image.open (x).convert('RGB' ), transforms.Resize((int (256 ), int (256 ))), transforms.ToTensor(), transforms.Normalize(mean=[0.485 , 0.456 , 0.406 ], std=[0.229 , 0.224 , 0.225 ]) ]) img = tf_cnn(img) syn_image1 = tf_cnn(syn_image) syn_swin = tf_swin(syn_image) label = torch.tensor(label) return img,syn_image1,syn_swin,label
2.模型方面 模型方面,我改的比较少,只能说还是需要两个,一个是forward函数,一个init来进行初始化,我们在init进行设置所需要的cnn,fc层,设计模型架构,然后再forward里面来进行传入图片,吧图片在上面的架构走一次,我们就得到模型所产生的结果了。这个地方改的不是很多,需要日后来讲解。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def forward(self, x_old, x_new): x_old = self.old_features(x_old) x_new = self.new_features(x_new) x = x_old + x_new heads = [] for i in range(self.num_head): heads.append(getattr(self, "cat_head%d" % i)(x)) heads = torch.stack(heads).permute([1, 0, 2]) if heads.size(1) > 1: heads = F.log_softmax(heads, dim=1) out = self.fc(heads.sum(dim=1)) out = self.bn(out) return out, x, heads
3.train方面 train方面这个代码是需要我自己来进行重构的,耦合比较严重。
3.1参数解析 首先就是设置相关的参数使用argparser来进行设置,一般我都是直接抄之前的代码,资格不需要自己修改,可以直接看绵绵这个,这个就是我们在服务器你启动代码所需在后面进行添加的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 def get_args_parser (): parser = argparse.ArgumentParser('MAE pre-training' , add_help=False ) parser.add_argument('--batch_size' , default=200 , type =int , help ='Batch size per GPU (effective batch size is batch_size * accum_iter * # gpus' ) parser.add_argument('--epochs' , default=200 , type =int ) parser.add_argument('--accum_iter' , default=1 , type =int , help ='Accumulate gradient iterations (for increasing the effective batch size under memory ' 'constraints)' ) parser.add_argument('--input_size' , default=224 , type =int , help ='images input size' ) parser.add_argument('--weight_decay' , type =float , default=0.0001 , help ='weight decay (default: 0.05)' ) parser.add_argument('--lr' , type =float , default=0.0001 , metavar='LR' , help ='learning rate (absolute lr)' ) parser.add_argument('--warmup_epochs' , type =int , default=40 , metavar='N' , help ='epochs to warmup LR' ) parser.add_argument('--root_path' , default='/home/tonnn/.nas/weijia/datasets/face_dataset/Oulu_CASIA_NIR_VIS/NI' , type =str , help ='dataset path' ) parser.add_argument('--output_dir' , default='./output_dir' , help ='path where to save, empty for no saving' ) parser.add_argument('--pth_path' , default='./checkpoint/image1k/accuracy_loss_change_train_2.pth' , help ='path where to save, empty for no saving' ) parser.add_argument('--log_dir' , default='./output_dir' , help ='path where to tensorboard log' ) parser.add_argument('--seed' , default=3407 , type =int ) parser.add_argument('--resume' , default='' , help ='resume from checkpoint' ) parser.add_argument('--start_epoch' , default=77 , type =int , metavar='N' , help ='start epoch' ) parser.add_argument('--num_workers' , default=8 , type =int ) parser.add_argument('--pin_memory' , action='store_true' , help ='Pin CPU memory in DataLoader for more efficient (sometimes) transfer to GPU.' ) parser.add_argument('--no_pin_mem' , action='store_false' , dest='pin_mem' ) parser.set_defaults(pin_mem=True ) parser.add_argument('--world_size' , default=1 , type =int , help ='number of distributed processes' ) parser.add_argument('--local_rank' , default=-1 , type =int ) parser.add_argument('--dist_on_itp' , action='store_true' ) parser.add_argument('--dist_url' , default='env://' , help ='url used to set up distributed training' ) return parser
上面就是需要的参数,我们可以对着上面来修改epoch还有学习率等其他内容,还有resume等,来进行加载
3.2构建数据集 我们使用函数来进行抽取构建数据集这个操作,这样,我们以后更换数据集,只需要更改build’datasest就可以了,方法还是和之前一样的
1 2 3 def build_datasets (mode, args ): dataset = FaceEmotionRecognize(args.root_path, args.input_size, mode) return dataset
上面那个face可以换成其他任意的dataset
3.3分布式训练 我们首先是需要把os的enviroment环境设置为0,1表示是在两个显卡上进行训练,之后我们还需要使用dataparallel还有cuda,来把模型传入到显卡上面
1 2 3 4 os.environ['CUDA_VISIBLE_DEVICES' ] = '0,1' model_swin = nn.DataParallel(model_swin) model_swin = model_swin.cuda()
3.3main函数的构建 main函数主要写的是整体流程,我们首先是是来进行构建数据集还有dataloader,对于train,我们一般使用shuffle,对于val,我们一般是使用不进行shuffle的。
之后就是进行构建模型,这一步,下一步就是进行设置loss函数我们一般使用交叉熵就可以了,然后之后就是使用优化器来优化模型,一般使用adam还有sgd,注意我们需要把模型的参数全部传入到优化器上面才可以(对于想cyclegan’这种,需要联合有何的参数,我们使用uitertools来进行连接两个模型的参数,到这个尤其来进行优化
之后就是来进行构建tensorboard,我们需要来构建tensorboard所在文件目录,之后就是设置log,还有度量值,这个我们直接使用util来进行设置(不用自己写)
1 2 3 4 5 6 os.makedirs(args.log_dir, exist_ok=True ) log_writer = SummaryWriter(log_dir=args.log_dir) loss_scaler = NativeScaler() misc.load_model(args=args, model_without_ddp=model, optimizer=optimizer, loss_scaler=loss_scaler)
下一步就是加载模型,也是使用何开明写好的直接进行加载,我们只需要传入模型,优化器,还有记录的scaler
这一步是我直接把模型进行保存了,然后直接使用load就可以加载出模型,之后我们还是需要把模型移动到显卡上面
1 2 3 4 pth_path = './checkpoint/swin2_base/accuracy_loss_change_train_11cnn.pth' model_cnn = torch.load(pth_path)
对于加载好的模型,我们想修改值的话,可以修改最后一层的圈连接层,对于前面的层数我们全部保留,最后一层的fc,我们来使用linear来进行修改就可以
1 2 3 4 5 model_conv = convnext_tiny(pretrained=True ) model_conv_ftrs = model_conv.head.fc.in_features model_conv.head.fc = nn.Linear(model_conv_ftrs,6 )
之后就是开始for epoch,首先我们进入到验证阶段
我们第一步就是设置模型为eval模式,
然后进入到验证阶段,传入的是数据集,还有模型
返回的就是记录的misc,之后我们对记录的值,传入到log里面,让他写入到tensorboard里面
之后我们就是进行train模式,对于train模式,我们首先就是把model的train来进行打开
传入大还是模型,数据集,还有loss,优化器,log直接在train里面记录到tensor里面
3.3.1验证阶段代码 这一步,我们需要设置为torch nograd才可以进行操作,下面是需要学习的,使用装饰器进行让代码处于在nograd状态下,使用misc来进行记录,接下来就是开始进行评估,得到图像还有属性,传入到显卡上面,使用model来得到结果,之后就是使用soft来得到最大的值(这个就是可能的属性),然后就是top1,top5的计算这个直接使用timm的库可以计算。
之后就是记录在tensorboard里面,记录loss还有acc1还有acc5
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 criterion = torch.nn.CrossEntropyLoss() metric_logger = misc.MetricLogger(delimiter=" " ) header = "Test:" model.eval () for batch in metric_logger.log_every(data_loader, 10 , header): images = batch[0 ] target = batch[-1 ] images = images.to(device, non_blocking=True ) target = target.to(device, non_blocking=True ) with torch.no_grad(): output = model(images) loss = criterion(output, target) output = torch.nn.functional.softmax(output, dim=1 ) acc1, acc5 = accuracy(output, target, topk=(1 , 5 )) batch_size = images.shape[0 ] metric_logger.update(loss=loss.item()) metric_logger.meters['acc1' ].update(acc1.item(), n=batch_size) metric_logger.meters['acc5' ].update(acc5.item(), n=batch_size) metric_logger.synchronize_between_processes() print (f'acc1:{metric_logger.acc1} , acc5:{metric_logger.acc5} ' ) return {k: meter.global_avg for k, meter in metric_logger.meters.items()}
返回的是misc记录的
3.3.2训练函数代码 首先是和之前一样的,把图片还有属性传入到显卡上面,之后使用模型来进行预测,然后设置优化器,之后就是计算交叉熵,迭代次数一般是在batch开始来进行zero_grad,这个修改的我有点看不懂,应该是在求loss的时候就进行更新。
最后一步就是log来进行就misc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 def train_one_epoch (model: torch.nn.Module, criterion: torch.nn.Module, data_loader: Iterable, optimizer: torch.optim.Optimizer, device: torch.device, epoch: int , loss_scaler, max_norm: float = 0 , log_writer=None , args=None ): if log_writer is not None : print (f'log_dir {log_writer.log_dir} ' ) model.train(True ) print_feq = 2 accum_iter = args.accum_iter for data_iter_step, (samples, targets) in enumerate (data_loader): samples = samples.to(device, non_blocking=True ) targets = targets.to(device, non_blocking=True ) output = model(samples) warmup_lr = args.lr optimizer.param_groups[0 ]["lr" ] = warmup_lr loss = criterion(output, targets) loss /= accum_iter loss_scaler(loss, optimizer, clip_grad=max_norm, parameters=model.parameters(), create_graph=False , update_grad=(data_iter_step + 1 ) % accum_iter == 0 ) loss_val = loss.item() if (data_iter_step + 1 ) % accum_iter == 0 : optimizer.zero_grad() if not math.isfinite(loss_val): print (f"loss is {loss_val} & stop training" ) sys.exit(1 ) if log_writer is not None and (data_iter_step + 1 ) % accum_iter == 0 : epoch_1000x = int ((data_iter_step / len (data_loader) + epoch) * 1000 ) log_writer.add_scalar("loss" , loss, epoch_1000x) log_writer.add_scalar("lr" , warmup_lr, epoch_1000x) print (f'Epoch:{epoch} ,loss:{loss} ,step:{data_iter_step} ,lr:{warmup_lr} ' )
上面的代码逻辑,可以多次复用不同的模型
如果要保存模型,使用save就可以
1 2 3 4 5 6 if args.output_dir: print ("saving checkpoints...." ) misc.save_model( args=args, model=model, model_without_ddp=model, optimizer=optimizer, epoch=epoch, loss_scaler=loss_scaler )
4.test阶段 我们可以直接仿照val阶段来进行计算,就可以,变成eval,数据放到test里面,然后使用模型来进行计算