(转)pointnet/sem_seg/train.py 代码详解

该文是转载CSDN博主「shaozhenghan」的原创文章,在该基础上,我又添加了一部分自己的理解,充实代码注释,方便自己日后能更好的查看,缕清思路。
原文通道:开源框架PointNet 代码详解——/pointnet/sem_seg/train.py

parser = argparse.ArgumentParser()
parser.add_argument('--gpu', type=int, default=0, help='GPU to use [default: GPU 0]')
parser.add_argument('--log_dir', default='log', help='Log dir [default: log]')
parser.add_argument('--num_point', type=int, default=4096, help='Point number [default: 4096]')
parser.add_argument('--max_epoch', type=int, default=50, help='Epoch to run [default: 50]')
parser.add_argument('--batch_size', type=int, default=24, help='Batch Size during training [default: 24]')
parser.add_argument('--learning_rate', type=float, default=0.001, help='Initial learning rate [default: 0.001]')
parser.add_argument('--momentum', type=float, default=0.9, help='Initial learning rate [default: 0.9]')
parser.add_argument('--optimizer', default='adam', help='adam or momentum [default: adam]')
parser.add_argument('--decay_step', type=int, default=300000, help='Decay step for lr decay [default: 300000]')
parser.add_argument('--decay_rate', type=float, default=0.5, help='Decay rate for lr decay [default: 0.5]')
parser.add_argument('--test_area', type=int, default=6, help='Which area to use for test, option: 1-6 [default: 6]')
FLAGS = parser.parse_args()

命令行解析。num_point默认4096,这与所用数据集有关

BATCH_SIZE = FLAGS.batch_size
NUM_POINT = FLAGS.num_point
MAX_EPOCH = FLAGS.max_epoch
NUM_POINT = FLAGS.num_point
BASE_LEARNING_RATE = FLAGS.learning_rate
GPU_INDEX = FLAGS.gpu
MOMENTUM = FLAGS.momentum
OPTIMIZER = FLAGS.optimizer
DECAY_STEP = FLAGS.decay_step
DECAY_RATE = FLAGS.decay_rate

从命令行接受的全局变量。DECAY_STEP 和 DECAY_RATE用来计算learning_rate的衰减。具体计算方法下面介绍。

LOG_DIR = FLAGS.log_dir
if not os.path.exists(LOG_DIR): os.mkdir(LOG_DIR)
os.system('cp model.py %s' % (LOG_DIR)) # bkp of model def
os.system('cp train.py %s' % (LOG_DIR)) # bkp of train procedure
"""
os.system()将字符串转化成命令再服务器上运行
system函数执行时,创建一个子进程在系统上执行命令行,子进程的执行结果无法影响主进程
os.system('cd /usr/local && mkdir aaa.txt')在同一子进程,可执行并行操作
cp命令用于复制文件或者目录 cp -r test/ newtest 将test/下的所有文件复制到新目录下
"""
LOG_FOUT = open(os.path.join(LOG_DIR, 'log_train.txt'), 'w')
LOG_FOUT.write(str(FLAGS)+'\n')

记录训练日志文件log_train.txt。以及备份 model.py 和 train.py。

MAX_NUM_POINT = 4096
NUM_CLASSES = 13
 
BN_INIT_DECAY = 0.5
BN_DECAY_DECAY_RATE = 0.5
#BN_DECAY_DECAY_STEP = float(DECAY_STEP * 2)
BN_DECAY_DECAY_STEP = float(DECAY_STEP)
BN_DECAY_CLIP = 0.99
 
HOSTNAME = socket.gethostname()

全局变量。每个样本为Block块,点数为4096,一共13个语义分类标签。

BN_ 开头的4个变量用来计算 Batch 的Decay参数,即decay参数也随着训练逐渐decay。具体计算方法下面解介绍。 socket 涉及网络编程。

ALL_FILES = provider.getDataFiles('indoor3d_sem_seg_hdf5_data/all_files.txt')
room_filelist = [line.rstrip() for line in open('indoor3d_sem_seg_hdf5_data/room_filelist.txt')]

因为论文中原作者所用数据集划分为了24个h5格式的文件,名字存在all_files.txt 中。
第一行是获取所有数据文件名。
第二行获取每个样本(Block)所对应的room。

ply_data_all_0.h5 至 ply_data_all_23.h5 一共 23×1000+585=23585行,每行一个物体,每个物体4096点,每点9个维度

room_filelist.txt 一共 23585 行; 即对应每个物体在哪个room采集的

# Load ALL data
data_batch_list = []
label_batch_list = []
for h5_filename in ALL_FILES:
    data_batch, label_batch = provider.loadDataFile(h5_filename)
    data_batch_list.append(data_batch)
    label_batch_list.append(label_batch)
data_batches = np.concatenate(data_batch_list, 0)
label_batches = np.concatenate(label_batch_list, 0)
print(data_batches.shape)
print(label_batches.shape)

依次从每个数据文件中加载数据。这里首先将数据定义为list,然后在循环中依次append,循环结束后再将list转换为numpy数组。
这样会比较浪费内存,因为要重新在内存中开辟一块与list一样大但是连续的内存,然后将list的内容复制过去。实际自己训练的时候可以用直接用numpy数组。但numpy数组不支持动态扩展,即np.append()每次都会重新为新数组分配内存,然后copy。这样效率很低。那么可以根据自己的训练集的大小预先np.zeros(),然后修改数据即可。

Python 中使用动态数组的方法参见:http://blog.chinaunix.net/uid-23100982-id-3164530.html

test_area = 'Area_'+str(FLAGS.test_area)
train_idxs = []
test_idxs = []
""" 训练索引和测试索引"""
# enumerate(): 用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标
for i,room_name in enumerate(room_filelist):
    if test_area in room_name:
    """
    if a in b 查看a对象是否在b对象中
    """
        test_idxs.append(i)
    else:
        train_idxs.append(i)
 
train_data = data_batches[train_idxs,...]
train_label = label_batches[train_idxs]
test_data = data_batches[test_idxs,...]
test_label = label_batches[test_idxs]
print(train_data.shape, train_label.shape)
print(test_data.shape, test_label.shape)

分配训练集和测试集。test_area 为从命令行解析的参数,原文数据集从6个区域中采样而得,训练时需指定哪一个区域的数据用来测试。

def log_string(out_str):
    LOG_FOUT.write(out_str+'\n')
    # flush() 刷新缓冲区,将缓冲区中的数据立刻写入文件,同时清空缓冲区
    LOG_FOUT.flush()
    print(out_str)
log_string(out_str)函数用来log训练日志。

 

def get_learning_rate(batch):
    learning_rate = tf.train.exponential_decay(
                        BASE_LEARNING_RATE,  # Base learning rate.
                        # decayed_learning_rate = learning_rate *
                        # decay_rate ^ (global_step / decay_steps)
                        batch * BATCH_SIZE,  # Current index into the dataset.
                        DECAY_STEP,          # Decay step.
                        DECAY_RATE,          # Decay rate.
                        staircase=True)
    learning_rate = tf.maximum(learning_rate, 0.00001) # CLIP THE LEARNING RATE!!
    return learning_rate  

计算指数衰减的学习率。训练时学习率最好随着训练衰减。
函数tf.train.exponential_decay(
learning_rate, #初始学习速率
global_step, #数据集当前的索引数量
decay_steps, #迭代衰减册数,当数量达到时,就进行衰减
decay_rate, #衰减速率
staircase=False,
name=None)
为指数衰减函数。计算公式如下:

decayed_learning_rate = learning_rate * decay_rate ^ (global_step / decay_steps)

此处 global_step = batch * BATCH_SIZE

if the argument staircase is True, then global_step /decay_steps is an integer division and the decayed learning rate follows a staircase function.

def get_bn_decay(batch):
    bn_momentum = tf.train.exponential_decay(
                      BN_INIT_DECAY,
                      batch*BATCH_SIZE,
                      BN_DECAY_DECAY_STEP,
                      BN_DECAY_DECAY_RATE,
                      staircase=True)
    bn_decay = tf.minimum(BN_DECAY_CLIP, 1 - bn_momentum)
    return bn_decay

计算衰减的Batch Normalization 的 decay。基本同上。

接下来是训练函数 train():

def train():
    with tf.Graph().as_default():
        with tf.device('/gpu:'+str(GPU_INDEX)):
        """ 手动选用GPU设备"""
            pointclouds_pl, labels_pl = placeholder_inputs(BATCH_SIZE, NUM_POINT)
            is_training_pl = tf.placeholder(tf.bool, shape=())
            
            # Note the global_step=batch parameter to minimize. 
            # global step 参数 初始化 为0, 每次自动加 1
            # That tells the optimizer to helpfully increment the 'batch' parameter for you every time it trains.
            batch = tf.Variable(0)
            """ 设置batch变量,在梯度优化处数量逐个增加"""
            bn_decay = get_bn_decay(batch)
            tf.summary.scalar('bn_decay', bn_decay)

这一段主要是placeholder,以及batch初始化为0。

  # Get model and loss 
    pred = get_model(pointclouds_pl, is_training_pl, bn_decay=bn_decay)
    loss = get_loss(pred, labels_pl)
    tf.summary.scalar('loss', loss)
    # tf.argmax(pred, 2) 返回pred C 这个维度的最大值索引
    # tf.equal() 比较两个张量对应位置是否想等,返回相同维度的bool值矩阵
    correct = tf.equal(tf.argmax(pred, 2), tf.to_int64(labels_pl))
    accuracy = tf.reduce_sum(tf.cast(correct, tf.float32)) / float(BATCH_SIZE*NUM_POINT)
    tf.summary.scalar('accuracy', accuracy)

  # Add ops to save and restore all the variables.
    saver = tf.train.Saver()
   """保存训练模型"""

预测值为pred,调用model.py 中的 get_model()得到。由get_model()可知,pred的维度为B×N×13,13为Channel数,对应13个分类标签。每个点的这13个值最大的一个的下标即为所预测的分类标签。

# Get training operator
learning_rate = get_learning_rate(batch)
tf.summary.scalar('learning_rate', learning_rate)
if OPTIMIZER == 'momentum':
    optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=MOMENTUM)
elif OPTIMIZER == 'adam':
    optimizer = tf.train.AdamOptimizer(learning_rate)
train_op = optimizer.minimize(loss, global_step=batch)

获得衰减后的学习率,以及选择优化器optimizer。

   # Create a session
    config = tf.ConfigProto()
    config.gpu_options.allow_growth = True
    config.allow_soft_placement = True
    config.log_device_placement = True
    sess = tf.Session(config=config)

配置session 运行参数。

config.gpu_options.allow_growth = True:让TensorFlow在运行过程中动态申请显存,避免过多的显存占用。

config.allow_soft_placement = True:当指定的设备不存在时,允许选择一个存在的设备运行。比如gpu不存在,自动降到cpu上运行。

config.log_device_placement = True:在终端打印出各项操作是在哪个设备上运行的。

# Init variables
init = tf.global_variables_initializer()
sess.run(init, {is_training_pl:True})
 
ops = {'pointclouds_pl': pointclouds_pl,
       'labels_pl': labels_pl,
       'is_training_pl': is_training_pl,
       'pred': pred,
       'loss': loss,
       'train_op': train_op,
       'merged': merged,
       'step': batch}
 
for epoch in range(MAX_EPOCH):
    log_string('**** EPOCH %03d ****' % (epoch))
    sys.stdout.flush()
     
    train_one_epoch(sess, ops, train_writer)
    eval_one_epoch(sess, ops, test_writer)
    
    # Save the variables to disk.
    if epoch % 10 == 0:
        save_path = saver.save(sess, os.path.join(LOG_DIR, "model.ckpt"))
        log_string("Model saved in file: %s" % save_path)

初始化参数,开始训练。train_one_epoch 函数用来训练一个epoch,eval_one_epoch函数用来每运行一个epoch后evaluate在测试集的accuracy和loss。每10个epoch保存1次模型。

训练函数 train() 结束

train_one_epoch(sess, ops, train_writer) 函数

def train_one_epoch(sess, ops, train_writer):
    """ ops: dict mapping from string to tf ops """
    is_training = True
    
    log_string('----')
    current_data, current_label, _ = provider.shuffle_data(train_data[:,0:NUM_POINT,:], train_label) 
    
    file_size = current_data.shape[0]
    num_batches = file_size // BATCH_SIZE # // 除完后对结果进行自动floor向下取整操作

provider.shuffle_data 函数随机打乱数据,返回打乱后的数据。 num_batches = file_size // BATCH_SIZE,计算在指定BATCH_SIZE下,训练1个epoch 需要几个mini-batch训练。

for batch_idx in range(num_batches):
    if batch_idx % 100 == 0:
        print('Current batch/total batch num: %d/%d'%(batch_idx,num_batches))
    start_idx = batch_idx * BATCH_SIZE
    end_idx = (batch_idx+1) * BATCH_SIZE
    
    feed_dict = {ops['pointclouds_pl']: current_data[start_idx:end_idx, :, :],
                 ops['labels_pl']: current_label[start_idx:end_idx],
                 ops['is_training_pl']: is_training,}
    summary, step, _, loss_val, pred_val = sess.run([ops['merged'], ops['step'], ops['train_op'], ops['loss'], ops['pred']],
                                     feed_dict=feed_dict)
    train_writer.add_summary(summary, step)
    pred_val = np.argmax(pred_val, 2)
    correct = np.sum(pred_val == current_label[start_idx:end_idx])
    total_correct += correct
    total_seen += (BATCH_SIZE*NUM_POINT)
    loss_sum += loss_val

在一个epoch 中逐个mini-batch训练直至遍历完一遍训练集。计算总分类正确数total_correct和已遍历样本数total_senn,总损失loss_sum.

 log_string('mean loss: %f' % (loss_sum / float(num_batches)))
    log_string('accuracy: %f' % (total_correct / float(total_seen)))

记录平均loss,以及平均accuracy。

train_one_epoch(sess, ops, train_writer) 函数 结束

eval_one_epoch(sess, ops, test_writer)

用来在测试集上评估evaluate。与train_one_epoch 类似。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值