import random
import struct
import numpy as np
import pandas as pd
def load_labels(file):
with open(file, "rb") as f:
data = f.read()
magic_number, num_samples = struct.unpack(">ii", data[:8])
if magic_number != 2049: # 0x00000801
print(f"magic number mismatch {magic_number} != 2049")
return None
labels = np.array(list(data[8:]))
return labels
def load_images(file):
with open(file, "rb") as f:
data = f.read()
magic_number, num_samples, image_width, image_height = struct.unpack(">iiii", data[:16])
if magic_number != 2051: # 0x00000803
print(f"magic number mismatch {magic_number} != 2051")
return None
image_data = np.asarray(list(data[16:]), dtype=np.uint8).reshape(num_samples, -1)
return image_data
def one_hot(labels, classes):
n = len(labels)
output = np.zeros((n, classes), dtype=np.int32)
for row, label in enumerate(labels):
output[row, label] = 1
return output
class Dataset:
def __init__(self, images, labels):
self.images = images
self.labels = labels
# 获取他的一个item, dataset = Dataset(), dataset[index]
def __getitem__(self, index):
return self.images[index], self.labels[index]
# 获取数据集的长度,个数
def __len__(self):
return len(self.images)
class DataLoaderIterator:
def __init__(self, dataloader):
self.dataloader = dataloader
self.cursor = 0
self.indexs = list(range(self.dataloader.count_data)) # 0, ... 60000
if self.dataloader.shuffle:
# 打乱一下
random.shuffle(self.indexs)
# 合并batch的数据
def merge_to(self, container, b):
if len(container) == 0:
for index, data in enumerate(b):
if isinstance(data, np.ndarray):
container.append(data)
else:
container.append(np.array([data], dtype=type(data)))
else:
for index, data in enumerate(b):
container[index] = np.vstack((container[index], data))
return container
def __next__(self):
if self.cursor >= self.dataloader.count_data:
raise StopIteration()
batch_data = []
remain = min(self.dataloader.batch_size, self.dataloader.count_data - self.cursor) # 256, 128
for n in range(remain):
index = self.indexs[self.cursor]
data = self.dataloader.dataset[index]
batch_data = self.merge_to(batch_data, data)
self.cursor += 1
return batch_data
class DataLoader:
# shuffle 打乱
def __init__(self, dataset, batch_size, shuffle):
self.dataset = dataset
self.shuffle = shuffle
self.count_data = len(dataset)
self.batch_size = batch_size
def __iter__(self):
return DataLoaderIterator(self)
# val_labels = load_labels("E:/杜老师课程/dataset/t10k-labels-idx1-ubyte") # 10000,
# val_images = load_images("E:/杜老师课程/dataset/t10k-images-idx3-ubyte") # 10000, 784
# numdata = val_images.shape[0] # 60000
# val_images = np.hstack((val_images / 255 - 0.5, np.ones((numdata, 1)))) # 10000, 785
# val_pd = pd.DataFrame(val_labels, columns=["label"])
class Module:
def __init__(self, name):
self.name = name
def __call__(self, *args):
return self.forward(*args)
class Initializer:
def __init__(self, name):
self.name = name
def __call__(self, *args):
return self.apply(*args)
class Parameter:
def __init__(self, value):
self.value = value
self.delta = np.zeros(value.shape)
def zero_grad(self):
self.delta[...] = 0
class LinearLayer(Module):
def __init__(self, input_feature, output_feature):
super().__init__("Linear")
self.input_feature = input_feature
self.output_feature = output_feature
self.weights = Parameter(np.zeros((input_feature, output_feature)))
self.bais = Parameter(np.zeros((1, output_feature)))
# 权重初始化
initer = GaussInitializer(0, 1.0)
initer.apply(self.weights.value)
def forward(self, x):
self.x_save = x.copy()
return x @ self.weights.value + self.bais.value
# AB=C G
# dB = A.T @ G
# dA = G @ B.T
def backward(self, G):
self.weights.delta = self.x_save.T @ G
self.bais.delta[...] = np.sum(G, 0) # 值复制
return G @ self.weights.value.T
class GaussInitializer(Initializer):
# where :math:`\mu` is the mean and :math:`\sigma` the standard
# deviation. The square of the standard deviation, :math:`\sigma^2`,
# is called the variance.
def __init__(self, mu, sigma):
self.mu = mu
self.sigma = sigma
def apply(self, value):
value[...] = np.random.normal(self.mu, self.sigma, value.shape)
class ReLULayer(Module):
def __init__(self, inplace=True):
super().__init__("ReLU")
self.inplace = inplace
def forward(self, x):
self.negative_position = x < 0
if not self.inplace:
x = x.copy()
x[self.negative_position] = 0
return x
def backward(self, G):
if not self.inplace:
G = G.copy()
G[self.negative_position] = 0
return G
class SigmoidCrossEntropyLayer(Module):
def __init__(self):
super().__init__("CrossEntropyLoss")
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def forward(self, x, label_onehot):
eps = 1e-4
self.label_onehot = label_onehot
self.predict = self.sigmoid(x)
self.predict = np.clip(self.predict, a_max=1 - eps, a_min=eps) # 裁切
self.batch_size = self.predict.shape[0]
return 1
def backward(self):
return (self.predict - self.label_onehot) / self.batch_size
class SoftmaxCrossEntropyLayer(Module):
def __init__(self):
super().__init__("CrossEntropyLoss")
def softmax(self, x):
return np.exp(x) / np.sum(np.exp(x), axis=1, keepdims=True)
def forward(self, x, label_onehot):
eps = 1e-4
self.label_onehot = label_onehot
self.predict = self.softmax(x)
self.predict = np.clip(self.predict, a_max=1 - eps, a_min=eps) # 裁切
self.batch_size = self.predict.shape[0]
return -np.sum(label_onehot * np.log(self.predict) + (1 - label_onehot) *
np.log(1 - self.predict)) / self.batch_size
def backward(self):
return (self.predict - self.label_onehot) / self.batch_size
class Model(Module):
def __init__(self, num_feature, num_hidden, num_classes):
super().__init__("Model")
self.input_to_hidden = LinearLayer(num_feature, num_hidden)
self.relu = ReLULayer()
self.hidden_to_output = LinearLayer(num_hidden, num_classes)
def forward(self, x):
x = self.input_to_hidden(x)
x = self.relu(x)
x = self.hidden_to_output(x)
return x
def backward(self, G):
G = self.hidden_to_output.backward(G)
G = self.relu.backward(G)
G = self.input_to_hidden.backward(G)
return G
class Optimizer:
def __init__(self, name, model, lr):
self.name = name
self.model = model
self.lr = lr
layers = []
self.params = []
for attr in model.__dict__:
layer = model.__dict__[attr]
if isinstance(layer, Module):
layers.append(layer)
for layer in layers:
for attr in layer.__dict__:
layer_param = layer.__dict__[attr]
if isinstance(layer_param, Parameter):
self.params.append(layer_param)
def zero_grad(self):
for param in self.params:
param.zero_grad()
def set_lr(self, lr):
self.lr = lr
class Adam(Optimizer):
def __init__(self, model, lr=1e-3, beta1=0.9, beta2=0.999, momentum=0.1):
super().__init__("Adam", model, lr)
self.momentum = momentum
self.beta1 = beta1
self.beta2 = beta2
self.t = 0
for param in self.params:
param.m = 0
param.v = 0
def step(self):
eps = 1e-8
self.t += 1
for param in self.params:
g = param.delta
param.m = self.beta1 * param.m + (1 - self.beta1) * g
param.v = self.beta2 * param.v + (1 - self.beta2) * g ** 2
mt_ = param.m / (1 - self.beta1 ** self.t)
vt_ = param.v / (1 - self.beta2 ** self.t)
param.value -= self.lr * mt_ / (np.sqrt(vt_) + eps)
def estimate_val(predict, gt_labels, classes, loss_func):
plabel = predict.argmax(1)
positive = plabel == val_labels
total_images = predict.shape[0]
accuracy = sum(positive) / total_images
return accuracy, loss_func(predict, one_hot(gt_labels, classes))
val_labels = load_labels("E:/杜老师课程/dataset/t10k-labels-idx1-ubyte") # 10000,
val_images = load_images("E:/杜老师课程/dataset/t10k-images-idx3-ubyte") # 10000, 784
numdata = val_images.shape[0] # 60000
val_images = np.hstack((val_images / 255 - 0.5, np.ones((numdata, 1)))) # 10000, 785
val_pd = pd.DataFrame(val_labels, columns=["label"])
train_labels = load_labels("E:/杜老师课程/dataset/train-labels-idx1-ubyte") # 60000,
train_images = load_images("E:/杜老师课程/dataset/train-images-idx3-ubyte") # 60000, 784
numdata = train_images.shape[0] # 60000
train_images = np.hstack((train_images / 255 - 0.5, np.ones((numdata, 1)))) # 60000, 785
train_pd = pd.DataFrame(train_labels, columns=["label"])
classes = 10 # 定义10个类别
batch_size = 32 # 定义每个批次的大小
epochs = 20 # 退出策略,也就是最大把所有数据看10次
lr = 1e-3
train_data = DataLoader(Dataset(train_images,one_hot(train_labels,classes)),batch_size,shuffle=True)
model = Model(train_images.shape[1],256,classes)
loss_func = SigmoidCrossEntropyLayer()
optim = Adam(model,lr)
iters = 0
for epoch in range(epochs):
for index,(image,label) in enumerate(train_data):
predict = model(image)
loss = loss_func(predict,label)
# optim.zero_grad()
G = loss_func.backward()
model.backward(G)
optim.step()
iters += 1
if iters % 1000 == 0:
print(f"Iter {iters}, {epoch} / {epochs}, Loss {loss:.3f}, LR {lr:g}")
val_accuracy, val_loss = estimate_val(model(val_images), val_labels, classes, loss_func)
print(f"Val set, Accuracy: {val_accuracy:.3f}, Loss: {val_loss:.3f}")