1、简介
本文仅是用于学习神经网络,用神经网络去判断一个数是否是质数没有意义。
2、模型概述
整个训练模型预测的过程分为四步:(1)获取数据。(2)定义模型。(3)使用数据训练模型。(4)使用模型预测。
3、代码实现
代码:【免费】使用MLP神经网络模型预测质数资源-CSDN下载
3.1、数据集
我这里是自己定义的数据,数据集就是"特征的矩阵"。
python
import torch
# 数据
# (1)数值
# (2)是否是整数
# (3)是否大于0
# (4)是否等于1
# (5)是否大于1
# (6)是否能被1整除
# (7)是否能被自己整除
# (8)是否能被1和自己以外的数字整除
# [数值,是否是整数,是否大于0,是否等于1,是否大于1,是否能被1整除,是否能被自己整除,是否能被1和自己以外的数字整除]
x = torch.tensor([
# 训练集
[-52.9, 0, 0, 0, 0, 0, 1, 0],
[-8.2, 0, 0, 0, 0, 0, 1, 0],
......
[-60, 1, 0, 0, 0, 1, 1, 1],
[-8, 1, 0, 0, 0, 1, 1, 1],
[-76, 1, 0, 0, 0, 1, 1, 1],
[-137, 1, 0, 0, 0, 1, 1, 0],
......
[127, 1, 1, 0, 1, 1, 1, 0],
[157, 1, 1, 0, 1, 1, 1, 0],
[89, 1, 1, 0, 1, 1, 1, 0],
......
[170, 1, 1, 0, 1, 1, 1, 1],
[-0.7, 0, 0, 0, 0, 0, 1, 0],
[-138.8, 0, 0, 0, 0, 0, 1, 0],
......
[88, 1, 1, 0, 1, 1, 1, 1],
[104, 1, 1, 0, 1, 1, 1, 1],
......
[30, 1, 1, 0, 1, 1, 1, 1],
[164, 1, 1, 0, 1, 1, 1, 1],
[-177.2, 0, 0, 0, 0, 0, 1, 0],
......
[138, 1, 1, 0, 1, 1, 1, 1]
], dtype=torch.float)
# 标签 0 表示非质数,1表示是质数
y = torch.tensor([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1,
1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
])
num_nodes = len(y)
split_size = num_nodes // 3
# 全部初始化为 False True表示这一条数据属于这个集合 False表示不属于
train_mask = torch.zeros(num_nodes, dtype=torch.bool)
val_mask = torch.zeros(num_nodes, dtype=torch.bool)
test_mask = torch.zeros(num_nodes, dtype=torch.bool)
# 前1/3 前三分之一数据属于训练集
train_mask[:split_size] = True
# 中间1/3 中间三分之一数据属于验证集
val_mask[split_size:2 * split_size] = True
# 后1/3 后三分之二数据属于测试集
test_mask[2 * split_size:] = True
# 输入维度
num_features = x.shape[1]
num_classes = len(torch.unique(y)) # num_classes = 2 因为只有true false两种
print(num_features)
print(num_classes)
3.2、模型
随机数种子的作用是让程序在每次运行时的初始参数都保持一致,从而确保每次训练结果的可复现性。比如每次运行程序时,都会去初始化每层全连接层(线性层)的初始参数,比如self.lin1和self.lin2的weight(权重)和bias(偏置)。
weight:每个输入特征的重要程度。告诉神经元哪个特征更重要?哪个特征应该放大?哪个特征应该减弱,用于描述输入特征对当前神经元输出的影响方向和影响程度。
bias:y=Wx+b,bias就是b,是一个常数。表示神经元自身的偏移量,它使模型即使输入全为0,也可以输出非0,从而提高模型表达能力。
weight和bias在训练的时候会不断更新调整,从而使模型达到最优的学习效果。
线性层公式:y=Wx+b
x:输入,比如用于训练的数据。数据里面会包含各个特征。结合3.1的GetData里的定义的数据,输入就是,x = 数值,是否整数,是否\>0,是否==1,是否\>1,能否被1整除,能否被自己整除,是否能被其它数字整除。
W:权重,告诉神经元每个特征的重要程度,用于描述输入特征对当前神经元输出的影响方向和影响程度。结合3.1的GetData里的定义的数据,权重可能是,0.2,1.3,-0.5,0.7,0.9,0.4,0.8,-3.1。
b:偏置,整体把结果往上移一点或者往下移一点。让模型具有更大的表达能力。
y:输出,线性层计算后的输出,作为下一层的输入或最终分类依据。
ReLU:激活函数,神经元可能输出负数即y=xW+b为负数,ReLU会把所有的负数都变成0。即ReLU(y)=0。

神经元的输出可以理解为:
**正数:**我认为我负责识别的这个特征存在,而且越大说明越明显。
**负数:**我认为这个特征不存在,或者与我要识别的模式相反。当前神经元认为该模式没有足够证据激活。
**ReLU:**把所有负响应都归零,只让"激活"的神经元继续向后传递信息。y>0是激活的神经元,y<=0是未激活的神经元。让没激活的神经元统一输出0方便告诉下一层我这里没有有价值的信息。
激活函数:赋予神经网络学习复杂非线性规律的能力。
常见激活函数:
Sigmoid:适用场景为二分类的最后一层。
Tanh:适用于RNN、LSTM、GRU。
ReLU:适用于MLP、CNN、GCN、GAT。
常见的神经网络
- MLP:全连接网络(基础,处理一维向量)
- CNN:卷积神经网络(网格结构数据:图像、时序)
- GCN:图卷积网络(图数据,基于邻接矩阵做空域平滑)
- GAT:图注意力网络(图数据,用注意力替代固定卷积权重)
python
import torch
from torch.nn import Linear
import torch.nn.functional as F
from demo import GetData
class MLP(torch.nn.Module): # MLP:多层感知机 定义一个神经网络
def __init__(self, hidden_channels): # 初始化
super().__init__()
torch.manual_seed(12345) # 固定随机数种子
# 输入8维
self.lin1 = Linear(GetData.num_features, hidden_channels)
# 输出2个类别
self.lin2 = Linear(hidden_channels, GetData.num_classes)
# 定义神经网络前向传播(Forward Propagation)的过程
# 定义数据如何流过这个模型(告诉pytorch:以后有人把数据送进来,请按照这个流程计算) 计算流程/数据流程
# forward并不会更新参数,只是完成一次预测。
def forward(self, x):
x = self.lin1(x) # 第一层全连接
x = x.relu() # relu激活函数
x = F.dropout(x, p=0.5, training=self.training) # 随机失活 训练时会故意删掉一些神经元,防止模型过拟合
x = self.lin2(x) # 第二层全连接
return x
# model = MLP(hidden_channels=16)
# print(model)
3.3、训练
python
import torch
from demo import GetData
from demo.MLP import MLP
model = MLP(hidden_channels=8)
criterion = torch.nn.CrossEntropyLoss() # 损失函数,CrossEntropyLoss内部已经包含Softmax。
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4) # 优化器
# 训练模型
def train():
model.train() # 训练模式
optimizer.zero_grad() # 清空梯度
out = model(GetData.x) #
loss = criterion(out[GetData.train_mask], GetData.y[GetData.train_mask]) # 计算损失函数
loss.backward() # Derive gradients. 反向传播 会影响更新参数
optimizer.step() # 基于梯度更新参数
return loss.item() # 本轮训练的损失值
# 验证模型:验证模型不能更新参数,因此不用loss.backward()和optimizer.step()
@torch.no_grad() # 让pytorch不计算梯度,速度会更快
def validate():
model.eval() # 切换到评估模式
out = model(GetData.x)
pred = out.argmax(dim=1)
correct = (
pred[GetData.val_mask] # 预测的类别是否等于实际的类别
==
GetData.y[GetData.val_mask]
)
acc = int(correct.sum()) / int(GetData.val_mask.sum())
return acc
# 测试模型:和validate几乎一样,只是用的数据集不一样。
@torch.no_grad()
def test():
model.eval()
out = model(GetData.x)
pred = out.argmax(dim=1)
correct = (
pred[GetData.test_mask]
==
GetData.y[GetData.test_mask]
)
acc = int(correct.sum()) / int(GetData.test_mask.sum())
return acc
# 训练代码,训练500次
for epoch in range(500):
loss = train()
val_acc = validate()
print(
epoch,
loss,
val_acc
)
# 最后,只测试一次
test_acc = test()
print(
"Test Accuracy:",
test_acc
)
# 保存模型参数
torch.save(
model.state_dict(),
"model/prime_number.pth"
)

损失函数:计算预测结果和真实答案差多少。
梯度:告诉神经网络,每一个参数应该朝哪个方向修改。梯度是损失函数对权重的导数。每一次训练,都要清空一下梯度optimizer.zero_grad()。

反向传播:预测的时候是"输入------>第一层------>第二层------>loss"是"正向传播"。而计算梯度的时候,要反着来,"loss------>第二层------>第一层"。因为是反着来就叫"反向传播"。
损失函数负责评价,梯度负责指出哪里错,优化器负责修改参数。
优化器:优化器会根据梯度修改参数,不断的让loss越来越小。修改每个神经元y=Wx+b中的权重W和偏置b。
3.4、推理
Predict.py
python
from sympy.printing.pytorch import torch
from demo.MLP import MLP
# 创建模型(结构必须一致)
model = MLP(hidden_channels=8)
# 加载参数
model.load_state_dict(torch.load("model/prime_number.pth"))
# 进入预测模式
# eval把模型切换到评估模式,会自动关闭原来模型中的dropout
# dropout只是在训练的时候让神经元随机失活防止过拟合。但预测的时候需要全部神经元参与发挥模型的全部能力。
model.eval()
new_method = torch.tensor([
#[72, 1, 1, 0, 1, 1, 1, 1]
# [47, 1, 1, 0, 1, 1, 1, 0]
#[7, 1, 1, 0, 1, 1, 1, 0]
#[-52.9, 0, 0, 0, 0, 0, 1, 0]
# [1009,1,1,0,1,1,1,0]
[1003,1,1,0,1,1,1,1]
], dtype=torch.float)
with torch.no_grad():
out = model(new_method)
pred = out.argmax(dim=1)
print(pred)

输出0,表示1003不是质数。
4、总结
我的数据集设计其实存在一个作弊特征:(8)是否能被1和自己以外的数字整除。这个其实就是质数的定义。相当于在数据集的第8维就已经告诉了模型答案了。这个就是机器学习里面的"Data Leakage(数据泄露)"。模型看到了答案。因此设计的数据集中就不能包含这种数据。
我的训练集划分也不合理。应该是随机划分的,确保训练集、测试集、验证集数据随机均匀分布。
当然本次只是为了学习MLP,只是作为demo而已。