前两篇实现了线性回归模型得训练,并再最后进行了采用合成特征做为特征输入,且过滤了离群值,效果彷佛不错,可是这个不错的结果来自于咱们采用了原来的训练数据进行的测试,这样的评估意义大吗?若是咱们使用了新的(训练模型从未碰见过的)数据进行预测,获得的结果可能会不敬人意。
假设我对模型不断的进行训练,不断调整超参数,通过若干天后,获得的模型可以达到完美的预测效果,对于输入的任意特征都能获得正确的target,可是这并非咱们想要的模型,由于其只达到了对训练数据的完美贴合,但并不必定能对它从未见过的新数据作出正确的预测,而且其训练了太久,模型也会变的过于复杂,这样就叫作过拟合python
大概过程以下图(图片来源于谷歌机器学习样图):
为了让您直观地理解这一律念,咱们将展现 3 张图。假设这些图中的每一个点表明一棵树在森林中的位置。图中的两种颜色分别表明如下含义:git
图 1. 生病(蓝色)和健康(橙色)的树。算法
您能设想出一个有效的模型来预测之后的生病或健康的树吗?花点时间在脑海里绘制一条弧线将蓝点与橙点分开,或者在脑海中圈住一些橙点或蓝点。而后再看看图 2,它显示某种机器学习模型如何将生病的树与健康的树区分开。请注意,该模型产生的损失很是低。api
图 2. 用于区分生病的树与健康的树的复杂模型。
乍一看,图 2 所示的模型在将健康的树与生病的树区分开方面彷佛表现得很是出色。真的是这样吗?网络
图 3. 该模型在预测新数据方面表现很是糟糕。
图 3 显示咱们向该模型中添加了新数据后所发生的状况。结果代表,该模型在处理新数据方面表现很是糟糕。请注意,该模型对大部分新数据的分类都不正确。数据结构
图 2 和图 3 所示的模型过拟合了训练数据的特性。过拟合模型在训练过程当中产生的损失很低,但在预测新数据方面的表现却很是糟糕。若是某个模型在拟合当前样本方面表现良好,那么咱们如何相信该模型会对新数据作出良好的预测呢?正如您稍后将看到的,过拟合是因为模型的复杂程度超出所需程度而形成的。机器学习的基本冲突是适当拟合咱们的数据,但也要尽量简单地拟合数据。app
一种解决方法是将您的数据集分红两个子集:dom
通常来讲,在-测试集上表现是否良好是衡量可否在新数据上表现良好的有用指标,前提是:机器学习
咱们从分布中随机抽取样本,且这些样本是独立同分布 (i.i.d) 的模块化
接下来咱们来谈谈训练集和测试集
若是咱们只有一个数据集,可是须要训练集和测试集,办法很简单,拆分就行了,比例大概能够是4:1这样子
不过,拆分数据时候得遵循几个原则
1.数据集够大
2.随机抽取的,能表明数据集的水平
注意:请勿对测试数据进行训练,若是最后测试获得的结果意外的好,那最好检查一下,多数是由于对测试数据进行了误训练
这是咱们刚才讨论的划分方式--训练集和测试集
可是这样的划分方式,会不会有问题呢?若是为了在最后的测试数据上得到最佳效果,从而更改学习速率、添加或移除特征,到从头开始设计全新模型。当该工做流程结束时,在测试数据上表现很好,那么最终依然颇有多是过拟合的。
因此咱们能够进一步划分,即训练集+验证集合+测试集合
在这一通过改进的工做流程中:
该工做流程之因此更好,缘由在于它暴露给测试集的信息更少
接下来,咱们将对以前的理论进行验证
与在以前的练习中同样,咱们将依然使用加利福尼亚州住房数据集,尝试根据 1990 年的人口普查数据在城市街区级别预测 median_house_value
咱们首先加载并准备数据。这一次,咱们将使用多个特征,所以咱们会将逻辑模块化,以对特征进行预处理:
import math from IPython import display from matplotlib import cm from matplotlib import gridspec from matplotlib import pyplot as plt import numpy as np import pandas as pd from sklearn import metrics import tensorflow as tf from tensorflow.python.data import Dataset tf.logging.set_verbosity(tf.logging.ERROR) pd.options.display.max_rows = 10 pd.options.display.float_format = '{:.1f}'.format california_housing_dataframe = pd.read_csv("https://storage.googleapis.com/mledu-datasets/california_housing_train.csv", sep=",") # california_housing_dataframe = california_housing_dataframe.reindex( # np.random.permutation(california_housing_dataframe.index)) # 对特征预处理 def preprocess_features(california_housing_dataframe): selected_features = california_housing_dataframe[ ["latitude", "longitude", "housing_median_age", "total_rooms", "total_bedrooms", "population", "households", "median_income"]] processed_features = selected_features.copy() # 此外多建立一个合成特征 processed_features["rooms_per_person"] = ( california_housing_dataframe["total_rooms"] / california_housing_dataframe["population"]) return processed_features # 对target预处理 def preprocess_targets(california_housing_dataframe): # output_targets为pd.DataFrame()类型的数据结构(这种结构相似于表格,有行有列的索引) output_targets = pd.DataFrame() output_targets["median_house_value"] = ( california_housing_dataframe["median_house_value"] / 1000.0) return output_targets # 训练集取前12000(共17000样本) training_examples = preprocess_features(california_housing_dataframe.head(12000)) training_examples.describe()
latitude | longitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | rooms_per_person | |
---|---|---|---|---|---|---|---|---|---|
count | 12000.0 | 12000.0 | 12000.0 | 12000.0 | 12000.0 | 12000.0 | 12000.0 | 12000.0 | 12000.0 |
mean | 34.6 | -118.5 | 27.5 | 2655.7 | 547.1 | 1476.0 | 505.4 | 3.8 | 1.9 |
std | 1.6 | 1.2 | 12.1 | 2258.1 | 434.3 | 1174.3 | 391.7 | 1.9 | 1.3 |
min | 32.5 | -121.4 | 1.0 | 2.0 | 2.0 | 3.0 | 2.0 | 0.5 | 0.0 |
25% | 33.8 | -118.9 | 17.0 | 1451.8 | 299.0 | 815.0 | 283.0 | 2.5 | 1.4 |
50% | 34.0 | -118.2 | 28.0 | 2113.5 | 438.0 | 1207.0 | 411.0 | 3.5 | 1.9 |
75% | 34.4 | -117.8 | 36.0 | 3146.0 | 653.0 | 1777.0 | 606.0 | 4.6 | 2.3 |
max | 41.8 | -114.3 | 52.0 | 37937.0 | 5471.0 | 35682.0 | 5189.0 | 15.0 | 55.2 |
training_targets = preprocess_targets(california_housing_dataframe.head(12000)) training_targets.describe()
median_house_value | |
---|---|
count | 12000.0 |
mean | 198.0 |
std | 111.9 |
min | 15.0 |
25% | 117.1 |
50% | 170.5 |
75% | 244.4 |
max | 500.0 |
# 测试集取尾5000 validation_examples = preprocess_features(california_housing_dataframe.tail(5000)) validation_examples.describe()
latitude | longitude | housing_median_age | total_rooms | total_bedrooms | population | households | median_income | rooms_per_person | |
---|---|---|---|---|---|---|---|---|---|
count | 5000.0 | 5000.0 | 5000.0 | 5000.0 | 5000.0 | 5000.0 | 5000.0 | 5000.0 | 5000.0 |
mean | 38.1 | -122.2 | 31.3 | 2614.8 | 521.1 | 1318.1 | 491.2 | 4.1 | 2.1 |
std | 0.9 | 0.5 | 13.4 | 1979.6 | 388.5 | 1073.7 | 366.5 | 2.0 | 0.6 |
min | 36.1 | -124.3 | 1.0 | 8.0 | 1.0 | 8.0 | 1.0 | 0.5 | 0.1 |
25% | 37.5 | -122.4 | 20.0 | 1481.0 | 292.0 | 731.0 | 278.0 | 2.7 | 1.7 |
50% | 37.8 | -122.1 | 31.0 | 2164.0 | 424.0 | 1074.0 | 403.0 | 3.7 | 2.1 |
75% | 38.4 | -121.9 | 42.0 | 3161.2 | 635.0 | 1590.2 | 603.0 | 5.1 | 2.4 |
max | 42.0 | -121.4 | 52.0 | 32627.0 | 6445.0 | 28566.0 | 6082.0 | 15.0 | 18.3 |
validation_targets = preprocess_targets(california_housing_dataframe.tail(5000)) validation_targets.describe()
median_house_value | |
---|---|
count | 5000.0 |
mean | 229.5 |
std | 122.5 |
min | 15.0 |
25% | 130.4 |
50% | 213.0 |
75% | 303.2 |
max | 500.0 |
在上面咱们是把数据集中除了median_house_value做为target外,其余得全部列都做为了feature输入
咱们虽然没有什么数据分析或者统计学得背景知识,可是其实仍是能够看一看,或者说猜一猜输入的这些特征究竟是些什么,它们可能具备哪些意义?取值范围是怎么样的,数值的大小正常嘛?等等等
latitude:纬度
longitude:经度
housing_median_age:房子年纪(中值)
total_rooms:房间总数(每一个街区)
total_bedrooms:卧室总数(每一个街区)
total_bedrooms:人口数(每一个街区)
households:户(一家人为一户)
median_income :收入(中值)
再看看这些feature的数值(最大值,最小值),理解一下它们的单位(固然,会有不合理的,由于存在数据比较特殊,并且这是1990年的数据了),这些数据会帮助咱们在宏观上了解这些数据集,特别是一旦发现不合理的地方更是要注意了(尤为在训练本身的数据时)
异常状况:
median_income
位于 3 到 15 的范围内。咱们彻底不清楚此范围究竟指的是什么,看起来多是某对数尺度?没法找到相关记录;咱们所能假设的只是,值越高,相应的收入越高。median_house_value
的最大值是 500001。这看起来像是某种人为设定的上限。rooms_per_person
特征一般在正常范围内,其中第 75 百分位数的值约为 2。但也有一些很是大的值(例如 18 或 55),这可能代表数据有必定程度的损坏。咱们将暂时使用提供的这些特征。但但愿这些示例可帮助您较为直观地了解如何检查来自未知来源的数据
### 绘制纬度/经度与房屋价值中位数的曲线图
咱们来详细了解一下 latitude
和 longitude
这两个特征。它们是相关城市街区的地理坐标。
利用这两个特征能够提供出色的可视化结果 - 咱们来绘制 latitude
和 longitude
的曲线图,而后用颜色标注 median_house_value
plt.figure(figsize=(13, 8)) ax = plt.subplot(1, 2, 1) ax.set_title("Validation Data") # 取消y轴的自动缩放,并定义上下限 ax.set_autoscaley_on(False) ax.set_ylim([32, 43]) # 取消x轴的自动缩放,并定义上下限 ax.set_autoscalex_on(False) ax.set_xlim([-126, -112]) # plt.scatter()参数说明 # validation_examples["longitude"],validation_examples["latitude"]:表明x,y # cmap:Colormap,颜色表 # c:color(色彩,或者颜色序列) plt.scatter(validation_examples["longitude"], validation_examples["latitude"], cmap="coolwarm", c=validation_targets["median_house_value"] / validation_targets["median_house_value"].max()) ax = plt.subplot(1,2,2) ax.set_title("Training Data") ax.set_autoscaley_on(False) ax.set_ylim([32, 43]) ax.set_autoscalex_on(False) ax.set_xlim([-126, -112]) plt.scatter(training_examples["longitude"], training_examples["latitude"], cmap="coolwarm", c=training_targets["median_house_value"] / training_targets["median_house_value"].max()) _ = plt.plot()
当当当当,这个图的问题很明显呢,训练和测试的数据差别很大呢,问题在哪里呢? 都是一个数据集的哎,其实就是处理数据的时候,忘记了随机排序了(再次说明打乱顺序真的很重要,咱们永远没法预知本来序列的数据可能出现哪些问题),此外采用图标分析也很重要,可以在训练以前帮助咱们发现问题,否则后面就完蛋啦,怎么训练都不会有好的结果的。
调整后的结果
如今很像一个地图了
咱们会使用数据集中的全部特征训练一个线性回归器,定义一下之前将数据加载到 TensorFlow 模型中时所使用的同一输入函数
def my_input_fn(features, targets, batch_size=1, shuffle=True, num_epochs=None): features = {key:np.array(value) for key,value in dict(features).items()} ds = Dataset.from_tensor_slices((features,targets)) # warning: 2GB limit ds = ds.batch(batch_size).repeat(num_epochs) if shuffle: ds = ds.shuffle(10000) features, labels = ds.make_one_shot_iterator().get_next() return features, labels
因为咱们如今使用的是多个输入特征,所以须要把用于将特征列配置为独立函数的代码模块化。(目前此代码至关简单,由于咱们的全部特征都是数值,但当咱们在从此的练习中使用其余类型的特征时,会基于此代码进行构建。)
def construct_feature_columns(input_features): return set([tf.feature_column.numeric_column(my_feature) for my_feature in input_features])
接下来,继续完成下面的 train_model()
代码,以设置输入函数和计算预测。
但要确保针对相应数据集调用 predict()
比较训练数据和验证数据的损失
def train_model( learning_rate, steps, batch_size, training_examples, training_targets, validation_examples, validation_targets): periods = 10 steps_per_period = steps / periods # 建立线性回归模型并设定好特征列和优化器 my_optimizer = tf.train.GradientDescentOptimizer(learning_rate=learning_rate) my_optimizer = tf.contrib.estimator.clip_gradients_by_norm(my_optimizer, 5.0) linear_regressor = tf.estimator.LinearRegressor( feature_columns=construct_feature_columns(training_examples), optimizer=my_optimizer ) # 建立训练、预测(使用训练集的数据)、验证(使用验证集的数据)的输入函数 training_input_fn = lambda: my_input_fn( training_examples, training_targets["median_house_value"], batch_size=batch_size) predict_training_input_fn = lambda: my_input_fn( training_examples, training_targets["median_house_value"], num_epochs=1, shuffle=False) predict_validation_input_fn = lambda: my_input_fn( validation_examples, validation_targets["median_house_value"], num_epochs=1, shuffle=False) # 训练(周期性输出结果) print "Training model..." print "RMSE (on training data):" training_rmse = [] validation_rmse = [] for period in range (0, periods): # 按照训练steps进行周期性训练 linear_regressor.train( input_fn=training_input_fn, steps=steps_per_period, ) # 记录预测值(分别使用训练集和验证集) training_predictions = linear_regressor.predict(input_fn=predict_training_input_fn) training_predictions = np.array([item['predictions'][0] for item in training_predictions]) validation_predictions = linear_regressor.predict(input_fn=predict_validation_input_fn) validation_predictions = np.array([item['predictions'][0] for item in validation_predictions]) # 计算RMSE(使用训练值和验证值) training_root_mean_squared_error = math.sqrt( metrics.mean_squared_error(training_predictions, training_targets)) validation_root_mean_squared_error = math.sqrt( metrics.mean_squared_error(validation_predictions, validation_targets)) print " period %02d : %0.2f" % (period, training_root_mean_squared_error) training_rmse.append(training_root_mean_squared_error) validation_rmse.append(validation_root_mean_squared_error) print "Model training finished." plt.ylabel("RMSE") plt.xlabel("Periods") plt.title("Root Mean Squared Error vs. Periods") plt.tight_layout() plt.plot(training_rmse, label="training") plt.plot(validation_rmse, label="validation") # plt.legend()画图例,图中右上角 plt.legend() return linear_regressor
训练...
linear_regressor = train_model( learning_rate=0.00003, steps=500, batch_size=5, training_examples=training_examples, training_targets=training_targets, validation_examples=validation_examples, validation_targets=validation_targets)
由于使用的features较多,要耐心等一下子就能够看到使用训练值和校验值的区别了,以及多特征的效果怎么样
Training model...
RMSE (on training data):
period 00 : 217.67
period 01 : 201.13
period 02 : 186.67
period 03 : 176.46
period 04 : 170.31
period 05 : 167.41
period 06 : 166.75
period 07 : 166.49
period 08 : 167.72
period 09 : 169.76
Model training finished.
learning_rate=0.00003
要注意这是两条线哦,还有一条是校验集的结果,真的是很贴合了哎,说明模型效果挺好的
上面的learning_rate=0.00003,最终的rmse为169.76,这比上次的合成特征的高了好多,因此下面尝试修改learning_rate
learning_rate=0.00015
learning_rate=0.005
learning_rate=0.05
咱们会发现此次的损失函数曲线好像很复杂的样子。。。。emmmmmmm,其实到这里,我尚未调好,不过无论啦,下一篇我会说明一下,多特征的时候怎么来调节超参数(其实我只是比较懒.....)
最后,再看看以学习率为0.00003训练出来的模型,遇到测试集的效果吧
california_housing_test_data = pd.read_csv("https://storage.googleapis.com/mledu-datasets/california_housing_test.csv", sep=",") test_examples = preprocess_features(california_housing_test_data) test_targets = preprocess_targets(california_housing_test_data) predict_test_input_fn = lambda: my_input_fn( test_examples, test_targets["median_house_value"], num_epochs=1, shuffle=False) test_predictions = linear_regressor.predict(input_fn=predict_test_input_fn) test_predictions = np.array([item['predictions'][0] for item in test_predictions]) root_mean_squared_error = math.sqrt( metrics.mean_squared_error(test_predictions, test_targets)) print "Final RMSE (on test data): %0.2f" % root_mean_squared_error
Final RMSE (on test data): 162.84
结果很接近了呢,说明没有过拟合。
讲完。