05.6 标准化标签值
5.6 对标签值标准化⚓︎
5.6.1 发现问题⚓︎
这一节里我们重点解决在训练过程中的数值的数量级的问题。
我们既然已经对样本数据特征值做了标准化,那么如此大数值的损失函数值是怎么来的呢?看一看损失函数定义:
其中,z_i 是预测值,y_i 是标签值。初始状态时,W 和 B 都是 0,所以,经过前向计算函数 Z=X \cdot W+B 的结果是 0,但是 Y 值很大,处于 [181.38, 674.37] 之间,再经过平方计算后,一下子就成为至少5位数的数值了。
再看反向传播时的过程:
def __backwardBatch(self, batch_x, batch_y, batch_z):
m = batch_x.shape[0]
dZ = batch_z - batch_y
dB = dZ.sum(axis=0, keepdims=True)/m
dW = np.dot(batch_x.T, dZ)/m
return dW, dB
dZ
,与房价是同一数量级的,这样经过反向传播后,dW
和dB
的值也会很大,导致整个反向传播链的数值都很大。我们可以debug一下,得到第一反向传播时的数值是:
dW
array([[-142.59982906],
[-283.62409678]])
dB
array([[-443.04543906]])
这么大的数值,需要把学习率设置得很小,比如0.001
,才可以落到 [0,1] 区间,但是损失函数值还是不能变得很小。
如果我们像对特征值做标准化一样,把标签值也标准化到 [0,1] 之间,是不是有帮助呢?
5.6.2 代码实现⚓︎
参照X的标准化方法,对Y的标准化公式如下:
在SimpleDataReader
类中增加新方法如下:
class SimpleDataReader(object):
def NormalizeY(self):
self.Y_norm = np.zeros((1,2))
max_value = np.max(self.YRaw)
min_value = np.min(self.YRaw)
# min value
self.Y_norm[0, 0] = min_value
# range value
self.Y_norm[0, 1] = max_value - min_value
y_new = (self.YRaw - min_value) / self.Y_norm[0, 1]
self.YTrain = y_new
原始数据中,Y 的数值范围是:
- 最大值:674.37
- 最小值:181.38
- 平均值:420.64
标准化后,Y 的数值范围是: - 最大值:1.0 - 最小值:0.0 - 平均值:0.485
注意,我们同样记住了Y_norm
的值便于以后使用。
修改主程序代码,增加对 Y 标准化的方法调用NormalizeY()
:
# main
if __name__ == '__main__':
# data
reader = SimpleDataReader()
reader.ReadData()
reader.NormalizeX()
reader.NormalizeY()
......
5.6.3 运行结果⚓︎
运行上述代码得到的结果其实并不令人满意:
......
199 99 0.0015663978030319194
[[-0.08194777] [ 0.80973365]] [[0.12714971]]
W= [[-0.08194777]
[ 0.80973365]]
B= [[0.12714971]]
z= [[0.61707273]]
虽然W和B的值都已经处于 [-1,1] 之间了,但是 z 的值也在 [0,1] 之间,一套房子不可能卖0.61万元!
聪明的读者可能会想到:既然对标签值做了标准化,那么我们在得到预测结果后,需要对这个结果应该做反标准化。
根据公式2,反标准化的公式应该是:
代码如下:
if __name__ == '__main__':
# data
reader = SimpleDataReader()
reader.ReadData()
reader.NormalizeX()
reader.NormalizeY()
# net
params = HyperParameters(eta=0.01, max_epoch=200, batch_size=10, eps=1e-5)
net = NeuralNet(params, 2, 1)
net.train(reader, checkpoint=0.1)
# inference
x1 = 15
x2 = 93
x = np.array([x1,x2]).reshape(1,2)
x_new = reader.NormalizePredicateData(x)
z = net.inference(x_new)
print("z=", z)
Z_real = z * reader.Y_norm[0,1] + reader.Y_norm[0,0]
print("Z_real=", Z_real)
倒数第二行代码,就是公式3。运行结果如下:
W= [[-0.08149004]
[ 0.81022449]]
B= [[0.12801985]]
z= [[0.61856996]]
Z_real= [[486.33591769]]
看Z_real
的值,完全满足要求!
总结一下从本章中学到的正确的方法:
- X 必须标准化,否则无法训练;
- Y 值不在 [0,1] 之间时,要做标准化,好处是迭代次数少;
- 如果 Y 做了标准化,对得出来的预测结果做关于 Y 的反标准化
至此,我们完美地解决了北京通州地区的房价预测问题!
5.6.4 总结⚓︎
归纳总结一下前面遇到的困难及解决办法:
- 样本不做标准化的话,网络发散,训练无法进行;
- 训练样本标准化后,网络训练可以得到结果,但是预测结果有问题;
- 还原参数值后,预测结果正确,但是此还原方法并不能普遍适用;
- 标准化测试样本,而不需要还原参数值,可以保证普遍适用;
- 标准化标签值,可以使得网络训练收敛快,但是在预测时需要把结果反标准化,以便得到真实值。
代码位置⚓︎
ch05, Level6