学习和理解本文章的要求:
- 有数理统计的知识
- 有矩阵论的知识
- 有图像分析和处理知识
- 有Python编程基础
- 有机器学习和深度学习理论支撑
本教程适合有机器学习,深度学习基础的人员学习。如果没有上面储备知识,可能学起来会吃力,有些概念可能听起来很玄学。
Keras 机器学习基础知识文章链接:
- 对服装图像进行分类
- 使用Tensorflow Hub对未处理的电影评论数据集IMDB进行分类
- Keras 机器学习基础知识之对预处理的电影评论文本分类
前几天文章用tensorflow2.x的API进行了分类问题的解决。今天来一篇使用tensorflow2.x解决回归问题。回归 (regression) 问题的目的是预测出如价格,股票或概率这样连续值的输出。而分类(classification) 问题,目的是从一系列的分类出选择出一个分类 (如,给出一张包含苹果或橘子的图片,识别出图片中是哪种水果),是离散值。
本文使用 Auto MPG 数据集,文章基于此数据集构建了一个用来预测70年代末到80年代初汽车燃油效率的模型。数据集提供了汽车相关描述,包含:气缸数,排量,马力以及重量等(这些统称为属性)。
import tensorflow as tf
import matplotlib.pyplot as plt
import pathlib
import seaborn as sns # 使用 seaborn 绘制矩阵图 (pairplot)
import pandas as pd
from tensorflow import keras
from tensorflow.keras import layers
print(tf.__version__)
1.下载数据集AUTO_MPG,数据文件为CSV格式文件
# 返回文件下载完之后保存路径
data_path = keras.utils.get_file("auto_mpg","http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data")
2.使用pandas解析下载好的数据文件
# 各个属性的名称
column_names = ['MPG','Cylinders','Displacement','Horsepower','Weight',
'Acceleration', 'Model Year', 'Origin']
'''
sep: 指定分割符,默认是’,’C引擎不能自动检测分隔符,但Python解析引擎可以
names: 指定列名,如果文件中不包含header的行,应该显性表示header=None
na_values: 默认None NaN包含哪些情况,默认情况下, ‘#N/A’, ‘#N/A N/A’, ‘#NA’, ‘-1.#IND’, ‘-1.#QNAN’, ‘-NaN’, ‘-nan’, ‘1.#IND’, ‘1.#QNAN’, ‘N/A’, ‘NA’, ‘NULL’, ‘NaN’, ‘n/a’, ‘nan’, ‘null’. 都表现为NAN
skipinitialspace: 忽略分隔符后的空格,默认false
标识着多余的行不被解析。如果该字符出现在行首,这一行将被全部忽略。这个参数只能是一个字符,空行(就像skip_blank_lines=True)注释行被header和skiprows忽略一样。例如如果指定comment='#' 解析‘#empty\na,b,c\n1,2,3’ 以header=0 那么返回结果将是以’a,b,c'作为header。
'''
raw_dataset = pd.read_csv(data_path,name=column_names,na_values="?",comment="\t",sep=" ",skipinitialspace=True)
dataset = raw_dataset.copy()
print(dataset.tail())
MPG Cylinders Displacement ... Acceleration Model Year Origin
393 27.0 4 140.0 ... 15.6 82 1
394 44.0 4 97.0 ... 24.6 82 2
395 32.0 4 135.0 ... 11.6 82 1
396 28.0 4 120.0 ... 18.6 82 1
397 31.0 4 119.0 ... 19.4 82 1
对含有非数值型或者属性值为类别型的记录进行处理。
1.统计非数值型记录的个数
dataset.isna().sum()
MPG 0
Cylinders 0
Displacement 0
Horsepower 6
Weight 0
Acceleration 0
Model Year 0
Origin 0
为了保证这个初始示例的简单性,删除这些行。
dataset = dataset.dropna()
2. 对类别型属性进行one-hot编码
"Origin"
列实际上代表分类,而不仅仅是一个数字。所以把它转换为独热码 (one-hot)
origin = dataset.pop('Origin')
dataset['USA'] = (origin == 1)*1.0
dataset['Europe'] = (origin == 2)*1.0
dataset['Japan'] = (origin == 3)*1.0
dataset.tail()
将数据集拆分为训练数据集和测试数据集。最后将使用测试数据集对模型进行评估。
# pandas.DataFrame.sample 随机选取若干行
# frac:抽取行的比例,例如frac=0.8,就是抽取其中80%。
train_dataset = dataset.sample(frac=0.8,random_state=0)
# 删除DataFrame的行:
test_dataset = dataset.drop(train_dataset.index)
快速查看训练集中几对列的联合分布。
sns.pairplot(train_dataset[["MPG", "Cylinders", "Displacement", "Weight"]], diag_kind="kde")
查看总体的数据统计:
train_stats = train_dataset.describe()
train_stats.pop("MPG")
train_stats = train_stats.transpose()
train_stats
count | mean | std | min | 25% | 50% | 75% | max | |
---|---|---|---|---|---|---|---|---|
Cylinders | 314.0 | 5.477707 | 1.699788 | 3.0 | 4.00 | 4.0 | 8.00 | 8.0 |
Displacement | 314.0 | 195.318471 | 104.331589 | 68.0 | 105.50 | 151.0 | 265.75 | 455.0 |
Horsepower | 314.0 | 104.869427 | 38.096214 | 46.0 | 76.25 | 94.5 | 128.00 | 225.0 |
Weight | 314.0 | 2990.251592 | 843.898596 | 1649.0 | 2256.50 | 2822.5 | 3608.00 | 5140.0 |
Acceleration | 314.0 | 15.559236 | 2.789230 | 8.0 | 13.80 | 15.5 | 17.20 | 24.8 |
Model Year | 314.0 | 75.898089 | 3.675642 | 70.0 | 73.00 | 76.0 | 79.00 | 82.0 |
USA | 314.0 | 0.624204 | 0.485101 | 0.0 | 0.00 | 1.0 | 1.00 | 1.0 |
Europe | 314.0 | 0.178344 | 0.383413 | 0.0 | 0.00 | 0.0 | 0.00 | 1.0 |
Japan | 314.0 | 0.197452 | 0.398712 | 0.0 | 0.00 | 0.0 | 0.00 | 1.0 |
将特征值从目标值或者"标签"中分离。 这个标签是你使用训练模型进行预测的值。
train_labels = train_dataset.pop('MPG')
test_labels = test_dataset.pop('MPG')
再次上面的 train_stats
部分,并注意每个特征的范围有什么不同。
使用不同的尺度和范围对特征归一化是好的实践。尽管模型可能 在没有特征归一化的情况下收敛,它会使得模型训练更加复杂,并会造成生成的模型依赖输入所使用的单位选择。
注意:尽管我们仅仅从训练集中有意生成这些统计数据,但是这些统计信息也会用于归一化的测试数据集。我们需要这样做,将测试数据集放入到与已经训练过的模型相同的分布中。
def norm(x):
return (x - train_stats['mean']) / train_stats['std']
normed_train_data = norm(train_dataset)
normed_test_data = norm(test_dataset)
我们使用一个“顺序”模型,其中包含两个紧密相连的隐藏层,以及返回单个连续值得输出层。模型的构建步骤包含于一个名叫 'build_model' 的函数中。
def build_model():
model = keras.Sequential([
layers.Dense(64, activation='relu', input_shape=[len(train_dataset.keys())]),
layers.Dense(64, activation='relu'),
layers.Dense(1)
])
optimizer = tf.keras.optimizers.RMSprop(0.001)
model.compile(loss='mse',
optimizer=optimizer,
metrics=['mae', 'mse'])
return model
model = build_model()
使用 .summary
方法来打印该模型的简单描述。
model.summary()
现在试用下这个模型。从训练数据中批量获取‘10’条例子并对这些例子调用 model.predict
。
example_batch = normed_train_data[:10]
example_result = model.predict(example_batch)
example_result
对模型进行1000个周期的训练,并在 history
对象中记录训练和验证的准确性。
# 通过为每个完成的时期打印一个点来显示训练进度
class PrintDot(keras.callbacks.Callback):
def on_epoch_end(self, epoch, logs):
if epoch % 100 == 0: print('')
print('.', end='')
EPOCHS = 1000
history = model.fit(
normed_train_data, train_labels,
epochs=EPOCHS, validation_split = 0.2, verbose=0,
callbacks=[PrintDot()])
使用 history
对象中存储的统计信息可视化模型的训练进度。
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
hist.tail()
loss | mae | mse | val_loss | val_mae | val_mse | epoch | |
---|---|---|---|---|---|---|---|
995 | 2.377308 | 0.972564 | 2.377308 | 9.812141 | 2.277914 | 9.812140 | 995 |
996 | 2.089346 | 0.881897 | 2.089346 | 11.054218 | 2.500603 | 11.054217 | 996 |
997 | 2.168902 | 0.915549 | 2.168902 | 9.853918 | 2.314816 | 9.853918 | 997 |
998 | 2.340033 | 0.944049 | 2.340033 | 10.559951 | 2.421101 | 10.559950 | 998 |
999 | 2.254203 | 0.926744 | 2.254203 | 10.530814 | 2.451076 | 10.530814 | 999 |
def plot_history(history):
hist = pd.DataFrame(history.history)
hist['epoch'] = history.epoch
plt.figure()
plt.xlabel('Epoch')
plt.ylabel('Mean Abs Error [MPG]')
plt.plot(hist['epoch'], hist['mae'],
label='Train Error')
plt.plot(hist['epoch'], hist['val_mae'],
label = 'Val Error')
plt.ylim([0,5])
plt.legend()
plt.figure()
plt.xlabel('Epoch')
plt.ylabel('Mean Square Error [$MPG^2$]')
plt.plot(hist['epoch'], hist['mse'],
label='Train Error')
plt.plot(hist['epoch'], hist['val_mse'],
label = 'Val Error')
plt.ylim([0,20])
plt.legend()
plt.show()
plot_history(history)
该图表显示在约100个 epochs 之后误差非但没有改进,反而出现恶化。 让我们更新 model.fit
调用,当验证值没有提高上是自动停止训练。 我们将使用一个 EarlyStopping callback 来测试每个 epoch 的训练条件。如果经过一定数量的 epochs 后没有改进,则自动停止训练。
model = build_model()
# patience 值用来检查改进 epochs 的数量
early_stop = keras.callbacks.EarlyStopping(monitor='val_loss', patience=10)
history = model.fit(normed_train_data, train_labels, epochs=EPOCHS,
validation_split = 0.2, verbose=0, callbacks=[early_stop, PrintDot()])
plot_history(history)
loss, mae, mse = model.evaluate(normed_test_data, test_labels, verbose=2)
print("Testing set Mean Abs Error: {:5.2f} MPG".format(mae))
绘制图像
test_predictions = model.predict(normed_test_data).flatten()
plt.scatter(test_labels, test_predictions)
plt.xlabel('True Values [MPG]')
plt.ylabel('Predictions [MPG]')
plt.axis('equal')
plt.axis('square')
plt.xlim([0,plt.xlim()[1]])
plt.ylim([0,plt.ylim()[1]])
_ = plt.plot([-100, 100], [-100, 100])
这看起来我们的模型预测得相当好。我们来看下误差分布。
error = test_predictions - test_labels
plt.hist(error, bins = 25)
plt.xlabel("Prediction Error [MPG]")
_ = plt.ylabel("Count")