This commit is contained in:
liubiren 2026-02-09 16:44:28 +08:00
parent 6d89ffe182
commit 70431cad4f
2 changed files with 140 additions and 71 deletions

BIN
parameters.pkl Normal file

Binary file not shown.

View File

@ -34,7 +34,6 @@ class NeuralNetwork:
:param epsilon: 极小值默认为1e-9
"""
print("正在初始化神经网络...", end="")
if not (
len(structure) >= 3
and all(x >= 1 if isinstance(x, int) else False for x in structure)
@ -55,66 +54,18 @@ class NeuralNetwork:
# 神经网络层数定义第0层为输入层第l层为隐含层l=1,2,...,L-1第L层为输出层L为神经网络层数深度为L+1
self.layer_counts = len(structure) - 1
# 初始化是否训练
self.training = True
numpy.random.seed(seed) # 设置随机种子
self.parameters = {0: {}}
# 初始化神经网络参数
for layer_index in range(1, self.layer_counts + 1):
self.parameters[layer_index] = {
"activate": (
activate := (
self.output_activate
if layer_index == self.layer_counts
else self.hidden_activate
)
), # 激活函数
"weight": self._init_weight(
activate=activate,
previous_layer_neuron_counts=self.structure[layer_index - 1],
current_layer_neuron_counts=(
current_layer_neuron_counts := self.structure[layer_index]
),
), # 权重,维度为[当前层神经元数,上一层神经元数],适配加权输入=权重*输入+平移
"bias": numpy.zeros((current_layer_neuron_counts, 1)), # 平移
}
self.parameters = {}
# 初始化模式(包括训练模式和推理模式)
self.training = None
# 初始化随机种子
self.seed = seed
# 初始化极小值
self.epsilon = epsilon
print("已完成")
def _init_weight(
self,
activate: Literal["relu", "linear", "softmax"],
previous_layer_neuron_counts: int,
current_layer_neuron_counts: int,
) -> numpy.floating:
"""
初始化权重
:param activate: 激活函数
:param previous_layer_neuron_counts: 上一层神经元数
:param current_layer_neuron_counts: 当前层神经元数
:return: 初始化后的权重维度为[当前层神经元数上一层神经元数]
"""
weight = numpy.random.randn(
current_layer_neuron_counts, previous_layer_neuron_counts
)
match activate:
case "relu":
return weight * numpy.sqrt(
2 / previous_layer_neuron_counts
) # 使用He初始化权重方法
case "linear":
return weight * numpy.sqrt(
2 / previous_layer_neuron_counts
) # 使用He初始化权重方法
case "softmax":
return weight * numpy.sqrt(
2 / (previous_layer_neuron_counts + current_layer_neuron_counts)
) # 使用Xavier初始化权重方法
def train(
self,
X: numpy.ndarray,
@ -145,7 +96,10 @@ class NeuralNetwork:
raise RuntimeError(
"输入和真实输出应为数组,其中输入维度应为[输入神经元数, 样本数],真实输出维度应为[输出神经元数, 样本数],样本数应需相同"
)
# 默认为训练模式
self.training = True
# 初始化神经网络参数
self._init_parameters()
# 归一化输入
self.parameters[0].update({"activation": self._normalize(input=X)})
@ -174,12 +128,80 @@ class NeuralNetwork:
epoch += 1
try:
with open("neural_network.pkl", "wb") as file:
pickle.dump(self, file, protocol=pickle.HIGHEST_PROTOCOL)
print(f"模型保存成功")
except Exception as exception:
raise RuntimeError(f"模型保存失败:{str(exception)}") from exception
# 保存神经网络参数
self._save_parameters()
def _init_parameters(self) -> None:
"""
初始化神经网络参数
:return:
"""
self.parameters = {0: {}}
# 初始化神经网络参数
for layer_index in range(1, self.layer_counts + 1):
self.parameters[layer_index] = {
"activate": (
activate := (
self.output_activate
if layer_index == self.layer_counts
else self.hidden_activate
)
), # 激活函数
"weight": self._init_weight(
activate=activate,
previous_layer_neuron_counts=self.structure[layer_index - 1],
current_layer_neuron_counts=(
current_layer_neuron_counts := self.structure[layer_index]
),
), # 权重,维度为[当前层神经元数,上一层神经元数],适配加权输入=权重*输入+平移
"bias": self._init_bias(
current_layer_neuron_counts=current_layer_neuron_counts
), # 平移
}
def _init_weight(
self,
activate: str,
previous_layer_neuron_counts: int,
current_layer_neuron_counts: int,
) -> numpy.ndarray: # pyright: ignore[reportReturnType]
"""
初始化权重
:param activate: 激活函数
:param previous_layer_neuron_counts: 上一层神经元数
:param current_layer_neuron_counts: 当前层神经元数
:return: 初始化后的权重维度为[当前层神经元数上一层神经元数]
"""
# 设置随机种子
numpy.random.seed(self.seed)
# 基于正态分布生成权重
weight = numpy.random.randn(
current_layer_neuron_counts, previous_layer_neuron_counts
)
match activate:
case "relu":
return weight * numpy.sqrt(
2 / previous_layer_neuron_counts
) # 使用He初始化权重方法
case "linear":
return weight * numpy.sqrt(
2 / previous_layer_neuron_counts
) # 使用He初始化权重方法
case "softmax":
return weight * numpy.sqrt(
2 / (previous_layer_neuron_counts + current_layer_neuron_counts)
) # 使用Xavier初始化权重方法
def _init_bias(
self,
current_layer_neuron_counts: int,
) -> numpy.ndarray:
"""
初始化平移
:param current_layer_neuron_counts: 当前层神经元数
:return: 初始化后的平移维度为[当前层神经元数, 1]
"""
return numpy.zeros((current_layer_neuron_counts, 1))
def _normalize(
self,
@ -206,7 +228,7 @@ class NeuralNetwork:
def _forward_propagate(self) -> None:
"""
前向传播
:return: 输出层的输出预测维度为[输出神经元数, 样本数]
:return: 输出层的预测输出维度为[输出神经元数, 样本数]
"""
for layer_index in range(1, self.layer_counts + 1):
self.parameters[layer_index].update(
@ -229,9 +251,9 @@ class NeuralNetwork:
def _activate(
self,
activate: Literal["relu", "linear", "softmax"],
activate: str,
input: numpy.ndarray,
) -> numpy.ndarray:
) -> numpy.ndarray: # pyright: ignore[reportReturnType]
"""
激活
:param activate: 激活函数
@ -385,23 +407,66 @@ class NeuralNetwork:
}
)
def _reason(self, input: numpy.ndarray) -> numpy.ndarray:
def _save_parameters(self) -> None:
"""
保存神经网络参数
:return:
"""
with open("parameters.pkl", "wb") as file:
pickle.dump(
obj=self.parameters,
file=file,
protocol=pickle.HIGHEST_PROTOCOL,
)
def reason(self, X: numpy.ndarray) -> numpy.ndarray:
"""
推理
:param input: 输入维度为[输入神经元数, 样本数]
:return: 输出维度为[输出神经元数, 样本数]
:param X: 输入维度为[输入神经元数, 样本数]
:return: 预测输出维度为[输出神经元数, 样本数]
"""
print(f"基于已训练神经网络进行推理...")
if not (
X.shape[0] == self.structure[0] if isinstance(X, numpy.ndarray) else False
):
raise RuntimeError("输入应为数组,输入维度应为[输入神经元数, 样本数]")
# 默认为推理模式
self.training = False
# 加载神经网络参数
self._load_parameters()
# 归一化输入
self.parameters[0].update({"activation": self._normalize(input=input)})
self.parameters[0].update({"activation": self._normalize(input=X)})
return self._forward_propagate()
# 前向传播
self._forward_propagate()
return self.parameters[self.layer_counts]["activation"]
def _load_parameters(self) -> None:
"""
加载神经网络参数
:return:
"""
with open("parameters.pkl", "rb") as file:
self.parameters = pickle.load(file=file)
# 校验神经网络参数
for layer_index in range(1, self.layer_counts + 1):
if not (
self.parameters[layer_index]["weight"].shape
== (self.structure[layer_index], self.structure[layer_index - 1])
and self.parameters[layer_index]["bias"].shape
== (self.structure[layer_index], 1)
if isinstance(self.parameters[layer_index]["weight"], numpy.ndarray)
and isinstance(self.parameters[layer_index]["bias"], numpy.ndarray)
else False
):
raise RuntimeError("神经网络参数中权重和偏置的维度与神经网络结构不匹配")
# 测试代码
if __name__ == "__main__":
X = numpy.random.randn(2, 100)
X = numpy.random.randn(2, 5000)
# 真实函数y = 2*x1 + 3*x2 + 1
y_true = 2 * X[0:1, :] ** 2 + 3 * X[1:2, :] + 1
@ -412,5 +477,9 @@ if __name__ == "__main__":
# 训练
neural_network.train(
X=X, y_true=y_true, target_loss=0.1, epochs=1_000, learning_rate=0.05
X=X, y_true=y_true, target_loss=0.01, epochs=1_000, learning_rate=0.1
)
print(f"推理结果:{y_true[:, 0:5]}")
print(f"推理结果:{neural_network.reason(X=X)[:, 0:5]}")