pytorch初接触——唐宇迪教教程的3D卷积视频动作识别。接触之后,发现pytorch比tensorflow的用户体验要好一点点,TF由于兼容性问题,从其他地方拿到代码,第一感觉就是跑不起来,很多代码都是基于TF1.x写的,跟2.x一堆不兼容问题。由此开始研究pytorch,后面用的顺手可能直接转pytorch,目前这个架构开始追赶TF。
一、pytorch基本步骤
python每个架构都有大致的操作步骤,学习过程中,查找API资料以及通过代码摸索,我总结了pytorch的基本操作步骤,如下:
①.定义model
model = Model
②.定义参数、优化器、loss
train_params = [{'params': params(model), 'lr': lr}]
criterion = nn.CrossEntropyLoss()#loss function
optimizer = optim.SGD(train_params,lr=lr,momentum=0.9,weight_decay=5e-4)
③.to decive
model.to(device)
criterion.to(device)
④.开始epoch循环
for input, target in dataset:
model.trian()
inputs = Variable()
labels = Varibale()
optimizer.zero_grad()
output = model(input)
pre = nn.activaiton()
loss = loss_fn(output, target)
loss.backward()
optimizer.step()
二、网络基本架构
开始前需要配置环境,我使用的是torch1.13 + cuda11.6,GPU版本,安装后如果要知道是否能使用GPU进行计算,就使用这句代码:torch.cuda.is_available() ,输出为True即表示GPU可以使用。
整个代码由几个部分组成:①.dataloader进行视频数据的处理;②train进行epoch训练;③inference进行预测。这个代码说是由一篇论文复现的,但我没有去看论文,model部分的代码直接复制唐宇迪源码(如有倾权,一定删除)。
1.dataloader数据处理
这部分我觉得是重点,理解整个代码,在这里花费了很长时间。这部分的思路是:定义基本参数,例如截取视频帧的数量等——判断输出路径文件夹是否建立、文件夹中的jpg是否符合情况——处理视频数据。preprocess(self)这个函数的作用是判断有没有建立train\val\test三个文件夹,如果有没有的话就使用os.mkdir函数建立这三个文件夹,用于存放处理后的jpg文件。process_video(self,video,action_name,save_dir)这个函数就是正式开始处理视频,EXTRACT_FREQUENCY这个参数的目的是为了每隔4个frame截取一张图片,如果整个视频截取小于16张frame,那EXTRACT_FREQUENCY-1,直到能够凑够16张frame以上为止。以下为这两个函数源码:
def preprocess(self):
if not os.path.exists(self.output_dir):
os.mkdir(self.output_dir)
os.mkdir(os.path.join(self.output_dir,'train'))
os.mkdir(os.path.join(self.output_dir, 'val'))
os.mkdir(os.path.join(self.output_dir, 'test'))
for file in os.listdir(self.root_dir):
file_path = os.path.join(self.root_dir,file)
video_files = [name for name in os.listdir(file_path)]
train_and_valid, test = train_test_split(video_files,test_size=0.2,random_state=42)
train,val = train_test_split(train_and_valid,test_size=0.2,random_state=42)
train_dir = os.path.join(self.output_dir,'train',file)
# print("train_dir is ",train_dir)
val_dir = os.path.join(self.output_dir,'val',file)
test_dir = os.path.join(self.output_dir,'test',file)
if not os.path.exists(train_dir):
os.mkdir(train_dir)
if not os.path.exists(val_dir):
os.mkdir(val_dir)
if not os.path.exists(test_dir):
os.mkdir(test_dir)
for video in train:
self.process_video(video,file,train_dir)
for video in val:
self.process_video(video, file, val_dir)
for video in test:
self.process_video(video, file, test_dir)
print('Preprocessing finished')
def process_video(self,video,action_name,save_dir):
# video name:v_ApplyEyeMakeup_g01_c01.avi
video_filename = video.split('.')[0]
if not os.path.exists(os.path.join(save_dir,video_filename)):
os.mkdir(os.path.join(save_dir,video_filename))
capture = cv2.VideoCapture(os.path.join(self.root_dir,action_name,video))
frame_count = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
frame_width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
frame_height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
# Make sure splited video has at least 16 frames
EXTRACT_FREQUENCY = 4
if frame_count // EXTRACT_FREQUENCY <= 16:
EXTRACT_FREQUENCY -= 1
if frame_count // EXTRACT_FREQUENCY <= 16:
EXTRACT_FREQUENCY -= 1
if frame_count // EXTRACT_FREQUENCY <= 16:
EXTRACT_FREQUENCY -= 1
count = 0
i = 0
retaining = True
while (count < frame_count and retaining):
retaining, frame = capture.read()
if frame is None:
continue
# resize the img and save to save_dir
if count % EXTRACT_FREQUENCY == 0:
if (frame_height != self.resize_height) or (frame_width != self.resize_width):
frame = cv2.resize(frame, (self.resize_width, self.resize_height))
cv2.imwrite(filename=os.path.join(save_dir, video_filename, '0000{}.jpg'.format(str(i))), img=frame)
i += 1
count += 1
# Release the VideoCapture once it is no longer needed
capture.release()
2.train进行训练
这里的模型采用C3D_model,3D卷积与2D卷积的最大区别在于多了一个时间维度T,是将视频拆解为多个frame,然后同时输出model中,将每一帧溶入到一个维度中,也就是现在的卷积核变为mxnxT,T对应多个图像。这里整个模型的参数如下:
nEpochs = 30#这里我只采用30个,跑一个需要10min,跑完这30个我花了5.5h,精度才到0.58
model = C3D_model.C3D(num_classes=num_classes,pretrained=False)
criterion = nn.CrossEntropyLoss()#loss function
optimizer = optim.SGD(train_params,lr=lr,momentum=0.9,weight_decay=5e-4)
model.to(device) criterion.to(device)
3.inference进行预测
在执行这个代码时,程序在下面这里报错,信息显示在forward(inputs)这里有问题。
with torch.no_grad(): outputs = model.forward(inputs)
具体是view这里报错,查了网上的解决方案,需要在view前面加上.contiguous().这一段,随后就可以正常运行了。
x = x.contiguous().view(-1, 8192)
我这边使用的配置是3060+6g的笔记本跑这个程序,30个epoch也只有0.58的准确率,用test数据测试的结果确实不是100%准确,如下图,结果应该是ApplyEyeMakeup,但识别到的是ApplyLipstick。
整个项目的代码和下载链接在:https://download.csdn.net/download/wenpeitao/87346700