人工智能小白日记之8 ML学习篇之4验证集

前言

这一篇是验证里面的编程练习,validation.ipynb。验证一文中提出,如果重复使用测试集做校验,会产生对验证集过拟合的问题。因此,提出了验证集,验证集做校验,测试集最后测试一遍即可。

人工智能小白日记之8 ML学习篇之4验证集
ps:终于要用上多特征了,还记得上一节差点误解合成特征是这个多特征。然后是验证数据,测试数据等的使用了。

课程内容

1 数据集

还是一样远程导入数据

from __future__ import print_function

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

#如果出现证书问题,添加以下代码
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

california_housing_dataframe = pd.read_csv("https://download.mlcc.google.cn/mledu-datasets/california_housing_train.csv", sep=",")

#california_housing_dataframe = california_housing_dataframe.reindex(
#     np.random.permutation(california_housing_dataframe.index))

ps:眼尖的人已经发现最后一行被注释掉了,这是干啥?把dataframe的随机化给去掉了。

2 数据处理

def preprocess_features(california_housing_dataframe):
  """Prepares input features from California housing data set.

  Args:
    california_housing_dataframe: A Pandas DataFrame expected to contain data
      from the California housing data set.
  Returns:
    A DataFrame that contains the features to be used for the model, including
    synthetic features.
  """
  selected_features = california_housing_dataframe[
    ["latitude",
     "longitude",
     "housing_median_age",
     "total_rooms",
     "total_bedrooms",
     "population",
     "households",
     "median_income"]]
  processed_features = selected_features.copy()
  # Create a synthetic feature.
  processed_features["rooms_per_person"] = (
    california_housing_dataframe["total_rooms"] /
    california_housing_dataframe["population"])
  return processed_features

def preprocess_targets(california_housing_dataframe):
  """Prepares target features (i.e., labels) from California housing data set.

  Args:
    california_housing_dataframe: A Pandas DataFrame expected to contain data
      from the California housing data set.
  Returns:
    A DataFrame that contains the target feature.
  """
  output_targets = pd.DataFrame()
  # Scale the target to be in units of thousands of dollars.
  output_targets["median_house_value"] = (
    california_housing_dataframe["median_house_value"] / 1000.0)
  return output_targets

由于这里使用了8个特征,于是封装了2个方法。第一个方法preprocess_features,传入california_housing_dataframe之后,会得到8个特征+rooms_per_person这个合成特征,结果是个DataFrame。

第2个方法preprocess_targets,参数也是california_housing_dataframe,最后得到median_house_value,结果是个Series。这些经过前面几节的学习现在不成问题了。

知识扩展
python里面单行注释用#,多行注释用啥?

3 筛选训练集

训练集包括训练用特征x和训练用标签y

#训练用特征x
training_examples = preprocess_features(california_housing_dataframe.head(12000))
training_examples.describe()

人工智能小白日记之8 ML学习篇之4验证集
知识扩展
california_housing_dataframe.head(12000),这里选择训练特征的时候选择了头部12000条数据,因为我们前面打印过,一共有17000条。所以这里没问题。

#训练用标签y
training_targets = preprocess_targets(california_housing_dataframe.head(12000))
training_targets.describe()

人工智能小白日记之8 ML学习篇之4验证集

4 筛选验证集

从共 17000 个样本中选择后 5000 个样本。

#验证用特征x
validation_examples = preprocess_features(california_housing_dataframe.tail(5000))
validation_examples.describe()
#验证用标签y
validation_targets = preprocess_targets(california_housing_dataframe.tail(5000))
validation_targets.describe()

任务 1:检查数据

好的,我们看一下上面的数据。可以使用的输入特征有 9 个。

快速浏览一下表格中的值。一切看起来正常吗?看一下您可以发现多少问题。如果您没有统计学方面的背景知识,也不必担心;您可以运用常识。

ps:好吧,恕我眼拙,我只看出来。max的数据有点夸张啊,跟其他值偏离有点大。
还有就是,你教我的训练集,验证集和测试集。现在你全部用完了,拿什么做测试集????。

揭晓下答案:

我们根据基准预期情况检查一下我们的数据:

对于一些值(例如 median_house_value),我们可以检查这些值是否位于合理的范围内(请注意,这是 1990 年的数据,不是现在的!)。

对于 latitude 和 longitude 等其他值,我们可以通过 Google 进行快速搜索,并快速检查一下它们与预期值是否一致。

如果您仔细看,可能会发现下列异常情况:

median_income 位于 3 到 15 的范围内。我们完全不清楚此范围究竟指的是什么,看起来可能是某对数尺度?无法找到相关记录;我们所能假设的只是,值越高,相应的收入越高。

median_house_value 的最大值是 500001。这看起来像是某种人为设定的上限。

rooms_per_person 特征通常在正常范围内,其中第 75 百分位数的值约为 2。但也有一些非常大的值(例如 18 或 55),这可能表明数据有一定程度的损坏。

ps:原来是这样,蒙到了1条。前面两条的大概意思是,我们根据这些特征名,可以初步大概查询一下,判断一下是否在正常值以内。

任务 2:绘制纬度/经度与房屋价值中位数的曲线图

我们来详细了解一下 latitude 和 longitude 这两个特征。它们是相关城市街区的地理坐标。

利用这两个特征可以提供出色的可视化结果 - 我们来绘制 latitude 和 longitude 的曲线图,然后用颜色标注 median_house_value。

分析:这次不吃上次的亏了,要求是绘制latitude 和 longitude 的曲线图,肯定是拿latitude做横坐标,longitude做纵坐标,绘制一个个点。用颜色标注median_house_value的话,就是给这些点上色,不过颜色是根据median_house_value变化的。那很明显相当于一个热力图,median_house_value越高的时候颜色越深就对了。

回忆一下,绘制散点图
plt.scatter(dataframe[“longitude”],
dataframe[“latitude”],
c=?)
大概这个样子,这个median_house_value的值怎么变成颜色呢?我们上次挖坑的时候,是不是刚好试过了cm.coolwarm(x),x必须是介于(-1,1)之间的数。那就需要对median_house_value的值做归一化,归一化的最好办法就是除以最大值。所以应该是这样的:
plt.scatter(dataframe[“longitude”],
dataframe[“latitude”],
c=cm.coolwarm(dataframe[‘median_house_value’]/dataframe[‘median_house_value’].max()))

好的我们来一发,这里选用训练集绘制一发:

 
'''
画图之前首先设置figure对象,此函数相当于设置一块自定义大小的画布,
使得后面的图形输出在这块规定了大小的画布上,其中参数figsize设置画布大小
'''
plt.figure(figsize=(8,8),dpi=80)
'''
将figure设置的画布大小分成几个部分,参数‘221’表示2(row)x2(colu),即将画布分成2x2,
两行两列的4块区域,1表示选择图形输出的区域在第一块,图形输出区域参数必须在“行x列”范围,
此处必须在1和2之间选择——如果参数设置为subplot(111),则表示画布整个输出,不分割成小块区域,图形直接输出在整块画布上
'''
plt.subplot(221) 
plt.scatter(validation_examples["longitude"],
            validation_examples["latitude"],
            c=cm.coolwarm(validation_targets['median_house_value']/validation_targets['median_house_value'].max()))

运行:
人工智能小白日记之8 ML学习篇之4验证集
哇咔咔,我这算完成了吗?

看一下官方给的答案:

plt.figure(figsize=(13, 8))

ax = plt.subplot(1, 2, 1)
ax.set_title("Validation Data")

ax.set_autoscaley_on(False)
ax.set_ylim([32, 43])
ax.set_autoscalex_on(False)
ax.set_xlim([-126, -112])
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()

人工智能小白日记之8 ML学习篇之4验证集
嗯,答案差不多,有细微差别,来来来,扩展一下。

知识扩展
1)ax.set_autoscaley_on(False)取消自动缩放,难道我那个可以缩放?貌似也不能啊
2)ax.set_ylim([32, 43]) 设置数值范围,这个可以从上面describe时后看到min和max
3)其他的差不多,就是c那里貌似有新的写法。

文中原话‘稍等片刻…现在应该已经呈现出一幅不错的加利福尼亚州地图了,其中旧金山和洛杉矶等住房成本高昂的地区用红色表示。’
加利福利亚州地图啊,我看看
人工智能小白日记之8 ML学习篇之4验证集
额,像吗?不太对啊,好像上面的两个图,就是把地图劈成两半有木有,我知道了,还记得我们选的训练集和验证集吗,不就是一头12000,一尾5000吗,如果拼起来就是全部地图了。

“需要注意的关键一点是,对于任何指定特征或列,训练集和验证集之间的值的分布应该大致相同。”根据这句话的话,以上的两张图显然不符合结果,原因就是数据分布不均匀,刚好顺序分配了,所以你发现问题在哪了吗?

任务 3:返回来看数据导入和预处理代码,看一下您是否发现了任何错误

如果您发现了错误,请修复该错误。将查看时间控制在一到两分钟之内。如果您未发现任何错误,请查看解决方案。

发现并解决问题后,重新运行上面的 latitude/longitude 绘图单元格,并确认我们的健全性检查的结果看上去更好了。

分析:很明显任务2的结果不对,因为训练集和验证集的分布不对,不对的原因是因为按顺序采样了,还记得一开始发现的随机化被注释了吗?原来这是作者给我们挖的坑啊,赶紧恢复过来。注意由于是数据集变了,后面代码都要重新执行一遍
人工智能小白日记之8 ML学习篇之4验证集
最后的结果:
人工智能小白日记之8 ML学习篇之4验证集
棒棒哒,这次肯定对了。

任务 4:训练和评估模型

花费约 5 分钟的时间尝试不同的超参数设置。尽可能获取最佳验证效果。

然后,我们会使用数据集中的所有特征训练一个线性回归器,看看其表现如何。

ps:这题,超难的有木有,直接投降了,1维直接变9维。抄代码什么的,可以,编这么多代码,对不起,还做不到啊。

1)输入函数还是一毛一样的

def my_input_fn(features, targets, batch_size=1, shuffle=True, num_epochs=None):
    """Trains a linear regression model of multiple features.
  
    Args:
      features: pandas DataFrame of features
      targets: pandas DataFrame of targets
      batch_size: Size of batches to be passed to the model
      shuffle: True or False. Whether to shuffle the data.
      num_epochs: Number of epochs for which data should be repeated. None = repeat indefinitely
    Returns:
      Tuple of (features, labels) for next data batch
    """
    
    # Convert pandas data into a dict of np arrays.
    features = {key:np.array(value) for key,value in dict(features).items()}                                           
 
    # Construct a dataset, and configure batching/repeating.
    ds = Dataset.from_tensor_slices((features,targets)) # warning: 2GB limit
    ds = ds.batch(batch_size).repeat(num_epochs)
    
    # Shuffle the data, if specified.
    if shuffle:
      ds = ds.shuffle(10000)
    
    # Return the next batch of data.
    features, labels = ds.make_one_shot_iterator().get_next()
    return features, labels

2)构建特征列

def construct_feature_columns(input_features):
  """Construct the TensorFlow Feature Columns.

  Args:
    input_features: The names of the numerical input features to use.
  Returns:
    A set of feature columns
  """ 
  return set([tf.feature_column.numeric_column(my_feature)
              for my_feature in input_features])

ps:这次有点不一样了,主要是因为采用了多特征,所以对多特征做遍历,分别定义特征列,然后放到set里。

“由于我们现在使用的是多个输入特征,因此需要把用于将特征列配置为独立函数的代码模块化。(目前此代码相当简单,因为我们的所有特征都是数值,但当我们在今后的练习中使用其他类型的特征时,会基于此代码进行构建。)”

3)然后是改造版训练方法
可以看到参数由以前的input_features升级成了4个

def train_model(
    learning_rate,
    steps,
    batch_size,
    training_examples,
    training_targets,
    validation_examples,
    validation_targets):
  """Trains a linear regression model of multiple features.
  
  In addition to training, this function also prints training progress information,
  as well as a plot of the training and validation loss over time.
  
  Args:
    learning_rate: A `float`, the learning rate.
    steps: A non-zero `int`, the total number of training steps. A training step
      consists of a forward and backward pass using a single batch.
    batch_size: A non-zero `int`, the batch size.
    training_examples: A `DataFrame` containing one or more columns from
      `california_housing_dataframe` to use as input features for training.
    training_targets: A `DataFrame` containing exactly one column from
      `california_housing_dataframe` to use as target for training.
    validation_examples: A `DataFrame` containing one or more columns from
      `california_housing_dataframe` to use as input features for validation.
    validation_targets: A `DataFrame` containing exactly one column from
      `california_housing_dataframe` to use as target for validation.
      
  Returns:
    A `LinearRegressor` object trained on the training data.
  """

  periods = 10
  steps_per_period = steps / periods
  
  # Create a linear regressor object.
  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
  )
  
  # 1. Create input functions.
  training_input_fn = # YOUR CODE HERE
  predict_training_input_fn = # YOUR CODE HERE
  predict_validation_input_fn = # YOUR CODE HERE
  
  # Train the model, but do so inside a loop so that we can periodically assess
  # loss metrics.
  print("Training model...")
  print("RMSE (on training data):")
  training_rmse = []
  validation_rmse = []
  for period in range (0, periods):
    # Train the model, starting from the prior state.
    linear_regressor.train(
        input_fn=training_input_fn,
        steps=steps_per_period,
    )
    # 2. Take a break and compute predictions.
    training_predictions = # YOUR CODE HERE
    validation_predictions = # YOUR CODE HERE
    
    # Compute training and validation loss.
    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))
    # Occasionally print the current loss.
    print("  period %02d : %0.2f" % (period, training_root_mean_squared_error))
    # Add the loss metrics from this period to our list.
    training_rmse.append(training_root_mean_squared_error)
    validation_rmse.append(validation_root_mean_squared_error)
  print("Model training finished.")

  # Output a graph of loss metrics over periods.
  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()

  return linear_regressor

ps:里面有坑啊,发现没,很多需要我们补充的
人工智能小白日记之8 ML学习篇之4验证集
回忆一下以前单特征的代码,都做了啥:获取特征和标签,获取特征列,构建模型,输入函数,训练,评估,预测。
所以

1)create input functions这里,需要对训练,预测,验证预测分别定义输入函数,模仿一下前面的写法:

training_input_fn = lambda:my_input_fn(my_feature_data, targets, batch_size=batch_size)
prediction_input_fn = lambda: my_input_fn(my_feature_data, targets, num_epochs=1, shuffle=False)

  # Create input functions.
  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)

2)预测这里
人工智能小白日记之8 ML学习篇之4验证集
以前是这样的
predictions = linear_regressor.predict(input_fn=prediction_input_fn)
predictions = np.array([item[‘predictions’][0] for item in predictions])

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])

其他貌似没啥不一样

运行一下:

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)

人工智能小白日记之8 ML学习篇之4验证集
又翘尾巴了,大概在第5步的时候,步伐跨大了,这里降低学习速率,我发现一个经验,10个周期第6个周期到位,我把当前学习速率*0.6就可以了

linear_regressor = train_model(
    learning_rate=0.000018,
    steps=500,
    batch_size=5,
    training_examples=training_examples,
    training_targets=training_targets,
    validation_examples=validation_examples,
    validation_targets=validation_targets)

人工智能小白日记之8 ML学习篇之4验证集
差不多哈。

任务 5:基于测试数据进行评估

在以下单元格中,载入测试数据集并据此评估模型。

我们已对验证数据进行了大量迭代。接下来确保我们没有过拟合该特定样本集的特性。

ps:原来有弄好的测试集,我说怎么不从那里面抽样呢

获取测试集

california_housing_test_data = pd.read_csv("https://download.mlcc.google.cn/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)

人工智能小白日记之8 ML学习篇之4验证集
这算是好吧,还是坏呢,误差比我的还小,应该算好的吧。

小结

这把可以,没有挖大坑,以后遇到坑果断闪避。
验证集的使用我们看出来了,就是抽样一小部分出来就好了,当然训练集和验证集还是不能重合,跟前面了解到的是一样的。整个训练过程也是一毛一样的。