code: ######### Base Imports #############
  import pandas as pd
  import psycopg2
  import math
  import pickle
  import numpy as np
  import os
  
  ################# Sklearn Imports #####################
  from sklearn import datasets
  # Our Classifier
  from sklearn.naive_bayes import GaussianNB
  # Get the accuracy score of the final model
  from sklearn.metrics import accuracy_score
  # Split the training set into train and test
  from sklearn.model_selection import train_test_split
  #MLP Classifer
  from sklearn.neural_network import MLPClassifier
  from sklearn.neighbors import KNeighborsClassifier
  from sklearn.svm import SVC
  # from sklearn.gaussian_process import GaussianProcessClassifier
  # from sklearn.gaussian_process.kernels import RBF
  from sklearn.tree import DecisionTreeClassifier
  from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, VotingClassifier
  from sklearn import preprocessing
  from sklearn.feature_selection import RFE
  
  
  ############## Paramater Switches ##############
  
  #### NEVER TOUCH ANY OF THESE!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  
  CURRDIR = os.path.dirname(os.path.abspath(__file__))
  
  #Scaling
  NORMILIZE = True
  SCALE = False
  
  #Spliting features and classifiers
  SUBSET_FEATURES = True
  SUBMODELS = True
  
  #Showing Each classifier
  SEPERATE_CLASSIFIERS = True
  
  #Combining output
  NN_COMBINE = True
  VOTING_CLASSIFIERS = True
  
  
  def train_models():
  
      #Get connection to the database
      try:
          conn = psycopg2.connect("dbname='capstone' user='samkreter' host='localhost'")
      except:
          print("I am unable to connect to the database")
  
      cur = conn.cursor()
  
      #Prepare the super weird sql to pull out the features in the right order
      sql = "select   fights.f1result, \
                  f1.height_inches AS f1_height,\
                  f1.reach_inches AS f1_reach, \
                  f1.weight_lbs AS f1_weight, \
                  f1.strike_offense_per_min AS f1_strike_offense_per_min, \
                  f1.strike_offense_accuracy AS f1_strike_offense_accuracy, \
                  f1.strike_defense_per_min AS f1_strike_defense_per_min, \
                  f1.strike_defense_accuracy AS f1_strike_defense_accuracy, \
                  f1.takedowns_per_fight AS f1_takedowns_per_fight, \
                  f1.takedowns_accuracy AS f1_takedowns_accuracy, \
                  f1.takedowns_defense AS f1_takedowns_defense, \
                  f1.submissions_per_fight AS f1_submissions_per_fight, \
                  f1.total_fights AS f1_total_fights, \
                  f1.association AS f1_association, \
                  f1.country AS f1_country, \
                  f1.wins AS f1_wins, \
                  f1.losses AS f1_losses, \
                  f1.total_fights AS f1_total_fights, \
                                                  \
                  f2.height_inches AS f2_height, \
                  f2.reach_inches AS f2_reach, \
                  f2.weight_lbs AS f2_weight, \
                  f2.strike_offense_per_min AS f2_strike_offense_per_min, \
                  f2.strike_offense_accuracy AS f2_strike_offense_accuracy, \
                  f2.strike_defense_per_min AS f2_strike_defense_per_min, \
                  f2.strike_defense_accuracy AS f2_strike_defense_accuracy, \
                  f2.takedowns_per_fight AS f2_takedowns_per_fight, \
                  f2.takedowns_accuracy AS f2_takedowns_accuracy, \
                  f2.takedowns_defense AS f2_takedowns_defense, \
                  f2.submissions_per_fight AS f2_submissions_per_fight, \
                  f2.total_fights AS f2_total_fights, \
                  f2.association AS f2_association, \
                  f2.country AS f2_country, \
                  f2.wins AS f2_wins, \
                  f2.losses AS f2_losses, \
                  f2.total_fights AS f2_total_fights \
                  from octagon.fights INNER JOIN octagon.fighters AS f1 ON octagon.fights.f1name = f1.name INNER JOIN octagon.fighters AS f2 ON octagon.fights.f2name = f2.name;"
  
      df = pd.read_sql_query(sql, conn)
  
      conn.close()
  
      #Convert the result from text to numeric, mostly interested in the wins
      df['f1result'] = df['f1result'].replace(['win', 'draw', 'NC'],[1,2,2])
  
  
      # Take only a subset of the features found using recursive feature elimination
      if SUBSET_FEATURES:
          subset = ['f1result','f1_strike_offense_per_min','f1_strike_defense_per_min','f1_association','f1_wins','f1_losses',
              'f2_strike_offense_per_min','f2_strike_defense_per_min','f2_association','f2_wins','f2_losses']
          df = df[subset]
  
  
      ## Selection how to interpolate the missing data
      df0 = df.interpolate()
      df0 = df.dropna()
      df0 = df.interpolate(method='spline', order=2)
      df = df.interpolate(method='pchip')
  
  
      #Reverse the training data in order to train on lossing fights aswell
      f1_end = math.ceil(len(df.columns) / 2)
      df2 = df.copy()
      tmp = df2.ix[:,1:f1_end]
      df2.ix[:,1:f1_end] = df2.ix[:,f1_end:].values
      df2.ix[:,f1_end:] = tmp.values
      df2['f1result'] = 0
  
      #combine the dataframe into one to help with the training
      df = df.append(df2)
      df = df.sample(frac=1).reset_index(drop=True)
  
      #Pull the labels out of the dataset
      y = df['f1result']
      del df['f1result']
  
      X = df
  
  
      #
      if NORMILIZE:
          X = preprocessing.normalize(X, norm='l2')
  
      if SCALE:
          X = preprocessing.scale(X)
  
      X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=.2,random_state = 1)
  
      names = [
               "MLP",
               "Naive Bayes",
               "KNN",
               "SVM Linear",
               "SVM gamma",
               "Decsion Tree",
               "Random Forest",
               "adaBoost"
              ]
  
      clfs = [
              MLPClassifier(solver='sgd', alpha=1e-5, hidden_layer_sizes=(5, 2), random_state=1,max_iter=400),
              GaussianNB(),
              KNeighborsClassifier(3),
              SVC(kernel="linear", C=0.025,probability=True),
              SVC(gamma=2, C=1,probability=True),
              DecisionTreeClassifier(max_depth=5),
              RandomForestClassifier(max_depth=5, n_estimators=10, max_features=1),
              AdaBoostClassifier()
             ]
  
      if SUBMODELS and SUBSET_FEATURES:
          #feats = [0,2,3]
          feats = [0,2,3,4,5,6,7]
          clfs = [clfs[i] for i in feats]
          names = [names[i] for i in feats]
      elif SUBMODELS:
          feat = [3,5,7]
          clfs = [clfs[i] for i in feats]
          names = [names[i] for i in feats]
  
      #Use Feature elemination to find the most important features
      if False:
          f1_end = int(len(df.columns) / 2)
          indexs = np.array([i for i in range(f1_end)])
          selector = RFE(clfs[3], 14, step=1)
          selector = selector.fit(X_train, y_train)
          print(selector.support_)
          print(indexs[selector.support_[:f1_end]])
          print(indexs[selector.support_[f1_end:]])
  
      #Train the individule classifiers and print their scores
      if SEPERATE_CLASSIFIERS:
  
          pred_list_train = []
          pred_list_test = []
  
          for index, clf in enumerate(clfs):
              clf.fit(X_train,y_train)
              preds = clf.predict(X_test)
  
              preds_train = np.amax(clf.predict_proba(X_train),axis=1)
              preds_test = np.amax(clf.predict_proba(X_test),axis=1)
  
              pred_list_train.append(preds_train)
              pred_list_test.append(preds_test)
              #print(names[index] + " Accuracy of the model is: %.2f%%" % (accuracy_score(preds,y_test) * 100))
              print("%.2f" % (accuracy_score(preds,y_test) * 100))
  
  
      #Use a soft committe style voting
      # This sums the class probabilites and calls and argmax for the class
      if VOTING_CLASSIFIERS:
          clf_vote = VotingClassifier(estimators=list(zip(names,clfs)),voting='soft')
          clf_vote.fit(X_train,y_train)
          preds = clf_vote.predict(X_test)
          print("%.2f" % (accuracy_score(preds,y_test) * 100))
  
  
      #Trains a MLP Neural Network on the class predicitons of each of the base networks
      if NN_COMBINE:
          combine_clf = MLPClassifier(solver='sgd', alpha=1e-5, hidden_layer_sizes=(5, 2), random_state=1)
          combine_clf.fit(np.array(pred_list_train).T,y_train)
          preds = combine_clf.predict(np.array(pred_list_test).T)
          print("%.2f" % (accuracy_score(preds,y_test) * 100))
  
  
  
  # Predict who will when in a UFC fight
  # \param f1id: id for the first fighter
  #\param f2id: id for the second fighter
  #\return: array with probabilites of winning [fighter1,fighter2,Uncertainty]
  def predict(f1id=2646,f2id=13767):
  
      print(f1id)
      print(f2id)
      #Open a connection to the database
      #TODO: Use env varibles for the connectoin strings
      try:
          conn = psycopg2.connect("dbname='llua' user='llua' password='capstone' host='macrohard.io'")
      except:
          print("I am unable to connect to the database")
  
      cur = conn.cursor()
  
      #Big ass sql string
      sql = "select fid, \
                  height_inches, \
                  reach_inches, \
                  weight_lbs, \
                  strike_offense_per_min, \
                  strike_offense_accuracy, \
                  strike_defense_per_min, \
                  strike_defense_accuracy, \
                  takedowns_per_fight, \
                  takedowns_accuracy, \
                  takedowns_defense, \
                  submissions_per_fight, \
                  total_fights, \
                  association, \
                  country, \
                  wins, \
                  losses, \
                  total_fights FROM octagon.fighters"
  
      #Read all fighters in from the database.
      #TODO: find a better way for interpolation
      df = pd.read_sql_query(sql, conn)
  
  
      #convert the text fields into numeric values
      df = convert_text(df)
  
      #Interpolate the data to fill in the missing values
      df = df.interpolate(method='pchip')
      df = df.dropna()
  
      #Take a subset of the features found in the feature selection process
      if SUBSET_FEATURES:
          subset = [
                      'fid',
                      'strike_offense_per_min',
                      'strike_defense_per_min',
                      'association',
                      'wins',
                      'losses'
                      ]
          df = df[subset]
  
      #Pull out the fighters that we want and remove their id field
      f1 = df[df['fid'] == f1id].drop('fid',axis=1)
      f2 = df[df['fid'] == f2id].drop('fid',axis=1)
  
  
      #Error out if the fighter could not be found after data cleaning
      if f1.empty:
          f1 = df[df['fid'] == 2646].drop('fid',axis=1)
  
      if f2.empty:
          f2 = df[df['fid'] == 13767].drop('fid',axis=1)
  
  
      #Create a feature vector from the two fighters data
      feat_vector = np.append(f1.values,f2.values)
  
      #Reshape the data to make sklearn feel better about itself and not give a warning
      feat_vector = feat_vector.reshape(1, -1)
  
      #Normilize the input vectors since the classifiers are trained on normilized data
      if NORMILIZE:
          feat_vector = preprocessing.normalize(feat_vector, norm='l2')
  
  
      #Load the saved classifiers
      with open(CURRDIR + "/main-clfs.pickle","rb") as f:
          clfs = pickle.load(f)
  
  
      #Names for each of the classifiers
      names = [
           "MLP",
           "KNN",
           "SVM Linear",
           "SVM gamma",
           "Voting"
          ]
  
  
      #TODO: take into account the individule values
      for index,clf in enumerate(clfs):
          print(names[index],clf.predict_proba(feat_vector))
  
      #Return the combined voting answer
      return clfs[-1].predict_proba(feat_vector)[0]
  
  
  # Convert text fields into a numeric in the interval [0,1] based on the index
  # \param df: dataframe that should be converted
  # \return: the dataframe with the converted values
  def convert_text(df):
  
  
      #Grab the recored names for the percentages
      with open(CURRDIR + '/text_based_names.pickle','rb') as f:
          names = pickle.load(f)
  
      #The preprocessed text fields
      text_based = ['country','association']
  
      #Iterate through and convert the text fields
      for index, name in enumerate(text_based):
          df[name] = df[name].apply(lambda x: names[index].index(x) / len(names[index]))
  
  
      return df
  
  
  
  
  
apis: sklearn.ensemble.AdaBoostClassifier sklearn.ensemble.RandomForestClassifier sklearn.neighbors.KNeighborsClassifier sklearn.neural_network.MLPClassifier sklearn.naive_bayes.GaussianNB sklearn.svm.SVC sklearn.preprocessing.scale sklearn.metrics.accuracy_score sklearn.preprocessing.normalize sklearn.tree.DecisionTreeClassifier sklearn.model_selection.train_test_split sklearn.feature_selection.RFE