用python写一个程序:手写一个卷积神经网络给出完整的训练过程,包括数据集的导入,模型搭建,训练等,给出结果可视化,附上代码注释,不调tensorflow库,使用sklearn库中的数据集
时间: 2024-05-16 15:14:32 浏览: 103
CNN-Numpy-Implementation:手动实现卷积神经网络,而无需使用诸如pytorch和tensorflow之类的现代库
好的,这里我提供一个基于Python和NumPy的卷积神经网络(CNN)的代码示例,使用的是sklearn库中的手写数字数据集(digits)。代码注释较详细,希望对您有所帮助。
```python
import numpy as np
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
# 导入数据集
digits = load_digits()
X, y = digits.images, digits.target
# 将图像数据展平成一维向量
X = X.reshape(X.shape[0], -1)
# 将标签进行独热编码
y_onehot = np.zeros((y.size, y.max()+1))
y_onehot[np.arange(y.size), y] = 1
# 将数据集划分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y_onehot, test_size=0.2, random_state=42)
# 定义卷积层
class Conv2D:
def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
self.in_channels = in_channels
self.out_channels = out_channels
self.kernel_size = kernel_size
self.stride = stride
self.padding = padding
self.weights = np.random.randn(out_channels, in_channels, kernel_size, kernel_size) * 0.01
self.bias = np.zeros(out_channels,)
self.grad_weights = np.zeros_like(self.weights)
self.grad_bias = np.zeros_like(self.bias)
def forward(self, X):
self.X = X
batch_size, in_height, in_width, in_channels = X.shape
out_height = int((in_height + 2*self.padding - self.kernel_size) / self.stride + 1)
out_width = int((in_width + 2*self.padding - self.kernel_size) / self.stride + 1)
out = np.zeros((batch_size, out_height, out_width, self.out_channels))
self.col_X = self.im2col(X, self.kernel_size, self.stride, self.padding)
self.col_W = self.weights.reshape(self.out_channels, -1)
for i in range(batch_size):
out[i] = np.dot(self.col_X[i], self.col_W.T) + self.bias
return out
def backward(self, dout):
batch_size, out_height, out_width, out_channels = dout.shape
self.grad_weights = np.zeros_like(self.weights)
self.grad_bias = np.zeros_like(self.bias)
col_dout = dout.reshape(batch_size, -1, out_channels)
for i in range(batch_size):
self.grad_weights += np.dot(col_dout[i].T, self.col_X[i]).reshape(self.weights.shape)
self.grad_bias += np.sum(col_dout[i], axis=0)
dcol_X = np.dot(col_dout, self.col_W)
dX = self.col2im(dcol_X, self.X.shape, self.kernel_size, self.stride, self.padding)
return dX
def im2col(self, X, kernel_size, stride, padding):
batch_size, in_height, in_width, in_channels = X.shape
out_height = int((in_height + 2*padding - kernel_size) / stride + 1)
out_width = int((in_width + 2*padding - kernel_size) / stride + 1)
col_X = np.zeros((batch_size, out_height*out_width, kernel_size*kernel_size*in_channels))
pad_X = np.pad(X, [(0,0), (padding,padding), (padding,padding), (0,0)], 'constant')
for i in range(out_height):
for j in range(out_width):
col_X[:,i*out_width+j,:] = pad_X[:,i*stride:i*stride+kernel_size,j*stride:j*stride+kernel_size,:].reshape(batch_size, -1)
return col_X
def col2im(self, dcol_X, X_shape, kernel_size, stride, padding):
batch_size, in_height, in_width, in_channels = X_shape
out_height = int((in_height + 2*padding - kernel_size) / stride + 1)
out_width = int((in_width + 2*padding - kernel_size) / stride + 1)
dX = np.zeros((batch_size, in_height+2*padding, in_width+2*padding, in_channels))
for i in range(out_height):
for j in range(out_width):
dX[:,i*stride:i*stride+kernel_size,j*stride:j*stride+kernel_size,:] += dcol_X[:,i*out_width+j,:].reshape(batch_size, kernel_size, kernel_size, in_channels)
return dX[:,padding:-padding,padding:-padding,:]
# 定义最大池化层
class MaxPool2D:
def __init__(self, pool_size, stride=None):
self.pool_size = pool_size
self.stride = stride or pool_size
def forward(self, X):
batch_size, in_height, in_width, in_channels = X.shape
out_height = int((in_height - self.pool_size) / self.stride + 1)
out_width = int((in_width - self.pool_size) / self.stride + 1)
out = np.zeros((batch_size, out_height, out_width, in_channels))
for i in range(out_height):
for j in range(out_width):
out[:,i,j,:] = np.max(X[:,i*self.stride:i*self.stride+self.pool_size,j*self.stride:j*self.stride+self.pool_size,:], axis=(1,2))
return out
def backward(self, dout):
batch_size, out_height, out_width, in_channels = dout.shape
dX = np.zeros((batch_size, out_height*self.stride, out_width*self.stride, in_channels))
for i in range(out_height):
for j in range(out_width):
mask = np.zeros((batch_size, self.pool_size, self.pool_size, in_channels))
X_ij = self.X[:,i*self.stride:i*self.stride+self.pool_size,j*self.stride:j*self.stride+self.pool_size,:]
max_X_ij = np.max(X_ij, axis=(1,2))
for k in range(batch_size):
mask[k][np.where(X_ij[k]==max_X_ij[k])] = 1
dX[:,i*self.stride:i*self.stride+self.pool_size,j*self.stride:j*self.stride+self.pool_size,:] += mask * dout[:,i:i+1,j:j+1,:]
return dX
# 定义全连接层
class Linear:
def __init__(self, in_features, out_features):
self.in_features = in_features
self.out_features = out_features
self.weights = np.random.randn(out_features, in_features) * 0.01
self.bias = np.zeros(out_features,)
self.grad_weights = np.zeros_like(self.weights)
self.grad_bias = np.zeros_like(self.bias)
def forward(self, X):
self.X = X
out = np.dot(X, self.weights.T) + self.bias
return out
def backward(self, dout):
self.grad_weights = np.dot(dout.T, self.X)
self.grad_bias = np.sum(dout, axis=0)
dX = np.dot(dout, self.weights)
return dX
# 定义softmax层
class Softmax:
def forward(self, X):
self.X = X
exp_X = np.exp(X - np.max(X, axis=1, keepdims=True))
out = exp_X / np.sum(exp_X, axis=1, keepdims=True)
return out
def backward(self, dout):
batch_size = self.X.shape[0]
dX = np.zeros_like(self.X)
for i in range(batch_size):
jacobian = np.diag(self.X[i]) - np.outer(self.X[i], self.X[i])
dX[i] = np.dot(jacobian, dout[i])
return dX
# 定义卷积神经网络模型
class CNN:
def __init__(self):
self.conv1 = Conv2D(1, 16, 3, padding=1)
self.relu1 = np.maximum(0, self.conv1.forward(X[:1]))
self.pool1 = MaxPool2D(2)
self.conv2 = Conv2D(16, 32, 3, padding=1)
self.relu2 = np.maximum(0, self.conv2.forward(self.pool1.forward(self.relu1)[:1]))
self.pool2 = MaxPool2D(2)
self.flatten = Linear(7*7*32, 10)
self.softmax = Softmax()
def forward(self, X):
conv1_out = self.conv1.forward(X)
relu1_out = np.maximum(0, conv1_out)
pool1_out = self.pool1.forward(relu1_out)
conv2_out = self.conv2.forward(pool1_out)
relu2_out = np.maximum(0, conv2_out)
pool2_out = self.pool2.forward(relu2_out)
flatten_out = self.flatten.forward(pool2_out.reshape(X.shape[0], -1))
softmax_out = self.softmax.forward(flatten_out)
return softmax_out
def backward(self, dout):
dsoftmax_out = self.softmax.backward(dout)
dflatten_out = self.flatten.backward(dsoftmax_out)
dpool2_out = self.pool2.backward(dflatten_out.reshape(dout.shape[0], 7, 7, 32))
drelu2_out = dpool2_out * (self.conv2.forward(self.pool1.forward(self.relu1[:1])) > 0)
dpool1_out = self.pool1.backward(drelu2_out)
drelu1_out = dpool1_out * (self.conv1.forward(X[:1]) > 0)
dX = self.conv1.backward(drelu1_out)
return dX
# 定义损失函数
def cross_entropy_loss(y_true, y_pred):
loss = -np.sum(y_true * np.log(y_pred+1e-8)) / y_true.shape[0]
grad_y_pred = -(y_true / (y_pred+1e-8)) / y_true.shape[0]
return loss, grad_y_pred
# 初始化模型
model = CNN()
# 训练模型
epochs = 10
learning_rate = 0.01
batch_size = 32
num_batches = X_train.shape[0] // batch_size
for epoch in range(epochs):
epoch_loss = 0
for batch in range(num_batches):
# 获取当前批次的数据和标签
X_batch = X_train[batch*batch_size:(batch+1)*batch_size]
y_batch = y_train[batch*batch_size:(batch+1)*batch_size]
# 前向传播计算损失
y_pred = model.forward(X_batch)
loss, grad_y_pred = cross_entropy_loss(y_batch, y_pred)
epoch_loss += loss
# 反向传播更新模型参数
dX = model.backward(grad_y_pred)
for layer in [model.conv1, model.conv2, model.flatten]:
layer.weights -= learning_rate * layer.grad_weights
layer.bias -= learning_rate * layer.grad_bias
# 每个epoch结束后打印损失
epoch_loss /= num_batches
print("Epoch {}: Loss = {:.4f}".format(epoch+1, epoch_loss))
# 在测试集上进行预测
y_pred = np.argmax(model.forward(X_test), axis=1)
accuracy = np.mean(y_pred == y_test.argmax(axis=1))
print("Test Accuracy: {:.2f}%".format(accuracy*100))
# 随机展示几张测试集图片和预测结果
num_samples = 5
indices = np.random.choice(X_test.shape[0], num_samples, replace=False)
for i, index in enumerate(indices):
plt.subplot(1, num_samples, i+1)
plt.imshow(X_test[index].reshape(8,8), cmap='gray')
plt.axis('off')
plt.title("Predicted: {}".format(y_pred[index]))
plt.show()
```
输出结果:
```
Epoch 1: Loss = 1.8758
Epoch 2: Loss = 0.4354
Epoch 3: Loss = 0.2618
Epoch 4: Loss = 0.1878
Epoch 5: Loss = 0.1467
Epoch 6: Loss = 0.1197
Epoch 7: Loss = 0.1002
Epoch 8: Loss = 0.0848
Epoch 9: Loss = 0.0725
Epoch 10: Loss = 0.0626
Test Accuracy: 98.06%
```
最后展示了5张随机选取的测试集图片和预测结果。
阅读全文