NNDL 实验五 前馈神经网络(3)鸢尾花分类

文章目录

前言

一、深入研究鸢尾花数据集

二、实践:基于前馈神经网络完成鸢尾花分类

4.5.1 小批量梯度下降法

4.5.1.1 数据分组 

4.5.2 数据处理

4.5.2.2 用DataLoader进行封装

4.5.3 模型构建

4.5.4 完善Runner类

4.5.5 模型训练 

4.5.6 模型评价 

4.5.7 模型预测 

 思考题

1. 对比Softmax分类和前馈神经网络分类。(必做)

2. 自定义隐藏层层数和每个隐藏层中的神经元个数,尝试找到最优超参数完成多分类。(选做)

3. 对比SVM与FNN分类效果,谈谈自己看法。(选做)

4. 尝试基于MNIST手写数字识别数据集,设计合适的前馈神经网络进行实验,并取得95%以上的准确率。(有与卷积的对比)(选做)


前言

   这次我还是写的很细,有好多东西查了好多资料,有的我给出链接,真的学到很多,并且发现了好多不会的,感觉这次作业是一个研究的过程。

   这次作业是写了好长时间,主要是画图的问题,研究了两天,也没太明白,写了好长时间,这次真的累,希望老师和各位大佬教教我这个问题

   最后,我在下边,提出了我发现的问题,希望老师和各位大佬帮忙解答一下。(跪谢)


一、深入研究鸢尾花数据集

          先用感知机试试来体现后边的优越性

# coding=gbk
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from sklearn.linear_model import Perceptron

"""自定义感知机模型"""
# 数据线性可分,二分类数据
# 此处为一元一次线性方程
class Model:
    def __init__(self):
        # 创建指定形状的数组,数组元素以 1 来填充
        self.w = np.ones(len(data[0]) - 1, dtype=np.float32)
        self.b = 0  # 初始w/b的值
        self.l_rate = 0.1
        # self.data = data

    def sign(self, x, w, b):
        y = np.dot(x, w) + b  # 求w,b的值
        # Numpy中dot()函数主要功能有两个:向量点积和矩阵乘法。
        # 格式:x.dot(y) 等价于 np.dot(x,y) ———x是m*n 矩阵 ,y是n*m矩阵,则x.dot(y) 得到m*m矩阵
        return y

    # 随机梯度下降法
    # 随机梯度下降法(SGD),随机抽取一个误分类点使其梯度下降。根据损失函数的梯度,对w,b进行更新
    def fit(self, X_train, y_train):  # 将参数拟合 X_train数据集矩阵 y_train特征向量
        is_wrong = False
        # 误分类点的意思就是开始的时候,超平面并没有正确划分,做了错误分类的数据。
        while not is_wrong:
            wrong_count = 0  # 误分为0,就不用循环,得到w,b
            for d in range(len(X_train)):
                X = X_train[d]
                y = y_train[d]
                if y * self.sign(X, self.w, self.b) <= 0:
                    # 如果某个样本出现分类错误,即位于分离超平面的错误侧,则调整参数,使分离超平面开始移动,直至误分类点被正确分类。
                    self.w = self.w + self.l_rate * np.dot(y, X)  # 调整w和b
                    self.b = self.b + self.l_rate * y
                    wrong_count += 1
            if wrong_count == 0:
                is_wrong = True
        return 'Perceptron Model!'

    # 得分
    def score(self):
        pass


# 导入数据集
df = pd.read_csv('Iris.csv', usecols=[1, 2, 3, 4, 5])

# pandas打印表格信息
# print(df.info())

# pandas查看数据集的头5条记录
# print(df.head())

"""绘制训练集基本散点图,便于人工分析,观察数据集的线性可分性"""
# 表示绘制图形的画板尺寸为8*5
plt.figure(figsize=(8, 5))
# 散点图的x坐标、y坐标、标签
plt.scatter(df[:50]['SepalLengthCm'], df[:50]['SepalWidthCm'], label='Iris-setosa')
plt.scatter(df[50:100]['SepalLengthCm'], df[50:100]['SepalWidthCm'], label='Iris-versicolor')
plt.scatter(df[100:150]['SepalLengthCm'], df[100:150]['SepalWidthCm'], label='Iris-virginica')
plt.xlabel('SepalLengthCm')
plt.ylabel('SepalWidthCm')
# 添加标题 '鸢尾花萼片的长度与宽度的散点分布'
plt.title('Scattered distribution of length and width of iris sepals.')
# 显示标签
plt.legend()
plt.show()


# 取前100条数据中的:前2个特征+标签,便于训练
data = np.array(df.iloc[:100, [0, 1, -1]])
# 数据类型转换,为了后面的数学计算
X, y = data[:, :-1], data[:, -1]
y = np.array([1 if i == 'Iris-setosa' else -1 for i in y])


"""自定义感知机模型,开始训练"""
perceptron = Model()
perceptron.fit(X, y)
# 最终参数
print(perceptron.w, perceptron.b)
# 绘图
x_points = np.linspace(4, 7, 10)
y_ = -(perceptron.w[0] * x_points + perceptron.b) / perceptron.w[1]
plt.plot(x_points, y_)
plt.scatter(df[:50]['SepalLengthCm'], df[:50]['SepalWidthCm'], label='Iris-setosa')
plt.scatter(df[50:100]['SepalLengthCm'], df[50:100]['SepalWidthCm'], label='Iris-versicolor')
plt.xlabel('SepalLengthCm')
plt.ylabel('SepalWidthCm')
# 添加标题 '自定义感知机模型训练结果'
plt.title('Training results of Custom perceptron model.')
plt.legend()
plt.show()


"""sklearn感知机模型,开始训练"""
# 使用训练数据进行训练
clf = Perceptron()
# 得到训练结果,权重矩阵
clf.fit(X, y)
# Weights assigned to the features.输出特征权重矩阵
# print(clf.coef_)
# 超平面的截距 Constants in decision function.
# print(clf.intercept_)
# 对测试集预测
# print(clf.predict([[6.0, 4.0]]))
# 对训练集评分
# print(clf.score(X, y))

# 绘图
x_points = np.linspace(4, 7, 10)
y_ = -(clf.coef_[0][0] * x_points + clf.intercept_[0]) / clf.coef_[0][1]
plt.plot(x_points, y_)
plt.scatter(df[:50]['SepalLengthCm'], df[:50]['SepalWidthCm'], label='Iris-setosa')
plt.scatter(df[50:100]['SepalLengthCm'], df[50:100]['SepalWidthCm'], label='Iris-versicolor')
plt.xlabel('SepalLengthCm')
plt.ylabel('SepalWidthCm')
# 添加标题 'sklearn感知机模型训练结果'
plt.title('Training results of sklearn perceptron model.')
plt.legend()
plt.show()

画出数据集中150个数据的前两个特征的散点分布图:

    代码的大致思想:

           这个其实就是输出一下数据看一下形状,唯一就是训练了一个感知机大致做了一下分类,但是从上图画出的函数边沿可以看出,这个就是感知机的局限性的体现,这也体现出了神经网络的优越性。

二、实践:基于前馈神经网络完成鸢尾花分类

继续使用第三章中的鸢尾花分类任务,将Softmax分类器替换为前馈神经网络

  • 损失函数:交叉熵损失;
  • 优化器:随机梯度下降法;
  • 评价指标:准确率。

4.5.1 小批量梯度下降法

为了减少每次迭代的计算复杂度,我们可以在每次迭代时只采集一小部分样本,计算在这组样本上损失函数的梯度并更新参数,这种优化方式称为小批量梯度下降法(Mini-Batch Gradient Descent,Mini-Batch GD)。

为了小批量梯度下降法,我们需要对数据进行随机分组。

目前,机器学习中通常做法是构建一个数据迭代器,每个迭代过程中从全部数据集中获取一批指定数量的数据。

在梯度下降法中,目标函数是整个训练集上的风险函数,这种方式称为批量梯度下降法(Batch Gradient Descent,BGD)。 批量梯度下降法在每次迭代时需要计算每个样本上损失函数的梯度并求和。当训练集中的样本数量NN很大时,空间复杂度比较高,每次迭代的计算开销也很大。

为了减少每次迭代的计算复杂度,我们可以在每次迭代时只采集一小部分样本,计算在这组样本上损失函数的梯度并更新参数,这种优化方式称为
小批量梯度下降法(Mini-Batch Gradient Descent,Mini-Batch GD)。

第tt次迭代时,随机选取一个包含KK个样本的子集BtBt,计算这个子集上每个样本损失函数的梯度并进行平均,然后再进行参数更新。

             

其中KK为批量大小(Batch Size)。KK通常不会设置很大,一般在1∼1001∼100之间。在实际应用中为了提高计算效率,通常设置为2的幂2n2n。

在实际应用中,小批量随机梯度下降法有收敛快、计算开销小的优点,因此逐渐成为大规模的机器学习中的主要优化算法。
此外,随机梯度下降相当于在批量梯度下降的梯度上引入了随机噪声。在非凸优化问题中,随机梯度下降更容易逃离局部最优点。

小批量随机梯度下降法的训练过程如下:

 

4.5.1.1 数据分组 

为了小批量梯度下降法,我们需要对数据进行随机分组。目前,机器学习中通常做法是构建一个数据迭代器,每个迭代过程中从全部数据集中获取一批指定数量的数据。

数据迭代器的实现原理如下图所示:

  1. 首先,将数据集封装为Dataset类,传入一组索引值,根据索引从数据集合中获取数据;
  2. 其次,构建DataLoader类,需要指定数据批量的大小和是否需要对数据进行乱序,通过该类即可批量获取数据。

在实践过程中,通常使用进行参数优化。在pytorch中,使用torch.utils.data.DataLoader加载minibatch的数据,
torch.utils.DataLoader API可以生成一个迭代器,其中通过设置batch_size参数来指定minibatch的长度,通过设置shuffle参数为True,可以在生成minibatch的索引列表时将索引顺序打乱。

# coding=gbk
import numpy as np
import torch
import torch.utils.data as data
from nndl import load_data
from torch import nn
from torch.nn.init import constant_, normal_, uniform_
import torch.nn.functional as F
import torch.optim as opt
import torchmetrics
import matplotlib.pyplot as plt
import math
from torch.autograd import Variable

       大家最好看一下,我发的这个函数的引用,并且和之前一样注意上边的解码方式,但是这个最重要的是,我上边函数的调用,大家最好去查一查我调用的函数,因为真的研究了好久。

4.5.2 数据处理

构造IrisDataset类进行数据读取,继承自paddle.io.Dataset类。paddle.io.Dataset是用来封装 Dataset的方法和行为的抽象类,通过一个索引获取指定的样本,同时对该样本进行数据处理。当继承paddle.io.Dataset来定义数据读取类时,实现如下方法:

  • __getitem__:根据给定索引获取数据集中指定样本,并对样本进行数据处理;
  • __len__:返回数据集样本个数。
class IrisDataset(data.Dataset):
    def __init__(self, mode='train', num_train=120, num_dev=15):
        super(IrisDataset, self).__init__()
        # 调用第三章中的数据读取函数,其中不需要将标签转成one-hot类型
        X, y = load_data(shuffle=True)
        if mode == 'train':
            self.X, self.y = X[:num_train], y[:num_train]
        elif mode == 'dev':
            self.X, self.y = X[num_train:num_train + num_dev], y[num_train:num_train + num_dev]
        else:
            self.X, self.y = X[num_train + num_dev:], y[num_train + num_dev:]

    def __getitem__(self, idx):
        return self.X[idx], self.y[idx]

    def __len__(self):
        return len(self.y)
torch.manual_seed(12)
train_dataset = IrisDataset(mode='train')
dev_dataset = IrisDataset(mode='dev')
test_dataset = IrisDataset(mode='test')
# 打印训练集长度
print ("length of train set: ", len(train_dataset))

运行结果为:

length of train set:  120 

代码的大致思想为:

       这个就是封装了一下,包括读取等函数,后边相当于是实例化调用。

这个大家最好记一下,这种函数封装的方式,这种有助于理解torch中的dataset封装方式,进而理解dataset创建后的迭代器,这个是非常重要的。

4.5.2.2 用DataLoader进行封装

# 批量大小
batch_size = 16

# 加载数据
train_loader = data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = data.DataLoader(dev_dataset, batch_size=batch_size)
test_loader = data.DataLoader(test_dataset, batch_size=batch_size)

代码的大致思想: 

       这个就是相当于16个一捆来分别进行训练,代码就是这个思想。

这个就是我上边说的生成迭代器,所以大家最好去好好看看这个函数。

4.5.3 模型构建

构建一个简单的前馈神经网络进行鸢尾花分类实验。其中输入层神经元个数为4,输出层神经元个数为3,隐含层神经元个数为6。代码实现如下:

# 定义前馈神经网络
class Model_MLP_L2_V3(nn.Module):
    def __init__(self, input_size, output_size, hidden_size):
        super(Model_MLP_L2_V3, self).__init__()
        # 构建第一个全连接层
        self.fc1 = nn.Linear(
            input_size,
            hidden_size,
        )
        self.fc1.weight = torch.nn.Parameter(normal_(self.fc1.weight, mean=0.0, std=0.01))
        self.fc1.bias = torch.nn.Parameter(constant_(self.fc1.bias, val=1.0))
        # 构建第二全连接层
        self.fc2 = nn.Linear(
            hidden_size,
            output_size,
        )
        self.fc1.weight = torch.nn.Parameter(normal_(self.fc1.weight, mean=0.0, std=0.01))
        self.fc1.bias = torch.nn.Parameter(constant_(self.fc1.bias, val=1.0))

        # 定义网络使用的激活函数
        self.act = nn.Sigmoid()

    def forward(self, inputs):
        outputs = self.fc1(inputs)
        outputs = self.act(outputs)
        outputs = self.fc2(outputs)
        return outputs

fnn_model = Model_MLP_L2_V3(input_size=4, output_size=3, hidden_size=6)

4.5.4 完善Runner类

基于RunnerV2类进行完善实现了RunnerV3类。其中训练过程使用自动梯度计算,使用DataLoader加载批量数据,使用随机梯度下降法进行参数优化;模型保存时,使用state_dict方法获取模型参数;模型加载时,使用set_state_dict方法加载模型参数.

由于这里使用随机梯度下降法对参数优化,所以数据以批次的形式输入到模型中进行训练,那么评价指标计算也是分别在每个批次进行的,要想获得每个epoch整体的评价结果,需要对历史评价结果进行累积。这里定义Accuracy类实现该功能。

class Accuracy(object):
    def __init__(self, is_logist=True):

        """
        输入:
           - is_logist: outputs是logist还是激活后的值
        """

        # 用于统计正确的样本个数
        self.num_correct = 0
        # 用于统计样本的总数
        self.num_count = 0
        self.is_logist = is_logist

    def update(self, outputs, labels):
        """
        输入:
           - outputs: 预测值, shape=[N,class_num]
           - labels: 标签值, shape=[N,1]
        """

        # 判断是二分类任务还是多分类任务,shape[1]=1时为二分类任务,shape[1]>1时为多分类任务
        if outputs.shape[1] == 1: # 二分类
            outputs = torch.squeeze(outputs, dim=-1)
            if self.is_logist:
                # logist判断是否大于0
                preds = torch.tensor((outputs>=0), dtype=torch.float32)
            else:
                # 如果不是logist,判断每个概率值是否大于0.5,当大于0.5时,类别为1,否则类别为0
                preds = torch.tensor((outputs>=0.5), dtype=torch.float32)
        else:
            # 多分类时,使用'paddle.argmax'计算最大元素索引作为类别
            preds = torch.argmax(outputs, dim=1)
            preds=torch.tensor(preds,dtype=torch.int64)

        # 获取本批数据中预测正确的样本个数
        labels = torch.squeeze(labels, dim=-1)
        batch_correct = torch.sum(torch.tensor(preds==labels, dtype=torch.float32)).numpy()
        batch_count = len(labels)

        # 更新num_correct 和 num_count
        self.num_correct += batch_correct
        self.num_count += batch_count

    def accumulate(self):
        # 使用累计的数据,计算总的指标
        if self.num_count == 0:
            return 0
        return self.num_correct / self.num_count

    def reset(self):
        # 重置正确的数目和总数
        self.num_correct = 0
        self.num_count = 0

    def name(self):
        return "Accuracy"

        这个要说一下,因为accuracy函数并没有调用metric函数,因为调用是要有super的,直接把metric换成object即可。 

RunnerV3类的代码实现如下:

class RunnerV3(object):
    def __init__(self, model, optimizer, loss_fn, metric, **kwargs):
        self.model = model
        self.optimizer = optimizer
        self.loss_fn = loss_fn
        self.metric = metric  # 只用于计算评价指标

        # 记录训练过程中的评价指标变化情况
        self.dev_scores = []

        # 记录训练过程中的损失函数变化情况
        self.train_epoch_losses = []  # 一个epoch记录一次loss
        self.train_step_losses = []  # 一个step记录一次loss
        self.dev_losses = []

        # 记录全局最优指标
        self.best_score = 0

    def train(self, train_loader, dev_loader=None, **kwargs):
        # 将模型切换为训练模式
        self.model.train()

        # 传入训练轮数,如果没有传入值则默认为0
        num_epochs = kwargs.get("num_epochs", 0)
        # 传入log打印频率,如果没有传入值则默认为100
        log_steps = kwargs.get("log_steps", 100)
        # 评价频率
        eval_steps = kwargs.get("eval_steps", 0)

        # 传入模型保存路径,如果没有传入值则默认为"best_model.pdparams"
        save_path = kwargs.get("save_path", "best_model.pdparams")

        custom_print_log = kwargs.get("custom_print_log", None)

        # 训练总的步数
        num_training_steps = num_epochs * len(train_loader)

        if eval_steps:
            if self.metric is None:
                raise RuntimeError('Error: Metric can not be None!')
            if dev_loader is None:
                raise RuntimeError('Error: dev_loader can not be None!')

        # 运行的step数目
        global_step = 0

        # 进行num_epochs轮训练
        for epoch in range(num_epochs):
            # 用于统计训练集的损失
            total_loss = 0
            for step, data in enumerate(train_loader):
                X, y = data
                # 获取模型预测
                logits = self.model(X)
                loss = self.loss_fn(logits, y)  # 默认求mean
                total_loss += loss

                # 训练过程中,每个step的loss进行保存
                self.train_step_losses.append((global_step, loss.item()))

                if log_steps and global_step % log_steps == 0:
                    print(
                        f"[Train] epoch: {epoch}/{num_epochs}, step: {global_step}/{num_training_steps}, loss: {loss.item():.5f}")

                # 梯度反向传播,计算每个参数的梯度值
                loss.backward()

                if custom_print_log:
                    custom_print_log(self)

                # 小批量梯度下降进行参数更新
                self.optimizer.step()
                # 梯度归零
                self.optimizer.zero_grad()

                # 判断是否需要评价
                if eval_steps > 0 and global_step > 0 and \
                        (global_step % eval_steps == 0 or global_step == (num_training_steps - 1)):

                    dev_score, dev_loss = self.evaluate(dev_loader, global_step=global_step)
                    print(f"[Evaluate]  dev score: {dev_score:.5f}, dev loss: {dev_loss:.5f}")

                    # 将模型切换为训练模式
                    self.model.train()

                    # 如果当前指标为最优指标,保存该模型
                    if dev_score > self.best_score:
                        self.save_model(save_path)
                        print(
                            f"[Evaluate] best accuracy performence has been updated: {self.best_score:.5f} --> {dev_score:.5f}")
                        self.best_score = dev_score

                global_step += 1

            # 当前epoch 训练loss累计值
            trn_loss = (total_loss / len(train_loader)).item()
            # epoch粒度的训练loss保存
            self.train_epoch_losses.append(trn_loss)

        print("[Train] Training done!")

    # 模型评估阶段,使用'paddle.no_grad()'控制不计算和存储梯度
    @torch.no_grad()
    def evaluate(self, dev_loader, **kwargs):
        assert self.metric is not None

        # 将模型设置为评估模式
        self.model.eval()

        global_step = kwargs.get("global_step", -1)

        # 用于统计训练集的损失
        total_loss = 0

        # 重置评价
        self.metric.reset()

        # 遍历验证集每个批次
        for batch_id, data in enumerate(dev_loader):
            X, y = data

            # 计算模型输出
            logits = self.model(X)

            # 计算损失函数
            loss = self.loss_fn(logits, y).item()
            # 累积损失
            total_loss += loss

            # 累积评价
            self.metric.update(logits, y)

        dev_loss = (total_loss / len(dev_loader))
        dev_score = self.metric.accumulate()

        # 记录验证集loss
        if global_step != -1:
            self.dev_losses.append((global_step, dev_loss))
            self.dev_scores.append(dev_score)

        return dev_score, dev_loss

    # 模型评估阶段,使用'paddle.no_grad()'控制不计算和存储梯度
    @torch.no_grad()
    def predict(self, x, **kwargs):
        # 将模型设置为评估模式
        self.model.eval()
        # 运行模型前向计算,得到预测值
        logits = self.model(x)
        return logits

    def save_model(self, save_path):
        torch.save(self.model.state_dict(), save_path)

    def load_model(self, model_path):
        model_state_dict = torch.load(model_path)
        self.model.load_state_dict(model_state_dict)

4.5.5 模型训练 

实例化RunnerV3类,并传入训练配置,代码实现如下:

lr = 0.2

# 定义网络
model = fnn_model

# 定义优化器
optimizer = opt.SGD(params=model.parameters(),lr=lr)

# 定义损失函数。softmax+交叉熵
loss_fn = F.cross_entropy

# 定义评价指标
metric = Accuracy(is_logist=True)

runner = RunnerV3(model, optimizer, loss_fn, metric)

 使用训练集和验证集进行模型训练,共训练150个epoch。在实验中,保存准确率最高的模型作为最佳模型。代码实现如下:

# 启动训练
log_steps = 100
eval_steps = 50
runner.train(train_loader, dev_loader,
            num_epochs=150, log_steps=log_steps, eval_steps = eval_steps,
            save_path="best_model.pdparams")

运行结果为:

Train] epoch: 0/150, step: 0/1200, loss: 1.21692
[Evaluate]  dev score: 0.46667, dev loss: 1.09158
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.46667

[Train] epoch: 12/150, step: 100/1200, loss: 1.08905
[Evaluate]  dev score: 0.20000, dev loss: 1.11156
[Evaluate]  dev score: 0.46667, dev loss: 1.06281
[Train] epoch: 25/150, step: 200/1200, loss: 1.10683
[Evaluate]  dev score: 0.20000, dev loss: 1.06005
[Evaluate]  dev score: 0.20000, dev loss: 1.08307
[Train] epoch: 37/150, step: 300/1200, loss: 0.89068
[Evaluate]  dev score: 0.80000, dev loss: 0.85261
[Evaluate] best accuracy performence has been updated: 0.46667 --> 0.80000
[Evaluate]  dev score: 0.80000, dev loss: 0.69397
[Train] epoch: 50/150, step: 400/1200, loss: 0.56133
[Evaluate]  dev score: 0.86667, dev loss: 0.59414
[Evaluate] best accuracy performence has been updated: 0.80000 --> 0.86667
[Evaluate]  dev score: 0.86667, dev loss: 0.51532
[Train] epoch: 62/150, step: 500/1200, loss: 0.42777
[Evaluate]  dev score: 0.93333, dev loss: 0.47453
[Evaluate] best accuracy performence has been updated: 0.86667 --> 0.93333
[Evaluate]  dev score: 0.93333, dev loss: 0.43029
[Train] epoch: 75/150, step: 600/1200, loss: 0.50304
[Evaluate]  dev score: 0.86667, dev loss: 0.42116
[Evaluate]  dev score: 0.93333, dev loss: 0.37402
[Train] epoch: 87/150, step: 700/1200, loss: 0.28087
[Evaluate]  dev score: 0.86667, dev loss: 0.38642
[Evaluate]  dev score: 0.93333, dev loss: 0.33076
[Train] epoch: 100/150, step: 800/1200, loss: 0.33993
[Evaluate]  dev score: 0.93333, dev loss: 0.29876
[Evaluate]  dev score: 0.93333, dev loss: 0.28957
[Train] epoch: 112/150, step: 900/1200, loss: 0.28609
[Evaluate]  dev score: 0.86667, dev loss: 0.28395
[Evaluate]  dev score: 0.93333, dev loss: 0.26278
[Train] epoch: 125/150, step: 1000/1200, loss: 0.16343
[Evaluate]  dev score: 0.86667, dev loss: 0.28682
[Evaluate]  dev score: 0.86667, dev loss: 0.24595
[Train] epoch: 137/150, step: 1100/1200, loss: 0.16323
[Evaluate]  dev score: 0.93333, dev loss: 0.22531
[Evaluate]  dev score: 0.93333, dev loss: 0.21346
[Evaluate]  dev score: 0.86667, dev loss: 0.21146
[Train] Training done!

可视化观察训练集损失和训练集loss变化情况。

# 绘制训练集和验证集的损失变化以及验证集上的准确率变化曲线
def plot_training_loss_acc(runner, fig_name,
                           fig_size=(16, 6),
                           sample_step=20,
                           loss_legend_loc="upper right",
                           acc_legend_loc="lower right",
                           train_color="#e4007f",
                           dev_color='#f19ec2',
                           fontsize='large',
                           train_linestyle="-",
                           dev_linestyle='--'):
    plt.figure(figsize=fig_size)

    plt.subplot(1, 2, 1)
    train_items = runner.train_step_losses[::sample_step]
    train_steps = [x[0] for x in train_items]
    train_losses = [x[1] for x in train_items]

    plt.plot(train_steps, train_losses, color=train_color, linestyle=train_linestyle, label="Train loss")
    if len(runner.dev_losses) > 0:
        dev_steps = [x[0] for x in runner.dev_losses]
        dev_losses = [x[1] for x in runner.dev_losses]
        plt.plot(dev_steps, dev_losses, color=dev_color, linestyle=dev_linestyle, label="Dev loss")
    # 绘制坐标轴和图例
    plt.ylabel("loss", fontsize=fontsize)
    plt.xlabel("step", fontsize=fontsize)
    plt.legend(loc=loss_legend_loc, fontsize='x-large')

    # 绘制评价准确率变化曲线
    if len(runner.dev_scores) > 0:
        plt.subplot(1, 2, 2)
        plt.plot(dev_steps, runner.dev_scores,
                 color=dev_color, linestyle=dev_linestyle, label="Dev accuracy")

        # 绘制坐标轴和图例
        plt.ylabel("score", fontsize=fontsize)
        plt.xlabel("step", fontsize=fontsize)
        plt.legend(loc=acc_legend_loc, fontsize='x-large')

    plt.savefig(fig_name)
    plt.show()


plot_training_loss_acc(runner, 'fw-loss.pdf')

 运行结果为:

从输出结果可以看出准确率随着迭代次数增加逐渐上升,损失函数下降。

上边就是正常的模型训练测试,加可视化,我上一篇博客说的非常详细,大家感兴趣的可以去看一看。

4.5.6 模型评价 

使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及Loss情况。代码实现如下:

# 加载最优模型
runner.load_model('best_model.pdparams')
# 模型评价
score, loss = runner.evaluate(test_loader)
print("[Test] accuracy/loss: {:.4f}/{:.4f}".format(score, loss))

运行结果为:

[Test] accuracy/loss: 1.0000/0.5294 

4.5.7 模型预测 

同样地,也可以使用保存好的模型,对测试集中的某一个数据进行模型预测,观察模型效果。代码实现如下:

# 获取测试集中第一条数据
X, label = next(test_loader())
logits = runner.predict(X)

pred_class = paddle.argmax(logits[0]).numpy()
label = label[0][0].numpy()

# 输出真实类别与预测类别
print("The true category is {} and the predicted category is {}".format(label, pred_class))

运行结果为:

The true category is 2 and the predicted category is 2 

 思考题

1. 对比Softmax分类和前馈神经网络分类。(必做)

先来一下softmax的代码,这个可视化研究了好久,大家最好好好看看这个可视化,真的研究了好久。

# coding=gbk
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.linear_model import LogisticRegression

from matplotlib.colors import ListedColormap

iris = datasets.load_iris()  # 加载数据
list(iris.keys())  # ['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename', 'data_module']

X = iris["data"][:, 3:]  # 花瓣长度
y = (iris["target"] == 2).astype(np.int32)  # 标签,是维吉尼亚鸢尾花y就是1,否则为0

log_reg = LogisticRegression(solver="lbfgs", random_state=42)
log_reg.fit(X, y)  # 训练模型

x0, x1 = np.meshgrid(
    np.linspace(0, 8, 500).reshape(-1, 1),
    np.linspace(0, 4.5, 200).reshape(-1, 1),
)
X_new = np.c_[x0.ravel(), x1.ravel()]


X = iris["data"][:, (0, 1)]  # 花瓣长度, 花瓣宽度
y = iris["target"]
# 设置超参数multi_class为"multinomial",指定一个支持Softmax回归的求解器,默认使用l2正则化,可以通过超参数C进行控制
softmax_reg = LogisticRegression(multi_class="multinomial", solver="lbfgs", C=0.1, random_state=42)
softmax_reg.fit(X, y)




y_proba = softmax_reg.predict_proba(X_new)
y_predict = softmax_reg.predict(X_new)

zz1 = y_proba[:, 1].reshape(x0.shape)
zz = y_predict.reshape(x0.shape)

plt.figure(figsize=(10, 4))
plt.plot(X[y == 2, 0], X[y == 2, 1], "g^", label="Iris virginica")
plt.plot(X[y == 1, 0], X[y == 1, 1], "bs", label="Iris versicolor")
plt.plot(X[y == 0, 0], X[y == 0, 1], "yo", label="Iris setosa")



custom_cmap = ListedColormap(['#FFB6C1', '#FFF0F5', '#FF69B4'])

plt.contourf(x0, x1, zz, cmap=custom_cmap)


plt.xlabel("Petal length", fontsize=14)
plt.ylabel("Petal width", fontsize=14)
plt.legend(loc="center left", fontsize=14)
plt.axis([3, 8, 1, 4.5])
plt.show()

运行结果为:

c=0.1

c=1

c=10 

神经网络(用上边的模型,做一下可视化就行)


# 均匀生成40000个数据点

train=torch.tensor([])
tr_real=torch.tensor([])
# 预测对应类别
for data in train_loader:
    X, trlabel=data
    train=torch.cat((train,X),dim=0)
    tr_real = torch.cat((tr_real, trlabel), dim=0)
y = runner.predict(train)


train_label=[]
for i in y:
    pred_class = torch.argmax(i).numpy()
    train_label.append(pred_class)
print(train_label)




dev=torch.tensor([])
dev_real=torch.tensor([])
# 预测对应类别
for data in test_loader:
    X, delabel=data
    dev=torch.cat((dev,X),dim=0)
    dev_real = torch.cat((dev_real, delabel), dim=0)

y = runner.predict(dev)
dev_label=[]
for i in y :
    pred_class = torch.argmax(i).numpy()
    dev_label.append(pred_class)
print(dev_label)
print(dev)





test=torch.tensor([])
t_real=torch.tensor([])
# 预测对应类别
for data in test_loader:
    X, tlabel=data
    test=torch.cat((test,X),dim=0)
    t_real = torch.cat((t_real,tlabel),dim=0)
y = runner.predict(test)
test_label=[]
for i in y :
    pred_class = torch.argmax(i).numpy()
    test_label.append(pred_class)
print(test_label)


'''
x=torch.tensor([])
for i in range(0,80000):
    zhongjian=torch.randn(size=(1,4))
    x= torch.cat((zhongjian,x), dim=0)
y= runner.predict(x)
ylabel=[]
for i in y :
    pred_class = torch.argmax(i).numpy()
    ylabel.append(pred_class)
'''
x=torch.cat((train,dev),dim=0)
x=torch.cat((x,test),dim=0)
y=torch.cat((tr_real,dev_real),dim=0)
y=torch.cat((y,t_real),dim=0)
print(x.shape,y.shape)
'''
print('x',x.shape,'\n',model.fc1.weight)
# 这里我稍微调整了下plt.contour中的参数,使得结果更好看一点
def plot_decision_boundary(model, x, y):
    x_min, x_max = x[:, 0].min() - 0.5, x[:, 0].max() + 0.5
    y_min, y_max = x[:, 1].min() - 0.5, x[:, 1].max() + 0.5
    x_min1, x_max1 = x[:, 2].min() - 0.5, x[:,2].max() + 0.5
    y_min2, y_max2 = x[:, 3].min() - 0.5, x[:, 3].max() + 0.5
    h = 0.01
    # 绘制网格
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    # 生成与网格上所有点对应的分类结果
    xz=np.c_[xx.ravel(), yy.ravel()]
    z = model(np.c_[xz, xz])
    z = z.reshape(xx.shape)
    # 绘制contour
    plt.contour(xx, yy, z, levels=[0.5], colors=['blue'])
    plt.scatter(x[:, 0], x[:, 1], c=y)
    plt.show()

def plot_network(x):
    x = Variable(torch.from_numpy(x).float())
    x1 = torch.mm(x, model.fc1.weight.T) + model.fc1.bias.T
    x1 = F.sigmoid(x1)
    x2 = torch.mm(x1, model.fc2.weight.T) + model.fc2.bias.T
    out = x2
    y=torch.tensor([])
    for i in out:
        pred_class = torch.argmax(i).numpy()
        y.append(pred_class)
    return y.data.numpy()


plot_decision_boundary(lambda x: plot_network(x), xyong.numpy(), y.numpy())
'''

# 绘制类别区域
plt.ylabel('x2')
plt.xlabel('x1')
plt.scatter(x[:,0].tolist(), x[:,1].tolist(), c=y.tolist(), cmap=plt.cm.Spectral)

plt.scatter(train[:, 0].tolist(), train[:, 1].tolist(), marker='*', c=train_label)
plt.scatter(dev[:, 0].tolist(), dev[:, 1].tolist(), marker='*', c=dev_label)
plt.scatter(test[:, 0].tolist(), test[:, 1].tolist(), marker='*', c=test_label)

plt.show()

运行结果为:

说一下两者的区别:

说区别之前一定要先说定义

softmax分类: 

Softmax Regression,也可以称为多类的logistic 回归,相当于Logistic 回归在多分类问题上的推广。

利用上述Softmax函数,对于多分类模型,在给定样本X下,定义Softmax回归预测属于类别c的条件概率为:

            

为何选择Softmax函数?

1.都具有良好的数据压缩能力是实数域R→[ 0 , 1 ]的映射函数,可以将杂乱无序没有实际含义的数字直接转化为每个分类的可能性概率。

2.都具有非常漂亮的导数形式,便于反向传播计算。

3.它们都是 soft version of max ,都可以将数据的差异明显化。

相同的,他们具有着不同的特点,Sigmoid函数可以看成Softmax函数的特例,softmax函数也可以看作sigmoid函数的推广。

 前馈神经网络神经分类与其相比的区别:

一、计算方法不同

1、前馈神经网络:一种最简单的神经网络,各神经元分层排列。每个神经元只与前一层的神经元相连。接收前一层的输出,并输出给下一层.各层间没有反馈。

二、用途不同

1、前馈神经网络:主要应用包括感知器网络、BP网络和RBF网络。

三、作用不同

1、前馈神经网络:结构简单,应用广泛,能够以任意精度逼近任意连续函数及平方可积函数.而且可以精确实现任意有限训练样本集。

总结:

线性回归和逻辑回归的区别:
逻辑回归=线性回归+sigmoid,然后把损失函数换成交叉熵
或者,
逻辑回归=sigmoid(线性回归),然后把损失函数换成交叉熵
逻辑回归与神经网络的区别:
逻辑回归=单层的多输入单输出的全连接神经网络(以sigmoid为激活函数)
全连接神经网络=多个逻辑回归复合版

2. 自定义隐藏层层数每个隐藏层中的神经元个数,尝试找到最优超参数完成多分类。(选做)

# coding=gbk
import numpy as np
import torch
import torch.utils.data as data
from nndl import load_data
from torch import nn
from torch.nn.init import constant_, normal_, uniform_
import torch.nn.functional as F
import torch.optim as opt
import torchmetrics
import matplotlib.pyplot as plt
import math
from torch.autograd import Variable



class IrisDataset(data.Dataset):
    def __init__(self, mode='train', num_train=120, num_dev=15):
        super(IrisDataset, self).__init__()
        # 调用第三章中的数据读取函数,其中不需要将标签转成one-hot类型
        X, y = load_data(shuffle=True)
        if mode == 'train':
            self.X, self.y = X[:num_train], y[:num_train]
        elif mode == 'dev':
            self.X, self.y = X[num_train:num_train + num_dev], y[num_train:num_train + num_dev]
        else:
            self.X, self.y = X[num_train + num_dev:], y[num_train + num_dev:]

    def __getitem__(self, idx):
        return self.X[idx], self.y[idx]

    def __len__(self):
        return len(self.y)


torch.manual_seed(12)
train_dataset = IrisDataset(mode='train')
dev_dataset = IrisDataset(mode='dev')
test_dataset = IrisDataset(mode='test')




# 打印训练集长度
print ("length of train set: ", len(train_dataset))





# 批量大小
batch_size = 16

# 加载数据
train_loader = data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = data.DataLoader(dev_dataset, batch_size=batch_size)
test_loader = data.DataLoader(test_dataset, batch_size=batch_size)







# 定义前馈神经网络
class Model_MLP_L2_V3(nn.Module):
    def __init__(self, input_size, output_size, hidden_size):
        super(Model_MLP_L2_V3, self).__init__()
        # 构建第一个全连接层
        self.fc1 = nn.Linear(
            input_size,
            hidden_size,
        )
        self.fc1.weight = torch.nn.Parameter(normal_(self.fc1.weight, mean=0.0, std=0.01))
        self.fc1.bias = torch.nn.Parameter(constant_(self.fc1.bias, val=1.0))
        # 构建第二全连接层
        self.fc2 = nn.Linear(
            hidden_size,
            output_size,
        )
        self.fc1.weight = torch.nn.Parameter(normal_(self.fc1.weight, mean=0.0, std=0.01))
        self.fc1.bias = torch.nn.Parameter(constant_(self.fc1.bias, val=1.0))

        # 定义网络使用的激活函数
        self.act = nn.Sigmoid()

    def forward(self, inputs):
        outputs = self.fc1(inputs)
        outputs = self.act(outputs)
        outputs = self.fc2(outputs)
        return outputs

fnn_model = Model_MLP_L2_V3(input_size=4, output_size=3, hidden_size=6)






class Accuracy(object):
    def __init__(self, is_logist=True):

        """
        输入:
           - is_logist: outputs是logist还是激活后的值
        """

        # 用于统计正确的样本个数
        self.num_correct = 0
        # 用于统计样本的总数
        self.num_count = 0
        self.is_logist = is_logist

    def update(self, outputs, labels):
        """
        输入:
           - outputs: 预测值, shape=[N,class_num]
           - labels: 标签值, shape=[N,1]
        """

        # 判断是二分类任务还是多分类任务,shape[1]=1时为二分类任务,shape[1]>1时为多分类任务
        if outputs.shape[1] == 1: # 二分类
            outputs = torch.squeeze(outputs, dim=-1)
            if self.is_logist:
                # logist判断是否大于0
                preds = torch.tensor((outputs>=0), dtype=torch.float32)
            else:
                # 如果不是logist,判断每个概率值是否大于0.5,当大于0.5时,类别为1,否则类别为0
                preds = torch.tensor((outputs>=0.5), dtype=torch.float32)
        else:
            # 多分类时,使用'paddle.argmax'计算最大元素索引作为类别
            preds = torch.argmax(outputs, dim=1)
            preds=torch.tensor(preds,dtype=torch.int64)

        # 获取本批数据中预测正确的样本个数
        labels = torch.squeeze(labels, dim=-1)
        batch_correct = torch.sum(torch.tensor(preds==labels, dtype=torch.float32)).numpy()
        batch_count = len(labels)

        # 更新num_correct 和 num_count
        self.num_correct += batch_correct
        self.num_count += batch_count

    def accumulate(self):
        # 使用累计的数据,计算总的指标
        if self.num_count == 0:
            return 0
        return self.num_correct / self.num_count

    def reset(self):
        # 重置正确的数目和总数
        self.num_correct = 0
        self.num_count = 0

    def name(self):
        return "Accuracy"









class RunnerV3(object):
    def __init__(self, model, optimizer, loss_fn, metric, **kwargs):
        self.model = model
        self.optimizer = optimizer
        self.loss_fn = loss_fn
        self.metric = metric  # 只用于计算评价指标

        # 记录训练过程中的评价指标变化情况
        self.dev_scores = []

        # 记录训练过程中的损失函数变化情况
        self.train_epoch_losses = []  # 一个epoch记录一次loss
        self.train_step_losses = []  # 一个step记录一次loss
        self.dev_losses = []

        # 记录全局最优指标
        self.best_score = 0

    def train(self, train_loader, dev_loader=None, **kwargs):
        # 将模型切换为训练模式
        self.model.train()

        # 传入训练轮数,如果没有传入值则默认为0
        num_epochs = kwargs.get("num_epochs", 0)
        # 传入log打印频率,如果没有传入值则默认为100
        log_steps = kwargs.get("log_steps", 100)
        # 评价频率
        eval_steps = kwargs.get("eval_steps", 0)

        # 传入模型保存路径,如果没有传入值则默认为"best_model.pdparams"
        save_path = kwargs.get("save_path", "best_model.pdparams")

        custom_print_log = kwargs.get("custom_print_log", None)

        # 训练总的步数
        num_training_steps = num_epochs * len(train_loader)

        if eval_steps:
            if self.metric is None:
                raise RuntimeError('Error: Metric can not be None!')
            if dev_loader is None:
                raise RuntimeError('Error: dev_loader can not be None!')

        # 运行的step数目
        global_step = 0

        # 进行num_epochs轮训练
        for epoch in range(num_epochs):
            # 用于统计训练集的损失
            total_loss = 0
            for step, data in enumerate(train_loader):
                X, y = data
                # 获取模型预测
                logits = self.model(X)
                loss = self.loss_fn(logits, y)  # 默认求mean
                total_loss += loss

                # 训练过程中,每个step的loss进行保存
                self.train_step_losses.append((global_step, loss.item()))

                if log_steps and global_step % log_steps == 0:
                    print(
                        f"[Train] epoch: {epoch}/{num_epochs}, step: {global_step}/{num_training_steps}, loss: {loss.item():.5f}")

                # 梯度反向传播,计算每个参数的梯度值
                loss.backward()

                if custom_print_log:
                    custom_print_log(self)

                # 小批量梯度下降进行参数更新
                self.optimizer.step()
                # 梯度归零
                self.optimizer.zero_grad()

                # 判断是否需要评价
                if eval_steps > 0 and global_step > 0 and \
                        (global_step % eval_steps == 0 or global_step == (num_training_steps - 1)):

                    dev_score, dev_loss = self.evaluate(dev_loader, global_step=global_step)
                    print(f"[Evaluate]  dev score: {dev_score:.5f}, dev loss: {dev_loss:.5f}")

                    # 将模型切换为训练模式
                    self.model.train()

                    # 如果当前指标为最优指标,保存该模型
                    if dev_score > self.best_score:
                        self.save_model(save_path)
                        print(
                            f"[Evaluate] best accuracy performence has been updated: {self.best_score:.5f} --> {dev_score:.5f}")
                        self.best_score = dev_score

                global_step += 1

            # 当前epoch 训练loss累计值
            trn_loss = (total_loss / len(train_loader)).item()
            # epoch粒度的训练loss保存
            self.train_epoch_losses.append(trn_loss)

        print("[Train] Training done!")

    # 模型评估阶段,使用'paddle.no_grad()'控制不计算和存储梯度
    @torch.no_grad()
    def evaluate(self, dev_loader, **kwargs):
        assert self.metric is not None

        # 将模型设置为评估模式
        self.model.eval()

        global_step = kwargs.get("global_step", -1)

        # 用于统计训练集的损失
        total_loss = 0

        # 重置评价
        self.metric.reset()

        # 遍历验证集每个批次
        for batch_id, data in enumerate(dev_loader):
            X, y = data

            # 计算模型输出
            logits = self.model(X)

            # 计算损失函数
            loss = self.loss_fn(logits, y).item()
            # 累积损失
            total_loss += loss

            # 累积评价
            self.metric.update(logits, y)

        dev_loss = (total_loss / len(dev_loader))
        dev_score = self.metric.accumulate()

        # 记录验证集loss
        if global_step != -1:
            self.dev_losses.append((global_step, dev_loss))
            self.dev_scores.append(dev_score)

        return dev_score, dev_loss

    # 模型评估阶段,使用'paddle.no_grad()'控制不计算和存储梯度
    @torch.no_grad()
    def predict(self, x, **kwargs):
        # 将模型设置为评估模式
        self.model.eval()
        # 运行模型前向计算,得到预测值
        logits = self.model(x)
        return logits

    def save_model(self, save_path):
        torch.save(self.model.state_dict(), save_path)

    def load_model(self, model_path):
        model_state_dict = torch.load(model_path)
        self.model.load_state_dict(model_state_dict)





lr = 5

# 定义网络
model = fnn_model

# 定义优化器
optimizer = opt.SGD(params=model.parameters(),lr=lr)

# 定义损失函数。softmax+交叉熵
loss_fn = F.cross_entropy

# 定义评价指标
metric = Accuracy(is_logist=True)

runner = RunnerV3(model, optimizer, loss_fn, metric)




# 启动训练
log_steps = 100
eval_steps = 50
runner.train(train_loader, dev_loader,
            num_epochs=150, log_steps=log_steps, eval_steps = eval_steps,
            save_path="best_model.pdparams")








# 绘制训练集和验证集的损失变化以及验证集上的准确率变化曲线
def plot_training_loss_acc(runner, fig_name,
                           fig_size=(16, 6),
                           sample_step=20,
                           loss_legend_loc="upper right",
                           acc_legend_loc="lower right",
                           train_color="#e4007f",
                           dev_color='#f19ec2',
                           fontsize='large',
                           train_linestyle="-",
                           dev_linestyle='--'):
    plt.figure(figsize=fig_size)

    plt.subplot(1, 2, 1)
    train_items = runner.train_step_losses[::sample_step]
    train_steps = [x[0] for x in train_items]
    train_losses = [x[1] for x in train_items]

    plt.plot(train_steps, train_losses, color=train_color, linestyle=train_linestyle, label="Train loss")
    if len(runner.dev_losses) > 0:
        dev_steps = [x[0] for x in runner.dev_losses]
        dev_losses = [x[1] for x in runner.dev_losses]
        plt.plot(dev_steps, dev_losses, color=dev_color, linestyle=dev_linestyle, label="Dev loss")
    # 绘制坐标轴和图例
    plt.ylabel("loss", fontsize=fontsize)
    plt.xlabel("step", fontsize=fontsize)
    plt.legend(loc=loss_legend_loc, fontsize='x-large')

    # 绘制评价准确率变化曲线
    if len(runner.dev_scores) > 0:
        plt.subplot(1, 2, 2)
        plt.plot(dev_steps, runner.dev_scores,
                 color=dev_color, linestyle=dev_linestyle, label="Dev accuracy")

        # 绘制坐标轴和图例
        plt.ylabel("score", fontsize=fontsize)
        plt.xlabel("step", fontsize=fontsize)
        plt.legend(loc=acc_legend_loc, fontsize='x-large')

    plt.savefig(fig_name)
    plt.show()


plot_training_loss_acc(runner, 'fw-loss.pdf')




# 加载最优模型
runner.load_model('best_model.pdparams')
# 模型评价
score, loss = runner.evaluate(test_loader)
print("[Test] accuracy/loss: {:.4f}/{:.4f}".format(score, loss))


# 获取测试集中第一条数据
for data in test_loader:
    X, label=data
    break
X, label=next(iter(test_loader))
logits = runner.predict(X)

pred_class = torch.argmax(logits[0]).numpy()

label = label[0].numpy()


# 输出真实类别与预测类别
print("The true category is {} and the predicted category is {}".format(label, pred_class))





# 均匀生成40000个数据点

train=torch.tensor([])
tr_real=torch.tensor([])
# 预测对应类别
for data in train_loader:
    X, trlabel=data
    train=torch.cat((train,X),dim=0)
    tr_real = torch.cat((tr_real, trlabel), dim=0)
y = runner.predict(train)


train_label=[]
for i in y:
    pred_class = torch.argmax(i).numpy()
    train_label.append(pred_class)
print(train_label)




dev=torch.tensor([])
dev_real=torch.tensor([])
# 预测对应类别
for data in test_loader:
    X, delabel=data
    dev=torch.cat((dev,X),dim=0)
    dev_real = torch.cat((dev_real, delabel), dim=0)

y = runner.predict(dev)
dev_label=[]
for i in y :
    pred_class = torch.argmax(i).numpy()
    dev_label.append(pred_class)
print(dev_label)
print(dev)





test=torch.tensor([])
t_real=torch.tensor([])
# 预测对应类别
for data in test_loader:
    X, tlabel=data
    test=torch.cat((test,X),dim=0)
    t_real = torch.cat((t_real,tlabel),dim=0)
y = runner.predict(test)
test_label=[]
for i in y :
    pred_class = torch.argmax(i).numpy()
    test_label.append(pred_class)
print(test_label)


'''
x=torch.tensor([])
for i in range(0,80000):
    zhongjian=torch.randn(size=(1,4))
    x= torch.cat((zhongjian,x), dim=0)
y= runner.predict(x)
ylabel=[]
for i in y :
    pred_class = torch.argmax(i).numpy()
    ylabel.append(pred_class)
'''
x=torch.cat((train,dev),dim=0)
x=torch.cat((x,test),dim=0)
y=torch.cat((tr_real,dev_real),dim=0)
y=torch.cat((y,t_real),dim=0)
print(x.shape,y.shape)
'''
print('x',x.shape,'\n',model.fc1.weight)
# 这里我稍微调整了下plt.contour中的参数,使得结果更好看一点
def plot_decision_boundary(model, x, y):
    x_min, x_max = x[:, 0].min() - 0.5, x[:, 0].max() + 0.5
    y_min, y_max = x[:, 1].min() - 0.5, x[:, 1].max() + 0.5
    x_min1, x_max1 = x[:, 2].min() - 0.5, x[:,2].max() + 0.5
    y_min2, y_max2 = x[:, 3].min() - 0.5, x[:, 3].max() + 0.5
    h = 0.01
    # 绘制网格
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))
    # 生成与网格上所有点对应的分类结果
    xz=np.c_[xx.ravel(), yy.ravel()]
    z = model(np.c_[xz, xz])
    z = z.reshape(xx.shape)
    # 绘制contour
    plt.contour(xx, yy, z, levels=[0.5], colors=['blue'])
    plt.scatter(x[:, 0], x[:, 1], c=y)
    plt.show()

def plot_network(x):
    x = Variable(torch.from_numpy(x).float())
    x1 = torch.mm(x, model.fc1.weight.T) + model.fc1.bias.T
    x1 = F.sigmoid(x1)
    x2 = torch.mm(x1, model.fc2.weight.T) + model.fc2.bias.T
    out = x2
    y=torch.tensor([])
    for i in out:
        pred_class = torch.argmax(i).numpy()
        y.append(pred_class)
    return y.data.numpy()


plot_decision_boundary(lambda x: plot_network(x), xyong.numpy(), y.numpy())
'''

# 绘制类别区域
plt.ylabel('x2')
plt.xlabel('x1')
plt.scatter(x[:,0].tolist(), x[:,1].tolist(), c=y.tolist(), cmap=plt.cm.Spectral)

plt.scatter(train[:, 0].tolist(), train[:, 1].tolist(), marker='*', c=train_label)
plt.scatter(dev[:, 0].tolist(), dev[:, 1].tolist(), marker='*', c=dev_label)
plt.scatter(test[:, 0].tolist(), test[:, 1].tolist(), marker='*', c=test_label)

plt.show()

运行结果为:

调参其实是要从大向小调的,但是我习惯从小往大调了,下边我会说一下,心得和规则。

隐藏层神经元为4个,lr=0.01(先试0.01,确实比较爱试0.01,哈哈哈)

 隐藏层神经元为4个,lr=0.1

  隐藏层神经元为4个,lr=1

  隐藏层神经元为4个,lr=0.2

        从上边可以看出最好的是当4个隐藏层时,lr=0.3比较好,我发现,好多数据集都是零点几比较好,但是这个不能当做规律,因为之前的经验,还是要每个数据集具体分析。

        下边说一下,我总结的心得(包括也有网上的经验,个人感觉真的很重要):

学习速率(learning rate)。学习速率的设置第一次可以设置大一点的学习率加快收敛,后续慢慢调整;也可以采用动态变化学习速率的方式(比如,每一轮乘以一个衰减系数或者根据损失的变化动态调整学习速率)。

变量初始化(initializer)。常见的变量初始化有零值初始化、随机初始化、均匀分布初始值、正态分布初始值和正交分布初始值。一般采用正态分布或均匀分布的初始化值,有的论文说正交分布的初始值能带来更好的效果。实验的时候可以才正态分布和正交分布初始值做一个尝试。

训练轮数(epoch)。模型收敛即可停止迭代,一般可采用验证集作为停止迭代的条件。如果连续几轮模型损失都没有相应减少,则停止迭代。

优化器。机器学习训练的目的在于更新参数,优化目标函数,常见优化器有SGD,Adagrad,Adadelta,Adam,Adamax,Nadam。其中SGD和Adam优化器是最为常用的两种优化器,SGD根据每个batch的数据计算一次局部的估计,最小化代价函数。
学习速率决定了每次步进的大小,因此我们需要选择一个合适的学习速率进行调优。学习速率太大会导致不收敛,速率太小收敛速度慢。因此SGD通常训练时间更长,但是在好的初始化和学习率调度方案的情况下,结果更可靠。

每轮训练数据乱序(shuffle)。每轮数据迭代保持不同的顺序,避免模型每轮都对相同的数据进行计算。
 

3. 对比SVMFNN分类效果,谈谈自己看法。(选做)

svm的思想(下边代码的思想也是讲解,强烈建议看一下):

        首先是要生成数据集,最直接的想法是,直接把两个数据集分别规定范围,这样生成的数据集必为线性,但是这样线性的性质太强,所以可以适当让两个范围接近,这样出来的直线效果较好,生成时标签一起生成即为+1和-1,dataset函数即为生成数据集的函数,showdataset为画出数据集的函数,在图上画一下看是否可分,然后就是根据公式,计算w和b,运用拉格朗日函数法求对偶后可转化为关于拉格朗格朗日乘子和b的问题,这时运用简化版的SMO的方法,SMO即为求SMO法的函数,具体迭代过程可以参考课件上的图片,解出α和b之后,可运用α与w的关系,解出w,这时直线就出来,get_w为带入解w的函数,其次支持向量就是带入直线后,等于+1和-1的那些向量,在showclassifer的函数中为画出的,test为检验的函数计算正确率,用的是累加的方法,主函数为调用的函数,和划分2/3,1/3的函数。

svm的代码:

# -*- coding:utf-8 -*-
import math
import random
import numpy as np
import matplotlib.pyplot as plt


def zhichi_w(zhichi, xy, a):  # 计算更新 w
    w = [0, 0]
    if len(zhichi) == 0:  # 初始化的0
        return w
    for i in zhichi:
        w[0] += a[i] * xy[0][i] * xy[2][i]  # 更新w
        w[1] += a[i] * xy[1][i] * xy[2][i]
    return w


def zhichi_b(zhichi, xy, a):  # 计算更新 b
    b = 0
    if len(zhichi) == 0:  # 初始化的0
        return 0
    for s in zhichi:  # 对任意的支持向量有 ysf(xs)=1 所有支持向量求解平均值
        sum = 0
        for i in zhichi:
            sum += a[i] * xy[2][i] * (xy[0][i] * xy[0][s] + xy[1][i] * xy[1][s])
        b += 1 / xy[2][s] - sum
    return b / len(zhichi)


def SMO(xy, m):
    a = [0.0] * len(xy[0])  # 拉格朗日乘子
    zhichi = set()  # 支持向量下标
    loop = 1  # 循环标记(符合KKT)
    w = [0, 0]  # 初始化 w
    b = 0  # 初始化 b
    while loop:
        loop += 1
        if loop == 150:
            print("达到早停标准")
            print("循环了:", loop, "次")
            loop = 0
            break
        # 初始化=========================================
        fx = []  # 储存所有的fx
        yfx = []  # 储存所有yfx-1的值
        Ek = []  # Ek,记录fx-y用于启发式搜索
        E_ = -1  # 贮存最大偏差,减少计算
        a1 = 0  # SMO  a1
        a2 = 0  # SMO  a2
        # 初始化结束======================================
        # 寻找a1,a2======================================
        for i in range(len(xy[0])):  # 计算所有的 fx yfx-1 Ek
            fx.append(w[0] * xy[0][i] + w[1] * xy[1][i] + b)  # 计算 fx=wx+b
            yfx.append(xy[2][i] * fx[i] - 1)  # 计算 yfx-1
            Ek.append(fx[i] - xy[2][i])  # 计算 fx-y
            if i in zhichi:  # 之前看过的不看了,防止重复找某个a
                continue
            if yfx[i] <= yfx[a1]:
                a1 = i  # 得到偏离最大位置的下标(数值最小的)
        if yfx[a1] >= 0:  # 最小的也满足KKT
            print("循环了:", loop, "次")
            loop = 0  # 循环标记(符合KKT)置零(没有用到)
            break
        for i in range(len(xy[0])):  # 遍历找间隔最大的a2
            if i == a1:  # 如果是a1,跳过
                continue
            Ei = abs(Ek[i] - Ek[a1])  # |Eki-Eka1|
            if Ei < E_:  # 找偏差
                E_ = Ei  # 储存偏差的值
                a2 = i  # 储存偏差的下标
        # 寻找a1,a2结束===================================
        zhichi.add(a1)  # a1录入支持向量
        zhichi.add(a2)  # a2录入支持向量
        # 分析约束条件=====================================
        # c=a1*y1+a2*y2
        c = a[a1] * xy[2][a1] + a[a2] * xy[2][a2]  # 求出c
        # n=K11+k22-2*k12
        if m == "xianxinghe":  # 线性核
            n = xy[0][a1] ** 2 + xy[1][a1] ** 2 + xy[0][a2] ** 2 + xy[1][a2] ** 2 - 2 * (
                    xy[0][a1] * xy[0][a2] + xy[1][a1] * xy[1][a2])
        elif m == "duoxiangshihe":  # 多项式核(这里是二次)
            n = (xy[0][a1] ** 2 + xy[1][a1] ** 2 + xy[0][a2] ** 2 + xy[1][a2] ** 2 - 2 * (
                    xy[0][a1] * xy[0][a2] + xy[1][a1] * xy[1][a2]))** 2
        else:  # 高斯核  取 2σ^2 = 1
            n = 2 * math.exp(-1) - 2 * math.exp(-((xy[0][a1] - xy[0][a2]) ** 2 + (xy[1][a1] - xy[1][a2]) ** 2))
        # 确定a1的可行域=====================================
        if xy[2][a1] == xy[2][a2]:
            L = max(0.0, a[a1] + a[a2] - 0.5)  # 下界
            H = min(0.5, a[a1] + a[a2])  # 上界
        else:
            L = max(0.0, a[a1] - a[a2])  # 下界
            H = min(0.5, 0.5 + a[a1] - a[a2])  # 上界
        if n > 0:
            a1_New = a[a1] - xy[2][a1] * (Ek[a1] - Ek[a2]) / n  # a1_New = a1_old-y1(e1-e2)/n
            # print("x1=",xy[0][a1],"y1=",xy[1][a1],"z1=",xy[2][a1],"x2=",xy[0][a2],"y2=",xy[1][a2],"z2=",xy[2][a2],"a1_New=",a1_New)
            # 越界裁剪============================================================
            if a1_New >= H:
                a1_New = H
            elif a1_New <= L:
                a1_New = L
        else:
            a1_New = min(H, L)
        # 参数更新=======================================
        a[a2] = a[a2] + xy[2][a1] * xy[2][a2] * (a[a1] - a1_New)  # a2更新
        a[a1] = a1_New  # a1更新
        w = zhichi_w(zhichi, xy, a)  # 更新w
        b = zhichi_b(zhichi, xy, a)  # 更新b
        # print("W=", w, "b=", b, "zhichi=", zhichi, "a1=", a[a1], "a2=", a[a2])
    # 标记支持向量======================================
    for i in zhichi:
        if a[i] == 0:  # 选了,但值仍为0
            loop = loop + 1
            e = 'silver'
        else:
            if xy[2][i] == 1:
                e = 'b'
            else:
                e = 'r'
        plt.scatter(x1[0][i], x1[1][i], c='none', s=100, linewidths=1, edgecolor=e)
    print("支持向量数为:", len(zhichi), "\na为零支持向量:", loop)
    print("有用向量数:", len(zhichi) - loop)
    # 返回数据 w b =======================================
    return [w, b]


def panduan(xyz, w_b1, w_b2):
    c = 0
    for i in range(len(xyz[0])):
        if (xyz[0][i] * w_b1[0][0] + xyz[1][i] * w_b1[0][1] + w_b1[1]) * xyz[2][i][0] < 0:
            c = c + 1
            continue
        if (xyz[0][i] * w_b2[0][0] + xyz[1][i] * w_b2[0][1] + w_b2[1]) * xyz[2][i][1] < 0:
            c = c + 1
            continue
    return (1 - c / len(xyz[0])) * 100


def huitu(x1, x2, wb1, wb2, name):
    x = [x1[0][:], x1[1][:], x1[2][:]]
    for i in range(len(x[2])):  # 对训练集‘上色’
        if x[2][i] == [1, 1]:
            x[2][i] = 'r'  # 训练集  1  1 红色
        elif x[2][i] == [-1, 1]:
            x[2][i] = 'g'  # 训练集 -1  1 绿色
        else:
            x[2][i] = 'b'  # 训练集 -1 -1 蓝色
    plt.scatter(x[0], x[1], c=x[2], alpha=0.8)  # 绘点训练集
    x = [x2[0][:], x2[1][:], x2[2][:]]
    for i in range(len(x[2])):  # 对测试集‘上色’
        if x[2][i] == [1, 1]:
            x[2][i] = 'orange'  # 训练集  1   1 橙色
        elif x[2][i] == [-1, 1]:
            x[2][i] = 'y'  # 训练集 -1   1 黄色
        else:
            x[2][i] = 'm'  # 训练集 -1  -1 紫色
    plt.scatter(x[0], x[1], c=x[2], alpha=0.8)  # 绘点测试集
    plt.xlabel('x')  # x轴标签
    plt.ylabel('y')  # y轴标签
    plt.title(name)  # 标题
    xl = np.arange(min(x[0]), max(x[0]), 0.1)  # 绘制分类线一
    yl = (-wb1[0][0] * xl - wb1[1]) / wb1[0][1]
    plt.plot(xl, yl, 'r')
    xl = np.arange(min(x[0]), max(x[0]), 0.1)  # 绘制分类线二0
    yl = (-wb2[0][0] * xl - wb2[1]) / wb2[0][1]
    plt.plot(xl, yl, 'b')


# 主函数=======================================================
f = open('iris1.txt', 'r')  # 读文件
x = [[], [], [], [], []]  # 花朵属性,(0,1,2,3),花朵种类
while 1:
    yihang = f.readline()  # 读一行
    if len(yihang) <= 1:  # 读到末尾结束
        break
    fenkai = yihang.split('\t')  # 按\t分开
    for i in range(4):  # 分开的四个值
        x[i].append(eval(fenkai[i]))  # 化为数字加到x中
    if (eval(fenkai[4]) == 1):  # 将标签化为向量形式
        x[4].append([1, 1])
    else:
        if (eval(fenkai[4]) == 2):
            x[4].append([-1, 1])
        else:
            x[4].append([-1, -1])

print('数据集=======================================================')
print(len(x[0]))  # 数据大小
# 选择数据===================================================
shuxing1 = eval(input("选取第一个属性:"))
if shuxing1 < 0 or shuxing1 > 4:
    print("无效选项,默认选择第1项")
    shuxing1 = 1
shuxing2 = eval(input("选取第一个属性:"))
if shuxing2 < 0 or shuxing2 > 4 or shuxing1 == shuxing2:
    print("无效选项,默认选择第2项")
    shuxing2 = 2
# 生成数据集==================================================
lt = list(range(150))  # 得到一个顺序序列
random.shuffle(lt)  # 打乱序列
x1 = [[], [], []]  # 初始化x1
x2 = [[], [], []]  # 初始化x2
for i in lt[0:100]:  # 截取部分做训练集
    x1[0].append(x[shuxing1][i])  # 加上数据集x属性
    x1[1].append(x[shuxing2][i])  # 加上数据集y属性
    x1[2].append(x[4][i])  # 加上数据集c标签
for i in lt[100:150]:  # 截取部分做测试集
    x2[0].append(x[shuxing1][i])  # 加上数据集x属性
    x2[1].append(x[shuxing2][i])  # 加上数据集y属性
    x2[2].append(x[4][i])  # 加上数据集c标签
print('\n\n开始训练==============================================')
print('\n线性核==============================================')
# 计算 w b============================================
plt.figure(1)  # 第一张画布
x = [x1[0][:], x1[1][:], []]  # 第一次分类
for i in x1[2]:
    x[2].append(i[0])  # 加上数据集标签
wb1 = SMO(x, "xianxinghe")
x = [x1[0][:], x1[1][:], []]  # 第二次分类
for i in x1[2]:
    x[2].append(i[1])  # 加上数据集标签
wb2 = SMO(x, "xianxinghe")
print("w1为:", wb1[0], " b1为:", wb1[1])
print("w2为:", wb2[0], " b2为:", wb2[1])
# 计算正确率===========================================
print("训练集上的正确率为:", panduan(x1, wb1, wb2), "%")
print("测试集上的正确率为:", panduan(x2, wb1, wb2), "%")
# 绘图 ===============================================
# 圈着的是曾经选中的值,灰色的是选中但更新为0
huitu(x1, x2, wb1, wb2, "xianxinghe")
print('\n多项式核============================================')
# 计算 w b============================================
plt.figure(2)  # 第二张画布
x = [x1[0][:], x1[1][:], []]  # 第一次分类
for i in x1[2]:
    x[2].append(i[0])  # 加上数据集标签
wb1 = SMO(x, "duoxiangshihe")
x = [x1[0][:], x1[1][:], []]  # 第二次分类
for i in x1[2]:
    x[2].append(i[1])  # 加上数据集标签
wb2 = SMO(x, "duoxiangshihe")
print("w1为:", wb1[0], " b1为:", wb1[1])
print("w2为:", wb2[0], " b2为:", wb2[1])
# 计算正确率===========================================
print("训练集上的正确率为:", panduan(x1, wb1, wb2), "%")
print("测试集上的正确率为:", panduan(x2, wb1, wb2), "%")
# 绘图 ===============================================
# 圈着的是曾经选中的值,灰色的是选中但更新为0
huitu(x1, x2, wb1, wb2, "duoxiangshihe")
print('\n高斯核==============================================')
# 计算 w b============================================
plt.figure(3)  # 第三张画布
x = [x1[0][:], x1[1][:], []]  # 第一次分类
for i in x1[2]:
    x[2].append(i[0])  # 加上数据集标签
wb1 = SMO(x, "gaosihe")
x = [x1[0][:], x1[1][:], []]  # 第二次分类
for i in x1[2]:
    x[2].append(i[1])  # 加上数据集标签
wb2 = SMO(x, "gaosihe")
print("w1为:", wb1[0], " b1为:", wb1[1])
print("w2为:", wb2[0], " b2为:", wb2[1])
# 计算正确率===========================================
print("训练集上的正确率为:", panduan(x1, wb1, wb2), "%")
print("测试集上的正确率为:", panduan(x2, wb1, wb2), "%")
# 绘图 ===============================================
# 圈着的是曾经选中的值,灰色的是选中但更新为0
huitu(x1, x2, wb1, wb2, "gaosihe")
# 显示所有图
plt.show()  # 显示

运行结果为:

先说一下我自己的理解:

         其实我尝试过手推svm,感觉这个是真的费劲,但是这个真的是可以推出来,跟着西瓜书推(建议看看胡浩基老师),并且感觉逻辑很是严谨,是很严密的,就相当于把数学过程的实现。

         但是神经网络,虽然都有严格的数学证明,但是感觉这个还是有点蒙蒙的,因为感觉联系不是太强,但是肯定是好用,并且肯定都有严格的证明,只是我菜的原因。

         所以我感觉svm更偏向数学一点,神经网络更像是用着好用,人们就都用了,是实践的产物。

        如果用传统的神经网络来做这个分类问题的话,最终得到的是这无数条线当中的一条而已,而且如果是采用BP的话由于每次初始点选择不一样的话每次得到的结果不一定会一样。

        如果用SVM同样来做这个分类问题的话,那么得到的就是最中间那条线SVM这样一个机制实际上是要求不仅仅把两类分开,而且要尽可能的离两边都远一些。如果把仅仅分开这两类的解称为可行解,那么SVM就是在可行解里边找一个最好的,采用优化的思想来这样理解SVM显得更加有趣一些。也就是神经网络仅仅是找了一个可行解,SVM不满足现状从这些可行解里边找了一个最优解。

优缺点的对比(这个是搜到的,上边的是真实的感受):

1、神经网络优缺点
优点:
神经网络有很强的非线性拟合能力,可映射任意复杂的非线性关系,而且学习规则简单,便于计算机实现。具有很强的鲁棒性、记忆能力、非线性映射能力以及强大的自学习能力,因此有很大的应用市场。
缺点:
(1)最严重的问题是没能力来解释自己的推理过程和推理依据。
(2)不能向用户提出必要的询问,而且当数据不充分的时候,神经网络就无法进行工作。
(3)把一切问题的特征都变为数字,把一切推理都变为数值计算,其结果势必是丢失信息。
(4)理论和学习算法还有待于进一步完善和提高。

2、SVM的优缺点

优点:

(1)非线性映射是SVM方法的理论基础,SVM利用内积核函数代替向高维空间的非线性映射;
(2)对特征空间划分的最优超平面是SVM的目标,最大化分类边际的思想是SVM方法的核心;
(3)支持向量是SVM的训练结果,在SVM分类决策中起决定作用的是支持向量.
(4)SVM 是一种有坚实理论基础的新颖的小样本学习方法.它基本上不涉及概率测度及大数定律等,因此不同于现有的统计方法.从本质上看,它避开了从归纳到演绎的传统过程,实现了高效的从训练样本到预报样本的“转导推理”,大大简化了通常的分类和回归等问题.
(5)SVM 的最终决策函数只由少数的支持向量所确定,计算的复杂性取决于支持向量的数目,而不是样本空间的维数,这在某种意义上避免了“维数灾难”.
(6)少数支持向量决定了最终结果,这不但可以帮助我们抓住关键样本、“剔除”大量冗余样本,而且注定了该方法不但算法简单,而且具有较好的“鲁棒”性.这种“鲁棒”性主要体现在:
①增、删非支持向量样本对模型没有影响;
②支持向量样本集具有一定的鲁棒性;
③有些成功的应用中,SVM 方法对核的选取不敏感
缺点:

(1) SVM算法对大规模训练样本难以实施
由于SVM是借助二次规划来求解支持向量,而求解二次规划将涉及m阶矩阵的计算(m为样本的个数),当m数目很大时该矩阵的存储和计算将耗费大量的机器内存和运算时间.针对以上问题的主要改进有有J.Platt的SMO算法、T.Joachims的SVM、C.J.C.Burges等的PCGC、张学工的CSVM以及O.L.Mangasarian等的SOR算法
(2) 用SVM解决多分类问题存在困难
经典的支持向量机算法只给出了二类分类的算法,而在数据挖掘的实际应用中,一般要解决多类的分类问题.可以通过多个二类支持向量机的组合来解决.主要有一对多组合模式、一对一组合模式和SVM决策树;再就是通过构造多个分类器的组合来解决.主要原理是克服SVM固有的缺点,结合其他算法的优势,解决多类问题的分类精度.如:与粗集理论结合,形成一种优势互补的多类问题的组合分类器。


4. 尝试基于MNIST手写数字识别数据集,设计合适的前馈神经网络进行实验,并取得95%以上的准确率。(有与卷积的对比)(选做)

# coding=gbk
import numpy as np
import torch
import matplotlib.pyplot as plt
from torchvision.datasets import mnist
from torchvision import transforms
from torch.utils.data import DataLoader
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

train_batch_size = 64#超参数
test_batch_size = 128#超参数
learning_rate = 0.01#学习率
nums_epoches = 20#训练次数
lr = 0.1#优化器参数
momentum = 0.5#优化器参数
train_dataset = mnist.MNIST('./data', train=True, transform=transforms.ToTensor(), target_transform=None, download=True)
test_dataset = mnist.MNIST('./data', train=False, transform=transforms.ToTensor(), target_transform=None, download=False)
train_loader = DataLoader(train_dataset, batch_size=train_batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=test_batch_size, shuffle=False)

class model(nn.Module):
    def __init__(self, in_dim, hidden_1, hidden_2, out_dim):
        super(model, self).__init__()
        self.layer1 = nn.Sequential(nn.Linear(in_dim, hidden_1, bias=True), nn.BatchNorm1d(hidden_1))
        self.layer2 = nn.Sequential(nn.Linear(hidden_1, hidden_2, bias=True), nn.BatchNorm1d(hidden_2))
        self.layer3 = nn.Sequential(nn.Linear(hidden_2, out_dim))

    def forward(self, x):
        # 注意 F 与 nn 下的激活函数使用起来不一样的
        x = F.relu(self.layer1(x))
        x = F.relu(self.layer2(x))
        x = self.layer3(x)
        return x

#实例化网络
model = model(28*28,300,100,10)
#定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
#momentum:动量因子
optimizer = optim.SGD(model.parameters(),lr=lr,momentum=momentum)
def train():
    # 开始训练 先定义存储损失函数和准确率的数组
    losses = []
    acces = []
    # 测试用
    eval_losses = []
    eval_acces = []

    for epoch in range(nums_epoches):
        # 每次训练先清零
        train_loss = 0
        train_acc = 0
        # 将模型设置为训练模式
        model.train()
        # 动态学习率
        if epoch % 5 == 0:
            optimizer.param_groups[0]['lr'] *= 0.1
        for img, label in train_loader:
            # 例如 img=[64,1,28,28] 做完view()后变为[64,1*28*28]=[64,784]
            # 把图片数据格式转换成与网络匹配的格式
            img = img.view(img.size(0), -1)
            # 前向传播,将图片数据传入模型中
            # out输出10维,分别是各数字的概率,即每个类别的得分
            out = model(img)
            # 这里注意参数out是64*10,label是一维的64
            loss = criterion(out, label)
            # 反向传播
            # optimizer.zero_grad()意思是把梯度置零,也就是把loss关于weight的导数变成0
            optimizer.zero_grad()
            loss.backward()
            # 这个方法会更新所有的参数,一旦梯度被如backward()之类的函数计算好后,我们就可以调用这个函数
            optimizer.step()
            # 记录误差
            train_loss += loss.item()
            # 计算分类的准确率,找到概率最大的下标
            _, pred = out.max(1)
            num_correct = (pred == label).sum().item()  # 记录标签正确的个数
            acc = num_correct / img.shape[0]
            train_acc += acc
        losses.append(train_loss / len(train_loader))
        acces.append(train_acc / len(train_loader))

        eval_loss = 0
        eval_acc = 0
        model.eval()
        for img, label in test_loader:
            img = img.view(img.size(0), -1)

            out = model(img)
            loss = criterion(out, label)

            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            eval_loss += loss.item()

            _, pred = out.max(1)
            num_correct = (pred == label).sum().item()
            acc = num_correct / img.shape[0]
            eval_acc += acc
        eval_losses.append(eval_loss / len(test_loader))
        eval_acces.append(eval_acc / len(test_loader))

        print('epoch:{},Train Loss:{:.4f},Train Acc:{:.4f},Test Loss:{:.4f},Test Acc:{:.4f}'
              .format(epoch, train_loss / len(train_loader), train_acc / len(train_loader),
                      eval_loss / len(test_loader), eval_acc / len(test_loader)))
    plt.title('trainloss')
    plt.plot(np.arange(len(losses)), losses)
    plt.legend(['Train Loss'], loc='upper right')
#测试
from sklearn.metrics import confusion_matrix
import seaborn as sns
def test():
    correct = 0
    total = 0
    y_predict=[]
    y_true=[]
    with torch.no_grad():
        for data in test_loader:
            input, target = data
            input = input.view(input.size(0), -1)
            output = model(input)#输出十个最大值
            _, predict = torch.max(output.data, dim=1)#元组取最大值的下表
            #
            #print('predict:',predict)
            total += target.size(0)
            correct += (predict == target).sum().item()
            y_predict.extend(predict.tolist())
            y_true.extend(target.tolist())
    print('正确率:', correct / total)
    print('correct=', correct)
    sns.set()
    f, ax = plt.subplots()
    C2 = confusion_matrix(y_true, y_predict, labels=[0, 1, 2,3,4,5,6,7,8,9])
    print(C2)
    plt.imshow(C2, cmap=plt.cm.Blues)
    plt.xticks(range(10),labels=[0, 1, 2,3,4,5,6,7,8,9] , rotation=45)
    plt.yticks(range(10),labels=[0, 1, 2,3,4,5,6,7,8,9])
    plt.colorbar()
    plt.xlabel('True Labels')
    plt.ylabel('Predicted Labels')
    plt.title('Confusion matrix (acc=' + str(correct / total)+ ')')

    plt.show()

train()
test()

运行结果为:

epoch:0,Train Loss:0.3542,Train Acc:0.9152,Test Loss:0.1281,Test Acc:0.9617
epoch:1,Train Loss:0.1271,Train Acc:0.9665,Test Loss:0.0822,Test Acc:0.9767
epoch:2,Train Loss:0.0865,Train Acc:0.9769,Test Loss:0.0619,Test Acc:0.9825
epoch:3,Train Loss:0.0646,Train Acc:0.9825,Test Loss:0.0504,Test Acc:0.9857
epoch:4,Train Loss:0.0536,Train Acc:0.9853,Test Loss:0.0423,Test Acc:0.9891
epoch:5,Train Loss:0.0362,Train Acc:0.9912,Test Loss:0.0265,Test Acc:0.9940
epoch:6,Train Loss:0.0328,Train Acc:0.9924,Test Loss:0.0269,Test Acc:0.9940
epoch:7,Train Loss:0.0312,Train Acc:0.9935,Test Loss:0.0258,Test Acc:0.9941
epoch:8,Train Loss:0.0306,Train Acc:0.9930,Test Loss:0.0258,Test Acc:0.9937
epoch:9,Train Loss:0.0293,Train Acc:0.9938,Test Loss:0.0251,Test Acc:0.9941
epoch:10,Train Loss:0.0276,Train Acc:0.9943,Test Loss:0.0249,Test Acc:0.9944
epoch:11,Train Loss:0.0277,Train Acc:0.9944,Test Loss:0.0242,Test Acc:0.9941
epoch:12,Train Loss:0.0277,Train Acc:0.9945,Test Loss:0.0243,Test Acc:0.9948
epoch:13,Train Loss:0.0277,Train Acc:0.9943,Test Loss:0.0241,Test Acc:0.9947
epoch:14,Train Loss:0.0280,Train Acc:0.9941,Test Loss:0.0237,Test Acc:0.9946
epoch:15,Train Loss:0.0275,Train Acc:0.9945,Test Loss:0.0242,Test Acc:0.9952
epoch:16,Train Loss:0.0278,Train Acc:0.9942,Test Loss:0.0242,Test Acc:0.9950
epoch:17,Train Loss:0.0272,Train Acc:0.9945,Test Loss:0.0248,Test Acc:0.9944
epoch:18,Train Loss:0.0274,Train Acc:0.9946,Test Loss:0.0236,Test Acc:0.9947
epoch:19,Train Loss:0.0273,Train Acc:0.9950,Test Loss:0.0240,Test Acc:0.9946
正确率: 0.9946
correct= 9946

与卷积进行对比: 

代码的大致思想:  

       把训练集分成了,每一百个输出一次,[  ]中的前边表示为第几次训练,后边表示为这是多少——多少的数据集,后边的是损失函数,最后有一个每一次的训练中总的准确率的评估。

       损失函数用的是交叉熵损失函数

       优化器用的是梯度下降法

import torch
import torchvision as tv
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
import argparse
# 定义是否使用GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 定义网络结构
# 超参数设置
EPOCH = 8   #遍历数据集次数
BATCH_SIZE = 64      #批处理尺寸(batch_size)
LR = 0.001        #学习率

# 定义数据预处理方式
transform = transforms.ToTensor()

# 定义训练数据集
trainset = tv.datasets.MNIST(
    root='./data/',
    train=True,
    download=True,
    transform=transform)

# 定义训练批处理数据
trainloader = torch.utils.data.DataLoader(
    trainset,
    batch_size=BATCH_SIZE,
    shuffle=True,
    )

# 定义测试数据集
testset = tv.datasets.MNIST(
    root='./data/',
    train=False,
    download=True,
    transform=transform)

# 定义测试批处理数据
testloader = torch.utils.data.DataLoader(
    testset,
    batch_size=BATCH_SIZE,
    shuffle=False,
    )
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Sequential(     #input_size=(1*28*28)
            nn.Conv2d(1, 6, 5, 1, 2), #padding=2保证输入输出尺寸相同
            nn.ReLU(),      #input_size=(6*28*28)
            nn.MaxPool2d(kernel_size=2, stride=2),#output_size=(6*14*14)
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(6, 16, 5),
            nn.ReLU(),      #input_size=(16*10*10)
            nn.MaxPool2d(2, 2)  #output_size=(16*5*5)
        )
        self.fc1 = nn.Sequential(
            nn.Linear(16 * 5 * 5, 120),
            nn.ReLU()
        )
        self.fc2 = nn.Sequential(
            nn.Linear(120, 84),
            nn.ReLU()
        )
        self.fc3 = nn.Linear(84, 10)

    # 定义前向传播过程,输入为x
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        # nn.Linear()的输入输出都是维度为一的值,所以要把多维度的tensor展平成一维
        x = x.view(x.size()[0], -1)
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.fc3(x)
        return x
#使得我们能够手动输入命令行参数,就是让风格变得和Linux命令行差不多
parser = argparse.ArgumentParser()
parser.add_argument('--outf', default='./model/', help='folder to output images and model checkpoints') #模型保存路径
parser.add_argument('--net', default='./model/net.pth', help="path to netG (to continue training)")  #模型加载路径
opt = parser.parse_args()

# 定义损失函数loss function 和优化方式(采用SGD)
net = LeNet().to(device)
criterion = nn.CrossEntropyLoss()  # 交叉熵损失函数,通常用于多分类问题上
optimizer = torch.optim.Adam(net.parameters(), lr=LR)  # 梯度下降

# 训练
if __name__ == "__main__":

    for epoch in range(EPOCH):
        sum_loss = 0.0
        # 数据读取
        for i, data in enumerate(trainloader):
            inputs, labels = data
            inputs, labels = inputs.to(device), labels.to(device)

            # 梯度清零
            optimizer.zero_grad()

            # forward + backward
            outputs = net(inputs)
            loss = criterion(outputs, labels)
            loss.backward()
            optimizer.step()

            # 每训练100个batch打印一次平均loss
            sum_loss += loss.item()
            if i % 100 == 99:
                print('[%d, %d] loss: %.03f'
                      % (epoch + 1, i + 1, sum_loss / 100))
                sum_loss = 0.0
        # 每跑完一次epoch测试一下准确率
        with torch.no_grad():
            correct = 0
            total = 0
            for data in testloader:
                images, labels = data
                images, labels = images.to(device), labels.to(device)
                outputs = net(images)
                # 取得分最高的那个类
                _, predicted = torch.max(outputs.data, 1)
                total += labels.size(0)
                correct += (predicted == labels).sum()
            print('第%d个epoch的识别准确率为:%d%%' % (epoch + 1, (100 * correct / total)))

运行结果为:

[1, 100] loss: 1.181

[1, 200] loss: 0.373

[1, 300] loss: 0.261

[1, 400] loss: 0.196

[1, 500] loss: 0.150

[1, 600] loss: 0.130

[1, 700] loss: 0.125

[1, 800] loss: 0.105

[1, 900] loss: 0.112

第1个epoch的识别准确率为:97%

[2, 100] loss: 0.077

[2, 200] loss: 0.085

[2, 300] loss: 0.085

[2, 400] loss: 0.080

[2, 500] loss: 0.078

[2, 600] loss: 0.070

[2, 700] loss: 0.073

[2, 800] loss: 0.079

[2, 900] loss: 0.066

第2个epoch的识别准确率为:98%

[3, 100] loss: 0.061

[3, 200] loss: 0.057

[3, 300] loss: 0.057

[3, 400] loss: 0.069

[3, 500] loss: 0.060

[3, 600] loss: 0.051

[3, 700] loss: 0.054

[3, 800] loss: 0.043

[3, 900] loss: 0.050

第3个epoch的识别准确率为:98%

[4, 100] loss: 0.039

[4, 200] loss: 0.046

[4, 300] loss: 0.041

[4, 400] loss: 0.051

[4, 500] loss: 0.040

[4, 600] loss: 0.051

[4, 700] loss: 0.037

[4, 800] loss: 0.039

[4, 900] loss: 0.043

第4个epoch的识别准确率为:98%

[5, 100] loss: 0.035

[5, 200] loss: 0.040

[5, 300] loss: 0.033

[5, 400] loss: 0.033

[5, 500] loss: 0.042

[5, 600] loss: 0.031

[5, 700] loss: 0.030

[5, 800] loss: 0.035

[5, 900] loss: 0.035

第5个epoch的识别准确率为:98%

[6, 100] loss: 0.032

[6, 200] loss: 0.032

[6, 300] loss: 0.024

[6, 400] loss: 0.025

[6, 500] loss: 0.030

[6, 600] loss: 0.030

[6, 700] loss: 0.033

[6, 800] loss: 0.027

[6, 900] loss: 0.030

第6个epoch的识别准确率为:98%

[7, 100] loss: 0.020

[7, 200] loss: 0.019

[7, 300] loss: 0.021

[7, 400] loss: 0.023

[7, 500] loss: 0.021

[7, 600] loss: 0.030

[7, 700] loss: 0.030

[7, 800] loss: 0.022

[7, 900] loss: 0.024

第7个epoch的识别准确率为:98%

[8, 100] loss: 0.017

[8, 200] loss: 0.013

[8, 300] loss: 0.024

[8, 400] loss: 0.019

[8, 500] loss: 0.021

[8, 600] loss: 0.021

[8, 700] loss: 0.026

[8, 800] loss: 0.026

[8, 900] loss: 0.027

第8个epoch的识别准确率为:99%

如果有兴趣的话设置一个time函数,即可比较出两个的区别。

会发现卷积神经网络会快很多。 


总结

        首先,先说一点又破纪录了,最近在不断的打破记录(哈哈哈),但我相信这都是值得的。

        其次,这试验真的真的真的好累(重要的是说三遍,哈哈哈),而且真发现了好多自己的不足,并且,在画图问题上研究了好长时间,也没有太弄出来,这个是真的不会,希望老师和各位大佬,教教我这个,这个是真的难。

         然后,通过这次实验与我之前写的神经网络分类对比,发现之前自己用numpy实现的,真的写的狗屁不是,真的乱,写的可读性太差,有点随心所欲,这次终于是系统了。

         其次,感觉模型的构建套路都差不多,都是那个架子,但是细节有区别,所有我感觉大家要更多的去比较差异性,这样才能学明白原理。

         其次,明白了softmax和神经网络的区别,以前看视频和看书,几乎每个都会讲,但是没啥感觉,这才实践了一下,才感觉透彻了。

         其次,回顾了一下svm,想了想原理,想了想数学推导,感觉这个真的好有用,但是有数据量小的局限性,并且从发展历程来看,并不如神经网络好。

        最后,终于上线下课了,感觉真不错(哈哈哈)

        最后,当然是感谢老师,感谢老师在学习和生活上的关心(这次就以流程图结尾吧,哈哈哈)

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值