Lab 4: Data Imputation using an Autoencoder

Deadline: June 11, 11:59pm

Late Penalty: There is a penalty-free grace period of one hour past the deadline. Any work that is submitted between 1 hour and 24 hours past the deadline will receive a 20% grade deduction. No other late work is accepted. Quercus submission time will be used, not your local computer time. You can submit your labs as many times as you want before the deadline, so please submit often and early.

In this lab, you will build and train an autoencoder to impute (or "fill in") missing data.

We will be using the Adult Data Set provided by the UCI Machine Learning Repository [1], available at https://archive.ics.uci.edu/ml/datasets/adult. The data set contains census record files of adults, including their age, martial status, the type of work they do, and other features.

Normally, people use this data set to build a supervised classification model to classify whether a person is a high income earner. We will not use the dataset for this original intended purpose.

Instead, we will perform the task of imputing (or "filling in") missing values in the dataset. For example, we may be missing one person's martial status, and another person's age, and a third person's level of education. Our model will predict the missing features based on the information that we do have about each person.

We will use a variation of a denoising autoencoder to solve this data imputation problem. Our autoencoder will be trained using inputs that have one categorical feature artificially removed, and the goal of the autoencoder is to correctly reconstruct all features, including the one removed from the input.

In the process, you are expected to learn to:

  1. Clean and process continuous and categorical data for machine learning.
  2. Implement an autoencoder that takes continuous and categorical (one-hot) inputs.
  3. Tune the hyperparameters of an autoencoder.
  4. Use baseline models to help interpret model performance.

[1] Dua, D. and Karra Taniskidou, E. (2017). UCI Machine Learning Repository [http://archive.ics.uci.edu/ml]. Irvine, CA: University of California, School of Information and Computer Science.

What to submit

Submit a PDF file containing all your code, outputs, and write-up. You can produce a PDF of your Google Colab file by going to File > Print and then save as PDF. The Colab instructions have more information (.html files are also acceptable).

Do not submit any other files produced by your code.

Include a link to your colab file in your submission.

Include a link to your Colab file here. If you would like the TA to look at your Colab file in case your solutions are cut off, please make sure that your Colab file is publicly accessible at the time of submission.

Colab Link: https://drive.google.com/file/d/14siiGm5gAhLp9rs4D3HDoa3OfXdcsVIC/view?usp=sharing

Part 0

We will be using a package called pandas for this assignment.

If you are using Colab, pandas should already be available. If you are using your own computer, installation instructions for pandas are available here: https://pandas.pydata.org/pandas-docs/stable/install.html

Part 1. Data Cleaning [15 pt]

The adult.data file is available at https://archive.ics.uci.edu/ml/machine-learning-databases/adult/adult.data

The function pd.read_csv loads the adult.data file into a pandas dataframe. You can read about the pandas documentation for pd.read_csv at https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.read_csv.html

Part (a) Continuous Features [3 pt]

For each of the columns ["age", "yredu", "capgain", "caploss", "workhr"], report the minimum, maximum, and average value across the dataset.

Then, normalize each of the features ["age", "yredu", "capgain", "caploss", "workhr"] so that their values are always between 0 and 1. Make sure that you are actually modifying the dataframe df.

Like numpy arrays and torch tensors, pandas data frames can be sliced. For example, we can display the first 3 rows of the data frame (3 records) below.

Alternatively, we can slice based on column names, for example df["race"], df["hr"], or even index multiple columns like below.

Numpy works nicely with pandas, like below:

Just like numpy arrays, you can modify entire columns of data rather than one scalar element at a time. For example, the code

df["age"] = df["age"] + 1

would increment everyone's age by 1.

Part (b) Categorical Features [1 pt]

What percentage of people in our data set are male? Note that the data labels all have an unfortunate space in the beginning, e.g. " Male" instead of "Male".

What percentage of people in our data set are female?

Part (c) [2 pt]

Before proceeding, we will modify our data frame in a couple more ways:

  1. We will restrict ourselves to using a subset of the features (to simplify our autoencoder)
  2. We will remove any records (rows) already containing missing values, and store them in a second dataframe. We will only use records without missing values to train our autoencoder.

Both of these steps are done for you, below.

How many records contained missing features? What percentage of records were removed?

Part (d) One-Hot Encoding [1 pt]

What are all the possible values of the feature "work" in df_not_missing? You may find the Python function set useful.

We will be using a one-hot encoding to represent each of the categorical variables. Our autoencoder will be trained using these one-hot encodings.

We will use the pandas function get_dummies to produce one-hot encodings for all of the categorical variables in df_not_missing.

Part (e) One-Hot Encoding [2 pt]

The dataframe data contains the cleaned and normalized data that we will use to train our denoising autoencoder.

How many columns (features) are in the dataframe data?

Briefly explain where that number come from.

Answer:

As shown in the code above, 57 columns (features) are in the dataframe data. The number comes from the sum of all continuous features and all possible values of each categorical feature.

Part (f) One-Hot Conversion [3 pt]

We will convert the pandas data frame data into numpy, so that it can be further converted into a PyTorch tensor. However, in doing so, we lose the column label information that a panda data frame automatically stores.

Complete the function get_categorical_value that will return the named value of a feature given a one-hot embedding. You may find the global variables cat_index and cat_values useful. (Display them and figure out what they are first.)

We will need this function in the next part of the lab to interpret our autoencoder outputs. So, the input to our function get_categorical_values might not actually be "one-hot" -- the input may instead contain real-valued predictions from our neural network.

Part (g) Train/Test Split [3 pt]

Randomly split the data into approximately 70% training, 15% validation and 15% test.

Report the number of items in your training, validation, and test set.

Part 2. Model Setup [5 pt]

Part (a) [4 pt]

Design a fully-connected autoencoder by modifying the encoder and decoder below.

The input to this autoencoder will be the features of the data, with one categorical feature recorded as "missing". The output of the autoencoder should be the reconstruction of the same features, but with the missing value filled in.

Note: Do not reduce the dimensionality of the input too much! The output of your embedding is expected to contain information about ~11 features.

Part (b) [1 pt]

Explain why there is a sigmoid activation in the last step of the decoder.

(Note: the values inside the data frame data and the training code in Part 3 might be helpful.)

Answer:

Since the values inside the data frame data are all between 0 and 1, the sigmoid activation function is applied to the output layer to scale the output from 0 to 1.

Part 3. Training [18]

Part (a) [6 pt]

We will train our autoencoder in the following way:

Complete the code to train the autoencoder, and plot the training and validation loss every few iterations. You may also want to plot training and validation "accuracy" every few iterations, as we will define in part (b). You may also want to checkpoint your model every few iterations or epochs.

Use nn.MSELoss() as your loss function. (Side note: you might recognize that this loss function is not ideal for this problem, but we will use it anyway.)

Part (b) [3 pt]

While plotting training and validation loss is valuable, loss values are harder to compare than accuracy percentages. It would be nice to have a measure of "accuracy" in this problem.

Since we will only be imputing missing categorical values, we will define an accuracy measure. For each record and for each categorical feature, we determine whether the model can predict the categorical feature given all the other features of the record.

A function get_accuracy is written for you. It is up to you to figure out how to use the function. You don't need to submit anything in this part. To earn the marks, correctly plot the training and validation accuracy every few iterations as part of your training curve.

Part (c) [4 pt]

Run your updated training code, using reasonable initial hyperparameters.

Include your training curve in your submission.

Part (d) [5 pt]

Tune your hyperparameters, training at least 4 different models (4 sets of hyperparameters).

Do not include all your training curves. Instead, explain what hyperparameters you tried, what their effect was, and what your thought process was as you chose the next set of hyperparameters to try.

Part 4. Testing [12 pt]

Part (a) [2 pt]

Compute and report the test accuracy.

Part (b) [4 pt]

Based on the test accuracy alone, it is difficult to assess whether our model is actually performing well. We don't know whether a high accuracy is due to the simplicity of the problem, or if a poor accuracy is a result of the inherent difficulty of the problem.

It is therefore very important to be able to compare our model to at least one alternative. In particular, we consider a simple baseline model that is not very computationally expensive. Our neural network should at least outperform this baseline model. If our network is not much better than the baseline, then it is not doing well.

For our data imputation problem, consider the following baseline model: to predict a missing feature, the baseline model will look at the most common value of the feature in the training set.

For example, if the feature "marriage" is missing, then this model's prediction will be the most common value for "marriage" in the training set, which happens to be "Married-civ-spouse".

What would be the test accuracy of this baseline model?

Answer:

As shown in the code above, the test accuracy would vary depending on the feature with this baseline model due to its dependency on the most common value of each feature. For example, the baseline model accuracy of the "marriage" feature is 46.68% because 46.68% of values for the feature are "Married-civ-spouse", which is the most common value.

Part (c) [1 pt]

How does your test accuracy from part (a) compared to your basline test accuracy in part (b)?

Answer:

The test accuracy of the autoencoder model is 70.54%, which is much higher compared to the estimated test accuracy of taking the most common value, which is only 46.68%.

Part (d) [1 pt]

Look at the first item in your test data. Do you think it is reasonable for a human to be able to guess this person's education level based on their other features? Explain.

Answer:

In my opinion it can be quite difficult to guess the person's education level based on their other features. Some speculations could be made based on the person's occupation. Since the person has an occupation in a professional specialty, I would guess that the person would at least have completed high school. Besides this connection, I cannot draw any other conclusions based on these features.

Part (e) [2 pt]

What is your model's prediction of this person's education level, given their other features?

Answer:

The autoencoder model's prediction of this person's education level is "Bachelors", which is correct.

Part (f) [2 pt]

What is the baseline model's prediction of this person's education level?

Answer:

The baseline model's prediction of this person's education level is "HS-grad", which is incorrect.