Logistic Regression

该概念现目前已经完全被Classification替换掉

就是通过输出加sigmoid函数,让输出接近0或1,达到分类的效果。

目标:最小化

dist(pred,y)

Logistic Regression 一般使用交叉熵作为Loss函数,在Pytoch梯度中的交叉熵一节有对交叉熵loss函数详细的讲解,这里就不再赘述。

多分类问题实战——函数API实现

前面对这一部分的介绍已经非常详细了,这里就不再赘述。

网络结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
w1, b1 = torch.randn(200, 784, requires_grad=True),\
torch.zeros(200, requires_grad=True)
w2, b2 = torch.randn(200, 200, requires_grad=True),\
torch.zeros(200, requires_grad=True)
w3, b3 = torch.randn(10, 200, requires_grad=True),\
torch.zeros(10, requires_grad=True)
# 以下三行是kaiming初始化
torch.nn.init.kaiming_normal_(w1)
torch.nn.init.kaiming_normal_(w2)
torch.nn.init.kaiming_normal_(w3)


def forward(x):
x = x@w1.t() + b1
x = F.relu(x)
x = x@w2.t() + b2
x = F.relu(x)
x = x@w3.t() + b3
x = F.relu(x) # logits
return x

注意:初始化中tensor第一个维度是out(下一层向量长度),第二个维度是in(这一层向量长度)

训练过程

训练过程的原理和博客PyTorch梯度最后讲的一样,这里不再赘述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
optimizer = optim.SGD([w1, b1, w2, b2, w3, b3], lr=learning_rate)
criteon = nn.CrossEntropyLoss()

for epoch in range(epochs):

for batch_idx, (data, target) in enumerate(train_loader):
data = data.view(-1, 28*28)

logits = forward(data)
loss = criteon(logits, target) # 包含了softmax和log求导部分

optimizer.zero_grad()
loss.backward()
# print(w1.grad.norm(), w2.grad.norm())
optimizer.step()

if batch_idx % 100 == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))

汇总

采用的是MINIST数据集,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import  torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms


batch_size=200
learning_rate=0.01
epochs=10

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=batch_size, shuffle=True)



w1, b1 = torch.randn(200, 784, requires_grad=True),\
torch.zeros(200, requires_grad=True)
w2, b2 = torch.randn(200, 200, requires_grad=True),\
torch.zeros(200, requires_grad=True)
w3, b3 = torch.randn(10, 200, requires_grad=True),\
torch.zeros(10, requires_grad=True)
# 以下三行是kaiming初始化
torch.nn.init.kaiming_normal_(w1)
torch.nn.init.kaiming_normal_(w2)
torch.nn.init.kaiming_normal_(w3)


def forward(x):
x = x@w1.t() + b1
x = F.relu(x)
x = x@w2.t() + b2
x = F.relu(x)
x = x@w3.t() + b3
x = F.relu(x)
return x



optimizer = optim.SGD([w1, b1, w2, b2, w3, b3], lr=learning_rate)
criteon = nn.CrossEntropyLoss()

for epoch in range(epochs):

for batch_idx, (data, target) in enumerate(train_loader):
data = data.view(-1, 28*28)

logits = forward(data)
loss = criteon(logits, target) # 包含了softmax和log求导部分

optimizer.zero_grad()
loss.backward()
# print(w1.grad.norm(), w2.grad.norm())
optimizer.step()

if batch_idx % 100 == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))


test_loss = 0
correct = 0
for data, target in test_loader:
data = data.view(-1, 28 * 28)
logits = forward(data)
test_loss += criteon(logits, target).item()

pred = logits.data.max(1)[1]
correct += pred.eq(target.data).sum()

test_loss /= len(test_loader.dataset)
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))

全连接层——类API实现

网络结构

在上一次的实践中,我们使用分散的w,b tensor实现了一个非常简单的三层全连接层(函数API实现)。但是这样虽然简单,但是不够直观,变量稍微有点多,这次我们介绍使用PyTorch自带的API实现一个和上面一样的三层全连接层。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [3]: x=torch.randn(1,784)
In [4]: x.shape
Out[4]: torch.Size([1, 784])
In [5]: from torch import nn
In [6]: layer1=nn.Linear(784,200)
In [7]: layer2=nn.Linear(200,200)
In [8]: layer3=nn.Linear(200,10)
In [9]: x=layer1(x)
In [10]: x.shape
Out[10]: torch.Size([1, 200])
In [11]: x=layer2(x)
In [12]: x.shape
Out[12]: torch.Size([1, 200])
In [13]: x=layer3(x)
In [14]: x.shape
Out[14]: torch.Size([1, 10])

这个只是比较像,但是还是有些区别,因为我们还没有加层之间的激活函数。

下面我们在层之间添加激活函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
In [3]: from torch import nn
In [4]: import torch.nn.functional as F
In [5]: x=torch.randn(1,784)
In [6]: layer1=nn.Linear(784,200)
In [7]: layer2=nn.Linear(200,200)
In [8]: layer3=nn.Linear(200,10)
In [9]: x=layer1(x)
In [10]: x=F.relu(x,inplace=True)
In [11]: x.shape
Out[11]: torch.Size([1, 200])
In [12]: x=layer2(x)
In [13]: x=F.relu(x,inplace=True)
In [14]: x.shape
Out[14]: torch.Size([1, 200])
In [15]: x=layer3(x)
In [16]: x=F.relu(x,inplace=True)
In [17]: x.shape
Out[17]: torch.Size([1, 10])

relu中的inplace参数如果设置为true的话,节省了内存,相当于输出和输入用同一份内存。

学会了如上API定义网络,因为一般情况下的工程,网络都会定义成为一个类,所以这里,我们学习如何将网络定义为一个类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MPL(nn.Module):
def __init__(self):
super(MLP,self).__init__()

self.model=nn.Sequential( #线性容器,可以容纳所有nn.Module类
nn.Linear(784,200),
nn.ReLU(inplace=True),
nn.Linear(200,200),
nn.ReLU(inplace=True),
nn.Linear(200,10),
nn.ReLU(inplace=True),
)
def forward(self,x):
x=self.model(x)

return x

注意区分F.relu(x,inplace=True)和上面nn.ReLU(inplace=True)这两种类型的API,前者是函数类型API,其中的tensor支持自己管理,后者是类-类型API,tensor为类内部变量不能随意访问,使用也必须将类实例化后才可以使用。

训练过程

代码如下,详情见注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
net=MLP()# 实例化网络
optimizer=optim.SGD(net.parameters(),lr=learning_rate) # 这里使用parameters()自动加载目标变量
criteon = nn.CrossEntropyLoss()

for epoch in range(epochs):

for batch_idx, (data,target) in enumerate(train_loader):
data=data.view(-1,28*28)

logits=net(data) # 重载forward后,直接传入参数默认forward
loss=criteon(logits,target)

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

汇总

采用类API书写,使用的是MINIST数据集,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
import  torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms


batch_size=200
learning_rate=0.01
epochs=10

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=batch_size, shuffle=True)



class MLP(nn.Module):

def __init__(self):
super(MLP, self).__init__()

self.model = nn.Sequential(
nn.Linear(784, 200),
nn.ReLU(inplace=True),
nn.Linear(200, 200),
nn.ReLU(inplace=True),
nn.Linear(200, 10),
nn.ReLU(inplace=True),
)

def forward(self, x):
x = self.model(x)

return x

net = MLP()
optimizer = optim.SGD(net.parameters(), lr=learning_rate)
criteon = nn.CrossEntropyLoss()

for epoch in range(epochs):

for batch_idx, (data, target) in enumerate(train_loader):
data = data.view(-1, 28*28)

logits = net(data)
loss = criteon(logits, target)

optimizer.zero_grad()
loss.backward()
# print(w1.grad.norm(), w2.grad.norm())
optimizer.step()

if batch_idx % 100 == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))


test_loss = 0
correct = 0
for data, target in test_loader:
data = data.view(-1, 28 * 28)
logits = net(data)
test_loss += criteon(logits, target).item()

pred = logits.data.max(1)[1]
correct += pred.eq(target.data).sum()

test_loss /= len(test_loader.dataset)
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))

以上为目前的主流版本,值得学习与参考!

激活函数与GPU加速

激活函数

  • tanh——RNN
  • sigmoid——probability
  • ReLU——DL
  • LeakyReLU——DL
  • SELU——优化了ReLU在0点导数不连续的情况
  • softplus——同SELU,光滑了ReLU在0点处的连接

GPU加速

现目前较高版本的PyTorch已经可以使用to方法指定使用特定设备进行运算,而不必像原来使用不同设备进行相同的运算需要调用不同的API。

使用GPU cuda加速运算的代码如下图所示:

1
2
3
4
5
6
7
8
9
10
device = torch.device('cuda:0')
net = MLP().to(device)
optimizer = optim.SGD(net.parameters(), lr=learning_rate)
criteon = nn.CrossEntropyLoss().to(device)

for epoch in range(epochs):

for batch_idx, (data, target) in enumerate(train_loader):
data = data.view(-1, 28*28)
data, target = data.to(device), target.cuda()# 建议统一用to,这里只是想说明用.cuda()也是可以的

上述代码相当于是将网络,loss函数和所有的数据都搬运到了GPU上去。

汇总

采用的是MINIST数据集,

  • 优化激活函数变为LeakyReLU
  • 使用了GPU cuda加速

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import  torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms


batch_size=200
learning_rate=0.01
epochs=10

train_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=True, download=True,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(
datasets.MNIST('../data', train=False, transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])),
batch_size=batch_size, shuffle=True)






class MLP(nn.Module):

def __init__(self):
super(MLP, self).__init__()

self.model = nn.Sequential(
nn.Linear(784, 200),
nn.LeakyReLU(inplace=True),
nn.Linear(200, 200),
nn.LeakyReLU(inplace=True),
nn.Linear(200, 10),
nn.LeakyReLU(inplace=True),
)

def forward(self, x):
x = self.model(x)

return x

device = torch.device('cuda:0')
net = MLP().to(device)
optimizer = optim.SGD(net.parameters(), lr=learning_rate)
criteon = nn.CrossEntropyLoss().to(device)

for epoch in range(epochs):

for batch_idx, (data, target) in enumerate(train_loader):
data = data.view(-1, 28*28)
data, target = data.to(device), target.cuda()

logits = net(data)
loss = criteon(logits, target)

optimizer.zero_grad()
loss.backward()
# print(w1.grad.norm(), w2.grad.norm())
optimizer.step()

if batch_idx % 100 == 0:
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))


test_loss = 0
correct = 0
for data, target in test_loader:
data = data.view(-1, 28 * 28)
data, target = data.to(device), target.cuda()
logits = net(data)
test_loss += criteon(logits, target).item()

pred = logits.data.max(1)[1]
correct += pred.eq(target.data).sum()

test_loss /= len(test_loader.dataset)
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset)))

测试环节

可以发现前面所讲的内容只是覆盖了网络结构的初始化以及训练过程,并没有讲解测试过程,下面我们就来学习一下当PyTorch训练完成后,如何进行测试。

对于MINIST数据集其实就是将最后的算loss和loss.backward()去掉,直接将logits接一个softmax层(其实也可以不加)然后找到最大值的index即可(使用argmax函数)。

计算准确率就是用预测正确的数量除以总数量。

代码实现

下面是一个简单的测试环节的模拟代码,其中计算准确率这一部分还是有一些技术性的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [3]: logits=torch.rand(4,10)
In [4]: import torch.nn.functional as F
In [5]: pred=F.softmax(logits,dim=1)
In [6]: pred.shape
Out[6]: torch.Size([4, 10])
In [7]: pred_label=pred.argmax(dim=1)
In [8]: pred_label
Out[8]: tensor([8, 0, 0, 0])
In [9]: logits.argmax(dim=1)
Out[9]: tensor([8, 0, 0, 0])
In [10]: label=torch.tensor([8,0,1,2])
In [11]: correct=torch.eq(pred_label,label)
In [12]: correct
Out[12]: tensor([ True, True, False, False])
In [13]: correct.sum().float().item()/4
Out[13]: 0.5

还有一些其他的评价参数,比如precision或recall,这些后面会单独写一篇博客进行讲解。

什么时候测试?

  • 在运行完几个Batch后进行一次test
  • 运行完一个epoch后进行一次测试

汇总

代码汇总见上面的汇总模块

Visdom可视化

step1:安装visdom

1
pip install visdom

step2:开启visdom Web服务器

命令行中输入:

1
python -m visdom.server

step3:然后就可以将数据丢入visdom进行可视化查看了

1
2
3
4
from visdom import Visdom
viz=Visdom()
viz.line([0.],[0.],win='train_loss',opts=dict(title='train_loss_title'))# 创建一条直线,前两个参数第一个是y,第二个是x
viz.line([loss.item()],[global_step],win='train_loss',update='append')# 传入仍是numpy数据(image可以接收tensor)
`win`:小窗口ID

env:大窗口ID,大窗口中可以有很多个小窗口,默认是main大窗口 update:若为append表示添加在当前直线的后面,若不指定会被覆盖掉

多条曲线一个窗口

上面的代码实现的是一条曲线一个窗口,下面我们来讲一下如何实现多条曲线画在一个窗口。

1
2
3
4
5
from matplotlib.pyplot import legend
from visdom import Visdom
viz=Visdom()
viz.line([0.,0.],[0.],win='test',opts=dict(title='train_loss&acc',legend=['loss','acc']))
viz.line([test_loss,correct/len(test_loader.dataset)],[global_step],win='train_loss',update='append')

其实就是将y参数的list增加了一个长度,就可以一个小窗口画两条曲线。

效果图

visual x

这是visdom提供的一个可视化的功能

1
2
3
4
5
6
from matplotlib.pyplot import legend
from visdom import Visdom
viz=Visdom()
#MINST为例
viz.images(data.view(-1,1,28,28),win='x') # 对于图片,这里可以直接接收tensor!!!
viz.text(str(pred.detach().cpu().numpy()),win='pred',opts=dict(title='pred')) # 对于String类型还是要先转到cpu然年转numpy然后转string

效果图


本站由 @anonymity 使用 Stellar 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。