一、PyTorch 简介
1、Why PyTorch?
PyTorch 的优势是建立的神经网络是动态的,比如 RNN 变化时间长度的输出。而且深入 API,是可能看懂底层在干嘛。.
Tensorflow 的优势是在分布式训练上下了很多功夫。高度工业化使得很难看懂底层代码。
二、PyTorch 神经网络基础
1、Torch 或 Numpy
Torch 可以将 tensor 放在 GPU 中加速运算(前提是有合适的 GPU),就像 Numpy 会把 array 放在 CPU 中加速运算。而且 torch 做的和 numpy 能够很好的兼容,这样就能自由地转换 numpy array 和 torch tensor 了。
其实 torch 中 tensor 的运算和 numpy array 的如出一辙,我们就以对比的形式来看。如果想了解 torch 中其它更多有用的运算符,API就是你要去的地方。
import torch
import numpy as np
# abs 绝对值计算
data = [-1, -2, 1, 2]
tensor = torch.FloatTensor(data) # 转换成32位浮点 tensor
print(
'\nabs',
'\nnumpy: ', np.abs(data), # [1 2 1 2]
'\ntorch: ', torch.abs(tensor) # [1 2 1 2]
)
# sin 三角函数 sin
print(
'\nsin',
'\nnumpy: ', np.sin(data), # [-0.84147098 -0.90929743 0.84147098 0.90929743]
'\ntorch: ', torch.sin(tensor) # [-0.8415 -0.9093 0.8415 0.9093]
)
# mean 均值
print(
'\nmean',
'\nnumpy: ', np.mean(data), # 0.0
'\ntorch: ', torch.mean(tensor) # 0.0
除了简单的计算,矩阵运算才是神经网络中最重要的部分。
import torch
import numpy as np
data = [[1, 2], [3, 4]]
tensor = torch.FloatTensor(data)
# 矩阵乘法
print('\n', np.matmul(data, data), '\n', torch.mm(tensor, tensor))
# 矩阵乘法
# numpy 和 torch 对于 dot 的不同之处
data = np.array(data)
print('\n', data.dot(data))
# 错误写法,因为新版本的 tensor.dot() 只能针对于一位数组,即转换成向量乘法
# print('\n', tensor.dot(tensor))
print(torch.tensordot(tensor, tensor))
# 对应元素相乘 x^2
data = np.array(data)
print('\n', data * data)
print('\n', tensor * tensor)
2、变量 Variable
在 Torch 中的 Variable 就是一个存放会变化的值的地理位置,里面的值会不停的变化。就像一个裝鸡蛋的篮子,鸡蛋数会不停变动。那谁是里面的鸡蛋呢,自然就是 Torch 的 Tensor 咯。如果用一个 Variable 进行计算,那返回的也是一个同类型的 Variable。
import torch
from torch.autograd import Variable
tensor = torch.FloatTensor([[1, 2], [3, 4]])
# requires_grad 是参与不参与误差反向传播,要不要计算梯度
var = Variable(tensor, requires_grad = True)
print(tensor)
print(var)
t_out = torch.mean(tensor * tensor) # x^2
v_out = torch.mean(var * var) # x^2
print(t_out)
print(v_out)
到目前为止我们看不出什么不同,但是时刻记住,Variable 计算时,它在背景幕布后面一步步默默地搭建着一个庞大的系统,叫做计算图 computational graph
。这个图是用来干嘛的? 原来是将所有的计算步骤 (节点) 都连接起来,最后进行误差反向传递的时候,一次性将所有 variable 里面的修改幅度 (梯度) 都计算出来,而 tensor 就没有这个能力啦。
v_out = torch.mean(variable*variable)
就是在计算图中添加的一个计算步骤,计算误差反向传递的时候有他一份功劳,我们就来举个例子:
v_out.backward() # 模拟 v_out 的误差反向传递
# 下面两步看不懂没关系, 只要知道 Variable 是计算图的一部分, 可以用来传递误差就好.
# v_out = 1/4 * sum(variable*variable) 这是计算图中的 v_out 计算步骤
# 针对于 v_out 的梯度就是, d(v_out)/d(variable) = 1/4*2*variable = variable/2
print(var.grad) # 初始 Variable 的梯度
那如何获取 Variable 里面的数据呢?直接print(variable)
只会输出 Variable 形式的数据,在很多时候是用不了的(比如想要用 plt 画图),所以我们要转换一下, 将它变成 tensor 形式:
print(variable) # Variable 形式
print(variable.data) # tensor 形式
print(variable.data.numpy()) # numpy 形式
3、什么是激励函数 Activation Function
为什么要使用激励函数?
因为激励函数可以解决日常生活中不能用线性方程 linear function 所概括的问题。
比如说我们可以把神经网络简化为 Y = WX,但是这样只能描述一个线性问题,于是就需要激励函数来将这条直线“掰弯”,即 Y = AF(WX)。
AF 其实就是激励函数,常有的选择是 relu sigmoid tanh,把这些掰弯利器嵌套在原有的结果就可以强行把原先的线性结果扭曲成曲线了。同时我们也可以自己创造激励函数,不过要求是必须可微分,因为在 back propagation 误差反向传递时,只有可微分的激励函数才能把误差传递回去。
恰当地使用激励函数,是有窍门的。
- 少量层结构中,对于隐藏层使用任意的激励函数不会有很大的影响。一般地,在 CNN 的卷积层中推荐 Relu,在 RNN 中推荐 tanh 或 Relu。(具体怎么选,看之后 RNN 中详细介绍)
- 多层神经网络中,掰弯利器不能随意选择,因为会涉及到
梯度爆炸
和梯度消失
的问题。
4、激励函数 Activation
什么是 Activation
一句话概括,就是神经网络可以描述非线性问题的步骤,让神经网络变得更强大。
Torch 中的激励函数
Torch 中的激励函数有很多,常用的有relu``sigmoid``tanh``softplus
。
import torch
import torch.nn.functional as F
from torch.autograd import Variable
import matplotlib.pyplot as plt
x = torch.linspace(-5, 5, 200)
x = Variable(x)
x_np = x.data.numpy() # 换成 numpy array,绘图时用
# 几种常用的 激励函数
y_relu = F.relu(x).data.numpy()
# y_sigmoid = F.sigmoid(x).data.numpy() 提示 nn.functional.sigmoid 已弃用
y_sigmoid = torch.sigmoid(x).data.numpy()
# y_tanh = F.tanh(x).data.numpy() 提示 nn.functional.tanh 已弃用
y_tanh = torch.tanh(x).data.numpy()
y_softplus = F.softplus(x).data.numpy()
# y_softmax = F.softmax(x) softmax 比较特殊,不能直接显示,不过它是关于概率的,用于分类
plt.figure(1, figsize=(8, 6))
plt.subplot(221)
plt.plot(x_np, y_relu, c='red', label='relu')
plt.ylim((-1, 5))
plt.legend(loc='best')
plt.subplot(222)
plt.plot(x_np, y_sigmoid, c='red', label='sigmoid')
plt.ylim((-0.2, 1.2))
plt.legend(loc='best')
plt.subplot(223)
plt.plot(x_np, y_tanh, c='red', label='tanh')
plt.ylim((-1.2, 1.2))
plt.legend(loc='best')
plt.subplot(224)
plt.plot(x_np, y_softplus, c='red', label='softplus')
plt.ylim((-0.2, 6))
plt.legend(loc='best')
plt.show()
三、建造第一个神经网络
1、关系拟合 (回归)
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim = 1)
y = x.pow(2) + 0.2*torch.rand(x.size())
# 建立神经网络
# 先定义所有的层属性 __init__(),再一层层搭建层与层的关系链接 forward(x)
class Net(torch.nn.Module):
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__() # 继承 __init__ 功能
self.hidden = torch.nn.Linear(n_feature, n_hidden) # 隐藏层线性输出
self.predict = torch.nn.Linear(n_hidden, n_output) # 输出层线性输出
def forward(self, x): # 这同时也是 Module 中的 forward 功能
# 正向传播输入值,神经网络分析输出值
x = F.relu(self.hidden(x)) # 激励函数(隐藏层的线性值)
x = self.predict(x) # 输出值
return x
net = Net(n_feature=1, n_hidden=10, n_output=1)
print(net) # 输出 net 的结构
# 训练网络
optimizer = torch.optim.SGD(net.parameters(), lr = 0.2) # 传入 net 的所有参数,学习率
loss_func = torch.nn.MSELoss()
'''
for t in range(100):
prediction = net(x)
loss = loss_func(prediction, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
'''
# 可视化训练过程
plt.ion()
for t in range(200):
prediction = net(x)
loss = loss_func(prediction, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if t%5==0:
plt.cla()
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw = 5)
plt.text(0.5, 0, 'Lpss=%.4f'%loss.data.numpy(), fontdict={'size':20, 'color':'red'})
plt.pause(0.1)
plt.ioff()
plt.show()
笔记:隐藏层数量设置巨大时,应该调小学习率,增加迭代次数。并且当隐藏层数量设置过大时,会发生震荡...比如,n_hidden = 100, lr = 0.05。
2、区分类型 (分类)
import torch
import matplotlib.pyplot as plt
import torch.nn.functional as F
# 构造假数据
n_data = torch.ones(100, 2)
x0 = torch.normal(2*n_data, 1)
y0 = torch.zeros(100)
x1 = torch.normal(-2*n_data, 1)
y1 = torch.ones(100)
# 注意 x y 的数据形式一定要像下面一样,torch.cat 是在合并数据
x = torch.cat((x0, x1), 0).type(torch.FloatTensor)
y = torch.cat((y0, y1), ).type(torch.LongTensor)
class Net(torch.nn.Module):
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__()
self.hidden = torch.nn.Linear(n_feature, n_hidden)
self.output = torch.nn.Linear(n_hidden, n_output)
def forward(self, x):
x = F.relu(self.hidden(x))
x = self.output(x)
return x
net = Net(2, 10, 2) # 几个类别就有几个 output
print(net)
optimizer = torch.optim.SGD(net.parameters(), lr = 0.02)
loss_func = torch.nn.CrossEntropyLoss()
plt.ion()
plt.show()
for t in range(100):
output = net(x)
loss = loss_func(output, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if t%2==0:
plt.cla()
# 过了一道 softmax 的激励函数后的最大概率才是预测值
prediction = torch.max(F.softmax(output), 1)[1]
pred_y = prediction.data.numpy().squeeze()
target_y = y.data.numpy()
plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=pred_y, s = 100, lw=0, cmap = 'RdYlGn')
accuracy = sum(pred_y==target_y)/200.
plt.text(1.5, -4, 'Accuracy=%.2f'%accuracy, fontdict={'size': 20, 'color': 'red'})
plt.pause(0.1)
plt.ioff()
plt.show()
3、快速搭建法
Torch 中提供了很多方便的途径,同样是神经网络,能快则快,我们看看如何用更简单的方式搭建同样的回归神经网络。
import torch.nn.functional as F
import torch
# 用 class 继承一个 torch 的神经网络结构,然后对其进行修改
class Net(torch.nn.Module):
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__()
self.hidden = torch.nn.Linear(n_feature, n_hidden)
self.output = torch.nn.Linear(n_hidden, n_output)
def forward(self, x):
x = F.relu(self.hidden(x))
x = self.output(x)
return x
net1 = Net(1, 10, 1)
print(net1)
# 快速搭建法
net2 = torch.nn.Sequential(
torch.nn.Linear(1, 10),
torch.nn.ReLU(),
torch.nn.Linear(10, 1)
)
print(net2)
发现net2
多显示了一些内容,这是为什么呢? 原来把激励函数也一同纳入进去了,但是net1
中,激励函数实际上是在 forward()
功能中才被调用的。这也就说明了,相比net2
,net1
的好处就是,可以根据个人需要更加个性化前向传播过程,比如(RNN)。不过如果不需要七七八八的过程,相信 net2
这种形式更适合。
4、保存提取
import torch
import matplotlib.pyplot as plt
torch.manual_seed(2) # reproducible。为 cpu 设置随机种子,保证每次产生的随机数相同
# 假数据
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim = 1)
y = x.pow(2) + 0.2*torch.rand(x.size()) # noisy y data (tensor)
# 保存网络
def save():
net1 = torch.nn.Sequential(
torch.nn.Linear(1, 10),
torch.nn.ReLU(),
torch.nn.Linear(10, 1)
)
optimizer = torch.optim.SGD(net1.parameters(), lr=0.5)
loss_func = torch.nn.MSELoss()
for t in range(100):
prediction = net1(x)
loss = loss_func(prediction, y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 两种保存方式,上面保存整个网络,下面只保存网络中的参数(速度快,占内存少)
torch.save(net1, 'net.pkl')
torch.save(net1.state_dict(), 'net_params.pkl')
# plot result
plt.figure(1, figsize=(10, 3))
plt.subplot(131)
plt.title('Net1')
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
# 提取整个网络
def restore_net():
net2 = torch.load('net.pkl')
prediction = net2(x)
# plot result
plt.subplot(132)
plt.title('Net2')
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
# 只提取网络参数
def resotre_params():
# 新建 net3 结构
net3 = torch.nn.Sequential(
torch.nn.Linear(1, 10),
torch.nn.ReLU(),
torch.nn.Linear(10, 1)
)
# 将保存的参数复制到 net3
net3.load_state_dict(torch.load('net_params.pkl'))
prediction = net3(x)
# plot result
plt.subplot(133)
plt.title('Net3')
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
plt.show()
save()
restore_net()
resotre_params()
5、批训练 Mini-batch Training
Torch 中提供了一种帮你整理你的数据结构的好东西, 叫做 DataLoader
, 我们能用它来包装自己的数据, 进行批训练.
import torch
import torch.utils.data as Data
torch.manual_seed(1) # reproducible
BATCH_SIZE = 5 # 批训练的数据个数
x = torch.linspace(1, 10, 10) # x data (torch tensor)
y = torch.linspace(10, 1, 10) # y data (torch tensor)
# 先转换成 torch 能识别的 Dataset
torch_dataset = Data.TensorDataset(data_tensor=x, target_tensor=y)
# 把 dataset 放入 DataLoader
loader = Data.DataLoader(
dataset=torch_dataset, # torch TensorDataset format
batch_size=BATCH_SIZE, # mini batch size
shuffle=True, # 要不要打乱数据 (打乱比较好)
num_workers=2, # 多线程来读数据
)
for epoch in range(3): # 训练所有!整套!数据 3 次
for step, (batch_x, batch_y) in enumerate(loader): # 每一步 loader 释放一小批数据用来学习
# 假设这里就是你训练的地方...
# 打出来一些数据
print('Epoch: ', epoch, '| Step: ', step, '| batch x: ',
batch_x.numpy(), '| batch y: ', batch_y.numpy())
"""
Epoch: 0 | Step: 0 | batch x: [ 6. 7. 2. 3. 1.] | batch y: [ 5. 4. 9. 8. 10.]
Epoch: 0 | Step: 1 | batch x: [ 9. 10. 4. 8. 5.] | batch y: [ 2. 1. 7. 3. 6.]
Epoch: 1 | Step: 0 | batch x: [ 3. 4. 2. 9. 10.] | batch y: [ 8. 7. 9. 2. 1.]
Epoch: 1 | Step: 1 | batch x: [ 1. 7. 8. 5. 6.] | batch y: [ 10. 4. 3. 6. 5.]
Epoch: 2 | Step: 0 | batch x: [ 3. 9. 2. 6. 7.] | batch y: [ 8. 2. 9. 5. 4.]
Epoch: 2 | Step: 1 | batch x: [ 10. 4. 8. 1. 5.] | batch y: [ 1. 7. 3. 10. 6.]
"""
6、加速神经网络训练 Speed Up Training
英文学习资料
越复杂的神经网络 , 越多的数据 , 我们需要在训练神经网络的过程上花费的时间也就越多. 原因很简单, 就是因为计算量太大了. 可是往往有时候为了解决复杂的问题, 复杂的结构和大数据又是不能避免的, 所以我们需要寻找一些方法, 让神经网络聪明起来, 快起来.
Stochastic Gradient Descent (SGD)
Momentum 更新方法
AdaGrad 更新方法
RMSprop 更新方法
Adam 更新方法
7、优化器 Optimizer
import torch
import torch.nn.functional as F
import torch.utils.data as Data
import matplotlib.pyplot as plt
torch.manual_seed(2)
LR = 0.01
BATCH_SIZE = 32
EPOCH = 12
x = torch.unsqueeze(torch.linspace(-1, 1, 1000), dim = 1)
y = x.pow(2) + 0.1*torch.normal(torch.zeros(x.size()))
torch_dataset = Data.TensorDataset(x, y)
loader = Data.DataLoader(
dataset= torch_dataset,
batch_size= BATCH_SIZE,
shuffle= True,
num_workers= 2
)
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.hidden = torch.nn.Linear(1, 20)
self.predict = torch.nn.Linear(20, 1)
def forward(self, x):
x = F.relu(self.hidden(x))
x = self.predict(x)
return x
if __name__ == '__main__':
# 不实用 Adagrad 是因为它在学习率上做文章
net_SGD = Net()
net_Momentum = Net()
net_RMSprop = Net()
net_Adam = Net()
nets = [net_SGD, net_Momentum, net_RMSprop, net_Adam]
opt_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
opt_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.8)
opt_RMSprop = torch.optim.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.9)
opt_Adam = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9, 0.99))
optimizers = [opt_SGD, opt_Momentum, opt_RMSprop, opt_Adam]
loss_func = torch.nn.MSELoss()
losses_his = [[], [], [], []]
for epoch in range(EPOCH):
print('Epoch: ', epoch)
for step, (batch_x, batch_y) in enumerate(loader):
for net, opt, l_his in zip(nets, optimizers, losses_his):
output = net(batch_x)
loss = loss_func(output, batch_y)
opt.zero_grad()
loss.backward()
opt.step()
l_his.append(loss.data.numpy())
labels = ['SGD', 'Momentum', 'RMSprop', 'Adam']
for i, l_his in enumerate(losses_his):
plt.plot(l_his, label = labels[i])
plt.legend(loc='best')
plt.xlabel('Steps')
plt.ylabel('Loss')
plt.ylim((0, 0.2))
plt.show()
SGD
是最普通的优化器, 也可以说没有加速效果, 而 Momentum
是 SGD 的改良版, 它加入了动量原则. 后面的 RMSprop
又是 Momentum 的升级版. 而 Adam
又是 RMSprop 的升级版. 不过从这个结果中我们看到, Adam 的效果似乎比 RMSprop 要差一点. 所以说并不是越先进的优化器, 结果越佳. 我们在自己的试验中可以尝试不同的优化器, 找到那个最适合你数据/网络的优化器.
四、高级神经网络结构
1、什么是卷积神经网络 CNN
CNN 适合用于处理 图像识别、语音识别 等的任务。
2、CNN
(待填坑 ing)
3、什么是循环神经网络 RNN
加了记忆功能,适合用于 语言分析,序列化数据 的任务。
4、什么是 LSTM 循环神经网络
LSTM 网络是在,RNN 的结构上加入了三个控制:输入控制,忘记控制,输出控制。其中忘记控制会控制将过去很久的记忆按比例更新成最新记忆。