波士顿房价预测
目标
这是一个经典的机器学习回归场景,我们利用Python和numpy来实现神经网络。该数据集统计了房价受到13个特征因素的影响,如图1所示。
对于预测问题,可以根据预测输出的类型是连续的实数值,还是离散值,区分是回归还是分类问题。**因为房价是一个连续值,这是一个回归任务。**下面利用简单的线性回归来解决这个问题,并利用神经网络来实现这个模型。
线性回归模型
假设房价和各个影像因素之间的函数关系是:
模型的目标就是通过拟合数据来求出和两个参数。线性回归模型采用均方误差MSE损失函数(),用以衡量预测房价和真实值的差异,公式:
思考:为什么要以均方误差为损失函数?考虑到便于求解。
线性回归的神经网络结构
神经网络结构就是一个个神经元加层来组成。线性回归认为是神经网络模型的一种简单特例,是一个只有加权求和,没有非线性变换的神经元,如图2:。
数据探查
import numpy as np
import json
# 读入训练数据
datafile = './work/housing.data'
data = np.fromfile(datafile, sep=' ')
- 数据变形
feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE','DIS',
'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
feature_num = len(feature_names)
data = data.reshape([data.shape[0] // feature_num, feature_num])
print(data.shape) # 将数据做了转化样本总数是506个样本
- 划分数据集
将数据集划分训练集和测试集,训练集用于确定模型的参数,测试集用于评估模型的效果。图5:
# 数据集划分操作
ratio = 0.8
offset = int(data.shape[0] * ratio)
training_data = data[:offset]
training_data.shape
# print(data.shape)
- 数据集归一化
利用最大值最小值归一方法,使得每个特征的值都是被缩放到[0,1]之间,这样做的好处有:1、模型训练更加高效,特征前的权重大小可以代表该变量对预测结果的贡献度。
maximums, minimums = \
training_data.max(axis=0), \
training_data.min(axis=0),
# 对数据进行归一化处理
for i in range(feature_num):
data[:, i] = (data[:, i] - minimums[i]) / (maximums[i] - minimums[i])
- 将上述过程封装为函数
def load_data():
# 从文件导入数据
datafile = './work/housing.data'
data = np.fromfile(datafile, sep=' ')
# 每条数据包括14项,其中前面13项是影响因素,第14项是相应的房屋价格中位数
feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', \
'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
feature_num = len(feature_names)
# 将原始数据进行Reshape,变成[N, 14]这样的形状
data = data.reshape([data.shape[0] // feature_num, feature_num])
# 将原数据集拆分成训练集和测试集
# 这里使用80%的数据做训练,20%的数据做测试
# 测试集和训练集必须是没有交集的
ratio = 0.8
offset = int(data.shape[0] * ratio)
training_data = data[:offset]
# 计算训练集的最大值,最小值
maximums, minimums = training_data.max(axis=0), \
training_data.min(axis=0)
# 对数据进行归一化处理
for i in range(feature_num):
data[:, i] = (data[:, i] - minimums[i]) / (maximums[i] - minimums[i])
# 训练集和测试集的划分比例
training_data = data[:offset]
test_data = data[offset:]
return training_data, test_data
获取数据
# 获取数据
training_data, test_data = load_data()
x = training_data[:, :-1]
y = training_data[:, -1:] # 实际值
模型设计
一层神经网络设计
模型的设计是深度学习模型关键要素之一,称为网络结构。相当于模型的假设空间,既是实现模型的“前向计算”(从输入到输出)过程。
如果将输入和输出都用向量表示,输入特征x有13个向量,y有1个向量。那么权重参数就是13*1。
w = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, -0.1, -0.2, -0.3, -0.4, 0.0] #第一层的权重参数
w_t = np.array(w).reshape([13, 1]) # 相当于矩阵的转置
w_t.shape
# 一个特征列就是一个向量
假设第一个样本x[0]和一层神经网络进行运算。运算逻辑。其中,x [0]的shape是(1,13),的维度是(13,1),刚好满足两个矩阵乘法的运算原则。
x1=x[0] #获取第一个样本
print(x1.shape)
t = np.dot(x1, w_t)
print(t.shape)
# 我们发现样本的特征向量与参数向量相乘的结果是scaler。
# 矩阵的乘法(1,13)(13,1) A*B其中A的列要和B的行相等才可进行矩阵的乘法运算。
b = -0.2
z = t + b
print(z)
完整的线性回归计算公式是:,b是初始化偏移量,以上从特征[0]到计算输出值的过程就是“前向计算”。
定义一个类方便后面调用:
class MyNetwork(object):
def __init__(self, num_of_weights):
np.random.seed(0)
self.w = np.random.randn(num_of_weights,1)
self.b = 0.
def forward_m(self,x):
# 表示的是前向计算
res = np.dot(x,self.w) +self.b
return res
二层神经网络
定义两层的神经网络,其中需要注意的地方:输入数据是直接和第一层进行运算,第一层每个神经元的输出则是做为第二层每个神经元的输入x。那么在第二层神经运算是,就是对应位置元素相乘,在与第二层的权值参数进行运算。
# 定义两层神经网络输出
class My2Network(object):
def __init__(self, num_of_weights):
np.random.seed(0)
# 两层神经网络共享权值w
self.w = np.random.randn(num_of_weights,1)
self.ww = np.random.randn(num_of_weights,1)
self.b = 0.
def forword(self,x):
# 前向计算有两层神经网络
z_1 = np.dot(x,self.w)
# 计算第一层输出做为第二层的输入
out_1 = self.w.reshape([1,13]) * x
z_2 = np.dot(out_1,self.ww)
return z_1 + z_2 + self.b
训练配置
模型设计完成后,需要通过训练配置寻找模型的最优值,即通过损失函数来衡量模型的好坏。训练配置也是深度学习模型关键要素之一。
通过模型计算表示的影响因素所对应的房价应该是z, 但实际数据告诉我们房价是y。这时我们需要有某种指标来衡量预测值z跟真实值y之间的差距。对于回归问题,最常采用的衡量方法是使用均方误差作为评价模型好坏的指标,公式为:
Loss就是损失函数,衡量模型好坏的指标。在回归问题中一般是均方误差作为损失函数,而在分类问题中采用交叉熵作为损失函数。
样本的损失:
loss = (y_1 - res_1)*(y_1-res_1)
print(loss)
因为在计算损失函数需要把每个样本的损失函数得到,求和在平均。
在前Network类中增加loss函数。
class Network(object):
def __init__(self, num_of_weights):
# 随机产生w的初始值
# 为了保持程序每次运行结果的一致性,此处设置固定的随机数种子
np.random.seed(0)
self.w = np.random.randn(num_of_weights, 1)
self.b = 0.
def forward(self, x):
z = np.dot(x, self.w) + self.b
return z
def loss(self, z, y):
error = z - y
cost = error * error
cost = np.mean(cost)
return cost
训练过程
在模型的前面两部完成,接下来就是求解参数的数值,这就是模型训练的过程。求解w和b使得损失函数取得极小值。
下面给出一个微积分的案例:一个曲线在某点的导数。导数等于在该点处的切线的斜率。
- 梯度下降算法
在现实中存在大量函数正向求解容易,但是反向求解不容易,被称为单向函数。神经网络的损失函数就是单向函数。
这种情况我们可以在现实生活中类比一个想从山峰走到山谷的盲人。他看不见山谷在哪儿(无法逆向求解损失函数为0时的参数值),但是可以伸出脚探索身边的坡度(当前点的导数,梯度)。所以求解Loss的最小值过程就是:在从当前参数取值,一步步按照梯度的反方向下降。直到到达最低点。
下面。我们随机从损失函数中去参数看看他们的变化情况。
我们将中除去之前的参数和b全部固定下来。可以用图画出的形式。
net = Network(13)
losses = []
#只画出参数w5和w9在区间[-160, 160]的曲线部分,以及包含损失函数的极值
w5 = np.arange(-160.0, 160.0, 1.0)
w9 = np.arange(-160.0, 160.0, 1.0)
losses = np.zeros([len(w5), len(w9)])
#计算设定区域内每个参数取值所对应的Loss
for i in range(len(w5)):
for j in range(len(w9)):
net.w[5] = w5[i]
net.w[9] = w9[j]
z = net.forward(x)
loss = net.loss(z, y)
losses[i, j] = loss
#使用matplotlib将两个变量和对应的Loss作3D图
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = Axes3D(fig)
w5, w9 = np.meshgrid(w5, w9)
ax.plot_surface(w5, w9, losses, rstride=1, cstride=1, cmap='rainbow')
plt.show()
- 曲线在最低点出是可导的。
- 越接近最低点,曲线的坡度逐渐放缓,有助于通过当前的梯度来判断接近最低点的程度(是否逐渐减少步长,以免错过最佳点)。
然而绝对值误差事不具备的。这也是损失函数的设计不仅仅要考虑“合理性”,还要追求“易解性”的原因。
后面的内容请查看下一篇博客:
文章出处登录后可见!