基于Softmax回归的多分类任务

        Logistic回归可以有效地解决二分类问题,但在分类任务中,还有一类多分类问题,即类别数C大于2 的分类问题。Softmax回归就是Logistic回归在多分类问题上的推广。

        使用Softmax回归模型对一个简单的数据集进行多分类实验。

        首先给大家看一下需要的资源包

代码最后都会放出。

1.数据集的构建

        我们首先构建一个简单的多分类任务,并构建训练集、验证集和测试集。

        本任务的数据来自3个不同的簇,每个簇对一个类别。我们采集1000条样本,每个样本包含2个特征。

        数据集的构建函数make_multiclass_classification的代码实现如下:

def make_multiclass_classification(n_samples=100, n_features=2, n_classes=3, shuffle=True, noise=0.1):"""生成带噪音的多类别数据输入:- n_samples:数据量大小,数据类型为int- n_features:特征数量,数据类型为int- shuffle:是否打乱数据,数据类型为bool- noise:以多大的程度增加噪声,数据类型为None或float,noise为None时表示不增加噪声输出:- X:特征数据,shape=[n_samples,2]- y:标签数据, shape=[n_samples,1]"""# 计算每个类别的样本数量n_samples_per_class = [int(n_samples / n_classes) for k in range(n_classes)]for i in range(n_samples - sum(n_samples_per_class)):n_samples_per_class[i % n_classes] += 1# 将特征和标签初始化为0X = torch.zeros((n_samples, n_features))y = torch.zeros(n_samples, dtype=torch.int32)# 随机生成3个簇中心作为类别中心centroids = torch.randperm(2 ** n_features)[:n_classes]centroids_bin = np.unpackbits(centroids.numpy().astype('uint8')).reshape((-1, 8))[:, -n_features:]centroids = torch.tensor(centroids_bin, dtype=torch.float32)# 控制簇中心的分离程度centroids = 1.5 * centroids - 1# 随机生成特征值X[:, :n_features] = torch.randn((n_samples, n_features))stop = 0# 将每个类的特征值控制在簇中心附近for k, centroid in enumerate(centroids):start, stop = stop, stop + n_samples_per_class[k]# 指定标签值y[start:stop] = k % n_classesX_k = X[start:stop, :n_features]# 控制每个类别特征值的分散程度A = 2 * torch.rand(size=(n_features, n_features)) - 1X_k[...] = torch.matmul(X_k, A)X_k += centroidX[start:stop, :n_features] = X_k# 如果noise不为None,则给特征加入噪声if noise > 0.0:# 生成noise掩膜,用来指定给那些样本加入噪声noise_mask = torch.rand(n_samples) < noisefor i in range(len(noise_mask)):if noise_mask[i]:# 给加噪声的样本随机赋标签值y[i] = torch.randint(n_classes, size=(1,), dtype=torch.int32)# 如果shuffle为True,将所有数据打乱if shuffle:idx = torch.randperm(X.shape[0])X = X[idx]y = y[idx]return X, y

                 随机采集1000个样本,并进行可视化。

# 采样1000个样本
n_samples = 1000
X, y = make_multiclass_classification(n_samples=n_samples, n_features=2, n_classes=3, noise=0.2)# 可视化生产的数据集,不同颜色代表不同类别
plt.figure(figsize=(5,5))
plt.scatter(x=X[:, 0].tolist(), y=X[:, 1].tolist(), marker='*', c=y.tolist())
plt.savefig('linear-dataset-vis2.pdf')
plt.show()

         运行结果如下:

        将实验数据拆分成训练集、验证集和测试集。其中训练集640条、验证集160条、测试集200条。

num_train = 640
num_dev = 160
num_test = 200X_train, y_train = X[:num_train], y[:num_train]
X_dev, y_dev = X[num_train:num_train + num_dev], y[num_train:num_train + num_dev]
X_test, y_test = X[num_train + num_dev:], y[num_train + num_dev:]# 打印X_train和y_train的维度
print("X_train shape: ", X_train.shape, "y_train shape: ", y_train.shape)

        这样,我们就完成了Multi1000数据集的构建。

# 打印前5个数据的标签
print(y_train[:5])

         运行结果如下:

2.模型构建

        在Softmax回归中,对类别进行预测的方式是预测输入属于每个类别的条件概率。与Logistic 回归不同的是,Softmax回归的输出值个数等于类别数C,而每个类别的概率值则通过Softmax函数进行求解。


softmax函数

        Softmax函数可以将多个标量映射为一个概率分布。对于一个$K$维向量,$\mathbf x=[x_1,\cdots,x_K]$,Softmax的计算公式为

\mathrm{softmax}(x_k) = \frac{\exp(x_k)}{\sum_{i=1}^K \exp(x_i)}

在Softmax函数的计算过程中,要注意上溢出下溢出的问题。假设Softmax 函数中所有的$x_k$都是相同大小的数值$a$,理论上,所有的输出都应该为$\frac{1}{k}$。但需要考虑如下两种特殊情况:

  • $a$为一个非常大的负数,此时$\exp(a)$ 会发生下溢出现象。计算机在进行数值计算时,当数值过小,会被四舍五入为0。此时,Softmax函数的分母会变为0,导致计算出现问题;
  • $a$为一个非常大的正数,此时会导致$\exp(a)$发生上溢出现象,导致计算出现问题。

为了解决上溢出和下溢出的问题,在计算Softmax函数时,可以使用$x_k - \max(\mathbf x)$代替$x_k$。 此时,通过减去最大值,$x_k$最大为0,避免了上溢出的问题;同时,分母中至少会包含一个值为1的项,从而也避免了下溢出的问题。 

Softmax函数的代码实现如下(activation.py):

# x为tensor
def softmax(X):"""输入:- X:shape=[N, C],N为向量数量,C为向量维度"""x_max = paddle.max(X, axis=1, keepdim=True)#N,1x_exp = paddle.exp(X - x_max)partition = paddle.sum(x_exp, axis=1, keepdim=True)#N,1return x_exp / partition# 观察softmax的计算方式
X = paddle.to_tensor([[0.1, 0.2, 0.3, 0.4],[1,2,3,4]])
predict = softmax(X)
print(predict)

         运行结果如下:


soft回归算子

在Softmax回归中,类别标签y\epsilon\left \{ 1,2,...,C \right \}。给定一个样本$\mathbf x$,使用Softmax回归预测的属于类别$c$的条件概率为

p(y=c|x)=softmax(w_{c}^{T}x+b_c)

其中w_c是第c类的权重向量,b_c是第c类的偏置。

Softmax回归模型其实就是线性模型与softmax函数的组合。

将N个样本归为一组进行成批的预测。

        \hat{\mathbf Y} = \mathrm{softmax}(\boldsymbol{X} \boldsymbol{W} + \mathbf b)

其中$\boldsymbol{X}\in \mathbb{R}^{N\times D}$为N个样本的特征矩阵,W = \left [ w_1,......,w_c \right ]$C$个类的权重向量组成的矩阵,$\hat{\mathbf Y}\in \mathbb{R}^{C}$为所有类别的预测条件概率组成的矩阵。

我们根据公式(3.13)实现Softmax回归算子,代码实现如下:

class model_SR(Op):def __init__(self, input_dim, output_dim):super(model_SR, self).__init__()self.params = {}#将线性层的权重参数全部初始化为0self.params['W'] = torch.zeros((input_dim, output_dim))#self.params['W'] = paddle.normal(mean=0, std=0.01, shape=[input_dim, output_dim])#将线性层的偏置参数初始化为0self.params['b'] = torch.zeros(output_dim)#存放参数的梯度self.grads = {}self.X = Noneself.outputs = Noneself.output_dim = output_dimdef __call__(self, inputs):return self.forward(inputs)def forward(self, inputs):self.X = inputs#线性计算score = torch.matmul(self.X, self.params['W']) + self.params['b']#Softmax 函数self.outputs = softmax(score)return self.outputs# 随机生成1条长度为4的数据
inputs = paddle.randn(shape=[1,4])
print('Input is:', inputs)
# 实例化模型,这里令输入长度为4,输出类别数为3
model = model_SR(input_dim=4, output_dim=3)
outputs = model(inputs)
print('Output is:', outputs)

          运行结果如下:

从输出结果可以看出,采用全0初始化后,属于每个类别的条件概率均为$\frac{1}{C}$。这是因为,不论输入值的大小为多少,线性函数$f(\mathbf x;\mathbf W,\mathbf b)$的输出值恒为0。此时,再经过Softmax函数的处理,每个类别的条件概率恒等。

3.损失函数

Softmax回归同样使用交叉熵损失作为损失函数,并使用梯度下降法对参数进行优化。通常使用$C$维的one-hot类型向量$\mathbf y \in \{0,1\}^C$来表示多分类任务中的类别标签。对于类别$c$,其向量表示为:

y=\left [ I(1=c),I(2=c),...,I(C=c) \right ]^T 

其中$I(\cdot )$是指示函数,即括号内的输入为“真”,$I(\cdot ) = 1$;否则,$I(\cdot ) = 0$

给定有$N$个训练样本的训练集$\{(\mathbf x^{(n)},y^{(n)})\} ^N_{n=1}$,令$\hat{\mathbf y}^{(n)}=\mathrm{softmax}(\mathbf W^ \mathrm{ T } \mathbf x^{(n)}+\mathbf b)$为样本$\mathbf x^{(n)}$在每个类别的后验概率。多分类问题的交叉熵损失函数定义为:

观察上式,$\mathbf y_c^{(n)}$$c$为真实类别时为1,其余都为0。也就是说,交叉熵损失只关心正确类别的预测概率,因此,上式又可以优化为:

其中$y^{(n)}$是第$n$个样本的标签。

因此,多类交叉熵损失函数的代码实现如下:

class MultiCrossEntropyLoss(Op):def __init__(self):self.predicts = Noneself.labels = Noneself.num = Nonedef __call__(self, predicts, labels):return self.forward(predicts, labels)def forward(self, predicts, labels):"""输入:- predicts:预测值,shape=[N, 1],N为样本数量- labels:真实标签,shape=[N, 1]输出:- 损失值:shape=[1]"""self.predicts = predictsself.labels = labelsself.num = self.predicts.shape[0]loss = 0for i in range(0, self.num):index = self.labels[i]loss -= torch.log(self.predicts[i][index])return loss / self.num# 测试一下
# 假设真实标签为第1类
labels = paddle.to_tensor([0])
# 计算风险函数
mce_loss = MultiCrossEntropyLoss()
print(mce_loss(outputs, labels))

          运行结果如下:

4.模型优化

计算风险函数$\cal R(\mathbf W,\mathbf b)$关于参数$\mathbf W$$\mathbf b$的偏导数。在Softmax回归中,计算方法为:

\frac{\partial \cal R(\mathbf W,\mathbf b)}{\partial \mathbf W} = -\frac{1}{N}\sum_{n=1}^N \mathbf x^{(n)}(y^{(n)}- \hat{ y}^{(n)})^T = -\frac{1}{N} \mathbf X^ \mathrm{ T } (\mathbf y- \hat{\mathbf y})

\frac{\partial \cal R(\mathbf W,\mathbf b)}{\partial \mathbf b} = -\frac{1}{N}\sum_{n=1}^N (y^{(n)}- \hat{y}^{(n)})^T = -\frac{1}{N} \mathbf 1 (\mathbf y- \hat{\mathbf y})

其中$\mathbf X\in \mathbb{R}^{N\times D}$$N$个样本组成的矩阵,$\mathbf y\in \mathbb{R}^{N}$$N$个样本标签组成的向量,$\hat{\mathbf y}\in \mathbb{R}^{N}$$N$个样本的预测标签组成的向量,$\mathbf{1}$$N$维的全1向量。

将上述计算方法定义在模型的backward函数中,代码实现如下:

class model_SR(Op):def __init__(self, input_dim, output_dim):super(model_SR, self).__init__()self.params = {}#将线性层的权重参数全部初始化为0self.params['W'] = torch.zeros((input_dim, output_dim))#self.params['W'] = paddle.normal(mean=0, std=0.01, shape=[input_dim, output_dim])#将线性层的偏置参数初始化为0self.params['b'] = torch.zeros(output_dim)#存放参数的梯度self.grads = {}self.X = Noneself.outputs = Noneself.output_dim = output_dimdef __call__(self, inputs):return self.forward(inputs)def forward(self, inputs):self.X = inputs#线性计算score = torch.matmul(self.X, self.params['W']) + self.params['b']#Softmax 函数self.outputs = softmax(score)return self.outputsdef backward(self, labels):"""输入:- labels:真实标签,shape=[N, 1],其中N为样本数量"""#计算偏导数N =labels.size()[0]labels = torch.nn.functional.one_hot(labels.to(torch.int64), self.output_dim)self.grads['W'] = -1 / N * torch.matmul(self.X.t(), (labels-self.outputs))self.grads['b'] = -1 / N * torch.matmul(torch.ones(N), (labels-self.outputs))

5.模型训练

# 特征维度
input_dim = 2
# 类别数
output_dim = 3
# 学习率
lr = 0.1# 实例化模型
model = model_SR(input_dim=input_dim, output_dim=output_dim)
# 指定优化器
optimizer = SimpleBatchGD(init_lr=lr, model=model)
# 指定损失函数
loss_fn = MultiCrossEntropyLoss()
# 指定评价方式
metric = accuracy
# 实例化RunnerV2类
runner = RunnerV2(model, optimizer, metric, loss_fn)# 模型训练
runner.train([X_train, y_train], [X_dev, y_dev], num_epochs=500, log_eopchs=50, eval_epochs=1, save_path="best_model.pdparams")# 可视化观察训练集与验证集的准确率变化情况
plot(runner,fig_name='linear-acc2.pdf')

           运行结果如下:

 6.模型评价

score, loss = runner.evaluate([X_test, y_test])
print("[Test] score/loss: {:.4f}/{:.4f}".format(score, loss))

           运行结果如下:

# 均匀生成40000个数据点
x1, x2 = torch.meshgrid(torch.linspace(-3.5, 2, 200), torch.linspace(-4.5, 3.5, 200))
x = torch.stack([torch.flatten(x1), torch.flatten(x2)], dim=1)
# 预测对应类别
y = runner.predict(x)
y = torch.argmax(y, dim=1)
# 绘制类别区域
plt.ylabel('x2')
plt.xlabel('x1')
plt.scatter(x[:,0].tolist(), x[:,1].tolist(), c=y.tolist(), cmap=plt.cm.Spectral)n_samples = 1000
X, y = make_multiclass_classification(n_samples=n_samples, n_features=2, n_classes=3, noise=0.2)plt.scatter(X[:, 0].tolist(), X[:, 1].tolist(), marker='*', c=y.tolist())
plt.legend()
plt.show()

         运行结果如下:

附录:

main.py 

import numpy as np
import torch
import matplotlib.pyplot as plt
from nndl.op import model_SR
from nndl.activation import softmax
from nndl.op import MultiCrossEntropyLoss
from nndl.opitimizer import SimpleBatchGD
from nndl.metric import accuracy
from nndl.runner import RunnerV2
from nndl.tools import plot
def make_multiclass_classification(n_samples=100, n_features=2, n_classes=3, shuffle=True, noise=0.1):"""生成带噪音的多类别数据输入:- n_samples:数据量大小,数据类型为int- n_features:特征数量,数据类型为int- shuffle:是否打乱数据,数据类型为bool- noise:以多大的程度增加噪声,数据类型为None或float,noise为None时表示不增加噪声输出:- X:特征数据,shape=[n_samples,2]- y:标签数据, shape=[n_samples,1]"""# 计算每个类别的样本数量n_samples_per_class = [int(n_samples / n_classes) for k in range(n_classes)]for i in range(n_samples - sum(n_samples_per_class)):n_samples_per_class[i % n_classes] += 1# 将特征和标签初始化为0X = torch.zeros((n_samples, n_features))y = torch.zeros(n_samples, dtype=torch.int32)# 随机生成3个簇中心作为类别中心centroids = torch.randperm(2 ** n_features)[:n_classes]centroids_bin = np.unpackbits(centroids.numpy().astype('uint8')).reshape((-1, 8))[:, -n_features:]centroids = torch.tensor(centroids_bin, dtype=torch.float32)# 控制簇中心的分离程度centroids = 1.5 * centroids - 1# 随机生成特征值X[:, :n_features] = torch.randn((n_samples, n_features))stop = 0# 将每个类的特征值控制在簇中心附近for k, centroid in enumerate(centroids):start, stop = stop, stop + n_samples_per_class[k]# 指定标签值y[start:stop] = k % n_classesX_k = X[start:stop, :n_features]# 控制每个类别特征值的分散程度A = 2 * torch.rand(size=(n_features, n_features)) - 1X_k[...] = torch.matmul(X_k, A)X_k += centroidX[start:stop, :n_features] = X_k# 如果noise不为None,则给特征加入噪声if noise > 0.0:# 生成noise掩膜,用来指定给那些样本加入噪声noise_mask = torch.rand(n_samples) < noisefor i in range(len(noise_mask)):if noise_mask[i]:# 给加噪声的样本随机赋标签值y[i] = torch.randint(n_classes, size=(1,), dtype=torch.int32)# 如果shuffle为True,将所有数据打乱if shuffle:idx = torch.randperm(X.shape[0])X = X[idx]y = y[idx]return X, y# 采样1000个样本
n_samples = 1000
X, y = make_multiclass_classification(n_samples=n_samples, n_features=2, n_classes=3, noise=0.2)# 可视化生产的数据集,不同颜色代表不同类别
plt.figure(figsize=(5,5))
plt.scatter(x=X[:, 0].tolist(), y=X[:, 1].tolist(), marker='*', c=y.tolist())
plt.savefig('linear-dataset-vis2.pdf')
plt.show()num_train = 640
num_dev = 160
num_test = 200X_train, y_train = X[:num_train], y[:num_train]
X_dev, y_dev = X[num_train:num_train + num_dev], y[num_train:num_train + num_dev]
X_test, y_test = X[num_train + num_dev:], y[num_train + num_dev:]# 打印X_train和y_train的维度
print("X_train shape: ", X_train.shape, "y_train shape: ", y_train.shape)
# 打印前5个数据的标签
print(y_train[:5])# 观察softmax的计算方式
X = torch.tensor([[0.1, 0.2, 0.3, 0.4],[1,2,3,4]], dtype=torch.float32)
predict = softmax(X)
print(predict)# 随机生成1条长度为4的数据
inputs = torch.randn(size=(1, 4))
print('Input is:', inputs)
# 实例化模型,这里令输入长度为4,输出类别数为3
model = model_SR(input_dim=4, output_dim=3)
outputs = model(inputs)
print('Output is:', outputs)labels = torch.tensor([0])
# 计算风险函数
mce_loss = MultiCrossEntropyLoss()
print(mce_loss(outputs, labels))# 特征维度
input_dim = 2
# 类别数
output_dim = 3
# 学习率
lr = 0.1# 实例化模型
model = model_SR(input_dim=input_dim, output_dim=output_dim)
# 指定优化器
optimizer = SimpleBatchGD(init_lr=lr, model=model)
# 指定损失函数
loss_fn = MultiCrossEntropyLoss()
# 指定评价方式
metric = accuracy
# 实例化RunnerV2类
runner = RunnerV2(model, optimizer, metric, loss_fn)# 模型训练
runner.train([X_train, y_train], [X_dev, y_dev], num_epochs=500, log_eopchs=50, eval_epochs=1, save_path="best_model.pdparams")# 可视化观察训练集与验证集的准确率变化情况
plot(runner,fig_name='linear-acc2.pdf')score, loss = runner.evaluate([X_test, y_test])
print("[Test] score/loss: {:.4f}/{:.4f}".format(score, loss))# 均匀生成40000个数据点
x1, x2 = torch.meshgrid(torch.linspace(-3.5, 2, 200), torch.linspace(-4.5, 3.5, 200))
x = torch.stack([torch.flatten(x1), torch.flatten(x2)], dim=1)
# 预测对应类别
y = runner.predict(x)
y = torch.argmax(y, dim=1)
# 绘制类别区域
plt.ylabel('x2')
plt.xlabel('x1')
plt.scatter(x[:,0].tolist(), x[:,1].tolist(), c=y.tolist(), cmap=plt.cm.Spectral)n_samples = 1000
X, y = make_multiclass_classification(n_samples=n_samples, n_features=2, n_classes=3, noise=0.2)plt.scatter(X[:, 0].tolist(), X[:, 1].tolist(), marker='*', c=y.tolist())
plt.legend()
plt.show()

nndl包

op.py 

import torch
from DL.实验4_2.nndl.activation import softmax
torch.seed() #设置随机种子class Op(object):def __init__(self):passdef __call__(self, inputs):return self.forward(inputs)def forward(self, inputs):raise NotImplementedErrordef backward(self, inputs):raise NotImplementedError# 线性算子
class Linear(Op):def __init__(self,dimension):"""输入:- dimension:模型要处理的数据特征向量长度"""self.dim = dimension# 模型参数self.params = {}self.params['w'] = torch.randn(self.dim,1,dtype=torch.float32)self.params['b'] = torch.zeros(1,dtype=torch.float32)def __call__(self, X):return self.forward(X)# 前向函数def forward(self, X):"""输入:- X: tensor, shape=[N,D]注意这里的X矩阵是由N个x向量的转置拼接成的,与原教材行向量表示方式不一致输出:- y_pred: tensor, shape=[N]"""N,D = X.shapeif self.dim==0:return torch.full((N, 1), self.params['b'])assert D==self.dim # 输入数据维度合法性验证# 使用paddle.matmul计算两个tensor的乘积y_pred = torch.matmul(X,self.params['w'])+self.params['b']return y_pred#新增Softmax算子
class model_SR(Op):def __init__(self, input_dim, output_dim):super(model_SR, self).__init__()self.params = {}#将线性层的权重参数全部初始化为0self.params['W'] = torch.zeros((input_dim, output_dim))#self.params['W'] = paddle.normal(mean=0, std=0.01, shape=[input_dim, output_dim])#将线性层的偏置参数初始化为0self.params['b'] = torch.zeros(output_dim)#存放参数的梯度self.grads = {}self.X = Noneself.outputs = Noneself.output_dim = output_dimdef __call__(self, inputs):return self.forward(inputs)def forward(self, inputs):self.X = inputs#线性计算score = torch.matmul(self.X, self.params['W']) + self.params['b']#Softmax 函数self.outputs = softmax(score)return self.outputsdef backward(self, labels):"""输入:- labels:真实标签,shape=[N, 1],其中N为样本数量"""#计算偏导数N =labels.size()[0]labels = torch.nn.functional.one_hot(labels.to(torch.int64), self.output_dim)self.grads['W'] = -1 / N * torch.matmul(self.X.t(), (labels-self.outputs))self.grads['b'] = -1 / N * torch.matmul(torch.ones(N), (labels-self.outputs))#新增多类别交叉熵损失
class MultiCrossEntropyLoss(Op):def __init__(self):self.predicts = Noneself.labels = Noneself.num = Nonedef __call__(self, predicts, labels):return self.forward(predicts, labels)def forward(self, predicts, labels):"""输入:- predicts:预测值,shape=[N, 1],N为样本数量- labels:真实标签,shape=[N, 1]输出:- 损失值:shape=[1]"""self.predicts = predictsself.labels = labelsself.num = self.predicts.shape[0]loss = 0for i in range(0, self.num):index = self.labels[i]loss -= torch.log(self.predicts[i][index])return loss / self.num

 activation.py

import torch# x为tensor
def softmax(X):"""输入:- X:shape=[N, C],N为向量数量,C为向量维度"""x_max = torch.max(X, dim=1, keepdim=True)  # N,1x_exp = torch.exp(X - x_max.values)partition = torch.sum(x_exp, dim=1, keepdim=True)  # N,1return x_exp / partition

opitimizer.py

import torchdef optimizer_lsm(model, X, y, reg_lambda=0):"""输入:- model: 模型- X: tensor, 特征数据,shape=[N,D]- y: tensor,标签数据,shape=[N]- reg_lambda: float, 正则化系数,默认为0输出:- model: 优化好的模型"""N, D = X.shape# 对输入特征数据所有特征向量求平均x_bar_tran = torch.mean(X,dim=0).T# 求标签的均值,shape=[1]y_bar = torch.mean(y)# paddle.subtract通过广播的方式实现矩阵减向量x_sub = torch.subtract(X,x_bar_tran)# 使用paddle.all判断输入tensor是否全0if torch.all(x_sub==0):model.params['b'] = y_barmodel.params['w'] = torch.zeros(D)return model# paddle.inverse求方阵的逆tmp = torch.inverse(torch.matmul(x_sub.T,x_sub)+reg_lambda*torch.eye(D))w = torch.matmul(torch.matmul(tmp,x_sub.T),(y-y_bar))b = y_bar-torch.matmul(x_bar_tran,w)model.params['b'] = bmodel.params['w'] = torch.squeeze(w,dim=-1)return modelfrom abc import abstractmethod#新增优化器基类
class Optimizer(object):def __init__(self, init_lr, model):"""优化器类初始化"""#初始化学习率,用于参数更新的计算self.init_lr = init_lr#指定优化器需要优化的模型self.model = model@abstractmethoddef step(self):"""定义每次迭代如何更新参数"""pass#新增梯度下降法优化器
class SimpleBatchGD(Optimizer):def __init__(self, init_lr, model):super(SimpleBatchGD, self).__init__(init_lr=init_lr, model=model)def step(self):#参数更新#遍历所有参数,按照公式(3.8)和(3.9)更新参数if isinstance(self.model.params, dict):for key in self.model.params.keys():self.model.params[key] = self.model.params[key] - self.init_lr * self.model.grads[key]

metric.py

import torchdef accuracy(preds, labels):"""输入:- preds:预测值,二分类时,shape=[N, 1],N为样本数量,多分类时,shape=[N, C],C为类别数量- labels:真实标签,shape=[N, 1]输出:- 准确率:shape=[1]"""# 判断是二分类任务还是多分类任务,preds.shape[1]=1时为二分类任务,preds.shape[1]>1时为多分类任务if preds.shape[1] == 1:# 二分类时,判断每个概率值是否大于0.5,当大于0.5时,类别为1,否则类别为0# 使用'torch.gt'比较preds和0.5,返回bool类型的tensor,再使用'torch.float32'将bool类型的tensor转换为float32类型的tensorpreds = torch.gt(preds, 0.5).float()else:# 多分类时,使用'torch.argmax'计算最大元素索引作为类别preds = torch.argmax(preds, dim=1)return torch.mean((preds == labels).float())

runner.py

import torch# 新增RunnerV2类
class RunnerV2(object):def __init__(self, model, optimizer, metric, loss_fn):self.model = modelself.optimizer = optimizerself.loss_fn = loss_fnself.metric = metric# 记录训练过程中的评价指标变化情况self.train_scores = []self.dev_scores = []# 记录训练过程中的损失函数变化情况self.train_loss = []self.dev_loss = []def train(self, train_set, dev_set, **kwargs):# 传入训练轮数,如果没有传入值则默认为0num_epochs = kwargs.get("num_epochs", 0)# 传入log打印频率,如果没有传入值则默认为100log_epochs = kwargs.get("log_epochs", 100)# 传入模型保存路径,如果没有传入值则默认为"best_model.pdparams"save_path = kwargs.get("save_path", "best_model.pdparams")# 梯度打印函数,如果没有传入则默认为"None"print_grads = kwargs.get("print_grads", None)# 记录全局最优指标best_score = 0# 进行num_epochs轮训练for epoch in range(num_epochs):X, y = train_set# 获取模型预测logits = self.model(X)# 计算交叉熵损失trn_loss = self.loss_fn(logits, y).item()self.train_loss.append(trn_loss)# 计算评价指标trn_score = self.metric(logits, y).item()self.train_scores.append(trn_score)# 计算参数梯度self.model.backward(y)if print_grads is not None:# 打印每一层的梯度print_grads(self.model)# 更新模型参数self.optimizer.step()dev_score, dev_loss = self.evaluate(dev_set)# 如果当前指标为最优指标,保存该模型if dev_score > best_score:self.save_model(save_path)print(f"best accuracy performence has been updated: {best_score:.5f} --> {dev_score:.5f}")best_score = dev_scoreif epoch % log_epochs == 0:print(f"[Train] epoch: {epoch}, loss: {trn_loss}, score: {trn_score}")print(f"[Dev] epoch: {epoch}, loss: {dev_loss}, score: {dev_score}")def evaluate(self, data_set):X, y = data_set# 计算模型输出logits = self.model(X)# 计算损失函数loss = self.loss_fn(logits, y).item()self.dev_loss.append(loss)# 计算评价指标score = self.metric(logits, y).item()self.dev_scores.append(score)return score, lossdef predict(self, X):return self.model(X)def save_model(self, save_path):torch.save(self.model.params, save_path)def load_model(self, model_path):self.model.params = torch.load(model_path)

tools.py

import matplotlib.pyplot as plt#新增绘制图像方法
def plot(runner,fig_name):plt.figure(figsize=(10,5))plt.subplot(1,2,1)epochs = [i for i in range(len(runner.train_scores))]#绘制训练损失变化曲线plt.plot(epochs, runner.train_loss, color='#e4007f', label="Train loss")#绘制评价损失变化曲线plt.plot(epochs, runner.dev_loss, color='#f19ec2', linestyle='--', label="Dev loss")#绘制坐标轴和图例plt.ylabel("loss", fontsize='large')plt.xlabel("epoch", fontsize='large')plt.legend(loc='upper right', fontsize='x-large')plt.subplot(1,2,2)#绘制训练准确率变化曲线plt.plot(epochs, runner.train_scores, color='#e4007f', label="Train accuracy")#绘制评价准确率变化曲线plt.plot(epochs, runner.dev_scores, color='#f19ec2', linestyle='--', label="Dev accuracy")#绘制坐标轴和图例plt.ylabel("score", fontsize='large')plt.xlabel("epoch", fontsize='large')plt.legend(loc='lower right', fontsize='x-large')plt.tight_layout()plt.savefig(fig_name)plt.show()

(PS:累太累了,下次证明少写点,嘤嘤嘤,小公式没给我累死,好处是对softmax有了更深的理解了)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/152876.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

多路彩灯控制器led流水灯VHDL速度可调仿真图视频、源代码

名称&#xff1a;多路彩灯控制器led流水灯VHDL速度可调 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a; 使用VHDL设计彩灯控制器&#xff0c;共24个led灯&#xff0c;分为5种不同的花样&#xff0c;可以通过按键切换花样的变化速度。 代码下载&#…

VBox启动失败、Genymotion启动失败、Vagrant迁移

VBox启动失败、Genymotion启动失败、Vagrant迁移 2023.10.9 最新版本vbox7.0.10、Genymotion3.5.0 Vbox启动失败 1、查看日志 Error -610 in supR3HardenedMainInitRuntime! (enmWhat4) Failed to locate ‘vcruntime140.dll’ 日志信息查看方法->找到虚拟机所在位置->…

如何开始学习量子机器学习

一、关于量子计算 这是我关于量子机器学习&#xff08;QML&#xff09;的第二篇文章&#xff0c;这是第一篇&#xff0c;关于为什么你应该开始学习QML。 开始研究量子机器学习很困难&#xff0c;因为我不知道我需要了解多少量子力学和计算知识。我在101年上大学时上了量子力学2…

抽象轻松的java-mybatis简单入门

第一步&#xff1a;用IDEA新建一个java包 第二步&#xff1a;在IDEA中添加数据库&#xff08;ps&#xff1a;自己百度&#xff09; 点击数据库 第二步&#xff0c;新建数据库 选择你使用的数据库 用户与密码根据自己的设置进行配置 为了更方便的查看数据库&#xff0c;可以像图…

【kubernetes】带你了解k8s中PV和PVC的由来

文章目录 1 为什么需要卷(Volume)2 卷的挂载2.1 k8s集群中可以直接使用2.2 需要额外的存储组件2.3 公有云 2 PV(Persistent Volume)3 SC(Storage Class) 和 PVC(Persistent Volume Claim)4 总结 1 为什么需要卷(Volume) Pod是由一个或者多个容器组成的&#xff0c;在启动Pod中…

Linux免密登录

目标&#xff1a; 192.168.233.31 ton-pc-003 192.168.233.32 ton-pc-004 在ton-pc-004&#xff08;以下简称004&#xff09;免密登录ton-pc-003&#xff08;以下简称003&#xff09; 具体流程和说明&#xff1a; 1、在004&#xff08;客户机&#xff09;中生成秘钥对 ssh…

【计算机视觉|人脸建模】学习从4D扫描中获取的面部形状和表情的模型

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Learning a model of facial shape and expression from 4D scans 链接&#xff1a;Learning a model of facial shape and expression from 4D scans | ACM Transactions on Graphics Pe…

解决方案:AI赋能工业生产3.0,从工业“制造”到“智造”

视频监控技术是一种既成熟又广泛应用于工业制造领域的先进技术。它可以通过安装各种摄像头和传感器来监测整个生产流程&#xff0c;包括原材料的采购、加工、装配和物流等环节&#xff0c;从而实现对生产过程的实时监控和管理&#xff0c;以及对异常事件的及时预警和响应。 在…

【SV中的多线程fork...join/join_any/join_none】

SV中fork_join/fork_join_any/fork_join_none 1 一目了然1.1 fork...join1.2 fork...join_any1.3 fork...join_none 2 总结 SV中fork_join和fork_join_any和fork_join_none; Note: fork_join在Verilog中也有&#xff0c;只有其他的两个是SV中独有的&#xff1b; 1 一目了然 1.…

FreeRTOS自我救赎3之USB虚拟串口

任何项目的功能都从需求出发&#xff0c;在这里我用的是斥侯蜂的一块STM32F407ZGT6 在开发一个项目的过程中&#xff0c;免不了串口调试&#xff0c;而这块板子板载的mircousb不是直接连的引脚而是一个OTGUSB

SpringCloud Alibaba - Seata 部署 TC 服务,并集成微服务

目录 一、Seata 架构 1.1、Seata 架构重要角色 1.2、部署 TC 服务 1.2.1、前言 1.2.2、下载 seata-server 包&#xff0c;解压 1.2.3、修改配置 1.2.4、在 nacos 中添加配置 1.2.5、创建数据库表 1.2.6、启动 TC 服务 1.3、微服务集成 Seata 1.3.1、引入依赖 1.3.2、…

SpringBoot 实现数据脱敏

SpringBoot 实现数据脱敏 前言Hutool 实现数据脱敏引入依赖脱敏工具类代码实现 使用注解的方式定义枚举自定义序列化类定义注解测试 前言 数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形&#xff0c;实现敏感隐私数据的可靠保护。 数据脱敏常用规则有替换、重排、加密…

C++对象模型(8)-- 数据语义学:this指针

1、this指针的认识 this 是 C 中的一个关键字&#xff0c;也是一个 const 指针 &#xff0c;它指向当前对象&#xff0c;通过它可以访问当前对象的所有成员。所谓当前对象&#xff0c;是指正在使用的对象。 假如有这么一个类&#xff1a; class Base { public:int b_i;int b…

[NewStarCTF 2023 公开赛道] week1 Crypto

brainfuck 题目描述&#xff1a; [>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<-]>>>>>>>.>----.<-----.>-----.>-----.<<<-.>>..…

论文解析——异构多芯粒神经网络加速器

作者 朱郭益, 马胜&#xff0c;张春元, 王波&#xff08;国防科技大学计算机学院&#xff09; 摘要 随着神经网络技术的快速发展, 出于安全性等方面考虑, 大量边缘计算设备被应用于智能计算领域。首先&#xff0c;设计了可应用于边缘计算的异构多芯粒神经网络加速器其基本结构…

TCP相关面试题

TCP相关面试题 题目1 介绍一下TCP三次握手的过程 介绍TCP三次握手应该从3个方面进行回答&#xff0c;分别是数据包名称&#xff0c;客户端与服务端的状态变化&#xff0c;数据包的序号变化。而不能只是简单回答发送的数据包名称。 TCP三次握手的过程如下&#xff1a; 从数据…

关于Jupyter notebook 创建python3 时进去不能重命名问题及不能编程问题

首先写这篇博客时&#xff0c;已经被这个问题折磨了三天&#xff0c;看了很多博客&#xff0c;其实解决这个问题的关键就是要么没有下pyzmq或者等级太高&#xff0c;要么等级太低&#xff0c;首先我会按照我思路来。 问题如图&#xff1a; 1.自动换行 2.不能重命名 我的解决办…

安装torchtext遇到的坑及解决办法

刚开始秉着需要什么就pip install什么的原则直接pip install torchtext&#xff0c;结果&#xff1a; 把我这个环境打乱了&#xff0c;自作主张的删掉之前的很多包重新安装了其他版本的包而不是自适应的安装当前torch所对应的torchtext。因为这个环境比较重要也用在其他的工程…

MySQL详解 七:数据库高级语句(视图表、存储过程)

文章目录 1. create view ---- (视图表)1.1 视图的简单介绍1.2 基本语法1.2.1 创建视图表1.2.2 查看视图表1.2.3 删除视图表1.2.4 修改视图表 1.3 通过视图表得出无交集 2. case语句3. 空值&#xff08;null&#xff09; 和 无值&#xff08; &#xff09; 的区别4. 正则表达式…

CSS盒子定位的扩张

定位的扩展 绝对定位&#xff08;固定定位&#xff09;会完全压住盒子 浮动元素不会压住下面标准流的文字&#xff0c;而绝对定位或固定位会压住下面标准流的所有内容 如果一个盒子既有向左又有向右&#xff0c;则执行左&#xff0c;同理执行上 显示隐藏 display: none&…