专栏名称: Cocoa开发者社区
CocoaChina苹果开发中文社区官方微信,提供教程资源、app推广营销、招聘、外包及培训信息、各类沙龙交流活动以及更多开发者服务。
目录
相关文章推荐
51好读  ›  专栏  ›  Cocoa开发者社区

iOS 10和macOS中的卷积神经网络

Cocoa开发者社区  · 公众号  · ios  · 2016-11-09 08:11

正文

请到「今天看啥」查看全文



既然卷积由相邻元素的顺序定义,那么靠近数组结尾的输出元素自然存在边界条件。为了避免这个问题,一钟很常见的做法是在输入序列x[n]的两端添加足够的元素(称为鬼元素)。如果你添加0,这个操作被称为零填充。其他方法也可以。在实现卷积时,你需要解决填充问题。


Swift的卷积


让我们来看看如何用Swift实现卷积。假设我们有以下的输入数组x和核数组w:


let x: [Float] = [1, 2, 3, 4, 5], M = x.count

let w: [Float] = [1, 2, 3], N = w.count

let T = N+M-1 // 这个之后需要


在我们开始之前,如上所述让我们添加N-1个0到序列x,和M-1个0到核来容纳计算。你可以使用以下函数:


func pad(sequence x: [Float], other sequence: [Float]) -> [Float] {

return x + [Float](repeatElement(0, count: sequence.count-1))

}


所以,填充过的新序列是:


let paddedX: [Float] = pad(sequence: x, other: kernel)

let paddedK: [Float] = pad(sequence: kernel, other: x)


现在,我们可以建立paddedX和paddedK之间的一个卷积:



最后,卷积的结果是:


// y = [1, 4, 10, 16, 22]


Accelerate的卷积


如果你想加速卷积处理,你可以使用Accelerate框架提供的vDSP_conv函数。同样,我需要处理边界条件和核反转。这一次,我对输入数组和核换个零填充的方式。另外,我需要反转核(文档里有解释),否则我得到的是两个序列的相关性。


以下是用Accelerate的实现:


import Accelerate

let x: [Float] = [1, 2, 3, 4, 5], M = x.count

let kernel: [Float] = [1, 2, 3], N = kernel.count

let T = N+M-1

var res = [Float](repeatElement(0, count: T))

let zeros = [Float](repeatElement(0, count: N-1))

let newXin = zeros + x + zeros

vDSP_conv(newXin, 1, kernel.reverse(), 1, &res, 1, vDSP_Length(T), vDSP_Length(N))


对于这个很短的输入序列,你不会感激Accelerate框架带来的加速。但如果我创建了100,000个元素的输入数组,并用和之前示例相同的w内核进行卷积。在我的MacBook Pro上,Swift的实现需要318 ms,而Accelerate的vDSP_conv方法只要159 ns。


Metal的卷积


让我们看一下如何用Metal实现相同的例子。看 这篇文章 学习如何配置一个GPU计算的Metal项目。


在这个特殊的例子中,我们需要创建3个Metal纹理(遵守MTLTexture协议的对象):第一个纹理存储输入序列,第二个纹理存储核,第三个纹理存储最终结果。


以下是创建这些纹理的源代码:


import Metal

let paddedX: [Float] = input + [Float](repeatElement(0, count: N-1))

let paddedK: [Float] = kernel + [Float](repeatElement(0, count: M-1))

let inputTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(with: .r32Float, width: paddedX.count, height: 1, mipmapped: false)

inputTextureDescriptor.usage = .shaderRead

inTexture = metalContext.device.newTexture(with: inputTextureDescriptor)

let region = MTLRegionMake2D(0, 0, paddedX.count, 1)

inTexture?.replace(region, mipmapLevel: 0, withBytes: paddedX, bytesPerRow: paddedX.count * sizeof(Float32.self))

let kernelTextureDescriptor = MTLTextureDescriptor.texture2DDescriptor(with: .r32Float, width: paddedK.count, height: 1, mipmapped: false)

kernelTexture = metalContext.device.newTexture(with: kernelTextureDescriptor)

let kernelRegion = MTLRegionMake2D(0, 0, paddedK.count, 1)

kernelTexture?.replace(kernelRegion, mipmapLevel: 0, withBytes: paddedK, bytesPerRow: paddedK.count * sizeof(Float32.self))







请到「今天看啥」查看全文