Copyright 2018 Google LLC

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Experiment 1: fixed detector in many scenarios

This notebook contains the code for computing the performance of the fixed strategies in various scenarios. The full experiment is described in Sec. 5.2 of CVPR submission "Learning Intelligent Dialogs for Bounding Box Annotation". Please note that this notebook does not reproduce the experiment since the starting detector is too strong, there is no re-training, and there are only two iterations being done.


In [0]:
import matplotlib.pyplot as plt
import numpy as np
from __future__ import division
from __future__ import print_function
import math
import gym

import pandas as pd
from gym import spaces

from sklearn import neural_network, model_selection
from sklearn.neural_network import MLPClassifier

from third_party import np_box_ops
import annotator, detector, dialog, environment

To specify the experiments, define:

  • type of drawing
  • desired quality of bounding boxes

In [0]:
# desired quality: high (min_iou=0.7) and low (min_iou=0.5)
min_iou = 0.7 # @param ["0.5", "0.7"]
# drawing speed: high (time_draw=7) and low (time_draw=25)
time_draw = 7 # @param ["7", "25"]

Other parameters of the experiment


In [0]:
random_seed = 80590 # global variable that fixes the random seed everywhere for replroducibility of results

# what kind of features will be used to represent the state
# numerical values 1-20 correspond to one hot encoding of class
predictive_fields = ['prediction_score', 'relative_size', 'avg_score', 'dif_avg_score', 'dif_max_score', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

time_verify = 1.8 # @param

Load all data


In [0]:
# Download GT:
# wget wget https://storage.googleapis.com/iad_pascal_annotations_and_detections/pascal_gt_for_iad.h5
# Download detections with features
# wget https://storage.googleapis.com/iad_pascal_annotations_and_detections/pascal_proposals_plus_features_for_iad.h5

download_dir = ''
ground_truth = pd.read_hdf(download_dir + 'pascal_gt_for_iad.h5', 'ground_truth')
box_proposal_features = pd.read_hdf(download_dir + 'pascal_proposals_plus_features_for_iad.h5', 'box_proposal_features')

Initialise the experiment


In [0]:
annotator_real = annotator.AnnotatorSimple(ground_truth, random_seed, time_verify, time_draw, min_iou)

In [0]:
# better call it image_class_pairs later
image_class = ground_truth[['image_id', 'class_id']]
image_class = image_class.drop_duplicates()

Select the trainig and testing data according to the selected fold. We split all images in 10 approximately equal parts and each fold includes these images together with all classes present in them.


In [0]:
unique_image = image_class['image_id'].drop_duplicates()

# divide the images into exponentially growing groups
im1 = unique_image.iloc[157]
im2 = unique_image.iloc[157+157]
im3 = unique_image.iloc[157+157+314]
im4 = unique_image.iloc[157+157+314+625]
im5 = unique_image.iloc[157+157+314+625+1253]

# image_class pairs groups are determined by the images in them
image_class_array = image_class.values[:,0]
in1 = np.searchsorted(image_class_array, im1, side='right')
in2 = np.searchsorted(image_class_array, im2, side='right')
in3 = np.searchsorted(image_class_array, im3, side='right')
in4 = np.searchsorted(image_class_array, im4, side='right')
in5 = np.searchsorted(image_class_array, im5, side='right')

Batch 1: Annotate 3.125% of data with strategy X


In [0]:
the_detector = detector.Detector(box_proposal_features, predictive_fields)
image_class_current = image_class.iloc[0:in1]

In [9]:
%output_height 300

env = environment.AnnotatingDataset(annotator_real, the_detector, image_class_current)
print('Running ', len(env.image_class), 'episodes with strategy X')

total_reward = 0
new_ground_truth_all = []
all_annotations = dict()

for i in range(len(env.image_class)):
  print('Episode ', i, end = ': ')
  state = env.reset(current_index=i)
  agent = dialog.FixedDialog(0)
  done = False
  while not(done):
    action = agent.get_next_action(state)    
    if action==0:
      print('V', end='')
    elif action==1:
      print('D', end='')
    next_state, reward, done, coordinates = env.step(action)
    state = next_state
    total_reward += reward

  dataset_id = env.current_image

  # ground truth with which we will initialise the new user
  new_ground_truth = {}
  new_ground_truth['image_id'] = dataset_id
  new_ground_truth['class_id'] = env.current_class
  new_ground_truth['xmax'] = coordinates['xmax']
  new_ground_truth['xmin'] = coordinates['xmin']
  new_ground_truth['ymax'] = coordinates['ymax']
  new_ground_truth['ymin'] = coordinates['ymin']
  new_ground_truth_all.append(new_ground_truth)


  if dataset_id not in all_annotations:
    current_annotation = dict()
    current_annotation['boxes'] = np.array([[coordinates['ymin'], coordinates['xmin'], coordinates['ymax'], coordinates['xmax']]], dtype=np.int32)
    current_annotation['box_labels'] = np.array([env.current_class])
    all_annotations[dataset_id] = current_annotation

  else:
    all_annotations[dataset_id]['boxes'] = np.append(all_annotations[dataset_id]['boxes'],  np.array([[coordinates['ymin'], coordinates['xmin'], coordinates['ymax'], coordinates['xmax']]], dtype=np.int32), axis=0)
    all_annotations[dataset_id]['box_labels'] = np.append(all_annotations[dataset_id]['box_labels'], np.array([env.current_class]))     

  print()

print('total_reward = ', total_reward)    
print('average episode reward = ', total_reward/len(env.image_class))

new_ground_truth_all = pd.DataFrame(new_ground_truth_all)


Running  241 episodes with strategy X
Episode  0: D
Episode  1: D
Episode  2: D
Episode  3: D
Episode  4: D
Episode  5: D
Episode  6: D
Episode  7: D
Episode  8: D
Episode  9: D
Episode  10: D
Episode  11: D
Episode  12: D
Episode  13: D
Episode  14: D
Episode  15: D
Episode  16: D
Episode  17: D
Episode  18: D
Episode  19: D
Episode  20: D
Episode  21: D
Episode  22: D
Episode  23: D
Episode  24: D
Episode  25: D
Episode  26: D
Episode  27: D
Episode  28: D
Episode  29: D
Episode  30: D
Episode  31: D
Episode  32: D
Episode  33: D
Episode  34: D
Episode  35: D
Episode  36: D
Episode  37: D
Episode  38: D
Episode  39: D
Episode  40: D
Episode  41: D
Episode  42: D
Episode  43: D
Episode  44: D
Episode  45: D
Episode  46: D
Episode  47: D
Episode  48: D
Episode  49: D
Episode  50: D
Episode  51: D
Episode  52: D
Episode  53: D
Episode  54: D
Episode  55: D
Episode  56: D
Episode  57: D
Episode  58: D
Episode  59: D
Episode  60: D
Episode  61: D
Episode  62: D
Episode  63: D
Episode  64: D
Episode  65: D
Episode  66: D
Episode  67: D
Episode  68: D
Episode  69: D
Episode  70: D
Episode  71: D
Episode  72: D
Episode  73: D
Episode  74: D
Episode  75: D
Episode  76: D
Episode  77: D
Episode  78: D
Episode  79: D
Episode  80: D
Episode  81: D
Episode  82: D
Episode  83: D
Episode  84: D
Episode  85: D
Episode  86: D
Episode  87: D
Episode  88: D
Episode  89: D
Episode  90: D
Episode  91: D
Episode  92: D
Episode  93: D
Episode  94: D
Episode  95: D
Episode  96: D
Episode  97: D
Episode  98: D
Episode  99: D
Episode  100: D
Episode  101: D
Episode  102: D
Episode  103: D
Episode  104: D
Episode  105: D
Episode  106: D
Episode  107: D
Episode  108: D
Episode  109: D
Episode  110: D
Episode  111: D
Episode  112: D
Episode  113: D
Episode  114: D
Episode  115: D
Episode  116: D
Episode  117: D
Episode  118: D
Episode  119: D
Episode  120: D
Episode  121: D
Episode  122: D
Episode  123: D
Episode  124: D
Episode  125: D
Episode  126: D
Episode  127: D
Episode  128: D
Episode  129: D
Episode  130: D
Episode  131: D
Episode  132: D
Episode  133: D
Episode  134: D
Episode  135: D
Episode  136: D
Episode  137: D
Episode  138: D
Episode  139: D
Episode  140: D
Episode  141: D
Episode  142: D
Episode  143: D
Episode  144: D
Episode  145: D
Episode  146: D
Episode  147: D
Episode  148: D
Episode  149: D
Episode  150: D
Episode  151: D
Episode  152: D
Episode  153: D
Episode  154: D
Episode  155: D
Episode  156: D
Episode  157: D
Episode  158: D
Episode  159: D
Episode  160: D
Episode  161: D
Episode  162: D
Episode  163: D
Episode  164: D
Episode  165: D
Episode  166: D
Episode  167: D
Episode  168: D
Episode  169: D
Episode  170: D
Episode  171: D
Episode  172: D
Episode  173: D
Episode  174: D
Episode  175: D
Episode  176: D
Episode  177: D
Episode  178: D
Episode  179: D
Episode  180: D
Episode  181: D
Episode  182: D
Episode  183: D
Episode  184: D
Episode  185: D
Episode  186: D
Episode  187: D
Episode  188: D
Episode  189: D
Episode  190: D
Episode  191: D
Episode  192: D
Episode  193: D
Episode  194: D
Episode  195: D
Episode  196: D
Episode  197: D
Episode  198: D
Episode  199: D
Episode  200: D
Episode  201: D
Episode  202: D
Episode  203: D
Episode  204: D
Episode  205: D
Episode  206: D
Episode  207: D
Episode  208: D
Episode  209: D
Episode  210: D
Episode  211: D
Episode  212: D
Episode  213: D
Episode  214: D
Episode  215: D
Episode  216: D
Episode  217: D
Episode  218: D
Episode  219: D
Episode  220: D
Episode  221: D
Episode  222: D
Episode  223: D
Episode  224: D
Episode  225: D
Episode  226: D
Episode  227: D
Episode  228: D
Episode  229: D
Episode  230: D
Episode  231: D
Episode  232: D
Episode  233: D
Episode  234: D
Episode  235: D
Episode  236: D
Episode  237: D
Episode  238: D
Episode  239: D
Episode  240: D
total_reward =  -1687
average episode reward =  -7.0

Batch 2

Starting from Batch 3 the code will be just repeated.


In [0]:
ground_truth_new = pd.DataFrame(new_ground_truth_all)
annotator_new = annotator.AnnotatorSimple(ground_truth_new, random_seed, time_verify, time_draw, min_iou)

In [11]:
# @title Collect data for classifier
env = environment.AnnotatingDataset(annotator_new, the_detector, image_class_current)

print('Running ', len(env.image_class), 'episodes with strategy V3X')

%output_height 300
total_reward = 0

data_for_classifier = []

for i in range(len(env.image_class)):
  print(i, end = ': ')
  agent = dialog.FixedDialog(3)
  state = env.reset(current_index=i)

  done = False
  while not(done):
    action = agent.get_next_action(state)
    next_state, reward, done, _ = env.step(action)
    if action==0:
      state_dict = dict(state)
      state_dict['is_accepted'] = done
      data_for_classifier.append(state_dict)
      print('V', end='')
    elif action==1:
      print('D', end='')
    state = next_state
    total_reward += reward

  print()

print('Average episode reward = ', total_reward/len(env.image_class))

data_for_classifier = pd.DataFrame(data_for_classifier)


Running  241 episodes with strategy V3X
0: VVVD
1: V
2: V
3: V
4: V
5: V
6: V
7: V
8: V
9: V
10: VVV
11: VVV
12: VVVD
13: VVVD
14: V
15: V
16: V
17: VV
18: VVVD
19: VV
20: V
21: VVVD
22: VVVD
23: V
24: V
25: V
26: V
27: V
28: VV
29: V
30: V
31: V
32: V
33: VVVD
34: V
35: VV
36: VVVD
37: VVVD
38: V
39: VV
40: VVVD
41: VVVD
42: VVVD
43: VV
44: V
45: V
46: V
47: V
48: VVV
49: V
50: VVVD
51: V
52: V
53: VVVD
54: VVV
55: VVVD
56: VVV
57: V
58: V
59: VVVD
60: V
61: VVVD
62: V
63: V
64: V
65: VVVD
66: VVVD
67: V
68: VV
69: VV
70: V
71: V
72: V
73: V
74: VVVD
75: VVVD
76: V
77: V
78: V
79: V
80: V
81: VV
82: V
83: V
84: VVVD
85: VV
86: VVVD
87: VVVD
88: V
89: V
90: VVVD
91: V
92: VVVD
93: VVVD
94: V
95: VVVD
96: V
97: V
98: V
99: V
100: V
101: V
102: VVVD
103: VVVD
104: VVVD
105: V
106: VVVD
107: V
108: V
109: V
110: V
111: VVVD
112: V
113: V
114: V
115: V
116: V
117: V
118: V
119: V
120: VVVD
121: V
122: VVVD
123: V
124: V
125: V
126: V
127: VVVD
128: V
129: V
130: V
131: V
132: VVVD
133: V
134: V
135: VVV
136: VVVD
137: VVVD
138: V
139: VVVD
140: V
141: V
142: VVV
143: V
144: VVVD
145: VVV
146: V
147: V
148: V
149: V
150: V
151: V
152: V
153: VVVD
154: V
155: V
156: V
157: VVVD
158: V
159: V
160: V
161: VV
162: VV
163: V
164: VVVD
165: VVVD
166: VVVD
167: VVVD
168: V
169: V
170: V
171: V
172: V
173: VVVD
174: V
175: V
176: VVVD
177: V
178: VVVD
179: VVVD
180: V
181: VVVD
182: VVVD
183: VVVD
184: VVVD
185: V
186: VV
187: V
188: V
189: V
190: V
191: V
192: VVVD
193: V
194: V
195: V
196: VV
197: VVVD
198: V
199: VV
200: VVVD
201: V
202: V
203: V
204: V
205: V
206: VVVD
207: VVVD
208: VVVD
209: VVVD
210: V
211: V
212: VVV
213: V
214: V
215: VV
216: VVVD
217: VVVD
218: V
219: V
220: V
221: VVVD
222: V
223: VVVD
224: V
225: V
226: V
227: VVVD
228: V
229: V
230: VVVD
231: V
232: V
233: V
234: VVVD
235: VVVD
236: VVVD
237: V
238: V
239: V
240: VV
Average episode reward =  -5.18423236515

In [12]:
# @title Train classification model (might take some time)

#model_mlp = neural_network.MLPClassifier(alpha = 0.0001, activation = 'relu', hidden_layer_sizes = (50, 50, 50, 50, 50), random_state=602)
#model_for_agent = model_mlp.fit(data_from_Vx3X[predictive_fields], data_from_Vx3X['is_accepted'])
np.random.seed(random_seed) # for reproducibility of fitting the classifier and cross-validation

print('Cross-validating parameters\' values... This might take some time.')

# possible parameter values
parameters = {'hidden_layer_sizes': ((20, 20, 20), (50, 50, 50), (80, 80, 80), (20, 20, 20, 20), (50, 50, 50, 50), (80, 80, 80, 80), (20, 20, 20, 20, 20), (50, 50, 50, 50, 50), (80, 80, 80, 80, 80)), 'activation': ('logistic', 'relu'), 'alpha': [0.0001, 0.001, 0.01]}
model_mlp = neural_network.MLPClassifier()
# cross-validate parameters
grid_search = model_selection.GridSearchCV(model_mlp, parameters, scoring='neg_log_loss', refit=True)
grid_search.fit(data_for_classifier[predictive_fields], data_for_classifier['is_accepted'])
print('best score = ', grid_search.best_score_)
print('best parameters = ', grid_search.best_params_)
# use the model with the best parameters
model_for_agent = grid_search.best_estimator_


Cross-validating parameters' values... This might take some time.
best score =  -0.613398861011
best parameters =  {'hidden_layer_sizes': (20, 20, 20, 20, 20), 'activation': 'relu', 'alpha': 0.01}
sklearn/neural_network/multilayer_perceptron.py:564: ConvergenceWarning: Stochastic Optimizer: Maximum iterations (200) reached and the optimization hasn't converged yet.

Now is the time to retrain the detector and obtain new box_proposal_features. This is not done in this notebook.


In [0]:
image_class_current = image_class.iloc[in1:in2]
the_detector = detector.Detector(box_proposal_features, predictive_fields)
agent = dialog.DialogProb(model_for_agent, annotator_real)

In [14]:
# @title Annotating data with intelligent dialog
env = environment.AnnotatingDataset(annotator_real, the_detector, image_class_current)

print('Running ', len(env.image_class), 'episodes with strategy IAD-Prob')

%output_height 300
print('intelligent dialog strategy')

total_reward = 0
# reset the gound truth because the user only needs to annotate the last 10% of data using the detector from the rest of the data
new_ground_truth_all = []

for i in range(len(env.image_class)):
  print(i, end = ': ')
  state = env.reset(current_index=i)

  done = False
  while not(done):
    action = agent.get_next_action(state)
    if action==0:
      print('V', end='')
    elif action==1:
      print('D', end='')
    next_state, reward, done, coordinates = env.step(action)
    state = next_state
    total_reward += reward

  dataset_id = env.current_image

  # ground truth with which we will initialise the new user
  new_ground_truth = {}
  new_ground_truth['image_id'] = dataset_id
  new_ground_truth['class_id'] = env.current_class
  new_ground_truth['xmax'] = coordinates['xmax']
  new_ground_truth['xmin'] = coordinates['xmin']
  new_ground_truth['ymax'] = coordinates['ymax']
  new_ground_truth['ymin'] = coordinates['ymin']
  new_ground_truth_all.append(new_ground_truth)


  if dataset_id not in all_annotations:
    current_annotation = dict()
    current_annotation['boxes'] = np.array([[coordinates['ymin'], coordinates['xmin'], coordinates['ymax'], coordinates['xmax']]], dtype=np.int32)
    current_annotation['box_labels'] = np.array([env.current_class])
    all_annotations[dataset_id] = current_annotation

  else:
    all_annotations[dataset_id]['boxes'] = np.append(all_annotations[dataset_id]['boxes'],  np.array([[coordinates['ymin'], coordinates['xmin'], coordinates['ymax'], coordinates['xmax']]], dtype=np.int32), axis=0)
    all_annotations[dataset_id]['box_labels'] = np.append(all_annotations[dataset_id]['box_labels'], np.array([env.current_class]))     

  print()

print('total_reward = ', total_reward)    
print('average episode reward = ', total_reward/len(env.image_class))

new_ground_truth_all = pd.DataFrame(new_ground_truth_all)


Running  250 episodes with strategy IAD-Prob
intelligent dialog strategy
0: V
1: V
2: V
3: V
4: V
5: V
6: V
7: VV
8: V
9: V
10: D
11: V
12: V
13: V
14: V
15: V
16: V
17: V
18: D
19: V
20: V
21: V
22: V
23: D
24: VD
25: VD
26: V
27: V
28: V
29: V
30: D
31: V
32: V
33: VVD
34: VVVVD
35: V
36: VD
37: D
38: V
39: V
40: V
41: VVD
42: VV
43: V
44: V
45: VD
46: D
47: V
48: VD
49: D
50: V
51: V
52: D
53: V
54: V
55: V
56: V
57: V
58: V
59: VVD
60: VD
61: VD
62: V
63: V
64: V
65: V
66: V
67: V
68: VVD
69: V
70: VVVVD
71: V
72: D
73: D
74: V
75: V
76: V
77: D
78: V
79: VD
80: VVD
81: V
82: V
83: V
84: VVV
85: V
86: V
87: D
88: V
89: V
90: V
91: D
92: V
93: V
94: V
95: V
96: VD
97: V
98: VD
99: V
100: V
101: V
102: V
103: D
104: D
105: V
106: V
107: V
108: VVD
109: VD
110: V
111: D
112: V
113: V
114: V
115: VD
116: V
117: V
118: V
119: V
120: V
121: V
122: VV
123: D
124: V
125: V
126: D
127: D
128: V
129: V
130: V
131: V
132: VD
133: V
134: V
135: V
136: V
137: V
138: D
139: V
140: VD
141: V
142: D
143: V
144: V
145: V
146: V
147: V
148: D
149: V
150: V
151: V
152: V
153: V
154: VD
155: VVD
156: D
157: D
158: V
159: V
160: V
161: V
162: V
163: V
164: V
165: V
166: V
167: V
168: VVV
169: VD
170: V
171: VD
172: VD
173: V
174: V
175: D
176: V
177: V
178: V
179: V
180: V
181: D
182: V
183: V
184: V
185: V
186: D
187: V
188: D
189: VD
190: V
191: V
192: V
193: V
194: V
195: V
196: V
197: V
198: VD
199: V
200: V
201: V
202: D
203: D
204: VV
205: D
206: D
207: VV
208: VVVVVVVVVVVVVVVVVVVVVVD
209: D
210: V
211: VD
212: D
213: V
214: D
215: V
216: V
217: VVD
218: V
219: VV
220: V
221: V
222: V
223: D
224: VD
225: V
226: V
227: V
228: V
229: D
230: VVVVVVD
231: V
232: VVVVVVVVVVVVVVVV
233: V
234: VD
235: D
236: D
237: VVVD
238: V
239: V
240: V
241: V
242: V
243: VV
244: V
245: VD
246: V
247: VD
248: VVD
249: V
total_reward =  -1050.0
average episode reward =  -4.2