def main():
    event_type = 'chisq'  # distribution of the hidden score for each stream
    seed_events = 500  # number of events to use on the first round of training
    update_events = 1500  # number of total events occurring in each round of batch update
    analysis_events = 1000  # number of events to use on each round of analysis
    ps = [0.5, 0.5, 0.5]  # fraction of class 1 examples in each stream
    seeds = [42, 13, 79]  # random seeds for each stream
    gs = [1., 1., 1.]  # gains to use in weighing each stream probability
    num_inputs = 10  # number of inputs in each stream
    classifier_kind = 'gbm'  # classifier to use
    criterion = 'competing_streams'  # type of selection condition
    batch_updates = 12  # number of batch updates to run for the models
    file_descriptor = 'seed%d_update%d_' % (seed_events, update_events)  # will be used for figure names
    datetimestr = datetime.datetime.now().strftime("%Y%B%d-%H%M")
    dirname = event_type + '-' + datetimestr
    if not os.path.exists(dirname):
        os.makedirs(dirname)
    save_metadata(event_type, seed_events, update_events, analysis_events, ps, seeds, num_inputs,
                  classifier_kind, criterion, batch_updates, file_descriptor, dirname)
    pn = plot_namer(dirname=dirname)

    # EventGenerators
    eg1 = EG(seed=seeds[0], num_inputs=num_inputs, kind=event_type, balance=ps[0])
    eg2 = EG(seed=seeds[1], num_inputs=num_inputs, kind=event_type, balance=ps[1])
    eg3 = EG(seed=seeds[2], num_inputs=num_inputs, kind=event_type, balance=ps[2])

    # ModelUpdaters
    mu1 = MU(kind=classifier_kind)
    mu2 = MU(kind=classifier_kind)
    mu3 = MU(kind=classifier_kind)

    # EventSelector
    es = ES(criterion=criterion)
    # TrainDataUpdaters
    tdu = TDU(num_events=seed_events)
    tdua = TDU(num_events=analysis_events)

    x1old, x2old, x3old = None, None, None
    y1old, y2old, y3old = None, None, None
    x1old_an, x2old_an, x3old_an = None, None, None
    y1old_an, y2old_an, y3old_an = None, None, None

    # global behavior: optimal logloss, and KL distributions at each batch update
    ll_cols = ['update_index', 'logloss_S1', 'logloss_S2', 'logloss_S3']
    kl_cols = ['update_index', 'KL_S1', 'KL_S2', 'KL_S3']
    df_lgls = pd.DataFrame(columns=ll_cols)
    df_kl = pd.DataFrame(columns=kl_cols)

    for batch_update in range(batch_updates):
        # create train stream events
        if batch_update == 0:  # on the first iteration use seed events, otherwise use update_event
            events = seed_events
        else:
            events = update_events
        x1r, y1r = eg1.get(events)
        x2r, y2r = eg2.get(events)
        x3r, y3r = eg3.get(events)
        # create analysis stream events
        x1a, y1a = eg1.get(analysis_events)
        x2a, y2a = eg2.get(analysis_events)
        x3a, y3a = eg3.get(analysis_events)

        # pass events through current models filter
        if batch_update == 0:
            xs, ys = es.filter(xs=(x1r, x2r, x3r), ys=(y1r, y2r, y3r),
                               models=(None, None, None), event_gains=ps)
            xsaf, ysaf = es.filter(xs=(x1a, x2a, x3a), ys=(y1a, y2a, y3a),
                                   models=(None, None, None), event_gains=ps)
        else:
            xs, ys = es.filter(xs=(x1r, x2r, x3r), ys=(y1r, y2r, y3r),
                               models=(m1, m2, m3), event_gains=ps)
            xsaf, ysaf = es.filter(xs=(x1a, x2a, x3a), ys=(y1a, y2a, y3a),
                                   models=(m1, m2, m3), event_gains=ps)
        x1, x2, x3 = xs
        y1, y2, y3 = ys
        x1af, x2af, x3af = xsaf
        y1af, y2af, y3af = ysaf
        print '---- Event Selector ----'
        print 'New events at %d:' % batch_update
        print x1.shape[0], x2.shape[0], x3.shape[0]

        # update train data
        X1u, Y1u = tdu.update(x1old, y1old, x1, y1)
        X2u, Y2u = tdu.update(x2old, y2old, x2, y2)
        X3u, Y3u = tdu.update(x3old, y3old, x3, y3)
        X1ua, Y1ua = tdua.update(x1old_an, y1old_an, x1af, y1af)
        X2ua, Y2ua = tdua.update(x2old_an, y2old_an, x2af, y2af)
        X3ua, Y3ua = tdua.update(x3old_an, y3old_an, x3af, y3af)

        # update models using new data
        m1 = mu1.train(X1u, Y1u, learning_rate=[0.005, 0.01, 0.03, 0.06, 0.1], n_estimators=[250],
                       subsample=0.5, max_depth=[2, 3], random_state=13, folds=5)
        m2 = mu2.train(X2u, Y2u, learning_rate=[0.005, 0.01, 0.03, 0.06, 0.1], n_estimators=[250],
                       subsample=0.5, max_depth=[2, 3], random_state=13, folds=5)
        m3 = mu3.train(X3u, Y3u, learning_rate=[0.005, 0.01, 0.03, 0.06, 0.1], n_estimators=[250],
                       subsample=0.5, max_depth=[2, 3], random_state=13, folds=5)

        # lookahead: pass events through updated models filter
        xsaf, ysaf = es.filter(xs=(x1a, x2a, x3a), ys=(y1a, y2a, y3a),
                               models=(m1, m2, m3), event_gains=ps)
        x1afnew, x2afnew, x3afnew = xsaf
        y1afnew, y2afnew, y3afnew = ysaf

        # look at distribution shifts and algorithm performance
        print '--- Data Tomographer ---'
        print 'Old model events at %d:' % batch_update
        print x1af.shape[0], x2af.shape[0], x3af.shape[0]
        print ''

        # unbiased data vs old biased data on updated model
        dt = DT(xrefs=[x1af, x2af, x3af], yrefs=[y1af, y2af, y3af],
                xus=[x1a, x2a, x3a], yus=[y1a, y2a, y3a],
                models=[m1, m2, m3])
        dt.plot_kl(ntiles=10, rule='auto', prior=1e-8, verbose=False,
                   saveas=pn('unbiased_feature_kl_' + file_descriptor + str(int(time()))))
        dt.plot_stagewise(metric='logloss', verbose=False,
                          saveas=pn('unbiased_stagewise_logloss_' + file_descriptor + str(int(time()))))
        # question: Does the distribution of data through model converge to some value?
        kls = dt.kuhl_leib(ntiles=10, rule='auto', prior=1e-8, verbose=False)
        mean_kls = [np.mean(kl) for kl in kls]
        df = pd.DataFrame(data=[[batch_update] + mean_kls], columns=kl_cols)
        df_kl = df_kl.append(df, ignore_index=True)

        # lookahead: old biased data vs new biased data on updated model
        dt = DT(xrefs=[x1af, x2af, x3af], yrefs=[y1af, y2af, y3af],
                xus=[x1afnew, x2afnew, x3afnew], yus=[y1afnew, y2afnew, y3afnew],
                models=[m1, m2, m3])
        dt.plot_hist(ntiles=10, rule='auto', minimal=True, plot_selection=([2], [9]), x_axis=(-3.5, 3.5),
                     saveas=pn('biased_feature_histogram_' + str(int(time()))), color='b', edgecolor='none', alpha=0.5)
        dt.plot_kl(ntiles=10, rule='auto', prior=1e-8, verbose=False, saveas=pn('biased_feature_kl_'+file_descriptor))
        dt.plot_stagewise(metric='logloss', verbose=False,
                          saveas=pn('biased_stagewise_logloss_'+file_descriptor + str(int(time()))))
        # question: Does the logloss on future data converge to some value?
        ll_af, ll_afnew = dt.stagewise_metric(metric='logloss', verbose=False)
        df = pd.DataFrame(data=[[batch_update] + [lls[-1] for lls in ll_afnew]], columns=ll_cols)
        df_lgls = df_lgls.append(df, ignore_index=True)

        # create "old" data for next iteration
        x1old, x2old, x3old = X1u, X2u, X3u
        y1old, y2old, y3old = Y1u, Y2u, Y3u
        x1old_an, x2old_an, x3old_an = X1ua, X2ua, X3ua
        y1old_an, y2old_an, y3old_an = Y1ua, Y2ua, Y3ua

    plt.figure()
    df_kl[kl_cols[1:]].plot()
    plt.savefig(pn(event_type + 'mean_kl_' + file_descriptor), bbox_inches='tight')
    plt.close()

    plt.figure()
    df_lgls[ll_cols[1:]].plot()
    plt.savefig(pn(event_type + 'logloss_' + file_descriptor), bbox_inches='tight')
    plt.close()
def main():
    seed_events = 100
    update_events = 30
    analysis_events = 1000
    p1, p2, p3 = 0.5, 0.5, 0.5

    # EventGenerators
    eg1 = EG(seed=42, num_inputs=10, kind='chisq', balance=p1)
    eg2 = EG(seed=13, num_inputs=10, kind='chisq', balance=p2)
    eg3 = EG(seed=79, num_inputs=10, kind='chisq', balance=p3)

    # ModelUpdaters
    mu1 = MU(kind='gbm')
    mu2 = MU(kind='gbm')
    mu3 = MU(kind='gbm')

    # EventSelector
    es = ES(criterion='competing_streams')
    # TrainDataUpdaters
    tdu = TDU(num_events=seed_events)
    atdu = TDU(num_events=analysis_events)

    # create events
    X1, Y1 = eg1.get(seed_events)
    X2, Y2 = eg2.get(seed_events)
    X3, Y3 = eg3.get(seed_events)

    # train models
    m1 = mu1.train(X1, Y1,learning_rate=[0.01, 0.03, 0.1], n_estimators=[50, 100, 150, 200, 300],
                   subsample=0.5, max_depth=[2, 3], random_state=13, folds=3)
    m2 = mu2.train(X2, Y2, learning_rate=[0.01, 0.03, 0.1], n_estimators=[50, 100, 150, 200, 300],
                   subsample=0.5, max_depth=[2, 3], random_state=13, folds=3)
    m3 = mu3.train(X3, Y3, learning_rate=[0.01, 0.03, 0.1], n_estimators=[50, 100, 150, 200, 300],
                   subsample=0.5, max_depth=[2, 3], random_state=13, folds=3)

    for chunk in range(10):
        # create events
        x1r, y1r = eg1.get(update_events)
        x2r, y2r = eg2.get(update_events)
        x3r, y3r = eg3.get(update_events)

        # pass events through current models filter
        xs, ys = es.filter(xs=(x1r, x2r, x3r), ys=(y1r, y2r, y3r),
                           models=(m1, m2, m3), event_gains=(p1, p2, p3))
        x1, x2, x3 = xs
        y1, y2, y3 = ys
        print 'New events at %d:' % chunk
        print x1.shape[0], x2.shape[0], x3.shape[0]

        # update train data
        X1u, Y1u = tdu.update(X1, Y1, x1, y1)
        X2u, Y2u = tdu.update(X2, Y2, x2, y2)
        X3u, Y3u = tdu.update(X3, Y3, x3, y3)

        # update models using new data
        m1o, m2o, m3o = m1, m2, m3

        m1 = mu1.train(X1u, Y1u, learning_rate=[0.01, 0.03, 0.1], n_estimators=[50, 100, 150, 200, 300],
                       subsample=0.5, max_depth=[2, 3], random_state=13, folds=3)
        m2 = mu2.train(X2u, Y2u, learning_rate=[0.01, 0.03, 0.1], n_estimators=[50, 100, 150, 200, 300],
                       subsample=0.5, max_depth=[2, 3], random_state=13, folds=3)
        m3 = mu3.train(X3u, Y3u, learning_rate=[0.01, 0.03, 0.1], n_estimators=[50, 100, 150, 200, 300],
                       subsample=0.5, max_depth=[2, 3], random_state=13, folds=3)

        # create "old" data for next iteration
        X1, X2, X3 = X1u, X2u, X3u
        Y1, Y2, Y3 = Y1u, Y2u, Y3u

        # look at distribution shifts and algorithm performance
        print '--- Data Tomographer ---'
        # create events
        x1a, y1a = eg1.get(analysis_events)
        x2a, y2a = eg2.get(analysis_events)
        x3a, y3a = eg3.get(analysis_events)

        # pass events through updated models filter
        xs, ys = es.filter(xs=(x1a, x2a, x3a), ys=(y1a, y2a, y3a),
                           models=(m1o, m2o, m3o), event_gains=(1., 1., 1.))
        x1o, x2o, x3o = xs
        y1o, y2o, y3o = ys
        print 'Old model events at %d:' %chunk
        print x1o.shape[0], x2o.shape[0], x3o.shape[0]

        # pass events through updated models filter
        xs, ys = es.filter(xs=(x1a, x2a, x3a), ys=(y1a, y2a, y3a),
                           models=(m1, m2, m3), event_gains=(1., 1., 1.))
        x1, x2, x3 = xs
        y1, y2, y3 = ys
        print 'New model events at %d:' %chunk
        print x1.shape[0], x2.shape[0], x3.shape[0]

        dt = DT([x1o, x2o, x3o], [y1o, y2o, y3o], [x1, x2, x3], [y1, y2, y3], [m1o, m2o, m3o])
        file_descriptor = 'seed%d_update%d_' % (seed_events, update_events)
        dt.plot_kl(ntiles=10, rule='auto', prior=1e-8, verbose=False, saveas='feature_kl_'+file_descriptor)
        dt.plot_stagewise(metric='logloss', verbose=False, saveas='stagewise_logloss_'+file_descriptor)