利用梯度下降法进行多变量二分类
首先让我们了解一下什么是二分类。
二分类就是指将数据点分为两个可能的类别之一,比如"+1",'-1'两个类别。
而多变量二分类就是指在多个输入变量的基础上,将数据点分为两个可能的类别之一。
以下代码就是利用梯度下降法来实现二分类的。
导包
import numpy as np
import matplotlib.pyplot as plt
数据准备
# 数据集
x=np.array([[0.180,0.001*1],[0.100,0.001*2],
[0.160,0.001*3],[0.080,0.001*4],
[0.090,0.001*5],[0.110,0.001*6],
[0.120,0.001*7],[0.170,0.001*8],
[0.150,0.001*9],[0.140,0.001*10],
[0.130,0.001*11]]) # 11*2
# 分类标签
y=np.array([+1,-1,+1,-1,-1,-1,-1,+1,+1,+1,-1])
然后获取数据集x的样本数n_samples和特征数n_features
n_samples,n_features=x.shape
我们学习的x.shape通常是用来获取维度维度的,那么在这里是如何使用的呢?
初始化参数
weights=np.zeros(n_features)
bias=0.0
lr=0.0001
epoch=10000
weights=np.zeros(n_features)
意思是为每一个特征分配一个权重,并且所有的这些权重都初始化为0
进行循环
for _ in range(epoch):
mis_flag=False
for i in range(n_samples):
# 计算预测值
y_pre=np.dot(weights,x[i])+bias
# 根据预测值和真实值调整权重和偏置
if y_pre*y[i]<=0:
weights=weights+lr*y[i]*x[i]
bias=bias+lr*y[i]
mis_flag=True
if not mis_flag:
break
print(weights)
print(bias)
我们将mis_flag初始化 为否,是指在此次迭代中不存在误分类样本
然后对每个样本计算预测值y_pre,是对w和x进行点积,再加偏置b得到的
接下来,根据预测值和真实值的乘积是否小于0来判断样本是否被误分类,如果是误分类,就要调整参数和偏置了
如果小于0,代表预测值和真实值符号相反,样本被误分类了,然后更新权值和偏置,同时将mis_flag设置为True,表示本次迭代中存在被误分类的样本
最后
if not mis_flag: break
它的意思是,如果没有被误分类的样本,可以提前终止循环。
对比预测值与真实值
y_pre=[]
for i in range(n_samples):
# 计算预测值
pre=np.dot(weights,x[i])+bias
if(pre>=0):
y_pre.append(1)
else:
y_pre.append(-1)
print(y)
print(y_pre)
把每个样本的预测值追加到y_pre中,将真实值与预测值进行对比,判断二分类是否正确
可视化
# 绘制决策边界和误分类点
x1=np.arange(0.135,0.145,0.0001)
x2=(-weights[0]*x1-bias)/weights[1]
plt.plot(x1,x2,'r',label='Decision Boundary')
for ii in range(11):
if y[ii]==+1:
plt.plot(x[ii][0],x[ii][1],'bo')
else:
plt.plot(x[ii][0],x[ii][1],'ro')
plt.legend()
plt.show()
完整代码
import numpy as np
import matplotlib.pyplot as plt
# 数据集
x=np.array([[0.180,0.001*1],[0.100,0.001*2],
[0.160,0.001*3],[0.080,0.001*4],
[0.090,0.001*5],[0.110,0.001*6],
[0.120,0.001*7],[0.170,0.001*8],
[0.150,0.001*9],[0.140,0.001*10],
[0.130,0.001*11]]) # 11*2
# 分类标签
y=np.array([+1,-1,+1,-1,-1,-1,-1,+1,+1,+1,-1])
n_samples,n_features=x.shape
# 初始化权重和偏置为0
weights=np.zeros(n_features)
bias=0.0
lr=0.0001
epoch=10000
for _ in range(epoch):
mis_flag=False
for i in range(n_samples):
# 计算预测值
y_pre=np.dot(weights,x[i])+bias
# 根据预测值和真实值调整权重和偏置
if y_pre*y[i]<=0:
weights=weights+lr*y[i]*x[i]
bias=bias+lr*y[i]
mis_flag=True
if not mis_flag:
break
print(weights)
print(bias)
y_pre=[]
for i in range(n_samples):
# 计算预测值
pre=np.dot(weights,x[i])+bias
if(pre>=0):
y_pre.append(1)
else:
y_pre.append(-1)
print(y)
print(y_pre)
# 绘制决策边界和误分类点
x1=np.arange(0.135,0.145,0.0001)
x2=(-weights[0]*x1-bias)/weights[1]
plt.plot(x1,x2,'r',label='Decision Boundary')
for ii in range(11):
if y[ii]==+1:
plt.plot(x[ii][0],x[ii][1],'bo')
else:
plt.plot(x[ii][0],x[ii][1],'ro')
plt.legend()
plt.show()
最终结果:
利用梯度下降法进行多变量线性回归
import numpy as np
# 数据集
x = np.array([[0.180, 0.001 * 1], [0.100, 0.001 * 2],
[0.160, 0.001 * 3], [0.080, 0.001 * 4],
[0.090, 0.001 * 5], [0.110, 0.001 * 6],
[0.120, 0.001 * 7], [0.170, 0.001 * 8],
[0.150, 0.001 * 9], [0.140, 0.001 * 10],
[0.130, 0.001 * 11]]) # 11*2
# 标签,真实值
y = np.array([[0.180 + 0.001 * 1, 0.100 + 0.001 * 2,
0.160 + 0.001 * 3, 0.080 + 0.001 * 4,
0.090 + 0.001 * 5, 0.110 + 0.001 * 6,
0.120 + 0.001 * 7, 0.170 + 0.001 * 8,
0.150 + 0.001 * 9, 0.140 + 0.001 * 10,
0.130 + 0.001 * 11]])
# # 超参初始化
lr = 0.001
epoches = 100000
# 1*2权重参数,随机参数初始化
w = np.array([[0.5214, 0.5215]])
# 模型,矩阵相乘
ya = np.matmul(w, x.T) # 1*2,2*11=1*11
# 目标损失函数
loss = 1 / 2.0 * np.sum((np.matmul(w, x.T) - y) ** 2)
w_list = [] # 记录模型权重
loss_list = [] # 记录每次迭代后的损失
loss_list.append(loss)
for i in range(epoches):
w_gra = np.mean(np.matmul((np.matmul(w, x.T) - y), x))
w = w - lr * w_gra
loss = 1 / 2.0 * np.sum((np.matmul(w, x.T) - y) ** 2)
if (loss > loss_list[-1]):
break;
w_list.append(w)
loss_list.append(loss)
print(f'迭代第{i}次,梯度为{w_gra:10f},权重为{w},损失为{loss:10f}')
print(w)
ya=np.matmul(w, x.T)
# 输出预测值
print("输出预测值", ya)
# 输出标签值,即真实值
print("输出真实值:", y)
在这里 10f 是什么意思呢
在print语句中, 10 f一个格式化字符串的一部分,用于指定输出的格式。具体来说, 10 f表示输出一个浮点数,并且占据10个字符的宽度,其中包括小数点和小数部分。如果实际的值不足10个字符宽度,将会在左侧填充空格以达到指定的宽度。 例如,如果w_gra的值为 3.14159,那么使用 10f格式化字符串将会输出 3.141590,其中前面有两个空格填充以达到10个字符的宽度。
感知机
单层感知机
概念及模型
激活函数
引入
除输入层外,隐藏层和输出层的每一个神经元都有一个激活函数(映射函数)
每一层的激活函数都是相同的
作用
激活函数是用来引入非线性因素的,即多层感知机可以解决非线性问题。
常用激活函数
Sigmoid激活函数
Tanh激活函数
ReLU激活函数
Softmax激活函数
Sigmoid激活函数
Sigmoid函数适用于将预测概率作为输出的模型。由于概率的取值范围是0到1,当感知机用于解决二分类问题时,输出层的神经元一般使用Sigmoid激活函数。
优缺点
数学公式及图像
此函数可以求导,因此我们也需要了解它是如何进行求导的
求导过程
ReLU激活函数
ReLU函数是深度神经网络中最常用的激活函数之一,在大多数情况下都表现得比较好。隐藏层的神经元建议使用ReLU激活函数(分段函数)。
优缺点
数学公式及图像
Tanh激活函数
Tanh解决了sigmoid函数以非0为中心的问题,但也有梯度消失的问题,所以隐藏层不建议使用。在一般的二元分类问题中,tanh 函数用于隐藏层,而 sigmoid 函数用于输出层,但这并不是固定的,需要根据特定问题进行调整。
优缺点
数学公式及图像
Softmax激活函数
在多分类问题中,我们通常会使用softmax函数作为网络输出层的激活函数,softmax函数可以对输出值进行归一化操作,把所有输出值都转化为概率,所有概率值加起来等于1。
公式
推导过程
Softmax激活函数只能用在输出层神经元上。当感知器用于解决多分类问题时,输出层的神经元使用Softmax激活函数。当感知器用于解决二分类问题时,输出层的神经元可以使用Softmax激活函数,也可以使用Sigmoid激活函数。
注意
激活函数相当于映射,可以在pycharm中直接使用,我们这里是大致了解一下他们的原理以及数学公式的推导,以便我们更好地掌握。
多层感知机
感知机基本原理
多层感知机结构
多层感知机结构分为输入层,隐藏层,输出层:
隐藏层可以有多个:
它的输入:
其中,
让我们来详细了解一下多层感知机:
多层感知机(Multilayer Perceptron,简称MLP)是一种基本的前馈人工神经网络模型,由多个神经元层组成。每个神经元层之间都是全连接的,即上一层的每个神经元都与下一层的每个神经元相连。多层感知机包含一个输入层、一个或多个隐藏层和一个输出层。输入层接收输入数据,隐藏层通过非线性的函数复合对信号进行逐步加工、特征提取以及表示学习,输出层则输出模型的预测结果。
在多层感知机中,每个神经元都有一个激活函数,通常使用非线性函数,如Sigmoid函数、ReLU函数等。这些激活函数使得多层感知机能够学习非线性关系和复杂的模式。多层感知机通过前向传播的方式进行训练和预测,使用反向传播算法和优化算法(如梯度下降法)来更新网络的权重和偏置,以最小化预测值与实际值之间的误差。
多层感知机相比于线性模型有两个主要特点:多层结构和激活函数。多层结构使得多层感知机能够模拟复杂的函数,解决非线性问题。激活函数的引入则使得多层感知机能够学习并逼近复杂的非线性函数
(BP神经网络)训练多层感知机并预测
同样的,我们在学习这部分知识时,也应先了解相应的知识。
我们先来看看神经网络与多层感知机的关系吧
也就是说,神经网络包含着多层感知机,多层感知机是神经网络的一种形式
数据准备
import numpy as np
x0 = np.array([[0,0,1], [0,1,1], [1,0,1], [1,1,1]]).T # (3, 4)
y = np.array([[0,1,1,0]]) # (1, 4)
w0 = np.random.random((4, 3)) # (4, 3)
w1 = np.random.random((1, 4)) # (1, 4)
正向传播
lr = 0.1
for j in range(60000):
# 正向传播,两层网络
# 第 1 层网络
z0 = np.matmul(w0, x0)
x1 = 1 / (1 + np.exp(-z0)) #f()函数为sigmoid函数
# 第 2 层网络
z1 = np.matmul(w1, x1)
y_pred = 1 / (1 + np.exp(-z1))
x1 = 1 / (1 + np.exp(-z0))
此代码为通过Sigmoid激活函数对z0
进行非线性变换,将输入值z0
映射到0和1之间的一个输出值x1
这时候,我们就应该复习一下Sigmoid激活函数的公式了
np.exp()是NumPy库中的一个函数,用于计算自然指数e
的幂。
在第二层时,再次通过Sigmoid激活函数对z1
进行非线性变换,得到网络的最终预测输出y_pred
计算损失
# 输出loss值,看是否梯度下降
loss = np.mean((y_pred - y) ** 2)
print("loss值:", loss)
反向传播(重点,难理解的部分)
# 反向传播
# 最后一层loss对预测值求导 loss = 1/2*(y-y_pred) ** 2
ypred_delta = -(y-y_pred)
# 倒数第1层
z1_delta = ypred_delta * (y_pred*(1-y_pred)) # 对z1求导
w1_delta = np.matmul(z1_delta, x1.T) # 对w1求导 ***重点
x1_delta = np.matmul(w1.T, z1_delta) # 对x1求导
z0_delta = x1_delta * (x1 * (1 - x1)) # 对z0求导
w0_delta = np.matmul(z0_delta, x0.T) # 对w0求导 ***重点
x0_delta = np.matmul(w0.T, z0_delta) # 对x0求导
z1_delta = ypred_delta * (y_pred*(1-y_pred))
这部分运用了激活函数求导和链式法则,我们可以回到Sigmoid中查看他的求导过程。
需要注意的是,反向传播是从激活值开始的。
链式法则的定义
权重更新
# 更新权重
w1 = w1 - lr * w1_delta
w0 = w0 - lr * w0_delta
输出结果
print("w0值是: ", w0)
print("w1值是: ", w1)
# 正向传播进行对x0的输入值进行预测得到预测值
z0 = np.matmul(w0, x0)
x1 = 1 / (1 + np.exp(-(z0)))
z1 = np.matmul(w1, x1)
x2 = 1 / (1 + np.exp(-(z1)))
print("x0的预测值是:", x2)
print("真实值为:",y)
我们可以看到,预测值与真实值的误差极小,这说明预测是准确的。
完整代码
import numpy as np
x0 = np.array([[0,0,1], [0,1,1], [1,0,1], [1,1,1]]).T # (3, 4)
y = np.array([[0,1,1,0]]) # (1, 4)
w0 = np.random.random((4, 3)) # (4, 3)
w1 = np.random.random((1, 4)) # (1, 4)
# 模型为 f(w1*f(w0*x0)) # (1, 4)
lr = 0.1
for j in range(60000):
# 正向传播,两层网络
# 第 1 层网络
z0 = np.matmul(w0, x0) # z0为线性输出
x1 = 1 / (1 + np.exp(-z0)) # x1为z0的激活输出
# 第 2 层网络
z1 = np.matmul(w1, x1)
y_pred = 1 / (1 + np.exp(-z1)) # y_pred也是激活输出,它作为预测值
# 输出loss值,使用均方误差,看是否梯度下降
loss = np.mean((y_pred - y) ** 2)
print("loss值:", loss)
# 反向传播
# 最后一层loss对预测值求导 loss = 1/2*(y-y_pred) ** 2
ypred_delta = -(y-y_pred)
# 倒数第1层
z1_delta = ypred_delta * (y_pred*(1-y_pred)) # 对z1求导
w1_delta = np.matmul(z1_delta, x1.T) # 对w1求导 ***重点
x1_delta = np.matmul(w1.T, z1_delta) # 对x1求导
z0_delta = x1_delta * (x1 * (1 - x1)) # 对z0求导
w0_delta = np.matmul(z0_delta, x0.T) # 对w0求导 ***重点
x0_delta = np.matmul(w0.T, z0_delta) # 对x0求导
# 更新权重
w1 = w1 - lr * w1_delta
w0 = w0 - lr * w0_delta
print("w0值是: ", w0)
print("w1值是: ", w1)
# 正向传播进行对x0的输入值进行预测得到预测值
z0 = np.matmul(w0, x0)
x1 = 1 / (1 + np.exp(-(z0)))
z1 = np.matmul(w1, x1)
x2 = 1 / (1 + np.exp(-(z1)))
print("x0的预测值是:", x2)
print("真实值为:",y)
python封装成类实现BP神经网络
我们在理解上面的BP网络之后,封装成类就会很容易理解了
类定义
import numpy as np
class BPNetwork:
def __init__(self,input_size,hidden_size,output_size):
self.input_size =input_size
self.hidden_size = hidden_size
self.output_size = output_size
# 初始化权重和偏置
self.w0=np.random.randn(self.hidden_size,self.input_size)
self.w1=np.random.randn(self.output_size,self.hidden_size)
前向传播和反向传播
def forward(self,x0):
# 前向传播
# 第1层网络
self.z0=np.matmul(self.w0,x0)
self.x1=1/(1+np.exp(-self.z0))
# 第2层网络
self.z1=np.matmul(self.w1,self.x1)
self.y_pred=1/(1+np.exp(-self.z1))
return self.y_pred
def backward(self,x0,y,lr):
# 反向传播
#最后一层loss对预测值求导
ypred_delta=-(y-self.y_pred)
# 倒数第1层
z1_delta=ypred_delta*(self.y_pred*(1-self.y_pred))
w1_delta=np.matmul(z1_delta,self.x1.T)
x1_delta=np.matmul(self.w1.T,z1_delta)
z0_delta=x1_delta*(self.x1*(1-self.x1))
w0_delta=np.matmul(z0_delta,x0.T)
# 更新权值
self.w1=self.w1-lr*w1_delta
self.w0=self.w0-lr*w0_delta
进行训练
# 数据集
x0 = np.array([[0,0,1], [0,1,1], [1,0,1], [1,1,1]]).T # (3, 4)
y = np.array([[0,1,1,0]])
lr=0.1
bpmodel=BPNetwork(3,4,1)
for j in range(60000):
bpmodel.forward(x0)
bpmodel.backward(x0,y,lr)
预测结果
# 预测结果
y_pred = bpmodel.forward(x0)
print(y_pred)
print(y)
注意
我们需要注意的一点是:
self.w0=np.random.randn(self.hidden_size,self.input_size) self.w1=np.random.randn(self.output_size,self.hidden_size)
w0和w1的randn是不同的,这是为什么呢
权重矩阵的形状是 (输出层神经元数量, 输入层神经元数量)
这样看可能不太直观
按照我个人的理解,输出层在输出层的位置,输入层在输入层的位置,剩下的一个就是隐藏层了。
完整代码
import numpy as np
class BPNetwork:
def __init__(self,input_size,hidden_size,output_size):
self.input_size =input_size
self.hidden_size = hidden_size
self.output_size = output_size
# 初始化权重和偏置
self.w0=np.random.randn(self.hidden_size,self.input_size)
self.w1=np.random.randn(self.output_size,self.hidden_size)
def forward(self,x0):
# 前向传播
# 第1层网络
self.z0=np.matmul(self.w0,x0)
self.x1=1/(1+np.exp(-self.z0))
# 第2层网络
self.z1=np.matmul(self.w1,self.x1)
self.y_pred=1/(1+np.exp(-self.z1))
return self.y_pred
def backward(self,x0,y,lr):
# 反向传播
#最后一层loss对预测值求导
ypred_delta=-(y-self.y_pred)
# 倒数第1层
z1_delta=ypred_delta*(self.y_pred*(1-self.y_pred))
w1_delta=np.matmul(z1_delta,self.x1.T)
x1_delta=np.matmul(self.w1.T,z1_delta)
z0_delta=x1_delta*(self.x1*(1-self.x1))
w0_delta=np.matmul(z0_delta,x0.T)
# 更新权值
self.w1=self.w1-lr*w1_delta
self.w0=self.w0-lr*w0_delta
# 数据集
x0 = np.array([[0,0,1], [0,1,1], [1,0,1], [1,1,1]]).T # (3, 4)
y = np.array([[0,1,1,0]])
lr=0.1
bpmodel=BPNetwork(3,4,1)
for j in range(60000):
bpmodel.forward(x0)
bpmodel.backward(x0,y,lr)
# 预测结果
y_pred = bpmodel.forward(x0)
print(y_pred)
print(y)
使用pytorch封装BP类
定义模型
import torch
class BPNetwork(torch.nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(BPNetwork, self).__init__()
self.hidden1=torch.nn.Linear(input_size, hidden_size)
self.out=torch.nn.Linear(hidden_size, output_size)
self.sigmoid=torch.nn.Sigmoid()
self.hidden1 = torch.nn.Linear(input_size, hidden_size):定义一个线性层(全连接层),输入大小为input_size,输出大小为hidden_size。
self.out = torch.nn.Linear(hidden_size, output_size):定义另一个线性层,输入大小为hidden_size,输出大小为output_size。
self.sigmoid = torch.nn.Sigmoid():定义Sigmoid激活函数。
前向传播
def forward(self, x):
x0=x.to(torch.float32)
z0=self.hidden1(x0)
x1=self.sigmoid(z0)
z1=self.out(x1)
y_pred=self.sigmoid(z1)
return y_pred
x0 = x.to(torch.float32):确保输入数据x是torch.float32类型。
z0 = self.hidden1(x0):将输入数据x0传递给隐藏层的线性层。
x1 = self.sigmoid(z0):对隐藏层的输出应用Sigmoid激活函数。
z1 = self.out(x1):将隐藏层的输出传递给输出层的线性层。
y_pred = self.sigmoid(z1):对输出层的输出应用Sigmoid激活函数,得到预测值y_pred。
准备数据
x0=torch.tensor([[0,0,1],[0,1,1],[1,0,1],[1,1,1]], dtype=torch.float32)
y=torch.tensor([[0,1,1,0]], dtype=torch.float32).T
model=BPNetwork(3,4,1)
criterion=torch.nn.MSELoss()
lr=0.1
optimizer=torch.optim.SGD(model.parameters(), lr)
criterion=torch.nn.MSELoss()使用均方误差(MSE)作为损失函数。
optimizer=torch.optim.SGD(model.parameters(), lr)使用随机梯度下降(SGD)作为优化器。
那么其中的参数是什么呢
也就是说,model.paramenters()指的是参数w和b的值
进行训练
for epoch in range(60000):
y_pred = model(x0)
loss = criterion(y_pred, y) # 计算损失值
optimizer.zero_grad() # 梯度清零
loss.backward() # 梯度反向传播
optimizer.step() #更新权值
输出结果
print("w0=",model.hidden1.weight.data)
print("b0=",model.hidden1.bias.data)
print("w1=",model.out.weight.data)
print("b1=",model.out.bias.data)
y_test=model(x0)
print("输入预测值为:",y_test.data)
完整代码
import torch
class BPNetwork(torch.nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(BPNetwork, self).__init__()
self.hidden1=torch.nn.Linear(input_size, hidden_size)
self.out=torch.nn.Linear(hidden_size, output_size)
self.sigmoid=torch.nn.Sigmoid()
def forward(self, x):
x0=x.to(torch.float32)
z0=self.hidden1(x0)
x1=self.sigmoid(z0)
z1=self.out(x1)
y_pred=self.sigmoid(z1)
return y_pred
x0=torch.tensor([[0,0,1],[0,1,1],[1,0,1],[1,1,1]], dtype=torch.float32)
y=torch.tensor([[0,1,1,0]], dtype=torch.float32).T
model=BPNetwork(3,4,1)
criterion=torch.nn.MSELoss()
lr=0.1
optimizer=torch.optim.SGD(model.parameters(), lr)
for epoch in range(60000):
y_pred=model(x0)
loss=criterion(y_pred,y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
print("w0=",model.hidden1.weight.data)
print("b0=",model.hidden1.bias.data)
print("w1=",model.out.weight.data)
print("b1=",model.out.bias.data)
y_test=model(x0)
print("输入预测值为:",y_test.data)