纯Numpy/Scipy卷积神经网络实现的速度提高方法

15

背景

我已经训练出了一种卷积神经网络,希望其他人能够使用它,而不需要像Theano这样难以安装的库(我在Linux上发现安装简单,但在Windows上非常困难)。

我使用Numpy/Scipy编写了一个实现,速度几乎足够快,但如果能再快两三倍就更好了。

我的尝试

以下代码的执行占用了90%的时间:

conv_out = np.sum([scipy.signal.convolve2d(x[i],W[f][i],mode='valid') for i in range(num_in)], axis=0)

这一行会被调用32次(每个特征图调用一次),num_in是16(上一层的特征数)。因此,这一行总体上较慢,因为它会导致convolve2d例程被调用32*16=512次。

x[i]仅为25*25,而W[f][i]为2*2。

问题

有没有更好的在Numpy/Scipy中表示此类型卷积层的方法可以更快地执行?

(我仅使用此代码来应用学习到的网络,因此我没有大量需要并行处理的图像。)

代码

进行时序实验的完整代码如下:

import numpy as np
import scipy.signal
from time import time

def max_pool(x):
    """Return maximum in groups of 2x2 for a N,h,w image"""
    N,h,w = x.shape
    return np.amax([x[:,(i>>1)&1::2,i&1::2] for i in range(4)],axis=0)

def conv_layer(params,x):
    """Applies a convolutional layer (W,b) followed by 2*2 pool followed by RelU on x"""
    W,biases = params
    num_in = W.shape[1]
    A = []
    for f,bias in enumerate(biases):
        conv_out = np.sum([scipy.signal.convolve2d(x[i],W[f][i],mode='valid') for i in range(num_in)], axis=0)
        A.append(conv_out + bias)
    x = np.array(A)
    x = max_pool(x)
    return np.maximum(x,0)

W = np.random.randn(32,16,2,2).astype(np.float32)
b = np.random.randn(32).astype(np.float32)
I = np.random.randn(16,25,25).astype(np.float32)

t0 = time()
O = conv_layer((W,b),I)
print time()-t0

此时打印出0.084秒。

更新

采用mplf的建议:

d = x[:,:-1,:-1]
c = x[:,:-1,1:]
b = x[:,1:,:-1]
a = x[:,1:,1:]
for f,bias in enumerate(biases):
    conv_out = np.sum([a[i]*W[f,i,0,0]+b[i]*W[f,i,0,1]+c[i]*W[f,i,1,0]+d[i]*W[f,i,1,1] for i in range(num_in)], axis=0)

我得到了0.075秒,略快一些。


这是一个有趣的问题,但在20次运行中,我看到的平均速度为0.037秒。你的基准速度是多少? - mproffitt
我有三个大小相似的神经网络,我想在实时摄像头输入上运行它们。目前它确实可以工作(大约每秒2帧),但我想提高速度-理想情况下超过10帧每秒。也许你有一台更快的电脑? - Peter de Rivaz
我在第一次运行时得到了 0.024,然后在搭载 Python 3.4 的 Windows 10 笔记本电脑 i5 4200M 上,在足够多的后台应用程序下使 CPU 利用率保持在 20%,结果为 0.018-0.020。你使用的是什么硬件/操作系统? - IVlad
相对于绝对速度,我更关心相对收益,因此我使用一台运行Windows 7的相对较旧的笔记本电脑。 - Peter de Rivaz
你能给我一些(i>>1)&1::2, i&1::2部分的提示吗? 我可以看到最终效果是以步长2采样2x2网格,但不太明白位运算部分。 如何将其推广到步长为s的最大nxn - Jason
显示剩余3条评论
2个回答

8

加速卷积

借鉴mplf的建议,我发现可以去掉两个for循环和convolve2d的调用:

d = x[:,:-1,:-1].swapaxes(0,1)
c = x[:,:-1,1:].swapaxes(0,1)
b = x[:,1:,:-1].swapaxes(0,1)
a = x[:,1:,1:].swapaxes(0,1)
x = W[:,:,0,0].dot(a) + W[:,:,0,1].dot(b) + W[:,:,1,0].dot(c) + W[:,:,1,1].dot(d) + biases.reshape(-1,1,1)

这比原始代码快10倍。

加速最大池化

有了这个新代码,最大池化阶段现在只占用50%的时间。使用以下方法还可以加速:

def max_pool(x):
    """Return maximum in groups of 2x2 for a N,h,w image"""
    N,h,w = x.shape
    x = x.reshape(N,h/2,2,w/2,2).swapaxes(2,3).reshape(N,h/2,w/2,4)
    return np.amax(x,axis=3)

这将使max_pool步骤加速10倍,因此整个程序再次加倍速度。


我正要建议一个等效的概括:np.sum(a*W[f,:,0,0][...,None,None]+b*W[f,:,0,1][...,None,None]+c*W[f,:,1,0][...,None,None]+d*W[f,:,1,1][...,None,None], axis=0) - hpaulj
2
max_pool 的等效表达式如下,只需要一个 reshape 和没有 swapaxesreturn x.reshape(N, h / 2, 2, w / 2, 2).max(axis=(2, 4)) - jdoerrie

6

谢谢,我明天早上会试一下。 - Peter de Rivaz
我认为这种方法仍然需要进行大约512*4个矩阵运算 - 你认为有没有办法将它们减少到更少的较大矩阵运算,这样速度会更快? - Peter de Rivaz
specialconvolve表达式全部是线性操作,因此很容易推广到包括num_in维度。如果需要,在另一个问题中询问它。 - hpaulj
但是 specialconvolve 是为特定的卷积核设计的,如何将其用于其他大小和值的卷积核? - Jason

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接