def main( out_dir=None, preds_dir=None, data_dir=None, metric_names=None, plot_output=None, results_file=None, sweep_param_name=None): if metric_names is None: metric_names = ('accuracy', 'edit_score', 'overlap_score') preds_dir = os.path.expanduser(preds_dir) out_dir = os.path.expanduser(out_dir) if not os.path.exists(out_dir): os.makedirs(out_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) logger.info(f"Writing to: {out_dir}") if results_file is None: results_file = os.path.join(out_dir, 'results.csv') if os.path.exists(results_file): os.remove(results_file) else: results_file = os.path.expanduser(results_file) def saveVariable(var, var_name): joblib.dump(var, os.path.join(out_data_dir, f'{var_name}.pkl')) def loadAll(seq_ids, var_name, data_dir): def loadOne(seq_id): fn = os.path.join(data_dir, f'trial={seq_id}_{var_name}') return joblib.load(fn) return tuple(map(loadOne, seq_ids)) assembly_vocab = joblib.load(os.path.join(data_dir, 'assembly-vocab.pkl')) trial_ids = utils.getUniqueIds(preds_dir, prefix='trial=') pred_seqs = loadAll(trial_ids, 'pred-label-seq.pkl', preds_dir) true_seqs = loadAll(trial_ids, 'true-label-seq.pkl', preds_dir) input_seqs = loadAll(trial_ids, 'score-seq.pkl', preds_dir) action_vocab = [] action_batch = [] assembly_batch = [] for i, trial_id in enumerate(trial_ids): logger.info(f"VIDEO {trial_id}:") pred_assembly_index_seq = pred_seqs[i] true_assembly_index_seq = true_seqs[i] pred_action_segs, pred_action_index_seq = actionsFromAssemblies( pred_assembly_index_seq, assembly_vocab, action_vocab ) lib_assembly.writeAssemblies( os.path.join(out_data_dir, f'trial={trial_id}_pred-actions.txt'), pred_action_segs ) true_action_segs, true_action_index_seq = actionsFromAssemblies( true_assembly_index_seq, assembly_vocab, action_vocab ) lib_assembly.writeAssemblies( os.path.join(out_data_dir, f'trial={trial_id}_true-actions.txt'), true_action_segs ) metric_dict = {} for name in metric_names: key = f"{name}_action" value = getattr(LCTM.metrics, name)(pred_action_index_seq, true_action_index_seq) / 100 metric_dict[key] = value logger.info(f" {key}: {value * 100:.1f}%") key = f"{name}_assembly" value = getattr(LCTM.metrics, name)( pred_assembly_index_seq, true_assembly_index_seq ) / 100 metric_dict[key] = value logger.info(f" {key}: {value * 100:.1f}%") utils.writeResults(results_file, metric_dict, sweep_param_name, {}) input_seq = input_seqs[i] action_batch.append((pred_action_index_seq, input_seq, true_action_index_seq, trial_id)) assembly_batch.append( (pred_assembly_index_seq, input_seq, true_assembly_index_seq, trial_id) ) lib_assembly.writeAssemblies(os.path.join(out_data_dir, 'action-vocab.txt'), action_vocab) label_names = ('true', 'pred') for preds, inputs, gt_labels, seq_id in action_batch: fn = os.path.join(fig_dir, f"trial={seq_id}_model-io_action.png") utils.plot_array(None, (gt_labels, preds), label_names, fn=fn, labels_together=True) label_names = ('true', 'pred') for preds, inputs, gt_labels, seq_id in assembly_batch: fn = os.path.join(fig_dir, f"trial={seq_id}_model-io_assembly.png") utils.plot_array(None, (gt_labels, preds), label_names, fn=fn, labels_together=True)
def main( out_dir=None, data_dir=None, model_name=None, model_params={}, cv_params={}, train_params={}, viz_params={}, plot_predictions=None, results_file=None, sweep_param_name=None): data_dir = os.path.expanduser(data_dir) out_dir = os.path.expanduser(out_dir) if not os.path.exists(out_dir): os.makedirs(out_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) if results_file is None: results_file = os.path.join(out_dir, f'results.csv') write_mode = 'w' else: results_file = os.path.expanduser(results_file) write_mode = 'a' fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) def saveVariable(var, var_name): joblib.dump(var, os.path.join(out_data_dir, f'{var_name}.pkl')) def loadAll(seq_ids, var_name, data_dir): def loadOne(seq_id): fn = os.path.join(data_dir, f'trial={seq_id}_{var_name}') return joblib.load(fn) return tuple(map(loadOne, seq_ids)) # Load data trial_ids = utils.getUniqueIds(data_dir, prefix='trial=') feature_seqs = loadAll(trial_ids, 'feature-seq.pkl', data_dir) label_seqs = loadAll(trial_ids, 'label-seq.pkl', data_dir) # Define cross-validation folds dataset_size = len(trial_ids) cv_folds = utils.makeDataSplits(dataset_size, **cv_params) def getSplit(split_idxs): split_data = tuple( tuple(s[i] for i in split_idxs) for s in (feature_seqs, label_seqs, trial_ids) ) return split_data for cv_index, cv_splits in enumerate(cv_folds): train_data, val_data, test_data = tuple(map(getSplit, cv_splits)) train_feats, train_labels, train_ids = train_data test_feats, test_labels, test_ids = test_data val_feats, val_labels, val_ids = val_data logger.info( f'CV fold {cv_index + 1} / {len(cv_folds)}: {len(trial_ids)} total ' f'({len(train_ids)} train, {len(val_ids)} val, {len(test_ids)} test)' ) input_dim = train_set.num_obsv_dims output_dim = train_set.num_label_types elif model_name == 'dummy': # FIXME model = None else: raise AssertionError() metric_str = ' '.join(str(m) for m in metric_dict.values()) logger.info('[TST] ' + metric_str) d = {k: v.value for k, v in metric_dict.items()} utils.writeResults( results_file, d, sweep_param_name, model_params, write_mode=write_mode ) if plot_predictions: io_fig_dir = os.path.join(fig_dir, 'model-io') if not os.path.exists(io_fig_dir): os.makedirs(io_fig_dir) label_names = ('gt', 'pred') preds, scores, inputs, gt_labels, ids = zip(*test_io_history) for batch in test_io_history: batch = map(lambda x: x.cpu().numpy(), batch) for preds, _, inputs, gt_labels, seq_id in zip(*batch): fn = os.path.join(io_fig_dir, f"trial={seq_id}_model-io.png") utils.plot_array(inputs, (gt_labels, preds), label_names, fn=fn) def saveTrialData(pred_seq, score_seq, feat_seq, label_seq, trial_id): saveVariable(pred_seq, f'trial={trial_id}_pred-label-seq') saveVariable(score_seq, f'trial={trial_id}_score-seq') saveVariable(label_seq, f'trial={trial_id}_true-label-seq') for batch in test_io_history: batch = map(lambda x: x.cpu().numpy(), batch) for io in zip(*batch): saveTrialData(*io) saveVariable(train_ids, f'cvfold={cv_index}_train-ids') saveVariable(test_ids, f'cvfold={cv_index}_test-ids') saveVariable(val_ids, f'cvfold={cv_index}_val-ids') saveVariable(train_epoch_log, f'cvfold={cv_index}_{model_name}-train-epoch-log') saveVariable(val_epoch_log, f'cvfold={cv_index}_{model_name}-val-epoch-log') train_fig_dir = os.path.join(fig_dir, 'train-plots') if not os.path.exists(train_fig_dir): os.makedirs(train_fig_dir)
def main(out_dir=None, data_dir=None, event_scores_dir=None, action_scores_dir=None, part_scores_dir=None, as_atomic_events=False, only_fold=None, plot_io=None, prefix='seq=', results_file=None, sweep_param_name=None, model_params={}, cv_params={}): data_dir = os.path.expanduser(data_dir) event_scores_dir = os.path.expanduser(event_scores_dir) action_scores_dir = os.path.expanduser(action_scores_dir) part_scores_dir = os.path.expanduser(part_scores_dir) out_dir = os.path.expanduser(out_dir) if not os.path.exists(out_dir): os.makedirs(out_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) if results_file is None: results_file = os.path.join(out_dir, 'results.csv') else: results_file = os.path.expanduser(results_file) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) loader = DataLoader( { 'event': os.path.join(data_dir, 'event-dataset'), 'action': os.path.join(data_dir, 'action-dataset'), 'part': os.path.join(data_dir, 'part-dataset') }, { 'event': event_scores_dir, 'action': action_scores_dir, 'part': part_scores_dir }, prefix=prefix) logger.info( f"Loaded ids for {len(loader.seq_ids)} sequences from {data_dir}") all_metrics = collections.defaultdict(list) if as_atomic_events: map_df = pd.read_csv(os.path.join(data_dir, 'labels', 'event-vocab.csv'), index_col=False, keep_default_na=False) else: map_df = pd.DataFrame( [[f"{action}({part})", action] + [part == n for n in loader.vocabs['part'] if n] for action in loader.vocabs['action'] for part in loader.vocabs['part']], columns=['event', 'action'] + [f"{n}_active" for n in loader.vocabs['part'] if n]) event_vocab = map_df['event'].to_list() utils.saveVariable(event_vocab, 'vocab', out_data_dir) # Define cross-validation folds cv_folds = utils.makeDataSplits(len(loader.seq_ids), **cv_params) utils.saveVariable(cv_folds, 'cv-folds', out_data_dir) for cv_index, cv_fold in enumerate(cv_folds): if only_fold is not None and cv_index != only_fold: continue train_indices, val_indices, test_indices = cv_fold logger.info( f"CV FOLD {cv_index + 1} / {len(cv_folds)}: " f"{len(train_indices)} train, {len(val_indices)} val, {len(test_indices)} test" ) action_part_counts = sum( count_action_part_bigrams(loader.vocabs['action'], loader.vocabs['part'], la, lp) for la, lp in loader._getLabels(train_indices, label_name=['action', 'part'], load_from_data=True)) model = AttributeClassifier(action_part_counts) plot_action_part_bigrams( loader.vocabs['action'], loader.vocabs['part'], (model.action_part_counts > 0).astype(int), os.path.join(fig_dir, f'cvfold={cv_index}_action-part-coocurrence.png')) for i in test_indices: seq_id = loader.seq_ids[i] logger.info(f" Processing sequence {seq_id}...") action_score_seq, part_score_seq, true_action_seq, true_part_seq = loader[ seq_id] event_score_seq = model.forward(action_score_seq, part_score_seq) pred_action_seq, pred_part_seq = model.predict(event_score_seq) true_event_seq = ap_to_event(true_action_seq, true_part_seq, map_df, loader.vocabs) pred_event_seq = ap_to_event(pred_action_seq, pred_part_seq, map_df, loader.vocabs) metric_dict = {} metric_dict = eval_metrics(pred_event_seq, true_event_seq, name_prefix='Event ', append_to=metric_dict) metric_dict = eval_metrics(pred_action_seq, true_action_seq, name_prefix='Action ', append_to=metric_dict) metric_dict = eval_metrics(pred_part_seq, true_part_seq, name_prefix='Part ', append_to=metric_dict) for name, value in metric_dict.items(): logger.info(f" {name}: {value * 100:.2f}%") all_metrics[name].append(value) seq_id_str = f"seq={seq_id}" utils.saveVariable(event_score_seq, f'{seq_id_str}_score-seq', out_data_dir) utils.saveVariable(true_event_seq, f'{seq_id_str}_true-label-seq', out_data_dir) utils.saveVariable(pred_event_seq, f'{seq_id_str}_pred-label-seq', out_data_dir) utils.writeResults(results_file, metric_dict, sweep_param_name, model_params) if plot_io: utils.plot_array( event_score_seq.reshape(event_score_seq.shape[0], -1).T, (true_event_seq, pred_event_seq), ('true', 'pred'), fn=os.path.join(fig_dir, f"seq={seq_id:03d}_event.png")) utils.plot_array(action_score_seq.T, (true_action_seq, pred_action_seq), ('true', 'pred'), fn=os.path.join( fig_dir, f"seq={seq_id:03d}_action.png")) utils.plot_array(part_score_seq.T, (true_part_seq, pred_part_seq), ('true', 'pred'), fn=os.path.join(fig_dir, f"seq={seq_id:03d}_part.png"))
def __test(fig_dir, num_classes=2, num_samples=10, min_dur=2, max_dur=4): eps_str = 'ε' bos_str = '<BOS>' eos_str = '<EOS>' dur_internal_str = 'I' dur_final_str = 'F' aux_symbols = (eps_str, bos_str, eos_str) dur_vocab = (dur_internal_str, dur_final_str) sample_vocab = tuple(i for i in range(num_samples)) dur_vocab = tuple(i for i in range(1, max_dur + 1)) class_vocab = tuple(i for i in range(num_classes)) class_dur_vocab = tuple((c, s) for c in class_vocab for s in dur_vocab) def to_strings(vocab): return tuple(map(str, vocab)) def to_integerizer(vocab): return {item: i for i, item in enumerate(vocab)} sample_vocab_str = to_strings(sample_vocab) dur_vocab_str = to_strings(dur_vocab) class_vocab_str = to_strings(class_vocab) class_dur_vocab_str = to_strings(class_dur_vocab) # sample_integerizer = to_integerizer(sample_vocab) dur_integerizer = to_integerizer(dur_vocab) class_integerizer = to_integerizer(class_vocab) # class_dur_integerizer = to_integerizer(class_dur_vocab) sample_str_integerizer = to_integerizer(sample_vocab_str) class_str_integerizer = to_integerizer(class_vocab_str) class_dur_str_integerizer = to_integerizer(class_dur_vocab_str) def get_parts(class_dur_key): c, s = class_dur_vocab[class_dur_str_integerizer[class_dur_key]] c_str = class_vocab_str[class_integerizer[c]] s_str = dur_vocab_str[dur_integerizer[s]] return c_str, s_str class_dur_to_str = {get_parts(name): name for name in class_dur_vocab_str} sample_symbols = libfst.makeSymbolTable(aux_symbols + sample_vocab, prepend_epsilon=False) # dur_symbols = libfst.makeSymbolTable(aux_symbols + dur_vocab, prepend_epsilon=False) class_symbols = libfst.makeSymbolTable(aux_symbols + class_vocab, prepend_epsilon=False) # dur_symbols = libfst.makeSymbolTable(aux_symbols + dur_vocab, prepend_epsilon=False) class_dur_symbols = libfst.makeSymbolTable(aux_symbols + class_dur_vocab, prepend_epsilon=False) obs_scores = np.zeros((num_samples, num_classes)) dur_scores = np.array([[0 if d >= min_dur else np.inf for d in dur_vocab] for c in class_vocab], dtype=float) def score_transition(c_prev, s_prev, c_cur, s_cur): if c_prev != c_cur: if s_prev == dur_final_str and s_cur == dur_internal_str: score = 0 else: score = np.inf else: if s_prev == dur_internal_str and s_cur == dur_final_str: score = 0 elif s_prev == dur_internal_str and s_cur == dur_internal_str: score = 0 else: score = np.inf return score transition_scores = np.array([[ score_transition(c_prev, s_prev, c_cur, s_cur) for (c_cur, s_cur) in class_dur_vocab ] for (c_prev, s_prev) in class_dur_vocab], dtype=float) init_scores = np.array([ 0 if c == 0 and s == dur_internal_str else np.inf for (c, s) in class_dur_vocab ], dtype=float) final_scores = np.array([ 0 if c == 1 and s == dur_final_str else np.inf for (c, s) in class_dur_vocab ], dtype=float) def score_arc_state(class_dur_key, class_key): c, s = class_dur_vocab[class_dur_str_integerizer[class_dur_key]] c_prime = class_str_integerizer[class_key] if c == c_prime: score = 0 else: score = np.inf return score class_dur_to_class_scores = np.array([[ score_arc_state(class_dur_key, class_key) for class_key in class_vocab_str ] for class_dur_key in class_dur_vocab_str], dtype=float) def log_normalize(arr, axis=1): denom = -scipy.special.logsumexp(-arr, axis=axis, keepdims=True) return arr - denom obs_scores = log_normalize(obs_scores) dur_scores = log_normalize(dur_scores) transition_scores = log_normalize(transition_scores) init_scores = log_normalize(init_scores, axis=None) final_scores = log_normalize(final_scores, axis=None) obs_fst = add_endpoints(fromArray(obs_scores, sample_vocab_str, class_vocab_str, input_symbols=sample_symbols, output_symbols=class_symbols, arc_type='standard'), bos_str=bos_str, eos_str=eos_str).arcsort(sort_type='ilabel') dur_fst = add_endpoints(make_duration_fst( dur_scores, class_vocab_str, class_dur_to_str, dur_internal_str=dur_internal_str, dur_final_str=dur_final_str, input_symbols=class_symbols, output_symbols=class_dur_symbols, allow_self_transitions=False, arc_type='standard', ), bos_str=bos_str, eos_str=eos_str).arcsort(sort_type='ilabel') transition_fst = fromTransitions( transition_scores, class_dur_vocab_str, class_dur_vocab_str, init_weights=init_scores, final_weights=final_scores, input_symbols=class_dur_symbols, output_symbols=class_dur_symbols).arcsort(sort_type='ilabel') class_dur_to_class_fst = single_state_transducer( class_dur_to_class_scores, class_dur_vocab_str, class_vocab_str, input_symbols=class_dur_symbols, output_symbols=class_symbols, arc_type='standard').arcsort(sort_type='ilabel') seq_model = openfst.compose(dur_fst, transition_fst) decode_lattice = openfst.compose(obs_fst, seq_model).rmepsilon() # Result is in the log semiring (ie weights are negative log probs) arc_scores = libfst.fstArcGradient(decode_lattice).arcsort( sort_type='ilabel') best_arcs = openfst.shortestpath(decode_lattice).arcsort( sort_type='ilabel') state_scores = openfst.compose( arc_scores, openfst.arcmap(class_dur_to_class_fst, map_type='to_log')) best_states = openfst.compose(best_arcs, class_dur_to_class_fst) state_scores_arr, weight_type = toArray(state_scores, sample_str_integerizer, class_str_integerizer) draw_fst(os.path.join(fig_dir, 'obs_fst'), obs_fst, vertical=True, width=50, height=50, portrait=True) draw_fst(os.path.join(fig_dir, 'dur_fst'), dur_fst, vertical=True, width=50, height=50, portrait=True) draw_fst(os.path.join(fig_dir, 'transition_fst'), transition_fst, vertical=True, width=50, height=50, portrait=True) draw_fst(os.path.join(fig_dir, 'seq_model'), seq_model, vertical=True, width=50, height=50, portrait=True) draw_fst(os.path.join(fig_dir, 'decode_lattice'), decode_lattice, vertical=True, width=50, height=50, portrait=True) draw_fst(os.path.join(fig_dir, 'state_scores'), state_scores, vertical=True, width=50, height=50, portrait=True) draw_fst(os.path.join(fig_dir, 'best_states'), best_states, vertical=True, width=50, height=50, portrait=True) utils.plot_array(obs_scores.T, (state_scores_arr.T, ), ('-logprobs', ), fn=os.path.join(fig_dir, "test_io.png"))
def main(out_dir=None, data_dir=None, segs_dir=None, scores_dir=None, vocab_dir=None, label_type='edges', gpu_dev_id=None, start_from=None, stop_at=None, num_disp_imgs=None, results_file=None, sweep_param_name=None, model_params={}, cv_params={}): data_dir = os.path.expanduser(data_dir) segs_dir = os.path.expanduser(segs_dir) scores_dir = os.path.expanduser(scores_dir) vocab_dir = os.path.expanduser(vocab_dir) out_dir = os.path.expanduser(out_dir) if not os.path.exists(out_dir): os.makedirs(out_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) if results_file is None: results_file = os.path.join(out_dir, 'results.csv') else: results_file = os.path.expanduser(results_file) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) io_dir_images = os.path.join(fig_dir, 'model-io_images') if not os.path.exists(io_dir_images): os.makedirs(io_dir_images) io_dir_plots = os.path.join(fig_dir, 'model-io_plots') if not os.path.exists(io_dir_plots): os.makedirs(io_dir_plots) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) seq_ids = utils.getUniqueIds(scores_dir, prefix='trial=', suffix='score-seq.*', to_array=True) logger.info( f"Loaded scores for {len(seq_ids)} sequences from {scores_dir}") link_vocab = {} joint_vocab = {} joint_type_vocab = {} vocab, parts_vocab, part_labels = load_vocab(link_vocab, joint_vocab, joint_type_vocab, vocab_dir) pred_vocab = [] # FIXME if label_type == 'assembly': logger.info("Converting assemblies -> edges") state_pred_seqs = tuple( utils.loadVariable(f"trial={seq_id}_pred-label-seq", scores_dir) for seq_id in seq_ids) state_true_seqs = tuple( utils.loadVariable(f"trial={seq_id}_true-label-seq", scores_dir) for seq_id in seq_ids) edge_pred_seqs = tuple(part_labels[seq] for seq in state_pred_seqs) edge_true_seqs = tuple(part_labels[seq] for seq in state_true_seqs) elif label_type == 'edge': logger.info("Converting edges -> assemblies (will take a few minutes)") edge_pred_seqs = tuple( utils.loadVariable(f"trial={seq_id}_pred-label-seq", scores_dir) for seq_id in seq_ids) edge_true_seqs = tuple( utils.loadVariable(f"trial={seq_id}_true-label-seq", scores_dir) for seq_id in seq_ids) state_pred_seqs = tuple( edges_to_assemblies(seq, pred_vocab, parts_vocab, part_labels) for seq in edge_pred_seqs) state_true_seqs = tuple( edges_to_assemblies(seq, vocab, parts_vocab, part_labels) for seq in edge_true_seqs) device = torchutils.selectDevice(gpu_dev_id) dataset = sim2real.LabeledConnectionDataset( utils.loadVariable('parts-vocab', vocab_dir), utils.loadVariable('part-labels', vocab_dir), utils.loadVariable('vocab', vocab_dir), device=device) all_metrics = collections.defaultdict(list) # Define cross-validation folds cv_folds = utils.makeDataSplits(len(seq_ids), **cv_params) utils.saveVariable(cv_folds, 'cv-folds', out_data_dir) for cv_index, cv_fold in enumerate(cv_folds): train_indices, val_indices, test_indices = cv_fold logger.info( f"CV FOLD {cv_index + 1} / {len(cv_folds)}: " f"{len(train_indices)} train, {len(val_indices)} val, {len(test_indices)} test" ) train_states = np.hstack( tuple(state_true_seqs[i] for i in (train_indices))) train_edges = part_labels[train_states] # state_train_vocab = np.unique(train_states) # edge_train_vocab = part_labels[state_train_vocab] train_freq_bigram, train_freq_unigram = edge_joint_freqs(train_edges) # state_probs = utils.makeHistogram(len(vocab), train_states, normalize=True) test_states = np.hstack( tuple(state_true_seqs[i] for i in (test_indices))) test_edges = part_labels[test_states] # state_test_vocab = np.unique(test_states) # edge_test_vocab = part_labels[state_test_vocab] test_freq_bigram, test_freq_unigram = edge_joint_freqs(test_edges) f, axes = plt.subplots(1, 2) axes[0].matshow(train_freq_bigram) axes[0].set_title('Train') axes[1].matshow(test_freq_bigram) axes[1].set_title('Test') plt.tight_layout() plt.savefig( os.path.join(fig_dir, f"edge-freqs-bigram_cvfold={cv_index}.png")) f, axis = plt.subplots(1) axis.stem(train_freq_unigram, label='Train', linefmt='C0-', markerfmt='C0o') axis.stem(test_freq_unigram, label='Test', linefmt='C1--', markerfmt='C1o') plt.legend() plt.tight_layout() plt.savefig( os.path.join(fig_dir, f"edge-freqs-unigram_cvfold={cv_index}.png")) for i in test_indices: seq_id = seq_ids[i] logger.info(f" Processing sequence {seq_id}...") trial_prefix = f"trial={seq_id}" # I include the '.' to differentiate between 'rgb-frame-seq' and # 'rgb-frame-seq-before-first-touch' # rgb_seq = utils.loadVariable(f"{trial_prefix}_rgb-frame-seq.", data_dir) # seg_seq = utils.loadVariable(f"{trial_prefix}_seg-labels-seq", segs_dir) score_seq = utils.loadVariable(f"{trial_prefix}_score-seq", scores_dir) # if score_seq.shape[0] != rgb_seq.shape[0]: # err_str = f"scores shape {score_seq.shape} != data shape {rgb_seq.shape}" # raise AssertionError(err_str) edge_pred_seq = edge_pred_seqs[i] edge_true_seq = edge_true_seqs[i] state_pred_seq = state_pred_seqs[i] state_true_seq = state_true_seqs[i] num_types = np.unique(state_pred_seq).shape[0] num_samples = state_pred_seq.shape[0] num_total = len(pred_vocab) logger.info( f" {num_types} assemblies predicted ({num_total} total); " f"{num_samples} samples") # edge_freq_bigram, edge_freq_unigram = edge_joint_freqs(edge_true_seq) # dist_shift = np.linalg.norm(train_freq_unigram - edge_freq_unigram) metric_dict = { # 'State OOV rate': oov_rate_state(state_true_seq, state_train_vocab), # 'Edge OOV rate': oov_rate_edges(edge_true_seq, edge_train_vocab), # 'State avg prob, true': state_probs[state_true_seq].mean(), # 'State avg prob, pred': state_probs[state_pred_seq].mean(), # 'Edge distribution shift': dist_shift } metric_dict = eval_edge_metrics(edge_pred_seq, edge_true_seq, append_to=metric_dict) metric_dict = eval_state_metrics(state_pred_seq, state_true_seq, append_to=metric_dict) for name, value in metric_dict.items(): logger.info(f" {name}: {value * 100:.2f}%") all_metrics[name].append(value) utils.writeResults(results_file, metric_dict, sweep_param_name, model_params) if num_disp_imgs is not None: pred_images = tuple( render(dataset, vocab[seg_label]) for seg_label in utils.computeSegments(state_pred_seq)[0]) imageprocessing.displayImages( *pred_images, file_path=os.path.join( io_dir_images, f"seq={seq_id:03d}_pred-assemblies.png"), num_rows=None, num_cols=5) true_images = tuple( render(dataset, vocab[seg_label]) for seg_label in utils.computeSegments(state_true_seq)[0]) imageprocessing.displayImages( *true_images, file_path=os.path.join( io_dir_images, f"seq={seq_id:03d}_true-assemblies.png"), num_rows=None, num_cols=5) utils.plot_array(score_seq.T, (edge_true_seq.T, edge_pred_seq.T), ('true', 'pred'), fn=os.path.join(io_dir_plots, f"seq={seq_id:03d}.png"))
def main(out_dir=None, video_data_dir=None, features_dir=None, activity_labels_dir=None, gt_keyframes_dir=None, use_gt_activity_labels=False): out_dir = os.path.expanduser(out_dir) video_data_dir = os.path.expanduser(video_data_dir) features_dir = os.path.expanduser(features_dir) activity_labels_dir = os.path.expanduser(activity_labels_dir) if gt_keyframes_dir is not None: gt_keyframes_dir = os.path.expanduser(gt_keyframes_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) def loadFromDir(var_name, dir_name): return joblib.load(os.path.join(dir_name, f"{var_name}.pkl")) def saveToWorkingDir(var, var_name): joblib.dump(var, os.path.join(out_data_dir, f"{var_name}.pkl")) trial_ids = utils.getUniqueIds(features_dir, prefix='trial=', suffix='.pkl') seq_lens = [] for seq_idx, trial_id in enumerate(trial_ids): logger.info( f"Processing video {seq_idx + 1} / {len(trial_ids)} (trial {trial_id})" ) logger.info(f" Loading data...") feature_seq = loadFromDir(f"trial={trial_id}_feature-seq", features_dir) raw_labels = loadFromDir(f"trial-{trial_id}_action-seq", video_data_dir) timestamp_seq = loadFromDir( f"trial-{trial_id}_rgb-frame-timestamp-seq", video_data_dir) if use_gt_activity_labels: activity_label_fn = f"trial={trial_id}_true-label-seq" else: activity_label_fn = f"trial={trial_id}_pred-label-seq" activity_labels = loadFromDir(activity_label_fn, activity_labels_dir) action_labels = makeActionLabels(raw_labels, seq_len=feature_seq.shape[0]) if timestamp_seq.shape[0] != feature_seq.shape[0]: logger.warning( f"Video dimensions don't match: " f"{feature_seq.shape} scores, {timestamp_seq.shape} timestamps" ) continue # trim sequences is_activity = activity_labels == 1 if not is_activity.any(): logger.warning(f"No activity detected: skipping trial") continue action_labels = action_labels[is_activity, ...] timestamp_seq = timestamp_seq[is_activity, ...] feature_seq = feature_seq[is_activity, ...] if feature_seq.shape[0] != action_labels.shape[0]: err_str = ( "Data dimensions don't match: " f"{feature_seq.shape} features, {action_labels.shape} labels") raise AssertionError(err_str) seq_lens.append(feature_seq.shape[0]) logger.info(f" Saving output...") trial_str = f"trial={trial_id}" saveToWorkingDir(timestamp_seq, f'{trial_str}_timestamp-seq') saveToWorkingDir(feature_seq, f'{trial_str}_feature-seq') saveToWorkingDir(action_labels, f'{trial_str}_label-seq') fn = os.path.join(fig_dir, f"trial={trial_id}.png") utils.plot_array(feature_seq.T, (action_labels, ), ('action', ), fn=fn) logger.info(f"min seq len: {min(seq_lens)} max seq len: {max(seq_lens)}")
def main( out_dir=None, modalities=['rgb', 'imu'], gpu_dev_id=None, plot_io=None, rgb_data_dir=None, rgb_attributes_dir=None, imu_data_dir=None, imu_attributes_dir=None): out_dir = os.path.expanduser(out_dir) rgb_data_dir = os.path.expanduser(rgb_data_dir) rgb_attributes_dir = os.path.expanduser(rgb_attributes_dir) imu_data_dir = os.path.expanduser(imu_data_dir) imu_attributes_dir = os.path.expanduser(imu_attributes_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) # Load data if modalities == ['rgb']: trial_ids = utils.getUniqueIds(rgb_data_dir, prefix='trial=', to_array=True) logger.info(f"Processing {len(trial_ids)} videos") else: rgb_trial_ids = utils.getUniqueIds(rgb_data_dir, prefix='trial=', to_array=True) imu_trial_ids = utils.getUniqueIds(imu_data_dir, prefix='trial=', to_array=True) trial_ids = np.array(sorted(set(rgb_trial_ids.tolist()) & set(imu_trial_ids.tolist()))) logger.info( f"Processing {len(trial_ids)} videos common to " f"RGB ({len(rgb_trial_ids)} total) and IMU ({len(imu_trial_ids)} total)" ) device = torchutils.selectDevice(gpu_dev_id) dataset = FusionDataset( trial_ids, rgb_attributes_dir, rgb_data_dir, imu_attributes_dir, imu_data_dir, device=device, modalities=modalities, ) utils.saveMetadata(dataset.metadata, out_data_dir) utils.saveVariable(dataset.vocab, 'vocab', out_data_dir) for i, trial_id in enumerate(trial_ids): logger.info(f"Processing sequence {trial_id}...") true_label_seq = dataset.loadTargets(trial_id) attribute_feats = dataset.loadInputs(trial_id) # (Process the samples here if we need to) attribute_feats = attribute_feats.cpu().numpy() true_label_seq = true_label_seq.cpu().numpy() trial_prefix = f"trial={trial_id}" utils.saveVariable(attribute_feats, f'{trial_prefix}_feature-seq', out_data_dir) utils.saveVariable(true_label_seq, f'{trial_prefix}_label-seq', out_data_dir) if plot_io: fn = os.path.join(fig_dir, f'{trial_prefix}.png') utils.plot_array( attribute_feats.T, (true_label_seq,), ('gt',), fn=fn )
def main(out_dir=None, data_dir=None, model_name=None, predict_mode='classify', gpu_dev_id=None, batch_size=None, learning_rate=None, independent_signals=None, active_only=None, output_dim_from_vocab=False, prefix='trial=', feature_fn_format='feature-seq.pkl', label_fn_format='label_seq.pkl', dataset_params={}, model_params={}, cv_params={}, train_params={}, viz_params={}, metric_names=['Loss', 'Accuracy', 'Precision', 'Recall', 'F1'], plot_predictions=None, results_file=None, sweep_param_name=None): data_dir = os.path.expanduser(data_dir) out_dir = os.path.expanduser(out_dir) if not os.path.exists(out_dir): os.makedirs(out_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) if results_file is None: results_file = os.path.join(out_dir, 'results.csv') else: results_file = os.path.expanduser(results_file) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) def saveVariable(var, var_name, to_dir=out_data_dir): return utils.saveVariable(var, var_name, to_dir) # Load data device = torchutils.selectDevice(gpu_dev_id) trial_ids = utils.getUniqueIds(data_dir, prefix=prefix, suffix=feature_fn_format, to_array=True) dataset = utils.CvDataset( trial_ids, data_dir, prefix=prefix, feature_fn_format=feature_fn_format, label_fn_format=label_fn_format, ) utils.saveMetadata(dataset.metadata, out_data_dir) utils.saveVariable(dataset.vocab, 'vocab', out_data_dir) # Define cross-validation folds cv_folds = utils.makeDataSplits(len(trial_ids), **cv_params) utils.saveVariable(cv_folds, 'cv-folds', out_data_dir) if predict_mode == 'binary multiclass': # criterion = torchutils.BootstrappedCriterion( # 0.25, base_criterion=torch.nn.functional.binary_cross_entropy_with_logits, # ) criterion = torch.nn.BCEWithLogitsLoss() labels_dtype = torch.float elif predict_mode == 'multiclass': criterion = torch.nn.CrossEntropyLoss() labels_dtype = torch.long elif predict_mode == 'classify': criterion = torch.nn.CrossEntropyLoss() labels_dtype = torch.long else: raise AssertionError() def make_dataset(feats, labels, ids, shuffle=True): dataset = torchutils.SequenceDataset(feats, labels, device=device, labels_dtype=labels_dtype, seq_ids=ids, **dataset_params) loader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True) return dataset, loader for cv_index, cv_fold in enumerate(cv_folds): train_data, val_data, test_data = dataset.getFold(cv_fold) if independent_signals: train_data = splitSeqs(*train_data, active_only=active_only) val_data = splitSeqs(*val_data, active_only=active_only) test_data = splitSeqs(*test_data, active_only=False) train_set, train_loader = make_dataset(*train_data, shuffle=True) test_set, test_loader = make_dataset(*test_data, shuffle=False) val_set, val_loader = make_dataset(*val_data, shuffle=True) logger.info( f'CV fold {cv_index + 1} / {len(cv_folds)}: {len(dataset.trial_ids)} total ' f'({len(train_set)} train, {len(val_set)} val, {len(test_set)} test)' ) logger.info(f'{train_set.num_label_types} unique labels in train set; ' f'vocab size is {len(dataset.vocab)}') input_dim = train_set.num_obsv_dims output_dim = train_set.num_label_types if output_dim_from_vocab: output_dim = len(dataset.vocab) if model_name == 'linear': model = torchutils.LinearClassifier( input_dim, output_dim, **model_params).to(device=device) elif model_name == 'conv': model = ConvClassifier(input_dim, output_dim, **model_params).to(device=device) elif model_name == 'TCN': if predict_mode == 'multiclass': num_multiclass = train_set[0][1].shape[-1] output_dim = max([ train_set.num_label_types, test_set.num_label_types, val_set.num_label_types ]) else: num_multiclass = None model = TcnClassifier(input_dim, output_dim, num_multiclass=num_multiclass, **model_params).to(device=device) elif model_name == 'LSTM': if predict_mode == 'multiclass': num_multiclass = train_set[0][1].shape[-1] output_dim = max([ train_set.num_label_types, test_set.num_label_types, val_set.num_label_types ]) else: num_multiclass = None model = LstmClassifier(input_dim, output_dim, num_multiclass=num_multiclass, **model_params).to(device=device) else: raise AssertionError() optimizer_ft = torch.optim.Adam(model.parameters(), lr=learning_rate, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False) lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer_ft, step_size=1, gamma=1.00) train_epoch_log = collections.defaultdict(list) val_epoch_log = collections.defaultdict(list) metric_dict = {name: metrics.makeMetric(name) for name in metric_names} model, last_model_wts = torchutils.trainModel( model, criterion, optimizer_ft, lr_scheduler, train_loader, val_loader, device=device, metrics=metric_dict, train_epoch_log=train_epoch_log, val_epoch_log=val_epoch_log, **train_params) # Test model metric_dict = {name: metrics.makeMetric(name) for name in metric_names} test_io_history = torchutils.predictSamples( model.to(device=device), test_loader, criterion=criterion, device=device, metrics=metric_dict, data_labeled=True, update_model=False, seq_as_batch=train_params['seq_as_batch'], return_io_history=True) if independent_signals: test_io_history = tuple(joinSeqs(test_io_history)) logger.info('[TST] ' + ' '.join(str(m) for m in metric_dict.values())) utils.writeResults(results_file, {k: v.value for k, v in metric_dict.items()}, sweep_param_name, model_params) if plot_predictions: io_fig_dir = os.path.join(fig_dir, 'model-io') if not os.path.exists(io_fig_dir): os.makedirs(io_fig_dir) label_names = ('gt', 'pred') preds, scores, inputs, gt_labels, ids = zip(*test_io_history) for batch in test_io_history: batch = tuple( x.cpu().numpy() if isinstance(x, torch.Tensor) else x for x in batch) for preds, _, inputs, gt_labels, seq_id in zip(*batch): fn = os.path.join(io_fig_dir, f"{prefix}{seq_id}_model-io.png") utils.plot_array(inputs, (gt_labels.T, preds.T), label_names, fn=fn) for batch in test_io_history: batch = tuple(x.cpu().numpy() if isinstance(x, torch.Tensor) else x for x in batch) for pred_seq, score_seq, feat_seq, label_seq, trial_id in zip( *batch): saveVariable(pred_seq, f'{prefix}{trial_id}_pred-label-seq') saveVariable(score_seq, f'{prefix}{trial_id}_score-seq') saveVariable(label_seq, f'{prefix}{trial_id}_true-label-seq') saveVariable(model, f'cvfold={cv_index}_{model_name}-best') train_fig_dir = os.path.join(fig_dir, 'train-plots') if not os.path.exists(train_fig_dir): os.makedirs(train_fig_dir) if train_epoch_log: torchutils.plotEpochLog(train_epoch_log, subfig_size=(10, 2.5), title='Training performance', fn=os.path.join( train_fig_dir, f'cvfold={cv_index}_train-plot.png')) if val_epoch_log: torchutils.plotEpochLog(val_epoch_log, subfig_size=(10, 2.5), title='Heldout performance', fn=os.path.join( train_fig_dir, f'cvfold={cv_index}_val-plot.png'))
def main(out_dir=None, data_dir=None, assembly_data_dir=None, scores_dir=None, event_attr_fn=None, connection_attr_fn=None, assembly_attr_fn=None, only_fold=None, plot_io=None, prefix='seq=', stop_after=None, background_action='', model_params={}, cv_params={}, stride=None, results_file=None, sweep_param_name=None): data_dir = os.path.expanduser(data_dir) assembly_data_dir = os.path.expanduser(assembly_data_dir) scores_dir = os.path.expanduser(scores_dir) event_attr_fn = os.path.expanduser(event_attr_fn) connection_attr_fn = os.path.expanduser(connection_attr_fn) assembly_attr_fn = os.path.expanduser(assembly_attr_fn) out_dir = os.path.expanduser(out_dir) if not os.path.exists(out_dir): os.makedirs(out_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) if results_file is None: results_file = os.path.join(out_dir, 'results.csv') else: results_file = os.path.expanduser(results_file) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) misc_dir = os.path.join(out_dir, 'misc') if not os.path.exists(misc_dir): os.makedirs(misc_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) seq_ids = utils.getUniqueIds(data_dir, prefix=prefix, suffix='labels.*', to_array=True) dataset = utils.FeaturelessCvDataset(seq_ids, data_dir, prefix=prefix, label_fn_format='labels') logger.info( f"Loaded scores for {len(seq_ids)} sequences from {scores_dir}") # Define cross-validation folds cv_folds = utils.makeDataSplits(len(seq_ids), **cv_params) utils.saveVariable(cv_folds, 'cv-folds', out_data_dir) # Load event, connection attributes assembly_vocab = tuple( tuple(sorted(tuple(sorted(joint)) for joint in a)) for a in utils.loadVariable('vocab', assembly_data_dir)) (*probs, assembly_transition_probs), vocabs = loadPartInfo( event_attr_fn, connection_attr_fn, assembly_attr_fn, background_action=background_action, assembly_vocab=assembly_vocab) event_assembly_scores = event_to_assembly_scores(*probs, vocabs) assembly_transition_scores = np.log(assembly_transition_probs) viz_transition_probs(os.path.join(fig_dir, 'action-transitions'), np.exp(event_assembly_scores), vocabs['event_vocab']) write_transition_probs(os.path.join(misc_dir, 'action-transitions'), np.exp(event_assembly_scores), vocabs['event_vocab'], vocabs['assembly_vocab']) for cv_index, cv_fold in enumerate(cv_folds): if only_fold is not None and cv_index != only_fold: continue train_indices, val_indices, test_indices = cv_fold logger.info( f"CV FOLD {cv_index + 1} / {len(cv_folds)}: " f"{len(train_indices)} train, {len(val_indices)} val, {len(test_indices)} test" ) train_data, val_data, test_data = dataset.getFold(cv_fold) cv_str = f'cvfold={cv_index}' class_priors, event_dur_probs = count_priors(train_data[0], len(dataset.vocab), stride=stride, approx_upto=0.95, support_only=True) event_dur_scores = np.log(event_dur_probs) event_dur_scores = np.zeros_like(event_dur_scores) scores = (event_dur_scores, event_assembly_scores, assembly_transition_scores) model = decode.AssemblyActionRecognizer(scores, vocabs, model_params) viz_priors(os.path.join(fig_dir, f'{cv_str}_priors'), class_priors, event_dur_probs) model.write_fsts(os.path.join(misc_dir, f'{cv_str}_fsts')) model.save_vocabs(os.path.join(out_data_dir, f'{cv_str}_model-vocabs')) for i, (_, seq_id) in enumerate(zip(*test_data)): if stop_after is not None and i >= stop_after: break trial_prefix = f"{prefix}{seq_id}" if model_params['return_label'] == 'input': true_seq = utils.loadVariable(f"{trial_prefix}_true-label-seq", scores_dir) elif model_params['return_label'] == 'output': try: true_seq = utils.loadVariable(f"{trial_prefix}_label-seq", assembly_data_dir) true_seq = true_seq[::stride] except AssertionError: # logger.info(f' Skipping sequence {seq_id}: {e}') continue logger.info(f" Processing sequence {seq_id}...") event_score_seq = utils.loadVariable(f"{trial_prefix}_score-seq", scores_dir) if event_score_seq.shape[0] != true_seq.shape[0]: err_str = (f'Event scores shape {event_score_seq.shape} ' f'!= labels shape {true_seq.shape}') raise AssertionError(err_str) # FIXME: the serialized variables are probs, not log-probs # event_score_seq = suppress_nonmax(event_score_seq) # event_score_seq = np.log(event_score_seq) decode_score_seq = model.forward(event_score_seq) pred_seq = model.predict(decode_score_seq) metric_dict = eval_metrics(pred_seq, true_seq) for name, value in metric_dict.items(): logger.info(f" {name}: {value * 100:.2f}%") utils.writeResults(results_file, metric_dict, sweep_param_name, model_params) utils.saveVariable(decode_score_seq, f'{trial_prefix}_score-seq', out_data_dir) utils.saveVariable(pred_seq, f'{trial_prefix}_pred-label-seq', out_data_dir) utils.saveVariable(true_seq, f'{trial_prefix}_true-label-seq', out_data_dir) if plot_io: utils.plot_array(event_score_seq.T, (pred_seq.T, true_seq.T), ('pred', 'true'), fn=os.path.join(fig_dir, f"seq={seq_id:03d}.png")) write_labels( os.path.join(misc_dir, f"seq={seq_id:03d}_pred-seq.txt"), pred_seq, model.output_vocab.as_raw()) write_labels( os.path.join(misc_dir, f"seq={seq_id:03d}_true-seq.txt"), true_seq, model.output_vocab.as_raw())
def main(out_dir=None, data_dir=None, actions_dir=None, parts_dir=None, events_dir=None, edges_dir=None, prefix='seq=', feature_fn_format='score-seq', label_fn_format='true-label-seq', stop_after=None, only_fold=None, plot_io=None, dataset_params={}, model_params={}, cv_params={}): data_dir = os.path.expanduser(data_dir) actions_dir = os.path.expanduser(actions_dir) parts_dir = os.path.expanduser(parts_dir) events_dir = os.path.expanduser(events_dir) edges_dir = os.path.expanduser(edges_dir) out_dir = os.path.expanduser(out_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) # vocab = utils.loadVariable( # 'assembly-action-vocab', # os.path.join(data_dir, 'event-dataset') # ) # vocab = [BlockAssembly()] + list(abs(x) for x in vocab) dataset = FusionDataset( actions_dir, parts_dir, events_dir, edges_dir, prefix=prefix, **dataset_params, # vocab=vocab, ) utils.saveMetadata(dataset.metadata, out_data_dir) utils.saveVariable(dataset.vocab, 'vocab', out_data_dir) seq_ids = dataset.trial_ids logger.info(f"Loaded scores for {len(seq_ids)} sequences from {data_dir}") # Define cross-validation folds # cv_folds = utils.makeDataSplits(len(seq_ids), **cv_params) # utils.saveVariable(cv_folds, 'cv-folds', out_data_dir) for i, seq_id in enumerate(seq_ids): try: labels = dataset.loadTargets(seq_id) features = dataset.loadInputs(seq_id) except AssertionError as e: logger.warning(f'Skipping sequence {seq_id}: {e}') continue logger.info(f"Processing sequence {seq_id}") if labels.shape[0] != features.shape[0]: message = f'Label shape {labels.shape} != feature shape {features.shape}' raise AssertionError(message) seq_prefix = f"seq={seq_id}" utils.saveVariable(features, f'{seq_prefix}_feature-seq', out_data_dir) utils.saveVariable(labels, f'{seq_prefix}_label-seq', out_data_dir) if plot_io: fn = os.path.join(fig_dir, f'{seq_prefix}.png') utils.plot_array(features.T, (labels.T, ), ('gt', ), fn=fn)
def main(out_dir=None, data_dir=None, scores_dir=None, frames_dir=None, vocab_from_scores_dir=None, only_fold=None, plot_io=None, prefix='seq=', results_file=None, sweep_param_name=None, model_params={}, cv_params={}): data_dir = os.path.expanduser(data_dir) scores_dir = os.path.expanduser(scores_dir) frames_dir = os.path.expanduser(frames_dir) out_dir = os.path.expanduser(out_dir) if not os.path.exists(out_dir): os.makedirs(out_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) if results_file is None: results_file = os.path.join(out_dir, 'results.csv') else: results_file = os.path.expanduser(results_file) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) io_dir_images = os.path.join(fig_dir, 'model-io_images') if not os.path.exists(io_dir_images): os.makedirs(io_dir_images) io_dir_plots = os.path.join(fig_dir, 'model-io_plots') if not os.path.exists(io_dir_plots): os.makedirs(io_dir_plots) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) seq_ids = utils.getUniqueIds(scores_dir, prefix=prefix, suffix='pred-label-seq.*', to_array=True) logger.info( f"Loaded scores for {len(seq_ids)} sequences from {scores_dir}") if vocab_from_scores_dir: vocab = utils.loadVariable('vocab', scores_dir) else: vocab = utils.loadVariable('vocab', data_dir) all_metrics = collections.defaultdict(list) # Define cross-validation folds cv_folds = utils.makeDataSplits(len(seq_ids), **cv_params) utils.saveVariable(cv_folds, 'cv-folds', out_data_dir) all_pred_seqs = [] all_true_seqs = [] for cv_index, cv_fold in enumerate(cv_folds): if only_fold is not None and cv_index != only_fold: continue train_indices, val_indices, test_indices = cv_fold logger.info( f"CV FOLD {cv_index + 1} / {len(cv_folds)}: " f"{len(train_indices)} train, {len(val_indices)} val, {len(test_indices)} test" ) for i in test_indices: seq_id = seq_ids[i] logger.info(f" Processing sequence {seq_id}...") trial_prefix = f"{prefix}{seq_id}" score_seq = utils.loadVariable(f"{trial_prefix}_score-seq", scores_dir) pred_seq = utils.loadVariable(f"{trial_prefix}_pred-label-seq", scores_dir) true_seq = utils.loadVariable(f"{trial_prefix}_true-label-seq", scores_dir) metric_dict = eval_metrics(pred_seq, true_seq) for name, value in metric_dict.items(): logger.info(f" {name}: {value * 100:.2f}%") all_metrics[name].append(value) utils.writeResults(results_file, metric_dict, sweep_param_name, model_params) all_pred_seqs.append(pred_seq) all_true_seqs.append(true_seq) if plot_io: utils.plot_array(score_seq.T, (true_seq, pred_seq), ('true', 'pred'), fn=os.path.join(io_dir_plots, f"seq={seq_id:03d}.png")) if False: confusions = metrics.confusionMatrix(all_pred_seqs, all_true_seqs, len(vocab)) utils.saveVariable(confusions, "confusions", out_data_dir) per_class_acc, class_counts = metrics.perClassAcc(confusions, return_counts=True) class_preds = confusions.sum(axis=1) logger.info(f"MACRO ACC: {np.nanmean(per_class_acc) * 100:.2f}%") metrics.plotConfusions(os.path.join(fig_dir, 'confusions.png'), confusions, vocab) metrics.plotPerClassAcc(os.path.join(fig_dir, 'per-class-results.png'), vocab, per_class_acc, class_preds, class_counts)
def main( out_dir=None, hand_detections_dir=None, labels_dir=None, plot_output=None, results_file=None, sweep_param_name=None): hand_detections_dir = os.path.expanduser(hand_detections_dir) labels_dir = os.path.expanduser(labels_dir) out_dir = os.path.expanduser(out_dir) if not os.path.exists(out_dir): os.makedirs(out_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) def saveVariable(var, var_name): joblib.dump(var, os.path.join(out_data_dir, f'{var_name}.pkl')) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) logger.info(f"Reading from: {hand_detections_dir}") logger.info(f"Writing to: {out_dir}") if results_file is None: results_file = os.path.join(out_dir, 'results.csv') # write_mode = 'w' else: results_file = os.path.expanduser(results_file) # write_mode = 'a' fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) part_names, part_names_to_idxs, part_idxs_to_bins = airplanecorpus.loadParts() for i, fn in enumerate(glob.glob(os.path.join(hand_detections_dir, '*.txt'))): video_id = utils.stripExtension(fn).split('.handsdetections')[0] logger.info(f"Processing video {video_id}") hand_detections = airplanecorpus.loadHandDetections( video_id, dir_name=hand_detections_dir, unflatten=True ) hand_detections = hand_detections.reshape(hand_detections.shape[0], -1) mean_detection = np.nanmean(hand_detections) hand_detections[np.isnan(hand_detections)] = mean_detection action_labels = airplanecorpus.loadLabels( video_id, dir_name=labels_dir, part_names_to_idxs=part_names_to_idxs ) bin_labels = makeBinLabels(action_labels, part_idxs_to_bins, hand_detections.shape[0]) fig_fn = os.path.join(fig_dir, f"{video_id}.png") utils.plot_array(hand_detections.T, (bin_labels,), ('bin',), fn=fig_fn) video_id = video_id.replace('_', '-') saveVariable(hand_detections, f'trial={video_id}_feature-seq') saveVariable(bin_labels, f'trial={video_id}_label-seq')
def main( out_dir=None, data_dir=None, results_file=None, cv_file=None, take_log=False, col_format=None, win_params={}, slowfast_csv_params={}): out_dir = os.path.expanduser(out_dir) data_dir = os.path.expanduser(data_dir) results_file = os.path.expanduser(results_file) cv_file = os.path.expanduser(cv_file) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) vocab = utils.loadVariable('vocab', data_dir) metadata = utils.loadMetadata(data_dir) slowfast_labels = pd.read_csv( cv_file, keep_default_na=False, index_col=0, **slowfast_csv_params ) seg_ids = slowfast_labels.index.to_numpy() vid_names = slowfast_labels['video_name'].unique().tolist() metadata['seq_id'] = metadata.index vid_ids = metadata.set_index('dir_name').loc[vid_names].set_index('seq_id').index metadata = metadata.drop('seq_id', axis=1) with open(results_file, 'rb') as file_: model_probs, gt_labels = pickle.load(file_) model_probs = model_probs.numpy() gt_labels = gt_labels.numpy() if len(model_probs) != len(seg_ids): err_str = f"{len(model_probs)} segment scores != {slowfast_labels.shape[0]} CSV rows" raise AssertionError(err_str) logger.info(f"Loaded {len(seg_ids)} segments, {len(vid_ids)} videos") for vid_id, vid_name in zip(vid_ids, vid_names): matches_video = (slowfast_labels['video_name'] == vid_name).to_numpy() win_labels = gt_labels[matches_video] win_probs = model_probs[matches_video, :] if win_labels.shape == win_probs.shape: win_preds = (win_probs > 0.5).astype(int) else: win_preds = win_probs.argmax(axis=1) if take_log: win_probs = np.log(win_probs) seq_id_str = f"seq={vid_id}" utils.saveVariable(win_probs, f'{seq_id_str}_score-seq', out_data_dir) utils.saveVariable(win_labels, f'{seq_id_str}_true-label-seq', out_data_dir) utils.saveVariable(win_preds, f'{seq_id_str}_pred-label-seq', out_data_dir) utils.plot_array( win_probs.T, (win_labels.T, win_preds.T), ('true', 'pred'), tick_names=vocab, fn=os.path.join(fig_dir, f"{seq_id_str}.png"), subplot_width=12, subplot_height=5 ) utils.saveVariable(vocab, 'vocab', out_data_dir) utils.saveMetadata(metadata, out_data_dir)
def main(out_dir=None, bin_scores_dir=None, action_labels_dir=None, subsample_period=None): bin_scores_dir = os.path.expanduser(bin_scores_dir) action_labels_dir = os.path.expanduser(action_labels_dir) out_dir = os.path.expanduser(out_dir) if not os.path.exists(out_dir): os.makedirs(out_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) logger.info(f"Writing to: {out_dir}") def saveVariable(var, var_name): joblib.dump(var, os.path.join(out_data_dir, f'{var_name}.pkl')) def loadAll(seq_ids, var_name, data_dir): def loadOne(seq_id): fn = os.path.join(data_dir, f'trial={seq_id}_{var_name}') return joblib.load(fn) return tuple(map(loadOne, seq_ids)) part_names, part_names_to_idxs, part_idxs_to_bins = airplanecorpus.loadParts( ) trial_ids = utils.getUniqueIds(bin_scores_dir, prefix='trial=') bin_score_seqs = loadAll(trial_ids, 'score-seq.pkl', bin_scores_dir) transition_to_index = {} assembly_label_seqs = [] for i, trial_id in enumerate(trial_ids): video_id = utils.stripExtension(trial_id).replace('-', '_') action_labels = airplanecorpus.loadLabels( video_id, dir_name=action_labels_dir, part_names_to_idxs=part_names_to_idxs) # Make assembly labels from action labels assembly_labels = makeAssemblyLabels(action_labels, part_names, bin_score_seqs[i].shape[0], vocabulary=transition_to_index) assembly_label_seqs.append(assembly_labels) transition_scores, initial_scores, final_scores = su.smoothCounts( *su.countSeqs(assembly_label_seqs), as_scores=True) fn = os.path.join(fig_dir, "transitions.png") su.plot_transitions(fn, transition_scores, initial_scores, final_scores) num_items = len(transition_to_index) transition_vocabulary = {v: k for k, v in transition_to_index.items()} transition_vocabulary = tuple(transition_vocabulary[i] for i in range(num_items)) saveVariable(transition_vocabulary, 'transition-vocabulary') for i, trial_id in enumerate(trial_ids): video_id = utils.stripExtension(trial_id).replace('-', '_') bin_score_seq = bin_score_seqs[i] assembly_labels = assembly_label_seqs[i] assembly_scores = makeAssemblyScores( bin_score_seq, part_names_to_idxs, part_idxs_to_bins, transition_vocabulary, ) assembly_scores = assembly_scores[::subsample_period, :] assembly_labels = assembly_labels[::subsample_period] fig_fn = os.path.join(fig_dir, f"{video_id}.png") utils.plot_array(assembly_scores.T, (assembly_labels, ), ('assembly', ), fn=fig_fn) video_id = video_id.replace('_', '-') saveVariable(assembly_scores, f'trial={video_id}_score-seq') saveVariable(assembly_scores, f'trial={video_id}_feature-seq') saveVariable(assembly_labels, f'trial={video_id}_label-seq')
def main(out_dir=None, data_dir=None, cv_data_dir=None, score_dirs=[], fusion_method='sum', prune_imu=None, standardize=None, decode=None, plot_predictions=None, results_file=None, sweep_param_name=None, gpu_dev_id=None, model_params={}, cv_params={}, train_params={}, viz_params={}): data_dir = os.path.expanduser(data_dir) out_dir = os.path.expanduser(out_dir) score_dirs = tuple(map(os.path.expanduser, score_dirs)) if cv_data_dir is not None: cv_data_dir = os.path.expanduser(cv_data_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) if results_file is None: results_file = os.path.join(out_dir, 'results.csv') else: results_file = os.path.expanduser(results_file) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) def saveVariable(var, var_name): joblib.dump(var, os.path.join(out_data_dir, f'{var_name}.pkl')) def loadAll(seq_ids, var_name, data_dir): def loadOne(seq_id): fn = os.path.join(data_dir, f'trial={seq_id}_{var_name}') return joblib.load(fn) return tuple(map(loadOne, seq_ids)) device = torchutils.selectDevice(gpu_dev_id) # Load data dir_trial_ids = tuple( set(utils.getUniqueIds(d, prefix='trial=', to_array=True)) for d in score_dirs) dir_trial_ids += (set( utils.getUniqueIds(data_dir, prefix='trial=', to_array=True)), ) trial_ids = np.array(list(sorted(set.intersection(*dir_trial_ids)))) for dir_name, t_ids in zip(score_dirs + (data_dir, ), dir_trial_ids): logger.info(f"{len(t_ids)} trial ids from {dir_name}:") logger.info(f" {t_ids}") logger.info(f"{len(trial_ids)} trials in intersection: {trial_ids}") assembly_seqs = loadAll(trial_ids, 'assembly-seq.pkl', data_dir) feature_seqs = tuple( loadAll(trial_ids, 'data-scores.pkl', d) for d in score_dirs) feature_seqs = tuple(zip(*feature_seqs)) # Combine feature seqs include_indices = [] for i, seq_feats in enumerate(feature_seqs): feat_shapes = tuple(f.shape for f in seq_feats) include_seq = all(f == feat_shapes[0] for f in feat_shapes) if include_seq: include_indices.append(i) else: warn_str = ( f'Excluding trial {trial_ids[i]} with mismatched feature shapes: ' f'{feat_shapes}') logger.warning(warn_str) trial_ids = trial_ids[include_indices] assembly_seqs = tuple(assembly_seqs[i] for i in include_indices) feature_seqs = tuple(feature_seqs[i] for i in include_indices) feature_seqs = tuple(np.stack(f) for f in feature_seqs) # Define cross-validation folds if cv_data_dir is None: dataset_size = len(trial_ids) cv_folds = utils.makeDataSplits(dataset_size, **cv_params) cv_fold_trial_ids = tuple( tuple(map(lambda x: trial_ids[x], splits)) for splits in cv_folds) else: fn = os.path.join(cv_data_dir, 'cv-fold-trial-ids.pkl') cv_fold_trial_ids = joblib.load(fn) def getSplit(split_idxs): split_data = tuple( tuple(s[i] for i in split_idxs) for s in (feature_seqs, assembly_seqs, trial_ids)) return split_data gt_scores = [] all_scores = [] num_keyframes_total = 0 num_rgb_errors_total = 0 num_correctable_errors_total = 0 num_oov_total = 0 num_changed_total = 0 for cv_index, (train_ids, test_ids) in enumerate(cv_fold_trial_ids): try: test_idxs = np.array( [trial_ids.tolist().index(i) for i in test_ids]) include_indices.append(cv_index) except ValueError: logger.info( f" Skipping fold {cv_index}: missing test data {test_ids}") logger.info(f'CV fold {cv_index + 1}: {len(trial_ids)} total ' f'({len(train_ids)} train, {len(test_ids)} test)') # TRAIN PHASE if cv_data_dir is None: train_idxs = np.array([trial_ids.index(i) for i in train_ids]) train_assembly_seqs = tuple(assembly_seqs[i] for i in train_idxs) train_assemblies = [] for seq in train_assembly_seqs: list( labels.gen_eq_classes(seq, train_assemblies, equivalent=None)) model = None else: fn = f'cvfold={cv_index}_train-assemblies.pkl' train_assemblies = joblib.load(os.path.join(cv_data_dir, fn)) train_idxs = [ i for i in range(len(trial_ids)) if i not in test_idxs ] fn = f'cvfold={cv_index}_model.pkl' model = joblib.load(os.path.join(cv_data_dir, fn)) train_features, train_assembly_seqs, train_ids = getSplit(train_idxs) if False: train_labels = tuple( np.array( list( labels.gen_eq_classes(assembly_seq, train_assemblies, equivalent=None)), ) for assembly_seq in train_assembly_seqs) train_set = torchutils.SequenceDataset(train_features, train_labels, seq_ids=train_ids, device=device) train_loader = torch.utils.data.DataLoader(train_set, batch_size=1, shuffle=True) train_epoch_log = collections.defaultdict(list) # val_epoch_log = collections.defaultdict(list) metric_dict = { 'Avg Loss': metrics.AverageLoss(), 'Accuracy': metrics.Accuracy() } criterion = torch.nn.CrossEntropyLoss() optimizer_ft = torch.optim.Adam(model.parameters(), lr=1e-3, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False) lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer_ft, step_size=1, gamma=1.00) model = FusionClassifier(num_sources=train_features[0].shape[0]) model, last_model_wts = torchutils.trainModel( model, criterion, optimizer_ft, lr_scheduler, train_loader, # val_loader, device=device, metrics=metric_dict, train_epoch_log=train_epoch_log, # val_epoch_log=val_epoch_log, **train_params) torchutils.plotEpochLog(train_epoch_log, subfig_size=(10, 2.5), title='Training performance', fn=os.path.join( fig_dir, f'cvfold={cv_index}_train-plot.png')) test_assemblies = train_assemblies.copy() for feature_seq, gt_assembly_seq, trial_id in zip( *getSplit(test_idxs)): gt_seq = np.array( list( labels.gen_eq_classes(gt_assembly_seq, test_assemblies, equivalent=None))) if plot_predictions: assembly_fig_dir = os.path.join(fig_dir, 'assembly-imgs') if not os.path.exists(assembly_fig_dir): os.makedirs(assembly_fig_dir) for i, assembly in enumerate(test_assemblies): assembly.draw(assembly_fig_dir, i) # TEST PHASE accuracies = [] for feature_seq, gt_assembly_seq, trial_id in zip( *getSplit(test_idxs)): gt_seq = np.array( list( labels.gen_eq_classes(gt_assembly_seq, test_assemblies, equivalent=None))) num_labels = gt_seq.shape[0] num_features = feature_seq.shape[-1] if num_labels != num_features: err_str = (f"Skipping trial {trial_id}: " f"{num_labels} labels != {num_features} features") logger.info(err_str) continue # Ignore OOV states in ground-truth sample_idxs = np.arange(feature_seq.shape[-1]) score_idxs = gt_seq[gt_seq < feature_seq.shape[1]] sample_idxs = sample_idxs[gt_seq < feature_seq.shape[1]] gt_scores.append(feature_seq[:, score_idxs, sample_idxs]) all_scores.append(feature_seq.reshape(feature_seq.shape[0], -1)) if fusion_method == 'sum': score_seq = feature_seq.sum(axis=0) elif fusion_method == 'rgb_only': score_seq = feature_seq[1] elif fusion_method == 'imu_only': score_seq = feature_seq[0] else: raise NotImplementedError() if not decode: model = None if model is None: pred_seq = score_seq.argmax(axis=0) elif isinstance(model, torch.nn.Module): inputs = torch.tensor(feature_seq[None, ...], dtype=torch.float, device=device) outputs = model.forward(inputs) pred_seq = model.predict(outputs)[0].cpu().numpy() else: dummy_samples = np.arange(score_seq.shape[1]) pred_seq, _, _, _ = model.viterbi(dummy_samples, log_likelihoods=score_seq, ml_decode=(not decode)) pred_assemblies = [train_assemblies[i] for i in pred_seq] gt_assemblies = [test_assemblies[i] for i in gt_seq] acc = metrics.accuracy_upto(pred_assemblies, gt_assemblies, equivalence=None) accuracies.append(acc) rgb_pred_seq = feature_seq[1].argmax(axis=0) num_changed = np.sum(rgb_pred_seq != pred_seq) rgb_is_wrong = rgb_pred_seq != gt_seq num_rgb_errors = np.sum(rgb_is_wrong) imu_scores = feature_seq[0] imu_scores_gt = np.array([ imu_scores[s_idx, t] if s_idx < imu_scores.shape[0] else -np.inf for t, s_idx in enumerate(gt_seq) ]) imu_scores_rgb = np.array([ imu_scores[s_idx, t] if s_idx < imu_scores.shape[0] else -np.inf for t, s_idx in enumerate(rgb_pred_seq) ]) # imu_scores_gt = imu_scores[gt_seq, range(len(rgb_pred_seq))] best_imu_scores = imu_scores.max(axis=0) imu_is_right = imu_scores_gt >= best_imu_scores rgb_pred_score_is_lower = imu_scores_gt > imu_scores_rgb is_correctable_error = rgb_is_wrong & imu_is_right & rgb_pred_score_is_lower num_correctable_errors = np.sum(is_correctable_error) prop_correctable = num_correctable_errors / num_rgb_errors num_oov = np.sum(gt_seq >= len(train_assemblies)) num_states = len(gt_seq) num_keyframes_total += num_states num_rgb_errors_total += num_rgb_errors num_correctable_errors_total += num_correctable_errors num_oov_total += num_oov num_changed_total += num_changed logger.info(f" trial {trial_id}: {num_states} keyframes") logger.info(f" accuracy (fused): {acc * 100:.1f}%") logger.info( f" {num_oov} OOV states ({num_oov / num_states * 100:.1f}%)" ) logger.info( f" {num_rgb_errors} RGB errors; " f"{num_correctable_errors} correctable from IMU ({prop_correctable * 100:.1f}%)" ) saveVariable(score_seq, f'trial={trial_id}_data-scores') saveVariable(pred_assemblies, f'trial={trial_id}_pred-assembly-seq') saveVariable(gt_assemblies, f'trial={trial_id}_gt-assembly-seq') if plot_predictions: io_figs_dir = os.path.join(fig_dir, 'system-io') if not os.path.exists(io_figs_dir): os.makedirs(io_figs_dir) fn = os.path.join(io_figs_dir, f'trial={trial_id:03}.png') utils.plot_array(feature_seq, (gt_seq, pred_seq, score_seq), ('gt', 'pred', 'scores'), fn=fn) score_figs_dir = os.path.join(fig_dir, 'modality-scores') if not os.path.exists(score_figs_dir): os.makedirs(score_figs_dir) plot_scores(feature_seq, k=25, fn=os.path.join(score_figs_dir, f"trial={trial_id:03}.png")) paths_dir = os.path.join(fig_dir, 'path-imgs') if not os.path.exists(paths_dir): os.makedirs(paths_dir) assemblystats.drawPath(pred_seq, trial_id, f"trial={trial_id}_pred-seq", paths_dir, assembly_fig_dir) assemblystats.drawPath(gt_seq, trial_id, f"trial={trial_id}_gt-seq", paths_dir, assembly_fig_dir) label_seqs = (gt_seq, ) + tuple( scores.argmax(axis=0) for scores in feature_seq) label_seqs = np.row_stack(label_seqs) k = 10 for i, scores in enumerate(feature_seq): label_score_seqs = tuple( np.array([ scores[s_idx, t] if s_idx < scores.shape[0] else -np.inf for t, s_idx in enumerate(label_seq) ]) for label_seq in label_seqs) label_score_seqs = np.row_stack(label_score_seqs) drawPaths(label_seqs, f"trial={trial_id}_pred-scores_modality={i}", paths_dir, assembly_fig_dir, path_scores=label_score_seqs) topk_seq = (-scores).argsort(axis=0)[:k, :] path_scores = np.column_stack( tuple(scores[idxs, i] for i, idxs in enumerate(topk_seq.T))) drawPaths(topk_seq, f"trial={trial_id}_topk_modality={i}", paths_dir, assembly_fig_dir, path_scores=path_scores) label_score_seqs = tuple( np.array([ score_seq[s_idx, t] if s_idx < score_seq.shape[0] else -np.inf for t, s_idx in enumerate(label_seq) ]) for label_seq in label_seqs) label_score_seqs = np.row_stack(label_score_seqs) drawPaths(label_seqs, f"trial={trial_id}_pred-scores_fused", paths_dir, assembly_fig_dir, path_scores=label_score_seqs) topk_seq = (-score_seq).argsort(axis=0)[:k, :] path_scores = np.column_stack( tuple(score_seq[idxs, i] for i, idxs in enumerate(topk_seq.T))) drawPaths(topk_seq, f"trial={trial_id}_topk_fused", paths_dir, assembly_fig_dir, path_scores=path_scores) if accuracies: fold_accuracy = float(np.array(accuracies).mean()) # logger.info(f' acc: {fold_accuracy * 100:.1f}%') metric_dict = {'Accuracy': fold_accuracy} utils.writeResults(results_file, metric_dict, sweep_param_name, model_params) num_unexplained_errors = num_rgb_errors_total - ( num_oov_total + num_correctable_errors_total) prop_correctable = num_correctable_errors_total / num_rgb_errors_total prop_oov = num_oov_total / num_rgb_errors_total prop_unexplained = num_unexplained_errors / num_rgb_errors_total prop_changed = num_changed_total / num_keyframes_total logger.info("PERFORMANCE ANALYSIS") logger.info( f" {num_rgb_errors_total} / {num_keyframes_total} " f"RGB errors ({num_rgb_errors_total / num_keyframes_total * 100:.1f}%)" ) logger.info(f" {num_oov_total} / {num_rgb_errors_total} " f"RGB errors are OOV ({prop_oov * 100:.1f}%)") logger.info(f" {num_correctable_errors_total} / {num_rgb_errors_total} " f"RGB errors are correctable ({prop_correctable * 100:.1f}%)") logger.info(f" {num_unexplained_errors} / {num_rgb_errors_total} " f"RGB errors are unexplained ({prop_unexplained * 100:.1f}%)") logger.info( f" {num_changed_total} / {num_keyframes_total} " f"Predictions changed after fusion ({prop_changed * 100:.1f}%)") gt_scores = np.hstack(tuple(gt_scores)) plot_hists(np.exp(gt_scores), fn=os.path.join(fig_dir, "score-hists_gt.png")) all_scores = np.hstack(tuple(all_scores)) plot_hists(np.exp(all_scores), fn=os.path.join(fig_dir, "score-hists_all.png"))
def main(out_dir=None, rgb_data_dir=None, rgb_attributes_dir=None, rgb_vocab_dir=None, imu_data_dir=None, imu_attributes_dir=None, modalities=['rgb', 'imu'], gpu_dev_id=None, plot_predictions=None, results_file=None, sweep_param_name=None, model_params={}, cv_params={}, train_params={}, viz_params={}): out_dir = os.path.expanduser(out_dir) rgb_data_dir = os.path.expanduser(rgb_data_dir) rgb_attributes_dir = os.path.expanduser(rgb_attributes_dir) rgb_vocab_dir = os.path.expanduser(rgb_vocab_dir) imu_data_dir = os.path.expanduser(imu_data_dir) imu_attributes_dir = os.path.expanduser(imu_attributes_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) if results_file is None: results_file = os.path.join(out_dir, 'results.csv') else: results_file = os.path.expanduser(results_file) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) def saveVariable(var, var_name, to_dir=out_data_dir): utils.saveVariable(var, var_name, to_dir) # Load data if modalities == ['rgb']: trial_ids = utils.getUniqueIds(rgb_data_dir, prefix='trial=', to_array=True) logger.info(f"Processing {len(trial_ids)} videos") else: rgb_trial_ids = utils.getUniqueIds(rgb_data_dir, prefix='trial=', to_array=True) imu_trial_ids = utils.getUniqueIds(imu_data_dir, prefix='trial=', to_array=True) trial_ids = np.array( sorted(set(rgb_trial_ids.tolist()) & set(imu_trial_ids.tolist()))) logger.info( f"Processing {len(trial_ids)} videos common to " f"RGB ({len(rgb_trial_ids)} total) and IMU ({len(imu_trial_ids)} total)" ) device = torchutils.selectDevice(gpu_dev_id) dataset = FusionDataset(trial_ids, rgb_attributes_dir, rgb_data_dir, imu_attributes_dir, imu_data_dir, device=device, modalities=modalities) utils.saveMetadata(dataset.metadata, out_data_dir) saveVariable(dataset.vocab, 'vocab') # parts_vocab = loadVariable('parts-vocab') edge_labels = { 'rgb': utils.loadVariable('part-labels', rgb_vocab_dir), 'imu': np.stack([ labels.inSameComponent(a, lower_tri_only=True) for a in dataset.vocab ]) } # edge_labels = revise_edge_labels(edge_labels, input_seqs) attribute_labels = tuple(edge_labels[name] for name in modalities) logger.info('Making transition probs...') transition_probs = make_transition_scores(dataset.vocab) saveVariable(transition_probs, 'transition-probs') model = AttributeModel(*attribute_labels, device=device) if plot_predictions: figsize = (12, 3) fig, axis = plt.subplots(1, figsize=figsize) axis.imshow(edge_labels['rgb'].T, interpolation='none', aspect='auto') plt.savefig(os.path.join(fig_dir, "edge-labels.png")) plt.close() for i, trial_id in enumerate(trial_ids): logger.info(f"Processing sequence {trial_id}...") trial_prefix = f"trial={trial_id}" true_label_seq = dataset.loadTargets(trial_id) attribute_feats = dataset.loadInputs(trial_id) score_seq = model(attribute_feats) pred_label_seq = model.predict(score_seq) attribute_feats = attribute_feats.cpu().numpy() score_seq = score_seq.cpu().numpy() true_label_seq = true_label_seq.cpu().numpy() pred_label_seq = pred_label_seq.cpu().numpy() saveVariable(score_seq.T, f'{trial_prefix}_score-seq') saveVariable(true_label_seq.T, f'{trial_prefix}_label-seq') if plot_predictions: fn = os.path.join(fig_dir, f'{trial_prefix}.png') utils.plot_array(attribute_feats.T, (true_label_seq, pred_label_seq, score_seq), ('gt', 'pred', 'scores'), fn=fn) metric_dict = eval_metrics(pred_label_seq, true_label_seq) for name, value in metric_dict.items(): logger.info(f" {name}: {value * 100:.2f}%") utils.writeResults(results_file, metric_dict, sweep_param_name, model_params)
def main(out_dir=None, data_dir=None, attributes_dir=None, use_gt_segments=None, segments_dir=None, cv_data_dir=None, ignore_trial_ids=None, gpu_dev_id=None, plot_predictions=None, results_file=None, sweep_param_name=None, model_params={}, cv_params={}, train_params={}, viz_params={}): data_dir = os.path.expanduser(data_dir) out_dir = os.path.expanduser(out_dir) attributes_dir = os.path.expanduser(attributes_dir) if segments_dir is not None: segments_dir = os.path.expanduser(segments_dir) if cv_data_dir is not None: cv_data_dir = os.path.expanduser(cv_data_dir) if results_file is None: results_file = os.path.join(out_dir, 'results.csv') else: results_file = os.path.expanduser(results_file) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) def saveVariable(var, var_name): joblib.dump(var, os.path.join(out_data_dir, f'{var_name}.pkl')) def loadAll(seq_ids, var_name, data_dir, prefix='trial='): def loadOne(seq_id): fn = os.path.join(data_dir, f'{prefix}{seq_id}_{var_name}') return joblib.load(fn) return tuple(map(loadOne, seq_ids)) # Load data trial_ids = utils.getUniqueIds(data_dir, prefix='trial=', to_array=True) assembly_seqs = loadAll(trial_ids, 'assembly-seq.pkl', data_dir, prefix='trial=') # Define cross-validation folds if cv_data_dir is None: dataset_size = len(trial_ids) cv_folds = utils.makeDataSplits(dataset_size, **cv_params) cv_fold_trial_ids = tuple( tuple(map(lambda x: trial_ids[x], splits)) for splits in cv_folds) else: fn = os.path.join(cv_data_dir, 'cv-fold-trial-ids.pkl') cv_fold_trial_ids = joblib.load(fn) test_ids = set().union(*tuple(set(fold[-1]) for fold in cv_fold_trial_ids)) logger.info(f"{len(test_ids)} final test ids: {test_ids}") def getSplit(split_idxs): split_data = tuple( tuple(s[i] for i in split_idxs) for s in (assembly_seqs, trial_ids)) return split_data for cv_index, (train_ids, test_ids) in enumerate(cv_fold_trial_ids): logger.info(f'CV fold {cv_index + 1}: {len(trial_ids)} total ' f'({len(train_ids)} train, {len(test_ids)} test)') try: test_idxs = torch.tensor( [trial_ids.tolist().index(i) for i in test_ids]) except ValueError: logger.info(" Skipping fold: missing test data") continue # TRAIN PHASE if cv_data_dir is None: train_idxs = torch.tensor([trial_ids.index(i) for i in train_ids]) train_assembly_seqs = tuple(assembly_seqs[i] for i in train_idxs) train_assemblies = [] for seq in train_assembly_seqs: list( labels.gen_eq_classes(seq, train_assemblies, equivalent=None)) else: fn = os.path.join(cv_data_dir, f'cvfold={cv_index}_train-assemblies.pkl') train_assemblies = joblib.load(fn) signatures = make_signatures(train_assemblies) if plot_predictions: figsize = (12, 3) fig, axis = plt.subplots(1, figsize=figsize) axis.imshow(signatures.numpy().T, interpolation='none', aspect='auto') plt.savefig( os.path.join(fig_dir, f"cvfold={cv_index}_signatures.png")) plt.close() # TEST PHASE accuracies = {'assembly': [], 'assembly_upto_eq': []} for gt_assembly_seq, trial_id in zip(*getSplit(test_idxs)): # if ignore_trial_ids is not None and trial_id in ignore_trial_ids: if False: logger.info(f" Ignoring trial {trial_id} in test fold") continue # FIXME: implement consistent data dimensions during serialization # (ie samples along rows) # feature_seq shape is (pairs, samples, classes) # should be (samples, pairs, classes) try: fn = os.path.join(attributes_dir, f'trial={trial_id}_score-seq.pkl') feature_seq = joblib.load(fn) except FileNotFoundError: logger.info(f" File not found: {fn}") continue test_assemblies = train_assemblies.copy() gt_assembly_id_seq = list( labels.gen_eq_classes(gt_assembly_seq, test_assemblies, equivalent=None)) gt_seq = makeAssemblyLabels(gt_assembly_id_seq, gt_assembly_seq) if use_gt_segments: segments = utils.makeSegmentLabels(gt_seq) elif segments_dir is not None: var_name = 'segment-seq-imu.pkl' fn = os.path.join(segments_dir, f'trial={trial_id}_{var_name}') try: segments = joblib.load(fn) except FileNotFoundError: logger.info(f" File not found: {fn}") continue else: segments = None feature_seq = torch.tensor(feature_seq, dtype=torch.float) if segments is not None: feature_seq = feature_seq.transpose(0, 1) feature_seq, _ = utils.reduce_over_segments( feature_seq.numpy(), segments, reduction=lambda x: x.mean(axis=0)) feature_seq = torch.tensor(feature_seq.swapaxes(0, 1), dtype=torch.float) gt_seq, _ = utils.reduce_over_segments(gt_seq.numpy(), segments) gt_seq = torch.tensor(gt_seq, dtype=torch.long) feature_seq = make_features(feature_seq) score_seq = score(feature_seq, signatures) pred_seq = predict(score_seq) pred_assemblies = [train_assemblies[i] for i in pred_seq] gt_assemblies = [test_assemblies[i] for i in gt_seq] acc = metrics.accuracy_upto(pred_assemblies, gt_assemblies, equivalence=components_equivalent) accuracies['assembly_upto_eq'].append(acc) acc = metrics.accuracy_upto(pred_assemblies, gt_assemblies) accuracies['assembly'].append(acc) # FIXME: Improve data naming convention in decode_keyframes.py saveVariable(score_seq, f'trial={trial_id}_data-scores') if plot_predictions: fn = os.path.join(fig_dir, f'trial={trial_id:03}.png') utils.plot_array( feature_seq, (gt_seq.numpy(), pred_seq.numpy(), score_seq.numpy()), ('gt', 'pred', 'scores'), fn=fn) if not any(v for v in accuracies.values()): continue metric_dict = {} for name, fold_accuracies in accuracies.items(): fold_accuracy = float(torch.tensor(fold_accuracies).mean()) logger.info(f' {name}: {fold_accuracy * 100:.1f}%') metric_dict[name] = fold_accuracy utils.writeResults(results_file, metric_dict, sweep_param_name, model_params)
def main( out_dir=None, data_dir=None, part_symmetries=None, plot_output=None, results_file=None, sweep_param_name=None, start_from=None): if part_symmetries is None: part_symmetries = [ ['frontbeam_hole_1', 'frontbeam_hole_2', 'backbeam_hole_1', 'backbeam_hole_2'], ['cushion_hole_1', 'cushion_hole_2'] ] part_symmetries = { symms[i]: symms for symms in part_symmetries for i in range(len(symms)) } data_dir = os.path.expanduser(data_dir) out_dir = os.path.expanduser(out_dir) if not os.path.exists(out_dir): os.makedirs(out_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) debug_dir = os.path.join(out_dir, 'debug') if not os.path.exists(debug_dir): os.makedirs(debug_dir) def saveVariable(var, var_name): joblib.dump(var, os.path.join(out_data_dir, f'{var_name}.pkl')) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) logger.info(f"Reading from: {data_dir}") logger.info(f"Writing to: {out_dir}") if results_file is None: results_file = os.path.join(out_dir, 'results.csv') # write_mode = 'w' else: results_file = os.path.expanduser(results_file) # write_mode = 'a' fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) labels_dir = os.path.join(data_dir, 'labels') with open(os.path.join(labels_dir, 'action_and_part_names.yaml'), 'rt') as f: names = yaml.safe_load(f) action_names = names['action_names'] action_name_to_index = {name: i for i, name in enumerate(action_names)} part_names = names['part_names'] part_name_to_index = {name: i for i, name in enumerate(part_names)} video_ids = [] all_label_arrs = [] for label_fn in glob.glob(os.path.join(labels_dir, "*.csv")): video_id = utils.stripExtension(label_fn) labels_arr = pd.read_csv(label_fn) all_label_arrs.append(labels_arr) video_ids.append(video_id) pose_dir = os.path.join(data_dir, 'poses') pose_ids = tuple( video_id for video_id in video_ids if os.path.exists(os.path.join(pose_dir, video_id)) ) keep_ids = tuple(v_id in pose_ids for v_id in video_ids) logger.info( f"Ignoring {len(keep_ids) - sum(keep_ids)} video(s) with missing data: " f"{', '.join([v_id for v_id, keep in zip(video_ids, keep_ids) if not keep])}" ) def filterSeq(seq): return tuple(x for x, keep_id in zip(seq, keep_ids) if keep_id) all_label_arrs = filterSeq(all_label_arrs) video_ids = filterSeq(video_ids) assembly_vocab = [] label_seqs = [] for i, video_id in enumerate(video_ids): if start_from is not None and i < start_from: continue logger.info("PROCESSING VIDEO {0}: {1}".format(i, video_id)) labels_arr = all_label_arrs[i] video_dir = os.path.join(pose_dir, video_id) def loadFile(part_name): path = os.path.join(video_dir, f'{part_name}.csv') arr = pd.read_csv(path) return arr part_data = tuple(loadFile(part_name) for part_name in part_names) poses_seq = np.stack(tuple(arr.values for arr in part_data), axis=-1) feature_seq = relativePose(poses_seq, lower_tri_only=True, magnitude_only=True) label_seq = actionLabels( labels_arr, feature_seq.shape[0], action_name_to_index, part_name_to_index ) part_pair_names = makePairs(part_names, lower_tri_only=True) is_possible = possibleConnections(part_pair_names) feature_seq = feature_seq[:, is_possible, :] label_seq = label_seq[:, is_possible] part_pair_names = tuple(n for (b, n) in zip(is_possible, part_pair_names) if b) utils.plot_multi( np.moveaxis(feature_seq, (0, 1, 2), (-1, 0, 1)), label_seq.T, axis_names=part_pair_names, label_name='action', feature_names=('translation_dist', 'rotation_dist'), tick_names=[''] + action_names, fn=os.path.join(fig_dir, f"{video_id}_actions.png") ) label_seq = assemblyLabels( labels_arr, feature_seq.shape[0], assembly_vocab=assembly_vocab, symmetries=part_symmetries ) # expanded_label_seq = _assemblyLabels( # labels_arr, feature_seq.shape[0], # assembly_vocab=assembly_vocab, symmetries=part_symmetries # ) utils.plot_array( feature_seq.sum(axis=-1).T, (label_seq,), ('assembly',), fn=os.path.join(fig_dir, f"{video_id}_assemblies.png") ) label_seqs.append(label_seq) label_segments, __ = utils.computeSegments(label_seq) assembly_segments = [assembly_vocab[i] for i in label_segments] lib_assembly.writeAssemblies( os.path.join(debug_dir, f'trial={video_id}_assembly-seq.txt'), assembly_segments ) video_id = video_id.replace('_', '-') saveVariable(feature_seq, f'trial={video_id}_feature-seq') saveVariable(label_seq, f'trial={video_id}_label-seq') # saveVariable(expanded_label_seq, f'trial={video_id}_expanded-label-seq') if False: from seqtools import utils as su transition_probs, start_probs, end_probs = su.smoothCounts( *su.countSeqs(label_seqs) ) # import pdb; pdb.set_trace() lib_assembly.writeAssemblies( os.path.join(debug_dir, 'assembly-vocab.txt'), assembly_vocab ) saveVariable(assembly_vocab, 'assembly-vocab') with open(os.path.join(out_data_dir, 'action-vocab.yaml'), 'wt') as f: yaml.dump(action_names, f) with open(os.path.join(out_data_dir, 'part-vocab.yaml'), 'wt') as f: yaml.dump(part_names, f)
def main(out_dir=None, data_dir=None, metadata_file=None, metric_names=None, ignore_initial_state=None, draw_paths=None, plot_predictions=None, results_file=None, sweep_param_name=None): if metric_names is None: metric_names = ('accuracy', 'edit_score', 'overlap_score') logger.info(f"Reading from: {data_dir}") logger.info(f"Writing to: {out_dir}") data_dir = os.path.expanduser(data_dir) out_dir = os.path.expanduser(out_dir) if results_file is None: results_file = os.path.join(out_dir, 'results.csv') else: results_file = os.path.expanduser(results_file) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) def loadVariable(var_name): return joblib.load(os.path.join(data_dir, f'{var_name}.pkl')) def loadAll(seq_ids, var_name): def loadOne(seq_id): fn = os.path.join(data_dir, f'trial={seq_id}_{var_name}') return joblib.load(fn) return tuple(map(loadOne, seq_ids)) trial_ids = utils.getUniqueIds(data_dir, prefix='trial=', to_array=True) pred_assembly_seqs = loadAll(trial_ids, "pred-assembly-seq.pkl") gt_assembly_seqs = loadAll(trial_ids, "gt-assembly-seq.pkl") all_assemblies = [] gt_assembly_label_seqs = tuple( np.array(list(labels.gen_eq_classes(gt_assembly_seq, all_assemblies))) for gt_assembly_seq in gt_assembly_seqs) pred_assembly_label_seqs = tuple( np.array(list(labels.gen_eq_classes(pred_assembly_seq, all_assemblies))) for pred_assembly_seq in pred_assembly_seqs) def checkPredictions(pred_assembly_label_seqs, gt_assembly_label_seqs, trial_ids): for i, trial_id in enumerate(trial_ids): pred_seq = pred_assembly_label_seqs[i] true_seq = gt_assembly_label_seqs[i] train_seqs = gt_assembly_label_seqs[:i] + gt_assembly_label_seqs[ i + 1:] train_vocab = np.unique(np.hstack(train_seqs)) pred_is_correct = pred_seq == true_seq pred_in_test_vocab = np.array( [np.any(train_vocab == i) for i in pred_seq]) pred_is_oov_and_correct = ~pred_in_test_vocab * pred_is_correct if not pred_in_test_vocab.all(): warn_str = ( f"trial {trial_id}: {np.sum(~pred_in_test_vocab)} / " f"{len(pred_in_test_vocab)} preds not in test; " f"{np.sum(pred_is_oov_and_correct)} oov preds are correct") logger.warning(warn_str) checkPredictions(pred_assembly_label_seqs, gt_assembly_label_seqs, trial_ids) def estimate_oov_rate(label_seqs): num_oov = 0 num_items = 0 for i in range(len(label_seqs)): test_seq = label_seqs[i] train_seqs = label_seqs[:i] + label_seqs[i + 1:] train_vocab = np.unique(np.hstack(train_seqs)) num_oov += sum( float(not np.any(train_vocab == i)) for i in test_seq) num_items += len(test_seq) return num_oov / num_items oov_rate = estimate_oov_rate(gt_assembly_label_seqs) logger.info(f"OOV rate: {oov_rate * 100:.2f}%") pred_action_seqs = tuple(map(actionsFromAssemblies, pred_assembly_seqs)) gt_action_seqs = tuple(map(actionsFromAssemblies, gt_assembly_seqs)) all_actions = [] gt_action_label_seqs = tuple( np.array(list(labels.gen_eq_classes(gt_action_seq, all_actions))) for gt_action_seq in gt_action_seqs) pred_action_label_seqs = tuple( np.array(list(labels.gen_eq_classes(pred_action_seq, all_actions))) for pred_action_seq in pred_action_seqs) if draw_paths: assembly_fig_dir = os.path.join(fig_dir, 'assembly-imgs') if not os.path.exists(assembly_fig_dir): os.makedirs(assembly_fig_dir) for i, assembly in enumerate(all_assemblies): assembly.draw(assembly_fig_dir, i) action_fig_dir = os.path.join(fig_dir, 'action-imgs') if not os.path.exists(action_fig_dir): os.makedirs(action_fig_dir) for i, action in enumerate(all_actions): action.draw(action_fig_dir, i) logger.info(f"Evaluating {len(trial_ids)} sequence predictions") batch = [] for i, trial_id in enumerate(trial_ids): logger.info(f"VIDEO {trial_id}:") pred_assembly_index_seq = pred_assembly_label_seqs[i] true_assembly_index_seq = gt_assembly_label_seqs[i] if ignore_initial_state: pred_assembly_index_seq = pred_assembly_index_seq[1:] true_assembly_index_seq = true_assembly_index_seq[1:] pred_action_index_seq = pred_action_label_seqs[i] true_action_index_seq = gt_action_label_seqs[i] if draw_paths: drawPaths([pred_assembly_index_seq, true_assembly_index_seq], f"trial={trial_id}_assembly-paths", fig_dir, assembly_fig_dir, path_labels=None, img_ext='png') drawPaths([pred_action_index_seq, true_action_index_seq], f"trial={trial_id}_action-paths", fig_dir, action_fig_dir, path_labels=None, img_ext='png') metric_dict = {} for name in metric_names: key = f"{name}_action" value = getattr(LCTM.metrics, name)(pred_action_index_seq, true_action_index_seq) / 100 metric_dict[key] = value logger.info(f" {key}: {value * 100:.1f}%") key = f"{name}_assembly" value = getattr(LCTM.metrics, name)(pred_assembly_index_seq, true_assembly_index_seq) / 100 metric_dict[key] = value logger.info(f" {key}: {value * 100:.1f}%") utils.writeResults(results_file, metric_dict, sweep_param_name, {}) batch.append( (pred_action_index_seq, None, true_action_index_seq, trial_id)) if plot_predictions: label_names = ('gt', 'pred') for preds, inputs, gt_labels, seq_id in batch: fn = os.path.join(fig_dir, f"trial={seq_id}_model-io.png") utils.plot_array(inputs, (gt_labels, preds), label_names, fn=fn, labels_together=True)
def main(out_dir=None, data_dir=None, model_name=None, part_symmetries=None, gpu_dev_id=None, batch_size=None, learning_rate=None, model_params={}, cv_params={}, train_params={}, viz_params={}, plot_predictions=None, results_file=None, sweep_param_name=None): if part_symmetries is None: part_symmetries = { 'beam_side': ('backbeam_hole_1', 'backbeam_hole_2', 'frontbeam_hole_1', 'frontbeam_hole_2'), 'beam_top': ('backbeam_hole_3', 'frontbeam_hole_3'), 'backrest': ('backrest_hole_1', 'backrest_hole_2') } data_dir = os.path.expanduser(data_dir) out_dir = os.path.expanduser(out_dir) if not os.path.exists(out_dir): os.makedirs(out_dir) logger = utils.setupRootLogger(filename=os.path.join(out_dir, 'log.txt')) if results_file is None: results_file = os.path.join(out_dir, 'results.csv') else: results_file = os.path.expanduser(results_file) fig_dir = os.path.join(out_dir, 'figures') if not os.path.exists(fig_dir): os.makedirs(fig_dir) out_data_dir = os.path.join(out_dir, 'data') if not os.path.exists(out_data_dir): os.makedirs(out_data_dir) def saveVariable(var, var_name): joblib.dump(var, os.path.join(out_data_dir, f'{var_name}.pkl')) def loadAll(seq_ids, var_name, data_dir): def loadOne(seq_id): fn = os.path.join(data_dir, f'trial={seq_id}_{var_name}') return joblib.load(fn) return tuple(map(loadOne, seq_ids)) # Load vocab with open(os.path.join(data_dir, "part-vocab.yaml"), 'rt') as f: link_vocab = yaml.safe_load(f) assembly_vocab = joblib.load(os.path.join(data_dir, 'assembly-vocab.pkl')) # Load data trial_ids = utils.getUniqueIds(data_dir, prefix='trial=') feature_seqs = loadAll(trial_ids, 'feature-seq.pkl', data_dir) label_seqs = loadAll(trial_ids, 'label-seq.pkl', data_dir) if part_symmetries: # Construct equivalence classes from vocab eq_classes, assembly_eq_classes, eq_class_vocab = makeEqClasses( assembly_vocab, part_symmetries) lib_assembly.writeAssemblies( os.path.join(fig_dir, 'eq-class-vocab.txt'), eq_class_vocab) label_seqs = tuple(assembly_eq_classes[label_seq] for label_seq in label_seqs) saveVariable(eq_class_vocab, 'assembly-vocab') else: eq_classes = None def impute_nan(input_seq): input_is_nan = np.isnan(input_seq) logger.info(f"{input_is_nan.sum()} NaN elements") input_seq[input_is_nan] = 0 # np.nanmean(input_seq) return input_seq # feature_seqs = tuple(map(impute_nan, feature_seqs)) for trial_id, label_seq, feat_seq in zip(trial_ids, label_seqs, feature_seqs): saveVariable(feat_seq, f"trial={trial_id}_feature-seq") saveVariable(label_seq, f"trial={trial_id}_label-seq") device = torchutils.selectDevice(gpu_dev_id) # Define cross-validation folds dataset_size = len(trial_ids) cv_folds = utils.makeDataSplits(dataset_size, **cv_params) def getSplit(split_idxs): split_data = tuple( tuple(s[i] for i in split_idxs) for s in (feature_seqs, label_seqs, trial_ids)) return split_data for cv_index, cv_splits in enumerate(cv_folds): train_data, val_data, test_data = tuple(map(getSplit, cv_splits)) train_feats, train_labels, train_ids = train_data train_set = torchutils.SequenceDataset(train_feats, train_labels, device=device, labels_dtype=torch.long, seq_ids=train_ids, transpose_data=True) train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size, shuffle=True) test_feats, test_labels, test_ids = test_data test_set = torchutils.SequenceDataset(test_feats, test_labels, device=device, labels_dtype=torch.long, seq_ids=test_ids, transpose_data=True) test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size, shuffle=False) val_feats, val_labels, val_ids = val_data val_set = torchutils.SequenceDataset(val_feats, val_labels, device=device, labels_dtype=torch.long, seq_ids=val_ids, transpose_data=True) val_loader = torch.utils.data.DataLoader(val_set, batch_size=batch_size, shuffle=True) logger.info( f'CV fold {cv_index + 1} / {len(cv_folds)}: {len(trial_ids)} total ' f'({len(train_ids)} train, {len(val_ids)} val, {len(test_ids)} test)' ) input_dim = train_set.num_obsv_dims output_dim = train_set.num_label_types if model_name == 'linear': model = torchutils.LinearClassifier( input_dim, output_dim, **model_params).to(device=device) elif model_name == 'dummy': model = DummyClassifier(input_dim, output_dim, **model_params) elif model_name == 'AssemblyClassifier': model = AssemblyClassifier(assembly_vocab, link_vocab, eq_classes=eq_classes, **model_params) else: raise AssertionError() criterion = torch.nn.CrossEntropyLoss() if model_name != 'dummy': train_epoch_log = collections.defaultdict(list) val_epoch_log = collections.defaultdict(list) metric_dict = { 'Avg Loss': metrics.AverageLoss(), 'Accuracy': metrics.Accuracy(), 'Precision': metrics.Precision(), 'Recall': metrics.Recall(), 'F1': metrics.Fmeasure() } optimizer_ft = torch.optim.Adam(model.parameters(), lr=learning_rate, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False) lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer_ft, step_size=1, gamma=1.00) model, last_model_wts = torchutils.trainModel( model, criterion, optimizer_ft, lr_scheduler, train_loader, val_loader, device=device, metrics=metric_dict, train_epoch_log=train_epoch_log, val_epoch_log=val_epoch_log, **train_params) logger.info(f'scale={float(model._scale)}') logger.info(f'alpha={float(model._alpha)}') # Test model metric_dict = { 'Avg Loss': metrics.AverageLoss(), 'Accuracy': metrics.Accuracy(), 'Precision': metrics.Precision(), 'Recall': metrics.Recall(), 'F1': metrics.Fmeasure() } test_io_history = torchutils.predictSamples( model.to(device=device), test_loader, criterion=criterion, device=device, metrics=metric_dict, data_labeled=True, update_model=False, seq_as_batch=train_params['seq_as_batch'], return_io_history=True) metric_str = ' '.join(str(m) for m in metric_dict.values()) logger.info('[TST] ' + metric_str) d = {k: v.value for k, v in metric_dict.items()} utils.writeResults(results_file, d, sweep_param_name, model_params) if plot_predictions: io_fig_dir = os.path.join(fig_dir, 'model-io') if not os.path.exists(io_fig_dir): os.makedirs(io_fig_dir) label_names = ('gt', 'pred') preds, scores, inputs, gt_labels, ids = zip(*test_io_history) for batch in test_io_history: batch = tuple( x.cpu().numpy() if isinstance(x, torch.Tensor) else x for x in batch) for preds, _, inputs, gt_labels, seq_id in zip(*batch): fn = os.path.join(io_fig_dir, f"trial={seq_id}_model-io.png") utils.plot_array(inputs.sum(axis=-1), (gt_labels, preds), label_names, fn=fn, **viz_params) def saveTrialData(pred_seq, score_seq, feat_seq, label_seq, trial_id): saveVariable(pred_seq, f'trial={trial_id}_pred-label-seq') saveVariable(score_seq, f'trial={trial_id}_score-seq') saveVariable(label_seq, f'trial={trial_id}_true-label-seq') for batch in test_io_history: batch = tuple(x.cpu().numpy() if isinstance(x, torch.Tensor) else x for x in batch) for io in zip(*batch): saveTrialData(*io) saveVariable(train_ids, f'cvfold={cv_index}_train-ids') saveVariable(test_ids, f'cvfold={cv_index}_test-ids') saveVariable(val_ids, f'cvfold={cv_index}_val-ids') saveVariable(train_epoch_log, f'cvfold={cv_index}_{model_name}-train-epoch-log') saveVariable(val_epoch_log, f'cvfold={cv_index}_{model_name}-val-epoch-log') saveVariable(metric_dict, f'cvfold={cv_index}_{model_name}-metric-dict') saveVariable(model, f'cvfold={cv_index}_{model_name}-best') train_fig_dir = os.path.join(fig_dir, 'train-plots') if not os.path.exists(train_fig_dir): os.makedirs(train_fig_dir) if train_epoch_log: torchutils.plotEpochLog(train_epoch_log, subfig_size=(10, 2.5), title='Training performance', fn=os.path.join( train_fig_dir, f'cvfold={cv_index}_train-plot.png')) if val_epoch_log: torchutils.plotEpochLog(val_epoch_log, subfig_size=(10, 2.5), title='Heldout performance', fn=os.path.join( train_fig_dir, f'cvfold={cv_index}_val-plot.png'))