机器学习笔记--训练一个模型

学习了Andrew NG的机器学习系列课程,下面我们来训练一个神经网络的模型。
设立目标:

  • 找一组数据
  • 定义一个网络
  • 训练网络参数(假设函数、损失函数、随即梯度下降)
  • 测试网络效果

下面的例子使用pytorch的图像分类经典示例,pytorch很适合新人上手,搭建环境方便,api调用简单。

训练一个图像分类器

一、找一组数据

import torch
import torchvision
import torchvision.transforms as transforms

transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

示例中使用了CIFAR-10的数据集。
CIFAR-10数据集由10个类的60000个32x32彩色图像组成,每个类有6000个图像。有50000个训练图像和10000个测试图像。
数据集分为五个训练批次和一个测试批次,每个批次有10000个图像。测试批次包含来自每个类别的恰好1000个随机选择的图像。训练批次以随机顺序包含剩余图像,但一些训练批次可能包含来自一个类别的图像比另一个更多。总体来说,五个训练集之和包含来自每个类的正好5000张图像。
以下是数据集中的类,以及来自每个类的10个随机图像:


image.png

除了CIFAR-10,CIFAR-100也是公开的数据集,包含100个类目。

pytorch提供的torchvision包已经对CIFAR-10做了封装,提供简单易用的API来下载、加载数据集,torchvision.datasets.CIFAR10可以下载并且对图片数据做预处理,示例中将01归一化至-0.50.5。数据集大约几十M,如果无法直接下载(墙),可以直接加载本地./data下的cifar-10-batches-py目录,将download参数改为false。

pytorch的工具包提供的数据集加载函数DataLoader,可以通过shuffle来控制数据集是否被打乱,示例中训练集是被打乱的,测试集则没有,打乱的数据集在重复训练参数收敛后有较好的模型泛化,固定顺序的数据集则在每次预测后得到相同的结果,适合测试复现问题。值得说的是DataLoader函数中的batch_size参数,这个参数来控制一次加载的图片个数,前面Andrew课程中损失函数是针对整个数据集来计算的,而这里batch_size则是每次计算损失函数的图片数量。训练时将训练集拆分为N个子集,这样可以减少每次lost的计算量,而且使模型泛化更好。

二、定义一个神经网络

import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


net = Net()

神经网络可以通过 torch.nn 包来构建。一个 nn.Module 包括层和一个方法 forward(input) 它会返回输出(output)。
上面代码定义的网络如下:


image.png
nn.Conv2d(3, 6, 5)

3是输入数据的channel,6是输出channel,5是卷积核大小。这里注意输出channel和输入channel不是一个维度,假设输入channel是3,输出channel是6,卷积核是5x5,则conv计算时会生成6组filter,每组filter有3个channel(同输入channel),卷积核为5x5,这样假设一份32x32的三通道数据输入,会分别与6组filter在3个通道上做5x5卷积,经过计算,数据从32x32x3变化到28x28x3x6。

self.pool = nn.MaxPool2d(2, 2)

池化,pooling 是仿照人的视觉系统进行降维(降采样),用更高层的抽象表示图像特征。池化层对特征图进行压缩。1.使特征图变小,简化网络计算复杂度,减少下一层的参数和计算量,防止过拟合;2.进行特征压缩,提取特征,保留主要的特征;保持某种不变性。


image.png
self.fc1 = nn.Linear(16 * 5 * 5, 120)

全连接层(fully connected layers,FC)在整个卷积神经网络中起到“分类器”的作用。如果说卷积层、池化层和激活函数层等操作是将原始数据映射到隐层特征空间的话,全连接层则起到将学到的“分布式特征表示”映射到样本标记空间的作用

F.relu()

激活函数,Andrew课程中使用sigmoid。


image.png

ReLU 的优点:
• 分段线性函数。相比于sigmoid/tanh,ReLU 只需要一个阈值就可以得到激活值,而不用去算一大堆复杂的运算。
• 无饱和问题,明显减轻梯度消失问题
• 深度网络能够进行优化的关键
• 加快训练速度

三、训练网络参数(假设函数、损失函数、随即梯度下降)

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

定义一个损失函数和优化器 让我们使用分类交叉熵Cross-Entropy 作损失函数,动量SGD做优化器。
训练网络 这里事情开始变得有趣,我们只需要在数据迭代器上循环传给网络和优化器 输入就可以。

for epoch in range(2):  # loop over the dataset multiple times

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # get the inputs
        inputs, labels = data

        # zero the parameter gradients
        optimizer.zero_grad()

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

        # print statistics
        running_loss += loss.item()
        if i % 2000 == 1999:    # print every 2000 mini-batches
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')

输出:

[1,  2000] loss: 2.187
[1,  4000] loss: 1.852
[1,  6000] loss: 1.672
[1,  8000] loss: 1.566
[1, 10000] loss: 1.490
[1, 12000] loss: 1.461
[2,  2000] loss: 1.389
[2,  4000] loss: 1.364
[2,  6000] loss: 1.343
[2,  8000] loss: 1.318
[2, 10000] loss: 1.282
[2, 12000] loss: 1.286
Finished Training

其中,优化器迭代直至梯度下降收敛。

optimizer.step()

示例中,将CIFAR-10训练集中50000张图片,每4张作为一个子集计算lost优化参数,每2000个子集打印一次平均lost,可以看到lost在不断收敛,12500个子集计算完毕后再重新使用原本数据集计算,这时虽然使用相同的数据集,但是数据顺序被shuffle过,lost仍在收敛,但收敛速度下降。

四、测试网络效果

import matplotlib.pyplot as plt
import numpy as np

def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()

# get some random training images
dataiter = iter(trainloader)
images, labels = dataiter.next()

# show images
imshow(torchvision.utils.make_grid(images, 4))
# print labels
print(' '.join('%5s' % classes[labels[j]] for j in range(4)))

outputs = net(images)
_, predicted = torch.max(outputs, 1)

print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]  for j in range(4)))

输出:


image.png
Predicted:    cat truck horse  deer

50%正确率!

代码中,

plt.imshow(np.transpose(npimg, (1, 2, 0)))

matplotlib.pyplot.imshow接收的是RGB数据,这时候CIFAR-10的tensor数据需要transpose。

_, predicted = torch.max(outputs, 1)

使用测试集来测试网络效果

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))

五、思考

Q:为什么定义这样的网络,有没有更好的网络,怎么评价一个网络
image.png

A:示例中的网络结构简单,训练速度快,准去率并不高。一个好的网络需要兼顾效果和性能,效果指标由具体解决的问题来确定,性能则需要考虑最终产品的生产环境、兼顾训练成本。
一定范围内,网络的深度越深效果越好,宽度越宽效果越好。

将示例中的网络宽度变大:

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 16, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(16, 32, 5)
        self.fc1 = nn.Linear(32 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 32 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

测试集上准确率从原先的50%提升至66%:

Accuracy of the network on the 10000 test images: 66 %

深度和宽度是深度神经网络的两个基本维度:
1.更深的网络,有更好的非线性表达能力,可以学习更复杂的变换,从而可以拟合更加复杂的特征,更深的网络可以更简单地学习复杂特征。
网络加深会带来梯度不稳定、网络退化的问题,过深的网络会使浅层学习能力下降。深度到了一定程度,性能就不会提升了,还有可能会下降。
2.足够的宽度可以保证每一层都学到丰富的特征,比如不同方向,不同频率的纹理特征。宽度太窄,特征提取不充分,学习不到足够信息,模型性能受限。
宽度贡献了网络大量计算量,太宽的网络会提取过多重复特征,加大模型计算负担。

除了深度和宽度,分辨率也决定着效果和性能。

Q:示例中的网络有多少个参数?

先回忆一下Andrew NG的课程:


image.png

Layer1-->Layer2,共有3(L1) x 3(L2) + 3(bias)个weight
Layer2-->Layer3,共有3(L2) x 1(L3) + 3(bias)个weight

示例中:

self.conv1 = nn.Conv2d(3, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)

Layer0-->Layer1,共有5 x 5(卷积核)x 3(L0) x 6 (L1)+ 6(bias)个weight
Layer1-->Layer2,共有5 x 5(卷积核)x 6(L1) x 16 (L2)+ 16(bias)个weight
Layer2-->Layer3,共有16 * 5 * 5(L2) x 120(L3)+ 120(bias)个weight
Layer3-->Layer4,共有120(L3) x 84(L4)+ 84(bias)个weight
Layer4-->Layer5,共有84(L4) x 10(L5) + 10(bias)个weight

pythorch中可以直接输出weight,我们来验证一下:

        params = list(net.parameters())
        print(len(params))
        for param in params:
            print(param.size())

输出:

10
torch.Size([6, 3, 5, 5])
torch.Size([6])
torch.Size([16, 6, 5, 5])
torch.Size([16])
torch.Size([120, 400])
torch.Size([120])
torch.Size([84, 120])
torch.Size([84])
torch.Size([10, 84])
torch.Size([10])
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容