# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Explanation: <a href="https://colab.research.google.com/github/google/applied-machine-learning-intensive/blob/master/content/03_regression/06_linear_regression_with_tensorflow/colab.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>
Copyright 2020 Google LLC.
End of explanation
"""


! chmod 600 kaggle.json && (ls ~/.kaggle 2>/dev/null || mkdir ~/.kaggle) && mv kaggle.json ~/.kaggle/ && echo 'Done'

"""
Explanation: Linear Regression with TensorFlow
In previous units we learned about regression and about how to build and apply a regression model using scikit-learn. For many regression cases, scikit-learn is more than adequate. However, there are times when more powerful tools are needed. TensorFlow is one of those tools. It is a computational toolkit built to perform machine learning and data science tasks at scale.
Problem Framing
Machine learning is one of a variety of solutions that might work for solving a problem. It is always important to understand the problem space before diving in and starting to clean data and code.
In this lab we would like to be able to predict the price of a house.
Questions we should ask ourselves might include the following:

Predict the price when? Now? In the past? In the future? For what range?
Where are we predicting prices for? One market? One state? One country?
What is our tolerance for being wrong?
Are we okay with a few huge outliers if the overall model is better?
What metrics are we using to define success and what are the acceptable values?
Is there an non-ML way to solve this problem?
What data is available to solve the problem?

The list of questions is boundless. Eventually you'll need to move on, but understanding the problem and the solution space is vital.

For this problem we'll further define the problem by saying:

We want to create a system that predicts the prices of houses in California in 1990. We have census data from 1990 available to build and test the system. We will accept a system with a root mean squared error of 200,000 or better.

Since this is a contrived example, we'll shortcut around the question of choosing a technique and say that our analysis has led us to believe that we want to use a linear regression model to serve as our prediction system.
Exploratory Data Analysis
The dataset we'll use for this Colab contains California housing data taken from the 1990 census data. This is a popular dataset for experimenting with machine learning models.
As with any data science project, it is a good idea to take some time and review the data schema and description. Ask yourself the following:

What data is available? What are the columns?
What do those columns mean?
What data types are those columns?
What is the granularity of the data? In this particular case, what is a "block"?
How many rows of data are there?
Roughly how big is the data? Kilobytes? Megabytes? Gigabytes? Terabytes? More?
Are any of the columns highly correlated?
What bias is contained in the data?

Load the Data
Now that we have an understanding of the data that we are going to use in our model, let's load it into this Colab and examine it more closely.
Since the data is hosted on Kaggle, you'll need to upload your kaggle.json file to this lab and then run the code block below.
End of explanation
"""


!kaggle datasets download camnugent/california-housing-prices
!ls

"""
Explanation: Once you are done, use the kaggle command to download the file into the lab.
End of explanation
"""


import pandas as pd

housing_df = pd.read_csv('california-housing-prices.zip')

housing_df

"""
Explanation: We now have a file called california-housing-prices.zip that we can load into a DataFrame.
End of explanation
"""


housing_df.dtypes

"""
Explanation: Exploration
You should always look at your data and statistics about that data before you begin modelling it. First, let's see the columns and data types that we have available.
End of explanation
"""


housing_df.describe(include='all')

"""
Explanation: Eight floating point features, one object features, and a floating point target, median_house_value. This is what we expect based on the data documentation.
Statistics
It is a good idea to also describe the dataset. We use the include='all' argument to ensure our object column is also described.
End of explanation
"""


housing_df['ocean_proximity'].hist()

"""
Explanation: In this case we can see that all of the column counts are the same except for total_bedrooms, which seems to be missing some data.
Looking at the min and max can be helpful, too. Does a 2.0 value for a minimum number of rooms for a block match your mental model of what a block is? What about that max of 39,320 rooms? In cases like this, it can be useful to research your topic area.
In this particular case, those numbers might be okay as long as the dense block is in an urban area with very dense and tall buildings on the block. As you probe a dataset, you should ask yourself questions like this. When something doesn't look right, investigate it.
Also notice the ocean proximity column. It has five unique values. Let's see what they are.
End of explanation
"""


# Your Code Goes Here

"""
Explanation: We used .hist() so that we can also see the distribution. We can see the five values and can see that the largest group is &lt;1H OCEAN. We don't seem to have any values missing.
Exercise 1: Sanity Check
Use Pandas to find the row of data that contains the census block with the largest number of rooms. Search for the latitude and longitude for that location and answer the questions below.
Student Solution
End of explanation
"""


housing_df.head(10)

"""
Explanation: What city is the block located in?
Your solution goes here


Are 39320.0 total rooms reasonable? Why?
Your solution goes here




Sampling
It is also a good idea to take a look at the actual data. We can use Panda's head(), tail(), and/or sample() methods to do this.
End of explanation
"""


import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(12,10))
_ = sns.heatmap(housing_df.corr(), cmap='coolwarm', annot=True)

"""
Explanation: Did you gain any insight from peeking at the actual data? Is the data sorted in a manner that might lead to a bad model?
In this case the data seems to be sorted ascending by longitude and possibly secondarily descending by latitude. We need to consider this when sampling or splitting the data.
Also, there are some duplicate latitude and longitude values. This seems odd, but will be difficult to troubleshoot. For now, we'll just accept that the values are okay.
Correlation
It is important to understand how columns relate to one another. Every feature that you add to your training set increases the amount of work that must be done to train your model. If you can find columns with a high degree of correlation, you can potentially not use one of the columns in your training and still get a model that performs well.
Let's create a correlation matrix heatmap for our data set.
End of explanation
"""


rooms = housing_df['total_rooms'].sort_values().reset_index(drop=True)

plt.figure(figsize=(12,10))
_ = sns.lineplot(x=rooms.index.values, y=rooms)

"""
Explanation: Exercise 2: Correlated Columns
Answer the following questions about the correlation between columns in our dataset.
Student Solution

Which columns are the most highly correlated?
Your solution goes here.


Which column is most strongly correlated with median_house_value?
Your solution goes here.


Which columns have the strongest negative correlation?
Your solution goes here.




Data Preprocessing
Now is the stage where we would perform model-independent preprocessing to the data to repair any missing data. Since there isn't very much missing data, we don't have much pre-processing to do.
Let's look at those room counts again, though. The values seem a little odd.
First we'll plot the room counts in ascending order.
End of explanation
"""


many_rooms = rooms[rooms > 10000].size

percent = (many_rooms / rooms.size) * 100

print(f'{many_rooms} blocks have more than 10000 rooms ' +
      f'which is {percent:0.2f}% of our data')

"""
Explanation: That's quite a spike there at the end!
Looking at the chart, let's pick a point where the number of rooms really starts to extremely slope upward, say 10,000. If we chose to drop the rows with really large values, what would that do to our data?
End of explanation
"""


has_all_data = housing_df[~housing_df['total_bedrooms'].isna()]

sums = has_all_data[['total_bedrooms', 'total_rooms']].sum().tolist()

bedrooms_to_total_rooms_ratio = sums[0] / sums[1]

bedrooms_to_total_rooms_ratio

"""
Explanation: So we'd knock out over 1% of our data by trying to remove what we think are outliers. That's not horrible, but it's probably not something we would want to do on a hunch.
We do need to fix our missing total bedrooms data. There are a few strategies that we could use:

Fill in the values with the mean of the total_bedrooms data in the dataset.
Fill in the values with zero.
Find the closest lat/long values and use the mean of them.
Find the ratio of total_bedrooms to total_rooms and multiply it against the total_rooms values that correspond with the missing total_bedrooms values.

Which is best?
Each method has pros and cons. For instance, using the dataset-wide mean values might lead to some unrealistic values if the blocks happen to be in extremely dense or extremely rural areas.
Filling in with zeros in this case works, but seems like a lazy approach that we can be pretty sure is not accurate.
Finding the closest lat/long that has a value and using its value - or the mean of a few of the closest lat/longs - is tempting since density probably changes slowly. However, this might be difficult to do, even with the data being sorted by latitude and longitude.
Finding the ratio of total rooms to bedrooms seems like a reasonable compromise since we have total_rooms data for every row, and the two columns are highly correlated. Using these values, we can derive a reasonable guess for the number of bedrooms.
The code to find the ratio sums the values for total bedrooms and total rooms across all fully-populated rows in the dataset.
End of explanation
"""


has_all_data = housing_df[~housing_df['total_bedrooms'].isna()]

sums = has_all_data[['total_bedrooms', 'total_rooms']].median().tolist()

bedrooms_to_total_rooms_ratio = sums[0] / sums[1]

bedrooms_to_total_rooms_ratio

"""
Explanation: If we think that the outliers might throw off the ratio, we can check the median.
End of explanation
"""


missing_total_bedrooms_idx = housing_df['total_bedrooms'].isna()

housing_df.loc[missing_total_bedrooms_idx, 'total_bedrooms'] = housing_df[
    missing_total_bedrooms_idx]['total_rooms'] * bedrooms_to_total_rooms_ratio

housing_df.describe()

"""
Explanation: It seems to match the mean pretty closely.
Now we just need to patch the data.
End of explanation
"""


target_column = 'median_house_value'
feature_columns = [c for c in housing_df.columns if c != target_column]

target_column, feature_columns

"""
Explanation: Modeling
It is time to actually build our model. In this case, we know we are going to build a linear regression model using TensorFlow. We could build the model by hand, but luckily we don't have too. TensorFlow provides many pre-built models in its estimator library. We are going to use the tensorflow.estimator.LinearRegressor model.
The Estimator class is the base class for TensorFlow estimators. Its methods define the API for estimators. In the remainder of this lab, we will create an instance of LinearRegressor and use the Estimator API to train the model and make test predictions.
Prepare the Data
Earlier we considered preprocessing the data. That preprocessing was intended to be more generic preprocessing that needed to be done to correct errors with the data set.
Now that we have chosen a model, we need to do specific preprocessing related to the type of model that we'll be building and how we are going to test and train the model.
Initially we'll be using the LinearRegressor with default options. For measuring model quality, we will perform hold-out testing with 20% of the data being held out for test.
Normalization
The scale and range of data in each column of our dataset varies widely. In many models larger values will be over-considered in training. In order to combat this we can normalize our data.
Note that we only want to normalize the feature data so let's first create variables to hold our feature and target column names.
End of explanation
"""


numeric_feature_columns = [c for c in feature_columns if c != 'ocean_proximity']

numeric_feature_columns

"""
Explanation: Also remember that ocean_proximity contains string values, so let's separate our features even more.
End of explanation
"""


housing_df.loc[:, numeric_feature_columns] = (
    housing_df[numeric_feature_columns] - 
      housing_df[numeric_feature_columns].min()) / (
          housing_df[numeric_feature_columns].max() -
            housing_df[numeric_feature_columns].min())

housing_df[numeric_feature_columns].describe()

"""
Explanation: To normalize, we subtract the minimum value from each column and then divide by the delta between the min and max. This should make all of our feature values fall into the range of 0.0 to 1.0. You can see in the describe() output that we now have a min values of 0.0 and max values of 1.0.
End of explanation
"""


housing_df[target_column].describe()

"""
Explanation: Another option would be to standardize the data. Standardization is the process of subtracting the mean from each column and then dividing by the standard deviation. We chose not to do that in this case because that creates negative values, which don't work well with this model.
Should we modify the target in any way?
Let's take a look at the values again.
End of explanation
"""


TARGET_FACTOR = 100000

housing_df[target_column] = housing_df[target_column] / TARGET_FACTOR

housing_df[target_column].describe()

"""
Explanation: Those are some pretty big values. It does look like there is a ceiling of 500,001 applied to the data and a minimum value of 14,999.
Given enough time, our model could train to predict values this large. However, we are going to be using a pretty small learning rate by default with the Ftrl optimizer: 0.0001. In order to speed things up, we can shrink the values in the target column by some constant.
End of explanation
"""


# Shuffle
housing_df = housing_df.sample(frac=1)

# Calculate test set size
test_set_size = int(len(housing_df) * 0.2)

# Split the data
testing_df = housing_df[:test_set_size]
training_df = housing_df[test_set_size:]

print(f'Holding out {len(testing_df)} records for testing. ')
print(f'Using {len(training_df)} records for training.')

"""
Explanation: We've reduced the values from the range of 14,999-500,001 to 0.14999-5.0. This should allow the model to converge faster. Of course, now our predictions will need to be multiplied by 100,000 in order to reflect real dollar values.
Train/Test Split
We want to go ahead and divide our data into testing and training splits. For this example we'll hold out 20% of the data for testing.
One easy way to do that is just to slice the data. Our data is sorted by latitude and longitude, however, so we need to shuffle it first so that we aren't testing with data from just one location in California.
End of explanation
"""


%tensorflow_version 2.x

"""
Explanation: Load TensorFlow
Next, we'll load the TensorFlow library.
TensorFlow released version 2.0 in late 2019. As of the writing of the lab, Colab supports both versions 1 and 2, but it defaults to version 1. In order to tell Colab to use TensorFlow 2, you need to run the magic in the cell below.
End of explanation
"""


import tensorflow as tf
tf.__version__

"""
Explanation: Next, we'll load TensorFlow and check to make sure that we are running version 2.
End of explanation
"""


tf.keras.backend.set_floatx('float64')

"""
Explanation: Finally, we can set some global settings for TensorFlow. In this case we want to ensure that any time there is a question about the size of a floating point value that it is processed as a 64-bit number.
End of explanation
"""


testing_ds = tf.data.Dataset.from_tensor_slices((
    {c: testing_df[c] for c in feature_columns},  # feature map
    testing_df[target_column]                     # labels
))

training_ds = tf.data.Dataset.from_tensor_slices((
    {c: training_df[c] for c in feature_columns},  # feature map
    training_df[target_column]                     # labels
))

testing_ds, training_ds

"""
Explanation: TensorFlow Data Set
DataFrame is a container for a dataset in Pandas. To process the data with TensorFlow we need to get the data in the DataFrame into a TensorFlow Dataset.
Since our housing data fits in memory, we can use the from_tensor_slices class method to create our Dataset. There are a few different data formats that we could pass the method, but our model expects a feature map and a list of labels.
A feature map is a Python dictionary with feature names for keys and an iterable of column values as the value. Labels are just an iterable of our target values.
Below, we create the test and training DataSet objects.
End of explanation
"""


import numpy as np

testing_ds_count = testing_ds.reduce(np.int64(0), lambda x, _: x + 1)
training_ds_count = training_ds.reduce(np.int64(0), lambda x, _: x + 1)

print(testing_ds_count.numpy())
print(training_ds_count.numpy())

"""
Explanation: The code above runs and displays two TensorSliceDataset objects that seem to have the correct columns. However, we can't tell how many rows of data each contains.
Intuitively you'd think this would be as simple as asking for the length of the data sets from Python:
len(testing_ds)
 len(training_ds)
This won't work, though. TensorFlow Dataset objects can represent in-memory data, like what we have now. They can also represent data in multiple sources stored in different locations. They can even represent a stream of data that is never-ending. For this reason having a standard len is impossible.
Because of this ], we'll need to do a little more work to get a count of the data in a TensorFlow dataset. To get a count, we'll use the reduce operation. This operation takes an initial value, in our case 0, and then performs some function over and over for each row in the dataset. In this case we just add one for each value. The reduction returns values for each row and feeds it to the next. The final row simply returns the value to the runtime.
We can see below that the reduce operation counts the number of rows for the testing and training dataset and they both match the values we saw above in the Colab.
End of explanation
"""


housing_features = [
    tf.feature_column.numeric_column(c, dtype=tf.dtypes.float64) 
      for c in numeric_feature_columns
]

housing_features.append(
    tf.feature_column.categorical_column_with_vocabulary_list(
        key='ocean_proximity',
        vocabulary_list=sorted(housing_df['ocean_proximity'].unique()))
)

linear_regressor = tf.estimator.LinearRegressor(
    feature_columns=housing_features,
)

linear_regressor

"""
Explanation: LinearRegressor
The model that we'll use is the LinearRegressor. This class complies with the  TensorFlow Estimator API. This API takes care of a lot of the low-level model plumbing, and exposes convenient methods for performing model training, evaluation, and inference.
Though the LinearRegressor has many configuration options, only feature columns have to be specified when the regressor is created.
We provide the regressor feature columns as a list of columns that we'd like the model to use for training and prediction. For now that will be every one of our features. Most of these columns are all floating point numbers so we use a list expansion to create a list of float64 numeric_column objects.
For the ocean_proximity column we create a categorical column. This converts the values in the column into numbers matching their index in the vocabulary list.
A warning will be issued if you don't specify a model_dir. For now that's fine since we don't plan on saving our model and plan to train it completely now. If we do specify a model directory, state will be saved, which can cause issues as you iterate on the design of the model.
End of explanation
"""


housing_features = [
    tf.feature_column.numeric_column(c, dtype=tf.dtypes.float64) 
      for c in numeric_feature_columns
]

housing_features.append(
    tf.feature_column.categorical_column_with_vocabulary_list(
        key='ocean_proximity',
        vocabulary_list=sorted(housing_df['ocean_proximity'].unique()))
)

mirrored_strategy = tf.distribute.MirroredStrategy()
config = tf.estimator.RunConfig(
    train_distribute=mirrored_strategy,
    eval_distribute=mirrored_strategy,
)

linear_regressor = tf.estimator.LinearRegressor(
    feature_columns=housing_features,
    config=config,
)

linear_regressor

"""
Explanation: If we had multiple workers, we could distribute the training and evaluation of the model by using a distribution strategy. In the example below, you can see that we are using a MirroredStrategy to spread out the work.
More information on distributing Estimator work can be found in the TensorFlow documentation.
End of explanation
"""


def training_input():
  ds = tf.data.Dataset.from_tensor_slices((
    {c: training_df[c] for c in feature_columns},  # feature map
    training_df[target_column]                     # labels
  ))
  ds = ds.repeat(100)
  ds = ds.shuffle(buffer_size=10000)
  ds = ds.batch(100)
  return ds

"""
Explanation: Training Input Function
The LinearRegressor that we just created is still not trained. To train the model we need to call the train method and pass it an input function that provides a Dataset to extract data from.
We saw how to create a Dataset earlier. It would be nice if we could reuse that Dataset, but TensorFlow requires that you create the Dataset in your function, so we'll use the same Dataset creation code from above.
We also need to change a few attributes of the dataset. Our training data only has 13600 records, which isn't a lot of data. We can choose to repeat the data so that it is fed to the model multiple times. In this case we chose to repeat it 10 times. Hopefully this will give the optimizer enough data to find a good solution.
Since we are repeating the same data over and over, we also are going to shuffle it in between repeats. This will add some variability to the training data.
Finally, we choose to process the data in batches of 100. These mini batches of 100 are used for a single optimization step.
End of explanation
"""


linear_regressor.train(input_fn=training_input)

"""
Explanation: Training
We can now call the train method on the regressor, passing it the input function that we defined.
End of explanation
"""


# Your code goes here

"""
Explanation: We can see in the above output how TensorFlow's LinearRegressor will tell us, while it's training, what the loss is as the model improves. This output can be useful when, later on, we'll tweak the learning rate.
Testing Input Function
In order to evaluate the quality of our model, we need to make predictions and see how close they are to reality. To do this we rely on the predict() method.
Similar to train, this method expects an input function. We'll create one similar to the one we created for train, only we won't repeat or shuffle the data and will process the data in batches of 1.
Exercise 3: Create a Testing Input Function
Create a testing input function called testing_input. The function should accept no arguments and should return a Dataset. The Dataset should not repeat, nor shuffle, and should have batches of size 1. Also, target/label values aren't needed for testing input.
End of explanation
"""


predictions = linear_regressor.predict(input_fn=testing_input)

"""
Explanation: Make Predictions
Now we need to make predictions using our test features. To do that we pass our testing input function to the predict method on our trained linear regressor.
End of explanation
"""


predicted_median_values = [item['predictions'][0] for item in predictions]
print("Our predictions: ", predicted_median_values)

"""
Explanation: That runs pretty fast... almost suspiciously fast. The reason is that the model isn't actually making predictions at this point. We have just built the graph to make predictions. TensorFlow is using lazy execution. The predictions won't be made until we ask for them.
Let's go ahead and get the predictions and put them in a NumPy array so that we can calculate our error.
End of explanation
"""


import math

from sklearn import metrics

mean_squared_error = metrics.mean_squared_error(
    np.array(predicted_median_values) * TARGET_FACTOR,
    testing_df[target_column] * TARGET_FACTOR
)
print("Mean Squared Error (on training data): %0.3f" % mean_squared_error)

root_mean_squared_error = math.sqrt(mean_squared_error)
print("Root Mean Squared Error (on training data): %0.3f" % root_mean_squared_error)

"""
Explanation: Evaluate Model
Now that we have predictions, we can compare them to our actual values and evaluate the quality of our model.
End of explanation
"""


testing_df[target_column].mean() * TARGET_FACTOR

"""
Explanation: What is this telling us? The mean square error is somewhat hard to think about. However, whenever you take the root you get the units of the target column. In our test run, we were 68700.557 dollars off on our predictions. (Your numbers might be slightly different because we randomly shuffled the data before splitting it into training and testing datasets.)
Is that good?
Let's see what the mean price is in our test data.
End of explanation
"""


%tensorflow_version 2.x

import math
import numpy as np
import pandas as pd
import tensorflow as tf

from sklearn import metrics

tf.keras.backend.set_floatx('float64')

# Load the data
url = ('https://download.mlcc.google.com/mledu-datasets/' + 
       'california_housing_train.csv')

housing_df = pd.read_csv(url)

# Repair data
has_all_data = housing_df[~housing_df['total_bedrooms'].isna()]
sums = has_all_data[['total_bedrooms', 'total_rooms']].median().tolist()
bedrooms_to_total_rooms_ratio = sums[0] / sums[1]
missing_total_bedrooms_idx = housing_df['total_bedrooms'].isna()
housing_df.loc[missing_total_bedrooms_idx, 'total_bedrooms'] = housing_df[
    missing_total_bedrooms_idx]['total_rooms'] * bedrooms_to_total_rooms_ratio

# Create lists of column names
target_column = 'median_house_value'
feature_columns = [c for c in housing_df.columns if c != target_column]
numeric_feature_columns = [c for c in feature_columns if c != 'ocean_proximity']

# Normalize the feature columns
housing_df.loc[:, numeric_feature_columns] = (
    housing_df[numeric_feature_columns] - 
      housing_df[numeric_feature_columns].min()) / (
          housing_df[numeric_feature_columns].max() -
            housing_df[numeric_feature_columns].min())

# Scale the target column
TARGET_FACTOR = 100000
housing_df[target_column] = housing_df[target_column] / TARGET_FACTOR

# Test/Train split
housing_df = housing_df.sample(frac=1)
test_set_size = int(len(housing_df) * 0.2)
testing_df = housing_df[:test_set_size]
training_df = housing_df[test_set_size:]

# Create TensorFlow features
housing_features = [
    tf.feature_column.numeric_column(c, dtype=tf.dtypes.float64) 
      for c in numeric_feature_columns
]
housing_features.append(
    tf.feature_column.categorical_column_with_vocabulary_list(
        key='ocean_proximity',
        vocabulary_list=sorted(housing_df['ocean_proximity'].unique()))
)

# Create model
linear_regressor = tf.estimator.LinearRegressor(
    feature_columns=housing_features,
    # TODO: Set Optimizer
)

# Train the model
def training_input():
  ds = tf.data.Dataset.from_tensor_slices((
    {c: training_df[c] for c in feature_columns},  # feature map
    training_df[target_column]                     # labels
  ))
  # TODO: Configure Dataset
  return ds

linear_regressor.train(
 input_fn=training_input
)

# Make predictions
def testing_input():
  ds = tf.data.Dataset.from_tensor_slices((
    {c: testing_df[c] for c in feature_columns},  # feature map
    testing_df[target_column]                     # labels
  ))
  # TODO: Configure Dataset
  return ds

predictions_node = linear_regressor.predict(
  input_fn=testing_input,
)

# Convert the predictions to a NumPy array
predicted_median_values = np.array(
    [item['predictions'][0] for item in predictions_node])

# Find the RMSE
root_mean_squared_error = math.sqrt(
    metrics.mean_squared_error(
      predicted_median_values * TARGET_FACTOR,
      testing_df[target_column] * TARGET_FACTOR
))

print("Root Mean Squared Error (on training data): %0.3f" % 
root_mean_squared_error)

"""
Explanation: About 206,700 dollars. 68,700 is about 33% of 206,700 so our model is off by a mean of 33% of the actual price. I probably wouldn't make many bets using this model.
Exercise 4: Hyperparameters
There are a few hyperparameters that we can adjust in order to try to improve our model. In the code cell below, you'll find most of the code that we've used so far in this lab. There are three TODO markers in the code. Find them and:

Have the model use the Adam Optimizer
Configure the training Dataset. Experiment with different batch sizes. Leave the batch size that performs the best in the code.
Configure the testing Dataset.

Student Solution
End of explanation
"""


# Your Code Goes Here

"""
Explanation: Exercise 5: Weights
The LinearRegressor builds a linear model with weights for each feature. Use the get_variable_names and get_variable_value methods to find the weights. Print the weights in a format similar to that shown below:
bias_weights 3.170546
population -12.792054
median_income 5.906482
total_bedrooms 5.3723865
households 4.3297663
longitude -3.7551448
latitude -3.533678
total_rooms -2.850763
housing_median_age 0.66154426
The columns are sorted by the relative impact to the formula (absolute value). Notice the bias_weights in the list. This is the constant bias and should go first in the list.
Student Solution
End of explanation
"""


