一、人工智能、机器学习、深度学习的关系
1、关系
概括来说,人工智能、机器学习和深度学习覆盖的技术范畴是逐层递减的。人工智能是最宽泛的概念。机器学习是当前比较有效的一种实现人工智能的方式。深度学习是机器学习算法中最热门的一个分支,近些年取得了显著的进展,并替代了大多数传统机器学习算法。
三者的关系如图所示,即:人工智能 > 机器学习 > 深度学习。
二、机器学习的实现
机器学习的实现可以分成两步:训练和预测,类似于我们熟悉的归纳和演绎:
- 归纳:
从具体案例中抽象一般规律,机器学习中的“训练”亦是如此。从一定数量的样本(已知模型输入X和模型输出Y)中,学习输出Y与输入X的关系(可以想象成是某种表达式)。 - 演绎:
从一般规律推导出具体案例的结果,机器学习中的“预测”亦是如此。基于训练得到的Y与X之间的关系,如出现新的输入X,计算出输出Y。通常情况下,如果通过模型计算的输出和真实场景的输出一致,则说明模型是有效的。
三、损失函数
- 衡量模型预测值和真实值差距的评价函数也被称为损失函数(损失Loss)。
- 优化算法:最小化损失是模型的优化目标,实现损失最小化的方法称为优化算法,也称为寻解算法(找到使得损失函数最小的参数解)
- 模型假设、评价函数(损失/优化目标)和优化算法是构成模型的三个部分。
四、神经网络
人工神经网络包括多个神经网络层,如卷积层、全连接层、LSTM等,每一层又包括很多神经元,超过三层的非线性神经网络都可以被称为深度神经网络。
-
神经元: 神经网络中每个节点称为神经元,由两部分组成:
加权和:将所有输入加权求和。 非线性变换(激活函数):加权和的结果经过一个非线性函数变换,让神经元计算具备非线性的能力。
-
多层连接: 大量这样的节点按照不同的层次排布,形成多层的结构连接起来,即称为神经网络。 前向计算:
从输入计算输出的过程,顺序从网络前至后。 -
计算图: 以图形化的方式展现神经网络的计算逻辑又称为计算图。我们也可以将神经网络的计算图以公式的方式表达如下:
Y=f3(f2(f1(w1⋅x1+w2⋅x2+w3⋅x3+b)+…)…)…)
五、波士顿房价预测 (代码详解)
1.项目介绍
- 波士顿房价预测是一个经典的机器学习任务,类似于程序员世界的“Hello World”
- 波士顿地区的房价是由诸多因素影响的。该数据集统计了13种可能影响房价的因素和该类型房屋的均价,期望构建一个基于13个因素进行房价预测的模型,如 图所示。
- housing.data数据集
这是部分数据集,可在网上下载,用word打开,一共有506行,14列数据。
- 房价是一个连续值,所以房价预测显然是一个回归任务。下面我们尝试用最简单的线性回归模型解决这个问题,并用神经网络来实现这个模型。
2.思路分析
3.代码实现
import numpy as np
import matplotlib.pyplot as plt
def load_data():
# 从文件导入数据
datafile = './work/housing.data'
# fromfile方法读取文件,需要读取时知道存入文件时数组的维度和元素类型
# a.tofile()和np.fromfile()需要配合使用可以通过元数据文件来存储额外信息
#如果指定了sep参数,则fromfile()和tofile()将以文本格式对数组进行输入输出。
#sep参数指定的是文本数据中数值的分隔符。
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]这样的形状
#shape[0]:表示矩阵的第二维长度,即行的长度,这里共506行
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, avgs = training_data.max(axis=0), training_data.min(axis=0), \
training_data.sum(axis=0) / training_data.shape[0]
# 对数据进行归一化处理
"""
运用平均数,将每个特征的取值缩放到-1~1之间。
如果当前值为最大值,则处理后也为最大值,但不一定是1,
如果当前为最小值,则处理后也为最小值,但不一定为-1,
但是当前值为平均值时,处理后为0。
"""
for i in range(feature_num):
#print(maximums[i], minimums[i], avgs[i])
#逗号“,”分隔各个维度,“:”表示各个维度内的切片,只有:表示取这个维度的全部值
#data[:, i]遍历所有行的数据
data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])
# 训练集和测试集的划分比例
training_data = data[:offset]
test_data = data[offset:]
return training_data, test_data
class Network(object):
def __init__(self, num_of_weights):
# 随机产生w的初始值
#seed( ) 用于指定随机数生成时所用算法开始的整数值
#如果使用相同的seed( )值,则每次生成的随即数都相同
# 为了保持程序每次运行结果的一致性,
# 此处设置固定的随机数种子
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
num_samples = error.shape[0]
cost = error * error
#求均值
#计算损失函数时需要把每个样本的损失函数值都考虑到,
#所以我们需要对单个样本的损失函数进行求和,并除以样本总数
cost = np.sum(cost) / num_samples
return cost
#计算梯度
def 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_b
def update(self, gradient_w, gradient_b, eta = 0.01):
self.w = self.w - eta * gradient_w
self.b = self.b - eta * gradient_b
# num_epoches:训练次数 batch_size:一次训练所选取的样本数 eta:学习率
def 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)]
# enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,
# 同时列出数据和数据下标,一般用在 for 循环当中。
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()
4.运行结果
5.代码解读
1.数据处理
数据处理包含五个部分:数据导入、数据形状变换、数据集划分、数据归一化处理和封装load data函数。数据预处理后,才能被模型调用。
(1). 数据导入
# 导入需要用到的package
import numpy as np
"""
json是java script object notation的缩写,用来存储和交换文本信息
使用时需要import json导入
json.dumps():将python对象编码为json的字符串
json.loads():将字符串编码为一个python对象
json.dump():将python对象序列化到一个文件,是文本文件,相当于将序列化后的json字符写到一个文件
json.load():从文件反序列表出python对象
总结:不带s的是序列化到文件或者从文件反序列化,带s的都是内存操作不涉及持久化
"""
import json
# 读入训练数据
datafile = './work/housing.data'
# fromfile方法读取文件,需要读取时知道存入文件时数组的维度和元素类型
# a.tofile()和np.fromfile()需要配合使用可以通过元数据文件来存储额外信息
# 如果指定了sep参数,则fromfile()和tofile()将以文本格式对数组进行输入输出。
# sep参数指定的是文本数据中数值的分隔符。
data = np.fromfile(datafile, sep=' ')
data
(2). 数据形状变换
由于读入的原始数据是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)
"""
shape函数可以了解数组的结构;reshape()函数改变数组的结构。
w.shape[0]返回的是w的行数
w.shape[1]返回的是w的列数
df.shape():查看行数和列数
数组(矩阵)只有一个维度时,shape只有shape[0],返回的是该一维数组(矩阵)中元素的个数
data.shape[0] // feature_num:新矩阵的行数 506行
feature_num:新矩阵的列数
"""
data = data.reshape([data.shape[0] // feature_num, feature_num])
到这里我们可以查看一下数据输出
# 查看数据
# 查看矩阵第一行的数据
x = data[0]
print(x.shape)
print(x)
输出如下
(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]
(3). 数据集划分
将数据集划分成训练集和测试集,其中训练集用于确定模型的参数,测试集用于评判模型的效果
# 将80%的数据用作训练集,20%用作测试集
ratio = 0.8
offset = int(data.shape[0] * ratio) #shape[0]:表示矩阵的第二维长度
training_data = data[:offset]
training_data.shape
(4). 数据归一化处理
对每个特征进行归一化处理,使得每个特征的取值缩放到0~1之间。这样做有两个好处:一是模型训练更高效;二是特征前的权重大小可以代表该变量对预测结果的贡献度(因为每个特征值本身的范围相同)。
# 计算train数据集的最大值,最小值,平均值
# axis=0代表往跨行(down),而axis=1代表跨列(across)
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])
"""
运用平均数,将每个特征的取值缩放到-1~1之间。
如果当前值为最大值,则处理后也为最大值,但不一定是1,
如果当前为最小值,则处理后也为最小值,但不一定为-1,
但是当前值为平均值时,处理后为0。
"""
# 遍历第i列所有元素
data[:, i] = (data[:, i] - avgs[i]) / (maximums[i] - minimums[i])
在此,我们可以将上述几个数据处理操作封装成load data函数,以便下一步模型的调用
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, 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
# 获取数据
training_data, test_data = load_data()
# 获取除最后一列以外的所有行和列
x = training_data[:, :-1]
# 获取最后一列的所有元素
y = training_data[:, -1:]
# 查看数据
print(x[0])
print(y[0])
2.模型设计
模型设计是深度学习模型关键要素之一,也称为网络结构设计,相当于模型的假设空间,即实现模型“前向计算”(从输入到输出)的过程。
首先,假设我们以如下任意数字赋值参数做初始化:
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]