text: # -*- coding: utf-8 -*-
  """musawenkosi_DS_Assignment1.ipynb
  
  Automatically generated by Colaboratory.
  
  Original file is located at
      https://colab.research.google.com/drive/1IFhafYBmSpi--1pgDWyC7u_d93nyP3ep
  
  I  have  provided  a  dataset  which  has  a  number  of  features  and  a  single  target  (last  column  in  dataset).  Your  task  is  to  use neural  networks  to  address  this multiclass classification problem.  
  
  Some  hints: 
  
  1. Are the classes in order in the dataset? Does something need to be done about that? 
  **The classes were not balanced '2' had too many values so it needs to be undersampled randomly**
  2. Is the scale of the dataset suitable as is?
  **The scale is not suitable**
  Import the dataset using Pandas as follows:
  
  
  
  ```
  pandas.read_csv("https://drive.google.com/uc?id=1PzJA23lUSlf2x1DeF_ek1YSAj-RFHWCp")
  ```
  """
  
  from keras.datasets import mnist
  import numpy as np
  np.random.seed(13378)
  import pandas
  import tensorflow as tf
  from tensorflow.keras import Sequential
  from tensorflow.keras.layers import Dense
  from tensorflow.keras import metrics
  from sklearn.preprocessing import StandardScaler
  from sklearn.model_selection import train_test_split
  from sklearn.metrics import confusion_matrix, accuracy_score
  import matplotlib.pyplot as plt
  from keras.utils import np_utils
  
  df = pandas.read_csv("https://drive.google.com/uc?id=1PzJA23lUSlf2x1DeF_ek1YSAj-RFHWCp") #This line of code reads the data from the url using pandas
  dataset = df.values #This line of code is making the dat look nice in the table formart
  
  df.head() #This line of code is for check how our data look like.
  
  """### **Using the hint I check if there is problem in the classes and below is the code that confirmed that the classes are not balanced.**"""
  
  df['Target'].value_counts() #This line of code checks if the classes consistent
  
  len(df) #This line of code checks the length of the dataframe so that I can calculate how much did I sampled my data
  
  """**We can see that we need to under-sample class 2 so that model will be accurate. I used seaborn to quickly visualise the discrepancy in the classes because using value count did not clearly show the problem in classes. Searbon is a python library for visualazation.**"""
  
  import seaborn #This line of code visuals the classes just to get a clear picture of which we should under-sample
  
  seaborn.countplot(df['Target']) #This life of code plots the graphs to see the difference
  
  df.describe() #This lets me know how is my data is described
  
  # Divide by class so that we can create the new dataframe with fair classes
  df_class_0 = df[df['Target'] == 0] #This line of code separate class 0
  df_class_1 = df[df['Target'] == 1] #This line of code separate class 1
  df_class_2 = df[df['Target'] == 2] #This line of code separate class 2
  df_class_3 = df[df['Target'] == 3] #This line of code separate class 3
  df_class_4 = df[df['Target'] == 4]#This line of code separate class 4
  
  """### **The code below randomly under sample '2' to 1200 because we still need '2' be the majority but not too much like before.After that I created a new dataframe with the new under sampled class '2'.**"""
  
  df_class_2_under = df_class_2.sample(1200) #This code randomly chooses 1300 classes which is 60% of our original dataframe
  df_test_under = pandas.concat([df_class_2_under, df_class_1,df_class_3,df_class_4,df_class_0], axis=0) #This line of code combines all the separated classes after under sampling class 2
  
  print('Random under-sampling:') #This code just print the message for random sampling
  print(df_test_under.value_counts()) #This line of code prints out the our new sampled data frame
  
  """### **After randomizing my data I wanted to sort it and the code below sort my data**"""
  
  df_test_under.sort_values(by='Target', ascending=True) #This line of code sort the data
  
  """### **After under sampling I use seaborn again to quickly see if the changes I made in my data is good or not.**"""
  
  seaborn.countplot(df_test_under['Target']) #This code visualise the changes we made on our dataframe to check if class 2 is still the majority
  
  df_test_under['Target'].value_counts() #This line of code checks if the new dataframe is balanced
  
  len(df_test_under) #This tells me the whole length of my data
  
  df_test_under.shape #This code shows the shape of the data
  
  X = df_test_under.loc[:, df.columns != 'Target'] #This code is for features
  Y = df_test_under.Target #This line of code is for the target feature.
  
  X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=.3) #This splits the data into training and testing.
  
  """### ***The code below finds the unique numbers from the train labels***"""
  
  classes = np.unique(Y_train)
  nClasses = len(classes)
  print('Total number of outputs : ', nClasses)
  print('Output classes : ', classes)
  
  X_train.shape #This code checks the shape of the features train data
  
  X_test.shape #This code checks the shape of the features test data
  
  """### **This code below creates a neural network model**"""
  
  def baseline():
    
      model = Sequential()
      model.add(Dense(21, input_dim=21, activation='relu'))
      model.add(Dense(40, activation='relu'))
      model.add(Dense(30, activation='relu'))
      model.add(Dense(20, activation='relu'))
      model.add(Dense(5, activation='softmax'))
      
      # Compile model
      model.compile(loss='categorical_crossentropy', 
                    optimizer='adam', 
                    metrics=['accuracy'])
      
      return model
  
  model = baseline()#This line of code initialise the model
  
  model.summary() #This code gives the summary of the model
  
  """### **This code converts from categorical labels to one-hot encoded vectors**"""
  
  Y_train = np_utils.to_categorical(Y_train)
  Y_test = np_utils.to_categorical(Y_test)
  
  """##**The code fits on the training features and targets,seperate the validation data I've set aside above.**"""
  
  X_train, X_val, Y_train, Y_val = train_test_split(X_train, Y_train, test_size=0.3)
  
  """### **This code sets the number of epochs, batch size and also explore various verbose values.**"""
  
  history = model.fit(X_train, Y_train, validation_data=(X_val, Y_val) ,epochs = 30)
  
  """### **This code below predicts on the test data**"""
  
  prediction = model.predict(X_test)
  prediction
  prediction_classes = np.argmax(prediction, axis=1)
  prediction_classes
  
  """### **This code give us a confusion matrix to check the performance of a classification model**"""
  
  confusion_matrix(np.argmax(Y_test,1), prediction_classes)
  
  """### **This code checks the accuracy of the model**"""
  
  accuracy_score(np.argmax(Y_test,1), prediction_classes)
  
  def plot_hist(h, xsize=6, ysize=10):
  
      fig_size = plt.rcParams["figure.figsize"]
      plt.rcParams["figure.figsize"] = [xsize, ysize]
      fig, axes = plt.subplots(nrows=1, ncols=1, sharex=True)
      
      # summarize history for Accuracy
      plt.subplot(211)
      plt.plot(h['accuracy'])
      plt.plot(h['val_accuracy'])
      plt.title('Training Performance')
      plt.ylabel('Accuracy')
      plt.xlabel('Epochs')
      plt.legend(['Train', 'Validation'], loc='best')
      
      plt.draw()
      plt.show()
  
      return
  
  plot_hist(history.history, xsize=8, ysize=12)
  
  """### **The graphs tell us that the model perfoms well as the number of epochs increase and the accuracy gets good very quickly**"""