1050 lines
38 KiB
Python
1050 lines
38 KiB
Python
|
|
"""Supporting script checks constraints for caption and question generation.
|
|
Author: Satwik Kottur
|
|
"""
|
|
|
|
import copy
|
|
import json
|
|
import random
|
|
import numpy as np
|
|
|
|
import global_vars as gvars
|
|
|
|
|
|
# Some quick methods.
|
|
def apply_immediate(hist): return (len(hist['objects']) == 1 and
|
|
hist['mergeable'] and
|
|
'exist' not in hist['template'])
|
|
|
|
|
|
def apply_group(hist): return (len(hist['objects']) >= 2 and
|
|
hist['mergeable'] and
|
|
'count' not in prev_group)
|
|
|
|
|
|
def caption(scene, templates):
|
|
"""Constraints for caption generation.
|
|
Args:
|
|
scene: CLEVR Scene graphs to generate captions with constraints
|
|
template: List of caption templates
|
|
Returns:
|
|
sample_captions: Samples from caption hypotheses
|
|
"""
|
|
|
|
caption_hypotheses = {}
|
|
|
|
# Sweep through all templates to extract 'interesting' captions.
|
|
n_objs = len(scene['objects'])
|
|
rels = scene['relationships']
|
|
|
|
# Caption Type 1: Extreme locations.
|
|
ext_loc_templates = [ii for ii in templates if ii['type'] == 'extreme-loc']
|
|
# number of objects in the scene
|
|
filter_objs = copy.deepcopy(scene['objects'])
|
|
attr_counts = get_attribute_counts_for_objects(scene, filter_objs)
|
|
hypotheses = []
|
|
for template in ext_loc_templates:
|
|
# absolute location based constraint
|
|
constraint = template['constraints'][0]
|
|
extreme_type = constraint['args'][0]
|
|
|
|
# check if there is an object that is at the center of the image
|
|
# roughly in the middle along front-back and right-left dim
|
|
if extreme_type == 'center':
|
|
for ii, obj in enumerate(filter_objs):
|
|
bla = [len(rels[kk][ii]) <= n_objs / 2
|
|
for kk in ['front', 'behind', 'right', 'left']]
|
|
matches = np.sum([len(rels[kk][ii]) <= n_objs / 2
|
|
for kk in ['front', 'behind', 'right', 'left']])
|
|
if matches == 4:
|
|
hypotheses.append((extreme_type, copy.deepcopy(obj)))
|
|
else:
|
|
for ii, obj in enumerate(filter_objs):
|
|
if len(rels[extreme_type][ii]) == 0:
|
|
hypotheses.append((extreme_type, copy.deepcopy(obj)))
|
|
|
|
# sample one at random, and create the graph item
|
|
# Filter hypothesis which are ambiguous otherwise.
|
|
for index, (_, hypothesis) in enumerate(hypotheses):
|
|
uniq_attr = [attr for attr in gvars.METAINFO['attributes']
|
|
if attr_counts[(attr, hypothesis[attr])] == 1]
|
|
|
|
for attr in uniq_attr:
|
|
del hypotheses[index][1][attr]
|
|
|
|
hypotheses = [ii for ii in hypotheses if len(ii[1]) > 1]
|
|
caption_hypotheses['extreme-loc'] = hypotheses
|
|
|
|
# Caption Type 2: Unique object and attribute.
|
|
filter_objs = copy.deepcopy(scene['objects'])
|
|
# each hypothesis is (object, attribute) pair
|
|
hypotheses = []
|
|
for ii, obj in enumerate(filter_objs):
|
|
# get unique set of attributes
|
|
uniq_attrs = [ii for ii in gvars.METAINFO['attributes']
|
|
if attr_counts[(ii, obj[ii])] == 1]
|
|
# for each, add it to hypothesis
|
|
for attr in uniq_attrs:
|
|
hypotheses.append((obj, attr))
|
|
caption_hypotheses['unique-obj'] = hypotheses
|
|
|
|
# Caption Type 3: Unique attribute count based caption.
|
|
# count unique object based constraint
|
|
# Each hypothesis is object collection.
|
|
caption_hypotheses['count-attr'] = [(attr_val, count)
|
|
for attr_val, count in attr_counts.items()
|
|
if count > 1]
|
|
|
|
# Caption Type 4: Relation between two objects.
|
|
# Out of the two, one has a unique attribute.
|
|
# find a pair of objects sharing a relation, unique
|
|
filter_objs = copy.deepcopy(scene['objects'])
|
|
n_objs = len(filter_objs)
|
|
|
|
# get a dict of unique attributes for each object
|
|
uniq_attr = [[] for ii in range(n_objs)]
|
|
non_uniq_attr = [[] for ii in range(n_objs)]
|
|
for ind, obj in enumerate(filter_objs):
|
|
uniq_attr[ind] = [attr for attr in gvars.METAINFO['attributes']
|
|
if attr_counts[(attr, obj[attr])] == 1]
|
|
non_uniq_attr[ind] = [attr for attr in gvars.METAINFO['attributes']
|
|
if attr_counts[(attr, obj[attr])] > 1]
|
|
uniqueness = [len(ii) > 0 for ii in uniq_attr]
|
|
|
|
# Hypothesis is a uniq object and non-unique obj2 sharing relation R
|
|
# global ordering for uniqueness
|
|
hypotheses = []
|
|
for rel, order in scene['relationships'].items():
|
|
num_rel = [(ii, len(order[ii])) for ii in range(n_objs)]
|
|
num_rel = sorted(num_rel, key=lambda x: x[1], reverse=True)
|
|
# take only the ids
|
|
num_rel = [ii[0] for ii in num_rel]
|
|
|
|
for index, obj_id in enumerate(num_rel[:-1]):
|
|
next_obj_id = num_rel[index + 1]
|
|
# if unique, check if the next one has non-unique attributes
|
|
if uniqueness[obj_id]:
|
|
if len(non_uniq_attr[next_obj_id]) > 0:
|
|
obj1 = (obj_id, random.choice(uniq_attr[obj_id]))
|
|
obj2 = (next_obj_id, random.choice(non_uniq_attr[next_obj_id]))
|
|
hypotheses.append((obj1, rel, obj2))
|
|
# if not unique, check if the next one has unique attributes
|
|
else:
|
|
if len(uniq_attr[next_obj_id]) > 0:
|
|
obj1 = (obj_id, random.choice(non_uniq_attr[obj_id]))
|
|
obj2 = (next_obj_id, random.choice(uniq_attr[next_obj_id]))
|
|
hypotheses.append((obj1, rel, obj2))
|
|
caption_hypotheses['obj-relation'] = hypotheses
|
|
sample_captions = sample_from_hypotheses(
|
|
caption_hypotheses, scene, templates)
|
|
return sample_captions
|
|
|
|
|
|
def question(scene, dialog, template):
|
|
"""Constraints question generation.
|
|
Inputs:
|
|
scene:Partial scene graphs on CLEVR images with generated captions
|
|
template: List of question templates to use
|
|
Output:
|
|
list of object groups
|
|
"""
|
|
|
|
ques_round = len(dialog['graph']['history']) - 1
|
|
graph = dialog['graph']
|
|
|
|
# check for constraints and answer question
|
|
if 'group' in template['label']:
|
|
groups = []
|
|
# Pick a group hypothesis
|
|
for ii in graph['history']:
|
|
if 'count' in ii or len(ii['objects']) == 0:
|
|
groups.append(ii)
|
|
|
|
if template['label'] == 'count-all':
|
|
# Preliminary checks:
|
|
# (A) count-all cannot follow count-all, count-other
|
|
for prev_history in graph['history'][1:]:
|
|
if prev_history['template'] in ['count-all', 'count-other']:
|
|
return []
|
|
|
|
# create object group
|
|
obj_group = []
|
|
new_obj = {'required': [], 'optional': []}
|
|
for obj_id, ii in enumerate(scene['objects']):
|
|
obj_copy = copy.deepcopy(new_obj)
|
|
obj_copy['id'] = ii['id']
|
|
obj_group.append(obj_copy)
|
|
|
|
# create graph item
|
|
graph_item = {'round': ques_round + 1,
|
|
'objects': copy.deepcopy(obj_group),
|
|
'template': template['label'],
|
|
'mergeable': True, 'count': len(obj_group)}
|
|
# clean graph item
|
|
graph_item = clean_graph_item(graph_item)
|
|
# no constraints, count the number of objects in true scene
|
|
return [{'answer': len(obj_group), 'group_id': ques_round + 1,
|
|
'objects': [], 'graph': graph_item}]
|
|
|
|
elif (template['label'] == 'count-other' or
|
|
template['label'] == 'exist-other'):
|
|
# preliminary checks:
|
|
# (A) exist-other cannot follow exist-other, count-all, count-other
|
|
# (B) count-other cannot follow count-all, count-other
|
|
for prev_history in graph['history'][1:]:
|
|
if prev_history['template'] in ['count-all', 'count-other']:
|
|
return []
|
|
|
|
if (prev_history['template'] == 'exist-other' and
|
|
template['label'] == 'exist-other'):
|
|
return []
|
|
|
|
# get a list of all objects we know
|
|
known_ids = [jj['id'] for ii in graph['history'] for jj in ii['objects']]
|
|
known_ids = list(set(known_ids))
|
|
n_objs = len(scene['objects'])
|
|
difference = n_objs - len(known_ids)
|
|
diff_ids = [ii for ii in range(n_objs) if ii not in known_ids]
|
|
|
|
# create empty objects for these
|
|
obj_group = [{'id': ii} for ii in diff_ids]
|
|
|
|
# create graph item
|
|
graph_item = {'round': ques_round + 1, 'objects': obj_group,
|
|
'template': template['label'], 'mergeable': False}
|
|
|
|
if 'count' in template['label']:
|
|
graph_item['count'] = difference
|
|
graph_item['mergeable'] = True # merge if count is known
|
|
answer = difference
|
|
elif 'exist' in template['label']:
|
|
# If heads (> 0.5) -- difference > 0
|
|
if random.random() > 0.5:
|
|
if difference > 0:
|
|
answer = 'yes'
|
|
else:
|
|
return []
|
|
else:
|
|
if difference == 0:
|
|
answer = 'no'
|
|
else:
|
|
return []
|
|
|
|
# no constraints, count the number of objects in true scene
|
|
return [{'answer': answer, 'group_id': ques_round + 1,
|
|
'objects': [], 'graph': graph_item}]
|
|
|
|
elif template['label'] == 'count-all-group':
|
|
# we need a group in the previous round
|
|
prev_group = graph['history'][-1]
|
|
prev_label = prev_group['template']
|
|
if not (len(prev_group['objects']) > 1 and
|
|
'count' not in prev_group and
|
|
'obj-relation' not in prev_label):
|
|
return []
|
|
|
|
# check if count is not given before
|
|
attrs = [ii for ii in gvars.METAINFO['attributes'] if ii in prev_group]
|
|
count = 0
|
|
for obj in prev_group['objects']:
|
|
count += all([obj[ii] == prev_group['objects'][0][ii] for ii in attrs])
|
|
|
|
# create object group
|
|
obj_group = []
|
|
new_obj = {'required': [], 'optional': []}
|
|
for obj_id, ii in enumerate(scene['objects']):
|
|
obj_copy = copy.deepcopy(new_obj)
|
|
obj_copy['id'] = ii['id']
|
|
obj_group.append(obj_copy)
|
|
|
|
# create graph item
|
|
graph_item = {'round': ques_round + 1, 'objects': copy.deepcopy(obj_group),
|
|
'template': template['label'],
|
|
'mergeable': True, 'count': count}
|
|
# clean graph item
|
|
graph_item = clean_graph_item(graph_item)
|
|
# no constraints, count the number of objects in true scene
|
|
return [{'answer': count, 'group_id': ques_round + 1,
|
|
'objects': [], 'graph': graph_item}]
|
|
|
|
elif ('count-obj-exclude' in template['label'] or
|
|
'exist-obj-exclude' in template['label']):
|
|
# placeholder for object description, see below
|
|
obj_desc = None
|
|
prev_history = graph['history'][-1]
|
|
scene_counts = get_attribute_counts_for_objects(scene)
|
|
|
|
if 'imm' in template['label']:
|
|
# we need an immediate group in the previous round
|
|
if apply_immediate(prev_history):
|
|
focus_id = prev_history['objects'][0]['id']
|
|
else:
|
|
return []
|
|
|
|
elif 'early' in template['label']:
|
|
# search through history for an object with unique attribute
|
|
attr_counts = get_known_attribute_counts(graph)
|
|
# get attributes with just one count
|
|
single_count = [ii for ii, count in attr_counts.items() if count == 1]
|
|
# remove attributes that point to objects in the previous round
|
|
# TODO: re-think this again
|
|
obj_ids = get_unique_attribute_objects(graph, single_count)
|
|
prev_history_obj_ids = [ii['id'] for ii in prev_history['objects']]
|
|
single_count = [ii for ii in single_count if
|
|
obj_ids[ii] not in prev_history_obj_ids]
|
|
|
|
if len(single_count) == 0:
|
|
return []
|
|
|
|
# give preference to attributes with multiple counts in scene graph
|
|
#scene_counts = get_attribute_counts_for_objects(scene)
|
|
ambiguous_attrs = [ii for ii in single_count if scene_counts[ii] > 1]
|
|
if len(ambiguous_attrs) > 0:
|
|
focus_attr = random.choice(ambiguous_attrs)
|
|
else:
|
|
focus_attr = random.choice(single_count)
|
|
focus_id = obj_ids[focus_attr]
|
|
|
|
# unique object description
|
|
obj_desc = {'required': [focus_attr[0]], 'optional': [],
|
|
focus_attr[0]: focus_attr[1]}
|
|
|
|
# get the known attributes for the current object
|
|
focus_obj = graph['objects'][focus_id]
|
|
known_attrs = [attr for attr in gvars.METAINFO['attributes']
|
|
if attr in focus_obj and
|
|
'%s_exclude_count' % attr not in focus_obj]
|
|
|
|
# for count: only if existence if True, else count it trivially zero
|
|
if 'count' in template['label']:
|
|
for attr in known_attrs[::-1]:
|
|
if not focus_obj.get('%s_exclude_exist' % attr, True):
|
|
known_attrs.remove(attr)
|
|
# for exist: get relations without exist before
|
|
elif 'exist' in template['label']:
|
|
known_attrs = [attr for attr in known_attrs
|
|
if '%s_exclude_exist' % attr not in focus_obj]
|
|
|
|
# select an attribute
|
|
if len(known_attrs) == 0:
|
|
return[]
|
|
|
|
# split this into zero and non-zero
|
|
if 'exist' in template['label']:
|
|
focus_attrs = [(ii, scene['objects'][focus_id][ii])
|
|
for ii in known_attrs]
|
|
zero_count = [ii for ii in focus_attrs if scene_counts[ii] == 1]
|
|
nonzero_count = [ii for ii in focus_attrs if scene_counts[ii] > 1]
|
|
|
|
if random.random() > 0.5:
|
|
if len(zero_count) > 0:
|
|
attr = random.choice(zero_count)[0]
|
|
else:
|
|
return []
|
|
else:
|
|
if len(nonzero_count) > 0:
|
|
attr = random.choice(nonzero_count)[0]
|
|
else:
|
|
return []
|
|
else:
|
|
attr = random.choice(known_attrs)
|
|
|
|
# create the object group
|
|
obj_group = []
|
|
new_obj = {'required': ['attribute'], 'optional': []}
|
|
for obj in scene['objects']:
|
|
# add if same attribute value and not focus object
|
|
if obj[attr] == focus_obj[attr] and obj['id'] != focus_id:
|
|
obj_copy = copy.deepcopy(new_obj)
|
|
obj_copy['id'] = obj['id']
|
|
obj_copy[attr] = focus_obj[attr]
|
|
obj_group.append(obj_copy)
|
|
answer = len(obj_group)
|
|
|
|
ref_obj = copy.deepcopy(new_obj)
|
|
ref_obj['id'] = focus_id
|
|
ref_obj['volatile'] = True
|
|
if 'exist' in template['label']:
|
|
answer = 'yes' if answer > 0 else 'no'
|
|
ref_obj['%s_exclude_exist' % attr] = answer
|
|
elif 'count' in template['label']:
|
|
ref_obj['%s_exclude_count' % attr] = answer
|
|
obj_group.append(ref_obj)
|
|
|
|
graph_item = {'round': ques_round+1, 'objects': copy.deepcopy(obj_group),
|
|
'template': template['label'], 'mergeable': True,
|
|
'focus_id': focus_id, 'focus_desc': obj_desc}
|
|
if 'count' in template['label']:
|
|
graph_item['count'] = answer
|
|
graph_item = clean_graph_item(graph_item)
|
|
|
|
ref_obj['attribute'] = attr
|
|
return [{'answer': answer, 'group_id': ques_round + 1,
|
|
'required': [], 'optional': [],
|
|
'objects': [ref_obj, obj_desc], 'graph': graph_item}]
|
|
|
|
elif ('count-obj-rel' in template['label'] or
|
|
'exist-obj-rel' in template['label']):
|
|
# placeholder for object description, see below
|
|
obj_desc = None
|
|
prev_history = graph['history'][-1]
|
|
|
|
# we need a single object in the previous round
|
|
if 'imm2' in template['label']:
|
|
# we need a obj-rel-imm in previous label, same as the current one
|
|
prev_label = prev_history['template']
|
|
cur_label = template['label']
|
|
if 'obj-rel-imm' not in prev_label or cur_label[:5] != prev_label[:5]:
|
|
return []
|
|
else:
|
|
focus_id = prev_history['focus_id']
|
|
|
|
elif 'imm' in template['label']:
|
|
# we need an immediate group in the previous round
|
|
if apply_immediate(prev_history):
|
|
focus_id = prev_history['objects'][0]['id']
|
|
else:
|
|
return []
|
|
|
|
elif 'early' in template['label']:
|
|
# search through history for an object with unique attribute
|
|
attr_counts = get_known_attribute_counts(graph)
|
|
|
|
# get attributes with just one count
|
|
single_count = [ii for ii, count in attr_counts.items() if count == 1]
|
|
# remove attributes that point to objects in the previous round
|
|
# TODO: re-think this again
|
|
obj_ids = get_unique_attribute_objects(graph, single_count)
|
|
prev_history_obj_ids = [ii['id'] for ii in prev_history['objects']]
|
|
single_count = [ii for ii in single_count if
|
|
obj_ids[ii] not in prev_history_obj_ids]
|
|
|
|
if len(single_count) == 0:
|
|
return []
|
|
focus_attr = random.choice(single_count)
|
|
for focus_id, obj in graph['objects'].items():
|
|
if obj.get(focus_attr[0], None) == focus_attr[1]:
|
|
break
|
|
|
|
# unique object description
|
|
obj_desc = {'required': [focus_attr[0]], 'optional': [],
|
|
focus_attr[0]: focus_attr[1]}
|
|
|
|
# get relations with unknown counts
|
|
unknown_rels = [rel for rel in gvars.METAINFO['relations']
|
|
if '%s_count' % rel not in graph['objects'][focus_id]]
|
|
# for count: only if existence if True, else count it trivially zero
|
|
if 'count' in template['label']:
|
|
for ii in unknown_rels[::-1]:
|
|
if not graph['objects'][focus_id].get('%s_exist' % ii, True):
|
|
unknown_rels.remove(ii)
|
|
|
|
# for exist: get relations without exist before
|
|
elif 'exist' in template['label']:
|
|
unknown_rels = [rel for rel in unknown_rels
|
|
if '%s_exist' % rel not in graph['objects'][focus_id]]
|
|
|
|
# select an object with some known objects
|
|
if len(unknown_rels) == 0:
|
|
return []
|
|
|
|
# pick between yes/no for exist questions, 50% of times
|
|
if 'exist' in template['label']:
|
|
zero_count = [ii for ii in unknown_rels
|
|
if len(scene['relationships'][ii][focus_id]) == 0]
|
|
nonzero_count = [ii for ii in unknown_rels
|
|
if len(scene['relationships'][ii][focus_id]) > 0]
|
|
|
|
if random.random() > 0.5:
|
|
if len(zero_count) > 0:
|
|
rel = random.choice(zero_count)
|
|
else:
|
|
return []
|
|
else:
|
|
if len(nonzero_count) > 0:
|
|
rel = random.choice(nonzero_count)
|
|
else:
|
|
return []
|
|
else:
|
|
rel = random.choice(unknown_rels)
|
|
|
|
# create the object group
|
|
obj_group = []
|
|
new_obj = {'required': ['relation'], 'optional': []}
|
|
obj_pool = scene['relationships'][rel][focus_id]
|
|
for obj_id in obj_pool:
|
|
obj_copy = copy.deepcopy(new_obj)
|
|
obj_copy['id'] = obj_id
|
|
obj_group.append(obj_copy)
|
|
answer = len(obj_pool)
|
|
|
|
ref_obj = copy.deepcopy(new_obj)
|
|
ref_obj['id'] = focus_id
|
|
ref_obj['volatile'] = True
|
|
if 'exist' in template['label']:
|
|
answer = 'yes' if answer > 0 else 'no'
|
|
ref_obj['%s_exist' % rel] = answer
|
|
elif 'count' in template['label']:
|
|
ref_obj['%s_count' % rel] = answer
|
|
obj_group.append(ref_obj)
|
|
|
|
graph_item = {'round': ques_round+1, 'objects': copy.deepcopy(obj_group),
|
|
'template': template['label'], 'mergeable': True,
|
|
'focus_id': focus_id, 'focus_desc': obj_desc}
|
|
if 'count' in template['label']:
|
|
graph_item['count'] = answer
|
|
graph_item = clean_graph_item(graph_item)
|
|
|
|
#ref_obj['relation'] = rel
|
|
# add attribute as argument
|
|
return [{'answer': answer, 'group_id': ques_round + 1,
|
|
'required': [], 'optional': [], 'relation': rel,
|
|
'objects': [ref_obj, obj_desc], 'graph': graph_item}]
|
|
|
|
elif ('count-attribute' in template['label'] or
|
|
'exist-attribute' in template['label']):
|
|
if 'group' in template['label']:
|
|
# we need an immediate group in the previous round
|
|
prev_history = graph['history'][-1]
|
|
prev_label = prev_history['template']
|
|
|
|
# if exist: > 0 is good, else > 1 is needed
|
|
min_count = 0 if 'exist' in prev_label else 1
|
|
if (len(prev_history['objects']) > min_count and
|
|
prev_history['mergeable'] and
|
|
'obj-relation' not in prev_label):
|
|
obj_pool = graph['history'][-1]['objects']
|
|
else:
|
|
return []
|
|
else:
|
|
obj_pool = scene['objects']
|
|
|
|
# get counts for attributes, and sample evenly with 0 and other numbers
|
|
counts = get_attribute_counts_for_objects(scene, obj_pool)
|
|
|
|
# if exist, choose between zero and others wiht 0.5 probability
|
|
zero_prob = 0.5 if 'exist' in template['label'] else 0.7
|
|
if random.random() > zero_prob:
|
|
pool = [ii for ii in counts if counts[ii] == 0]
|
|
else:
|
|
pool = [ii for ii in counts if counts[ii] != 0]
|
|
|
|
# check if count is already known
|
|
attr_pool = filter_attributes_with_known_counts(graph, pool)
|
|
|
|
# for exist: get known attributes and remove them
|
|
if 'exist' in template['label']:
|
|
known_attr = get_known_attributes(graph)
|
|
attr_pool = [ii for ii in attr_pool if ii not in known_attr]
|
|
|
|
# if non-empty, sample it
|
|
if len(attr_pool) == 0:
|
|
return []
|
|
|
|
attr, value = random.choice(attr_pool)
|
|
# add a hypothesi, and return the answer
|
|
count = 0
|
|
obj_group = []
|
|
new_obj = {attr: value, 'required': [attr], 'optional': []}
|
|
for index, obj in enumerate(obj_pool):
|
|
if scene['objects'][obj['id']][attr] == value:
|
|
obj_copy = copy.deepcopy(new_obj)
|
|
obj_copy['id'] = obj['id']
|
|
obj_group.append(obj_copy)
|
|
count += 1
|
|
|
|
graph_item = {'round': ques_round + 1, 'objects': copy.deepcopy(obj_group),
|
|
'template': template['label'], 'mergeable': True, attr: value}
|
|
|
|
if 'count' in template['label']:
|
|
graph_item['count'] = count
|
|
answer = count
|
|
elif 'exist' in template['label']:
|
|
answer = 'yes' if count > 0 else 'no'
|
|
# Clean graph item.
|
|
graph_item = clean_graph_item(graph_item)
|
|
if count == 0:
|
|
# Fake object group, to serve for arguments.
|
|
obj_group = [{attr: value, 'required': [attr], 'optional': []}]
|
|
|
|
return [{'answer': answer, 'group_id': ques_round + 1,
|
|
'required': [attr], 'optional': [],
|
|
'count': 9999, 'objects': obj_group, 'graph': graph_item}]
|
|
|
|
elif 'seek-attr-rel' in template['label']:
|
|
# Placeholder for object description, see below.
|
|
obj_desc = None
|
|
prev_history = graph['history'][-1]
|
|
|
|
if 'imm' in template['label']:
|
|
# we need an immediate group in the previous round
|
|
if apply_immediate(prev_history):
|
|
focus_id = prev_history['objects'][0]['id']
|
|
else:
|
|
return []
|
|
|
|
elif 'early' in template['label']:
|
|
# search through history for an object with unique attribute
|
|
attr_counts = get_known_attribute_counts(graph)
|
|
|
|
# get attributes with just one count
|
|
single_count = [ii for ii, count in attr_counts.items() if count == 1]
|
|
# remove attributes that point to objects in the previous round
|
|
# TODO: re-think this again
|
|
obj_ids = get_unique_attribute_objects(graph, single_count)
|
|
prev_history_obj_ids = [ii['id'] for ii in prev_history['objects']]
|
|
single_count = [ii for ii in single_count if
|
|
obj_ids[ii] not in prev_history_obj_ids]
|
|
if len(single_count) == 0:
|
|
return []
|
|
|
|
# give preference to attributes with multiple counts in scene graph
|
|
scene_counts = get_attribute_counts_for_objects(scene)
|
|
ambiguous_attrs = [ii for ii in single_count if scene_counts[ii] > 1]
|
|
if len(ambiguous_attrs) > 0:
|
|
focus_attr = random.choice(ambiguous_attrs)
|
|
else:
|
|
focus_attr = random.choice(single_count)
|
|
focus_id = obj_ids[focus_attr]
|
|
|
|
# unique object description
|
|
obj_desc = {'required': [focus_attr[0]], 'optional': [],
|
|
focus_attr[0]: focus_attr[1]}
|
|
|
|
# for each relation, get the object, sample an attribute, and sample
|
|
hypotheses = []
|
|
for rel in gvars.METAINFO['relations']:
|
|
gt_relations = scene['relationships'][rel]
|
|
objs = [(ii, len(gt_relations[ii])) for ii in gt_relations[focus_id]]
|
|
objs = sorted(objs, key=lambda x: x[1], reverse=True)
|
|
if len(objs) == 0:
|
|
# add a null hypotheses
|
|
# check if the object is known to be extreme
|
|
if ('%s_count' % rel not in graph['objects'][focus_id] and
|
|
'%s_exist' % rel not in graph['objects'][focus_id]):
|
|
random_attr = random.choice(gvars.METAINFO['attributes'])
|
|
hypotheses.append((None, rel, random_attr))
|
|
continue
|
|
|
|
closest_obj = objs[0][0]
|
|
# check what attributes are known/unknown
|
|
known_info = graph['objects'].get(closest_obj, {})
|
|
for attr in gvars.METAINFO['attributes']:
|
|
if attr not in known_info:
|
|
hypotheses.append((closest_obj, rel, attr))
|
|
|
|
if len(hypotheses) == 0:
|
|
return []
|
|
sample_id, rel, attr = random.choice(hypotheses)
|
|
# add the new attribute to object
|
|
new_obj = {'required': ['attribute', 'relation'],
|
|
'optional': [], 'id': sample_id}
|
|
|
|
if sample_id is not None:
|
|
answer = scene['objects'][sample_id][attr]
|
|
else:
|
|
answer = 'none'
|
|
new_obj[attr] = answer
|
|
|
|
graph_item = {'round': ques_round+1, 'objects': [copy.deepcopy(new_obj)],
|
|
'template': template['label'], 'mergeable': True,
|
|
'focus_id': focus_id, 'focus_desc': obj_desc}
|
|
# remove objects if none
|
|
if sample_id is None:
|
|
graph_item['objects'] = []
|
|
graph_item = clean_graph_item(graph_item)
|
|
|
|
# Add attribute as argument.
|
|
new_obj['attribute'] = attr
|
|
return [{'answer': new_obj[attr], 'group_id': ques_round + 1,
|
|
'required': [], 'optional': [], 'relation': rel,
|
|
'objects': [new_obj, obj_desc], 'graph': graph_item}]
|
|
|
|
elif 'seek-attr' in template['label']:
|
|
# placeholder for object description, see below
|
|
obj_desc = None
|
|
prev_history = graph['history'][-1]
|
|
prev_label = prev_history['template']
|
|
implicit_attr = None
|
|
|
|
# we need a single object in the previous round
|
|
if 'imm2' in template['label']:
|
|
# we need a seek-attr-imm/seek-attr-rel-imm in previous label
|
|
if ('seek-attr-imm' not in prev_label and
|
|
'seek-attr-rel-imm' not in prev_label):
|
|
return []
|
|
elif len(prev_history['objects']) == 0:
|
|
return []
|
|
else:
|
|
focus_id = prev_history['objects'][0]['id']
|
|
|
|
elif 'imm' in template['label']:
|
|
# we need an immediate group in the previous round
|
|
if apply_immediate(prev_history):
|
|
focus_id = prev_history['objects'][0]['id']
|
|
else:
|
|
return []
|
|
|
|
elif 'sim' in template['label']:
|
|
if 'seek-attr-imm' not in prev_label:
|
|
return[]
|
|
else:
|
|
prev_obj = prev_history['objects'][0]
|
|
focus_id = prev_obj['id']
|
|
attr = [ii for ii in gvars.METAINFO['attributes'] if ii in prev_obj]
|
|
assert len(attr) == 1, 'Something wrong in previous history!'
|
|
implicit_attr = attr[0]
|
|
|
|
if 'early' in template['label']:
|
|
# search through history for an object with unique attribute
|
|
attr_counts = get_known_attribute_counts(graph)
|
|
|
|
# get attributes with just one count
|
|
single_count = [ii for ii, count in attr_counts.items() if count == 1]
|
|
# remove attributes that point to objects in the previous round
|
|
# TODO: re-think this again
|
|
obj_ids = get_unique_attribute_objects(graph, single_count)
|
|
prev_history_obj_ids = [ii['id'] for ii in prev_history['objects']]
|
|
single_count = [ii for ii in single_count if
|
|
obj_ids[ii] not in prev_history_obj_ids]
|
|
|
|
# if there is an attribute, eliminate those options
|
|
if implicit_attr is not None:
|
|
single_count = [ii for ii in single_count if ii[0] != implicit_attr]
|
|
obj_ids = get_unique_attribute_objects(graph, single_count)
|
|
|
|
# again rule out objects whose implicit_attr is known
|
|
single_count = [ii for ii in single_count
|
|
if implicit_attr not in graph['objects'][obj_ids[ii]]]
|
|
|
|
if len(single_count) == 0:
|
|
return []
|
|
|
|
# give preference to attributes with multiple counts in scene graph
|
|
scene_counts = get_attribute_counts_for_objects(scene)
|
|
ambiguous_attrs = [ii for ii in single_count if scene_counts[ii] > 1]
|
|
if len(ambiguous_attrs) > 0:
|
|
focus_attr = random.choice(ambiguous_attrs)
|
|
else:
|
|
focus_attr = random.choice(single_count)
|
|
focus_id = get_unique_attribute_objects(graph, [focus_attr])[focus_attr]
|
|
|
|
# unique object description
|
|
obj_desc = {'required': [focus_attr[0]], 'optional': [],
|
|
focus_attr[0]: focus_attr[1]}
|
|
|
|
# get unknown attributes, randomly sample one
|
|
if implicit_attr is None:
|
|
unknown_attrs = [attr for attr in gvars.METAINFO['attributes']
|
|
if attr not in graph['objects'][focus_id]]
|
|
|
|
# TODO: select an object with some known objects
|
|
if len(unknown_attrs) == 0:
|
|
return []
|
|
attr = random.choice(unknown_attrs)
|
|
else:
|
|
attr = implicit_attr
|
|
|
|
# add the new attribute to object
|
|
new_obj = {'required': ['attribute'], 'optional': [], 'id': focus_id}
|
|
if 'sim' in template['label']:
|
|
new_obj['required'] = []
|
|
new_obj[attr] = scene['objects'][focus_id][attr]
|
|
|
|
graph_item = {'round': ques_round+1, 'objects': [copy.deepcopy(new_obj)],
|
|
'template': template['label'], 'mergeable': True,
|
|
'focus_id': focus_id, 'focus_desc': obj_desc}
|
|
graph_item = clean_graph_item(graph_item)
|
|
|
|
# add attribute as argument
|
|
new_obj['attribute'] = attr
|
|
return [{'answer': new_obj[attr], 'group_id': ques_round + 1,
|
|
'required': [], 'optional': [],
|
|
'objects': [new_obj, obj_desc], 'graph': graph_item}]
|
|
return []
|
|
|
|
|
|
def sample_from_hypotheses(caption_hypotheses, scene, cap_templates):
|
|
"""Samples from caption hypotheses given the scene and caption templates.
|
|
Args:
|
|
caption_hypotheses: List of hypotheses for objects/object pairs
|
|
scene: CLEVR image scene graph
|
|
cap_templates: List of caption templates to sample captions
|
|
Returns:
|
|
obj_groups: List of object groups and corresponding sampled captions
|
|
"""
|
|
|
|
obj_groups = []
|
|
|
|
# Caption Type 1: Extreme location.
|
|
hypotheses = caption_hypotheses['extreme-loc']
|
|
if len(hypotheses) > 0:
|
|
# extreme location hypotheses
|
|
extreme_type, focus_obj = random.choice(hypotheses)
|
|
# sample optional attributes
|
|
obj_attrs = [attr for attr in gvars.METAINFO['attributes']
|
|
if attr in focus_obj]
|
|
focus_attr = random.choice(obj_attrs)
|
|
optional_attrs = [ii for ii in obj_attrs if ii != focus_attr]
|
|
sampled_attrs = sample_optional_tags(optional_attrs,
|
|
gvars.METAINFO['probabilities'])
|
|
|
|
# add additional attributes
|
|
req_attrs = sampled_attrs + [focus_attr]
|
|
filter_obj = {attr: val for attr, val in focus_obj.items()
|
|
if attr in req_attrs}
|
|
filter_obj['required'] = req_attrs
|
|
filter_obj['optional'] = req_attrs
|
|
filter_obj['id'] = focus_obj['id']
|
|
obj_group = {'required': req_attrs, 'optional': [], 'group_id': 0,
|
|
'objects': [filter_obj]}
|
|
|
|
# also create a clean graph object
|
|
graph_item = copy.deepcopy(obj_group)
|
|
graph_item = clean_graph_item(graph_item)
|
|
graph_item['mergeable'] = True
|
|
graph_item['objects'][0]['%s_count' % extreme_type] = 0
|
|
graph_item['objects'][0]['%s_exist' % extreme_type] = False
|
|
graph_item['template'] = 'extreme-%s' % extreme_type
|
|
obj_group['graph'] = graph_item
|
|
obj_groups.append([obj_group])
|
|
|
|
# Caption Type 2: Unique object.
|
|
hypotheses = caption_hypotheses['unique-obj']
|
|
if len(hypotheses) > 0:
|
|
# sample one at random, and create the graph item
|
|
focus_obj, focus_attr = random.choice(hypotheses)
|
|
# sample optional attributes
|
|
optional_attrs = [ii for ii in gvars.METAINFO['attributes']
|
|
if ii != focus_attr]
|
|
sampled_attrs = sample_optional_tags(optional_attrs,
|
|
gvars.METAINFO['probabilities'])
|
|
|
|
# add additional attributes
|
|
req_attrs = sampled_attrs + [focus_attr]
|
|
filter_obj = {attr: val for attr, val in focus_obj.items()
|
|
if attr in req_attrs}
|
|
filter_obj['required'] = req_attrs
|
|
filter_obj['optional'] = req_attrs
|
|
filter_obj['id'] = focus_obj['id']
|
|
obj_group = {'required': req_attrs, 'optional': [], 'group_id': 0,
|
|
'objects': [filter_obj]}
|
|
|
|
# also create a clean graph object
|
|
graph_item = copy.deepcopy(obj_group)
|
|
graph_item = clean_graph_item(graph_item)
|
|
graph_item['mergeable'] = True
|
|
graph_item['objects'][0]['unique'] = True
|
|
graph_item['template'] = 'unique-obj'
|
|
obj_group['graph'] = graph_item
|
|
obj_groups.append([obj_group])
|
|
|
|
# Caption Type 3: Unique attribute count based caption.
|
|
hypotheses = caption_hypotheses['count-attr']
|
|
if len(hypotheses) > 0:
|
|
# Randomly sample one hypothesis and one template.
|
|
(attr, value), count = random.choice(hypotheses)
|
|
# Segregate counting templates.
|
|
count_templates = [ii for ii in cap_templates if 'count' in ii['type']]
|
|
template = random.choice(count_templates)
|
|
obj_group = {'group_id': 0, 'count': count, attr: value,
|
|
'optional': [], 'required': [], 'objects': []}
|
|
|
|
# get a list of objects which are part of this collection
|
|
for ii, obj in enumerate(scene['objects']):
|
|
if obj[attr] == value:
|
|
new_obj = {'id': obj['id'], attr: value}
|
|
new_obj['required'] = [attr]
|
|
new_obj['optional'] = []
|
|
obj_group['objects'].append(new_obj)
|
|
|
|
if 'no' in template['label']:
|
|
# Count is not mentioned.
|
|
del obj_group['count']
|
|
graph_item = copy.deepcopy(obj_group)
|
|
graph_item['mergeable'] = False
|
|
else:
|
|
# Count is mentioned.
|
|
for index, ii in enumerate(obj_group['objects']):
|
|
obj_group['objects'][index]['required'].append('count')
|
|
graph_item = copy.deepcopy(obj_group)
|
|
graph_item['mergeable'] = True
|
|
|
|
# clean up graph item
|
|
graph_item['template'] = template['label']
|
|
graph_item = clean_graph_item(graph_item)
|
|
obj_group['graph'] = graph_item
|
|
obj_group['use_plural'] = True
|
|
obj_groups.append([obj_group])
|
|
|
|
# Caption Type 4: Relation between two objects (one of them is unique).
|
|
hypotheses = caption_hypotheses['obj-relation']
|
|
if len(hypotheses) > 0:
|
|
(obj_id1, attr1), rel, (obj_id2, attr2) = random.choice(hypotheses)
|
|
obj_group = {'group_id': 0, 'relation': rel}
|
|
|
|
# create object dictionaries
|
|
obj1 = {'optional': [], 'required': [attr1], 'id': obj_id1,
|
|
attr1: scene['objects'][obj_id1][attr1]}
|
|
obj2 = {'optional': [], 'required': [attr2], 'id': obj_id2,
|
|
attr2: scene['objects'][obj_id2][attr2]}
|
|
obj_group['objects'] = [obj2, obj1]
|
|
|
|
# also create a clean graph object
|
|
graph_item = copy.deepcopy(obj_group)
|
|
graph_item = clean_graph_item(graph_item)
|
|
graph_item['mergeable'] = True
|
|
graph_item['template'] = 'obj-relation'
|
|
obj_group['graph'] = graph_item
|
|
obj_groups.append([obj_group])
|
|
return obj_groups
|
|
|
|
|
|
def get_known_attributes(graph):
|
|
"""Fetches a list of known attributes given the scene graph.
|
|
Args:
|
|
graph: Scene graph to check unique attributes from
|
|
Returns:
|
|
known_attrs: List of known attributes from the scene graph
|
|
"""
|
|
|
|
known_attrs = []
|
|
for obj_id, obj_info in graph['objects'].items():
|
|
# The attribute is unique already.
|
|
# if obj_info.get('unique', False): continue
|
|
for attr in gvars.METAINFO['attributes']:
|
|
if attr in obj_info:
|
|
known_attrs.append((attr, obj_info[attr]))
|
|
|
|
# also go over the groups
|
|
for ii in graph['history']:
|
|
# a group of objects, with unknown count
|
|
#if 'count' not in ii: continue
|
|
for attr in gvars.METAINFO['attributes']:
|
|
if attr in ii:
|
|
known_attrs.append((attr, ii[attr]))
|
|
known_attrs = list(set(known_attrs))
|
|
return known_attrs
|
|
|
|
|
|
def get_known_attribute_counts(graph):
|
|
"""Fetches a count of known attributes given the scene graph.
|
|
Calls get_known_attributes method internally.
|
|
Args:
|
|
graph: Scene graph to check unique attributes from
|
|
Returns:
|
|
counts: Count of known attributes from the scene graph
|
|
"""
|
|
|
|
known_attrs = get_known_attributes(graph)
|
|
# Go through objects and count.
|
|
counts = {ii: 0 for ii in known_attrs}
|
|
for _, obj in graph['objects'].items():
|
|
for attr, val in known_attrs:
|
|
if obj.get(attr, None) == val:
|
|
counts[(attr, val)] += 1
|
|
return counts
|
|
|
|
|
|
def filter_attributes_with_known_counts(graph, known_attrs):
|
|
"""Filters attributes whose counts are known, given the scene graph.
|
|
Args:
|
|
graph: Scene graph from the dialog generated so far
|
|
known_attrs: List of known attributes from the ground truth scene graph
|
|
Returns:
|
|
known_attrs: List of attributes with unknown counts removed inplace
|
|
"""
|
|
|
|
for attr, val in known_attrs[::-1]:
|
|
for ii in graph['history']:
|
|
# A group of objects, with unknown count.
|
|
if 'count' not in ii:
|
|
continue
|
|
# Count is absent.
|
|
if ii.get(attr, None) == val:
|
|
known_attrs.remove((attr, val))
|
|
return known_attrs
|
|
|
|
|
|
def clean_graph_item(graph_item):
|
|
"""Cleans up graph item (remove 'required' and 'optional' tags).
|
|
Args:
|
|
graph_item: Input graph item to be cleaned.
|
|
Returns:
|
|
clean_graph_item: Copy of the graph item after cleaning.
|
|
"""
|
|
|
|
clean_graph_item = copy.deepcopy(graph_item)
|
|
if 'optional' in clean_graph_item:
|
|
del clean_graph_item['optional']
|
|
if 'required' in clean_graph_item:
|
|
del clean_graph_item['required']
|
|
|
|
for index, ii in enumerate(clean_graph_item['objects']):
|
|
if 'optional' in ii:
|
|
del clean_graph_item['objects'][index]['optional']
|
|
if 'required' in ii:
|
|
del clean_graph_item['objects'][index]['required']
|
|
return clean_graph_item
|
|
|
|
|
|
def get_attribute_counts_for_objects(scene, objects=None):
|
|
"""Counts attributes for a given set of objects.
|
|
Args:
|
|
scene: Scene graph for the dialog generated so far
|
|
objects: List of objects. Default = None selects all objects
|
|
Returns:
|
|
counts: Counts for the attributes for attributes
|
|
"""
|
|
|
|
# Initialize the dictionary.
|
|
counts = {}
|
|
for attr, vals in gvars.METAINFO['values'].items():
|
|
for val in vals:
|
|
counts[(attr, val)] = 0
|
|
|
|
# Now count for each given object.
|
|
if objects is None:
|
|
objects = scene['objects']
|
|
for obj in objects:
|
|
for attr in gvars.METAINFO['attributes']:
|
|
key = (attr, scene['objects'][obj['id']][attr])
|
|
counts[key] = counts.get(key, 0) + 1
|
|
return counts
|
|
|
|
|
|
def get_unique_attribute_objects(graph, uniq_attrs):
|
|
"""Fetches objects from given scene graph with unique attributes.
|
|
Args:
|
|
graph: Scene graph constructed from the dialog generated so far
|
|
uniq_attrs: List of unique attributes to get attributes
|
|
Returns:
|
|
obj_ids: List of object ids with the unique attributes
|
|
"""
|
|
|
|
obj_ids = {}
|
|
for obj_id, obj in graph['objects'].items():
|
|
for attr, val in uniq_attrs:
|
|
if obj.get(attr, '') == val:
|
|
# At this point the key should not be present.
|
|
assert (attr, val) not in obj_ids, 'Attributes not unique!'
|
|
obj_ids[(attr, val)] = obj_id
|
|
return obj_ids
|
|
|
|
|
|
def sample_optional_tags(optional, sample_probs):
|
|
"""Samples additional tags depending on given sample probabilities.
|
|
Args:
|
|
optional: List of optional tags to sample from.
|
|
sample_probs: Probabilities of sampling 'n' tags.
|
|
Returns:
|
|
sampled: Sampled tags from the optional list
|
|
"""
|
|
|
|
sampled = []
|
|
if len(optional) > 0:
|
|
n_sample = np.random.choice([0, 1], 1, p=sample_probs[:2])[0]
|
|
n_sample = min(n_sample, len(optional))
|
|
sampled = random.sample(optional, n_sample)
|
|
return sampled
|