机器学习-线性模型(波士顿房价预测)
文章目录
- 机器学习-线性模型(波士顿房价预测)
- 人工智能、机器学习、深度学习的关系
- 机器学习
- 深度学习
- 波士顿房价预测
- 数据集介绍
- 模型假设 → \rightarrow →线性回归模型
- 评价函数 → \rightarrow →均方误差
- 线性回归模型网络结构
- 实现波士顿房价预测任务
- 1. 数据处理
- 2. 模型设计
- 3. 训练配置
- 4. 训练过程
- 1. 梯度下降法(Gradient Descent,GD)
- 2. 随机梯度下降法(Stochastic Gradient Descent)
- 完整代码
- numpy 函数
- np.fromfile
- ndarray.shape
- ndarray.reshape
- numpy.dot
- numpy随机数
- np.newaxis
- np.random.shuffle
- matplotlib 3D绘图
- 参考
人工智能、机器学习、深度学习的关系
- 人工智能(Artificial Intelligence,AI),是研发用于模拟,延伸和扩展人的智能的理论、方法、技术以及应用系统的一门技术科学,这个定义只阐述了目标,没有限定方法。
- 机器学习(Machine Learning,ML)是当前比较有效的一种实现人工智能的方式。
- 深度学习(Deep Learning,DL)是机器学习算法中最热门的一个分支,替代了大多数传统机器学习算法。
机器学习
机器学习是专门研究用计算机模拟或者实现人类的学习行为,以获取新的知识或技能,并重新组织已有的知识结构,使之不断改善自身的性能。
机器学习的实现可以分为两个步骤:训练和预测,类似于归纳和演绎。
- 归纳:从具体示例中抽象一般规律。即从一定数量的样本(已知输入x和输出y)中,学习输出y与输入x的关系。
- 演绎:从一般规律中推导出具体案例的结果。基于训练得到的y与x之间的关系,由新的x计算出y。
如果通过模型计算的输出和真实场景中的输出一致,则说明模型是有效的。模型有效的基本条件是能够拟合已知的样本。
损失函数(Loss): 衡量模型预测值和真实值差距的评价函数
确定模型的三个关键要素:模型假设、评价函数、优化算法
最小化损失是模型的优化目标,实现损失最小化的方法称为优化算法。
机器学习任务的框架,其学习的本质是参数估计,未知目标函数
f
f
f以训练样本
D
D
D为依据,从假设集合
H
H
H中,通过学习算法
A
A
A找到一个函数
g
g
g,如果
g
g
g能够最大程度的拟合训练样本
D
D
D,可以认为函数
g
g
g接近于目标函数
f
f
f。
深度学习
目前,多数机器学习任务都可以使用深度学习模型解决,尤其在语音、计算机视觉和自然语言处理等领域,深度学习模型的效果比传统机器学习算法有显著提升。
机器学习和深度学习两者在理论结构上是一致的,即模型假设、评价函数和优化算法,根本差别在假设的复杂度。
人工神经网络包括多个神经网络层:卷积层、全连接层、LSTM等,每一层又包含很多神经元,超过三层的非线性神经网络都可以称为深度神经网络。
深度学习的模型可以视为是从输入到输出的映射函数,足够深的神经网络理论上可以拟合任何复杂的函数。神经网络非常适合学习样本数据的内在规律和表示层次,对文字、图像和语音任务有很好的适用性。
深度学习被称为实现人工智能的基础。
-
神经元
神经网络中每个节点称为神经元,由两个部分组成,加权和以及激活函数
- 加权和:将所有输出加权求和
- 非线性变换(激活函数):加权和的结果经过一个非线性函数变换,让神经元计算具有非线性的能力。
-
多层连接
大量神经元按照不同层次排布,形成多层的结构连接起来,称为神经网路
-
前向计算
从输入计算输出的过程,顺序为从网络前到后。
-
计算图
以图形化的方式展现神经网络的计算逻辑。
也可以将神经网络的计算图以公式的方式表达。
Y = f 3 ( f 2 ( f 1 ( w 1 ⋅ x 1 + w 2 ⋅ x 2 + . . . + b ) . . . ) . . . ) Y = f3(f2(f1(w_1 · x_1 + w_2 · x_2+ ... + b)...)...) Y=f3(f2(f1(w1⋅x1+w2⋅x2+...+b)...)...)
神经网络本质是一个含有很多参数的大公式。
波士顿房价预测
使用Python语言和Numpy库来构建神经网络模型
数据集介绍
波士顿地区的房价受诸多因素影响,数据集统计了13中可能影响房价的因素和该类型房屋的均价,期望构建一个基于13个因素进行房价预测的模型。
预测问题,可以根据预测输出的类型是连续的实数值,还是离散的标签,区分为回归任务和分类任务。
模型假设 → \rightarrow →线性回归模型
假设房价和各影响因素之间能够用线性关系描述
y
=
∑
j
=
1
M
x
j
w
j
+
b
y=\sum_{j=1}^{M}x_jw_j+b
y=j=1∑Mxjwj+b
模型的求解过程即是通过已知的样本数据拟合出每个
w
j
wj
wj 和
b
b
b,
w
j
wj
wj 和
b
b
b分别表示该线性模型的权重和偏置。
一维情况下, w j wj wj 和 b b b是直线的斜率和截距
评价函数 → \rightarrow →均方误差
线性回归模型使用均方误差(Mean Squared Error, MSE)作为损失函数(Loss),用以衡量预测房价和真实房价的差异。
M
S
E
=
1
N
∑
i
=
1
N
(
Y
i
^
−
Y
i
)
2
MSE = \frac{1}{N}\sum_{i=1}^{N}(\hat{Y_i} - Y_i)^2
MSE=N1i=1∑N(Yi^−Yi)2
将模型在每个训练样本上的预测误差求和累加,来衡量整体样本的准确性。
损失函数的设计不仅要考虑合理性(有物理意义),同时需要考虑易解性(易于求解)
线性回归模型网络结构
神经网络的标准结构中每个神经元由加权和与非线性变换构成,然后将多个神经元分层的摆放并连接形成神经网络。
线性回归模型,是只有加权和,没有非线性变换的神经元,无需形成网络。
实现波士顿房价预测任务
不同场景的深度学习模型具备一定的通用性,基本具有5个步骤
-
数据处理
数据读取,并完成预处理操作(数据校验、格式化等),保证模型可读取
-
模型设计
网络结构设计,相当于模型的假设空间
-
训练配置
设定模型采用的寻解算法,优化器,并指定计算资源
-
训练过程
循环调用训练过程,每轮都包括前向计算,损失函数(优化目标)和反向传播三个步骤
-
模型保存
将训练好的模型保存,模型预测时调用
在构建不同的模型时,只有模型三要素不同(模型假设、评价函数、优化算法),其他步骤基本一致
1. 数据处理
数据处理包含5个基本部分:数据导入、数据维度变换、数据集划分、数据归一化处理和封装load_data函数。
数据预处理后,才能被模型调用。
数据导入
import numpy as np
import json
# 读取训练数据
datafile = "./work/housing.data"
data = np.fromfile(datafile,sep=" ")
维度变换
读入原始数据是一维的,所有数据连在一起,需要进行维度变换,形成2维矩阵,每行一个数据样本(14个值),包含13个 x x x(影响房价的特征)和一个 y y y(该类型房屋的均价)
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])
数据集划分
将数据集划分为训练集和测试集,训练集用于确定模型的参数,测试集用于评测模型的效果。
# 将80%的数据用作训练集,20%用作测试集
ratio = 0.8
offset = int(data.shape[0]*ratio)
training_data = data[:offset]
数据归一化处理
对每个特征进行归一化处理,使得每个特征的取值缩放到0~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])
做输入特征的归一化,也是为了后续让统一的学习步长更加合适。
特征输入归一化后,不同参数输出的Loss是一个比较规整的曲线,学习率可以设置成统一的值 ;
特征输入未归一化时,不同特征对应的参数所需的步长不一致,尺度较大的参数需要大步长,尺寸较小的参数需要小步长,导致无法设置统一的学习率。
未归一化的特征,会导致不同特征维度的理想步长不同
封装成load_data函数
将以上的数据处理操作进行封装形成load_data函数,以便下一步模型的调用
def 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.8
offset = int(data.shape[0] * ratio)
# 计算训练集的最大值,最小值
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
2. 模型设计
模型设计是深度学习模型关键要素之一,也称为网络结构设计,实现模型的前向计算过程。
输入特征 x x x有13个向量, y y y有1个向量,参数权重的形状是 13 × 1 13\times1 13×1
完整的线性回归公式,还需要初始化偏移量
b
b
b,线性回归模型的完整输出是
z
=
t
+
b
z=t+b
z=t+b
从特征和参数计算输出值的过程称为前向计算。
实现forward函数,完成从特征和参数到输出预测值的计算过程。
class Network():
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):
# x -> (1,13) , w-> (13,1)
z = np.dot(x,self.w) + self.b
return z
3. 训练配置
模型设计完成后,需要通过训练配置寻找模型的最优值,即通过损失函数来衡量模型的好坏。
通过模型计算 x x x的影响因素所对应的房价应该是 z z z,实际数据的房价是 y y y, 需要有某种指标来衡量预测值 z z z与真实值 y y y之间的差距。
对于回归问题,最常采用的衡量方法是使用均方误差作为评价模型好坏的指标。
L
o
s
s
=
(
y
−
z
)
2
Loss = (y-z)^2
Loss=(y−z)2
L
o
s
s
Loss
Loss通常被称为损失函数,是衡量模型好坏的指标。
在回归问题中常用均方误差作为损失函数,在分类问题中常采用交叉熵(Cross-Entropy) 作为损失函数。
因为计算损失函数需要把每个样本的损失函数值都考虑到,所以需要对单个样本的损失函数进行求和,并除以样本总数
N
N
N。
L
o
s
s
=
1
N
∑
i
=
1
N
(
y
i
−
z
i
)
2
Loss = \frac{1}{N}\sum_{i=1}^{N}(y_i-z_i)^2
Loss=N1i=1∑N(yi−zi)2
在Network类中添加损失函数
class Network():
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):
# x -> (1,13) , w-> (13,1)
z = np.dot(x,self.w) + self.b
return z
def loss(self,z,y):
error = z-y
loss = np.mean(error * error)
return loss
4. 训练过程
上述过程描述了构建神经网络的过程,通过神经网络完成预测值和损失函数的计算。
模型的训练过程,完成参数 w w w 和 b b b的求解。
训练过程的目标是让定义的损失函数尽可能的小,即,找到一个参数解 w w w和 b b b,使得损失函数取得极小值。
由于求一条曲线在某个点的斜率等于函数曲线在该点的导数值。处于曲线极值点的斜率为0,即函数在极值点的导数为0。让损失函数取得极小值的 w w w和 b b b应该是下述方程组的解。
∂ L ∂ w = 0 \frac{\partial{L}}{\partial{w}} = 0 ∂w∂L=0
∂ L ∂ b = 0 \frac{\partial{L}}{\partial{b}} = 0 ∂b∂L=0
将样本数据 ( x , y ) (x,y) (x,y)带入上面的方程组即可求得 w 和 b w和b w和b的值,但是这种方法只对线性回归这样的简单任务有效。如果模型中包含非线性变换,或者损失函数不是均方差这种简单的形式,则很难通过上式求解。
更加普适的数值求解方法:梯度下降法
1. 梯度下降法(Gradient Descent,GD)
存在大量的函数正向求解容易,但是反向求解较难,称为单向函数,这种函数在密码学中大量被应用。
密码锁的特点是可以迅速判断一个密钥是否是正确的(已知 x x x,求 y y y容易),但是即使获取到了密码锁系统,无法破解正确的秘钥是什么(已知 y y y,求 x x x很难)。
神经网络模型的损失函数就是单向函数,反向求解不容易。
梯度下降法,求解Loss函数最小值的实现思想:从当前的参数取值,一步步按照下坡的方向下降,直到走到最低点。
训练的关键是找到一组 ( w , b ) (w,b) (w,b),使得损失函数 L L L取极小值。
损失函数类别的选择:采用绝对值误差和均方误差的对比
- 绝对值误差的Loss函数,不可微分
- 均方误差的Loss函数,可以微分
均方误差有两个好处
- 曲线的最低点是可导的
- 越接近最低点,曲线的坡度逐渐放缓,有助于通过当前的梯度来判断接近最低点的程度(可以考虑是否逐渐减少步长,避免错过最低点)
参数更新过程需要遵循的原则:
- 保证Loss是下降的
- 下降的趋势尽可能的快
沿着梯度的反方向,是函数值下降最快的方向。
计算梯度
为了使梯度计算更加简洁(求导过程会产生因子2),引入因子
1
2
\frac{1}{2}
21,定义损失函数
L
=
1
2
N
∑
i
=
1
N
(
y
i
−
z
i
)
2
L = \frac{1}{2N}\sum_{i=1}^{N}(y_i - z_i)^2
L=2N1i=1∑N(yi−zi)2
其中
z
i
z_i
zi是对第
i
i
i个样本的预测值
z
i
=
∑
j
=
0
12
x
i
j
⋅
w
j
+
b
z_i = \sum_{j=0}^{12}x_i^j ·w_j + b
zi=j=0∑12xij⋅wj+b
梯度的定义
g
r
a
d
i
e
n
t
=
(
∂
L
∂
w
0
,
∂
L
∂
w
1
,
.
.
.
,
∂
L
∂
w
12
,
∂
L
∂
b
)
gradient = (\frac{\partial{L}}{\partial{w_0}},\frac{\partial{L}}{\partial{w_1}},...,\frac{\partial{L}}{\partial{w_{12}}},\frac{\partial{L}}{\partial{b}})
gradient=(∂w0∂L,∂w1∂L,...,∂w12∂L,∂b∂L)
计算
L
L
L对
w
w
w和
b
b
b的偏导数
∂
L
∂
w
j
=
1
N
∑
i
=
1
N
(
z
i
−
y
i
)
∂
z
i
∂
w
j
=
1
N
∑
i
=
1
N
(
z
i
−
y
i
)
x
i
j
\frac{\partial{L}}{\partial{w_j}} = \frac{1}{N}\sum_{i=1}^N(z_i-y_i)\frac{\partial{z_i}}{\partial{w_j}}= \frac{1}{N}\sum_{i=1}{N}(z_i-y_i)x_i^j
∂wj∂L=N1i=1∑N(zi−yi)∂wj∂zi=N1i=1∑N(zi−yi)xij
∂ L ∂ b = 1 N ∑ i = 1 N ( z i − y i ) ∂ z i ∂ b = 1 N ∑ i = 1 N ( z i − y i ) \frac{\partial{L}}{\partial{b}} = \frac{1}{N}\sum_{i=1}^N(z_i - y_i)\frac{\partial{z_i}}{\partial{b}} = \frac{1}{N}\sum_{i=1}^{N}(z_i-y_i) ∂b∂L=N1i=1∑N(zi−yi)∂b∂zi=N1i=1∑N(zi−yi)
计算过程由于Numpy的广播机制,实现起来更加简洁
从另一个角度考虑总的梯度:
每个样本都对梯度有一个贡献值,总梯度是样本对梯度贡献值的平均值
∂
L
∂
w
j
=
1
N
∑
i
=
1
N
(
z
i
−
y
i
)
∂
z
i
∂
w
j
=
1
N
∑
i
=
1
N
(
z
i
−
y
i
)
x
i
j
\frac{\partial{L}}{\partial{w_j}} = \frac{1}{N}\sum_{i=1}^N(z_i-y_i)\frac{\partial{z_i}}{\partial{w_j}}= \frac{1}{N}\sum_{i=1}{N}(z_i-y_i)x_i^j
∂wj∂L=N1i=1∑N(zi−yi)∂wj∂zi=N1i=1∑N(zi−yi)xij
增加Network类的gradient函数
class Network():
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):
# x -> (1,13) , w-> (13,1)
z = np.dot(x,self.w) + self.b
return z
def loss(self,z,y):
error = z-y
loss = np.mean(error * error)
return loss
def gradient(self,x,y):
z = self.forward(x)
gradient_w = (z-y)*x
gradient_w = np.mean(gradient_w,axis=0)
gradient_w = gradient_w[:,np.newaxis]
gradient_b = (z-y)
gradient_b = np.mean(gradient_b)
return gradient_w,gradient_b
确定损失函数更小的点,更新梯度的方法,沿着梯度的反方向移动一小步。
net.w[5] = net.w[5] - eta *gradient_w5
- 相减,参数需要向梯度的反方向移动
- eta: 控制每次参数值沿梯度的反方向变动的大小,即每次移动的步长,称为学习率
封装Train函数
将循环计算过程封装在train和update函数中
实现逻辑:前向计算输出、根据输出和真实值计算Loss, 基于Loss和输入计算梯度,根据梯度更新参数值
class Network():
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):
# x -> (1,13) , w-> (13,1)
z = np.dot(x,self.w) + self.b
return z
def loss(self,z,y):
error = z-y
loss = np.mean(error * error)
return loss
def gradient(self,x,y):
z = self.forward(x)
gradient_w = (z-y)*x
gradient_w = np.mean(gradient_w,axis=0)
gradient_w = gradient_w[:,np.newaxis]
gradient_b = (z-y)
gradient_b = np.mean(gradient_b)
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
def train(self,x,y,iterations=100,eta = 0.01):
losses = []
for i in range(iterations):
z = self.forward(x)
L = self.loss(z,y)
gradient_w, gradient_b = self.gradient(x,y)
self.update(gradient_w,gradient_b,eta)
losses.append(L)
if (i+1)%10 == 0:
print("iter {},loss {}".format(i,L))
return losses
# 获取数据
train_data ,test_data = load_data()
x = train_data[:,:-1]
y = train_data[:,-1:]
# 创建网络
net = Network(13)
num_iterations = 1000
# 启动训练
losses = net.train(x,y,iterations=num_iterations,eta=0.01)
# 画出损失函数的变化趋势
plot_x = np.arange(num_iterations)
plot_y = np.array(losses)
plt.plot(plot_x,plot_y)
plt.show()
2. 随机梯度下降法(Stochastic Gradient Descent)
梯度下降法中每次损失函数和梯度计算都是基于数据集中的全量数据,但在实际问题中,数据集往往非常大,如果每次都使用全量数据进行计算,效率非常低。由于参数每次只沿着梯度反方向更新一点点,因此方向并不需要那么精确。一个合理的解决方案是每次从总的数据集中随机抽取出小部分数据来代表整体,基于这部分数据计算梯度和损失来更新参数,这种方法被称作随机梯度下降法(Stochastic Gradient Descent,SGD)
核心概念
- minibatch: 每次迭代时抽取出来的一批数据被称为一个minibatch
- batch size: 每个minibatch所包含的样本数目称为batch size
- Epoch 当程序迭代的时候,按minibatch逐渐抽取出样本,当把整个数据集都遍历到的时候,则完成了一轮训练,也叫一个Epoch(轮次),启动训练时,可以将训练的轮数num_epochs 和 batch_size 作为参数传入
将train_data 分成大小为batch_size 的多个minibatch
batch_size = 10
n = train_data.shape[0]
np.random.shuffle(train_data)
mini_batches = [train_data[k:k+batch_size] for k in range(0,n,batch_size)]
SGD里是随机抽取一部分样本代表整体,为了实现随机抽取的效果,可以先将train_data里面的样本顺序随机打乱,然后再抽取minibatch。
实验发现模型对最后出现的数据印象更加深刻,训练数据导入后,越接近模型训练的结束,最后一个批次数据对模型参数的影响越大,为了避免模型记忆影响训练效果,需要进行样本乱序操作。
训练过程,将每个随机抽取的minibatch数据输入到模型中用于模型训练,训练过程的核心是两层循环
-
第一层循环,代表样本集合要被训练几次,称为epoch
for epoch_id in range(num_epochs): pass
-
第二层循环,代表每次遍历时,样本集合被拆分的多个批次,需要全部执行训练,称为iter(iteration)
for iter_id,mini_batch in emumerate(mini_batches): pass
内层循环是经典的四步训练流程:
- 前向计算
- 计算损失
- 计算梯度
- 更新参数
def train(self,training_data,num_epochs,batch_size=10,eta=0.01):
n= len(training_data)
losses = []
for epoch_id in range(num_epochs):
# 在每轮迭代开始之前,将训练数据的顺序进行随机打乱
# 然后再按照每次取出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):
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
随机梯度下降加快了训练过程,但是由于每次仅基于少量样本跟新参数和计算损失,会导致损失下降曲线出现震荡。
使用神经网络构建模型的三个要点
- 构建网络,初始化参数 w w w和 b b b, 定义预测和损失函数的计算方法
- 随机选择初始点,建立梯度的计算方法和参数更新方式
- 将数据集的数据按照batch_size 的大小 分成多个minibatch, 分别放入模型计算梯度并更新参数,不断迭代直到损失函数几乎不再下降。
完整代码
https://download.csdn.net/download/first_bug/87733907
import numpy as np
from matplotlib import pyplot as plt
def load_data():
datafile = "./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])
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
class Network():
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):
# x -> (1,13) , w-> (13,1)
z = np.dot(x,self.w) + self.b
return z
def loss(self,z,y):
error = z-y
loss = np.mean(error * error)
return loss
def gradient(self,x,y):
z = self.forward(x)
gradient_w = (z-y)*x
gradient_w = np.mean(gradient_w,axis=0)
gradient_w = gradient_w[:,np.newaxis]
gradient_b = (z-y)
gradient_b = np.mean(gradient_b)
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
def train_gd(self,x,y,iterations=100,eta = 0.01):
losses = []
for i in range(iterations):
z = self.forward(x)
L = self.loss(z,y)
gradient_w, gradient_b = self.gradient(x,y)
self.update(gradient_w,gradient_b,eta)
losses.append(L)
if (i+1)%10 == 0:
print("iter {},loss {}".format(i,L))
return losses
def train_sgd(self,training_data,num_epochs,batch_size=10,eta=0.01):
n= len(training_data)
losses = []
for epoch_id in range(num_epochs):
# 在每轮迭代开始之前,将训练数据的顺序进行随机打乱
# 然后再按照每次取出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):
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
def valid(self,test_data):
x = test_data[:,:-1]
y = test_data[:,-1:]
a = self.forward(x)
loss = self.loss(a,y)
return loss
def main():
# 获取数据
train_data ,test_data = load_data()
x = train_data[:,:-1]
y = train_data[:,-1:]
# 创建网络
net = Network(13)
num_iterations = 1000
# 启动训练
losses = net.train_gd(x,y,iterations=num_iterations,eta=0.01)
#losses = net.train_sgd(train_data,num_iterations,batch_size=10,eta=0.01)
print("valid loss: {:.4f}".format(net.valid(test_data)))
# 画出损失函数的变化趋势
plot_x = np.arange(len(losses))
plot_y = np.array(losses)
plt.plot(plot_x,plot_y)
plt.show()
if __name__ == "__main__":
main()
numpy 函数
np.fromfile
np.fromfile(file,dtype=float,count=-1,sep="",offset=0,like=None)
从文本或者二进制文件中的数据构造一个数组,读取具有已知数据类型的二进制数据以及解析简单格式化文本文件的高效方法。
-
file
打开文件对象或者文件路径
-
dtype
返回数组的数据类型,对于二进制文件,用于确定文件中项目的大小和字节顺序,支持大多数内置数据类型
-
count: int
要读取的项目数,-1表示完整文件
-
sep: str
如果文件是文本文件,则指定项之间的分隔符,空(“”)分隔符表示文件被视为二进制文件。分隔符中的空格(" ")匹配零个或者多个空白字符。
-
offset: int
与文件当前位置的偏移量(以字节为单位),默认为0,仅允许用于二进制文件。
-
like: array_like
允许创建不是Numpy数组的数组引用对象,如果作为like传入的array-like支持__array_function__协议,则结果将由它定义,在这种情况下,确保创建一个与通过此参数传入的对象兼容的数组对象。
ndarray.shape
返回矩阵的维度信息( 第0维长度, 第1维长度, …, 第n维长度)
两行三列矩阵: (2,3)
ndarray.reshape
修改矩阵的维度信息,参数为维度信息
返回一个指向相同数据的新ndarray对象,修改一个ndarray也会修改其他指向该数据的ndarray对象
numpy.dot
数组的运算是元素级的,数组相乘的结果是各对应元素的积组成的数组。
对于矩阵而言,numpy提供了用于矩阵乘法的dot函数
res = np.dot(a,b,out=None)
获取两个元素a,b的乘积。
numpy随机数
设置随机数种子
np.random.seed(n)
生成随机数
np.random.rand(d0,d1,d2,...,dn)
返回一组服从 0 1 0~1 0 1均匀分布的随机样本值,样本的取值范围是 [ 0 , 1 ) [0,1) [0,1),不包括1
np.random.randn(d0,d1,d2,...,dn)
返回一组服从标准正态分布的随机样本值,取值基本主要在 − 1.96 ∼ + 1.96 -1.96 \thicksim + 1.96 −1.96∼+1.96之间,较大值的概率较小。
np.newaxis
np.newaxis的功能是增加新的维度,放的位置不同,产生的矩形的形状也不同
np.newaxis放在哪个位置,就会给哪个位置增加维度
x[:,np.newaxis]
, 放在后面,会给列上增加维度x[np.newaxis,:]
,放在前面,会给行上增加维度
通常用它将一维的数据转换成一个矩阵,就可以与其他矩阵进行相乘。
np.random.shuffle
在原数组上进行打乱操作,对元素进行重新排序,打乱原有顺序。
matplotlib 3D绘图
-
创建三维坐标轴对象Axes3D
# 方法一,利用参数projection='3d' from matplotlib import pyplot as plt # 定义坐标轴 fig = plt.figure() ax1 = plt.axes(projection="3d")
# 方法二: 利用三维轴 ( 已废弃 ) from matplotlib import pyplot as plt from mpl_toolkits.mplot3d import Axes3D # 定义图像和三维格式坐标轴 fig = plt.figure() ax2 = Axes3D(fig)
-
绘图
import numpy as np from matplotlib import pyplot as plt # 定义坐标轴 fig = plt.figure() ax1 = plt.axes(projection="3d") # 设置xyz方向的变量 z = np.linspace(0,13,1000) # 在[0-13]之间等距取1000个点 x = 5*np.sin(z) y = 5*np.cos(z) #设置坐标轴 ax1.set_xlabel('X') ax1.set_ylabel('Y') ax1.set_zlabel('Z') ax1.plot3D(x,y,z,'gray')#绘制空间曲线 plt.show()#显示图像
import numpy as np from matplotlib import pyplot as plt #定义新坐标轴 fig=plt.figure() ax3=plt.axes(projection='3d') #定义三维数据 xx=np.arange(-5,5,0.5) yy=np.arange(-5,5,0.5) #生成网格点坐标矩阵,对x和y数据执行网格化 X,Y=np.meshgrid(xx,yy) #计算z轴数据 Z=np.sin(X)+np.cos(Y) #绘图 #函数plot_surface期望其输入结构为一个规则的二维网格 ax3.plot_surface(X,Y,Z,cmap='rainbow') #cmap是颜色映射表 plt.title("3D") plt.show()
- X、Y 数据决定坐标点
- Z 轴数据决定 X、Y 坐标点对应的高度
参考
https://aistudio.baidu.com/aistudio/projectdetail/5687440