构建神经网络/深度学习模型的基本步骤
深度学习模型具有一定的通用性,使得深度学习的门槛降低,这是深度学习得以重新占据计算机领域一席之地的重要原因,深度学习均可以从下述五个步骤来完成模型的构建和训练。
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.8offset = int(data.shape[0] * ratio)training_data = data[:offset]# 计算train数据集的最大值,最小值,平均值maximums, minimums, avgs = training_data.max(axis=0), training_data.min(axis=0), \training_data.sum(axis=0) / training_data.shape[0]# 对数据进行归一化处理for i in range(feature_num):#print(maximums[i], minimums[i], avgs[i])data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])# 训练集和测试集的划分比例training_data = data[:offset]test_data = data[offset:]return training_data, test_data
构建神经网络
基本概念
首先我们要清楚神经网络负责什么,我们起初假设了波士顿的房价和各个因素成线性关系,神经网络根据数据训练出一组最适合,最能拟合数据的一组参数,这就是神经网络需要做的事情,输入是数据,输出的一组最优参数。
首先,必须先了解以下几个概念:
一般过程
我们使用神经网络进行训练的过程可以用下面的伪代码进行表述:
iteration=N; # 迭代的次数
init(w,b); # 为参数赋初值
while(i<iteration)z = forward(w,b,data); # 计算这一组参数对数据的预测值Loss = cul_loss(z,y); # 计算预测值和真实值之间的损失gradient = cul_gradient(); # 房价预测模型很简单,我们用梯度下降法更新参数,计算梯度,梯度的计算过程如上。w,b = update(w, b, gradient); # 根据梯度更新参数
完整代码
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3Ddef load_data(): # 从文件导入数据datafile = './data/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.8offset = int(data.shape[0] * ratio)training_data = data[:offset]# 计算train数据集的最大值,最小值,平均值maximums, minimums, avgs = training_data.max(axis=0), training_data.min(axis=0), \training_data.sum(axis=0) / training_data.shape[0]# 对数据进行归一化处理for i in range(feature_num):#print(maximums[i], minimums[i], avgs[i])data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])# 训练集和测试集的划分比例training_data = data[:offset]test_data = data[offset:]return training_data, test_dataclass 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.breturn zdef loss(self, z, y):error = z - ynum_samples = error.shape[0]cost = error * errorcost = np.sum(cost) / num_samplesreturn costdef gradient(self, x, y):z = self.forward(x)N = x.shape[0]gradient_w = 1. / N * np.sum((z-y) * x, axis=0)gradient_w = gradient_w[:, np.newaxis]gradient_b = 1. / N * np.sum(z-y)return gradient_w, gradient_bdef update(self, gradient_w, gradient_b, eta = 0.01):self.w = self.w - eta * gradient_wself.b = self.b - eta * gradient_bdef train(self, training_data, num_epoches, batch_size=10, eta=0.01):n = len(training_data)losses = []for epoch_id in range(num_epoches):# 在每轮迭代开始之前,将训练数据的顺序随机的打乱,# 然后再按每次取batch_size条数据的方式取出np.random.shuffle(training_data)# 将训练数据进行拆分,每个mini_batch包含batch_size条的数据mini_batches = [training_data[k:k+batch_size] for k in range(0, n, batch_size)]for iter_id, mini_batch in enumerate(mini_batches):#print(self.w.shape)#print(self.b)x = mini_batch[:, :-1]y = mini_batch[:, -1:]a = self.forward(x)loss = self.loss(a, y)gradient_w, gradient_b = self.gradient(x, y)self.update(gradient_w, gradient_b, eta)losses.append(loss)print('Epoch {:3d} / iter {:3d}, loss = {:.4f}'.format(epoch_id, iter_id, loss))return losses# 获取数据
train_data, test_data = load_data()# 创建网络
net = Network(13)
# 启动训练
losses = net.train(train_data, num_epoches=50, batch_size=100, eta=0.1)# 画出损失函数的变化趋势
plot_x = np.arange(len(losses))
plot_y = np.array(losses)
plt.plot(plot_x, plot_y)
plt.show()
- 数据处理:从本地文件或网络地址读取数据,并做预处理操作,如校验数据的正确性等。
- 模型设计:完成网络结构的设计(模型要素1),相当于模型的假设空间,即模型能够表达的关系集合。
- 训练配置:设定模型采用的寻解算法(模型要素2),即优化器,并指定计算资源。
- 训练过程:循环调用训练过程,每轮均包括前向计算 、损失函数(优化目标,模型要素3)和后向传播这三个步骤。
- 保存模型:将训练好的模型保存,以备预测时调用
-
线性回归模型
假设房价和各影响因素之间能够用线性关系来描述:y = ∑ i = 0 M x i w i + b y = \sum_{i=0}^M x_iw_i+by=∑i=0Mxiwi+b
模型的求解即是通过数据拟合出每个w i w_iwi和b bb,w i w_iwi和b bb分别表示线性模型的权重和偏值,一维情况下代表直线的斜率和截距。数据处理
下面代码中数据处理的部分可以简单概括为:
- 从文件中读入的数据是一维的,数据存放的格式是【因素1_1,因素1_2,…,因素1_13,房价均价1,…,因素M_1,…,因素M_13,房价均价M】,首先应该将原始数据变成[N,14]的形状。
- 将数据集找分成训练集和测试集。
- 一般来说,有的数据中还有Null项,或者是0项,如果有必要的话,这些项也要处理,否则会影响后面的计算,下面的数据是相当标准的,没有Null和0,所以也就没有处理。
- 将训练集的数据进行归一化处理,将各个因素的取值范围全部规范到(0,1)之间。这样做的话,每个特征值的范围相同,所以每个特征值对结果的影响程度只看w i w_iwi就可以了。
- 损失函数(Loss):损失函数是评价预测值好坏的一个参数,对于波士顿房价预测的案列来说,
L o s s = ( z − y ) 2 , z : 预 测 值 , y : 真 实 值 Loss = (z-y)^2,z:预测值,y:真实值Loss=(z−y)2,z:预测值,y:真实值,
L o s s s u m = 1 / M ∗ ∑ i = 0 M ( z i − y i ) 2 Loss_{sum} = 1/M*\sum_{i=0}^M (z^i-y^i)^2Losssum=1/M∗∑i=0M(zi−yi)2
这里我们说一下为什么要取平方?简单来说,现在要求
预 测 值 z = f ( w 1 , w 2 , . . . , w 3 ) , w i 是 参 数 预测值z=f(w_1,w_2,...,w_3),w_i是参数预测值z=f(w1,w2,...,w3),wi是参数
这个方程的极小值,说白了就是多元函数求极值问题,我们后续可能涉及到求导数问题。而对于方程y = ∣ x ∣ , y = x 2 y=|x|,y=x^2y=∣x∣,y=x2,y = ∣ x ∣ y=|x|y=∣x∣在极值点不可导,所以为了避免出现这样的问题,我们对Loss取个平方。 - 前向计算:前向计算是根据参数计算结果。
- 后向计算:后向计算是根据结果更新参数。
- 梯度下降法:梯度下降法是求函数极值的一个常用方法。我争取用一句话讲清楚什么是梯度下降法。假如你在一座山的半山腰,现在伸手不见五指,你需要下山,你只能伸出你的小脚丫去试探,看看哪里是向下走的一条路,然后如此往复,你一定能走到一个低谷。这就是梯度下降法。梯度下降法一定能找到一个局部最优值,他不能找到最小值,从刚才的例子也能清楚的感觉到。而这里还有一个重要的概念,你下山的时候每走的一步的步长称作学习率,学习率越大,函数收敛的也就也快,但是容易跳过极值点,学习率太小,那么函数收敛的也就越慢,最好的办法就是我们根据山的陡峭程度来决定我这一步迈的大还是小,也就是梯度越大学习率也就越大,梯度越小学习率也就小。
另外,前面我们数据处理的时候提到了归一化,不难看出,如果我们不进行归一化,学习率的大小对每个参数的影响程度都不同,归一化后,学习率的大小对每个参数的影响程度都一样,这也是为什么要归一化的一个重要原因。
对于本题,我们看看怎么用梯度下降法,我们下山的时候需要用脚去试探,才知道哪边是下坡的方向,而对于多元函数来说,有一个重要的概念叫做梯度,梯度的反方向就是下降最快的方向。那我们现在看看,线性回归模型的梯度是什么?