与分类问题不同的是,回归问题的预测值是连续值,而分类问题的预测值的离散值。
1 认识数据
2 数据准备
2.1 导入数据
from tensorflow.keras.datasets import boston_housing
(train_data,train_targets),(test_data,test_targets) = boston_housing.load_data()#回归问题目标值记为target,分类问题记为label
与之前两个分类任务这个问题的数据不同的是:
1、数据的样本量明显的减少。数据导入的默认的测试集的默认划分比例是0.2,一共有404个训练样本,102个测试样本。这样小的数据集意味着在验证时需要使用K折交叉验证的方法去获取可信的评估值。
2、数据的特征列中有离散的,有连续的。并且数据的取值范围有的在0-1,有的在0-100。目标值房价也是有区间的,有最大值最小值。这意味着在数据准备时要对数据进行合理的变换。
2.2 数据变换
由于数据的取值范围差异较大,并且神经网络模型对差异较大的数据的学习能力有局限性,为了使得模型能够更好的学习到相关的信息,要对数据进行归一化处理(包括训练集和测试集的数据)。值得注意的是,测试集的数据因为要进行模型评估和推断,用的是训练集训练出的模型,但是其归一化所用到的均值和方差要是训练集数据的均值和方差!在深度学习工作流程中,你不能使用在测试数据上计算得到的任何结果,即使是像数据标准化这么简单的操作也不行!
一般来讲,数据变化既要注意形状(要是张量),又要注意数值(要差异不是特别大)。这里,我的输入的特征和标签的形状已经是张量了,我就只需要对数值进行标准化即可。
在 NumPy 中,
axis
参数用于指定操作的维度。具体来说,axis=0
和axis=1
的含义如下:
axis=0
: 表示沿着第一个轴(行的方向)进行操作,把行保留,求列的均值。在二维数组中,这意味着对每一列进行操作。例如,当你计算train_data.mean(axis=0)
时,NumPy 会计算每一列的均值,返回一个包含每列均值的一维数组。
axis=1
: 表示沿着第二个轴(列的方向)进行操作,把列保留,求行的均值。在二维数组中,这意味着对每一行进行操作。例如,train_data.mean(axis=1)
会计算每一行的均值,返回一个包含每行均值的一维数组。
#数据标准化,包括训练集和测试集,不包括标签(预测值)
#先计算均值和方差
mean = train_data.mean(axis=0)
std = train_data.std(axis=0)
#对训练集、测试集进行标准化,减去均值除以标准差
train_data -= mean
train_data /= std
test_data -= mean
test_data /= std
3 构建模型
3.1 层与模型结构
要根据处理问题的具体情况来选择层的设定(神经元的数量和激活函数)与模型结构(层的数量、以什么样的结构进行堆叠)。
对于本例,样本量少是一个主要特征。在构建模型时,要使用较小的模型。一般来说,训练数据越少,过拟合就会越严重,而较小的模型可以降低过拟合。
对于标量回归问题,最后一层的输出维度定为1,并且不设置激活函数。这样可以将输入映射到任意的值上,实现连续值的回归。
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(64,activation='relu'),
layers.Dense(64,activation='relu'),
layers.Dense(1)
])
3.2 配置模型
包括损失函数、优化器、监测指标。
损失函数:由于是回归,所以采用MSE(mean square error)。这是Loss!是衡量训练时模型要去向这个方向减少。求法是预测值和真实值的差的平方,对于一轮一批训练的多个样本,求均值就是MSA。
优化器:没有特殊情况就选rmsprop
监测指标:现在只选择MAE(mean absolute error)。是预测值和真实值之差的绝对值。回归问题经常使用MSE和MAE作为Loss和监测指标,用于让人了解训练情况。
我大胆猜测一下,使用mae做为监测指标而不是mse,可能是因为mae更让人容易理解一点。
model.compile(
optimizer="rmsprop",
loss="mse",
metrics=['mae']
)
3.3 包装
这个部分主要是为了在K折交叉验证部分起作用。在K折交叉验证部分会反复调用模型的构建。在读这一部分时可以先阅读后卫 的K折交叉验证的部分。
这个包装的部分就是将模型构建和模型编译封装成一个构建模型的函数。
def build_model():
model = keras.Sequential([
layers.Dense(64,activation='relu'),
layers.Dense(64,activation='relu'),
layers.Dense(1)
])
model.compile(
optimizer="rmsprop",
loss="mse",
metrics=['mae']
)
return model
4 训练模型以调整参数
4.1 选择验证方法
当样本的数量还可以的时候,我们会设置验证集来得出验证集的指标,从而调整模型训练时的参数。如果数据分布的标签是随机的,那么就可以随机取其中的一部分当作验证集,剩下的用于模型训练。也就是说,训练集的取出任意部分都可以当作验证集。
但是,如果数据的样本量非常少,验证集的样本数量也会非常少,因此,拿一个很小的验证集去测评模型的训练情况波动会很大,因为数量小的验证集里面数据分布会随着更换验证集而出现较大的变动。变动的大小取决于整体数据的差异和能取到的验证集的样本大小。
对于本例,是一个回归问题。而且,训练样本就只有404个。显然在训练样本中无论怎么取,数量总是少的。
验证方法有两种:直接人为单一划分验证集、K折交叉验证,后者就是专门处理训练集样本数量导致验证集不能很全面的反应训练情况,而构建的一种专门的方法。
4.2 正式训练
k = 4 #设置分区数,分区数也等于折数
num_val_samples = len(train_data) // k #设置每一个分区的样本数量是多少
num_epochs = 500 #假设之前构建了神经网络用于回归,对于每个折都要进行模型的训练
all_mae_histories = []#设置一个空列表用于存储所有的metrics值(metric值取的是mae)
for i in range(k):#每一折都要训练,对于每一折结束,都要保存这一折的metric,从而用于平均。
print(f'Processing fold #{i}')
#先定好val的位置,然后剩下的数据使用concatenate方法拼在一起
val_data = train_data[i * num_val_samples : (i+1) * num_val_samples]#获取验证集的索引
val_targets = train_targets[i * num_val_samples : (i+1) * num_val_samples]#获取验证集的索引
partial_train_data = np.concatenate([train_data[:i * num_val_samples],train_data[(i+1) * num_val_samples:]],axis=0)#传入一个列表,里面的参数是想要进行合并的数据,axis=0表示行不变
partial_train_targets = np.concatenate([train_targets[:i * num_val_samples],train_targets[(i+1) * num_val_samples:]],axis=0)
model = build_model()#每折训练都是一个全新的模型进行训练
history = model.fit(
partial_train_data,
partial_train_targets,
validation_data=(val_data,val_targets),#这里相当于val_mse,val_mae = model.evaluate(val_data,val_targets,verbose=0)
epochs=num_epochs,
batch_size=16,
verbose=0
)
mae_hitory = history.history['val_mae']
all_mae_histories.append(mae_hitory)
average_mae_history = [
np.mean([x[i] for x in all_mae_histories]) for i in range (num_epochs)
]
#all_mae_histories是一个二维列表。
#[x[i] for x in all_mae_histories]:这是一个列表推导式,用于提取所有模型在第 i 个 epoch 的 MAE 值。
5 重新训练
5.1 观察模拟训练结果
import matplotlib.pyplot as plt
plt.plot(range(1,len(average_mae_history) + 1), average_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()
5.2 确定最佳epochs重新训练
从图中可以看出,MAE在120-140轮后不在显著增加,表示这之后就过拟合了,故我们的最佳的epoch设置在130。
注意!!!以上确定最佳epoch用的数据是划分了验证集的训练集,也可以说验证集的存在是为了得到最佳的epoch,K折交叉验证是训练得到最佳的epoch过程中的一个方法。当确定了最佳的epoch后,再次训练模型就是用的没有验证集的全部训练集。
model=build_model()
model.fit(
train_data,#这里是全部的训练集
train_targets,
epochs=130,
batch_size=16,
verbose=0
)
5.3 评估模型
test_mse_score,test_mae_score = model.evaluate(test_data,test_targets)
6 推断
prediction = model.predict(test_data)
prediction[0]