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

  1. 首先写init,需要传入的参数有,resize,mode,path,三个是图片的大小,当前是什么模式,还有一个是csv存在的地址,之后我们就进行切割数据集,使用6:2:2来进行切分,主要思路,就是把从loadcsv得到完整的列表,然后进行切分
  2. 之后就是getitem,我们只需要按照给定的idx,返回对应的图片还有属性值就可以,一般transform可以在外面写好,也可以在dataset你写好,使用transform来进行组合所需要的操作,然后对图片来进行执行就可以了,最后返回图片还有属性值
  3. 返回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': # 20% = 60%->80%
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': # 20% = 80%->9999%
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'), # string path= > image data
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'), # string path= > image data
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)')

# Model parameters
# parser.add_argument('--model', default='mae_vit_large_patch16', type=str, metavar='MODEL',
# help='Name of model to train')

parser.add_argument('--input_size', default=224, type=int,
help='images input size')

# Optimizer parameters
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')

# Dataset parameters
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)

# distributed training parameters
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'
# pth_path = args.pth_path
# model = resnet18(pretrained=False)
model_cnn = torch.load(pth_path)

对于加载好的模型,我们想修改值的话,可以修改最后一层的圈连接层,对于前面的层数我们全部保留,最后一层的fc,我们来使用linear来进行修改就可以

1
2
3
4
5
model_conv = convnext_tiny(pretrained=True)
# print(model_conv.head.fc.in_features)
# exit()
model_conv_ftrs = model_conv.head.fc.in_features
model_conv.head.fc = nn.Linear(model_conv_ftrs,6)

之后就是开始for epoch,首先我们进入到验证阶段

  1. 我们第一步就是设置模型为eval模式,
  2. 然后进入到验证阶段,传入的是数据集,还有模型
  3. 返回的就是记录的misc,之后我们对记录的值,传入到log里面,让他写入到tensorboard里面

之后我们就是进行train模式,对于train模式,我们首先就是把model的train来进行打开

  1. 传入大还是模型,数据集,还有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)
# 计算loss,统计得分
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]
# 写下在tensorboard
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.items()}
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()

# 完成一次epoch就进行置为,走
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}')

# # gather the stats from all processes
# log_writer.synchronize_between_processes()
# print("Averaged stats:", log_writer)
# return {k: meter.global_avg for k, meter in log_writer.meters.items()}

上面的代码逻辑,可以多次复用不同的模型

如果要保存模型,使用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里面,然后使用模型来进行计算