Let us begin the tutorial with a classical problem called Linear Regression [1]. In this chapter, we will train a model from a realistic dataset to predict home prices. Some important concepts in Machine Learning will be covered through this example.
The source code for this tutorial lives on book/fit_a_line. For instructions on getting started with PaddlePaddle, see PaddlePaddle installation guide.
Suppose we have a dataset of
Each home is associated with
In our problem setup, the attribute
where
We first show the result of our model. The dataset UCI Housing Data Set is used to train a linear model to predict the home prices in Boston. The figure below shows the predictions the model makes for some home prices. The
Figure 1. Predicted Value V.S. Actual Value
In the UCI Housing Data Set, there are 13 home attributes
where
Now we need an objective to optimize, so that the learned parameters can make
For Linear Regression, the most common loss function is Mean Square Error (MSE) which has the following form:
That is, for a dataset of size
After setting up our model, there are several major steps to go through to train it:
- Initialize the parameters including the weights
and the bias . For example, we can set their mean values as $0$s, and their standard deviations as $1$s. - Feedforward. Evaluate the network output and compute the corresponding loss.
- Backpropagate the errors. The errors will be propagated from the output layer back to the input layer, during which the model parameters will be updated with the corresponding errors.
- Repeat steps 2~3, until the loss is below a predefined threshold or the maximum number of repeats is reached.
Our program starts with importing necessary packages:
import paddle.v2 as paddle
import paddle.v2.dataset.uci_housing as uci_housing
We encapsulated the UCI Housing Data Set in our Python module uci_housing
. This module can
- download the dataset to
~/.cache/paddle/dataset/uci_housing/housing.data
, if not yet, and - preprocesses the dataset.
The UCI housing dataset has 506 instances. Each instance describes the attributes of a house in surburban Boston. The attributes are explained below:
Attribute Name | Characteristic | Data Type |
---|---|---|
CRIM | per capita crime rate by town | Continuous |
ZN | proportion of residential land zoned for lots over 25,000 sq.ft. | Continuous |
INDUS | proportion of non-retail business acres per town | Continuous |
CHAS | Charles River dummy variable | Discrete, 1 if tract bounds river; 0 otherwise |
NOX | nitric oxides concentration (parts per 10 million) | Continuous |
RM | average number of rooms per dwelling | Continuous |
AGE | proportion of owner-occupied units built prior to 1940 | Continuous |
DIS | weighted distances to five Boston employment centres | Continuous |
RAD | index of accessibility to radial highways | Continuous |
TAX | full-value property-tax rate per $10,000 | Continuous |
PTRATIO | pupil-teacher ratio by town | Continuous |
B | 1000(Bk - 0.63)^2 where Bk is the proportion of blacks by town | Continuous |
LSTAT | % lower status of the population | Continuous |
MEDV | Median value of owner-occupied homes in $1000's | Continuous |
The last entry is the median home price.
We define a feature vector of length 13 for each home, where each entry corresponds to an attribute. Our first observation is that, among the 13 dimensions, there are 12 continuous dimensions and 1 discrete dimension.
Note that although a discrete value is also written as numeric values such as 0, 1, or 2, its meaning differs from a continuous value drastically. The linear difference between two discrete values has no meaning. For example, suppose
We also observe a huge difference among the value ranges of the 13 features (Figure 2). For instance, the values of feature B fall in
There are at least three reasons for Feature Normalization (Feature Scaling):
- A value range that is too large or too small might cause floating number overflow or underflow during computation.
- Different value ranges might result in varying importances of different features to the model (at least in the beginning of the training process). This assumption about the data is often unreasonable, making the optimization difficult, which in turn results in increased training time.
- Many machine learning techniques or models (e.g., L1/L2 regularization and Vector Space Model) assumes that all the features have roughly zero means and their value ranges are similar.
Figure 2. The value ranges of the features
We split the dataset in two, one for adjusting the model parameters, namely, for model training, and the other for model testing. The model error on the former is called the training error, and the error on the latter is called the test error. Our goal in training a model is to find the statistical dependency between the outputs and the inputs, so that we can predict new outputs given new inputs. As a result, the test error reflects the performance of the model better than the training error does. We consider two things when deciding the ratio of the training set to the test set: 1) More training data will decrease the variance of the parameter estimation, yielding more reliable models; 2) More test data will decrease the variance of the test error, yielding more reliable test errors. One standard split ratio is
When training complex models, we usually have one more split: the validation set. Complex models usually have Hyperparameters that need to be set before the training process, such as the number of layers in the network. Because hyperparameters are not part of the model parameters, they cannot be trained using the same loss function. Thus we will try several sets of hyperparameters to train several models and cross-validate them on the validation set to pick the best one; finally, the selected trained model is tested on the test set. Because our model is relatively simple, we will omit this validation process.
fit_a_line/trainer.py
demonstrates the training using PaddlePaddle.
paddle.init(use_gpu=False, trainer_count=1)
Logistic regression is essentially a fully-connected layer with linear activation:
x = paddle.layer.data(name='x', type=paddle.data_type.dense_vector(13))
y_predict = paddle.layer.fc(input=x,
size=1,
act=paddle.activation.Linear())
y = paddle.layer.data(name='y', type=paddle.data_type.dense_vector(1))
cost = paddle.layer.mse_cost(input=y_predict, label=y)
parameters = paddle.parameters.create(cost)
optimizer = paddle.optimizer.Momentum(momentum=0)
trainer = paddle.trainer.SGD(cost=cost,
parameters=parameters,
update_equation=optimizer)
PaddlePaddle provides the reader mechanism for loadinng training data. A reader may return multiple columns, and we need a Python dictionary to specify the mapping from column index to data layers.
feeding={'x': 0, 'y': 1}
Moreover, an event handler is provided to print the training progress:
# event_handler to print training and testing info
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 100 == 0:
print "Pass %d, Batch %d, Cost %f" % (
event.pass_id, event.batch_id, event.cost)
if isinstance(event, paddle.event.EndPass):
result = trainer.test(
reader=paddle.batch(
uci_housing.test(), batch_size=2),
feeding=feeding)
print "Test %d, Cost %f" % (event.pass_id, result.cost)
# event_handler to print training and testing info
from paddle.v2.plot import Ploter
train_title = "Train cost"
test_title = "Test cost"
plot_cost = Ploter(train_title, test_title)
step = 0
def event_handler_plot(event):
global step
if isinstance(event, paddle.event.EndIteration):
if step % 10 == 0: # every 10 batches, record a train cost
plot_cost.append(train_title, step, event.cost)
if step % 100 == 0: # every 100 batches, record a test cost
result = trainer.test(
reader=paddle.batch(
uci_housing.test(), batch_size=2),
feeding=feeding)
plot_cost.append(test_title, step, result.cost)
if step % 100 == 0: # every 100 batches, update cost plot
plot_cost.plot()
step += 1
if isinstance(event, paddle.event.EndPass):
if event.pass_id % 10 == 0:
with open('params_pass_%d.tar' % event.pass_id, 'w') as f:
parameters.to_tar(f)
trainer.train(
reader=paddle.batch(
paddle.reader.shuffle(
uci_housing.train(), buf_size=500),
batch_size=2),
feeding=feeding,
event_handler=event_handler_plot,
num_passes=30)
test_data_creator = paddle.dataset.uci_housing.test()
test_data = []
test_label = []
for item in test_data_creator():
test_data.append((item[0],))
test_label.append(item[1])
if len(test_data) == 5:
break
# load parameters from tar file.
# users can remove the comments and change the model name
# with open('params_pass_20.tar', 'r') as f:
# parameters = paddle.parameters.Parameters.from_tar(f)
probs = paddle.infer(
output_layer=y_predict, parameters=parameters, input=test_data)
for i in xrange(len(probs)):
print "label=" + str(test_label[i][0]) + ", predict=" + str(probs[i][0])
This chapter introduces Linear Regression and how to train and test this model with PaddlePaddle, using the UCI Housing Data Set. Because a large number of more complex models and techniques are derived from linear regression, it is important to understand its underlying theory and limitation.
- https://en.wikipedia.org/wiki/Linear_regression
- Friedman J, Hastie T, Tibshirani R. The elements of statistical learning[M]. Springer, Berlin: Springer series in statistics, 2001.
- Murphy K P. Machine learning: a probabilistic perspective[M]. MIT press, 2012.
- Bishop C M. Pattern recognition[J]. Machine Learning, 2006, 128.
This tutorial is contributed by PaddlePaddle, and licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.