2022.6.8
官方文档网址:


一、机器学习、人工智能、深度学习的关系


人工智能>机器学习>深度学习
image.png
图1 官方关系图
机器学习的实现:(1)训练、预测。(2)模型假设、评价函数、优化算法
深度学习:基于神经网络算法。
人工神经网络:
神经元
多层连接
向前计算
计算图

二、利用神经网络模型的波士顿房价预测实践


1.线性回归模型

线性函数

线性回归模型是房价和各个影响因素之间的线性关系方程式。
image.png
xj为各个影像因素,wi为各个因素的权重,b为各个因素的偏置。

损失函数

损失函数使用均方误差函数。
image.png

2.深度学习模型

paddle官方文档学习 - 图4
数据处理:数据导入、数据形状变换、数据集划分、数据归一化处理、封装函数。

3.波士顿房价数据准备

数据文件名:housing.data;

文件的下载:

  1. wget https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data -O housing.data

数据结构:

image.png

4.波士顿房价预测

(1)数据处理:

①数据导入:

  1. # 导入需要用到的package
  2. import numpy as np
  3. import json
  4. # 读入训练数据
  5. datafile = 'housing.data'
  6. data = np.fromfile(datafile, sep=' ')

②数据形状变换

读入的数据为一维状态,需要将数据转换为二维数据。

  1. feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE','DIS',
  2. 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
  3. feature_num = len(feature_names)
  4. data = data.reshape([data.shape[0] // feature_num, feature_num])

③数据集拆分

此处80%的数据作为训练集,20%的数据作为测试集。

  1. ratio = 0.8 #设置抽取的数据集所占的比例
  2. offset = int(data.shape[0] * ratio) #生成数据集
  3. training_data = data[:offset] #获取训练数据集
  4. test_data = data[offset:] #获取测试数据集

④数据归一化处理

  1. # 计算train数据集的最大值,最小值,平均值
  2. maximums, minimums, avgs = \
  3. training_data.max(axis=0), \
  4. training_data.min(axis=0), \
  5. training_data.sum(axis=0) / training_data.shape[0]
  6. # 对数据进行归一化处理
  7. for i in range(feature_num):
  8. #print(maximums[i], minimums[i], avgs[i])
  9. data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])

注:

获取数据集命令应放在归一化处理以后执行。

⑤封装数据读取导入处理函数

  1. def load_data():
  2. # 从文件导入数据
  3. datafile = './work/housing.data'
  4. data = np.fromfile(datafile, sep=' ')
  5. # 每条数据包括14项,其中前面13项是影响因素,第14项是相应的房屋价格中位数
  6. feature_names = [ 'CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', \
  7. 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV' ]
  8. feature_num = len(feature_names)
  9. # 将原始数据进行Reshape,变成[N, 14]这样的形状
  10. data = data.reshape([data.shape[0] // feature_num, feature_num])
  11. # 将原数据集拆分成训练集和测试集
  12. # 这里使用80%的数据做训练,20%的数据做测试
  13. # 测试集和训练集必须是没有交集的
  14. ratio = 0.8
  15. offset = int(data.shape[0] * ratio)
  16. training_data = data[:offset]
  17. # 计算训练集的最大值,最小值,平均值
  18. maximums, minimums, avgs = training_data.max(axis=0), training_data.min(axis=0), \
  19. training_data.sum(axis=0) / training_data.shape[0]
  20. # 对数据进行归一化处理
  21. for i in range(feature_num):
  22. #print(maximums[i], minimums[i], avgs[i])
  23. data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])
  24. # 训练集和测试集的划分比例
  25. training_data = data[:offset]
  26. test_data = data[offset:]
  27. return training_data, test_data
  1. # 获取数据
  2. training_data, test_data = load_data()
  3. x = training_data[:, :-1]
  4. y = training_data[:, -1:]
  1. # 查看一条x,y数据
  2. print(x[0])
  3. print(y[0])

(2)模型设计

①向前计算

首先假设一个权值w

  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]
  2. w = np.array(w).reshape([13, 1])
  1. 然后,取一条特征向量与参数向量相乘。
  1. x1=x[0]
  2. t = np.dot(x1, w)
  3. print(t)
  4. #结果:[0.03395597]
  1. 之后同样假设一个初始化偏移量b<br />这样就得到了一个线性回归方程的完整输出:z = t + b<br />这个从特征和参数计算输出值的过程称为“向前计算”。<br />**注**:<br />特征向量:数据处理结果x的任意一条数据都称为x的一个特征向量。<br />参数向量:每个x矩阵每个特征的权重所组成的向量w。<br />t = np.dot(x1 , w):np.dot()是矩阵乘法。例:np.dot([1,2,3],[4,5,6])=1*4+2*5+3*6=32<br />**封装向前计算**:<br />将向前计算封装为一个类。
  1. class Network(object):
  2. #此函数用以初始化w值(随机生成w值)
  3. def __init__(self, num_of_weights):
  4. # 设置固定的随机数种子,为了保持程序每次运行生成的随机值相同。
  5. np.random.seed(0)
  6. #生成随机值
  7. self.w = np.random.randn(num_of_weights, 1)
  8. #设置b的值为0.0
  9. self.b = 0.
  10. def forward(self, x):
  11. z = np.dot(x, self.w) + self.b
  12. return z
  1. **封装后的调用方法**:
  1. net = Network(13)
  2. x1 = x[0]
  3. y1 = y[0]
  4. z = net.forward(x1)
  5. print(z)
  6. #结果:[-0.63182506]

(3)训练配置

所谓的训练配置其实就是通过给定的损失函数来计算模型的好坏,从而获取最优解
对于回归问题最常用衡量方法(损失函数)就是均方误差
image.png
Loss(损失函数指标)简记为:L

①损失函数指标的计算

image.png

  1. Loss = (y1 - z)*(y1 - z)
  2. print(Loss)
  3. #结果:[0.39428312]
  1. **损失函数封装**:封装后的损失函数需要添加到Network()类中。
  1. def loss(self, z, y):
  2. error = z - y
  3. cost = error * error
  4. cost = np.mean(cost)
  5. return cost

:以上计算过程中的w和z值均为随机值,而在机器学习中,w和z的值应为计算得出。故以下过程讲解如何求解w和z的值。

(4)训练过程

①线性回归方法

所谓的训练过程就是找到一个让损失函数指标值尽可能小的w和z,即最优w和z
此处利用导数求解极值的思想,当斜率为0时,函数值取得极值
①首先损失函数可写为:
image.png
②对b求偏导数,并令结果等于0,求解得:
image.png
其中image.pngimage.png
③此时将b带入损失函数中,经计算可以得到:
image.png
将训练的数据x,y带入上式并求解,即可得到最优的w和z值
*注
:以上方法只对线性回归有效。普适性较差。

②梯度下降法

思想:利用数据集依次计算w1,w2…….,w12;
损失函数改写
image.png
预测函数
image.png
梯度的定义与计算
image.png
对L求导计算各个梯度w和b
image.png
image.png

  1. x1 = x[0]
  2. y1 = y[0] #y1 [-0.00390539]
  3. z1 = net.forward(x1) #z1 [-12.05947643]
  4. gradient_w0 = (z1 - y1) * x1[0] #gradient_w0 [0.25875126]此时计算的是w0
  5. gradient_w1 = (z1 - y1) * x1[1] #此处计算的是w1
  1. # 利用循环来计算w
  2. w = []
  3. for i in x1:
  4. gradient_wi = (z1 - y1) * x1[i]
  5. w.append(gradient_wi)
  6. print(w) # 输出w
  1. # 计算一个样本
  2. gradient_w = (z1 - y1) * x1
  3. # 计算多个样本
  4. x3samples = x[0:3]
  5. y3samples = y[0:3]
  6. z3samples = net.forward(x3samples)
  7. gradient_w = (z3samples - y3samples) * x3samples
  8. # 扩展样本维度
  9. z = net.forward(x)
  10. gradient_w = (z - y) * x # shape (404, 13)
  1. **计算样本梯度贡献值**:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/28439303/1654932251699-333be198-31e6-4cce-afa6-e294eecdd7cc.png#clientId=u9678fea2-2140-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=63&id=u6cc28d52&margin=%5Bobject%20Object%5D&name=image.png&originHeight=79&originWidth=498&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12274&status=done&style=none&taskId=uee28c6c9-ff24-491c-9a92-2edb2c21b8d&title=&width=398.4)
  1. # axis = 0 表示把每一行做相加然后再除以总的行数
  2. gradient_w = np.mean(gradient_w, axis=0)
  1. **w维度变换**:
  1. gradient_w = gradient_w[:, np.newaxis]
  1. **综合计算w梯度**:
  1. z = net.forward(x)
  2. gradient_w = (z - y) * x
  3. gradient_w = np.mean(gradient_w, axis=0)
  4. gradient_w = gradient_w[:, np.newaxis]
  5. gradient_w
  1. **计算b梯度**:
  1. gradient_b = (z - y)
  2. gradient_b = np.mean(gradient_b)
  3. gradient_b

封装numpy计算梯度函数

  1. def gradient(self, x, y):
  2. z = self.forward(x)
  3. gradient_w = (z-y)*x
  4. gradient_w = np.mean(gradient_w, axis=0)
  5. gradient_w = gradient_w[:, np.newaxis]
  6. gradient_b = (z - y)
  7. gradient_b = np.mean(gradient_b)
  8. return gradient_w, gradient_b
  1. **调用封装的函数计算梯度**:
  1. # 调用上面定义的gradient函数,计算梯度
  2. # 初始化网络
  3. net = Network(13)
  4. # 设置[w5, w9] = [-100., -100.]
  5. net.w[5] = -100.0
  6. net.w[9] = -100.0
  7. z = net.forward(x)
  8. loss = net.loss(z, y)
  9. gradient_w, gradient_b = net.gradient(x, y)
  10. gradient_w5 = gradient_w[5][0]
  11. gradient_w9 = gradient_w[9][0]
  12. print('point {}, loss {}'.format([net.w[5][0], net.w[9][0]], loss))
  13. print('gradient {}'.format([gradient_w5, gradient_w9]))

梯度更新
沿着梯度反方向移动一小步观察损失函数的变化。

  1. # 在[w5, w9]平面上,沿着梯度的反方向移动到下一个点P1
  2. # 定义移动步长 eta
  3. eta = 0.1
  4. # 更新参数w5和w9
  5. net.w[5] = net.w[5] - eta * gradient_w5
  6. net.w[9] = net.w[9] - eta * gradient_w9
  7. # 重新计算z和loss
  8. z = net.forward(x)
  9. loss = net.loss(z, y)
  10. gradient_w, gradient_b = net.gradient(x, y)
  11. gradient_w5 = gradient_w[5][0]
  12. gradient_w9 = gradient_w[9][0]
  13. print('point {}, loss {}'.format([net.w[5][0], net.w[9][0]], loss))
  14. print('gradient {}'.format([gradient_w5, gradient_w9]))
  15. # point [-99.91499266760042, -99.38615876351922], loss 678.6472185028845
  16. # gradient [-0.8556356178645292, -6.0932268634065805]

eta:用于控制沿梯度反方向移动距离的大小,又称为学习效率
相减:表明参数向反方向移动。
封装梯度更新函数

  1. def update(self, gradient_w, gradient_b, eta = 0.01):
  2. self.w = self.w - eta * gradient_w
  3. self.b = self.b - eta * gradient_b

封装Train(变换)函数

  1. def train(self, x, y, iterations=100, eta=0.01):
  2. losses = []
  3. for i in range(iterations):
  4. z = self.forward(x)
  5. L = self.loss(z, y)
  6. gradient_w, gradient_b = self.gradient(x, y)
  7. self.update(gradient_w, gradient_b, eta)
  8. losses.append(L)
  9. if (i+1) % 10 == 0:
  10. print('iter {}, loss {}'.format(i, L))
  11. return losses
  12. # 获取数据
  13. train_data, test_data = load_data()
  14. x = train_data[:, :-1]
  15. y = train_data[:, -1:]
  16. # 创建网络
  17. net = Network(13)
  18. num_iterations=1000
  19. # 启动训练
  20. losses = net.train(x,y, iterations=num_iterations, eta=0.01)
  21. # 画出损失函数的变化趋势
  22. plot_x = np.arange(num_iterations)
  23. plot_y = np.array(losses)
  24. plt.plot(plot_x, plot_y)
  25. plt.show()

③随机梯度下降法

每次从总的数据集中随机抽取出小部分数据来代表整体,基于这部分数据计算梯度和损失来更新参数。
mini-batch:每次迭代时抽取出来的一批数据被称为一个mini-batch。
batch_size:一个mini-batch所包含的样本数目称为batch_size。
epoch:当程序迭代的时候,按mini-batch逐渐抽取出样本,当把整个数据集都遍历到了的时候,则完成了一轮训练,也叫一个epoch。启动训练时,可以将训练的轮数num_epochs和batch_size作为参数传入。

④总结:

训练代码

  1. import numpy as np
  2. class Network(object):
  3. def __init__(self, num_of_weights):
  4. # 随机产生w的初始值
  5. # 为了保持程序每次运行结果的一致性,此处设置固定的随机数种子
  6. #np.random.seed(0)
  7. self.w = np.random.randn(num_of_weights, 1)
  8. self.b = 0.
  9. def forward(self, x):
  10. z = np.dot(x, self.w) + self.b
  11. return z
  12. def loss(self, z, y):
  13. error = z - y
  14. num_samples = error.shape[0]
  15. cost = error * error
  16. cost = np.sum(cost) / num_samples
  17. return cost
  18. def gradient(self, x, y):
  19. z = self.forward(x)
  20. N = x.shape[0]
  21. gradient_w = 1. / N * np.sum((z-y) * x, axis=0)
  22. gradient_w = gradient_w[:, np.newaxis]
  23. gradient_b = 1. / N * np.sum(z-y)
  24. return gradient_w, gradient_b
  25. def update(self, gradient_w, gradient_b, eta = 0.01):
  26. self.w = self.w - eta * gradient_w
  27. self.b = self.b - eta * gradient_b
  28. def train(self, training_data, num_epochs, batch_size=10, eta=0.01):
  29. n = len(training_data)
  30. losses = []
  31. for epoch_id in range(num_epochs):
  32. # 在每轮迭代开始之前,将训练数据的顺序随机打乱
  33. # 然后再按每次取batch_size条数据的方式取出
  34. np.random.shuffle(training_data)
  35. # 将训练数据进行拆分,每个mini_batch包含batch_size条的数据
  36. mini_batches = [training_data[k:k+batch_size] for k in range(0, n, batch_size)]
  37. for iter_id, mini_batch in enumerate(mini_batches):
  38. #print(self.w.shape)
  39. #print(self.b)
  40. x = mini_batch[:, :-1]
  41. y = mini_batch[:, -1:]
  42. a = self.forward(x)
  43. loss = self.loss(a, y)
  44. gradient_w, gradient_b = self.gradient(x, y)
  45. self.update(gradient_w, gradient_b, eta)
  46. losses.append(loss)
  47. print('Epoch {:3d} / iter {:3d}, loss = {:.4f}'.
  48. format(epoch_id, iter_id, loss))
  49. return losses
  50. # 获取数据
  51. train_data, test_data = load_data()
  52. # 创建网络
  53. net = Network(13)
  54. # 启动训练
  55. losses = net.train(train_data, num_epochs=50, batch_size=100, eta=0.1)
  56. # 画出损失函数的变化趋势
  57. plot_x = np.arange(len(losses))
  58. plot_y = np.array(losses)
  59. plt.plot(plot_x, plot_y)
  60. plt.show()

⑤保存模型:

  1. np.save('w.npy', net.w)
  2. np.save('b.npy', net.b)

三、飞桨深度学习开源开放平台

1.深度度学习框架设计思路

建模者只实现模型中个性化的部分,这样可以在“节省投入”和“产出强大”之间达到一个平衡。