pointNet 代码之 train.py 的注释(完整的运行逻辑和过程)

train.py 执行顺序为:首先为建立 tensorflow Graph。

由 import argparse----> import math---->......---->import sys---->BASE_DIR....---> import provider 该文件为自定义文件,进入执行里面的人东西。主要为键入相关命令和各个定义函数(只加载函数名),依次为

import os--->import sys--->import numpy as np---> import h5py--->BASE_DIR=xxx--->sys.path.append(BASE_DIR)--->DATA_DIR = xxx--->if not os.path.exists(DATA_DIR)--->if not os.pathxxx--->def shuffle_data(data, labels)--->def rotate_point_cloud(batch_data)--->def rotate_point_cloud_by_angle--->....--->def loadDataFile_with_seg

然后返回到train.py 文件继续执行命令

--->import tf_util ---> parser = argparse.ArgumentParser()--->parser.add_argument(系列解析命令)--->BATCH_SIZE(系列赋值命令)--->MODEL=xxx--->MODEL_FILE--->LOG_DIR(系列日志加载命令)--->MAX_NUM_POINT(系列赋值命令)--->HOSTNAME=xxx--->TRAIN_FILES=xxx--->TEST_FILES--->def log_string(out_str)--->def get_learning_rate(batch)--->def get_bn_decay(batch)--->def train()--->def train_one_epoch(sess, ops, train_writer)--->def eval_one_epoch(sess, ops, test_writer)---->if __name__ == "__main__": (这样就已经建立完成train.py 的图)。

加下来执行代码,由  if __name__ == "__main__" 进入

if __name__ == "__main__":
    train()
    LOG_FOUT.close()
先进入train()函数, 再一次执行里面的各个命令及调用函数.
# train.py调用provider和tf_util包。作用是完成基于数据集的训练,并且log了训练结果。
# 参考博客:https://blog.csdn.net/weixin_41389170/article/details/89083964
#           https://blog.csdn.net/RNG_uzi_/article/details/87373348

import argparse #程序使用端口指令
import math
import h5py
import numpy as np
import tensorflow as tf
import socket
import importlib
import os
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.append(BASE_DIR)
sys.path.append(os.path.join(BASE_DIR, 'models'))
sys.path.append(os.path.join(BASE_DIR, 'utils'))
import provider
import tf_util

#命令解析工作 num_point默认4096,这与所用数据集有关。
parser = argparse.ArgumentParser()
parser.add_argument('--gpu', type=int, default=0, help='GPU to use [default: GPU 0]')  #默认不使用GPU
parser.add_argument('--model', default='pointnet_cls', help='Model name: pointnet_cls or pointnet_cls_basic [default: pointnet_cls]')
parser.add_argument('--log_dir', default='log', help='Log dir [default: log]')
parser.add_argument('--num_point', type=int, default=1024, help='Point Number [256/512/1024/2048] [default: 1024]')
parser.add_argument('--max_epoch', type=int, default=250, help='Epoch to run [default: 250]')  #250个回合
parser.add_argument('--batch_size', type=int, default=32, help='Batch Size during training [default: 32]') #默认32个点云一批
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=200000, help='Decay step for lr decay [default: 200000]')
parser.add_argument('--decay_rate', type=float, default=0.7, help='Decay rate for lr decay [default: 0.8]')
FLAGS = parser.parse_args() #然后所有的命令放入FLAGS


BATCH_SIZE = FLAGS.batch_size
NUM_POINT = FLAGS.num_point
MAX_EPOCH = FLAGS.max_epoch
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

#加载基础网络模型
MODEL = importlib.import_module(FLAGS.model) # import network module
# Namespace(batch_size=32, decay_rate=0.7, decay_step=200000, gpu=0, learning_rate=0.001, log_dir='log', max_epoch=250, model='pointnet_cls', momentum=0.9, num_point=1024, optimizer='adam')

# FLAGS.model 为取出 FLAGS中的model属性,构建一个读入pointnet_cls.py文件路径。我的路径如下:
# 'E:\\pythonStudy\\program2pointCloud\\pointnet-master\\models\\pointnet_cls.py'
MODEL_FILE = os.path.join(BASE_DIR, 'models', FLAGS.model+'.py')



LOG_DIR = FLAGS.log_dir
if not os.path.exists(LOG_DIR): os.mkdir(LOG_DIR)
os.system('cp %s %s' % (MODEL_FILE, LOG_DIR))     # bkp of model def
os.system('cp train.py %s' % (LOG_DIR))           # bkp of train procedure
LOG_FOUT = open(os.path.join(LOG_DIR, 'log_train.txt'), 'w')
LOG_FOUT.write(str(FLAGS)+'\n')

MAX_NUM_POINT = 2048
NUM_CLASSES = 40

BN_INIT_DECAY = 0.5
BN_DECAY_DECAY_RATE = 0.5
BN_DECAY_DECAY_STEP = float(DECAY_STEP)
BN_DECAY_CLIP = 0.99

HOSTNAME = socket.gethostname()        #得到主机的名字

# ModelNet40 official train/test split (训练集和测试集)
TRAIN_FILES = provider.getDataFiles( \
    os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/train_files.txt'))
TEST_FILES = provider.getDataFiles(\
    os.path.join(BASE_DIR, 'data/modelnet40_ply_hdf5_2048/test_files.txt'))

#日志记录函数
def log_string(out_str):
    LOG_FOUT.write(out_str+'\n')
    LOG_FOUT.flush() #清空缓存区
    print(out_str)

#计算指数衰减的学习率。训练时学习率最好随着训练衰减。
# tf.train.exponential_decay函数实现指数衰减学习率。
def get_learning_rate(batch):
    learning_rate = tf.train.exponential_decay(
                        BASE_LEARNING_RATE,  # Base learning rate.
                        batch * BATCH_SIZE,  # Current index into the dataset.
                        DECAY_STEP,          # Decay step.
                        DECAY_RATE,          # Decay rate.
                        staircase=True)

    # 训练时学习率最好随着训练衰减,learning_rate最大取0.00001 (衰减后的学习率和0.00001取最大)
    learning_rate = tf.maximum(learning_rate, 0.00001) # CLIP THE LEARNING RATE!
    return learning_rate        

#if the argument staircase is True, then global_step /decay_steps is an integer division and the decayed learning rate follows a staircase function.
#计算衰减的Batch Normalization 的 decay。基本同上。
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

#这一段主要是通过placeholder进行赋值, 模型的参数准备和构建整个训练网络(数据处理+loss+优化器),模型记录工作,最后进行训练.
def train():
    with tf.Graph().as_default():#表示将这个类实例,也就是新生成的图作为整个 tensorflow
                                
        with tf.device('/gpu:'+str(GPU_INDEX)):#如果需要切换成GPU运算,可以调用
            pointclouds_pl, labels_pl = MODEL.placeholder_inputs(BATCH_SIZE, NUM_POINT)
            # 调用 model\pointnet_cls下的placeholder_inputs()为 pointclouds_pl 和 labels_pl 赋值
            # 为 pointclouds_pl 包括:batch X num_points X dim2point;   labels_pl 是 batch的大小
            # batch_size 是一个 batch 内有多少个点云
            is_training_pl = tf.placeholder(tf.bool, shape=()) # tf.bool 为tensorflow 下的bool 值(True or False)
            print(is_training_pl)
            
            # Note the global_step=batch parameter to minimize. # globe_step初始化为0,每次自动加1
            # That tells the optimizer to helpfully increment the 'batch' parameter for you every time it trains.
            batch = tf.Variable(0)                  #创建一个变量,初始化为 0
            bn_decay = get_bn_decay(batch)          # 批训练时, 得到 batch 的衰减率
            tf.summary.scalar('bn_decay', bn_decay) # 用来显示标量信息

            # 创建的数据处理网络为pred,调用 model\pointnet_cls 下的get_model()得到。由get_model()可知,pred的维度为B×N×40,40为分出的类别
            # Channel数,对应40个分类标签。每个点的这40个值最大的一个的下标即为所预测的分类标签。
            # 首先使用共享参数的MLP对每个点进行特征提取,再使用MaxPooling在特征维进行池化操作,使得网络对不同数量点的点云产生相同维度的特征向量,
            # 且输出对输入点的顺序产生不变性。在得到固定维度的特征向量之后,再使用一个MLP对其进行分类。
            # Get model and loss
            pred, end_points = MODEL.get_model(pointclouds_pl, is_training_pl, bn_decay=bn_decay)

            loss = MODEL.get_loss(pred, labels_pl, end_points)  # 调用pointnet_cls下的get_loss()
            tf.summary.scalar('loss', loss)

            # tf.argmax(pred, 2) 返回pred C 这个维度的最大值索引返回相同维度的bool值矩阵
            # tf.equal() 比较两个张量对应位置是否想等,
            correct = tf.equal(tf.argmax(pred, 1), tf.to_int64(labels_pl))
            accuracy = tf.reduce_sum(tf.cast(correct, tf.float32)) / float(BATCH_SIZE) # 压缩求和,用于降维
            tf.summary.scalar('accuracy', accuracy)

            # Get training operator#获得衰减后的学习率,以及选择优化器optimizer。
            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)
            # minimize的内部存在两个操作:(1)计算各个变量的梯度 (2)用梯度更新这些变量的值
            # (1)计算loss对指定val_list的梯度(导数),返回元组列表[(gradient,variable),…]
            # (2)用计算得到的梯度来更新对应的变量(权重)
            # 注意:在程序中global_step初始化为0,每次更新参数时,自动加1
            # 将minimize()分成两个步骤的原因:在某种情况下对梯度进行修正,防止梯度消失或者梯度爆炸
            
            # Add ops to save and restore all the variables.
            saver = tf.train.Saver()
            
        # Create a session#配置session 运行参数。
        config = tf.ConfigProto()              #创建sess的时候对sess进行参数配置
        config.gpu_options.allow_growth = True # =True是让TensorFlow在运行过程中动态申请显存,避免过多的显存占用。
        config.allow_soft_placement = True     #当指定的设备不存在时,允许选择一个存在的设备运行。比如gpu不存在,自动降到cpu上运行
        config.log_device_placement = False    #在终端打印出各项操作是在哪个设备上运行的
        sess = tf.Session(config=config)       # 创建 sess, 才能运行框架
        
        # Add summary writers
        #merged = tf.merge_all_summaries()
        merged = tf.summary.merge_all()
        train_writer = tf.summary.FileWriter(os.path.join(LOG_DIR, 'train'),
                                  sess.graph)
        test_writer = tf.summary.FileWriter(os.path.join(LOG_DIR, 'test'))

        # Init variables #初始化参数,开始训练
        # train_one_epoch 函数用来训练一个epoch,eval_one_epoch函数用来每运行一个epoch后evaluate在测试集的
        # accuracy和loss。每10个epoch保存1次模型。
        init = tf.global_variables_initializer()
        #sess.run(init)
        sess.run(init, {is_training_pl: True}) # 运行sess初始化所有的全局变量

        # ops 是一个字典,作为接口传入训练和评估 epoch 循环中。
        #  pred 是数据处理网络模块;loss 是 损失函数;train_op 是优化器;batch 是当前的批次
        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) #train_one_epoch 函数用来训练一个epoch
            eval_one_epoch(sess, ops, test_writer)   #eval_one_epoch函数用来每运行一个epoch后evaluate在测试集的accuracy和loss
            
            # Save the variables to disk.每10个epoch保存1次模型
            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)


#provider.shuffle_data 函数随机打乱数据,返回打乱后的数据。
#num_batches = file_size/BATCH_SIZE,计算在指定BATCH_SIZE下,训练1个epoch 需要几个mini-batch训练。
def train_one_epoch(sess, ops, train_writer):
    """ ops: dict mapping from string to tf ops """
    is_training = True
    
    # Shuffle train files #随机打乱训练数据
    train_file_idxs = np.arange(0, len(TRAIN_FILES))
    np.random.shuffle(train_file_idxs)
    
    for fn in range(len(TRAIN_FILES)):
        log_string('----' + str(fn) + '-----')
        current_data, current_label = provider.loadDataFile(TRAIN_FILES[train_file_idxs[fn]])
        current_data = current_data[:,0:NUM_POINT,:]
        current_data, current_label, _ = provider.shuffle_data(current_data, np.squeeze(current_label))            
        current_label = np.squeeze(current_label)
        
        file_size = current_data.shape[0]
        num_batches = file_size // BATCH_SIZE
        
        total_correct = 0
        total_seen = 0
        loss_sum = 0
        # 在一个epoch 中逐个mini-batch训练直至遍历完一遍训练集。计算总分类正确数total_correct和已遍历样本数
        # total_senn,总损失loss_sum.
        for batch_idx in range(num_batches):
            start_idx = batch_idx * BATCH_SIZE
            end_idx = (batch_idx+1) * BATCH_SIZE
            
            # Augment batched point clouds by rotation and jittering
            rotated_data = provider.rotate_point_cloud(current_data[start_idx:end_idx, :, :]) #调用provider中rotate_point_cloud
            jittered_data = provider.jitter_point_cloud(rotated_data) #调用provider中jitter_point_cloud
            feed_dict = {ops['pointclouds_pl']: jittered_data,
                         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)
            # 训练,使用 tf 的 session 运行设计的框架,ops['pred'] 为整个网络,feed_dict 为网络提供的数据
            train_writer.add_summary(summary, step)
            pred_val = np.argmax(pred_val, 1)
            correct = np.sum(pred_val == current_label[start_idx:end_idx])
            total_correct += correct
            total_seen += BATCH_SIZE
            loss_sum += loss_val

        # 记录平均loss,以及平均accuracy。
        log_string('mean loss: %f' % (loss_sum / float(num_batches)))
        log_string('accuracy: %f' % (total_correct / float(total_seen)))

        
def eval_one_epoch(sess, ops, test_writer):
    """ ops: dict mapping from string to tf ops """
    is_training = False
    total_correct = 0
    total_seen = 0
    loss_sum = 0
    total_seen_class = [0 for _ in range(NUM_CLASSES)]
    total_correct_class = [0 for _ in range(NUM_CLASSES)]
    
    for fn in range(len(TEST_FILES)):
        log_string('----' + str(fn) + '-----')
        current_data, current_label = provider.loadDataFile(TEST_FILES[fn])
        current_data = current_data[:,0:NUM_POINT,:]
        current_label = np.squeeze(current_label)
        
        file_size = current_data.shape[0]
        num_batches = file_size // BATCH_SIZE
        
        for batch_idx in range(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['loss'], ops['pred']], feed_dict=feed_dict)
            pred_val = np.argmax(pred_val, 1)
            correct = np.sum(pred_val == current_label[start_idx:end_idx])
            total_correct += correct
            total_seen += BATCH_SIZE
            loss_sum += (loss_val*BATCH_SIZE)
            for i in range(start_idx, end_idx):
                l = current_label[i]
                total_seen_class[l] += 1
                total_correct_class[l] += (pred_val[i-start_idx] == l)
            
    log_string('eval mean loss: %f' % (loss_sum / float(total_seen)))
    log_string('eval accuracy: %f'% (total_correct / float(total_seen)))
    log_string('eval avg class acc: %f' % (np.mean(np.array(total_correct_class)/np.array(total_seen_class,dtype=np.float))))
         


if __name__ == "__main__":
    train()
    LOG_FOUT.close()

train.py 的一些相关命令notes:

#Step: 训练模型的步数

#Batch Size(批尺寸): 计算梯度所需的样本数量,太小会导致效率低下,无法收敛。太大会导致内存撑不住,
# Batch Size增大到一定程度后,其下降方向变化很小了,所以Batch Size是一个很重要的参数。

#Epoch(回合):代表样本集内所有的数据经过了一次训练。

#Iteration(迭代):理解迭代,只需要知道乘法表或者一个计算器就可以了。迭代是 batch 需要完成一个 epoch 的次数。
# 记住:在一个 epoch 中,batch 数和迭代数是相等的。比如对于一个有 2000 个训练样本的数据集。将 2000 个样本分成大
# 小为 500 的 batch,那么完成一个 epoch 需要 4 个 iteration
#函数实现指数衰减学习率。
tf.train.exponential_decay(
    learning_rate,初始学习率
    global_step,当前迭代次数
    decay_steps,衰减速度(在迭代到该次数时学习率衰减为earning_rate * decay_rate)
    decay_rate,学习率衰减系数,通常介于0-1之间。
    staircase=False,(默认值为False,当为True时,(global_step/decay_steps)则被转化为整数) ,选择不同的衰减方式。
    name=None
)

# 公式:decayed_learning_rate = learning_rate * decay_rate ^ (global_step / decay_steps)
# 步骤:1.首先使用较大学习率(目的:为快速得到一个比较优的解);
#       2.然后通过迭代逐步减小学习率(目的:为使模型在训练后期更加稳定);

 

tf.Graph().as_default():#表示将这个类实例,也就是新生成的图作为整个 tensorflow
                        # 运行环境的默认图,如果只有一个主线程不写也没有关系,tensorflow
                        # 里面已经存好了一张默认图,可以使用tf.get_default_graph() 来调用
                        # (显示这张默认纸),当你有多个线程就可以创造多个tf.Graph(),
                        # 就是你可以有一个画图本,有很多张图纸,这时候就会有一个默认图的概念了。
with tf.device('/gpu:'+str(GPU_INDEX)):#如果需要切换成CPU运算,可以调用tf.device(device_name)函数,
                                       # 其中device_name格式如/cpu:0其中的0表示设备号,TF不区分CPU的
                                       # 设备号,设置为0即可。GPU区分设备号/gpu:0和/gpu:1表示两张不同
                                       # 的显卡
# placeholder()函数是在神经网络构建graph的时候在模型中的占位,此时并没有把要输入的数据传入模型,它只会分配必要的内存,用于传入外部数据。

tf.placeholder(dtype, shape=None, name=None)

此函数可以理解为形参,用于定义过程,在执行的时候再赋具体的值.
是不是可以理解为:预先开辟一个空间,先占下,然后进行值传递?


参数:

dtype:数据类型。常用的是tf.float32,tf.float64等数值类型
shape:数据形状。默认是None,就是一维值,也可以是多维,比如[2,3], [None, 3]表示列是3,行不定
name:名称。

关于 tensor sess.run() 的注记:

# Session 是 Tensorflow 为了控制,和输出文件的执行的语句. 即运行代码,根据输入得到结果。
# 运行 session.run(),可以获得你要得知的运算结果, 或者是你所要运算的部分
# Session对象创建完毕,便可以使用它最重要的方法run()来启动所需要的数据流图进行计算
# run(fetches, feed_dict=None,options=None,run_metadata=None)
# feed_dict 可选项,给数据流图提供运行时数据。feed_dict的数据结构为python中的字典,其元素为各种键值对。
# "key"为各种Tensor对象的句柄;"value"很广泛,但必须和“键”的类型相匹配,或能转换为同一类型
 

参考:https://blog.csdn.net/u012436149/article/details/52908692 

           https://blog.csdn.net/jiangjingxuan/article/details/54729505

           

你可能感兴趣的:(python,机器视觉,点云)