如果你一直有关注Apple去年所发布的消息,就会知道他们在机器学习上投入了大量心力。自他们去年在WWDC 2017上推出Core ML以来,已经有大量结合机器学习技术的应用程序涌现。
但是,开发人员经常遇到的其中一个挑战是:如何创建模型?幸运的是,Apple在去年冬天宣布从GraphLab收购了Turi Create,正正解决了我们的问题。Turi Create是Apple的工具,可以帮助开发人员简化创建客制化模型的步骤。使用Turi Create,你可以建立自己的客制化机器学习模型。
Turi Create 快速入门
如果你有关注其他机器学习教学文章,你可能会觉得奇怪,「今年Apple不是有发布一个叫Create ML的工具吗?那相较于Create ML来说,Turi Create有什么优势?」
虽然对于刚开始研究机器学习的人来说,Create ML 是一个很好的工具,但它在使用方面严重受到限制,例如只能使用文本或图像数据。虽然这已经可以完成大多数的项目,但是对于稍微复杂的机器学习应用程序(例如风格转换(Style Transfer)), Create ML 就可能会变得毫无用处。
使用Turi Create,你除了可以创建所有原本使用Create ML创建出的Core ML模型之外,更能创造更多不同类型的模型!由于Turi Create比Create ML复杂得多,因此它与其他机器学习工具如Keras和TensorFlow有高度的整合性。在我们的CreateML教学之中,你看到我们可以使用Create ML制作Core ML模型的类型。以下是你可以使用Turi Create制作的演算法类型:
你可以看到列表中包含了分类器与回归器(regressors),它们都可以使用Create ML 或Turi Create 来完成。这就是为什么Turi Create被更有经验的数据科学家所青睐,因为它提供了一种在Create ML中无法提供的可定制性。
什么是风格转换?
现在你大致了解到什么是Turi Create,那么让我们来看看什么是风格转换。风格转换是一种使用另一张图像风格将图像重新组合的技术,即是什么意思?看看下面利用Prisma 创造出来的图像:
Style Transfer Example如你所见,上面早餐餐盘的图像风格转换成漫画了。由Gatys等人发表了一篇论文,描述如何使用卷积神经网路(Convolutional Neural Networks, CNNs)将一张图像的美术风格转换到另一张图像,风格转换就开始兴起。
卷积神经网路是一种机器学习的神经网路,通常应用于图像辨识及分类。它已经成功地解决电脑视觉方面的问题,例如:脸部辨识、物件辨识等。这是一个复杂的议题,所以我不会在这里讨论太多。
构建自己的风格转换应用程序
现在你已经了解了本教学涵盖到的工具和概念,我们终于可以开始了!我们将会利用Turi Create 构建自己的风格转换模型,并把它汇入iOS 项目来看看效果!
coreml-turi-create-1首先,在这里下载起始项目,在本次的教学中我们将会用到Python 2、Jupyter Notebook和Xcode 9。
训练风格转换模型
Turi Create是一个Python套件,但它并没有内建在macOS里面,所以让我带你快速安装它。你的macOS应该已经安装了Python,若你的设备还没有安装Python
或pip
,你可以在这里了解安装流程。
安装Turi Create 及Jupyter
打开终端机并输入下列指令:
pip install turicreate==5.0b2
Python套件安装过程大约1-2分钟。与此同时,我们可以下载Jupyter Notebook。Jupyter Notebook是一个供开发人员使用、支持许多语言的编译器,它包含丰富和互动的输出视觉效果。由于Turi Create仅支持Python 2,因此请在终端机输入以下命令以安装适用于Python 2的Jupyter Notebook。
python -m pip install --upgrade pip
python -m pip install jupyter
coreml-turi-terminal-message
当所有的套件都安装好,就可以开始创造我们的演算法了!
使用Turi Create 撰写程序
我们即将构建的风格转换模型会以梵谷的作品星夜(Starry Night)为基础。简单来说,我们创造的模型可以将任何图像转换成星夜(Starry Night)风格的复制品。
starry-night-1093721_1280首先,下载训练数据并解压,里面有一个content
资料夹和一个style
资料夹。打开content
资料夹,你会看到大约有70张不同的图片。这个资料夹包含了各式各样的图片,这样就可以让我们的演算法知道有什么类型的图片需要做转换。因为我们想要转换所有图像,我们就需要有多样化的图片才行。
而在Style
资料夹中就很简单地只有一张图片:StarryNight.jpg。这个资料夹包含了我们想要转换的美术风格来源。
现在,让我们打开Jupyter Notebook
开始撰写代码。输入下列指令到终端机中:
jupyter notebook
这将会打开Safari 并显示这个页面:
coreml-turi-create-4点击New
按钮,然后按下Python 2!
备注:请确认你的Jupyter Notebook是在使用Python 2,这一点非常重要,因为目前Turi Create并不支持Python 3。译者注: Turi Create未来可能会支持Python 3。
按下按钮后,将会弹出一个新页面,这就是我们要建立模型的地方。按下第一个Cell,并汇入Turi Create 套件:
import turicreate as tc
按下SHIFT+Enter 来执行这一个Cell 中的代码,等待套件汇入完成。下一步,来创建一个包含图像资料夹的参考。请确认你已经把代码中的参数设为资料夹的路径。
style = tc.load_images('/Path/To/Folder/style')
content = tc.load_images('/Path/To/Folder/content')
执行代码后,你应该会收到这样输出讯息:
coreml-turi-create-6不用太担心这样的警告。接下来,我们将输入指令来创建风格转换模型。强烈建议你一台在拥有GPU运算资源的Mac上执行下列代码,像是最新的MacBook Pro或iMac。如果你选择在MacBook Air上执行,那么程序将会透过CPU来运算,这可能会花上好几天的时间。
model = tc.style_transfer.create(style, content)
执行代码,这可能因为你的设备而花上一段很长的时间才能完成,像我在MacBook Air上透过CPU运算就花了3天半才完成。如果你没有足够的时间,不用担心,你可以在这里下载最后的Core ML模型(CoreML模型名为“StarryStyle”)。然而,可以的话你还是试试执行整个程序,感受一下它是怎样运作的!
coreml-turi-create-7你可以看到表格中包含了三个栏位: Iteration(叠代次数)、Loss(损失)和Elapsed Time(花费时间)。在机器学习之中,会有特定函数执行多次向前和向后运算。当函数向前运算就是cost,往后运算就是loss。每次执行函数时,目的是调整参数来减少Loss。因此每次更改参数时,就会在增加一次Iteration,目标是为了得到更少的Loss。在训练的过程中,你可以发现Loss会渐渐地变少。而Elapsed time指的就是运算所消耗的时间。
当模型已经完成训练,只需要储存它就可以了!这可以简单地用一行代码来完成!
model.export_coreml("StarryStyle.mlmodel")
image
就这样完成了,你可以到函式库看看最终的模型!
coreml-turi-create-9Xcode 项目概览
现在我们已经有了自己的模型,剩下来要做的就是将它汇入到Xcode 项目之中。打开Xcode 9 来看一下我们的项目。
turi create demo app构建并执行项目,这样可以确认我们可以编译此项目。应用程序目前还未能运作,当你按下Van Gogh!
按钮,你会发现什么事都没发生!现在,轮到我们来撰写代码了,让我们开始吧!
实战机器学习
首先,将我们的模型文件(即是StarryStyle.mlmodel
)拖曳到项目之中,请确保你有勾选Copy Items If Needed
,以及已经选了目标项目。
接下来,我们需要在ViewController.swift
加入代码来处理机器学习流程,大部分的代码会在transformImage()
函数中撰写。让我们从汇入Core ML套件并调用模型开始吧!
import CoreML
...
@IBAction func transformImage(_ sender: Any) {
// Style Transfer Here
let model = StarryStyle()
}
这行代码简单地将Core ML模型指定为叫做model
的常数。
图像转换
下一步,我们需要将使用者所选取的图像转换成可读数据。再看看StarryStyle.mlmodel
文件,你就会发现它接受的图像尺寸是256×256,因此我们必须执行转换。在我们的transformImage()
函数下方加入一个新的函数。
func pixelBuffer(from image: UIImage) -> CVPixelBuffer? {
// 1
UIGraphicsBeginImageContextWithOptions(CGSize(width: 256, height: 256), true, 2.0)
image.draw(in: CGRect(x: 0, y: 0, width: 256, height: 256))
let newImage = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
// 2
let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary
var pixelBuffer : CVPixelBuffer?
let status = CVPixelBufferCreate(kCFAllocatorDefault, 256, 256, kCVPixelFormatType_32ARGB, attrs, &pixelBuffer)
guard (status == kCVReturnSuccess) else {
return nil
}
// 3
CVPixelBufferLockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer!)
// 4
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: pixelData, width: 256, height: 256, bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer!), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue)
// 5
context?.translateBy(x: 0, y: 256)
context?.scaleBy(x: 1.0, y: -1.0)
// 6
UIGraphicsPushContext(context!)
image.draw(in: CGRect(x: 0, y: 0, width: 256, height: 256))
UIGraphicsPopContext()
CVPixelBufferUnlockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
return pixelBuffer
}
这是一个辅助函数(Helper Function),与我们之前Core ML教学文章所使用的函数有点相似。如果你已经忘了,别担心,让我一步一步解释这个函数。
- 因为我们的模型只能接受尺寸为
256 x 256
的图像,所以我们将图片转换为正方形,接着将正方形图像指定到另一个newImage
的常数。 - 现在,我们将
newImage
转换成为CVPixelBuffer
。如果你对CVPixelBuffer
不熟悉,它基本上是一个图像缓冲区,用来将像素存于主要记忆体中。你可以在这里了解更多关于CVPixelBuffers
的资讯。 - 取得图片中的所有像素后,我们将它转换成设备所对应的RGB色彩空间。接着,将所有数据创建为
CGContext
,当我们需要渲染(或改变)某些底层的属性时,就可以简单地调用它,这是我们在下列两行代码中透过转化及缩放图像所做的事。 - 最后,我们将图像内容放入当前内容中,渲染图像,并移除堆叠最上层的内容。当这些变更都完成后,回传像素缓冲器。
这其实是一些非常进阶的Core Image
代码,已经超出了本篇教学文章的范围。如果有某些部分不了解其实不用担心。整段代码的主要目的,是藉由转换一张图像为像素缓冲器来提取它的数据,让Core ML可以更方便地读取它。
将风格转换应用于图像
现在我们有了Core ML辅助函数,让我们回到transformImage()
并实战代码。在我们声明 model
常数的那行下面,输入下列代码:
let styleArray = try? MLMultiArray(shape: [1] as [NSNumber], dataType: .double)
styleArray?[0] = 1.0
Turi Create允许你将多于一种「风格」打包到模型之中,虽然这次的项目只有一种风格,就是Starry Night。如果你想添加更多种风格,你可以加入更多图片到style
资料夹中。我们将styleArray
声明 为MLMultiArray,这是一种被Core ML所使用来作模型输入及输出的阵列型态。由于我们只有一种风格,所以只有一种形状及数据元素,因此我们将styleArray
的数据元素设为1。
最后,只需要利用我们的模型进行预测,并将结果设置为imageView
。
if let image = pixelBuffer(from: imageView.image!) {
do {
let predictionOutput = try model.prediction(image: image, index: styleArray!)
let ciImage = CIImage(cvPixelBuffer: predictionOutput.stylizedImage)
let tempContext = CIContext(options: nil)
let tempImage = tempContext.createCGImage(ciImage, from: CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(predictionOutput.stylizedImage), height: CVPixelBufferGetHeight(predictionOutput.stylizedImage)))
imageView.image = UIImage(cgImage: tempImage!)
} catch let error as NSError {
print("CoreML Model Error: \(error)")
}
}
这个函数首先检查imageView
之中是否有图像。在这段代码中,我们先定义了predictionOutput
用来储存模型预测的输出结果。我们以使用者的影像以及风格阵列作为参数,调用模型的prediction
方法。预测的结果是像素缓冲器,但是我们无法将像素缓冲器设定为UIImageView
,因此我们想出了一个非常有创意的方法来实现。
首先,我们将像素缓冲器predictionOutput.stylizedImage
设置为CIImage
类型的图像。然后,创建一个tempContext
变量,它是CIContext
的实例。我们调用context的内建函数(也就是createCGImage
),它从ciImage
产生CGImage
。最后,我们可以将imageView
设置为tempImage
。这样就完成了!如果有任何错误,我们可以将错误印出来好好处理。
构建并执行项目。你可以从图库中选一张图片,然后测试应用程序!
Core ML Style Transfer Demo你可能会注意到模型的输出结果看起来不太接近原本的Starry Night,而这种情况可以有很多原因。可能我们需要更多的训练数据?或是我们训练数据时需要更多次的叠代次数?我强烈的建议你回到前面几个步骤,再玩玩这些参数,直到你满意输出结果为止!
总结
教学文章就到此为止了!我已经向你介绍了Turi Create,并创造了你自己的风格转换模型,如果是在5 年前,一个人定必无法完成。你也学习到了如何将Core ML 模型汇入iOS 应用程序中,并有创意地应用它!
但是,风格转换只是一个开始。如我在前文提过,Turi Create 可以用来创造各类型的应用程序,下面是一些帮助你更进一步的资源:
- Apple's Gitbook on Turi Create Applications
- A Guide to Turi Create – WWDC 2018
- Turi Create Repository
如果需要完整的项目,请到GitHub下载。如果你有任何意见或问题,请在下面留言,与我分享你的想法。
原文:Creating a Prisma-like App with Core ML, Style Transfer and Turi Create
作者:
网友评论