From cda16a45f7ede7b8c825f00263c98512180c9dff Mon Sep 17 00:00:00 2001 From: godspeed1989 Date: Wed, 16 May 2018 23:38:23 +0000 Subject: [PATCH 1/7] smallfix --- clean.sh | 27 ++++++++++++ part_dataset.py | 36 +++++++-------- test.py | 4 +- tf_ops/approxmatch/Makefile | 23 ++++++++++ tf_ops/approxmatch/setenv.sh | 6 +++ tf_ops/approxmatch/tf_approxmatch.pyc | Bin 4165 -> 0 bytes tf_ops/approxmatch/tf_approxmatch_compile.sh | 5 --- tf_ops/approxmatch/tf_approxmatch_g.cu.o | Bin 34344 -> 0 bytes tf_ops/approxmatch/tf_approxmatch_so.so | Bin 85904 -> 0 bytes tf_ops/nn_distance/Makefile | 23 ++++++++++ tf_ops/nn_distance/setenv.sh | 6 +++ tf_ops/nn_distance/tf_nndistance.py | 4 +- tf_ops/nn_distance/tf_nndistance.pyc | Bin 2886 -> 0 bytes tf_ops/nn_distance/tf_nndistance_compile.sh | 3 -- tf_ops/nn_distance/tf_nndistance_cpu.pyc | Bin 2277 -> 0 bytes tf_ops/nn_distance/tf_nndistance_g.cu.o | Bin 23616 -> 0 bytes tf_ops/nn_distance/tf_nndistance_so.so | Bin 67965 -> 0 bytes train.py | 44 +++++++++---------- utils/render_balls_so.so | Bin 12814 -> 0 bytes 19 files changed, 128 insertions(+), 53 deletions(-) create mode 100644 clean.sh create mode 100644 tf_ops/approxmatch/Makefile create mode 100644 tf_ops/approxmatch/setenv.sh delete mode 100644 tf_ops/approxmatch/tf_approxmatch.pyc delete mode 100644 tf_ops/approxmatch/tf_approxmatch_compile.sh delete mode 100644 tf_ops/approxmatch/tf_approxmatch_g.cu.o delete mode 100755 tf_ops/approxmatch/tf_approxmatch_so.so create mode 100644 tf_ops/nn_distance/Makefile create mode 100644 tf_ops/nn_distance/setenv.sh delete mode 100644 tf_ops/nn_distance/tf_nndistance.pyc delete mode 100644 tf_ops/nn_distance/tf_nndistance_compile.sh delete mode 100644 tf_ops/nn_distance/tf_nndistance_cpu.pyc delete mode 100644 tf_ops/nn_distance/tf_nndistance_g.cu.o delete mode 100755 tf_ops/nn_distance/tf_nndistance_so.so delete mode 100755 utils/render_balls_so.so diff --git a/clean.sh b/clean.sh new file mode 100644 index 0000000..030a72f --- /dev/null +++ b/clean.sh @@ -0,0 +1,27 @@ +rm -vrf ./__pycache__/ +rm -vrf ./models/__pycache__/ + +rm -vrf ./tf_ops/nn_distance/*.pyc +rm -vrf ./tf_ops/nn_distance/__pycache__/ +cd ./tf_ops/nn_distance +make clean +cd ../.. + +rm -vrf ./tf_ops/approxmatch/*.pyc +rm -vrf ./tf_ops/approxmatch/__pycache__/ +cd ./tf_ops/approxmatch +make clean +cd ../.. + +rm -vrf ./utils/*.so +rm -vrf ./utils/__pycache__/ + + +log_list=`find . -maxdepth 1 -name 'log*'` +if [ -n "$log_list" ]; then + echo -e "\033[1m\033[95m" + echo "Log Exist, Move or Remove It!" + echo "$log_list" + echo -e "\033[0m\033[0m" +fi + diff --git a/part_dataset.py b/part_dataset.py index 25ba8b2..35ae0bb 100644 --- a/part_dataset.py +++ b/part_dataset.py @@ -27,7 +27,7 @@ def rotate_point_cloud(batch_data): BxNx3 array, rotated batch of point clouds """ rotated_data = np.zeros(batch_data.shape, dtype=np.float32) - for k in xrange(batch_data.shape[0]): + for k in range(batch_data.shape[0]): rotation_angle = np.random.uniform() * 2 * np.pi cosval = np.cos(rotation_angle) sinval = np.sin(rotation_angle) @@ -44,17 +44,17 @@ def __init__(self, root, npoints = 2500, classification = False, class_choice = self.root = root self.catfile = os.path.join(self.root, 'synsetoffset2category.txt') self.cat = {} - + self.classification = classification self.normalize = normalize - + with open(self.catfile, 'r') as f: for line in f: ls = line.strip().split() self.cat[ls[0]] = ls[1] if class_choice is not None: self.cat = {k:v for k,v in self.cat.items() if k in class_choice} - + self.meta = {} with open(os.path.join(self.root, 'train_test_split', 'shuffled_train_file_list.json'), 'r') as f: train_ids = set([str(d.split('/')[2]) for d in json.load(f)]) @@ -78,28 +78,28 @@ def __init__(self, root, npoints = 2500, classification = False, class_choice = else: print('Unknown split: %s. Exiting..'%(split)) exit(-1) - + for fn in fns: - token = (os.path.splitext(os.path.basename(fn))[0]) + token = (os.path.splitext(os.path.basename(fn))[0]) self.meta[item].append((os.path.join(dir_point, token + '.pts'), os.path.join(dir_seg, token + '.seg'))) - + self.datapath = [] for item in self.cat: for fn in self.meta[item]: self.datapath.append((item, fn[0], fn[1])) - - - self.classes = dict(zip(self.cat, range(len(self.cat)))) + + + self.classes = dict(zip(self.cat, range(len(self.cat)))) self.num_seg_classes = 0 if not self.classification: - for i in range(len(self.datapath)/50): + for i in range(len(self.datapath)//50): l = len(np.unique(np.loadtxt(self.datapath[i][-1]).astype(np.uint8))) if l > self.num_seg_classes: self.num_seg_classes = l - + self.cache = {} # from index to (point_set, cls, seg) tuple self.cache_size = 18000 - + def __getitem__(self, index): if index in self.cache: point_set, seg, cls = self.cache[index] @@ -113,8 +113,8 @@ def __getitem__(self, index): seg = np.loadtxt(fn[2]).astype(np.int64) - 1 if len(self.cache) < self.cache_size: self.cache[index] = (point_set, seg, cls) - - + + choice = np.random.choice(len(seg), self.npoints, replace=True) #resample point_set = point_set[choice, :] @@ -123,7 +123,7 @@ def __getitem__(self, index): return point_set, cls else: return point_set, seg - + def __len__(self): return len(self.datapath) @@ -135,13 +135,13 @@ def __len__(self): tic = time.time() i = 100 ps, seg = d[i] - print np.max(seg), np.min(seg) + print(np.max(seg), np.min(seg)) print(time.time() - tic) print(ps.shape, type(ps), seg.shape,type(seg)) sys.path.append('utils') import show3d_balls show3d_balls.showpoints(ps, ballradius=8) - + d = PartDataset(root = os.path.join(BASE_DIR, 'data/shapenetcore_partanno_segmentation_benchmark_v0'), classification = True) print(len(d)) ps, cls = d[0] diff --git a/test.py b/test.py index 8d7517e..c1c5c81 100644 --- a/test.py +++ b/test.py @@ -59,7 +59,7 @@ def get_model(batch_size, num_point): def inference(sess, ops, pc, batch_size): ''' pc: BxNx3 array, return BxN pred ''' assert pc.shape[0]%batch_size == 0 - num_batches = pc.shape[0]/batch_size + num_batches = pc.shape[0] // batch_size logits = np.zeros((pc.shape[0], pc.shape[1], 3)) for i in range(num_batches): feed_dict = {ops['pointclouds_pl']: pc[i*batch_size:(i+1)*batch_size,...], @@ -80,7 +80,7 @@ def inference(sess, ops, pc, batch_size): np.random.shuffle(indices) for i in range(len(TEST_DATASET)): ps, seg = TEST_DATASET[indices[i]] - pred = inference(sess, ops, np.expand_dims(ps,0), batch_size=1) + pred = inference(sess, ops, np.expand_dims(ps,0), batch_size=1) pred = pred.squeeze() show3d_balls.showpoints(ps, ballradius=8) diff --git a/tf_ops/approxmatch/Makefile b/tf_ops/approxmatch/Makefile new file mode 100644 index 0000000..8672bbe --- /dev/null +++ b/tf_ops/approxmatch/Makefile @@ -0,0 +1,23 @@ +CUDA_ROOT = /opt/cuda + +INCLUDE += -I ${TF_INC} +INCLUDE += -I ${TF_INC}/external/nsync/public +INCLUDE += -I /opt +LIB = -lcudart -ltensorflow_framework -L ${CUDA_ROOT}/lib64 -L ${TF_LIB} +FLAGS = -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=1 +CUDA_FLAGS = -D GOOGLE_CUDA=1 -x cu -Xcompiler -fPIC --expt-relaxed-constexpr --Wno-deprecated-gpu-targets + +#fix cannot find cuda/cuda_config.h +# cd `python -c 'import tensorflow as tf; print(tf.sysconfig.get_include())'` +# cd tensorflow/stream_executor/cuda +# sudo curl -O https://raw.githubusercontent.com/tensorflow/tensorflow/master/third_party/toolchains/gpus/cuda/cuda/cuda_config.h + +all: tf_approxmatch_so.so + +tf_approxmatch_so.so: tf_approxmatch.cpp tf_approxmatch_g.cu.o + g++ -fPIC -shared ${FLAGS} $+ -o $@ ${INCLUDE} ${LIB} +tf_approxmatch_g.cu.o: + nvcc -std=c++11 -c -o $@ tf_approxmatch_g.cu ${INCLUDE} ${CUDA_FLAGS} + +clean: + rm -rf *.o *.so diff --git a/tf_ops/approxmatch/setenv.sh b/tf_ops/approxmatch/setenv.sh new file mode 100644 index 0000000..43c6b9a --- /dev/null +++ b/tf_ops/approxmatch/setenv.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +export TF_INC=$(python -c 'import tensorflow as tf; print(tf.sysconfig.get_include())') +export TF_LIB=$(python -c 'import tensorflow as tf; print(tf.sysconfig.get_lib())') + +export LD_LIBRARY_PATH=$TF_LIB:$LD_LIBRARY_PATH diff --git a/tf_ops/approxmatch/tf_approxmatch.pyc b/tf_ops/approxmatch/tf_approxmatch.pyc deleted file mode 100644 index dee7adbd7b50124a9513963458a98e3a33d4af1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4165 zcmcIm&2Jmm5uYU`TBM{})`zVxJ52l$){UiFYEs8>V#|^vr-@~o3ZizAV6ocwNWL|> zOYJ_|q9L8sqChXrvHwF)J>}e6PwlO@0!42{k3~_Sm-IKYq(mqELR)LOqxWX!&G*dP z{GZ25+von~2Gsi-$NxKc)6OuFB60y8QaNfms3!Nf8O{zpsa+K^1t5I@iNR5$uwr|!sa%cPNIdbRv>v?kL`|AaA7f6kh zyC_=D3%o!IHo=-jQWG?-&QL86m5%$0kjtcJDV(S9)jrc1I?7Y{8mURPjE%cU8t1`( zk-c;;$-&E{;fx^lB|gHOSA_IBg%wg$j0wL<`ihXQ3h7&#-wC*WmFmzt zN5&z2<0OO#(pw1lrwHBK)CWUk_z%oZI(wcj#uO6vE z{NJVhzi_DZdu#!`oC8*Z_Pc)25RcoW?+DAg11t*V@{a7oHPWkm(sA#SUZ8NDH1urr zMS_SBa&S>=)9M19gN7H$GhZcxn>jLt`tCmwa6d?h|qxqeE z-8XOJvmel>d2%<&y-)53;=%X&9^B{t7_@nF5CJPuChT&iN0bcc`D>;pGxLAcgXTHb zgKETY@z@`4Xs4IH!O}clbK!9*f7lObM#-x31uk^FdWD01lyhd`+EnB$aEp7~;r&BWuX6FI*XXSKh%|nWNlBscnEJwbwD*OB-_8znKK_1$ z9?Q{o^JLiVr-*TuEj|?h+FNBHBr568@hbYPl87ILHtxF zucze=lUhFt^irwxFfnx#`OUK5v5h#nS-$Vbd&bAH_x-5!AXcVkv?}j*%j*q4Y3j6G zT`E5|CG#czwki&cuz7&Eou<>gsd;{8lW&8Dm&QwJ9OOXYml@abrV{{`D0Jw6=m7o0 zUa_q7X8IDx1f*bIJ=Ev7>-% zl8guJZ@}T_mu-h|T>`&tC)e~P?X_1xw<%&NbjtE+@*3JNzs-c7*xBXd=(6Gl}T; zw|fh^V-Uuy#?pr0)>#NSq=qZdvTL)ptMXj23oqa>esFz6OSQyr>1T2Bgk@wmL)uNV zgl9&zecKS>$|Mn(W$o{#g3n@GGffSxyt%u!eb3wYplZ3+5I2P}=rzr5;wN44F~lt( z)hN?XEZvGXQB+1pwkl=IiLL57f})dS3QzG~D%lFC3hmb;aY=6LG&OM~VpFWesicz$#W8FACgga2v*|tUS)QfyDrIPBEAS) z*jP{m!AaPMi*+p}XvEP5zCeN}a z(Y}>^ue`@~m-4VGeSFroHw28xj9hIJFCF1;W_WjJ6rOG&M+iTIg`%c0cx|TcK`qY diff --git a/tf_ops/approxmatch/tf_approxmatch_compile.sh b/tf_ops/approxmatch/tf_approxmatch_compile.sh deleted file mode 100644 index cf31396..0000000 --- a/tf_ops/approxmatch/tf_approxmatch_compile.sh +++ /dev/null @@ -1,5 +0,0 @@ -#/bin/bash - -# TF1.4 -/usr/local/cuda-8.0/bin/nvcc tf_approxmatch_g.cu -o tf_approxmatch_g.cu.o -c -O2 -DGOOGLE_CUDA=1 -x cu -Xcompiler -fPIC -g++ -std=c++11 tf_approxmatch.cpp tf_approxmatch_g.cu.o -o tf_approxmatch_so.so -shared -fPIC -I /usr/local/lib/python2.7/dist-packages/tensorflow/include -I /usr/local/cuda-8.0/include -I /usr/local/lib/python2.7/dist-packages/tensorflow/include/external/nsync/public -lcudart -L /usr/local/cuda-8.0/lib64/ -L/usr/local/lib/python2.7/dist-packages/tensorflow -ltensorflow_framework -O2 -D_GLIBCXX_USE_CXX11_ABI=0 diff --git a/tf_ops/approxmatch/tf_approxmatch_g.cu.o b/tf_ops/approxmatch/tf_approxmatch_g.cu.o deleted file mode 100644 index 7ae2a4eb37b3818b26a58b2c42feaf8d4b45fac4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34344 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 NTl=@MEO@p&|2G`8=+*!L diff --git a/tf_ops/approxmatch/tf_approxmatch_so.so b/tf_ops/approxmatch/tf_approxmatch_so.so deleted file mode 100755 index 31dd2ad068f1200284ed44588ae91208562e6654..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 85904 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

i}Hc?@B(Zt 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 diff --git a/tf_ops/nn_distance/Makefile b/tf_ops/nn_distance/Makefile new file mode 100644 index 0000000..3f687f6 --- /dev/null +++ b/tf_ops/nn_distance/Makefile @@ -0,0 +1,23 @@ +CUDA_ROOT = /opt/cuda + +INCLUDE += -I ${TF_INC} +INCLUDE += -I ${TF_INC}/external/nsync/public +INCLUDE += -I /opt +LIB = -lcudart -ltensorflow_framework -L ${CUDA_ROOT}/lib64 -L ${TF_LIB} +FLAGS = -std=c++11 -D_GLIBCXX_USE_CXX11_ABI=1 +CUDA_FLAGS = -D GOOGLE_CUDA=1 -x cu -Xcompiler -fPIC --expt-relaxed-constexpr --Wno-deprecated-gpu-targets + +#fix cannot find cuda/cuda_config.h +# cd `python -c 'import tensorflow as tf; print(tf.sysconfig.get_include())'` +# cd tensorflow/stream_executor/cuda +# sudo curl -O https://raw.githubusercontent.com/tensorflow/tensorflow/master/third_party/toolchains/gpus/cuda/cuda/cuda_config.h + +all: tf_nndistance_so.so + +tf_nndistance_so.so: tf_nndistance.cpp tf_nndistance_g.cu.o + g++ -fPIC -shared ${FLAGS} $+ -o $@ ${INCLUDE} ${LIB} +tf_nndistance_g.cu.o: + nvcc -std=c++11 -c -o $@ tf_nndistance_g.cu ${INCLUDE} ${CUDA_FLAGS} + +clean: + rm -rf *.o *.so diff --git a/tf_ops/nn_distance/setenv.sh b/tf_ops/nn_distance/setenv.sh new file mode 100644 index 0000000..43c6b9a --- /dev/null +++ b/tf_ops/nn_distance/setenv.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +export TF_INC=$(python -c 'import tensorflow as tf; print(tf.sysconfig.get_include())') +export TF_LIB=$(python -c 'import tensorflow as tf; print(tf.sysconfig.get_lib())') + +export LD_LIBRARY_PATH=$TF_LIB:$LD_LIBRARY_PATH diff --git a/tf_ops/nn_distance/tf_nndistance.py b/tf_ops/nn_distance/tf_nndistance.py index e4faa5e..4c92637 100644 --- a/tf_ops/nn_distance/tf_nndistance.py +++ b/tf_ops/nn_distance/tf_nndistance.py @@ -58,11 +58,11 @@ def _nn_distance_grad(op,grad_dist1,grad_idx1,grad_dist2,grad_idx2): t0=time.time() t1=t0 best=1e100 - for i in xrange(100): + for i in range(100): trainloss,_=sess.run([loss,train]) newt=time.time() best=min(best,newt-t1) - print i,trainloss,(newt-t0)/(i+1),best + print(i,trainloss,(newt-t0)/(i+1),best) t1=newt #print sess.run([inp1,retb,inp2,retd]) #grads=compute_gradient([inp1,inp2],[(16,32,3),(16,32,3)],loss,(1,),[xyz1,xyz2]) diff --git a/tf_ops/nn_distance/tf_nndistance.pyc b/tf_ops/nn_distance/tf_nndistance.pyc deleted file mode 100644 index b713308f59dff77e5b642527fb358d3c171e532d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2886 zcmcIlOLH4V5bl+2*_Q426~AyE8}d?KvaKXMQWOMYa8eX`C@Dg1h1y!}jHFRkv+~S{ z*s1tr?p(OQKj6THzW~36D>sTN=R0FxB@0A?XL zo&%VJU@oWUA(&@+6p{t#EJCmdo}7dB5(oz5$>Y#Iz$3P|K#Y;E?}QgDLtoYhMF-nT=in4Ir1HU17BqTqU_D z=b=4MOt1=a6@oPon7=A)ZGybs&&&GPF$6dA-RS2EH2Hv@6(G0?!8@>Lu zcDg+${X4Cw(~#z(_1Bekk&b1hRC-`kQ|T!2qn>M}=9YgqN;g%coBn5!u9Ut=g=#1% z{Q5TSH;J@QmYCG|J{?w39E;0AC?yVlMU(+IT|eFvdUD>jX>{(%PR-N9j}aB=`` z4_*Ph!dfQ8WXk@IbE%Km`mQDY+?Jm%?ff`N zd%~8K#;|YsTiY+!Zu#YU` zca>uyA!mKf(Gs&%YzG&s2NtU}0#GAVd<3jd91{QIS-5F*fyc8~B!u=f3N4)q6ehgv zaU-M{b|R(2kXwqQT*O%qYCwLr$ZU+NK5#zTnKehFd1J*HWxA z?7O z%}pYM;Rr1EFi~|AnQb=L##YI-!e!1$s>vqSwlo`VywOT2IHJeWT9xX)&{}p$9yI%k ztlRF&Lq+(+^dv|9Wn@%T$KJsesk>_|#r90!JC{qVOc?2+oT%qSoD+gi&q&dWWoUa@ zbV=IB;|$i9TSV}AuGXcEF|Y?+%m9943_I>9%^ZIqO)uMlGN+;hmBJ`V!f*PJv$J-q zS(#-0t&ZP1j(Q6d(3FlF0AEK2z;H+wGHNYfIQE2Y%P7sdB2)i$YvkxY$olk$R13w(-p(m+#yjJkDHOd)e3rcv$j#zf5uaJ)$8kRC{R0Nn B&vpO+ diff --git a/tf_ops/nn_distance/tf_nndistance_compile.sh b/tf_ops/nn_distance/tf_nndistance_compile.sh deleted file mode 100644 index aae2c19..0000000 --- a/tf_ops/nn_distance/tf_nndistance_compile.sh +++ /dev/null @@ -1,3 +0,0 @@ -/usr/local/cuda-8.0/bin/nvcc tf_nndistance_g.cu -o tf_nndistance_g.cu.o -c -O2 -DGOOGLE_CUDA=1 -x cu -Xcompiler -fPIC -g++ -std=c++11 tf_nndistance.cpp tf_nndistance_g.cu.o -o tf_nndistance_so.so -shared -fPIC -I /usr/local/lib/python2.7/dist-packages/tensorflow/include -I /usr/local/cuda-8.0/include -I /usr/local/lib/python2.7/dist-packages/tensorflow/include/external/nsync/public -lcudart -L /usr/local/cuda-8.0/lib64/ -L/usr/local/lib/python2.7/dist-packages/tensorflow -ltensorflow_framework -O2 -D_GLIBCXX_USE_CXX11_ABI=0 - diff --git a/tf_ops/nn_distance/tf_nndistance_cpu.pyc b/tf_ops/nn_distance/tf_nndistance_cpu.pyc deleted file mode 100644 index 1a50f8716a4a19ba3cf7a0d33c23805be9ec7412..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2277 zcmcIl&2A$_5Uw8o|3F~Pq69e11<64R7Us5Ep%ojEa7cn2q?H1z(J<3a#@IcR>7FG) z_9^frJP+@H#3OLy0$YDn#sr>my``7pWI*(|1dH8>f#~wh$ z_$itaovYQ@qw@;I9{mhumEwvFYNYEFSLp|j^2elWbW5a+Y(DGP;3)YU*Ga_AyLjq& zp5U=c5zUDv#5)jbZ=z`7(OkWts*v+&QpzgtKnAxS@4#=4gcMy=S+l5aTZ%zxIbYc? zEo(G~s~YVt>aquO9Kxd>4u!MaDz#|HLY?LfJ}Jk9vMI{Owz9Qtsl>9qZCNTiG;h+p zMe{bzJ2b9RSt0ueO40UlePy!3J(M9&v*^=@{&<>(&j031-)Uph+)s>eFT+gx{VyJz zJb1G2yG!klliWH#OB3V#Xqp!BhL7RhK8{yDtd3F>|HsK`;kJ^B6YE};*1peHp^1=L zp3au(xzsOXtdmRPYq&zvpABDj!hPT9FxNPp(aFW-_o%>~E;;b4PW)ne%abENspDpP zaCKLD$1h9o|6C?`&6F?@Tood`PON~V-(*z_;zGMXUan*0JP)UZM)PBFEIV8>)vklN zzRp4u2XQj9u8n!F<08_5EoPh388TdXQfwgRk>+q9bPq(#1x* zkHf;H+C*ur^MM-&X=Vq;AZ*!LtTJXb3!<#}B)gF_Y#6bcYi-} zbM7cG7u*~?i)qoO>u*IRo)4ZK)1o7JgNsTkZ41v6{o>KMLVG1=f~WXPu-5Lzb?U+A zzbxj_qpRQP>D6!a6w1RR*q-&dDNhmzDw|$D1l)TGH>6;oFH?cUdUZo5c5KpT03>3< zx<2>6fWlqqkYyW6@*r4uoXH?J4L=L{46F_j8v&*LgC& z0W{V~38EtV04vsqAoZ5&s(ZM1aKEqKEN^cI(CDdCCFKf&S(un0=rg>9Qp%4HzC}Nl z1?Kl*idl9u;+Rs_(T8lz)eZs^&NQ}TTw+~DuLbZN@6jxci>YSZlI8c&Rb8*O+v;`I EKlnBU2LJ#7 diff --git a/tf_ops/nn_distance/tf_nndistance_g.cu.o b/tf_ops/nn_distance/tf_nndistance_g.cu.o deleted file mode 100644 index b5ee1051409ebce49939a7312a0d9a24d3592695..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23616 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%0D$A#Pzxo$vg{z5Ea2L;Bez0LzY~r7>1}4? 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 Date: Sat, 19 May 2018 11:04:25 +0800 Subject: [PATCH 2/7] update maxpool --- README.md | 12 ++++++++++++ models/model.py | 15 +++++++++------ models/model_emd.py | 17 ++++++++++------- models/model_fc_upconv.py | 17 ++++++++++------- models/model_hierachy.py | 25 ++++++++++++++++--------- models/model_upconv.py | 18 +++++++++--------- test.py | 11 +++++++---- tf_ops/approxmatch/tf_approxmatch.py | 6 +++--- train.py | 4 ++++ 9 files changed, 80 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 5cbe79d..1a0dd77 100644 --- a/README.md +++ b/README.md @@ -38,3 +38,15 @@ To test and visualize results of the trained autoencoder above, simply run: You can check more options for testing by: python test.py -h + +### Evaluation origin + +model(nn) 8552576 0.0026 + +model_emd 8552576 0.0028 + +model_upconv 5892995 0.0023 + +model_hierachy 9568417 0.0023 + +model_fc_upconv 6880644 0.0023 diff --git a/models/model.py b/models/model.py index 560fde7..714e355 100644 --- a/models/model.py +++ b/models/model.py @@ -16,9 +16,13 @@ sys.path.append(os.path.join(ROOT_DIR, 'tf_ops/nn_distance')) import tf_nndistance -def placeholder_inputs(batch_size, num_point): - pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) - labels_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) +num_point = 0 + +def placeholder_inputs(batch_size, n_point): + global num_point + num_point = n_point + pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) + labels_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) return pointclouds_pl, labels_pl @@ -32,8 +36,8 @@ def get_model(point_cloud, is_training, bn_decay=None): net: TF tensor BxNx3, reconstructed point clouds end_points: dict """ + global num_point batch_size = point_cloud.get_shape()[0].value - num_point = point_cloud.get_shape()[1].value point_dim = point_cloud.get_shape()[2].value end_points = {} @@ -60,8 +64,7 @@ def get_model(point_cloud, is_training, bn_decay=None): padding='VALID', stride=[1,1], bn=True, is_training=is_training, scope='conv5', bn_decay=bn_decay) - global_feat = tf_util.max_pool2d(net, [num_point,1], - padding='VALID', scope='maxpool') + global_feat = tf.reduce_max(net, axis=1, keepdims=False) net = tf.reshape(global_feat, [batch_size, -1]) end_points['embedding'] = net diff --git a/models/model_emd.py b/models/model_emd.py index 885574d..0322702 100644 --- a/models/model_emd.py +++ b/models/model_emd.py @@ -18,9 +18,13 @@ sys.path.append(os.path.join(ROOT_DIR, 'tf_ops/approxmatch')) import tf_approxmatch -def placeholder_inputs(batch_size, num_point): - pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) - labels_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) +num_point = 0 + +def placeholder_inputs(batch_size, n_point): + global num_point + num_point = n_point + pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) + labels_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) return pointclouds_pl, labels_pl @@ -34,8 +38,8 @@ def get_model(point_cloud, is_training, bn_decay=None): net: TF tensor BxNxC, reconstructed point clouds end_points: dict """ + global num_point batch_size = point_cloud.get_shape()[0].value - num_point = point_cloud.get_shape()[1].value point_dim = point_cloud.get_shape()[2].value end_points = {} @@ -62,8 +66,7 @@ def get_model(point_cloud, is_training, bn_decay=None): padding='VALID', stride=[1,1], bn=True, is_training=is_training, scope='conv5', bn_decay=bn_decay) - global_feat = tf_util.max_pool2d(net, [num_point,1], - padding='VALID', scope='maxpool') + global_feat = tf.reduce_max(net, axis=1, keepdims=False) net = tf.reshape(global_feat, [batch_size, -1]) end_points['embedding'] = net @@ -93,5 +96,5 @@ def get_loss(pred, label, end_points): with tf.Graph().as_default(): inputs = tf.zeros((32,1024,3)) outputs = get_model(inputs, tf.constant(True)) - print outputs + print(outputs) loss = get_loss(outputs[0], tf.zeros((32,1024,3)), outputs[1]) diff --git a/models/model_fc_upconv.py b/models/model_fc_upconv.py index 06db27e..130ae2f 100644 --- a/models/model_fc_upconv.py +++ b/models/model_fc_upconv.py @@ -16,9 +16,14 @@ sys.path.append(os.path.join(ROOT_DIR, 'tf_ops/nn_distance')) import tf_nndistance -def placeholder_inputs(batch_size, num_point): - pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) - labels_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) + +num_point = 0 + +def placeholder_inputs(batch_size, n_point): + global num_point + num_point = n_point + pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) + labels_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) return pointclouds_pl, labels_pl @@ -32,9 +37,8 @@ def get_model(point_cloud, is_training, bn_decay=None): net: TF tensor BxNx3, reconstructed point clouds end_points: dict """ + global num_point batch_size = point_cloud.get_shape()[0].value - num_point = point_cloud.get_shape()[1].value - assert(num_point==2048) point_dim = point_cloud.get_shape()[2].value end_points = {} @@ -61,8 +65,7 @@ def get_model(point_cloud, is_training, bn_decay=None): padding='VALID', stride=[1,1], bn=True, is_training=is_training, scope='conv5', bn_decay=bn_decay) - global_feat = tf_util.max_pool2d(net, [num_point,1], - padding='VALID', scope='maxpool') + global_feat = tf.reduce_max(net, axis=1, keepdims=False) net = tf.reshape(global_feat, [batch_size, -1]) net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training, scope='fc00', bn_decay=bn_decay) diff --git a/models/model_hierachy.py b/models/model_hierachy.py index 9e36fab..9a0e24a 100644 --- a/models/model_hierachy.py +++ b/models/model_hierachy.py @@ -16,9 +16,14 @@ sys.path.append(os.path.join(ROOT_DIR, 'tf_ops/nn_distance')) import tf_nndistance -def placeholder_inputs(batch_size, num_point): - pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) - labels_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) + +num_point = 0 + +def placeholder_inputs(batch_size, n_point): + global num_point + num_point = n_point + pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) + labels_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) return pointclouds_pl, labels_pl @@ -32,8 +37,8 @@ def get_model(point_cloud, is_training, bn_decay=None): pc_xyz: TF tensor BxNxC, reconstructed point clouds end_points: dict """ + global num_point batch_size = point_cloud.get_shape()[0].value - num_point = point_cloud.get_shape()[1].value point_dim = point_cloud.get_shape()[2].value end_points = {} @@ -60,8 +65,7 @@ def get_model(point_cloud, is_training, bn_decay=None): padding='VALID', stride=[1,1], bn=True, is_training=is_training, scope='conv5', bn_decay=bn_decay) - global_feat = tf_util.max_pool2d(net, [num_point,1], - padding='VALID', scope='maxpool') + global_feat = tf.reduce_max(net, axis=1, keepdims=False) net = tf.reshape(global_feat, [batch_size, -1]) net = tf_util.fully_connected(net, 512, bn=True, is_training=is_training, scope='fc00', bn_decay=bn_decay) @@ -78,10 +82,13 @@ def get_model(point_cloud, is_training, bn_decay=None): pc1_xyz = tf.reshape(pc1_xyz, [batch_size, 64, 3]) end_points['pc1_xyz'] = pc1_xyz + # (N,64,256) -> (N, 64, sampled*3) -> (N, 64, sampled, 3) + sampled = num_point // 64 # 32(2048/64) pc2 = tf_util.conv1d(pc1_feat, 256, 1, padding='VALID', stride=1, bn=True, is_training=is_training, scope='fc_conv1', bn_decay=bn_decay) - pc2_xyz = tf_util.conv1d(pc2, (num_point/64)*3, 1, padding='VALID', stride=1, activation_fn=None, scope='fc_conv3') # B,64,32*3 - pc2_xyz = tf.reshape(pc2_xyz, [batch_size, 64, num_point/64, 3]) - pc1_xyz_expand = tf.expand_dims(pc1_xyz, 2) # B,64,1,3 + pc2_xyz = tf_util.conv1d(pc2, sampled*3, 1, padding='VALID', stride=1, activation_fn=None, scope='fc_conv3') + pc2_xyz = tf.reshape(pc2_xyz, [batch_size, 64, sampled, 3]) + # (N,64,3) -> (N,64,1,3) + pc1_xyz_expand = tf.expand_dims(pc1_xyz, 2) # Translate local XYZs to global XYZs pc2_xyz = pc2_xyz + pc1_xyz_expand pc_xyz = tf.reshape(pc2_xyz, [batch_size, num_point, 3]) diff --git a/models/model_upconv.py b/models/model_upconv.py index 9aebbae..f9040ca 100644 --- a/models/model_upconv.py +++ b/models/model_upconv.py @@ -16,9 +16,13 @@ sys.path.append(os.path.join(ROOT_DIR, 'tf_ops/nn_distance')) import tf_nndistance -def placeholder_inputs(batch_size, num_point): - pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) - labels_pl = tf.placeholder(tf.float32, shape=(batch_size, num_point, 3)) +num_point = 0 + +def placeholder_inputs(batch_size, n_point): + global num_point + num_point = n_point + pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) + labels_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) return pointclouds_pl, labels_pl @@ -32,9 +36,8 @@ def get_model(point_cloud, is_training, bn_decay=None): net: TF tensor BxNx3, reconstructed point clouds end_points: dict """ + global num_point batch_size = point_cloud.get_shape()[0].value - num_point = point_cloud.get_shape()[1].value - assert(num_point==2048) point_dim = point_cloud.get_shape()[2].value end_points = {} @@ -61,13 +64,10 @@ def get_model(point_cloud, is_training, bn_decay=None): padding='VALID', stride=[1,1], bn=True, is_training=is_training, scope='conv5', bn_decay=bn_decay) - global_feat = tf_util.max_pool2d(net, [num_point,1], - padding='VALID', scope='maxpool') + global_feat = tf.reduce_max(net, axis=1, keepdims=False) net = tf.reshape(global_feat, [batch_size, -1]) net = tf_util.fully_connected(net, 1024, bn=True, is_training=is_training, scope='fc00', bn_decay=bn_decay) - - net = tf.reshape(net, [batch_size, -1]) end_points['embedding'] = net # UPCONV Decoder diff --git a/test.py b/test.py index c1c5c81..5883be7 100644 --- a/test.py +++ b/test.py @@ -11,7 +11,6 @@ ROOT_DIR = BASE_DIR sys.path.append(BASE_DIR) # model sys.path.append(ROOT_DIR) # provider -sys.path.append(os.path.join(ROOT_DIR, 'models')) sys.path.append(os.path.join(ROOT_DIR, 'utils')) import show3d_balls import part_dataset @@ -21,14 +20,14 @@ parser.add_argument('--num_point', type=int, default=2048, help='Point Number [default: 2048]') parser.add_argument('--category', default=None, help='Which single class to train on [default: None]') parser.add_argument('--model', default='model', help='Model name [default: model]') -parser.add_argument('--model_path', default='log/model.ckpt', help='model checkpoint file path [default: log/model.ckpt]') +parser.add_argument('--log_path', default='./log', help='model checkpoint file path [default: ./log]') parser.add_argument('--num_group', type=int, default=1, help='Number of groups of generated points -- used for hierarchical FC decoder. [default: 1]') FLAGS = parser.parse_args() -MODEL_PATH = FLAGS.model_path GPU_INDEX = FLAGS.gpu NUM_POINT = FLAGS.num_point +sys.path.append(FLAGS.log_path) MODEL = importlib.import_module(FLAGS.model) # import network module DATA_PATH = os.path.join(BASE_DIR, 'data/shapenetcore_partanno_segmentation_benchmark_v0') TEST_DATASET = part_dataset.PartDataset(root=DATA_PATH, npoints=NUM_POINT, classification=False, class_choice=FLAGS.category, split='test',normalize=True) @@ -47,8 +46,12 @@ def get_model(batch_size, num_point): config.gpu_options.allow_growth = True config.allow_soft_placement = True sess = tf.Session(config=config) + # Statistic parameters + parameter_num = np.sum([np.prod(v.shape.as_list()) for v in tf.trainable_variables()]) + print('Parameter number: {}'.format(parameter_num)) # Restore variables from disk. - saver.restore(sess, MODEL_PATH) + checkpoint_file = tf.train.latest_checkpoint(FLAGS.log_path) + saver.restore(sess, checkpoint_file) ops = {'pointclouds_pl': pointclouds_pl, 'labels_pl': labels_pl, 'is_training_pl': is_training_pl, diff --git a/tf_ops/approxmatch/tf_approxmatch.py b/tf_ops/approxmatch/tf_approxmatch.py index 115e82f..885be53 100644 --- a/tf_ops/approxmatch/tf_approxmatch.py +++ b/tf_ops/approxmatch/tf_approxmatch.py @@ -77,7 +77,7 @@ def _match_cost_grad(op,grad_cost): while True: meanloss=0 meantrueloss=0 - for i in xrange(1001): + for i in range(1001): #phi=np.random.rand(4*npoint)*math.pi*2 #tpoints=(np.hstack([np.cos(phi)[:,None],np.sin(phi)[:,None],(phi*0)[:,None]])*random.random())[None,:,:] #tpoints=((np.random.rand(400)-0.5)[:,None]*[0,2,0]+[(random.random()-0.5)*2,0,0]).astype('float32')[None,:,:] @@ -87,7 +87,7 @@ def _match_cost_grad(op,grad_cost): #trainmatch=trainmatch.transpose((0,2,1)) show=np.zeros((400,400,3),dtype='uint8')^255 trainmypoints=sess.run(mypoints) - for i in xrange(len(tpoints[0])): + for i in range(len(tpoints[0])): u=np.random.choice(range(len(trainmypoints[0])),p=trainmatch[0].T[i]) cv2.line(show, (int(tpoints[0][i,1]*100+200),int(tpoints[0][i,0]*100+200)), @@ -98,7 +98,7 @@ def _match_cost_grad(op,grad_cost): for x,y,z in trainmypoints[0]: cv2.circle(show,(int(y*100+200),int(x*100+200)),3,cv2.cv.CV_RGB(0,0,255)) cost=((tpoints[0][:,None,:]-np.repeat(trainmypoints[0][None,:,:],4,axis=1))**2).sum(axis=2)**0.5 - print trainloss#,trueloss + print(trainloss) #,trueloss cv2.imshow('show',show) cmd=cv2.waitKey(10)%256 if cmd==ord('q'): diff --git a/train.py b/train.py index a175079..87bc479 100644 --- a/train.py +++ b/train.py @@ -121,6 +121,10 @@ def train(): # Add ops to save and restore all the variables. saver = tf.train.Saver() + # Statistic parameters + parameter_num = np.sum([np.prod(v.shape.as_list()) for v in tf.trainable_variables()]) + print('Parameter number: {}'.format(parameter_num)) + # Create a session config = tf.ConfigProto() config.gpu_options.allow_growth = True From 7135ddee9146df3bde26a5fad35377ae2cd87841 Mon Sep 17 00:00:00 2001 From: godspeed1989 Date: Sat, 19 May 2018 23:05:02 +0000 Subject: [PATCH 3/7] add upconv_v --- models/model_upconv_v.py | 124 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 models/model_upconv_v.py diff --git a/models/model_upconv_v.py b/models/model_upconv_v.py new file mode 100644 index 0000000..23be555 --- /dev/null +++ b/models/model_upconv_v.py @@ -0,0 +1,124 @@ +""" TF model for point cloud autoencoder. PointNet encoder, UPCONV decoder. +Using GPU Chamfer's distance loss. Required to have 2048 points. + +Author: Charles R. Qi +Date: May 2018 +""" +import tensorflow as tf +import numpy as np +import math +import sys +import os +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +ROOT_DIR = os.path.dirname(BASE_DIR) +sys.path.append(os.path.join(ROOT_DIR, 'utils')) +import tf_util +sys.path.append(os.path.join(ROOT_DIR, 'tf_ops/nn_distance')) +import tf_nndistance + + +def placeholder_inputs(batch_size, n_point): + pointclouds_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) + labels_pl = tf.placeholder(tf.float32, shape=(batch_size, None, 3)) + return pointclouds_pl, labels_pl + + +def get_model(point_cloud, is_training, bn_decay=None): + """ Autoencoder for point clouds. + Input: + point_cloud: TF tensor BxNx3 + is_training: boolean + bn_decay: float between 0 and 1 + Output: + net: TF tensor BxNx3, reconstructed point clouds + end_points: dict + """ + batch_size = point_cloud.get_shape()[0].value + point_dim = point_cloud.get_shape()[2].value + end_points = {} + + input_image = tf.expand_dims(point_cloud, -1) + + # Encoder + net = tf_util.conv2d(input_image, 64, [1,point_dim], + padding='VALID', stride=[1,1], + bn=True, is_training=is_training, + scope='conv1', bn_decay=bn_decay) + net = tf_util.conv2d(net, 64, [1,1], + padding='VALID', stride=[1,1], + bn=True, is_training=is_training, + scope='conv2', bn_decay=bn_decay) + point_feat = tf_util.conv2d(net, 64, [1,1], + padding='VALID', stride=[1,1], + bn=True, is_training=is_training, + scope='conv3', bn_decay=bn_decay) + net = tf_util.conv2d(point_feat, 128, [1,1], + padding='VALID', stride=[1,1], + bn=True, is_training=is_training, + scope='conv4', bn_decay=bn_decay) + net = tf_util.conv2d(net, 512, [1,1], + padding='VALID', stride=[1,1], + bn=True, is_training=is_training, + scope='conv5', bn_decay=bn_decay) + global_feat = tf.reduce_max(net, axis=1, keepdims=False) + global_feat = tf.reshape(global_feat, [batch_size, -1]) + ''' + net = tf_util.fully_connected(net, 1024, bn=True, is_training=is_training, scope='fc00', bn_decay=bn_decay) + end_points['embedding'] = net + ''' + with tf.variable_scope('vae'): + def glorot_init(shape): + return tf.random_normal(shape=shape, stddev=1. / tf.sqrt(shape[0] / 2.)) + # Variables + hidden_dim = 512 + latent_dim = 1024 + weights = { + 'z_mean': tf.Variable(glorot_init([hidden_dim, latent_dim])), + 'z_std': tf.Variable(glorot_init([hidden_dim, latent_dim])), + } + biases = { + 'z_mean': tf.Variable(glorot_init([latent_dim])), + 'z_std': tf.Variable(glorot_init([latent_dim])), + } + z_mean = tf.matmul(global_feat, weights['z_mean']) + biases['z_mean'] + z_std = tf.matmul(global_feat, weights['z_std']) + biases['z_std'] + end_points['z_mean'] = z_mean + end_points['z_std'] = z_std + # Sampler: Normal (gaussian) random distribution + eps = tf.random_normal(tf.shape(z_std), dtype=tf.float32, mean=0., stddev=1.0, name='epsilon') + z = z_mean + tf.exp(z_std / 2) * eps + + # UPCONV Decoder + # (N,1024) -> (N,1,2,512) + net = tf.reshape(z, [batch_size, 1, 2, -1]) + net = tf_util.conv2d_transpose(net, 512, kernel_size=[2,2], stride=[2,2], padding='VALID', scope='upconv1', bn=True, bn_decay=bn_decay, is_training=is_training) + net = tf_util.conv2d_transpose(net, 256, kernel_size=[3,3], stride=[1,1], padding='VALID', scope='upconv2', bn=True, bn_decay=bn_decay, is_training=is_training) + net = tf_util.conv2d_transpose(net, 256, kernel_size=[4,5], stride=[2,3], padding='VALID', scope='upconv3', bn=True, bn_decay=bn_decay, is_training=is_training) + net = tf_util.conv2d_transpose(net, 128, kernel_size=[5,7], stride=[3,3], padding='VALID', scope='upconv4', bn=True, bn_decay=bn_decay, is_training=is_training) + net = tf_util.conv2d_transpose(net, 3, kernel_size=[1,1], stride=[1,1], padding='VALID', scope='upconv5', activation_fn=None) + end_points['xyzmap'] = net + net = tf.reshape(net, [batch_size, -1, 3]) + + return net, end_points + +def get_loss(pred, label, end_points): + """ pred: BxNx3, + label: BxNx3, """ + # Reconstruction loss + dists_forward,_,dists_backward,_ = tf_nndistance.nn_distance(pred, label) + loss = tf.reduce_mean(dists_forward+dists_backward) + end_points['pcloss'] = loss + # KL Divergence loss + kl_div_loss = 1 + end_points['z_std'] - tf.square(end_points['z_mean']) - tf.exp(end_points['z_std']) + kl_div_loss = -0.5 * tf.reduce_sum(kl_div_loss, 1) + kl_div_loss = tf.reduce_mean(kl_div_loss) + end_points['kl_div_loss'] = kl_div_loss + return loss*100 + kl_div_loss, end_points + + +if __name__=='__main__': + with tf.Graph().as_default(): + inputs = tf.zeros((32,2048,3)) + outputs = get_model(inputs, tf.constant(True)) + print(outputs) + loss = get_loss(outputs[0], tf.zeros((32,2048,3)), outputs[1]) From db64ca0f63d4377e1f7afbd5db5960f50bfbe74e Mon Sep 17 00:00:00 2001 From: godspeed1989 Date: Sun, 20 May 2018 11:54:33 +0000 Subject: [PATCH 4/7] update pc aug --- models/model_upconv_v.py | 2 +- train.py | 7 ++- utils/pc_util.py | 103 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 utils/pc_util.py diff --git a/models/model_upconv_v.py b/models/model_upconv_v.py index 23be555..21bd48e 100644 --- a/models/model_upconv_v.py +++ b/models/model_upconv_v.py @@ -111,7 +111,7 @@ def get_loss(pred, label, end_points): # KL Divergence loss kl_div_loss = 1 + end_points['z_std'] - tf.square(end_points['z_mean']) - tf.exp(end_points['z_std']) kl_div_loss = -0.5 * tf.reduce_sum(kl_div_loss, 1) - kl_div_loss = tf.reduce_mean(kl_div_loss) + kl_div_loss = tf.reduce_mean(kl_div_loss) * 0.001 end_points['kl_div_loss'] = kl_div_loss return loss*100 + kl_div_loss, end_points diff --git a/train.py b/train.py index 87bc479..9c667d2 100644 --- a/train.py +++ b/train.py @@ -14,6 +14,7 @@ sys.path.append(os.path.join(BASE_DIR, 'utils')) import part_dataset import show3d_balls +import pc_util parser = argparse.ArgumentParser() parser.add_argument('--gpu', type=int, default=0, help='GPU to use [default: GPU 0]') @@ -200,7 +201,11 @@ def train_one_epoch(sess, ops, train_writer): if FLAGS.no_rotation: aug_data = batch_data else: - aug_data = part_dataset.rotate_point_cloud(batch_data) + #aug_data = part_dataset.rotate_point_cloud(batch_data) + # Augment batched point clouds by rotation and jittering + rotated_data = pc_util.rotate_point_cloud(batch_data) + jittered_data = pc_util.jitter_point_cloud(rotated_data) + aug_data = pc_util.shuffle_point_cloud(jittered_data) feed_dict = {ops['pointclouds_pl']: aug_data, ops['labels_pl']: aug_data, ops['is_training_pl']: is_training,} diff --git a/utils/pc_util.py b/utils/pc_util.py new file mode 100644 index 0000000..f768908 --- /dev/null +++ b/utils/pc_util.py @@ -0,0 +1,103 @@ +""" + point cloud utilities +""" +import numpy as np +import random + +def rand_rotation_matrix(deflection=1.0, seed=None): + '''Creates a random rotation matrix (3x3). + + deflection: the magnitude of the rotation. For 0, no rotation; for 1, completely random + rotation. Small deflection => small perturbation. + + DOI: http://www.realtimerendering.com/resources/GraphicsGems/gemsiii/rand_rotation.c + http://blog.lostinmyterminal.com/python/2015/05/12/random-rotation-matrix.html + ''' + if seed is not None: + np.random.seed(seed) + + randnums = np.random.uniform(size=(3,)) + + theta, phi, z = randnums + + theta = theta * 2.0 * deflection * np.pi # Rotation about the pole (Z). + phi = phi * 2.0 * np.pi # For direction of pole deflection. + z = z * 2.0 * deflection # For magnitude of pole deflection. + + # Compute a vector V used for distributing points over the sphere + # via the reflection I - V Transpose(V). This formulation of V + # will guarantee that if x[1] and x[2] are uniformly distributed, + # the reflected points will be uniform on the sphere. Note that V + # has length sqrt(2) to eliminate the 2 in the Householder matrix. + + r = np.sqrt(z) + V = ( + np.sin(phi) * r, + np.cos(phi) * r, + np.sqrt(2.0 - z)) + + st = np.sin(theta) + ct = np.cos(theta) + + R = np.array(((ct, st, 0), (-st, ct, 0), (0, 0, 1))) + + # Construct the rotation matrix ( V Transpose(V) - I ) R. + M = (np.outer(V, V) - np.eye(3)).dot(R) + return M + +def rotate_point_cloud(batch_data): + """ Randomly rotate the point clouds to augument the dataset + rotation is per shape based along up direction + Input: + BxNx3 array, original batch of point clouds + Return: + BxNx3 array, rotated batch of point clouds + """ + rotated_data = np.zeros(batch_data.shape, dtype=batch_data.dtype) + for k in range(batch_data.shape[0]): + rotation_matrix = rand_rotation_matrix(deflection=random.random()) + #shape_pc = batch_data[k, ...] + #rotated_data[k, ...] = np.dot(shape_pc.reshape((-1, 3)), rotation_matrix) + rotated_data[k, ...] = np.dot(batch_data[k, ...], rotation_matrix) + return rotated_data + +def jitter_point_cloud(batch_data, sigma=0.01, clip=0.05): + """ Randomly jitter points. jittering is per point. + Input: + BxNx3 array, original batch of point clouds + Return: + BxNx3 array, jittered batch of point clouds + """ + B, N, C = batch_data.shape + assert clip > 0 + jittered_data = np.clip(sigma * np.random.randn(B, N, C), -1*clip, clip) + jittered_data += batch_data + return jittered_data + +def shuffle_point_cloud(batch_data): + """ Randomly shuffle points per sample. + Input: + BxNx3 array, original batch of point clouds + Return: + BxNx3 array, shuffled batch of point clouds + """ + shuffled_data = np.zeros(batch_data.shape, dtype=batch_data.dtype) + num_point = batch_data.shape[1] + for k in range(batch_data.shape[0]): + permutation = np.random.permutation(num_point) + shuffled_data[k, range(num_point), ...] = batch_data[k, permutation, ...] + return shuffled_data + +if __name__ == '__main__': + batch_data = np.array([[[ 1, 1, 1], + [-1,-1,-1], + [ 1,-1, 1], + [ 1, 1,-1]]], dtype=float) + print(batch_data.shape) # (1, 4, 3) + print('batch_data', batch_data) + shuffled_data = shuffle_point_cloud(batch_data) + print('shuffled_data', shuffled_data) + jittered_data = jitter_point_cloud(batch_data) + print('jittered_data', jittered_data) + rotated_data = rotate_point_cloud(batch_data) + print('rotated_data', rotated_data) From 18fda5fe2f9f386498096e464714c5da6fea18c8 Mon Sep 17 00:00:00 2001 From: godspeed1989 Date: Sun, 20 May 2018 17:07:54 +0000 Subject: [PATCH 5/7] update vae --- models/model_upconv_v.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/models/model_upconv_v.py b/models/model_upconv_v.py index 21bd48e..fd4463b 100644 --- a/models/model_upconv_v.py +++ b/models/model_upconv_v.py @@ -85,8 +85,10 @@ def glorot_init(shape): end_points['z_mean'] = z_mean end_points['z_std'] = z_std # Sampler: Normal (gaussian) random distribution - eps = tf.random_normal(tf.shape(z_std), dtype=tf.float32, mean=0., stddev=1.0, name='epsilon') - z = z_mean + tf.exp(z_std / 2) * eps + samples = tf.random_normal([batch_size, latent_dim], dtype=tf.float32, mean=0., stddev=1.0, name='epsilon') + # z = µ + σ * N (0, 1) + z = z_mean + z_std * samples + #z = z_mean + tf.exp(z_std / 2) * samples # UPCONV Decoder # (N,1024) -> (N,1,2,512) From 9e867ceb1a86ca10edabe74630edb156ddc749c3 Mon Sep 17 00:00:00 2001 From: godspeed1989 Date: Mon, 30 Jul 2018 22:41:25 +0000 Subject: [PATCH 6/7] update tf_util --- utils/tf_util.py | 148 ++++++++++++++++------------------------------- 1 file changed, 49 insertions(+), 99 deletions(-) diff --git a/utils/tf_util.py b/utils/tf_util.py index 6c32a19..90cec4b 100644 --- a/utils/tf_util.py +++ b/utils/tf_util.py @@ -7,7 +7,7 @@ import numpy as np import tensorflow as tf -def _variable_on_cpu(name, shape, initializer, use_fp16=False): +def _variable_on_cpu(name, shape, initializer, use_fp16=False, trainable=True): """Helper to create a Variable stored on CPU memory. Args: name: name of the variable @@ -18,10 +18,10 @@ def _variable_on_cpu(name, shape, initializer, use_fp16=False): """ with tf.device("/cpu:0"): dtype = tf.float16 if use_fp16 else tf.float32 - var = tf.get_variable(name, shape, initializer=initializer, dtype=dtype) + var = tf.get_variable(name, shape, initializer=initializer, dtype=dtype, trainable=trainable) return var -def _variable_with_weight_decay(name, shape, stddev, wd, use_xavier=True): +def _variable_with_weight_decay(name, shape, stddev, wd, use_xavier=True, trainable=True): """Helper to create an initialized Variable with weight decay. Note that the Variable is initialized with a truncated normal distribution. @@ -42,13 +42,12 @@ def _variable_with_weight_decay(name, shape, stddev, wd, use_xavier=True): initializer = tf.contrib.layers.xavier_initializer() else: initializer = tf.truncated_normal_initializer(stddev=stddev) - var = _variable_on_cpu(name, shape, initializer) + var = _variable_on_cpu(name, shape, initializer, trainable=trainable) if wd is not None: weight_decay = tf.multiply(tf.nn.l2_loss(var), wd, name='weight_loss') tf.add_to_collection('losses', weight_decay) return var - def conv1d(inputs, num_output_channels, kernel_size, @@ -62,6 +61,7 @@ def conv1d(inputs, activation_fn=tf.nn.relu, bn=False, bn_decay=None, + trainable=True, is_training=None): """ 1D convolution with non-linear operation. @@ -96,27 +96,24 @@ def conv1d(inputs, shape=kernel_shape, use_xavier=use_xavier, stddev=stddev, - wd=weight_decay) + wd=weight_decay, + trainable=trainable) outputs = tf.nn.conv1d(inputs, kernel, stride=stride, - padding=padding, - data_format=data_format) + padding=padding) biases = _variable_on_cpu('biases', [num_output_channels], - tf.constant_initializer(0.0)) - outputs = tf.nn.bias_add(outputs, biases, data_format=data_format) + tf.constant_initializer(0.0), trainable=trainable) + outputs = tf.nn.bias_add(outputs, biases) if bn: - outputs = batch_norm_for_conv1d(outputs, is_training, - bn_decay=bn_decay, scope='bn', - data_format=data_format) + outputs = batch_norm_for_conv1d(outputs, is_training, trainable=trainable, + bn_decay=bn_decay, scope='bn', data_format=data_format) if activation_fn is not None: outputs = activation_fn(outputs) return outputs - - def conv2d(inputs, num_output_channels, kernel_size, @@ -126,10 +123,12 @@ def conv2d(inputs, data_format='NHWC', use_xavier=True, stddev=1e-3, - weight_decay=None, + weight_decay=0.0, activation_fn=tf.nn.relu, + bias=True, bn=False, bn_decay=None, + trainable=True, is_training=None): """ 2D convolution with non-linear operation. @@ -165,18 +164,20 @@ def conv2d(inputs, shape=kernel_shape, use_xavier=use_xavier, stddev=stddev, - wd=weight_decay) + wd=weight_decay, + trainable=trainable) stride_h, stride_w = stride outputs = tf.nn.conv2d(inputs, kernel, [1, stride_h, stride_w, 1], padding=padding, data_format=data_format) - biases = _variable_on_cpu('biases', [num_output_channels], - tf.constant_initializer(0.0)) - outputs = tf.nn.bias_add(outputs, biases, data_format=data_format) + if bias: + biases = _variable_on_cpu('biases', [num_output_channels], + tf.constant_initializer(0.0), trainable=trainable) + outputs = tf.nn.bias_add(outputs, biases, data_format=data_format) if bn: - outputs = batch_norm_for_conv2d(outputs, is_training, + outputs = batch_norm_for_conv2d(outputs, is_training, trainable=trainable, bn_decay=bn_decay, scope='bn', data_format=data_format) @@ -184,7 +185,6 @@ def conv2d(inputs, outputs = activation_fn(outputs) return outputs - def conv2d_transpose(inputs, num_output_channels, kernel_size, @@ -198,6 +198,7 @@ def conv2d_transpose(inputs, activation_fn=tf.nn.relu, bn=False, bn_decay=None, + trainable=True, is_training=None): """ 2D convolution transpose with non-linear operation. @@ -230,7 +231,8 @@ def conv2d_transpose(inputs, shape=kernel_shape, use_xavier=use_xavier, stddev=stddev, - wd=weight_decay) + wd=weight_decay, + trainable=trainable) stride_h, stride_w = stride # from slim.convolution2d_transpose @@ -253,11 +255,11 @@ def get_deconv_dim(dim_size, stride_size, kernel_size, padding): [1, stride_h, stride_w, 1], padding=padding) biases = _variable_on_cpu('biases', [num_output_channels], - tf.constant_initializer(0.0)) + tf.constant_initializer(0.0), trainable=trainable) outputs = tf.nn.bias_add(outputs, biases) if bn: - outputs = batch_norm_for_conv2d(outputs, is_training, + outputs = batch_norm_for_conv2d(outputs, is_training, trainable=trainable, bn_decay=bn_decay, scope='bn', data_format=data_format) @@ -265,8 +267,6 @@ def get_deconv_dim(dim_size, stride_size, kernel_size, padding): outputs = activation_fn(outputs) return outputs - - def conv3d(inputs, num_output_channels, kernel_size, @@ -279,6 +279,7 @@ def conv3d(inputs, activation_fn=tf.nn.relu, bn=False, bn_decay=None, + trainable=True, is_training=None): """ 3D convolution with non-linear operation. @@ -309,17 +310,18 @@ def conv3d(inputs, shape=kernel_shape, use_xavier=use_xavier, stddev=stddev, - wd=weight_decay) + wd=weight_decay, + trainable=trainable) stride_d, stride_h, stride_w = stride outputs = tf.nn.conv3d(inputs, kernel, [1, stride_d, stride_h, stride_w, 1], padding=padding) biases = _variable_on_cpu('biases', [num_output_channels], - tf.constant_initializer(0.0)) + tf.constant_initializer(0.0), trainable=trainable) outputs = tf.nn.bias_add(outputs, biases) if bn: - outputs = batch_norm_for_conv3d(outputs, is_training, + outputs = batch_norm_for_conv3d(outputs, is_training, trainable=trainable, bn_decay=bn_decay, scope='bn') if activation_fn is not None: @@ -335,6 +337,7 @@ def fully_connected(inputs, activation_fn=tf.nn.relu, bn=False, bn_decay=None, + trainable=True, is_training=None): """ Fully connected layer with non-linear operation. @@ -351,20 +354,20 @@ def fully_connected(inputs, shape=[num_input_units, num_outputs], use_xavier=use_xavier, stddev=stddev, - wd=weight_decay) + wd=weight_decay, + trainable=trainable) outputs = tf.matmul(inputs, weights) biases = _variable_on_cpu('biases', [num_outputs], - tf.constant_initializer(0.0)) + tf.constant_initializer(0.0), trainable=trainable) outputs = tf.nn.bias_add(outputs, biases) if bn: - outputs = batch_norm_for_fc(outputs, is_training, bn_decay, 'bn') + outputs = batch_norm_for_fc(outputs, is_training, bn_decay, trainable, 'bn') if activation_fn is not None: outputs = activation_fn(outputs) return outputs - def max_pool2d(inputs, kernel_size, scope, @@ -415,7 +418,6 @@ def avg_pool2d(inputs, name=sc.name) return outputs - def max_pool3d(inputs, kernel_size, scope, @@ -466,55 +468,10 @@ def avg_pool3d(inputs, name=sc.name) return outputs - -def batch_norm_template_unused(inputs, is_training, scope, moments_dims, bn_decay): - """ NOTE: this is older version of the util func. it is deprecated. - Batch normalization on convolutional maps and beyond... - Ref.: http://stackoverflow.com/questions/33949786/how-could-i-use-batch-normalization-in-tensorflow - - Args: - inputs: Tensor, k-D input ... x C could be BC or BHWC or BDHWC - is_training: boolean tf.Varialbe, true indicates training phase - scope: string, variable scope - moments_dims: a list of ints, indicating dimensions for moments calculation - bn_decay: float or float tensor variable, controling moving average weight - Return: - normed: batch-normalized maps - """ - with tf.variable_scope(scope) as sc: - num_channels = inputs.get_shape()[-1].value - beta = _variable_on_cpu(name='beta',shape=[num_channels], - initializer=tf.constant_initializer(0)) - gamma = _variable_on_cpu(name='gamma',shape=[num_channels], - initializer=tf.constant_initializer(1.0)) - batch_mean, batch_var = tf.nn.moments(inputs, moments_dims, name='moments') - decay = bn_decay if bn_decay is not None else 0.9 - ema = tf.train.ExponentialMovingAverage(decay=decay) - # Operator that maintains moving averages of variables. - # Need to set reuse=False, otherwise if reuse, will see moments_1/mean/ExponentialMovingAverage/ does not exist - # https://github.com/shekkizh/WassersteinGAN.tensorflow/issues/3 - with tf.variable_scope(tf.get_variable_scope(), reuse=False): - ema_apply_op = tf.cond(is_training, - lambda: ema.apply([batch_mean, batch_var]), - lambda: tf.no_op()) - - # Update moving average and return current batch's avg and var. - def mean_var_with_update(): - with tf.control_dependencies([ema_apply_op]): - return tf.identity(batch_mean), tf.identity(batch_var) - - # ema.average returns the Variable holding the average of var. - mean, var = tf.cond(is_training, - mean_var_with_update, - lambda: (ema.average(batch_mean), ema.average(batch_var))) - normed = tf.nn.batch_normalization(inputs, mean, var, beta, gamma, 1e-3) - return normed - - -def batch_norm_template(inputs, is_training, scope, moments_dims_unused, bn_decay, data_format='NHWC'): +def batch_norm_template(inputs, is_training, scope, moments_dims_unused, bn_decay, trainable=True, data_format='NHWC'): """ Batch normalization on convolutional maps and beyond... Ref.: http://stackoverflow.com/questions/33949786/how-could-i-use-batch-normalization-in-tensorflow - + Args: inputs: Tensor, k-D input ... x C could be BC or BHWC or BDHWC is_training: boolean tf.Varialbe, true indicates training phase @@ -526,14 +483,13 @@ def batch_norm_template(inputs, is_training, scope, moments_dims_unused, bn_deca normed: batch-normalized maps """ bn_decay = bn_decay if bn_decay is not None else 0.9 - return tf.contrib.layers.batch_norm(inputs, + return tf.contrib.layers.batch_norm(inputs, center=True, scale=True, is_training=is_training, decay=bn_decay,updates_collections=None, - scope=scope, + scope=scope, trainable=trainable, data_format=data_format) - -def batch_norm_for_fc(inputs, is_training, bn_decay, scope): +def batch_norm_for_fc(inputs, is_training, bn_decay, trainable, scope): """ Batch normalization on FC data. Args: @@ -544,10 +500,9 @@ def batch_norm_for_fc(inputs, is_training, bn_decay, scope): Return: normed: batch-normalized maps """ - return batch_norm_template(inputs, is_training, scope, [0,], bn_decay) + return batch_norm_template(inputs, is_training, scope, [0,], bn_decay, trainable=trainable) - -def batch_norm_for_conv1d(inputs, is_training, bn_decay, scope, data_format): +def batch_norm_for_conv1d(inputs, is_training, bn_decay, trainable, scope, data_format): """ Batch normalization on 1D convolutional maps. Args: @@ -559,12 +514,9 @@ def batch_norm_for_conv1d(inputs, is_training, bn_decay, scope, data_format): Return: normed: batch-normalized maps """ - return batch_norm_template(inputs, is_training, scope, [0,1], bn_decay, data_format) - + return batch_norm_template(inputs, is_training, scope, [0,1], bn_decay, trainable=trainable, data_format=data_format) - - -def batch_norm_for_conv2d(inputs, is_training, bn_decay, scope, data_format): +def batch_norm_for_conv2d(inputs, is_training, bn_decay, trainable, scope, data_format): """ Batch normalization on 2D convolutional maps. Args: @@ -576,10 +528,9 @@ def batch_norm_for_conv2d(inputs, is_training, bn_decay, scope, data_format): Return: normed: batch-normalized maps """ - return batch_norm_template(inputs, is_training, scope, [0,1,2], bn_decay, data_format) + return batch_norm_template(inputs, is_training, scope, [0,1,2], bn_decay, trainable=trainable, data_format=data_format) - -def batch_norm_for_conv3d(inputs, is_training, bn_decay, scope): +def batch_norm_for_conv3d(inputs, is_training, bn_decay, trainable, scope): """ Batch normalization on 3D convolutional maps. Args: @@ -590,8 +541,7 @@ def batch_norm_for_conv3d(inputs, is_training, bn_decay, scope): Return: normed: batch-normalized maps """ - return batch_norm_template(inputs, is_training, scope, [0,1,2,3], bn_decay) - + return batch_norm_template(inputs, is_training, scope, [0,1,2,3], bn_decay, trainable=trainable) def dropout(inputs, is_training, From 5e1ec0fcd1fdf54ae1c3d32a8e40b194abc71928 Mon Sep 17 00:00:00 2001 From: godspeed1989 Date: Mon, 30 Jul 2018 22:57:31 +0000 Subject: [PATCH 7/7] update tf_util --- utils/tf_util.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/tf_util.py b/utils/tf_util.py index 90cec4b..b002928 100644 --- a/utils/tf_util.py +++ b/utils/tf_util.py @@ -103,7 +103,7 @@ def conv1d(inputs, padding=padding) biases = _variable_on_cpu('biases', [num_output_channels], tf.constant_initializer(0.0), trainable=trainable) - outputs = tf.nn.bias_add(outputs, biases) + outputs = tf.nn.bias_add(outputs, biases, data_format=data_format) if bn: outputs = batch_norm_for_conv1d(outputs, is_training, trainable=trainable, @@ -123,7 +123,7 @@ def conv2d(inputs, data_format='NHWC', use_xavier=True, stddev=1e-3, - weight_decay=0.0, + weight_decay=None, activation_fn=tf.nn.relu, bias=True, bn=False, @@ -360,7 +360,7 @@ def fully_connected(inputs, biases = _variable_on_cpu('biases', [num_outputs], tf.constant_initializer(0.0), trainable=trainable) outputs = tf.nn.bias_add(outputs, biases) - + if bn: outputs = batch_norm_for_fc(outputs, is_training, bn_decay, trainable, 'bn')