Repository navigation
Expand file tree
/
Copy pathmain_1v.py
More file actions
executable file
·358 lines (318 loc) · 16 KB
/
Copy pathmain_1v.py
File metadata and controls
executable file
·358 lines (318 loc) · 16 KB
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
#在python3中训练
import argparse
import os
import time
import pickle
import sys
print(sys.path)
sys.path.append('/home/wgk/code/PointNetGPD/PointNetGPD/model')
import torch
import torch.utils.data
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import numpy as np
#from tensorboardX import SummaryWriter
#现在pytorch已经集成了tensorboard了
from torch.utils.tensorboard import SummaryWriter
from torch.optim.lr_scheduler import StepLR
##################/home/wgk/code/PointNetGPD/PointNetGPD/model 包###########################
#需要安装pcl
from model.dataset import *
#pointnet的具体网络结构
from model.pointnet import PointNetCls, DualPointNetCls
import argparse
#创建一个argparse模块中的ArgumentParser类对象
# description只是解释一下,这个类对象是做什么的
parser = argparse.ArgumentParser(description='pointnetGPD')
#添加一个参数 tag 这个可以自定义,主要用于区分指定训练结果的存放文件夹等
parser.add_argument('--tag', type=str, default='default')
#默认epoch训练的轮数,大小为200
parser.add_argument('--epoch', type=int, default=200)
#添加模式参数,指定从train或者test中选择,且是必选项
parser.add_argument('--mode', choices=['train', 'test'], required=True)
#设定batch-size,默认是1
parser.add_argument('--batch-size', type=int, default=1)
#意思是,当命令中出现 --cuda的时候,就把 object.cuda的值设置为真
parser.add_argument('--cuda', action='store_true')
#添加参数 选择使用的gpu编号,默认使用0号gpu
parser.add_argument('--gpu', type=int, default=0)
parser.add_argument('--lr', type=float, default=0.005)
parser.add_argument('--load-model', type=str, default='')
parser.add_argument('--load-epoch', type=int, default=-1)
parser.add_argument('--model-path', type=str, default='./assets/learned_models',
help='pre-trained model path')
parser.add_argument('--data-path', type=str, default='~/dataset/PointNetGPD/', help='data path')
#设置每隔多少次迭代,就打印出来一次loss还有训练进度
parser.add_argument('--log-interval', type=int, default=10)
#保存间隔,设置每隔几个epoch就保存一次模型,比如epoch=10就是训练10轮之后就保存当前的训练模型
parser.add_argument('--save-interval', type=int, default=1)
#此时,使用对象parser中的解析函数.parse_args(),读取命令行给出的参数,集合成namspace 返回给args
#例子: python main_1v.py --epoch 200 --mode train --batch-size 3
args = parser.parse_args()
#查看是否安装好了cuda
args.cuda = args.cuda if torch.cuda.is_available else False
#为当前的gpu设置随机种子;这样在以后运行该程序的时候,随机数都是相同的,不会每次运行都变化一次
#作用主要是为了固定随机初始化的权重值,这样就可以在每次重新从头训练网络的时候,权重值虽然是随机的
#但是都是固定的,不会每次都在变化
#其中的seed值,可以随便写
"""
https://blog.csdn.net/weixin_43002433/article/details/104706950?utm_
medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1
.add_param_isCf&depth_1-utm_source=distribute.pc_relevant.none-task-blog-Blog
CommendFromMachineLearnPai2-1.add_param_isCf
"""
if args.cuda:
torch.cuda.manual_seed(1)
#获取当前文件所在目录(文件夹)的绝对路径
path=os.path.dirname(os.path.abspath(__file__))
#更改(确保)当前所在目录是工作目录
os.chdir(path)
print(os.getcwd())
#获取当前的时间,年月日 时间
current_time = time.strftime("%Y-%m-%dT%H:%M", time.localtime())
logger = SummaryWriter(os.path.join('./assets/log/', current_time))
#设置numpy的随机数种子,但是注意,这里的随机数种子是随着系统时间变化的,因此每次运行出现的随机数都是不同的
np.random.seed(int(time.time()))
"""
使用单线程进行数据集导入时候,有时候比较慢,会阻碍到计算的过程,于是考虑用多个线程进行数据的导入
如果参数num_workers设置的大于1,就是指定了多个线程,于是考虑使用多个线程导入数据,防止影响计算
每个线程叫一个"worker",这个 worker_init_fn就是定义每个线程worker初始化的时候,需要执行哪些操作
其中,pid就是子线程的线程号,直接写成这样就行
"""
def worker_init_fn(pid):
"""
为后台工作进程设置唯一但具有确定性的随机种子,只需要对numpy设置种子就行了,
pytorch和python的随机数生成器会自己管理自己的子线程?
设置torch.initial_seed() % (2**31-1)取余数,是因为numpy的种子范围是0到2**31-1
"""
np.random.seed(torch.initial_seed() % (2**31-1))
"""
将样本采样器返回的list中对应的样构建成一个minibatch的tensor,自定义的tensor的话
对于map型的数据集,且有多个读取进程的情况,采样器会先将样本集分成一个个batch,返回每个batch的样本的索引的 list,
再根据my_collate函数,说明如何把这些索引指定的样本整合成一个data Tensor和lable Tensor
"""
def my_collate(batch):
batch = list(filter(lambda x:x is not None, batch))
return torch.utils.data.dataloader.default_collate(batch)
#
grasp_points_num=750
thresh_good=0.6
thresh_bad=0.6
#设置点是只有xyz ?
point_channel=3
"""
数据加载器,主要实现数据加载到网络中的相关作用,核心类是torch.utils.data.DataLoader
DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
batch_sampler=None, num_workers=0, collate_fn=None,
pin_memory=False, drop_last=False, timeout=0,
worker_init_fn=None)
dataset:加载的数据集,继承自torch.utils.data.Dataset类的自定义类的对象,或者就是torch.utils.data.Dataset类的对象
batch_size:batch size,就是batchs ize
shuffle::是否将数据打乱
sampler: 样本抽样,后续会详细介绍
batch_sampler,从注释可以看出,其和batch_size、shuffle等参数是互斥的,一般采用默认
num_workers:使用多进程加载的进程数,0代表不使用多进程
collate_fn: 如何将多个样本数据拼接成一个batch,一般使用默认的拼接方式即可
pin_memory:是否将数据保存在pin memory区,pin memory中的数据转到GPU会快一些
drop_last:dataset中的数据个数可能不是batch_size的整数倍,drop_last为True会将多出来不足一个batch的数据丢弃
"""
train_loader = torch.utils.data.DataLoader(
#设置dataset,传入的是继承了torch.utils.data.Dataset类的子类的实例对象 class PointGraspOneViewDataset(torch.utils.data.Dataset):
PointGraspOneViewDataset(
#设置夹爪内部的点数量最少要有多少个
grasp_points_num=grasp_points_num,
path=args.data_path,
prefix='panda',
tag='train',
grasp_amount_per_file=16800, #每个物体已经生成的抓取点云个数,140×120 (单物体的生成140个不同抓取姿态×单物体共有120个不同视角点云)
thresh_good=thresh_good,
thresh_bad=thresh_bad,
),
#设置batch_size
batch_size=args.batch_size,
#设置使用多少个子线程来导入数据,如果设置为0,那就是直接把数据导入到主线程中
num_workers=32,
#如果设置为True,就使得Tensor数据最开始存放于内存的锁页内存中,这样将内存Tensor转移到GPU显存就会快一些
# 当计算机内存充足的时候,选择True,如果不充足,可能使用到虚拟内存的时候,就写为False
pin_memory=True,
#如果设置为True,会默认构建一个乱序采样器(为False时,构建顺序采样器);每次epoch之后都会把数据集打乱
shuffle=True,
#
worker_init_fn=worker_init_fn,
#collate_fn函数,将sampler返回的数据list合并成为一个整体的tensor,作为一个mini-batch
collate_fn=my_collate,
#设置,如何处理训练到最后,数据集长度不足一个batch_size时的数据,True就抛弃,否则保留
drop_last=True,
)
"""
测试时候用的数据集加载器;这里的test数据集是用来测试训练好的网络的准确率
"""
test_loader = torch.utils.data.DataLoader(
PointGraspOneViewDataset(
grasp_points_num=grasp_points_num,
path=args.data_path,
prefix='panda',
#设置标签为test
tag='test',
grasp_amount_per_file=500, #
thresh_good=thresh_good,
thresh_bad=thresh_bad,
with_obj=True,
),
batch_size=args.batch_size,
num_workers=32,
pin_memory=True,
shuffle=True,
worker_init_fn=worker_init_fn,
collate_fn=my_collate,
)
is_resume = 0
if args.load_model and args.load_epoch != -1:
is_resume = 1
#如果是测试模式
if is_resume or args.mode == 'test':
#加载网络结构和参数,加载到命令行 指定的gpu中
model = torch.load(args.load_model, map_location='cuda:{}'.format(args.gpu))
model.device_ids = [args.gpu]
print('load model {}'.format(args.load_model))
#如果是训练模式,就加载模型
else:
model = PointNetCls(num_points=grasp_points_num, input_chann=point_channel, k=2)
#如果命令行出现了cuda字眼(args.cuda将会自动设置为True)
if args.cuda:
if args.gpu != -1:
#设置为指定编号的gpu,使用指定编号的gpu进行训练
torch.cuda.set_device(args.gpu)
#将网络模型的所有参数等都转移到gpu中(刚指定的那个)
model = model.cuda()
else:
#如果args.gpu=-1
device_id = [0]
#在这里选择使用哪个gpu
torch.cuda.set_device(device_id[0])
#这句话,使得原有的model(网络),重构成为一个新的model,这个模型能够调用多个gpu同时运行
model = nn.DataParallel(model,device_id).cuda()
#选择adam优化器
optimizer = optim.Adam(model.parameters(), lr=args.lr)
"""
from torch.optim.lr_scheduler import StepLR
StepLR类主要用来调整学习率,lr=learning rate,让学习率随着epoch变化
构造函数 输入数据optimizer选择优化器;选择step_size=30, gamma=0.5
成员函数StepLR.step()是用来
"""
scheduler = StepLR(optimizer, step_size=30, gamma=0.5)
#训练函数,仅仅是一个epoch,其中包含了很多batch
def train(model, loader, epoch):
#每一次epoch之前,都更新一下学习率
scheduler.step()
#调用train函数训练;这个model是提前设置好的pointnet
model.train()
torch.set_grad_enabled(True)
correct = 0
dataset_size = 0
"""
注意,如果使用了多线程导入数据集的情况下,在当调用enumerate时,将会在此时在后台创建多线程导入数据
loader是一个可迭代对象,索引是batch_idx,对象是(data, target)
这里实现的效果是:一个一个batch的训练网络,直到一个epoch训练完毕
"""
for batch_idx, (data, target) in enumerate(loader):
#print(len(data),"data len is")
"""
在实际的实验过程中发现,当某一个batch(比如剩下来的某一个batch)中间只含有一个sample
那么很有可能会报错,这里判断一下本次的batch中是不是只有一个样本,如果只有一个样本
那就跳过这个batch
"""
if len(data) <=1:
continue
dataset_size += data.shape[0]
data, target = data.float(), target.long().squeeze()
#如果使用cuda的话
if args.cuda:
data, target = data.cuda(), target.cuda()
optimizer.zero_grad()
#这里进行前向传播,并输出结果 但是这个output是什么
output, _ = model(data)
#计算loss
loss = F.nll_loss(output, target)
#反向传播,更新权重
loss.backward()
#进行学习率的优化
optimizer.step()
pred = output.data.max(1, keepdim=True)[1]
correct += pred.eq(target.view_as(pred)).long().cpu().sum()
#设置每隔多少次迭代(batch)打印一次loss,这里默认是10次迭代
if batch_idx % args.log_interval == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}\t{}'.format(
#当前第几个epoch, 当前训练到该epoch中的第几个batch,数据集总共有多大
epoch, batch_idx * args.batch_size, len(loader.dataset),
#计算当前训练百分比
100. * batch_idx * args.batch_size / len(loader.dataset), loss.item(), args.tag))
"""在tensorboard上面绘制折线图
def add_scalar(self, tag, scalar_value, global_step=None, walltime=None):
参数含义:
tag (string): 显示什么名称
scalar_value (float or string/blobname): 需要打印出来(同时会保存下来)的变量
global_step (int): 就是坐标系横轴的下标显示什么变量
walltime (float): Optional override default walltime (time.time())
with seconds after epoch of event
"""
logger.add_scalar('train_loss',
loss.cpu().item(), #这里的loss.cpu是不是利用到了cpu? 做了什么计算?
batch_idx + epoch * len(loader))#横坐标下标是迭代次数,训练了几次epoch
"""len(loader)和len(loader.dataset)区别
len(loader.dataset) 返回的是数据集中的sample的数量
len(loader) 返回的是 len(loader.dataset)/batch_size向上取整 就是一个epoch中包含有多少个batch(一个epoch迭代多少次)
"""
return float(correct)/float(dataset_size)
#测试训练好的网络
def test(model, loader):
model.eval()
torch.set_grad_enabled(False)
test_loss = 0
correct = 0
dataset_size = 0
da = {}
db = {}
res = []
for data, target, obj_name in loader:
dataset_size += data.shape[0]
data, target = data.float(), target.long().squeeze()
if args.cuda:
data, target = data.cuda(), target.cuda()
output, _ = model(data) # N*C
test_loss += F.nll_loss(output, target, size_average=False).cpu().item()
pred = output.data.max(1, keepdim=True)[1]
correct += pred.eq(target.view_as(pred)).long().cpu().sum()
for i, j, k in zip(obj_name, pred.data.cpu().numpy(), target.data.cpu().numpy()):
res.append((i, j[0], k))
test_loss /= len(loader.dataset)
acc = float(correct)/float(dataset_size)
return acc, test_loss
def main():
#
if args.mode == 'train':
for epoch_i in range(is_resume*args.load_epoch, args.epoch):
#训练一个epoch,把网络模型、数据集、当前是第几轮epoch的编号,都写进去
acc_train = train(model, train_loader, epoch_i)
#训练完毕i,精度等于多少
print('Train done, acc={}'.format(acc_train))
#使用测试数据集进行测试
acc, loss = test(model, test_loader)
#打印测试出的精度和loss
print('Test done, acc={}, loss={}'.format(acc, loss))
#然后把训练的结果测试的结果,存放在logger中,之后使用折线图打印出来
logger.add_scalar('train_acc', acc_train, epoch_i)
logger.add_scalar('test_acc', acc, epoch_i)
logger.add_scalar('test_loss', loss, epoch_i)
if epoch_i % args.save_interval == 0:
path = os.path.join(args.model_path, current_time + '_{}.model'.format(epoch_i))
#如果满足要求了,就保存下来,注意这里,需要选定_use_new_zipfile_serialization=False
torch.save(model, path,_use_new_zipfile_serialization=False)
print('Save model @ {}'.format(path))
else:
print('testing...')
acc, loss = test(model, test_loader)
print('Test done, acc={}, loss={}'.format(acc, loss))
if __name__ == "__main__":
main()