def recolor_exi_graph(exi_graph, rootmost_nodes): node_dict = ut.nx_node_dict(exi_graph) for node in exi_graph.nodes(): if node_dict[node]['root_specifiable']: node_dict[node]['color'] = [1, .7, .6] for node in rootmost_nodes: node_dict[node]['color'] = [1, 0, 0]
def is_same(infr, aid_pairs): if infr.ibs is not None: return infr.ibeis_is_same(aid_pairs) node_dict = ut.nx_node_dict(infr.graph) nid1 = [node_dict[n1]['orig_name_label'] for n1, n2 in aid_pairs] nid2 = [node_dict[n2]['orig_name_label'] for n1, n2 in aid_pairs] return np.equal(nid1, nid2)
def _get_truth(verif, edge): infr = verif.infr if edge in infr.edge_truth: return infr.edge_truth[edge] node_dict = ut.nx_node_dict(infr.graph) nid1 = node_dict[edge[0]]['orig_name_label'] nid2 = node_dict[edge[1]]['orig_name_label'] return POSTV if nid1 == nid2 else NEGTV
def get_edge_truth(infr, n1, n2): node_dict = ut.nx_node_dict(infr.graph) nid1 = node_dict[n1]['orig_name_label'] nid2 = node_dict[n2]['orig_name_label'] try: view1 = node_dict[n1]['viewpoint'] view2 = node_dict[n2]['viewpoint'] comparable = view1 in adjacent_views[view2] except KeyError: comparable = True # raise same = nid1 == nid2 if not comparable: return 2 else: return int(same)
def show_exi_graph(inputs, inter=None): """ CommandLine: python -m dtool.input_helpers TableInput.show_exi_graph --show Example: >>> # DISABLE_DOCTEST >>> from dtool.input_helpers import * # NOQA >>> from dtool.example_depcache2 import * # NOQA >>> depc = testdata_depc3() >>> import plottool as pt >>> # table = depc['smk_match'] >>> table = depc['neighbs'] >>> inputs = table.rootmost_inputs >>> print('inputs = %r' % (inputs,)) >>> from plottool.interactions import ExpandableInteraction >>> inter = ExpandableInteraction(nCols=1) >>> inputs.show_exi_graph(inter=inter) >>> # FIXME; Expanding inputs can overspecify inputs >>> #inputs = inputs.expand_input(2) >>> #print('inputs = %r' % (inputs,)) >>> #inputs.show_exi_graph(inter=inter) >>> #inputs = inputs.expand_input(1) >>> #inputs = inputs.expand_input(3) >>> #inputs = inputs.expand_input(2) >>> #inputs = inputs.expand_input(2) >>> #inputs = inputs.expand_input(1) >>> #print('inputs = %r' % (inputs,)) >>> #inputs.show_exi_graph(inter=inter) >>> inter.start() >>> ut.show_if_requested() """ import plottool as pt from plottool.interactions import ExpandableInteraction autostart = inter is None if inter is None: inter = ExpandableInteraction() tablename = inputs.table.tablename exi_graph = inputs.exi_graph.copy() recolor_exi_graph(exi_graph, inputs.exi_nodes()) # Add numbering to indicate the input order node_dict = ut.nx_node_dict(exi_graph) for count, rmi in enumerate(inputs.rmi_list, start=0): if rmi.ismulti: node_dict[rmi.node]['label'] += ' #%d*' % (count, ) else: node_dict[rmi.node]['label'] += ' #%d' % (count, ) plot_kw = {'fontname': 'Ubuntu'} #inter.append_plot( # ut.partial(pt.show_nx, G, title='Dependency Subgraph (%s)' % (tablename), **plot_kw)) inter.append_plot( ut.partial(pt.show_nx, exi_graph, title='Expanded Input (%s)' % (tablename, ), **plot_kw)) if autostart: inter.start() return inter
def make_expanded_input_graph(graph, target): """ Starting from the `target` property we trace all possible paths in the `graph` back to all sources. Args: graph (nx.DiMultiGraph): the dependency graph with a single source. target (str): a single target node in graph Notes: Each edge in the graph must have a `local_input_id` that defines the type of edge it is: (eg one-to-many, one-to-one, nwise/multi). # Step 1: Extracting the Relevant Subgraph We start by searching for all sources of the graph (we assume there is only one). Then we extract the subgraph defined by all edges between the sources and the target. We augment this graph with a dummy super source `s` and super sink `t`. This allows us to associate an edge with the real source and sink. # Step 2: Trace all paths from `s` to `t`. Create a set of all paths from the source to the sink and accumulate the `local_input_id` of each edge along the path. This will uniquely identify each path. We use a hack to condense the accumualated ids in order to display them nicely. # Step 3: Create the new `exi_graph` Using the traced paths with ids we construct a new graph representing expanded inputs. The nodes in the original graph will be copied for each unique path that passes through the node. We identify these nodes using the accumulated ids built along the edges in our path set. For each path starting from the target we add each node augmented with the accumulated ids on its output(?) edge. We also add the edges along these paths which results in the final `exi_graph`. # Step 4: Identify valid inputs candidates The purpose of this graph is to identify which inputs are needed to compute dependant properties. One valid set of inputs is all sources of the graph. However, sometimes it is preferable to specify a model that may have been trained from many inputs. Therefore any node with a one-to-many input edge may also be specified as an input. # Step 5: Identify root-most inputs The user will only specify one possible set of the inputs. We refer to this set as the "root-most" inputs. This is a set of candiate nodes such that all paths from the sink to the super source are blocked. We default to the set of inputs which results in the fewest dependency computations. However this is arbitary. The last step that is not represented here is to compute the order that the branches must be specified in when given to the depcache for a computation. Returns: nx.DiGraph: exi_graph: the expanded input graph Notes: All * nodes are defined to be distinct. TODO: To make a * node non-distinct it must be suffixed with an identifier. CommandLine: python -m dtool.input_helpers make_expanded_input_graph --show Example: >>> # ENABLE_DOCTEST >>> from dtool.input_helpers import * # NOQA >>> from dtool.example_depcache2 import * # NOQA >>> depc = testdata_depc3() >>> table = depc['smk_match'] >>> table = depc['vsone'] >>> graph = table.depc.explicit_graph.copy() >>> target = table.tablename >>> exi_graph = make_expanded_input_graph(graph, target) >>> x = list(exi_graph.nodes())[0] >>> print('x = %r' % (x,)) >>> ut.quit_if_noshow() >>> import plottool as pt >>> pt.show_nx(graph, fnum=1, pnum=(1, 2, 1)) >>> pt.show_nx(exi_graph, fnum=1, pnum=(1, 2, 2)) >>> ut.show_if_requested() """ # FIXME: this does not work correctly when # The nesting of non-1-to-1 dependencies is greater than 2 (I think) # algorithm for finding inputs does not work. # FIXME: two vocabs have the same edge id, they should be the same in the # Expanded Input Graph as well. Their accum_id needs to be changed. def condense_accum_ids(rinput_path_id): # Hack to condense and consolidate graph sources prev = None compressed = [] for item in rinput_path_id: if item == '1' and prev is not None: pass # done append ones elif item != prev: compressed.append(item) prev = item #if len(compressed) > 1 and compressed[0] in ['1', '*']: if len(compressed) > 1 and compressed[0] == '1': compressed = compressed[1:] compressed = tuple(compressed) return compressed BIG_HACK = True #BIG_HACK = False def condense_accum_ids_stars(rinput_path_id): # Hack to condense and consolidate graph sources rcompressed = [] has_star = False # Remove all but the final star (this is a really bad hack) for item in reversed(rinput_path_id): is_star = '*' in item if not (is_star and has_star): if not has_star: rcompressed.append(item) has_star = has_star or is_star compressed = tuple(rcompressed[::-1]) return compressed def accumulate_input_ids(edge_list): """ python -m dtool.example_depcache2 testdata_depc4 --show """ edge_data = ut.take_column(edge_list, 3) # We are accumulating local input ids toaccum_list_ = ut.dict_take_column(edge_data, 'local_input_id') if BIG_HACK and True: v_list = ut.take_column(edge_list, 1) # show the local_input_ids at the entire level pred_ids = ([[ x['local_input_id'] for x in list(graph.pred[node].values())[0].values() ] if len(graph.pred[node]) else [] for node in v_list]) toaccum_list = [ x + ':' + ';'.join(y) for x, y in zip(toaccum_list_, pred_ids) ] else: toaccum_list = toaccum_list_ # Default dumb accumulation accum_ids_ = ut.cumsum(zip(toaccum_list), tuple()) accum_ids = ut.lmap(condense_accum_ids, accum_ids_) if BIG_HACK: accum_ids = ut.lmap(condense_accum_ids_stars, accum_ids) accum_ids = [('t', ) + x for x in accum_ids] ut.dict_set_column(edge_data, 'accum_id', accum_ids) return accum_ids sources = list(ut.nx_source_nodes(graph)) print(sources) # assert len(sources) == 1, 'expected a unique source' source = sources[0] graph = graph.subgraph(ut.nx_all_nodes_between(graph, source, target)).copy() # Remove superfluous data ut.nx_delete_edge_attr( graph, [ 'edge_type', 'isnwise', 'nwise_idx', # 'parent_colx', 'ismulti' ]) # Make all '*' edges have distinct local_input_id's. # TODO: allow non-distinct suffixes count = ord('a') for edge in graph.edges(keys=True, data=True): dat = edge[3] if dat['local_input_id'] == '*': dat['local_input_id'] = '*' + chr(count) dat['taillabel'] = '*' + chr(count) count += 1 # Augment with dummy super source/sink nodes source_input = 'source_input' target_output = 'target_output' graph.add_edge(source_input, source, local_input_id='s', taillabel='1') graph.add_edge(target, target_output, local_input_id='t', taillabel='1') # Find all paths from the table to the source. paths_to_source = ut.all_multi_paths(graph, source_input, target_output, data=True) # Build expanded input graph # The inputs to this table can be derived from this graph. # The output is a new expanded input graph. exi_graph = nx.DiGraph() for path in paths_to_source: # Accumlate unique identifiers along the reversed path edge_list = ut.reverse_path_edges(path) accumulate_input_ids(edge_list) # A node's output(?) on this path determines its expanded branch id exi_nodes = [ ExiNode(v, BranchId(d['accum_id'], k, d.get('parent_colx', -1))) for u, v, k, d in edge_list[:-1] ] exi_node_to_label = { node: node[0] + '[' + ','.join([str(x) for x in node[1]]) + ']' for node in exi_nodes } exi_graph.add_nodes_from(exi_nodes) nx.set_node_attributes(exi_graph, name='label', values=exi_node_to_label) # Undo any accumulation ordering and remove dummy nodes old_edges = ut.reverse_path_edges(edge_list[1:-1]) new_edges = ut.reverse_path_edges(list(ut.itertwo(exi_nodes))) for new_edge, old_edge in zip(new_edges, old_edges): u2, v2 = new_edge[:2] d = old_edge[3] taillabel = d['taillabel'] parent_colx = d.get('parent_colx', -1) if not exi_graph.has_edge(u2, v2): exi_graph.add_edge(u2, v2, taillabel=taillabel, parent_colx=parent_colx) sink_nodes = list(ut.nx_sink_nodes(exi_graph)) source_nodes = list(ut.nx_source_nodes(exi_graph)) assert len(sink_nodes) == 1, 'expected a unique sink' sink_node = sink_nodes[0] # First identify if a node is root_specifiable node_dict = ut.nx_node_dict(exi_graph) for node in exi_graph.nodes(): root_specifiable = False # for edge in exi_graph.in_edges(node, keys=True): for edge in exi_graph.in_edges(node): # key = edge[-1] # assert key == 0, 'multi di graph is necessary' edata = exi_graph.get_edge_data(*edge) if edata.get('taillabel').startswith('*'): if node != sink_node: root_specifiable = True if exi_graph.in_degree(node) == 0: root_specifiable = True node_dict[node]['root_specifiable'] = root_specifiable # Need to specify any combo of red nodes such that # 1) for each path from a (leaf) to the (root) there is exactly one red # node along that path. This garentees that all inputs are gievn. path_list = ut.flatten([ nx.all_simple_paths(exi_graph, source_node, sink_node) for source_node in source_nodes ]) rootmost_nodes = set([]) for path in path_list: flags = [node_dict[node]['root_specifiable'] for node in path] valid_nodes = ut.compress(path, flags) rootmost_nodes.add(valid_nodes[-1]) # Rootmost nodes are the ones specifiable by default when computing the # normal property. for node in rootmost_nodes: node_dict[node]['rootmost'] = True # We actually need to hack away any root-most nodes that have another # rootmost node as the parent. Otherwise, this would cause constraints in # what the user could specify as valid input combinations. # ie: specify a vocab and an index, but the index depends on the vocab. # this forces the user to specify the vocab that was the parent of the index # the user should either just specify the index and have the vocab inferred # or for now, we just dont allow this to happen. nx.get_node_attributes(exi_graph, 'rootmost') recolor_exi_graph(exi_graph, rootmost_nodes) return exi_graph
def continue_if(G, child, edge): node_dict = ut.nx_node_dict(G) return not node_dict[child].get('root_specifiable')
def yield_if(G, child, edge): node_dict = ut.nx_node_dict(G) return node_dict[child].get('root_specifiable')
def demo2(): """ CommandLine: python -m wbia.algo.graph.demo demo2 --viz python -m wbia.algo.graph.demo demo2 Example: >>> # DISABLE_DOCTEST >>> from wbia.algo.graph.demo import * # NOQA >>> result = demo2() >>> print(result) """ import wbia.plottool as pt from wbia.scripts.thesis import TMP_RC import matplotlib as mpl mpl.rcParams.update(TMP_RC) # ---- Synthetic data params params = { 'redun.pos': 2, 'redun.neg': 2, } # oracle_accuracy = .98 # oracle_accuracy = .90 # oracle_accuracy = (.8, 1.0) oracle_accuracy = (0.85, 1.0) # oracle_accuracy = 1.0 # --- draw params VISUALIZE = ut.get_argflag('--viz') # QUIT_OR_EMEBED = 'embed' QUIT_OR_EMEBED = 'quit' TARGET_REVIEW = ut.get_argval('--target', type_=int, default=None) START = ut.get_argval('--start', type_=int, default=None) END = ut.get_argval('--end', type_=int, default=None) # ------------------ # rng = np.random.RandomState(42) # infr = demodata_infr(num_pccs=4, size=3, size_std=1, p_incon=0) # infr = demodata_infr(num_pccs=6, size=7, size_std=1, p_incon=0) # infr = demodata_infr(num_pccs=3, size=5, size_std=.2, p_incon=0) infr = demodata_infr(pcc_sizes=[5, 2, 4]) infr.verbose = 100 # apply_dummy_viewpoints(infr) # infr.ensure_cliques() infr.ensure_cliques() infr.ensure_full() # infr.apply_edge_truth() # Dummy scoring infr.init_simulation(oracle_accuracy=oracle_accuracy, name='demo2') # infr_gt = infr.copy() dpath = ut.ensuredir(ut.truepath('~/Desktop/demo')) ut.remove_files_in_dir(dpath) fig_counter = it.count(0) def show_graph(infr, title, final=False, selected_edges=None): if not VISUALIZE: return # TODO: rich colored text? latest = '\n'.join(infr.latest_logs()) showkw = dict( # fontsize=infr.graph.graph['fontsize'], # fontname=infr.graph.graph['fontname'], show_unreviewed_edges=True, show_inferred_same=False, show_inferred_diff=False, outof=(len(infr.aids)), # show_inferred_same=True, # show_inferred_diff=True, selected_edges=selected_edges, show_labels=True, simple_labels=True, # show_recent_review=not final, show_recent_review=False, # splines=infr.graph.graph['splines'], reposition=False, # with_colorbar=True ) verbose = infr.verbose infr.verbose = 0 infr_ = infr.copy() infr_ = infr infr_.verbose = verbose infr_.show(pickable=True, verbose=0, **showkw) infr.verbose = verbose # logger.info('status ' + ut.repr4(infr_.status())) # infr.show(**showkw) ax = pt.gca() pt.set_title(title, fontsize=20) fig = pt.gcf() fontsize = 22 if True: # postprocess xlabel lines = [] for line in latest.split('\n'): if False and line.startswith('ORACLE ERROR'): lines += ['ORACLE ERROR'] else: lines += [line] latest = '\n'.join(lines) if len(lines) > 10: fontsize = 16 if len(lines) > 12: fontsize = 14 if len(lines) > 14: fontsize = 12 if len(lines) > 18: fontsize = 10 if len(lines) > 23: fontsize = 8 if True: pt.adjust_subplots(top=0.95, left=0, right=1, bottom=0.45, fig=fig) ax.set_xlabel('\n' + latest) xlabel = ax.get_xaxis().get_label() xlabel.set_horizontalalignment('left') # xlabel.set_x(.025) xlabel.set_x(-0.6) # xlabel.set_fontname('CMU Typewriter Text') xlabel.set_fontname('Inconsolata') xlabel.set_fontsize(fontsize) ax.set_aspect('equal') # ax.xaxis.label.set_color('red') from os.path import join fpath = join(dpath, 'demo_{:04d}.png'.format(next(fig_counter))) fig.savefig( fpath, dpi=300, # transparent=True, edgecolor='none', ) # pt.save_figure(dpath=dpath, dpi=300) infr.latest_logs() if VISUALIZE: infr.update_visual_attrs(groupby='name_label') infr.set_node_attrs('pin', 'true') node_dict = ut.nx_node_dict(infr.graph) logger.info(ut.repr4(node_dict[1])) if VISUALIZE: infr.latest_logs() # Pin Nodes into the target groundtruth position show_graph(infr, 'target-gt') logger.info(ut.repr4(infr.status())) infr.clear_feedback() infr.clear_name_labels() infr.clear_edges() logger.info(ut.repr4(infr.status())) infr.latest_logs() if VISUALIZE: infr.update_visual_attrs() infr.prioritize('prob_match') if VISUALIZE or TARGET_REVIEW is None or TARGET_REVIEW == 0: show_graph(infr, 'initial state') def on_new_candidate_edges(infr, edges): # hack updateing visual attrs as a callback infr.update_visual_attrs() infr.on_new_candidate_edges = on_new_candidate_edges infr.params.update(**params) infr.refresh_candidate_edges() VIZ_ALL = VISUALIZE and TARGET_REVIEW is None and START is None logger.info('VIZ_ALL = %r' % (VIZ_ALL, )) if VIZ_ALL or TARGET_REVIEW == 0: show_graph(infr, 'find-candidates') # _iter2 = enumerate(infr.generate_reviews(**params)) # _iter2 = list(_iter2) # assert len(_iter2) > 0 # prog = ut.ProgIter(_iter2, label='demo2', bs=False, adjust=False, # enabled=False) count = 1 first = 1 for edge, priority in infr._generate_reviews(data=True): msg = 'review #%d, priority=%.3f' % (count, priority) logger.info('\n----------') infr.print('pop edge {} with priority={:.3f}'.format(edge, priority)) # logger.info('remaining_reviews = %r' % (infr.remaining_reviews()),) # Make the next review if START is not None: VIZ_ALL = count >= START if END is not None and count >= END: break infr.print(msg) if ut.allsame(infr.pos_graph.node_labels(*edge)) and first: # Have oracle make a mistake early feedback = infr.request_oracle_review(edge, accuracy=0) first -= 1 else: feedback = infr.request_oracle_review(edge) AT_TARGET = TARGET_REVIEW is not None and count >= TARGET_REVIEW - 1 SHOW_CANDIATE_POP = True if SHOW_CANDIATE_POP and (VIZ_ALL or AT_TARGET): # import utool # utool.embed() infr.print( ut.repr2(infr.task_probs['match_state'][edge], precision=4, si=True)) infr.print('len(queue) = %r' % (len(infr.queue))) # Show edge selection infr.print('Oracle will predict: ' + feedback['evidence_decision']) show_graph(infr, 'pre' + msg, selected_edges=[edge]) if count == TARGET_REVIEW: infr.EMBEDME = QUIT_OR_EMEBED == 'embed' infr.add_feedback(edge, **feedback) infr.print('len(queue) = %r' % (len(infr.queue))) # infr.apply_nondynamic_update() # Show the result if VIZ_ALL or AT_TARGET: show_graph(infr, msg) # import sys # sys.exit(1) if count == TARGET_REVIEW: break count += 1 infr.print('status = ' + ut.repr4(infr.status(extended=False))) show_graph(infr, 'post-review (#reviews={})'.format(count), final=True) # ROUND 2 FIGHT # if TARGET_REVIEW is None and round2_params is not None: # # HACK TO GET NEW THINGS IN QUEUE # infr.params = round2_params # _iter2 = enumerate(infr.generate_reviews(**params)) # prog = ut.ProgIter(_iter2, label='round2', bs=False, adjust=False, # enabled=False) # for count, (aid1, aid2) in prog: # msg = 'reviewII #%d' % (count) # logger.info('\n----------') # logger.info(msg) # logger.info('remaining_reviews = %r' % (infr.remaining_reviews()),) # # Make the next review evidence_decision # feedback = infr.request_oracle_review(edge) # if count == TARGET_REVIEW: # infr.EMBEDME = QUIT_OR_EMEBED == 'embed' # infr.add_feedback(edge, **feedback) # # Show the result # if PRESHOW or TARGET_REVIEW is None or count >= TARGET_REVIEW - 1: # show_graph(infr, msg) # if count == TARGET_REVIEW: # break # show_graph(infr, 'post-re-review', final=True) if not getattr(infr, 'EMBEDME', False): if ut.get_computer_name().lower() in ['hyrule', 'ooo']: pt.all_figures_tile(monitor_num=0, percent_w=0.5) else: pt.all_figures_tile() ut.show_if_requested()