一、分类问题标签表示方法:以不同的整数表示不同的类别,如以y ∈ {1, 2, 3}分别代表{狗, 猫, 鸡};独热编码(one‐hot encoding):独热编码是一个向量,它的分量和类别一样多。类别对 应的分量设置为1,其他所有分量设置为0。如标签定义y为三维向量,其中(1, 0, 0)对应于 “猫”、(0, 1, 0)对应于“鸡”、(0, 0, 1)对应于“狗”。二、网络架构仿射函数:数学和机器学习中的基础概念,它描述了输入与输出之间的线性关系,同时允许存在平移(截距项)。为了解决线性模型的分类问题,定义与输出一样多的仿射函数,即每个输出对应一个仿射函 数。如有4个特征和3个可能的输出类别,利用仿射函数可表达为:可简化为向量形式表达式:o = Wx + b。与线性回归一样,softmax回归也是一个单层神经网络。 由于计算每个输出o1、o2和o3取决于所有输入x1、x2、x3和x4,所以softmax回归的输出层也是全连接层。三、softmax运算softmax函数:对每个未规范化的预测求幂,再让每个求幂后的结果除以它们的总和,即:从以上定义可以看出,softmax函数能够将未规范化的预测变换为非负数并且总和为1,同时让模型保持可导的性质。softmax运算不会改变未规范化的预测o之间的大小次序,只会确定分配给每个类别的概率。尽管softmax是一个非线性函数,但softmax回归的输出仍然由输入特征的仿射变换决定。因此,softmax回 归是一个线性模型(linear model)。softmax函数的导数及推导:四、交叉熵损失函数Cross-entropy(交叉熵损失函数) 交叉熵是用来评估当前训练得到的概率分布与真实分布的差异情况。 它刻画的是实际输出(概率)与期望输出(概率)的距离,也就是交叉熵的值越小,两个概率分布就越接近。常作为分类问题的损失函数。具体公式如下:其中,y_j是一个表示真实标签的长度为q的独热编码向量,即除了第j项为1其余项均为0;y ̂_j表示第j项的预测的概率值。交叉熵损失函数的导数及推导:五、分类问题采用交叉熵而不是均方差的为损失函数的解释交叉熵损失在分类任务中因其梯度高效、概率解释性强和优化稳定性,成为比 MSE 更优的选择。六、softmax函数上溢的解决方案在softmax计算之前,先从所有x_k中减去max(x_k):在减法和规范化步骤之后,可能有些x_i − max(x)具有较大的负值,exp(x_i − max(x))将有接近零的值而出现下溢(underflow),进而导致ˆy_i为零,并且使得log(ˆy_i )的值为-inf。反向传播几步后,我们可能会发现自己面对一屏幕可怕的nan结果。尽管我们要计算指数函数,但我们最终在计算交叉熵损失时会取它们的对数。通过将softmax和交叉熵结合在一起,可以避免反向传播过程中可能会困扰我们的数值稳定性问题。七、softmax分类的实现import torchfrom torch.utils import dataimport torchvisionfrom torchvision import transformsimport matplotlib.pyplot as pltimport typingfrom matplotlib_inline import backend_inlinefrom IPython import displayimport plotShow# 定义一个累加器,实现对n个变量进行累加class Accumulator: ''' 在n个变量上累加 ''' def __init__(self, n:int): self.data = [0.0]*n def add(self, *args): self.data = [a+float(b) for a, b in zip(self.data, args)] def reset(self): self.data = [0.0]*len(self.data) def __getitem__(self, idx): return self.data[idx]class Animator: ''' 在动画中绘制数据 ''' def __init__(self, xlabel=None, ylabel=None, legend=None, xlim=None, ylim=None, xscale='linear', yscale='linear', fmts=('-', 'm--', 'g-.', 'r:'), nrows=1, ncols=1, figsize=(3.5,2.5)): # 增量地绘制多条线条 if legend is None: legend = [] backend_inline.set_matplotlib_formats('svg') self.fig, self.axes = plt.subplots(nrows, ncols, figsize=figsize) if nrows*ncols==1: self.axes = [self.axes, ] # 使用lambda函数捕获参数 self.configAxes = lambda: plotShow.setAxes(self.axes[0], xlabel, ylabel, xlim, ylim, xscale, yscale, legend) self.X, self.Y, self.fmts = None, None, fmts def add(self, x, y): # 向图表中添加多个数据点 if not hasattr(y, '__len__'): y = [y] n = len(y) if not hasattr(x, '__len__'): x = [x]*n if not self.X: self.X = [[] for _ in range(n)] if not self.Y: self.Y = [[] for _ in range(n)] for i, (a, b) in enumerate(zip(x, y)): if a is not None and b is not None: self.X[i].append(a) self.Y[i].append(b) self.axes[0].cla() for x, y, fmt in zip(self.X, self.Y, self.fmts): self.axes[0].plot(x, y, fmt) self.configAxes() display.display(self.fig) plt.draw() plt.pause(0.001) display.clear_output(wait=True) def show(self): display.display(self.fig)def getFashionMnistLabels(labels:typing.Sequence): ''' 返回FashionMnist数据集的文本标签 ''' textLabels = ['t-shirt', 'trouser', 'pullever', 'dress', 'coat', 'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot'] return [textLabels[int(i)] for i in labels]def showImages(imgs:list, numRows:int, numCols:int, titles:list=None, scale:float=1.5): figsize = (numCols*scale, numRows*scale) _, axes = plt.subplots(numRows, numCols, figsize=figsize) axes = axes.flatten() for i, (ax, img) in enumerate(zip(axes, imgs)): if torch.is_tensor(img): # 图片张量 ax.imshow(img.numpy()) else: # PIL图片 ax.imshow(img) ax.axes.get_xaxis().set_visible(False) ax.axes.get_yaxis().set_visible(False) if titles: ax.set_title(titles[i]) return axesdef getDataloaderWorkers()->int: ''' 使用4个进程来读取数据 ''' return 4# 读取数据集def loadDataFashionMnist(batchSize:int, resize=None)->tuple: ''' 下载Fashion-MNIST数据集,然后将其加载到内存中 ''' trans = [transforms.ToTensor()] if resize: trans.insert(0, transforms.Resize(resize)) trans = transforms.Compose(trans) mnistTrain = torchvision.datasets.FashionMNIST(root='../data', train=True, transform=trans, download=True) mnistTest = torchvision.datasets.FashionMNIST(root='../data', train=False, transform=trans, download=True) return (data.DataLoader(mnistTrain, batchSize, shuffle=True, num_workers=getDataloaderWorkers()), data.DataLoader(mnistTest, batchSize, shuffle=False, num_workers=getDataloaderWorkers()))# softmax函数def softmax(X:torch.Tensor)->torch.Tensor: ''' 1. 对每个项求幂(使用exp); 2. 对每一行求和(小批量中每个样本是一行), 得到每个样本的规范化常数; 3. 将每一行除以其规范化常数, 确保结果的和为1。 ''' XExp:torch.Tensor = torch.exp(X) partition:torch.Tensor = XExp.sum(1, keepdim=True) return XExp/partition# 定义分类精度def accuracy(yHat:torch.Tensor, y:torch.Tensor)->float: ''' 计算预测正确的数量 ''' if len(yHat.shape)>1 and yHat.shape[1]>1: yHat = yHat.argmax(axis=1) cmp = yHat.type(y.dtype)==y return float(cmp.type(y.dtype).sum())def evaluateAccuracy(net:torch.nn.Module, dataIter:typing.Tuple[torch.Tensor])->float: ''' 计算在指定数据集上模型的精度 ''' if isinstance(net, torch.nn.Module): net.eval() # 将模型设置为评估模式 metric = Accumulator(2) # 统计正确预测数、预测总数 with torch.no_grad(): for X, y in dataIter: metric.add(accuracy(net(X), y), y.numel()) return metric[0]/metric[1]# 定义训练模型def trainEpochCh3(net:torch.nn.Module, trainIter:typing.Tuple[torch.Tensor], loss:typing.Callable[[torch.Tensor, torch.Tensor], torch.Tensor], updater:torch.optim.Optimizer)->typing.Tuple[float]: ''' 训练模型一个周期 ''' # 将模型设置为训练模型 if isinstance(net, torch.nn.Module): net.train() # 训练损失总和、训练准确度总和、样本数 metric = Accumulator(3) for X, y in trainIter: yHat:torch.Tensor = net(X) l:torch.Tensor = loss(yHat, y) if isinstance(updater, torch.optim.Optimizer): # 使用PyTorch内置的优化器和损失函数 updater.zero_grad() l.mean().backward() updater.step() else: # 使用定制的优化器和损失函数 l.sum().backward() updater(X.shape[0]) metric.add(float(l.sum()), accuracy(yHat, y), y.numel()) # 返回训练损失和训练精度 return metric[0]/metric[2], metric[1]/metric[2]def trainCh3(net:torch.nn.Module, trainIter:data.DataLoader, testIter:data.DataLoader, loss:typing.Callable[[torch.Tensor, torch.Tensor], torch.Tensor], numEpochs: int, updater: torch.optim.Optimizer)->None: ''' 训练模型 ''' animator = Animator(xlabel='epoch', xlim=[1, numEpochs], ylim=[0.3,0.9], legend=['train loss', 'train acc', 'test acc']) for epoch in range(numEpochs): trainMetrics = trainEpochCh3(net, trainIter, loss, updater) testAcc = evaluateAccuracy(net, testIter) animator.add(epoch+1, trainMetrics+(testAcc, )) trainLoss, trainAcc = trainMetrics assert trainLoss<0.5, trainLoss assert trainAcc<=1 and trainAcc>0.7, trainAcc assert testAcc<=1 and testAcc>0.7, testAcc# 定义优化算法def SGD(params:list[torch.Tensor], lr: float, batchSize:int): ''' 小批量随机梯度下降法 params: 模型参数集 合 lr: 学习率,确定每一步更新的大小 batchSize: 批量样本大小 ''' with torch.no_grad(): for param in params: param -= lr*param.grad/batchSize param.grad.zero_()# 预测def predictCh3(net, testIter, n=6): ''' 预测标签 ''' for X, y in testIter: break trues = getFashionMnistLabels(y) preds = getFashionMnistLabels(net(X).argmax(axis=1)) titles = [true + '\n' + pred for true, pred in zip(trues, preds)] showImages(X[0:n].reshape((n, 28, 28)), 1, n, titles=titles[0:n]) plt.show()# softmax回归的从零开始实现if __name__ == '__main__': # 1. 读取数据集 batchSize: int = 256 trainIter, testIter = loadDataFashionMnist(batchSize) # 2. 模型参数初始化 numInputs: int = 784 # 原始数据集每个样本是28*28的图像,因此以构造一个长度为784的向量来表示每一个像素点 numOutPuts: int = 10 # 数据集中有10个类别 W = torch.normal(0.0, 0.01, size=(numInputs, numOutPuts), requires_grad=True) # 权重项 b = torch.zeros(numOutPuts, requires_grad=True) # 偏置项 # 3. 定义softmax模型 def net(X:torch.Tensor)->torch.Tensor: return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b) # 4. 定义损失函数 def crossEntropy(yHat:torch.Tensor, y:torch.Tensor)->torch.Tensor: ''' yHat: 预测概率 y: 标签向量 ''' return -torch.log(yHat[range(len(yHat)), y]) # 5. 定义优化函数 lr = 0.1 def updater(batchSize:int): return SGD([W,b], lr, batchSize) # 6. 模型训练 numEpochs:int = 10 trainCh3(net, trainIter, testIter, crossEntropy, numEpochs, updater) # 7. 预测 predictCh3(net, testIter, n=10)# softmax回归的简洁实现if __name__ == '__main__': # 1. 读取数据集 batchSize:int = 256 trainIter, testIter = loadDataFashionMnist(batchSize) # 2. 初始化模型参数 # PyTorch不会隐式地调整输入的形状。因此, # 我们在线性层前定义了展平层(flatten),来调整网络输入的形状 net = torch.nn.Sequential(torch.nn.Flatten(), torch.nn.Linear(784, 10)) def initWeight(m): if type(m)==torch.nn.Linear: torch.nn.init.normal_(m.weight, std=0.01) net.apply(initWeight) # 3. 定义损失函数 loss = torch.nn.CrossEntropyLoss(reduction='none') # 4. 定义优化算法 trainer = torch.optim.SGD(net.parameters(), lr=0.1) # 5. 训练 numEpochs:int = 10 trainCh3(net, trainIter, testIter, loss, numEpochs, trainer)来源:檐苔