使用Numpy构建神经网络
本节将使用Python语言和Numpy库来构建神经网络模型,向读者展示神经网络的基本概念和工作过程。
构建神经网络/深度学习模型的基本步骤
如之前的介绍,应用于不同场景的深度学习模型具备一定的通用性,均可以从下述五个步骤来完成模型的构建和训练。
- 数据处理:从本地文件或网络地址读取数据,并做预处理操作,如校验数据的正确性等。
- 模型设计:完成网络结构的设计(模型要素1),相当于模型的假设空间,即模型能够表达的关系集合。
- 训练配置:设定模型采用的寻解算法(模型要素2),即优化器,并指定计算资源。
- 训练过程:循环调用训练过程,每轮均包括前向计算 、损失函数(优化目标,模型要素3)和后向传播这三个步骤。
- 保存模型:将训练好的模型保存,以备预测时调用。
下面使用Python编写预测波士顿房价的模型,一样遵循这样的五个步骤。 正是由于这个建模和训练的过程存在通用性,即不同的模型仅仅在模型三要素上不同,而五个步骤中的其它部分保持一致,深度学习框架才有用武之地。
波士顿房价预测
波士顿房价预测是一个经典的机器学习问题,类似于程序员世界的“Hello World”。波士顿地区的房价是由诸多因素影响的,该数据集统计了13种可能影响房价的因素和该类型房屋的均价,期望构建一个基于13个因素预测房价的模型。预测问题根据预测输出的类型是连续的实数值,还是离散的标签,区分为回归任务和分类任务。因为房价是一个连续值,所以房价预测显然是一个回归任务。下面我们尝试用最简单的线性回归模型解决这个问题,并用神经网络来实现这个模型。
线性回归模型
假设房价和各影响因素之间能够用线性关系来描述(类似牛顿第二定律的案例):
y=∑j=1Mxjwj+by = {\sum_{j=1}^Mx_j w_j} + b y=j=1∑Mxjwj+b
模型的求解即是通过数据拟合出每个wjw_jwj和bbb。wjw_jwj和bbb分别表示该线性模型的权重和偏置。一维情况下,wjw_jwj和bbb就是直线的斜率和截距。
# 导入需要用到的package
import numpy as np
import json
# 读入训练数据
datafile = './work/housing.data'
data = np.fromfile(datafile, sep=' ')
data
- 1
- 2
- 3
- 4
- 5
- 6
- 7
./work/housing.data
array([6.320e-03, 1.800e+01, 2.310e+00, ..., 3.969e+02, 7.880e+00, 1.190e+01])
因为读入的原始数据是1维的,所有数据都连在了一起。所以将数据的形状进行变换,形成一个2维的矩阵。每行为一个数据样本(14个值),每个数据样本包含13个X(影响房价的特征)和一个Y(该类型房屋的均价)。
# 读入之后的数据被转化成1维array,其中array的
# 第0-13项是第一条数据,第14-27项是第二条数据,....
# 这里对原始数据做reshape,变成N x 14的形式
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(len(feature_names))
print(data.shape[0])
print(data.shape[0] // feature_num)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
14 506 36
# 查看数据
x = data[0]
print(x.shape)
print(x)
- 1
- 2
- 3
- 4
(14,) [6.320e-03 1.800e+01 2.310e+00 0.000e+00 5.380e-01 6.575e+00 6.520e+01 4.090e+00 1.000e+00 2.960e+02 1.530e+01 3.969e+02 4.980e+00 2.400e+01]
取80%的数据作为训练集,预留20%的数据用于测试模型的预测效果(训练好的模型预测值与实际房价的差距)。打印训练集的形状可见,我们共有404个样本,每个样本含有13个特征和1个预测值。
ratio = 0.8
offset = int(data.shape[0] * ratio)
training_data = data[:offset]
training_data.shape
- 1
- 2
- 3
- 4
(404, 14)
对每个特征进行归一化处理,使得每个特征的取值缩放到0~1之间。这样做有两个好处:
- 模型训练更高效。
- 特征前的权重大小可代表该变量对预测结果的贡献度(因为每个特征值本身的范围相同)。
# 计算train数据集的最大值,最小值,平均值
maximums, minimums, avgs = \
training_data.max(axis=0), \
training_data.min(axis=0), \
training_data.sum(axis=0) / training_data.shape[0]
print(maximums)
print(minimums)
#print(avgs)
# 对数据进行归一化处理
for i in range(feature_num):
#print(maximums[i], minimums[i], avgs[i])
data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
[0.97853679 0.85767327 0.64103506 0.91336634 0.69808245 0.4688429 0.36634938 0.72313892 0.74827809 0.65363071 0.42274068 0.0519112 0.73441086 0.57387239] [-0.02146321 -0.14232673 -0.35896494 -0.08663366 -0.30191755 -0.5311571 -0.63365062 -0.27686108 -0.25172191 -0.34636929 -0.57725932 -0.9480888 -0.26558914 -0.42612761] [-1.23663456e-18 5.45493244e-17 -1.09923072e-18 -5.13890360e-17 1.67632684e-17 -4.12211519e-19 -3.02288447e-18 -3.09158639e-18 -3.02288447e-17 2.19846143e-18 -2.74807679e-18 1.68319704e-18 -3.29769215e-18 4.25951903e-18]
将上述几个数据处理操作合并成load data函数,并确认函数的执行效果。
def load_data():
# 从文件导入数据
datafile = './work/housing.data'
data = np.fromfile(datafile, sep=' ')
<span class="hljs-comment"># 每条数据包括14项,其中前面13项是影响因素,第14项是相应的房屋价格中位数</span>
feature_names = [ <span class="hljs-string">'CRIM'</span>, <span class="hljs-string">'ZN'</span>, <span class="hljs-string">'INDUS'</span>, <span class="hljs-string">'CHAS'</span>, <span class="hljs-string">'NOX'</span>, <span class="hljs-string">'RM'</span>, <span class="hljs-string">'AGE'</span>, \
<span class="hljs-string">'DIS'</span>, <span class="hljs-string">'RAD'</span>, <span class="hljs-string">'TAX'</span>, <span class="hljs-string">'PTRATIO'</span>, <span class="hljs-string">'B'</span>, <span class="hljs-string">'LSTAT'</span>, <span class="hljs-string">'MEDV'</span> ]
feature_num = len(feature_names)
<span class="hljs-comment"># 将原始数据进行Reshape,变成[N, 14]这样的形状</span>
data = data.reshape([data.shape[<span class="hljs-number">0</span>] // feature_num, feature_num])
<span class="hljs-comment"># 将原数据集拆分成训练集和测试集</span>
<span class="hljs-comment"># 这里使用80%的数据做训练,20%的数据做测试</span>
<span class="hljs-comment"># 测试集和训练集必须是没有交集的</span>
ratio = <span class="hljs-number">0.8</span>
offset = int(data.shape[<span class="hljs-number">0</span>] * ratio)
training_data = data[:offset]
<span class="hljs-comment"># 计算train数据集的最大值,最小值,平均值</span>
maximums, minimums, avgs = training_data.max(axis=<span class="hljs-number">0</span>), training_data.min(axis=<span class="hljs-number">0</span>), \
training_data.sum(axis=<span class="hljs-number">0</span>) / training_data.shape[<span class="hljs-number">0</span>]
<span class="hljs-comment"># 对数据进行归一化处理</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(feature_num):
<span class="hljs-comment">#print(maximums[i], minimums[i], avgs[i])</span>
data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])
<span class="hljs-comment"># 训练集和测试集的划分比例</span>
training_data = data[:offset]
test_data = data[offset:]
<span class="hljs-keyword">return</span> training_data, test_data</code></pre></div></div></div></div><div><div class="cc cc-container"><div class="cc-aside"><div class="cc-in">In[7]</div></div><div class="cc-main"><div class="cc-output"><pre><code class="hljs"><span class="hljs-comment"># 获取数据</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5