将AI拉下神坛(二):重复的学习居然真的有效!来来,先将课文抄写100遍!

1 历经30年的阿拉伯数字学习

爸爸妈妈拿出 0 ~ 910张数字图片……
爸爸妈妈: 这是0,这是1,这是2……这是9

爸爸妈妈又紧接着测试我……
爸爸妈妈: 宝宝这10个数字你都认识了么?

我感觉那个 0 似乎和大苹果有点像,圆圆的,其他的……¥#@@#¥……都是什么玩意儿!
宝宝(我): 0、#¥%@。

爸爸妈妈很有耐心,又教了宝宝一遍。
再次测试
这次宝宝认出了2个数字!
日复一日,年复一年……
爸爸妈妈无数次地重复教我这10个数字……

30年后……
笔者终于可以分辨 0~910 个数字了!
耶!

这个故事再次告诉我们:

重复的学习是有效的!(呃……即便要30年……)

所以,当老师再让我们将背不会的课文抄写100遍的时候,不要再反抗了哦!

2 算法是重复学习的成果

将AI拉下神坛(一):黑白纸片摆出的神经网络

上一篇当中 有小伙伴就产生了疑问

image.png

我们提到的算法由 网络权重(连线) 组成

所谓的算法

然而:
网络,未说明为什么网络是这样;
权重,未说明值是怎么来的……
呃……说起来这算法来的确实有点突兀……

但同样的问题如果你问机器,ta会告诉你说:算法是重复学习的成果

先给权重赋一些随机值,然后不停地将训练集(那10张标准数字)输入给算法。算法正确识别正确了,我们告诉算法这是对的,要继续这么做;算法识别错误了,我们告诉算法识别错误了,然后给出正确的答案,让算法后续改正。

貌似……机器真的可以像人一样……学习?

3 让机器学习

我们只需要4步,即可让机器自行学习。

3.1 定义并加载网络

比如上篇我们提到的 FullConnect 操作,我们用 pytorch 中的 Linear 操作来模拟它,并将它作为网络的唯一操作。

nn.Linear(25, 10, bias=False)

25:即输入25个值(5x5个小纸片)
10:即输出10个值(输入为0~9这10个数字的可能性)
bias=False:不需要偏置参数,可暂时不关注。

pytroch 的 Linear官方算法说明
感兴趣的小伙伴可以参考哦

image.png

3.2 构造并加载数据

我们选择的训练集、测试集数据如下

训练集
测试集

和上篇一致,这次就不解释咯~

3.3 设置训练策略

诸如 学习率损失函数 的选择配置,此篇我们暂不详细说明(以后再讲),目前知道有这个东西就好啦。

3.4 训练

来来,大训 300 回合!

训练 300轮
即:epoch = 300
即:让算法学习训练集的每个数字 300次
即:训练过程要执行算法 10 * 300 = 3000次

以上4步即可完成一个我们数字识别模型的训练。

4 训练结果

4.1 训练集10张图、测试集3张图、训练30000轮

训练集、测试集的准确率随着训练轮数的变换如下:

训练到59轮,训练集完全识别
训练到300轮,测试集识别率66.7%
训练到3000轮,测试集识别率66.7%
训练到30000轮,测试集识别率66.7%

我们发现,训练到第 59轮 时,我们的算法识别率对训练集、测试集已达到是 100%、66.7%,但训练到第 300轮3000轮30000轮时……准确率依然如此,测试集的识别率始终无法提升到100%

5.2 训练集13张图,训练300轮

但如果我们将 3张 测试集的数据也交给机器学习呢?即 训练13张图片

第209轮达到100%的识别率

我们发现,在进行到第 209轮 的时候,所有 13张 图片已经可以完全被机器识别了。
我们来看看机器学到的权重数据。

13张图片训练300轮的权重数据

再看看笔者上篇给出的权重数据:

笔者上篇直接赋值的权重数据

复杂度不在一个层面上……

5 代码

那么,这样一个包含 网络定义、训练集、测试集、训练、模型准确率计算 的完整训练代码,一定很复杂吧?不,它不到 150行!而且只依赖 python环境pytorch、numpy 两个 python工具包,嗯……值得亲自跑跑看!

# coding: UTF-8

import torch
import torch.nn as nn
import numpy as np

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.full_connect = nn.Linear(25, 10, bias=False)

    def forward(self, x):
        out = self.full_connect(x);
        return out;

def load_train_data():
    tdatas_tensor = torch.from_numpy(train_array).float()
    tlabels_tensor = torch.from_numpy(train_label).long()
    return tdatas_tensor, tlabels_tensor


def load_test_data():
    tdatas_tensor = torch.from_numpy(test_array).float()
    tlabels_tensor = torch.from_numpy(test_label).long()
    return tdatas_tensor, tlabels_tensor


def batch_train_one_epock(model, batch_num, train_data, train_label, loss_func, optimizer):
    model.train()

    train_loss = 0.
    train_acc = 0.
    loop_num = int(len(train_data) / batch_num)

    for i in range(loop_num):
        # 1 获取 训练数据 & 训练Label
        batch_input = train_data[i * batch_num: ((i + 1) * batch_num)]
        batch_label = train_label[i * batch_num: ((i + 1) * batch_num)]

        # 2 执行推理
        batch_out = model(batch_input)

        # 3 计算模型损失
        batch_loss = loss_func(batch_out, batch_label)
        train_loss += batch_loss.data

        # 4 模型推理准确值累加(用以计算准确率)
        pred = torch.max(batch_out, 1)[1]
        train_correct = (pred == batch_label).sum()
        train_acc += train_correct.data

        # 5 适时打印信息
        # print("第", epoch + 1, "轮,已训练", i * batch_num, "项,该批Loss:", np.around(batch_loss.data.numpy(), decimals=6));

        # 6 反馈更新权重
        optimizer.zero_grad()
        batch_loss.backward()
        optimizer.step()

    train_num = loop_num * batch_num
    return train_loss, train_acc, train_num


def batch_test(model, batch_num, test_data, test_label, loss_func, optimizer):
    model.eval()

    eval_loss = 0.
    eval_acc = 0.
    loop_num = int(len(test_data) / batch_num)

    for i in range(loop_num):
        # 1 获取 训练数据 & 训练Label
        batch_input = test_data[i * batch_num: ((i + 1) * batch_num)]
        batch_label = test_label[i * batch_num: ((i + 1) * batch_num)]

        # 2 执行推理
        batch_out = model(batch_input)

        # 3 计算模型损失
        batch_loss = loss_func(batch_out, batch_label)
        eval_loss += batch_loss.data
        # print(np.around(eval_loss.data.numpy(), decimals=3));

        # 4 模型推理准确值累加(用以计算准确率)
        pred = torch.max(batch_out, 1)[1]
        num_correct = (pred == batch_label).sum()
        eval_acc += num_correct.data

    test_num = loop_num * batch_num
    return eval_loss, eval_acc, test_num

# 训练集
train_array = np.array( \
    [[0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0], \
     [0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0], \
     [0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0], \
     [0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0], \
     [0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0], \
     [0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0], \
     [0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0], \
     [0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0], \
     [0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0], \
     [0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0], \
     [1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0], \
     [0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0], \
     [0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0] \
     ])

# 训练集标注
train_label = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9 \
                           , 5, 5, 5
                       ])

# 测试集
test_array = np.array( \
    [[1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0], \
     [0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0], \
     [0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0]])

# 测试集标注
test_label = np.array([5, 5, 5]);

# 1 加载模型
model = Net();

# 2 加载训练数据
train_data = torch.from_numpy(train_array).float()
train_label = torch.from_numpy(train_label).long()

# 3 加载测试数据
test_data = torch.from_numpy(test_array).float()
test_label = torch.from_numpy(test_label).long()

# 4 设置训练策略
optimizer = torch.optim.Adam(model.parameters())
loss_func = torch.nn.CrossEntropyLoss()

# 5 训练300轮
for epoch in range(300):

    # 单轮训练
    train_loss, train_acc, train_num = batch_train_one_epock(model, 1, train_data, train_label, loss_func, optimizer)
    print('第', epoch + 1, '轮 ', 'Train Loss: {:.6f}, Acc: {:.6f}'.format(train_loss / train_num, train_acc.float() / train_num))

    # 每轮测试准确率
    test_loss, test_acc, test_num = batch_test(model, 1, test_data, test_label, loss_func, optimizer)
    print('第', epoch + 1, '轮 ', 'Test Loss: {:.6f}, Acc: {:.6f}'.format(test_loss / test_num, test_acc.float() / test_num))

    # print(model.state_dict());

6 结束 or 开始?

我们已经可以训练模型了哎!那么AI灭神计划是不是已经到尾声了呢?不,我们没有解释的问题还很多很多!将AI拉下神坛,才刚刚开始!

你可能感兴趣的:(将AI拉下神坛(二):重复的学习居然真的有效!来来,先将课文抄写100遍!)