目录:

  • 第3章 线性分类
    • 3.1 基于Logistic回归的二分类任务
      • 3.1.1 数据集构建
      • 3.1.2 模型构建
        • 1. Logistic函数
        • 2. Logistic回归算子
      • 3.1.3 损失函数
      • 3.1.4 模型优化
        • 3.1.4.1 梯度计算
        • 3.1.4.2 参数更新
      • 3.1.5 评价指标
      • 3.1.6 完善Runner类
      • 3.1.7 模型训练
      • 3.1.8 模型评价

第3章 线性分类

分类是机器学习中最常见的一类任务,其预测标签是一些离散的类别(符号)。根据分类任务的类别数量又可以分为二分类任务和多分类任务。

线性分类是指利用一个或多个线性函数将样本进行分类。常用的线性分类模型有Logistic回归和Softmax回归。
Logistic回归是一种常用的处理二分类问题的线性模型。Softmax回归是Logistic回归在多分类问题上的推广。

线性模型的相关内容,关键知识点 如图 所示,以便更好的理解和掌握相应的理论知识,及其在实践中的应用方法。

本章内容基于 《神经网络与深度学习》第3章:线性模型 相关内容进行设计,主要包含两部分:

  1. 模型解读:介绍两个最常用的线性分类模型Logistic回归和Softmax回归的原理剖析和相应的代码实现。通过理论和代码的结合,加深对线性模型的理解;

  2. 案例实践:基于Softmax回归算法完成鸢尾花分类任务。

3.1 基于Logistic回归的二分类任务

实现一个Logistic回归模型,并对一个简单的数据集进行二分类实验。

3.1.1 数据集构建

我们首先构建一个简单的分类任务,并构建训练集、验证集和测试集。
本任务的数据来自带噪音的两个弯月形状函数,每个弯月对一个类别。我们采集1000条样本,每个样本包含2个特征。

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

# 首先导入包
import math
import copy
import torch# 数据集的构建函数make_moons的代码实现如下:
def make_moons(n_samples=1000, shuffle=True, noise=None):''':param n_samples: 数据量大小,数据类型为int:param shuffle:是否打乱数据,数据类型为bool:param noise:以多大的程度增加噪声,数据类型为None或float,noise为None时表示不增加噪声:return:- X:特征数据,shape=[n_samples, 2]- y:特征数据,shape=[n_samples]'''n_samples_out = n_samples // 2  # 这里是只去除完之后的整数部分。n_samples_in = n_samples - n_samples_out# 采集第一类数据,特征为(X,y)# 使用'torch.linspace'在0到pi上均匀取n_samples_out个值# 使用'torch.cos'计算上述取值的余弦值作为特征1,使用'torch.sin'计算上述取值的正弦值作为特征2outer_circ_x = torch.cos(torch.linspace(0, math.pi, n_samples_out))outer_circ_y = torch.sin(torch.linspace(0, math.pi, n_samples_out))inner_circ_x = 1 - torch.cos(torch.linspace(0, math.pi, n_samples_in))inner_circ_y = 0.5 - torch.sin(torch.linspace(0, math.pi, n_samples_in))print('outer_circ_x.shape:', outer_circ_x.shape, 'outer_circ_y.shape:', outer_circ_y.shape)print('outer_circ_x.shape:', inner_circ_x.shape, 'inner_circ_y.shape:', inner_circ_y.shape)# 使用'torch.cat'将两类数据的特征1和特征2分别延维度0拼接在一起,得到全部特征1和特征2# 使用'torch.stack'将两类特征延维度1堆叠在一起X = torch.stack([torch.cat([outer_circ_x, inner_circ_x]),torch.cat([outer_circ_y, inner_circ_y])],dim=1)print('after concat shape:', torch.cat([outer_circ_x, inner_circ_x]).shape)print('X shape:', X.shape)# 使用'torch. zeros'将第一类数据的标签全部设置为0# 使用'torch. ones'将第一类数据的标签全部设置为1y = torch.cat([torch.zeros(size=[n_samples_out]), torch.ones(size=[n_samples_in])])print('y shape:', y.shape)# 如果shuffle为True,将所有数据打乱if shuffle:# 使用'torch.randperm'生成一个数值在0到X.shape[0],随机排列的一维Tensor做索引值,用于打乱数据idx = torch.randperm(X.shape[0])X = X[idx]y = y[idx]# 如果noise不为None,则给特征值加入噪声if noise is not None:# 使用'torch.normal'生成符合正态分布的随机Tensor作为噪声,并加到原始特征上X += torch.normal(mean=0.0, std=noise, size=X.shape)return X, y

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

# 随机采集1000个样本,并进行可视化。
# 采样1000个样本
n_samples = 1000
X, y = make_moons(n_samples=n_samples, shuffle=True, noise=0.5)# 可视化生产的数据集,不同颜色代表不同类别
plt.figure(figsize=(5, 5))
plt.scatter(x=X[:, 0].tolist(), y=X[:, 1].tolist(), marker='*', c=y.tolist())
plt.xlim(-3, 4)
plt.ylim(-3, 4)
plt.savefig('linear-dataset-vis.pdf')
plt.show()

运行结果:

outer_circ_x.shape: torch.Size([500]) outer_circ_y.shape: torch.Size([500])
outer_circ_x.shape: torch.Size([500]) inner_circ_y.shape: torch.Size([500])
after concat shape: torch.Size([1000])
X shape: torch.Size([1000, 2])
y shape: torch.Size([1000])


小笔记:

  1. // 表示取除完之后的整数部分
  2. 使用’torch.stack’将两类特征延维度1堆叠在一起
  3. 使用’torch.cat’将两类数据的特征1和特征2分别延维度0拼接在一起,得到全部特征1和特征2
  4. idx = torch.randperm(X.shape[0]) # 打乱顺序的一个Tensor
    X = X[idx] # 按照idx的顺序排列的X

将1000条样本数据拆分成训练集、验证集和测试集,其中训练集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:]y_train = y_train.reshape([-1,1])
y_dev = y_dev.reshape([-1,1])
y_test = y_test.reshape([-1,1])

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

# 打印X_train和y_train的维度
print("X_train shape: ", X_train.shape, "y_train shape: ", y_train.shape)# 打印一下前5个数据的标签
print (y_train[:5])

运行结果:

X_train shape:  torch.Size([640, 2]) y_train shape:  torch.Size([640, 1])
tensor([[1.],[1.],[1.],[0.],[0.]])

3.1.2 模型构建

Logistic回归是一种常用的处理二分类问题的线性模型。与线性回归一样,Logistic回归也会将输入特征与权重做线性叠加。不同之处在于,Logistic回归引入了非线性函数 g : R D → ( 0 , 1 ) g:R^D→(0,1) g:RD→(0,1),预测类别标签的后验概率 p ( y = 1 ∣ x ) p(y=1|x) p(y=1∣x) ,从而解决连续的线性函数不适合进行分类的问题。
p ( y = 1 ∣ x ) = σ ( w T x + b ) , ( 3.1 ) p(y=1|x)=σ(w^Tx+b),(3.1) p(y=1∣x)=σ(wTx+b),(3.1)其中判别函数 σ ( ⋅ ) σ(⋅) σ(⋅) 为Logistic函数,也称为激活函数,作用是将线性函数 f ( x ; w , b ) f(x;w,b) f(x;w,b) 的输出从实数区间“挤压”到(0,1)之间,用来表示概率。Logistic函数定义为:
σ ( x ) = 1 1 + e x p ( − x ) , ( 3.2 ) σ(x)={1 \over 1+exp(−x)},(3.2) σ(x)=1+exp(−x)1​,(3.2)

1. Logistic函数

Logistic函数的代码实现如下:

def logistic(x):return 1 / (1 + torch.exp(-x))

这里我们做个图像看一下

# 在[-10,10]的范围内生成一系列的输入值,用来绘制函数曲线
x = torch.linspace(-10, 10, 10000)
plt.figure()
plt.plot(x.tolist(), logistic(x).tolist(), color="#e4007f", label="Logistic Function")  # tolist转为列表
# 设置坐标轴
ax = plt.gca()
# 取消右侧和上侧坐标轴、
ax.spines['top'].set_color('none')
ax.spines['right'].set_color('none')
# 设置默认的x轴和y轴方向
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
# 设置坐标原点为(0,0)
ax.spines['left'].set_position(('data', 0))
ax.spines['bottom'].set_position(('data', 0))
# 添加图例
plt.legend()
plt.savefig('linear-logistic.pdf')
plt.show()

运行结果:

从输出结果看,当输入在0附近时,Logistic函数近似为线性函数;而当输入值非常大或非常小时,函数会对输入进行抑制。输入越小,则越接近0;输入越大,则越接近1。正因为Logistic函数具有这样的性质,使得其输出可以直接看作为概率分布。

2. Logistic回归算子

Logistic回归模型其实就是线性层与Logistic函数的组合,通常会将 Logistic回归模型中的权重和偏置初始化为0,同时,为了提高预测样本的效率,我们将N个样本归为一组进行成批地预测。
y ^ = p ( y ∣ x ) = σ ( X w + b ) , ( 3.3 ) \hat y=p(y|x)=σ(Xw+b),(3.3) y^​=p(y∣x)=σ(Xw+b),(3.3)其中 X ∈ R N × D X∈R^{N×D} X∈RN×D为N个样本的特征矩阵, y ^ \hat y y^​ 为N个样本的预测值构成的 N N N维向量。

这里,我们构建一个Logistic回归算子,代码实现如下:

import op # 这里导入op,可以看一下上一章的内容# Logistic回归算子
class model_LR(op.Op):def __init__(self, input_dim):super(model_LR, self).__init__()self.params = {}# 将线性层的权重参数全部初始化为0self.params['w'] = torch.zeros(size=[input_dim, 1])# self.params['w'] = torch.normal(mean=0, std=0.01, shape=[input_dim, 1])# 将线性层的偏置参数初始化为0self.params['b'] = torch.zeros(size=[1])def __call__(self, inputs):return self.forward(inputs)def forward(self, inputs):''':param inputs:shape=[N,D], N是样本数量,D为特征维度:return:-  outputs:预测标签为1的概率,shape=[N,1]'''# 线性算子score = torch.matmul(inputs, self.params['w'] + self.params['b'])# Logistic函数outputs = logistic(score)return outputs

测试一下

随机生成3条长度为4的数据输入Logistic回归模型,观察输出结果。

# 固定随机种子,保持每次运行结果一致
torch.manual_seed(10)
# 随机生成3条长度为4的数据
inputs = torch.randn(size=[3, 4])
print('Input is:', inputs)
# 实例化模型
model = model_LR(4)
outputs = model(inputs)
print('Output is:', outputs)

运行结果:

Input is: tensor([[-0.6014, -1.0122, -0.3023, -1.2277],[ 0.9198, -0.3485, -0.8692, -0.9582],[-1.1920,  1.9050, -0.9373, -0.8465]])
Output is: tensor([[0.5000],[0.5000],[0.5000]])

从输出结果看,模型最终的输出 g ( ⋅ ) g(⋅) g(⋅) 恒为0.5。这是由于采用全0初始化后,不论输入值的大小为多少,Logistic函数的输入值恒为0,因此输出恒为0.5。

3.1.3 损失函数

在模型训练过程中,需要使用损失函数来量化预测值和真实值之间的差异。
给定一个分类任务, y y y表示样本 x x x的标签的真实概率分布,向量 y ^ = p ( y ∣ x ) \hat y=p(y|x) y^​=p(y∣x)表示预测的标签概率分布。
训练目标是使得 y ^ \hat y y^​ 尽可能地接近 y y y,通常可以使用交叉熵损失函数。
在给定y的情况下,如果预测的概率分布 y ^ \hat y y^​ 与标签真实的分布 y y y越接近,则交叉熵越小;如果 p ( x ) p(x) p(x)和 y y y越远,交叉熵就越大。

对于二分类任务,我们只需要计算 y ^ = p ( y = 1 ∣ x ) \hat y=p(y=1|x) y^​=p(y=1∣x),用 1 − y ^ 1−\hat y 1−y^​来表示 p ( y = 0 ∣ x ) p(y=0|x) p(y=0∣x)。
给定有 N N N 个训练样本的训练集 { ( x ( n ) , y ( n ) } n = 1 N \{(x(n),y(n)\}^N_{n=1} {(x(n),y(n)}n=1N​,使用交叉熵损失函数,Logistic回归的风险函数计算方式为:
R ( w , b ) = − 1 N ∑ n = 1 N ( y ( n ) l o g y ^ ( n ) + ( 1 − y ( n ) ) l o g ( 1 − y ^ ( n ) ) ) , ( 3.4 ) R(w,b)={−{1 \over N}∑_{n=1}^N(y^{(n)}log\hat y^{(n)}+(1−y^{(n)})log(1−\hat y^{(n)}))},(3.4) R(w,b)=−N1​n=1∑N​(y(n)logy^​(n)+(1−y(n))log(1−y^​(n))),(3.4)向量形式可以表示为:
R ( w , b ) = − 1 N ( y l o g y ^ + ( 1 − y ) T l o g ( 1 − y ^ ) ) , ( 3.4 ) R(w,b)={−{1 \over N}(ylog\hat y+(1−y)^Tlog(1−\hat y))},(3.4) R(w,b)=−N1​(ylogy^​+(1−y)Tlog(1−y^​)),(3.4)其中 y ∈ [ 0 , 1 ] N y∈[0,1]^N y∈[0,1]N 为 N N N 个样本的真实标签构成的N维向量, y ^ \hat y y^​ 为 N N N 个样本标签为1的后验概率构成的 N N N 维向量。

二分类任务的交叉熵损失函数的代码实现如下:

# 3.1.3 损失函数class BinaryCrossEntropyLoss(op.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):''':param predicts:预测值,shape=[N, 1],N为样本数量:param labels:真实标签,shape=[N, 1]:return:- 损失值:shape=[1]'''self.predicts = predictsself.labels = labelsself.num = self.predicts.shape[0]loss = -1. / self.num * (torch.matmul(self.labels.t(), torch.log(self.predicts)) + torch.matmul((1 - self.labels.t()),torch.log(1 - self.predicts)))loss = torch.squeeze(loss, dim=1)return loss# 测试一下
# 生成一组长度为3,值为1的标签数据
labels = torch.ones(size=[3, 1])
print('labels is:', labels)
# 计算风险函数
bce_loss = BinaryCrossEntropyLoss()
print(bce_loss(outputs, labels))

运行结果:

labels is: tensor([[1.],[1.],[1.]])
tensor([0.6931])

3.1.4 模型优化

不同于线性回归中直接使用最小二乘法即可进行模型参数的求解,Logistic回归需要使用优化算法对模型参数进行有限次地迭代来获取更优的模型,从而尽可能地降低风险函数的值。
在机器学习任务中,最简单、常用的优化算法是梯度下降法。

使用梯度下降法进行模型优化,首先需要初始化参数W和 b,然后不断地计算它们的梯度,并沿梯度的反方向更新参数。

3.1.4.1 梯度计算

在Logistic回归中,风险函数 R ( w , b ) R(w,b) R(w,b) 关于参数 w w w 和 b b b 的偏导数为:
∂ R ( w , b ) ∂ w = − 1 N ∑ n = 1 N x ( n ) ( y ( n ) − y ^ ( n ) ) = − 1 N X T ( y − y ^ ) , ( 3.6 ) {∂R(w,b)\over ∂w}=−{1\over N}∑^N_{n=1}x^{(n)}(y(n)−\hat y^{(n)})=−{1\over N}X^T(y−\hat y), (3.6) ∂w∂R(w,b)​=−N1​n=1∑N​x(n)(y(n)−y^​(n))=−N1​XT(y−y^​),(3.6) ∂ R ( w , b ) ∂ b = − 1 N ∑ n = 1 N ( y ( n ) − y ^ ( n ) ) = − 1 N s u m ( y − y ^ ) 。 ( 3.7 ) {∂R(w,b)\over ∂b}=−{1\over N}∑_{n=1}^N(y^{(n)}-\hat y^{(n)})=−{1\over N}sum(y−\hat y)。(3.7) ∂b∂R(w,b)​=−N1​n=1∑N​(y(n)−y^​(n))=−N1​sum(y−y^​)。(3.7)通常将偏导数的计算过程定义在Logistic回归算子的backward函数中,代码实现如下:

class model_LR(op.Op):def __init__(self, input_dim):super(model_LR, self).__init__()# 存放线性层参数self.params = {}# 将线性层的权重参数全部初始化为0self.params['w'] = torch.zeros(size=[input_dim, 1])# self.params['w'] = paddle.normal(mean=0, std=0.01, shape=[input_dim, 1])# 将线性层的偏置参数初始化为0self.params['b'] = torch.zeros(size=[1])# 存放参数的梯度self.grads = {}self.X = Noneself.outputs = Nonedef __call__(self, inputs):return self.forward(inputs)def forward(self, inputs):self.X = inputs# 线性算子score = torch.matmul(inputs, self.params['w']) + self.params['b']# Logistic 函数self.outputs = logistic(score)return self.outputsdef backward(self, labels):''':param labels:- labels:真实标签,shape=[N, 1]'''N = labels.shape[0]# 计算偏导数self.grads['w'] = -1 / N * torch.matmul(self.X.t(), (labels - self.outputs))self.grads['b'] = -1 / N * torch.sum(labels - self.outputs)

3.1.4.2 参数更新

在计算参数的梯度之后,我们按照下面公式更新参数:
w ← w − α ∂ R ( w , b ) ∂ w ,( 3.8 ) w←w−α{∂R(w,b) \over ∂w},(3.8) w←w−α∂w∂R(w,b)​,(3.8) b ← b − α ∂ R ( w , b ) ∂ w ,( 3.9 ) b←b−α{∂R(w,b) \over ∂w},(3.9) b←b−α∂w∂R(w,b)​,(3.9)其中 α α α 为学习率。

将上面的参数更新过程包装为优化器,首先定义一个优化器基类Optimizer,方便后续所有的优化器调用。在这个基类中,需要初始化优化器的初始学习率init_lr,以及指定优化器需要优化的参数。

代码实现如下:

from abc import abstractmethod# 优化器基类
class Optimizer(object):def __init__(self, init_lr, model):"""优化器类初始化"""# 初始化学习率,用于参数更新的计算self.init_lr = init_lr# 指定优化器需要优化的模型self.model = model@abstractmethoddef step(self):"""定义每次迭代如何更新参数"""pass

然后实现一个梯度下降法的优化器函数SimpleBatchGD来执行参数更新过程。其中step函数从模型的grads属性取出参数的梯度并更新。

代码实现如下:

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]

小笔记:

  1. isinstance(self.model.params, dict), isinstance用来判断是否是类型
  2. 使用abstractmethod装饰器定义抽象方法或抽象属性

3.1.5 评价指标

在分类任务中,通常使用准确率(Accuracy)作为评价指标。如果模型预测的类别与真实类别一致,则说明模型预测正确。准确率即正确预测的数量与总的预测数量的比值:
A = 1 N ∑ n = 1 N I ( y ( n ) = y ^ ( n ) ) , ( 3.10 ) A={1\over N}∑_{n=1}^NI(y^{(n)}=\hat y^{(n)}),(3.10) A=N1​n=1∑N​I(y(n)=y^​(n)),(3.10)其中 I ( ⋅ ) I(⋅) I(⋅)是指示函数。

代码实现如下:

def 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.as_tensor()'将preds的数据类型转换为float32类型preds = torch.as_tensor((preds >= 0.5),dtype=torch.float32)else:# 多分类时,使用'torch.argmax'计算最大元素索引作为类别preds = torch.argmax(preds, dim=1).int()return torch.mean(torch.as_tensor((preds == labels),dtype=torch.float32))# 假设模型的预测值为[[0.],[1.],[1.],[0.]],真实类别为[[1.],[1.],[0.],[0.]],计算准确率
preds = torch.Tensor([[0.], [1.], [1.], [0.]])
labels = torch.Tensor([[1.], [1.], [0.], [0.]])
print("accuracy is:", accuracy(preds, labels))

运行结果:

accuracy is: tensor(0.5000)

paddle.equal(),用来判断两个数是否相等,相等返回True,用pytorch的话,可以直接用(a==b),或者a.eq(b)来实现

3.1.6 完善Runner类

基于RunnerV1,本章的RunnerV2类在训练过程中使用梯度下降法进行网络优化,模型训练过程中计算在训练集和验证集上的损失及评估指标并打印,训练过程中保存最优模型。

代码实现如下:

# 用RunnerV2类封装整个训练过程
class RunnerV2(object):def __init__(self, model, optimizer, metric, loss_fn):self.model = modelself.optimizer = optimizerself.metric = metricself.loss_fn = loss_fn# 记录训练过程中的评价指标变化情况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)

3.1.7 模型训练

下面进行Logistic回归模型的训练,使用交叉熵损失函数和梯度下降法进行优化。
使用训练集和验证集进行模型训练,共训练 500个epoch,每隔50个epoch打印出训练集上的指标。

代码实现如下:

# 固定随机种子,保持每次运行结果一致
torch.manual_seed(102)# 特征维度
input_dim = 2
# 学习率
lr = 0.1# 实例化模型
model = model_LR(input_dim=input_dim)
# 指定优化器
optimizer = SimpleBatchGD(init_lr=lr, model=model)
# 指定损失函数
loss_fn = BinaryCrossEntropyLoss()
# 指定评价方式
metric = accuracy# 实例化RunnerV2类,并传入训练配置
runner = RunnerV2(model, optimizer, metric, loss_fn)runner.train([X_train, y_train], [X_dev, y_dev], num_epochs=500, log_epochs=50, save_path="best_model.pdparams")

运行结果:

best accuracy performence has been updated: 0.00000 --> 0.75000
[Train] epoch: 0, loss: 0.693146824836731, score: 0.5
[Dev] epoch: 0, loss: 0.6844645738601685, score: 0.75
[Train] epoch: 50, loss: 0.48319950699806213, score: 0.807812511920929
[Dev] epoch: 50, loss: 0.519908607006073, score: 0.75
[Train] epoch: 100, loss: 0.4398561418056488, score: 0.8140624761581421
[Dev] epoch: 100, loss: 0.4893949627876282, score: 0.75
best accuracy performence has been updated: 0.75000 --> 0.75625
[Train] epoch: 150, loss: 0.42317506670951843, score: 0.817187488079071
[Dev] epoch: 150, loss: 0.4799766540527344, score: 0.7562500238418579
best accuracy performence has been updated: 0.75625 --> 0.76250
[Train] epoch: 200, loss: 0.41500502824783325, score: 0.823437511920929
[Dev] epoch: 200, loss: 0.47652289271354675, score: 0.762499988079071
[Train] epoch: 250, loss: 0.4104517996311188, score: 0.8203125
[Dev] epoch: 250, loss: 0.47522956132888794, score: 0.7437499761581421
[Train] epoch: 300, loss: 0.40770575404167175, score: 0.8218749761581421
[Dev] epoch: 300, loss: 0.4748341143131256, score: 0.75
[Train] epoch: 350, loss: 0.4059614837169647, score: 0.823437511920929
[Dev] epoch: 350, loss: 0.4748414158821106, score: 0.7562500238418579
[Train] epoch: 400, loss: 0.40481358766555786, score: 0.8265625238418579
[Dev] epoch: 400, loss: 0.47503310441970825, score: 0.75
[Train] epoch: 450, loss: 0.40403881669044495, score: 0.828125
[Dev] epoch: 450, loss: 0.4753051698207855, score: 0.75

可视化观察训练集与验证集的准确率和损失的变化情况。

# 可视化观察训练集与验证集的指标变化情况
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()plot(runner,fig_name='linear-acc.pdf')

运行结果:

从输出结果可以看到,在训练集与验证集上,loss得到了收敛,同时准确率指标都达到了较高的水平,训练比较充分。

3.1.8 模型评价

使用测试集对训练完成后的最终模型进行评价,观察模型在测试集上的准确率和loss数据。

代码实现如下:

# 3.1.8 模型评价
score, loss = runner.evaluate([X_test, y_test])
print("[Test] score/loss: {:.4f}/{:.4f}".format(score, loss))

运行结果:

[Test] score/loss: 0.8100/0.4706

可视化观察拟合的决策边界 X w + b = 0 Xw+b=0 Xw+b=0。

def decision_boundary(w, b, x1):w1, w2 = wx2 = (- w1 * x1 - b) / w2return x2
plt.figure(figsize=(5,5))
# 绘制原始数据
plt.scatter(X[:, 0].tolist(), X[:, 1].tolist(), marker='*', c=y.tolist())w = model.params['w']
b = model.params['b']
x1 = torch.linspace(-2, 3, 1000)
x2 = decision_boundary(w, b, x1)
# 绘制决策边界
plt.plot(x1.tolist(), x2.tolist(), color="red")
plt.show()

运行结果:


内容太多了,如果太多了,可能会没有耐心看完,而且查找也不是很方便。
这里我分成了上中下三篇,分别为基于Logistic回归的二分类任务(上篇),基于Softmax回归的多分类任务(中篇),实践:基于Softmax回归完成鸢尾花分类任务(下篇)。

中篇:深度学习 第3章线性分类 实验四 pytorch实现 Softmax回归 中篇
下篇:深度学习 第3章线性分类 实验四 pytorch实现 Softmax回归 鸢尾花分类任务 下篇


创作不易,如果对你有帮助,求求你给我个赞!!!
点赞 + 收藏 + 关注!!!
如有错误与建议,望告知!!!(将于下篇文章更正)
请多多关注我!!!谢谢!!!

深度学习 第3章线性分类 实验四 pytorch实现 Logistic回归 上篇相关推荐

  1. 深度学习 第3章线性分类 实验四 pytorch实现 Softmax回归 鸢尾花分类任务 下篇

    目录: 第3章 线性分类 3.3 实践:基于Softmax回归完成鸢尾花分类任务 3.3.1 数据处理 3.3.1.1 数据集介绍 3.3.1.2 数据清洗 1. 缺失值分析 2. 异常值处理 3.3 ...

  2. 深度学习原理—代码分析线性分类与神经网络分类的区别

    https://www.toutiao.com/a6687727778487337476/ 利用sklearn.dataset随机产生数据,随机生成两类数据,用不同的颜色展示出来,如下图: 产生的随机 ...

  3. [翻译] 神经网络与深度学习 第六章 深度学习 - Chapter 6 Deep learning

    目录: 首页 译序 关于本书 关于习题和难题 第一章 利用神经网络识别手写数字 第二章 反向传播算法是如何工作的 第三章 提升神经网络学习的效果 第四章 可视化地证明神经网络可以计算任何函数 第五章 ...

  4. 基于深度学习的弹道目标智能分类

    关注微信公众号:人工智能技术与咨询.了解更多资讯! 来源:系统工程与电子技术,作者李江等 摘要 针对弹道目标微动分类前需平动补偿及典型雷达散射截面积(radar cross-section, RCS) ...

  5. bert使用做文本分类_使用BERT进行深度学习的多类文本分类

    bert使用做文本分类 Most of the researchers submit their research papers to academic conference because its ...

  6. 《Python深度学习》第一章笔记

    <Python深度学习>第一章笔记 1.1人工智能.机器学习.深度学习 人工智能 机器学习 深度学习 深度学习的工作原理 1.2深度学习之前:机器学习简史 概率建模 早期神经网络 核方法 ...

  7. 深度学习 第三章 tensorflow手写数字识别

    深度学习入门视频-唐宇迪 (笔记加自我整理) 深度学习 第三章 tensorflow手写数字识别 1.tensorflow常见操作 这里使用的是tensorflow1.x版本,tensorflow基本 ...

  8. 基于深度学习的网络加密流量分类与入侵检测框架

    写在前面: 本文翻译供个人研究学习之用,不保证严谨与准确 github链接:https://github.com/WithHades/network_traffic_classification_pa ...

  9. 机器学习_深度学习毕设题目汇总——文本分类

    下面是该类的一些题目:| 题目 | |–| |基于主题特征的多标签文本分类方法研究| |融合全局和局部特征的文本分类方法研究| |BiGRU-CapsNet文本分类模型研究| |基于Attentio ...

最新文章

  1. MapReduce统计排序和HDFS的读写
  2. 总结自己的Git常用命令
  3. win10 下载 linux系统安装教程,Win10安装Linux子系统图文教程
  4. Angela启动步骤
  5. crank storyboard学习笔记(一)环境安装
  6. 破解百度网盘的Pandownload开发者被捕,让人唏嘘
  7. 锁表的进程和语句,并杀掉
  8. MaxCompute - ODPS重装上阵 第一弹 - 善用MaxCompute编译器的错误和警告
  9. react实现页面多个模块的切换
  10. iPhone 14不会全部采用挖孔屏 仅两款Pro版采用
  11. PyTorch: 各种图像格式相互转化
  12. 蓝绿色——三色配色篇
  13. 基于STM32F103——SIM900A发送短信+串口打印
  14. windows系统扩展C盘的工具推荐(解决了C盘和压缩卷不相邻无法扩展C盘问题)
  15. thinkphp3.2 七牛 bad token
  16. Oracle数据库断电致使控制文件不一致的恢复方法
  17. 黑马程序员——OC基础---核心语法(id,构造方法,Category,description,SEL)
  18. 线性代数考研笔记(二)
  19. 【环境问题】Anaconda-Navigator 更新后无法打开,运行出现UnicodeDecodeError的解决方案
  20. 《网络安全审查办法》将影响我们什么?

热门文章

  1. 荣耀x1 鸿蒙系统 优酷投屏,真正的智慧体验 荣耀智慧屏X1智能互联与投屏功能...
  2. sonarqube导出PDF报告
  3. 使用selenium+Chrome()无图版模拟浏览器进行抓取淘宝商品信息
  4. Git服务器所有项目代码迁移,Gitlab代码工程迁移
  5. 【搞一点AUTOSAR】MCAL-ADC转换请求和转换队列
  6. godday生成crt和key文件
  7. 一加7pro何时发布android10,一加7/7Pro Android 10阶段性推送正式开始
  8. ICEY攻略 论如何获得所有奖杯达成成就(没错,我就是拿CSDN写了游戏攻略)
  9. scala的可变列表
  10. Jquery通过submitHandler 实现验证后跳转到别的页面