如何用 NumPy 实现人工神经网络
传统的人工神经网络(Neural Network)是一个三层模型。这里借助矩阵运算实现一个最最传统的神经网络模型。这里作为演示。
说明
- 多分类模型,最终的输出为标签的编号。
- 每行为一个输入,不同于传统写法是每列,所以这里矩阵采用右乘。
- 用 numpy 进行矩阵运算。
- 使用
scipy.optimize.minimize进行优化。 - 为了显示出效果,这里测试集划分得比较大。
- 损失函数用交叉熵还是均方差都是可以的。
- 可以打印学习过程中损失函数的变化。
- 这里使用准确率作为评价指标。
import numpy as np
from scipy.optimize import minimize
from scipy.special import expit
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
class NeuralNetwork:
def __init__(self, input_layer_size, hidden_layer_size, num_labels,):
self.input_layer_size = input_layer_size
self.hidden_layer_size = hidden_layer_size
self.num_labels = num_labels
self.theta1 = np.random.randn(input_layer_size + 1, hidden_layer_size) * 0.1
self.theta2 = np.random.randn(hidden_layer_size + 1, num_labels) * 0.1
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def forward(self, X):
m = X.shape[0]
X = np.hstack((np.ones((m, 1)), X))
z2 = np.dot(X, self.theta1)
a2 = self.sigmoid(z2)
a2 = np.hstack((np.ones((m, 1)), a2))
z3 = np.dot(a2, self.theta2)
h = self.sigmoid(z3)
return h
def cost_function(self, nn_params, X, y):
self.theta1 = nn_params[:self.hidden_layer_size * (self.input_layer_size + 1)].reshape(
self.hidden_layer_size, self.input_layer_size + 1)
self.theta2 = nn_params[self.hidden_layer_size * (self.input_layer_size + 1):].reshape(
self.num_labels, self.hidden_layer_size + 1)
m = X.shape[0]
h = self.forward(X)
y_one_hot = np.eye(self.num_labels)[y]
J = (-1 / m) * np.sum(y_one_hot * np.log(h) + (1 - y_one_hot) * np.log(1 - h))
# print(J)
return J
def train(self, X, y):
initial_nn_params = np.concatenate((self.theta1.flatten(), self.theta2.flatten()))
options = {'maxiter': 100}
result = minimize(self.cost_function, initial_nn_params, args=(X, y), method='CG', options=options)
nn_params = result.x
self.theta1 = nn_params[:self.hidden_layer_size * (self.input_layer_size + 1)].reshape(
self.input_layer_size + 1, self.hidden_layer_size)
self.theta2 = nn_params[self.hidden_layer_size * (self.input_layer_size + 1):].reshape(
self.hidden_layer_size + 1, self.num_labels)
def predict(self, X):
predictions = self.forward(X)
return np.argmax(predictions, axis=1)
# 加载 Iris 数据集
iris = load_iris()
X = iris.data
y = iris.target
# 数据标准化(可选,预处理,好像这里没起作用)
scaler = StandardScaler()
X = scaler.fit_transform(X)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5)
# 神经网络参数设置
input_layer_size = 4
hidden_layer_size = 5
num_labels = 3
# 创建神经网络实例
nn = NeuralNetwork(input_layer_size, hidden_layer_size, num_labels)
# 训练神经网络
nn.train(X_train, y_train)
# 在测试集上进行预测
predicted_labels = nn.predict(X_test)
# 计算准确率
accuracy = np.mean(predicted_labels == y_test)
print(f"测试集准确率: {accuracy * 100:.2f}%")
附录
用 pytorch 重写以上代码
说明
- 对应以上代码,把 numpy + scipy 转换为 pytorch。
- 深度学习中可以利用更复杂的网络结构和更多的训练数据。
- 也可以用 pytorch-lighting 这样的库来简化训练代码。
- 在 pytorch 中不需要显式地写出输出层的 softmax,该层包含在优化器中了。
- 可以尝试随机种子和固定输入样本后手工进行参数的调整。
import torch
import torch.nn as nn
import torch.optim as optim
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# 加载 Iris 数据集
iris = load_iris()
X = iris.data
y = iris.target
数据标准化
scaler = StandardScaler()
X = scaler.fit_transform(X)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5)
# 将数据转换为 PyTorch 张量
X_train = torch.FloatTensor(X_train)
y_train = torch.LongTensor(y_train)
X_test = torch.FloatTensor(X_test)
y_test = torch.LongTensor(y_test)
# 定义神经网络模型
class IrisNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(IrisNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(hidden_size, output_size)
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
return out
# 初始化模型、损失函数和优化器
input_size = 4
hidden_size = 5
output_size = 3
model = IrisNet(input_size, hidden_size, output_size)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)
# 训练模型
num_epochs = 100
for epoch in range(num_epochs):
# 前向传播
outputs = model(X_train)
loss = criterion(outputs, y_train)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 10 == 0:
print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')
# 在测试集上进行评估
with torch.no_grad():
outputs = model(X_test)
_, predicted = torch.max(outputs.data, 1)
accuracy = (predicted == y_test).sum().item() / y_test.size(0)
print(f'测试集准确率: {accuracy * 100:.2f}%')
结果的可视化
通过对模型的可视化,可以一种直观的评价的方式,可以感受结果的好坏,并且补充了一些细节层面包含的影响。
对于分类模型,可以使用一下图表类型
- 混淆矩阵,不同样本上的指标
- 精准召回曲线,在不同阈值下的对应。
- 散点聚类图