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