From 8732c289f2b3023d1a6185b0f761116c05f70499 Mon Sep 17 00:00:00 2001 From: tgupta6 <tgupta6@illinois.edu> Date: Wed, 23 Mar 2016 09:56:57 -0500 Subject: [PATCH] glorot initialization, fixed bug in region score, step-wise training --- .../answer_classifier/ans_data_io_helper.py | 98 ++++++--- .../answer_classifier/ans_data_io_helper.pyc | Bin 5192 -> 5894 bytes .../answer_classifier/train_ans_classifier.py | 197 +++++++++++------- classifiers/region_ranker/perfect_ranker.py | 35 +++- classifiers/region_ranker/perfect_ranker.pyc | Bin 2750 -> 3307 bytes classifiers/tf_graph_creation_helper.py | 56 +++-- classifiers/tf_graph_creation_helper.pyc | Bin 7719 -> 8335 bytes 7 files changed, 257 insertions(+), 129 deletions(-) diff --git a/classifiers/answer_classifier/ans_data_io_helper.py b/classifiers/answer_classifier/ans_data_io_helper.py index 5898855..5c55cde 100644 --- a/classifiers/answer_classifier/ans_data_io_helper.py +++ b/classifiers/answer_classifier/ans_data_io_helper.py @@ -1,6 +1,7 @@ import json import sys import os +import time import matplotlib.pyplot as plt import matplotlib.image as mpimg import numpy as np @@ -13,7 +14,7 @@ import region_ranker.perfect_ranker as region_proposer qa_tuple = namedtuple('qa_tuple','image_id question answer') num_proposals = 22 -region_coords = region_proposer.get_region_coords() +region_coords, region_coords_ = region_proposer.get_region_coords(75, 75) def create_ans_dict(): @@ -65,7 +66,40 @@ def get_vocab(qa_dict): inv_vocab = {v: k for k, v in vocab.items()} return vocab, inv_vocab + + +def save_regions(image_dir, out_dir, qa_dict, region_anno_dict, start_id, + batch_size, img_width, img_height): + + print('Saving Regions ...') + if not os.path.exists(out_dir): + os.mkdir(out_dir) + + region_shape = np.array([img_height/3, img_width/3], np.int32) + + image_done = np.zeros(shape=[batch_size], dtype=bool) + for i in xrange(start_id, start_id + batch_size): + i + image_id = qa_dict[i].image_id + if image_done[image_id-1]==False: + gt_regions_for_image = region_anno_dict[image_id] + image = mpimg.imread(os.path.join(image_dir, + str(image_id) + '.jpg')) + resized_image = misc.imresize(image, (img_height, img_width)) + regions = region_proposer.rank_regions(resized_image, None, + region_coords, + region_coords_, + gt_regions_for_image) + for j in xrange(num_proposals): + filename = os.path.join(out_dir, '{}_{}.png'.format(image_id,j)) + resized_region = misc.imresize(regions[j].image, + (region_shape[0], + region_shape[1])) + misc.imsave(filename,resized_region) + image_done[image_id-1] = True + + def ans_mini_batch_loader(qa_dict, region_anno_dict, ans_dict, vocab, image_dir, mean_image, start_index, batch_size, img_height=100, img_width=100, channels = 3): @@ -74,52 +108,56 @@ def ans_mini_batch_loader(qa_dict, region_anno_dict, ans_dict, vocab, for i in xrange(start_index, start_index + batch_size): answer = qa_dict[i].answer ans_labels[i-start_index, ans_dict[answer]] = 1 - + # number of regions in the batch count = batch_size*num_proposals; - - region_images = np.zeros(shape=[count, img_height, - img_width, channels]) + region_shape = np.array([img_height/3, img_width/3], np.int32) + region_images = np.zeros(shape=[count, region_shape[0], + region_shape[1], channels]) region_score = np.zeros(shape=[1,count]) partition = np.zeros(shape=[count]) question_encodings = np.zeros(shape=[count, len(vocab)]) for i in xrange(start_index, start_index + batch_size): + image_id = qa_dict[i].image_id question = qa_dict[i].question answer = qa_dict[i].answer gt_regions_for_image = region_anno_dict[image_id] - image = mpimg.imread(os.path.join(image_dir, - str(image_id) + '.jpg')) - regions = region_proposer.rank_regions(image, question, region_coords, - gt_regions_for_image) + start1 = time.time() + regions = region_proposer.rank_regions(None, question, + region_coords, region_coords_, + gt_regions_for_image, + False) + + end1 = time.time() +# print('Ranking Region: ' + str(end1-start1)) + + for word in question[0:-1].split(): + if word not in vocab: + word = 'unk' + question_encodings[0, vocab[word]] += 1 + for j in xrange(num_proposals): counter = j + (i-start_index)*num_proposals - resized_region = misc.imresize(regions[j].image, \ - (img_height, img_width)) + proposal = regions[j] + + start2 = time.time() + resized_region = mpimg.imread(os.path.join(image_dir, + '{}_{}.png' + .format(image_id,j))) + end2 = time.time() +# print('Reading Region: ' + str(end2-start2)) region_images[counter,:,:,:] = (resized_region / 254.0) \ - mean_image - region_score[0,counter] = regions[j].score + region_score[0,counter] = proposal.score partition[counter] = i-start_index + + question_encodings[counter,:] = question_encodings[0,:] - for word in question[0:-1].split(): - if word not in vocab: - word = 'unk' - question_encodings[counter, vocab[word]] += 1 - - # Check for nans, infs - assert (not np.any(np.isnan(region_images))), "NaN in region_images" - assert (not np.any(np.isnan(ans_labels))), "NaN in labels" - assert (not np.any(np.isnan(question_encodings))), "NaN in question_encodings" - assert (not np.any(np.isnan(region_score))), "NaN in region_score" - assert (not np.any(np.isnan(partition))), "NaN in partition" - - assert (not np.any(np.isinf(region_images))), "Inf in region_images" - assert (not np.any(np.isinf(ans_labels))), "Inf in labels" - assert (not np.any(np.isinf(question_encodings))), "Inf in question_encodings" - assert (not np.any(np.isinf(region_score))), "Inf in region_score" - assert (not np.any(np.isinf(partition))), "Inf in partition" - + score_start_id = (i-start_index)*num_proposals + region_score[0, score_start_id:score_start_id+num_proposals] /= \ + np.sum(region_score[0,score_start_id:score_start_id+num_proposals]) return region_images, ans_labels, question_encodings, region_score, partition diff --git a/classifiers/answer_classifier/ans_data_io_helper.pyc b/classifiers/answer_classifier/ans_data_io_helper.pyc index 6eda2294ef5263df1f1e80c298cf9d93111a0b99..9ed62f9ffe32ff02671645bc90a02302313167b1 100644 GIT binary patch literal 5894 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 F^Do(_ve5tl delta 2379 zcmZuxOK%)S5dM00z4pG}FFQ7|6F(9s0V#k51eBLa0?43ALZl(lYO^z5&tyHjo*9D^ z&1xkaaYu7N;(){%0a9`SDI$mi;>-bw8(aVhAtB`lKvjF}4JNxi)zwvBS5;S4_Xj6_ zSeS@BN+<q$=kK>$2!AP>Pt)h#`*7l!ffa=|0v*r=Ix$$I(8hL`acGHMJ_#))c?F#` z)-%vD5WFpdP8RDqXt|v=naRT{Kr6z?L61To!L|ae1U&|=45J9Ef^9&nLLVLAE^#&J zW0I*uk4t78I=QBigr36P2($?p2cV~MOM&x+&@<4psA`j7E=Syy<nqKl1tS6LAoK$C zBC6X0+BA$hj7cy*BSSa@V~UmoXoq3cIG*bi<Seu~7~?Qz7|N1)DIw>yjgYf2=3$VB zY3Ot!8f7_>*iPd52($%gN1z>rwg~MQj3cm)<HAMbXgDu~^CAn6;X>k~EmMDap%Oe2 z+I>~|UWl2X9{ofV?|&8jOo;jWdh8n^&IE_Xz7r>d-^OMpP0BmW2$5Ej$N~D|^qr&6 z{W16{?Yt_uoPGxW82mW=1lmzLqUdMg0hZM23Ip1Tl;?(W778iM2VZ9<2L)`sE4&E& zm>i>&PzC32G(|rJAMjJCTQT&Ddy7%@OM8nb`f2zSOA+;c8QvJII6Nv(5*F=`!XtH& zldVUjW#al3co|q(_!am!OYm~=%h)DURc!qZip8Ur5){PG=-1%WAX72aNL;G+>+oa) z`c+odEs9KLVU>=mpg#_unk$O3KZ2T1PU^vYZh8=cDROGs5t4%Nr$WPio&~42UV~2+ zr|S~Kv_&C-Vt5Yy3HUT3xHSrAUePH56QHvC1>Qw+($>jZOF^Boz7#pNky5U0bi}od zKX8o;Qo(>HiXqDfqmgnQ#go^97VisL9!kp&h?*?~eI!9~UhfAL_COCLDDfmvZWlCg zf0Za{dcUdsF3OEXIH=VBOwloikuOS6`bnT+<Znq(W*0P&S>W@LsbT1w5+zOlCny5# zh3<YVL2|txG>rVA1j#jlxb8_=AnL6=R!0W7yilO~vqC*Yw`i@|Gfcr;l*kFI*KX4c zeA_*3@@)P?KlLziZi&cBbLF6EA9Rei*|i(ydb4e~ZhENbG;bOm*R2mF8=HpfG1#zM zUEQ<?ZPzUh)uAP~)pZOvH<b69j%V^d>GqPn`ee7E;xV_OWWU?c>m_^exTT@naNC~S zg>j0-Q;66-nW;O5({<@a0v*HlVl;0#O}lNVH3o7D?9KH?&*}EMZnNX66_Th`)^VQK zd**su?mBF)J4RENrpX{@z1Q^CBx7|=Tg{Rr_&hl`V4GojTHUUryDACzVWFy!4cEM5 zNZYasJJsYYs<VvZK%4gM9e`^$ZF*>cGp=h;Uc2_P<8+-+Y1*p`{IaUkypd}ly#{6~ z*;4bYm}dn)ywX)eHywJ|=uk#8>;aEoXV;^eHB4JKwxs%I(`&6YWSESm1T@wRv%Thp zrrM47Ox;@x@`c6VOhLWkaf7wJ#?G1D#%kBu=`i(WQh1|8LY2o2Ye`9~yJ>rLCO+Lx zPV~@#x=5nn)3K#ssaU_UObho45k#lNxJZiwk%W>JSy2;ZF{QAC{|QkL6_Un9k+>Xj zqvD9DiVPuAB%O=Qitw)tx!}v<pT!eoTb&>xbMM}E)#rnIrL$!YUVWD3+z8Ypz1!;3 zLA>m|_yTLV#nh9m=2W+h-SaoXn!m()&Ww79$#EuEgNNm_$@9$0!Yv1>%Hq*;yk;XZ sJq=kk^$ixj&g4xduP|8&nw6{3i^K*GDz!u^5--J5@ls(a_@y%UFXv6^ivR!s diff --git a/classifiers/answer_classifier/train_ans_classifier.py b/classifiers/answer_classifier/train_ans_classifier.py index 6d27300..1bf795b 100644 --- a/classifiers/answer_classifier/train_ans_classifier.py +++ b/classifiers/answer_classifier/train_ans_classifier.py @@ -4,6 +4,7 @@ import json import matplotlib.pyplot as plt import matplotlib.image as mpimg import numpy as np +import math import tensorflow as tf import object_classifiers.obj_data_io_helper as obj_data_loader import attribute_classifiers.atr_data_io_helper as atr_data_loader @@ -11,25 +12,28 @@ import tf_graph_creation_helper as graph_creator import plot_helper as plotter import ans_data_io_helper as ans_io_helper import region_ranker.perfect_ranker as region_proposer - +import time val_start_id = 106115 -val_batch_size = 1000 - -batch_size = 10 - +val_batch_size = 5000 +val_batch_size_small = 100 +batch_size = 20 +crop_n_save_regions = False +restore_intermediate_model = True def evaluate(accuracy, qa_anno_dict, region_anno_dict, ans_vocab, vocab, - image_dir, mean_image, start_index, batch_size, + image_dir, mean_image, start_index, val_batch_size, placeholders, img_height=100, img_width=100): correct = 0 - for i in xrange(start_index, start_index + batch_size): + max_iter = int(math.floor(val_batch_size/batch_size)) + for i in xrange(max_iter): region_images, ans_labels, questions, \ region_score, partition= \ ans_io_helper.ans_mini_batch_loader(qa_anno_dict, region_anno_dict, ans_vocab, vocab, image_dir, mean_image, - i, 1, + start_index+i*batch_size, + batch_size, img_height, img_width, 3) feed_dict = { @@ -42,7 +46,7 @@ def evaluate(accuracy, qa_anno_dict, region_anno_dict, ans_vocab, vocab, correct = correct + accuracy.eval(feed_dict) - return correct/batch_size + return correct/max_iter def train(train_params): @@ -51,12 +55,20 @@ def train(train_params): train_anno_filename = '/home/tanmay/Code/GenVQA/GenVQA/' + \ 'shapes_dataset/train_anno.json' + test_anno_filename = '/home/tanmay/Code/GenVQA/GenVQA/' + \ + 'shapes_dataset/test_anno.json' + regions_anno_filename = '/home/tanmay/Code/GenVQA/GenVQA/' + \ 'shapes_dataset/regions_anno.json' image_dir = '/home/tanmay/Code/GenVQA/GenVQA/' + \ 'shapes_dataset/images' + # image_regions_dir = '/home/tanmay/Code/GenVQA/Exp_Results/' + \ + # 'image_regions' + + image_regions_dir = '/mnt/ramdisk/image_regions' + outdir = '/home/tanmay/Code/GenVQA/Exp_Results/Ans_Classifier' if not os.path.exists(outdir): os.mkdir(outdir) @@ -66,28 +78,39 @@ def train(train_params): ans_vocab, inv_ans_vocab = ans_io_helper.create_ans_dict() vocab, inv_vocab = ans_io_helper.get_vocab(qa_anno_dict) + # Save region crops + if crop_n_save_regions == True: + qa_anno_dict_test = ans_io_helper.parse_qa_anno(test_anno_filename) + ans_io_helper.save_regions(image_dir, image_regions_dir, + qa_anno_dict, region_anno_dict, + 1, 111351, 75, 75) + ans_io_helper.save_regions(image_dir, image_regions_dir, + qa_anno_dict_test, region_anno_dict, + 111352, 160725-111352+1, 75, 75) + + # Create graph image_regions, questions, keep_prob, y, region_score= \ graph_creator.placeholder_inputs_ans(len(vocab), len(ans_vocab), mode='gt') - y_pred_obj = graph_creator.obj_comp_graph(image_regions, keep_prob) + y_pred_obj = graph_creator.obj_comp_graph(image_regions, 1.0) obj_feat = tf.get_collection('obj_feat', scope='obj/conv2') - y_pred_atr = graph_creator.atr_comp_graph(image_regions, keep_prob, obj_feat[0]) + y_pred_atr = graph_creator.atr_comp_graph(image_regions, 1.0, obj_feat[0]) atr_feat = tf.get_collection('atr_feat', scope='atr/conv2') # model restoration obj_atr_saver = tf.train.Saver() model_to_restore = '/home/tanmay/Code/GenVQA/GenVQA/classifiers/' + \ 'saved_models/obj_atr_classifier-1' - obj_atr_saver.restore(sess, model_to_restore) +# obj_atr_saver.restore(sess, model_to_restore) - y_pred = graph_creator.ans_comp_graph(image_regions, questions, keep_prob, \ - obj_feat[0], atr_feat[0], - vocab, inv_vocab, len(ans_vocab)) + y_pred, logits = graph_creator.ans_comp_graph(image_regions, + questions, keep_prob, \ + obj_feat[0], atr_feat[0], + vocab, inv_vocab, len(ans_vocab)) y_avg = graph_creator.aggregate_y_pred(y_pred, region_score, batch_size, ans_io_helper.num_proposals, len(ans_vocab)) -# y_avg = tf.matmul(region_score,y_pred) cross_entropy = graph_creator.loss(y, y_avg) accuracy = graph_creator.evaluation(y, y_avg) @@ -97,21 +120,34 @@ def train(train_params): train_step = tf.train.AdamOptimizer(train_params['adam_lr']) \ .minimize(cross_entropy, var_list=vars_to_opt) - - vars_to_restore = [] - vars_to_restore.append(tf.get_collection(tf.GraphKeys.VARIABLES, - scope='obj')) - vars_to_restore.append(tf.get_collection(tf.GraphKeys.VARIABLES, - scope='atr')) + print(train_step.name) + vars_to_restore = tf.get_collection(tf.GraphKeys.VARIABLES,scope='obj') + \ + tf.get_collection(tf.GraphKeys.VARIABLES, scope='atr') + \ + tf.get_collection(tf.GraphKeys.VARIABLES, scope='ans/word_embed') all_vars = tf.get_collection(tf.GraphKeys.VARIABLES) - vars_to_init = [var for var in all_vars if var not in vars_to_restore] + vars_to_init = [var for var in all_vars if var not in vars_to_restore] + # Session saver - saver = tf.train.Saver() - - # Initializing all variables except those restored - print('Initializing variables') - sess.run(tf.initialize_variables(vars_to_init)) + + saver = tf.train.Saver(vars_to_restore) + + if restore_intermediate_model==True: + intermediate_model = '/home/tanmay/Code/GenVQA/Exp_Results/' + \ + 'Ans_Classifier/ans_classifier_question_only-9' + print('vars_to_restore: ') + print([var.name for var in vars_to_restore]) + print('vars_to_init: ') + print([var.name for var in vars_to_init]) + saver.restore(sess, intermediate_model) +# print('Initializing variables') + sess.run(tf.initialize_variables(vars_to_init)) + start_epoch = 0 + else: + # Initializing all variables except those restored + print('Initializing variables') + sess.run(tf.initialize_variables(vars_to_init)) + start_epoch = 0 # Load mean image mean_image = np.load('/home/tanmay/Code/GenVQA/Exp_Results/' + \ @@ -122,29 +158,21 @@ def train(train_params): # Start Training # batch_size = 1 max_epoch = 10 - max_iter = 9500 + max_iter = 5000 val_acc_array_epoch = np.zeros([max_epoch]) train_acc_array_epoch = np.zeros([max_epoch]) - for epoch in range(max_epoch): + for epoch in range(start_epoch, max_epoch): + start = time.time() for i in range(max_iter): - if i%100==0: - print('Iter: ' + str(i)) - - # val_accuracy = evaluate(accuracy, qa_anno_dict, - # region_anno_dict, ans_vocab, vocab, - # image_dir, mean_image, - # val_start_id, val_batch_size, - # placeholders, 25, 25) - # print(val_accuracy) - + train_region_images, train_ans_labels, train_questions, \ train_region_score, train_partition= \ ans_io_helper.ans_mini_batch_loader(qa_anno_dict, region_anno_dict, - ans_vocab, vocab, image_dir, - mean_image, 1+i*batch_size, - batch_size, 25, 25, 3) - - + ans_vocab, vocab, + image_regions_dir, mean_image, + 1+i*batch_size, batch_size, + 75, 75, 3) + feed_dict_train = { image_regions : train_region_images, questions: train_questions, @@ -152,38 +180,61 @@ def train(train_params): y: train_ans_labels, region_score: train_region_score, } - - tf.shape(y_pred) - - q_feat = tf.get_collection('q_feat', scope='ans/q_embed') - _,current_train_batch_acc,q_feat_eval = \ - sess.run([train_step, accuracy, q_feat[0]], + start1 = time.time() + _, current_train_batch_acc, y_avg_eval, y_pred_eval, logits_eval = \ + sess.run([train_step, accuracy, y_avg, y_pred, logits], feed_dict=feed_dict_train) - -# print(q_feat_eval) - # print(q_feat_eval.shape) -# print(i) -# print(train_questions) -# print(train_ans_labels) -# print(train_region_score) - + end1 = time.time() + # print('Training Pass: ' + str(end1-start1)) train_acc_array_epoch[epoch] = train_acc_array_epoch[epoch] + \ current_train_batch_acc + try: + assert (not np.any(np.isnan(y_avg_eval))) + except AssertionError: + print('Run NaNs coming') + print(1+i*batch_size) + print(y_avg_eval) + exit(1) + + if (i+1)%500==0: + print(logits_eval[0:22,:]) + print(train_region_score[0,0:22]) +# print(train_ans_labels[0,:]) + print(y_avg_eval[0,:]) +# print(y_pred_eval) + val_accuracy = evaluate(accuracy, qa_anno_dict, + region_anno_dict, ans_vocab, vocab, + image_regions_dir, mean_image, + val_start_id, val_batch_size_small, + placeholders, 75, 75) + + print('Iter: ' + str(i+1) + ' Val Sm Acc: ' + str(val_accuracy)) + + end = time.time() + print('Per Iter Time: ' + str(end-start)) train_acc_array_epoch[epoch] = train_acc_array_epoch[epoch] / max_iter - # val_accuracy = evaluate(accuracy, qa_anno_dict, - # region_anno_dict, ans_vocab, vocab, - # image_dir, mean_image, 9501, 499, - # placeholders, 25, 25) - # val_acc_array_epoch[epoch] = val_accuracy - # print(val_accuracy) - # plotter.plot_accuracies(xdata=np.arange(0, epoch + 1) + 1, - # ydata_train=train_acc_array_epoch[0:epoch + 1], - # ydata_val=val_acc_array_epoch[0:epoch + 1], - # xlim=[1, max_epoch], ylim=[0, 1.0], - # savePath=os.path.join(outdir, - # 'acc_vs_epoch.pdf')) - save_path = saver.save(sess, os.path.join(outdir, 'ans_classifier'), + start = time.time() + val_acc_array_epoch[epoch] = evaluate(accuracy, qa_anno_dict, + region_anno_dict, ans_vocab, vocab, + image_regions_dir, mean_image, + val_start_id, val_batch_size, + placeholders, 75, 75) + end=time.time() + print('Per Validation Time: ' + str(end-start)) + print('Val Acc: ' + str(val_acc_array_epoch[epoch]) + + ' Train Acc: ' + str(train_acc_array_epoch[epoch])) + + + plotter.plot_accuracies(xdata=np.arange(0, epoch + 1) + 1, + ydata_train=train_acc_array_epoch[0:epoch + 1], + ydata_val=val_acc_array_epoch[0:epoch + 1], + xlim=[1, max_epoch], ylim=[0, 1.0], + savePath=os.path.join(outdir, + 'acc_vs_epoch_q_o_atr.pdf')) + + save_path = saver.save(sess, + os.path.join(outdir,'ans_classifier_question_obj_atr_only'), global_step=epoch) sess.close() @@ -191,6 +242,6 @@ def train(train_params): if __name__=='__main__': train_params = { - 'adam_lr' : 0.001, + 'adam_lr' : 0.00001, } train(train_params) diff --git a/classifiers/region_ranker/perfect_ranker.py b/classifiers/region_ranker/perfect_ranker.py index a26d8e1..9d44e14 100644 --- a/classifiers/region_ranker/perfect_ranker.py +++ b/classifiers/region_ranker/perfect_ranker.py @@ -1,5 +1,6 @@ import json import os +import math import numpy as np from collections import namedtuple import matplotlib.pyplot as plt @@ -19,8 +20,8 @@ def parse_region_anno(json_filename): return region_anno_dict -def get_region_coords(): - region_coords = np.array([[ 1, 1, 100, 100], +def get_region_coords(img_height, img_width): + region_coords_ = np.array([[ 1, 1, 100, 100], [ 101, 1, 200, 100], [ 201, 1, 300, 100], [ 1, 101, 100, 200], @@ -42,9 +43,17 @@ def get_region_coords(): [ 1, 201, 200, 300], [ 101, 201, 300, 300], [ 1, 1, 300, 300]]) - return region_coords -def rank_regions(image, question, region_coords, gt_regions_for_image): + region_coords = np.copy(region_coords_) + region_coords[:,0] = np.ceil(region_coords[:,0]*img_width/300.0) + region_coords[:,1] = np.ceil(region_coords[:,1]*img_height/300.0) + region_coords[:,2] = np.round(region_coords[:,2]*img_width/300.0) + region_coords[:,3] = np.round(region_coords[:,3]*img_height/300.0) + print(region_coords) + return region_coords, region_coords_ + +def rank_regions(image, question, region_coords, region_coords_, + gt_regions_for_image, crop=True): num_regions, _ = region_coords.shape regions = dict() @@ -52,17 +61,27 @@ def rank_regions(image, question, region_coords, gt_regions_for_image): count = 0; no_region_flag = True for i in xrange(num_regions): + x1_ = region_coords_[i,0] + y1_ = region_coords_[i,1] + x2_ = region_coords_[i,2] + y2_ = region_coords_[i,3] + x1 = region_coords[i,0] y1 = region_coords[i,1] x2 = region_coords[i,2] y2 = region_coords[i,3] - - cropped_image = image[y1-1:y2, x1-1:x2, :] + + if crop: + cropped_image = image[y1-1:y2, x1-1:x2, :] + else: + cropped_image = None + score = 0 for gt_region in gt_regions_for_image: - x1_, y1_, x2_, y2_ = gt_regions_for_image[gt_region] - if x1==x1_ and x2==x2_ and y1==y1_ and y2==y2_: + gt_x1, gt_y1, gt_x2, gt_y2 = gt_regions_for_image[gt_region] + if gt_x1==x1_ and gt_x2==x2_ and gt_y1==y1_ and \ + gt_y2==y2_ and gt_region in question: score = 1 no_region_flag = False break diff --git a/classifiers/region_ranker/perfect_ranker.pyc b/classifiers/region_ranker/perfect_ranker.pyc index ac7c5aca66999387ddddd8c1307cd88b3c37391b..6807d1410576a03a277f3b0cf87f780fe3fe750f 100644 GIT binary patch literal 3307 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 C6?T^Z delta 1123 zcmYjQO-~b16g_vQozApVTKWY;T^J%#jDWh(4T&ZaWP?#iKqe4un1N7AvF(^(oJkkx z&cv8W{0A<KD?(hjap#|K$Jz~v3lleb@1r%&ym#lG_i^5R_n!ITd@Wb?pV@THKVRKO z|1tP`k9&9MU3H?T!vj1G%>)jTaFZaxgr<Q;3a+W98Cqzh;bs)n(X?@pg_{NWNq7l( zNgQi%hu|4_1-LnQDdpthnaUZ4Uw~&}UqdHN9&;7p6MBsj8fAD)Y%7Wi{1NyScv*pO zD{x2Q0}TiMD2@s47}hwv$4`!?;g4Z{a~y6Jeihyj951K%&lJwXd9c_%1Glx7y3<N+ znD4#!ho6TvVmC;iF3}sB%1AnFHlPBPHH)%T(Nw}@)fo5t+`F4}s_E-w0wJ(cOU3KS z@oG)usX$EYS&-yUz0;|`Q1tQd<f>hfoil7e<@l*F(aTYeMrk|2BW3P#wqpOtXN4LX zz=nn>fhdW9a12C-6eLQ?DUpezNeXm?3CYI%rbCe>+d@kg?PzeK^Z;uYI5WVy;GPrN zgMq}wf~?#p<PvBY4YPR7Z0QSN3lKp-oE@FnxyBq!4o4nBodeiFlov1Ld~a~n!-xuq ziik?W5)P}1oFL>wi{ke>3^=61V1iFC^?eS2nFD4q@u+<QZfl%VR#&K`6)IizZ~HGQ zzVk8k&_I~N9Cv0J;cT^UVl6Z!X`;CgbhHS`+d@Bacfg73lk-*!PJ{Ez&6M1hS--+4 z!znI;qPH;D-X#%JXvCk)c{1a3b8<<FzEE#={bf<Xa!HI-y(Z;VFks1`{oL;cdxx#% z0)s$S?FL&Nf0u{4TRVrHt-XW#t{=a&?#AD(nSyK%tp8WVU#!b<&z`+4O6}BJ`-9!H zjJvnkK+BP(8DvnA@)xf*O@_vDrW^6+^tb#aUbLKIqZE7CjpNKVmEvESNuwxJ*W)Ss g{k5y?+9YqD#hMeVTN%r-uGq|Gla=G&M9=d0U$FDSnE(I) diff --git a/classifiers/tf_graph_creation_helper.py b/classifiers/tf_graph_creation_helper.py index 1b18cc0..be5d854 100644 --- a/classifiers/tf_graph_creation_helper.py +++ b/classifiers/tf_graph_creation_helper.py @@ -1,14 +1,15 @@ import numpy as np +import math import tensorflow as tf import answer_classifier.ans_data_io_helper as ans_io_helper -def weight_variable(shape, var_name = 'W'): - initial = tf.truncated_normal(shape, stddev=0.1) +def weight_variable(shape, var_name = 'W', std=0.1): + initial = tf.truncated_normal(shape, stddev=std) return tf.Variable(initial, name=var_name) def bias_variable(shape, var_name = 'b'): - initial = tf.constant(0.1, shape=shape) + initial = tf.constant(0.001, shape=shape) return tf.Variable(initial, name=var_name) @@ -96,13 +97,14 @@ def ans_comp_graph(image_regions, questions, keep_prob, \ obj_feat, atr_feat, vocab, inv_vocab, ans_vocab_size): with tf.name_scope('ans') as ans_graph: with tf.name_scope('word_embed') as word_embed: - initial = tf.truncated_normal(shape=[len(vocab),100], stddev=0.1) -# initial = tf.random_uniform(shape=[len(vocab),100], minval=0, maxval=1) + initial = tf.truncated_normal(shape=[len(vocab),50], + stddev=math.sqrt(3.0/(31.0+300.0))) word_vecs = tf.Variable(initial, name='word_vecs') with tf.name_scope('q_embed') as q_embed: q_feat = tf.matmul(questions, word_vecs) -# q_feat = tf.truediv(q_feat, tf.cast(len(vocab),tf.float32)) + # q_feat = tf.truediv(q_feat, tf.cast(len(vocab),tf.float32)) + q_feat = tf.truediv(q_feat, tf.reduce_sum(questions,1,keep_dims=True)) with tf.name_scope('conv1') as conv1: W_conv1 = weight_variable([5,5,3,4]) @@ -120,21 +122,38 @@ def ans_comp_graph(image_regions, questions, keep_prob, \ h_pool2_drop_flat = tf.reshape(h_pool2_drop, [-1, 392], name='h_pool_drop_flat') with tf.name_scope('fc1') as fc1: - W_region_fc1 = weight_variable([392, ans_vocab_size], var_name='W_region') - W_obj_fc1 = weight_variable([392, ans_vocab_size], var_name='W_obj') - W_atr_fc1 = weight_variable([392, ans_vocab_size], var_name='W_atr') - W_q_fc1 = weight_variable([100, ans_vocab_size], var_name='W_q') - b_fc1 = bias_variable([ans_vocab_size]) - - y_pred = tf.nn.softmax(tf.matmul(h_pool2_drop_flat, W_region_fc1) + \ - tf.matmul(obj_feat, W_obj_fc1) + \ - tf.matmul(atr_feat, W_atr_fc1) + \ - tf.matmul(q_feat, W_q_fc1) + b_fc1) + fc1_dim = 300 + W_region_fc1 = weight_variable([392, fc1_dim], var_name='W_region') + W_obj_fc1 = weight_variable([392, fc1_dim], var_name='W_obj', + std=math.sqrt(3/(392+ans_vocab_size))) + W_atr_fc1 = weight_variable([392, fc1_dim], var_name='W_atr', + std=math.sqrt(3/(392+ans_vocab_size))) + W_q_fc1 = weight_variable([50, fc1_dim], var_name='W_q', + std=math.sqrt(3.0/(50.0+ans_vocab_size))) + b_fc1 = bias_variable([fc1_dim]) + + h_tmp = tf.matmul(q_feat, W_q_fc1) + b_fc1 + \ + tf.matmul(obj_feat, W_obj_fc1) + \ + tf.matmul(atr_feat, W_atr_fc1) + + #tf.matmul(h_pool2_drop_flat, W_region_fc1) + \ + + h_fc1 = tf.nn.relu(h_tmp, name='h') + h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob, name='h_drop') + + with tf.name_scope('fc2') as fc2: + W_fc2 = weight_variable([fc1_dim, ans_vocab_size], + std=math.sqrt(3.0/(fc1_dim))) + b_fc2 = bias_variable([ans_vocab_size]) + + logits = tf.matmul(h_fc1_drop, W_fc2) + b_fc2 + + y_pred = tf.nn.softmax(logits) tf.add_to_collection('region_feat', h_pool2_drop_flat) tf.add_to_collection('q_feat', q_feat) - return y_pred + return y_pred, logits def aggregate_y_pred(y_pred, region_score, batch_size, num_proposals, ans_vocab_size): y_pred_list = tf.split(0, batch_size, y_pred) @@ -153,7 +172,8 @@ def evaluation(y, y_pred): def loss(y, y_pred): cross_entropy = -tf.reduce_sum(y * tf.log(y_pred), name='cross_entropy') - return cross_entropy + batch_size = tf.shape(y) + return tf.truediv(cross_entropy, tf.cast(batch_size[0],tf.float32)) if __name__ == '__main__': diff --git a/classifiers/tf_graph_creation_helper.pyc b/classifiers/tf_graph_creation_helper.pyc index cb966f7932fe3d93f724fcdb8c59eb25570ee614..4defda24febbdc5564e8f0d5b4820a16d9ee72e0 100644 GIT binary patch delta 3237 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 delta 2677 zcmb7FO>A355T1Sh|BHW;_}_7yq^+AKX_J<=hfoT%D(O$4FHlErgfV^(*KT45`z2DV zXM#utRgtKw6;k<84_qp^=EQ+JLa0Il4yYVjsTWQlK)u4uJf|To2e9MG%<RnUw>!Jv zUVrxXbjtQe!24<Qw{sQfetPi#QT(+Z+(;!}wSfkp30Sm4-2p`>XbNaHxFVqF0?iJZ zgLOCioveF6!yz~8UeM6=vhHKu$GRUhKU{$uHz(KhUeF?NQGwPyq4h$s4|FHg`(Yhm zL4jfviZRgoK<kHCG0tNN(BrU<2$F23Ku?lM(P=1VK(|4Cfbxr3(6dk<B>nogAap<I zIndJ-O-dA%HPE7<#b8|#3pOYYL2($0BcKOB9|C;@{ZQP&C3k`r2Q2{_%_fSwK=0|s z=(|8mL1G>A^P8b*4$W}rZqS1qI!qP2(fV#qnnyS~V(!9$bvG18xyT+~!WigdP#<T{ z1bGm<C};xC&NsL-bNK=6R_!nR%o+mKLPbGolaoqX(G+>kHrQu4siB6Z>8sU`Kcdf< z&&bQl;gk*ihJyw#l$UkGjoNCtRce%%borxlQaI$Gt>EgW$hT~Vgj@canvf4{Vd0ez zZTB2uObN=n_II-Z)Koe>YLEnh$WwilMM%Ewh&m%=yST(Rj*#e;cO7>`On%|KB_eXh zH7{ZtpSeZ^yT%nYE+4w5d<jZQl1Rz@p2>I`RfA@$EtRW!sijwI%|=_zNYk?-hBkip ze4q??DBMpXMA3E>Eq!rCCAex--tx~T7)ypmDY*H1y;N<LmoKP!=?EMbd*rFWc`+_; z2TEdc!_{*{c{-0_HQKBit*Aydd6nvJ(nd+_p#j=O?nmt^_Cp<`fpPgx=)n2Cq;7|% z?jxTCi?m4+QzZ72I6&el6!|D=k1(qfWF90zu+&4+AC5a|dUaZkhBJu#OnA`wJUNch ze#faZWusN9G?$jGmA-(U_Nest49lOx!O0;sX|pJ&{WZ4F0H_PP{CMQ9Zzvu2rbl7V zQJIcp*EA;{N~PoFF*}%e<N5aE73uh(fb4O0zFWw{X$k1~IoJWF0Mkb|tP|KC>E+1F z(CGm@x*9SE{vl@splYX~P7n;71<f@^*9mb&H`F~~c)<iPgH+cHL5$$^V3O#B!SI0@ z<^qw;1V5M&Fp<<SjB-S$cRQ94W4Q^PKE}e#g)?hm#)9*A`WXz`7MAE{X$uQhM>0b! z(SKrzF&3m9%#Uv-AeQ)cED0V8fQhsd0i?&Og|v^6CZQmI>GiK+eeBRGbW#kl7fcri zTEvDcPe0)&0u`Oi6aEDI2iT9@d{l=yual*flG9r0ptVzL++f^%>=qsw>}wKCBw1zC zw1J64s6ebcjD?Y(w`^QrmThwoVzeVU&I}SkVl9jml!<m4%mCYF4(6=GrGXvh@)`Mk ze=dTj4OHU5EtZQ%v#;?LdHm-7mrG0KTqu|r3tgsBPmr4~k3l?8D7`7ai>B7{bayPS z<7-azzyXVTLnYR*?(+Y-{e1E1UaBmHNh8LxZvPBwOx~-_N?Cp$3r*$7Lo<{c?Jk?m zQaCY^`XY&!NW4tqUuk1%9ES|z&CS)8-l$BfCn@0+4bg7kirKqGF7l^%<Y)<p+S4dN ziHL|8QBwFnZo{7liXFl)a%6Y)6poW+eft%UZAP9*{E#|;>HJz%nvF`?$a~RNkC7OW z2a`KQPA(+h3%`$!HW3<#e*8KW`DgNelAee>ZM#Y@qLp=lv>FoN^w5W>w&@`Ru}_SM z3Njo}11W=+(%vP{q<79C7tHayHOu7A4^5@jY`05#!)P^^FKs<)NHz4VXe$-H)LvP# zy4|c=0b3ZF#J_zs%_5fhgr*_`#IQV_x#pwiOpTM6koPmWJhipWO*C42aT`{r@^qlM z(~Xs-<x2)3Z!F8b1J%P6XT5V<Pe_)0J4qZTahk+=5>*ln5-oXW;HvW+sRg-^9X|33 zsgE79W9Tzzm#eyg&&J}SUNP{c<-^AZ;ZkWCS3jlB$gi^3hMvPnKejoj_#ODQ<Jad8 SdSdt;^LRZ@k3(J@9Qhm0d*9mt -- GitLab