This commit is contained in:
parent
6d89ffe182
commit
70431cad4f
Binary file not shown.
211
神经网络/main.py
211
神经网络/main.py
|
|
@ -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]}")
|
||||
|
|
|
|||
Loading…
Reference in New Issue