pytorch初步

pytorch一出来,就立刻试用了一下. 在过年的期间,用pytorch写了一些代码,感觉非常棒. 也许在工程方面,不是最好的,但是用于research确实很爽.

目录

1. pytorch VS tensorflow

不得不提的是tensorflow, 网上有很多的对比, 不再赘述, pytorch还有很长的路要走. tensorflow毕竟有谷歌在后面撑着,而且已经开源了一年, 网上有很多基于tensorflow的再开发和系统的优化, 除了使用tensorflow训练模型之外, 还有tensorflow的server用于提供预估服务, 在企业应用方面, 谷歌是大范围使用了tensorflow, 很多研究院也会使用tensorflow进行研究. 在tensorflow 刚开源的时候, 我就使用了一下tensorflow, 还给公司的同事们做过PPT介绍tensorflow, 但是奇怪的语法着实让人感觉不那么美……当然啦, 自动求导这个奇葩的工具, 给大家灌水带来了非常大的便利. 现在很多深度学习库, 都会带这个功能, 比如mxnet, 以及今天要介绍的pytorch. 当然啦,目前的趋势就是底层都是c/c++, 而上层包装一个Python. 在公司里, 写c++写多了, 感觉用c++做机器学习也没啥不好的, 刚开始学机器学习的, 可都是认为python最好的. 后来才发现, 语言都不是什么事情, 什么合适就用啥!

那么我选择pytorch, 而不是tensorflow的原因是什么? 可能主要是语法吧, 不太喜欢tensorflow那种要定义session, session run以及feed数据的方式吧. 静态图和自动求导, 给调试和测试带来了不少麻烦, 当然我相信以后tensorflow会有一个非常好用的debug工具的. 而pytorch一开始就是以research为主的, 因为平时在家学习一些新的东西, 希望自己能够快速实验一些想法, 所以选择了pytorch为主. 2016年自己,用python或者c++写了不少机器学习算法库, 2017年的话, 就基于pytorch玩一些有意思的东西.

以上是瞎扯部分. 主要想表达: 我们玩玩pytorch吧!

入门的操作可以参考官方的一些入门教程 我自己写一点简单的操作, 这些操作在建模和数据预处理的都非常重要.

2. 基础操作

基本的加减乘除

import torch
from torch.autograd import Variable
import torch.nn.functional as F
import numpy as np
dtype = torch.FloatTensor

d = 2
h = 2
# 初始化变量, 可以直接使用 torch.ones(d, h) 或者 torch.randn 进行随机初始化
# 主要目的是, 初始化一个容器, 在容器里放一些数据, 这个好处是可以直接用容器里的数据进行运算和测试
x = Variable(torch.ones(d, h).type(dtype), requires_grad=False)
y = Variable(torch.ones(d, h).type(dtype), requires_grad=False)

# 也可以从numpy中进行初始话
a = np.array([[1, 2], [1, 2]])
b = np.array([[2, 3], [3, 2]])
x = torch.from_numpy(a)
y = torch.from_numpy(b)

# 四则运算, 注意x和y 已经从a 和 b里进行初始化了
print(x, y)            # 分别看看x, y是啥
print(x + y)           # 加法
print(x.add(y))        # 等价于 x + y
print(x * 2 + y)       # 还是加法
print(x * y)           # 对应元素的乘积, 得到 [[2, 6], [3, 4]]
print(x.mul(y))        # 等价于 x * y
print(x.dot(y))        # 点乘, 得到 15 = 2 + 6 + 3 + 4
print(x @ y)           # 矩阵叉乘, 得到[[8, 7], [8, 7]]
print(x.mm(y))         # 等价于 x @ y
print(x / y)           # 除法! 一定小心! 如果打印出来一定能够看到x,y是整形的, 所以结果是0!! 而不是小数

# 是否有下划线的区别
print(x.add(y))       # 输出 x + y, x保持不变
print(x.add_(y))      # 输出 x + y, 同时修改x的值 = x + y, 同理dot, mm, mul等操作!!

3.函数运算和梯度计算

# 简单的case
x = Variable(torch.ones(1), requires_grad = True)
y = x ** 2 + 3
y.backward(retain_variables=True)
print(x.grad)  # 梯度值是 2*x = 2
# 复杂一点,
target = torch.FloatTensor([10])
y.backward(target, retain_variables=True)
print(x.grad)  # 梯度值是 2*x = 20, 因为retain的设置为true, grad会加上原来的梯度值2, 结果是22

x = Variable(torch.ones(1), requires_grad = True)
y = x ** 2 + 3
target = torch.FloatTensor([10])
y.backward(target)
print(x.grad)      # 直接设置backward, 得到梯度值 20
y.backward(target) # 会报错, 因为x的grad已经被填充了, 不能再放东西进去了

这里只是一个随便的x做的测试, 主要是提醒大家retain_variables的问题, 可能会引起的潜在问题.

从上面可以看出来, pytorch可以任意的定义函数,并且进行梯度的计算, 最重要的是我们可以随便输入一个简单的值, 比如1, 0等等进行forward的验证. 同时,我们也可以设置顶一个值, 进行backward的测试, 来验证我们的模型是否符合预期, 以及建模中经常会遇到的维度不一致导致的叉乘点乘混淆在一起.

4.简单的回归方法

官方给出了一个回归的example, 我稍微简化一下, 介绍如下

import torch
import torch.autograd
import torch.nn.functional as F
from torch.autograd import Variable

feature_num = 3
batch_size = b
w_target = torch.randn(feature_num, 1) * 4   # 初始化一下参数w, 作为我们的目标参数
b_target = torch.randn(1) * 3                # 初始化一下bias项, 作为我们的目标参数

def f(x):
    return x.mm(w_target) + b_target[0]

def print_param(W, b):
    result = 'y = '
    for i, w in enumerate(W):
        result += '{:+.2f} x_{} '.format(w, len(W) - i)
    result += '{:+.2f}'.format(b[0])
    return result

def get_batch(batch_size=32):
    """生成一些训练数据, 用于训练"""
    x = torch.randn(batch_size, feature_num)
    y = f(x)
    return Variable(x), Variable(y)

# 定义模型, 通常情况我们都会用nn库中的一些函数来建模, 利用也有的库, 而不是自己写一个
fc = torch.nn.Linear(w_target.size(0), 1)

# 看一下我们要学习的函数
print('Actual  function:\t%s' %(print_param(w_target.view(-1), b_target)))

for batch_idx in range(100):
    batch_x, batch_y = get_batch()
    # 重置梯度
    fc.zero_grad()
    # Forward 操作
    output = F.smooth_l1_loss(fc(batch_x), batch_y)
    loss = output.data[0]
    # Backward 操作
    output.backward()
    # 随机梯度下降学习
    for param in fc.parameters():
        param.data.add_(-0.1 * param.grad.data)
    # 设置停止训练的标准
    if loss < 1e-3:
        break

print('Loss: {:.6f} after {} batches'.format(loss, batch_idx))
print('Learned function:\t%s' %(print_param(fc.weight.data.view(-1), fc.bias.data)))
print('Actual  function:\t%s' %(print_param(w_target.view(-1), b_target)))

这个case中, 我们使用了torch自带的nn中的Linear函数和smooth_l1_loss, 但是我们的随机梯度下降却仍然是自己实现的, 当然啦, pytorch也提供了SGD以及相关的函数实现. 详细的见下一个case吧!

5.定义一个神经网络

现在我们来实验一个简单的神经网络吧, 依旧是使用mnist数据集, 这个已经被玩烂的数据集, 仍然是入门和测试的好数据集.

import torch
import torch.autograd
import torch.nn.functional as F
from torch.autograd import Variable
import torch.optim as optim
import numpy as np

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # 常用的一些操作, 主要是卷积, 池化和全链接, 可以尝试加dropout和batchNorm
        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()
# 定义目标函数
criterion = nn.CrossEntropyLoss()
# 定义优化方法, 这里使用SGD + 动量的方法
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

# 训练整个网络
for epoch in range(2):
    running_loss = 0.0
    # 这里需要自己搞一下数据 官网提供了一些现成的数据供玩耍
    for i, data in enumerate(trainloader, 0):
        # get the inputs
        inputs, labels = data
        # wrap them in Variable
        inputs, labels = Variable(inputs), Variable(labels)
        # 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.data[0]
        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')

后面的预估什么的, 都是比较简单的, 详细可以看一下官网的case. 这里重点要强调一点(官网上有, 但是这里没有写出来的):

  • 查看参数的方式, params = list(net.parameters())
  • 调试模型:
    • 可以先使用 x_t = Variable(torch.randn(1, 1, 32, 32)), 之后 out = net(input) 来看看中间结果是否能够正常运行
    • 同样,我们可以随机一个out, 通过 net.zero_grad()out.backward(torch.randn(1, 10))来看看误差反馈正常与否
  • 查看函数链 print(loss.creator.previous_functions[0][0]) # Linear 可以不断看到这个函数的前面的函数是啥

学习一个库, 如果是为了应用的话, 一定要多注意check, 因为你可能不知道有很多用法,就会踩坑!! 当然, 看一些教程啥的, 还是能够帮助你入门的.

但是, 最重要是, 读官方文档, 读官方文档, 读官方文档!!!

通过官方文档, 了解基本的运算和基本的坑, 后面就发挥的你想象开始应用吧. 感兴趣的童鞋, 可以看看torch的实现源码!

本文总阅读量