스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
머신 러닝 개발 경험 살펴보기
우수한 머신 러닝(ML) 기반 경험을 앱에 제공하는 방법을 알아보세요. 모델 검색, 변환, 학습에 대해 안내하고, ML을 위한 팁과 모범 사례를 제공합니다. 또한 ML 과정을 시작할 경우 고려해야 할 사항을 소개하고, 모델 성능을 평가하기 위한 기술을 시연하며, 모델을 조정하여 기기에서 실시간 성능을 확보하는 방법을 알아보겠습니다. 이 세션에서 다루는 기술에 대해 자세히 알아보려면 WWDC22의 ‘Optimize your Core ML usage(Core ML 사용 최적화)'와 ‘Accelerate machine learning with Metal(Metal을 통한 머신 러닝 가속화)'를 시청하시기 바랍니다.
리소스
- Core ML
- Core ML Converters
- Core ML Tools PyTorch Conversion Documentation
- Integrating a Core ML Model into Your App
관련 비디오
WWDC23
WWDC22
Tech Talks
WWDC20
-
다운로드
♪ ♪
안녕하세요, Geppy Parziale입니다 Apple에서 머신 러닝 엔지니어로 일하고 있고요 오늘 저는 전문가가 필요한 특수 작업에서 문제 발생 시 해결할 수 있는 머신 러닝 사용 앱을 만드는 방법에 대해 알려드리려고 합니다
이번 여정을 통해 앱에 오픈 소스 머신 러닝 모델을 추가하고 새로운 경험을 할 수 있는 방법을 여러분께 보여드릴 수 있을 것 같습니다 오늘 여정에서는 머신 러닝 사용 앱을 만들 때 여러분께서 사용하실 수 있는 Apple의 도구, 프레임워크, API도 보여드릴 건데요
개발자로서 앱을 만들 때 유저들에게 최고의 경험을 선사하길 바라며 여러 선택을 하시잖아요 앱에 머신 러닝 기능을 추가할 때도 마찬가지일 겁니다
진행되는 동안 아마 궁금하실 거예요 '이 기능을 위해 머신 러닝을 사용해야 하는가?' '머신 러닝 모델을 어떻게 얻을 수 있는가?' 'Apple 플랫폼 호환 모델을 어떻게 만드는가?' '모델이 특정 사용 사례에서 작동할 것인가?' 'Apple Neural Engine'에서 실행되는가?' 자, 그럼 여정을 시작해 보죠 저는 지하실 오래된 상자에서 발견한 가족 흑백 사진에 실제 색을 추가할 수 있는 앱을 만들고 싶습니다
물론 프로 사진사라면 수작업으로 할 수 있겠죠 사진 편집 도구에서 시간을 좀 써야겠지만요 대신 이 프로세스를 자동화하여 단 몇 초만에 컬러라이제이션을 적용하고 싶다면요? 이건 머신 러닝이 하기 딱 좋은 일입니다
Apple에서는 앱에 머신 러닝 기능을 구축하고 통합하는 걸 돕는 프레임워크와 도구를 제공합니다 데이터 프로세싱부터 모델 트레이닝, 추론까지 모든 것을 제공하는데요 이 여정에서 이들 중 일부를 사용하려고 합니다 하지만 여러분께서 개발 중인 특정 머신 러닝 태스크에 따라 다양한 선택이 가능하다는 것을 기억해 주세요 저는 앱의 머신 러닝 기능을 개발할 때 일련의 단계들을 거칩니다
먼저, 과학 출판물이나 전문 웹사이트에서 올바른 머신 러닝 모델을 찾습니다
사진 컬러라이제이션에 대해 찾아 보았고 제게 딱 필요한 모델 'Colorizer'를 찾았죠 이 모델로 얻을 수 있는 컬러라이제이션의 예시입니다
다른 예시이고요
또 다른 예시입니다 정말 멋있죠 이게 어떻게 작동하는지 보여드리겠습니다 Colorizer 모델은 흑백 이미지를 입력물로 예상합니다 이 코드는 RGB 이미지를 LAB 색 공간 이미지로 바꿉니다
이 색 공간은 3개의 채널이 있죠 L 채널은 이미지 밝기를 나타내고 나머지 두 채널은 색상 성분을 나타냅니다 밝기가 이 모델의 입력값이 되는 반면 색상 성분은 버려집니다
그 다음 이 모델은 입력값 L 채널과 결합하여 컬러 이미지를 제공하는 색 속성 2개를 추정하죠
여기서 이 모델을 제 앱과 호환시켜야 합니다 이를 위해 오리지널 PyTorch 모델을 coremltools를 사용해 Coe ML 포맷으로 변환할 겁니다 이건 제가 사용한 Python 스크립트인데요
먼저 PyTorch 모델의 아키텍처와 가중치를 불러옵니다
그 다음 불러온 모델을 추적하죠 마지막으로 PyTorch 모델을 Core ML 모델로 바꿔 저장합니다
이 포맷으로 변환되면 제대로 변환이 됐는지 검증해야 하는데요 Python에서 다시 한 번 coremltools를 사용해야 합니다 쉬운 작업이죠 RGB 색 속성 이미지를 불러 옵니다 그리고 LAB 색 속성으로 변환합니다
그 다음 색 채널의 밝기를 분리해서 버립니다
그 다음 Core ML 모델을 사용해 예측합니다
마지막으로 입력 밝기를 추정 색 속성과 결합 후 RGB로 변환합니다
그럼 변환 모델 기능이 오리지널 PyTorch 모델의 기능과 매치하는지 검증할 수 있습니다 이 단계를 '모델 검증' 단계라고 부르겠습니다 하지만 중요한 점을 하나 더 확인해야 하는데요 이 모델이 목표 장치에서 충분히 빠른지 확인해야 합니다 장치에서 모델을 평가해 최고 사용자 경험을 제공할 수 있을지 봐야죠 Xcode 14에서 볼 수 있는 Core ML Performance는 해당 모델에 대한 시간 기반 분석을 수행합니다 이 모델을 Xcode에 드래그앤드롭하여 단 몇 초 만에 성능 보고서를 만들어야 합니다
추정 예상 시간은 M1 칩이 탑재된 iPad Pro와 iPadOS 16에서 거의 90밀리초네요
사진 컬러라이제이션 앱에 완벽한 정도입니다 Performance Report에 대해 더 알고 싶으시다면 다음 세션을 찾아보시길 추천하겠습니다 따라서 이 리포트는 모델 평가에 도움을 주고 최고의 온디바이스 사용자 경험을 선사합니다
제 모델의 기능과 성능은 검증했으니 이제 앱에 통합해 보겠습니다
통합 프로세스는 Python에서 했던 것과 동일한데요 하지만 이번에는 Swift에서 Xcode와 친숙한 툴을 사용해 자연스럽게 이루어져야 합니다
이제 이 모델은 Core ML 포맷에서 밝기를 나타내는 단일 채널 이미지를 예상합니다
Python에서 했던 것과 비슷하게 RGB 입력값 이미지를 LAB 색 속성 사용 이미지로 변환해야 합니다
다양한 방식으로 이 변환식을 쓸 수 있죠 Swift에서 vImage로 직접 쓰거나 Metal을 이용하는 거죠
저는 문서를 더 깊게 탐구해 Core Image 프레임워크에 도움이 될만한 게 있다는 걸 발견했습니다
RGB에서 LAB로의 변환 방법을 보여드리고 Core ML 모델을 사용해 예측해 보겠습니다
RGB 이미지 밝기에서 추출하여 Core ML 모델에 보낸 Swift 코드입니다 RGB 이미지를 LAB로 변환 후 밝기를 추출합니다
그 다음, 밝기를 CGImage로 변환하고 Core ML 모델에 대한 입력값을 준비합니다
마지막으로 예측을 실행하죠 입력값 RGB 이미지에서 L 채널을 추출하기 위해 새로운 CIFilter convertRGBtoLab을 사용해서요 먼저 RGB 이미지를 LAB 이미지로 변환합니다 밝기값은 0에서 100 사이로 설정됩니다
그 다음 LAB 이미지를 색상 행렬과 곱하고 색상 채널은 버린 후 밝기를 콜러에게 반환합니다 모델의 출력값에서 어떤 일이 일어났을까요?
Core ML 모델은 추정 색상을 포함한 2개의 MLShapedArray를 반환합니다
예측 후 이들을 2개의 CIImage로 변환합니다
마지막으로 이들을 모델 입력값 밝기와 결합합니다 RGB로 변환한 새 LAB 이미지가 생성되고 이걸 반환합니다
2개의 MLShapedArray를 CIImage로 변환하기 위해 먼저 각 배열에서 값을 추출합니다 두 색상 채널을 나타내는 코어 이미지 2개를 만든 후 반환합니다 밝기와 추정 색상 채널을 결합하기 위해 3개 채널을 입력값으로 취하고 CIImage를 반환하는 커스텀 CIKernel을 사용합니다
다음으로 새로운 CIFilter.convertLabToRGB로 LAB 이미지를 RGB로 변환해 콜러에 반환합니다 제가 사용한 커스텀 CIKernel이 단일 CIImage로 된 밝기와 두 추정 색상 채널을 결합한 소스 코드입니다
RGB 이미지를 LAB로, LAB 이미지를 RGB로 변환하는 새 CI 필터에 대해 더 알아보고 싶으시다면 다음 세션을 확인해 보시기 바랍니다
앱에 머신 러닝 기능을 통합했으니 한 번 실행해 보죠 여기서 잠깐! 앱에서는 제 가족 사진을 실시간으로 어떻게 변환할까요? 디지털화해서 앱으로 보내기엔 시간이 좀 걸리겠죠
그래서 좋은 생각이 떠올랐습니다 만약 iPad 카메라를 사용해 사진을 스캔하고 바로 컬러라이징할 수 있다면? 재밌을 것 같고 필요한 것도 다 있습니다 하지만 그 전에 해결해야 할 문제가 있는데요
제 모델에서는 이미지 처리에 90밀리초가 소요됩니다 영상 처리를 원한다면 더 빨라야겠죠
순조로운 사용자 경험을 위해 최소 30fps의 장치 카메라를 실행하려고 합니다
카메라가 약 30밀리초마다 프레임 하나를 생성한단 뜻이죠
하지만 이 모델에선 90밀리초가 필요합니다 각 컬러라이제이션마다 프레임 2~3개를 포기해야 하죠
모델의 총 예상 시간은 아키텍처와 이게 매핑되는 컴퓨팅 유닛 연산으로 이루어진 함수입니다 다시 이 성능 보고서를 보시면 신경 엔진과 CPU 결합에서 총 61개 연산이 실행되고 있네요
더 빠른 예측 시간을 원한다면 모델을 바꿔야 할 겁니다 그래서 모델 아키텍처로 실험을 진행해 더 빠를지 모르는 대안을 탐구해 보기로 했죠 하지만 아키텍처에서의 변화에는 네트워크 재훈련이 필요합니다
Apple에서는 Mac에 직접 머신 러닝 훈련을 가능케 하는 솔루션을 제공합니다
제 경우 오리지널 모델이 PyTorch에서 개발됐기 때문에 Metal에서 새로운 PyTorch를 사용하기로 했죠 Apple Silicon이 제공하는 엄청난 하드웨어 가속화를 이용할 수 있을 겁니다
Metal로 가속화된 PyTorch를 더 알아보고 싶으시다면 다음 섹션을 확인해 보시기 바랍니다
그 전에 우리 여정에서 뒤로 한 단계 돌아가야 합니다
재훈련 후 결과값을 Core ML 포맷으로 바꾸고 검증을 다시 해야 할텐데요
이번 모델 통합은 기존 모델에서 새 모델로 바꾸는 정도입니다 후보 대체 모델 몇 가지를 재훈련한 뒤에 제 요건에 맞을 듯한 모델 하나를 검증했는데요 그에 따른 성능 보고서입니다 모두 신경 엔진에서 실행되고 있고 예측 시간은 약 16밀리초입니다 영상이 작동하겠군요
하지만 Performance Report에서는 앱의 성능만 볼 수 있습니다
실제로 앱을 실행한 후 컬러라이제이션이 제 예상만큼 순조롭지 않다는 걸 발견했죠 런타임에 제 앱에서는 어떤 일이 일어날까요?
이해를 위해 Instruments에 있는 새 Core ML 템플릿을 사용하죠
모델 로딩 후 트레이스 초기 부분을 분석하니 앱이 예측을 축적한다는 사실이 눈에 띕니다 예측하지 못했던 건데요 대신 프레임 당 단일 시간을 예상할 수 있겠죠
트레이스를 확대해 첫 예측을 확인해 보면 앱이 1차 측이 끝나기도 전에 2차 예측을 요청한 사실이 관찰됩니다
2차 요청이 Core ML에 주어지는 이때 인공 지능은 여전히 1차 요청 작업 중입니다
이와 비슷하게 2차 요청이 처리되는 동안 3차 요청이 시작되죠 4차 요청 이후에도 요청과 실행 간 시간 격차는 약 20밀리초입니다 이런 격차가 계속되는 걸 피하기 위해 이전 예측이 끝나야만 새 예측이 시작되게 해야겠죠
이 문제를 해결하면서 카메라 프레임 레이트가 원하던 30fps가 아닌 60fps라는 사실을 발견하기도 했습니다
이전 예측이 완료되기 전에 앱이 새로운 프레임을 처리하게 만들고 카메라 프레임 레이트도 30fps로 설정하니 Core ML이 Apple Neural Engine에 단일 예측을 정확히 전송하고 앱도 순조롭게 작동하는 게 확인되네요
이렇게 여정의 끝에 도달했습니다
제 가족 사진에 앱을 테스트해볼까요?
제가 지하실에서 찾은 흑백 사진인데요 제가 아주 오래전 방문했던 이탈리아를 포착한 사진입니다
로마 콜로세움 사진입니다
벽과 하늘의 색상이 정말 현실적이죠
다음 사진도 볼까요?
이탈리아 남부의 카스텔델몬테입니다 정말 멋지죠
이곳은 제 고향인 그로탈리에입니다 이 이미지들에 색을 입히니 많은 기억이 떠오르더군요
씬의 나머지는 흑백을 유지하고 사진만 컬러라이징했다는 점 알아두세요
여기서는 사각형 검출 알고리즘을 이용하고 있습니다 Vision 프레임워크에서 이용 가능하죠 VNDetectRectangleRequest를 사용해 포토를 씬에서 분리하고 Colorizer의 입력값으로 사용할 수 있고요
이제 정리해 보겠습니다
앱을 위한 머신 러닝 기능을 준비, 통합, 평가하고자 이번 여정 동안 Apple이 제공하는 수많은 프레임워크, API, 도구를 탐구했는데요 오픈 소스 머신 러닝 모델이 해결에 필요한 문제를 식별하는 것으로 이 여정을 시작했습니다
원하는 기능을 갖춘 오픈 소스 모델을 찾았고 Apple 플랫폼과 호환되게 만들었죠 새로운 Performance Report를 사용하여 장치에서 직접 모델 성능을 평가했습니다 여러분에게 친숙한 도구와 프레임워크를 사용해 앱에 모델을 통합했고요
새로운 Core ML 템플릿으로 모델을 최적화했습니다 Apple 도구와 프레임워크로 데이터 준비, 훈련, 통합 최적화까지의 개발 단계를 Apple 장치와 플랫폼에서 직접 거칠 수 있었네요
Apple 제공 프레임워크와 도구로 이룰 수 있는 걸 오늘 개발자 여러분께 간략하게 알려드렸는데요 영감을 주는 아이디어를 통해 머신 러닝을 이용할 수 있도록 이와 관련된 이전 세션들도 참고하시기 바랍니다 여러 가지 프레임워크와 도구를 탐구하고 시도해 보세요 소프트웨어와 하드웨어의 엄청난 시너지를 이용하여 머신 러닝 기능을 가속화하고 여러분의 앱 사용자 경험을 풍성하게 만들어 보세요 WWDC 잘 즐기시고요 안녕히 계세요
-
-
3:06 - Colorization pre-processing
from skimage import color in_lab = color.rgb2lab(in_rgb) in_l = in_lab[:,:,0]
-
3:39 - Colorization post-processing
from skimage import color import numpy as np import torch out_lab = torch.cat((in_l, out_ab), dim=1) out_rgb = color.lab2rgb(out_lab.data.numpy()[0,…].transpose((1,2,0)))
-
3:56 - Convert colorizer model to Core ML
import coremltools as ct import torch import Colorizer torch_model = Colorizer().eval() example_input = torch.rand([1, 1, 256, 256]) traced_model = torch.jit.trace(torch_model, example_input) coreml_model = ct.convert(traced_model, inputs=[ct.TensorType(name="input", shape=example_input.shape)]) coreml_model.save("Colorizer.mlpackage")
-
4:26 - Core ML model verification using Core ML Tools
import coremltools as ct from PIL import Image from skimage import color in_img = Image.open(“image.png").convert("RGB") in_rgb = np.array(in_img) in_lab = color.rgb2lab(in_rgb, channel_axis=2) lab_components = np.split(in_lab, indices_or_sections=3, axis=-1) (in_l, _, _) = [ np.expand_dims(array.transpose((2, 0, 1)).astype(np.float32), 0) for array in lab_components ] out_ab = coreml_model.predict({"input": in_l})[0] out_lab = np.squeeze(np.concatenate([in_l, out_ab], axis=1), axis=0).transpose((1, 2, 0)) out_rgb = color.lab2rgb(out_lab, channel_axis=2).astype(np.uint8) out_img = Image.fromarray(out_rgb)
-
7:11 - Colorization in Swift
import CoreImage import CoreML func colorize(image inputImage: CIImage) throws -> CIImage { let lightness: CIImage = extractLightness(from: inputImage) let modelInput = try ColorizerInput(inputWith: lightness.cgImage!) let modelOutput: ColorizerOutput = try colorizer.prediction(input: modelInput) let (aChannel, bChannel): (CIImage, CIImage) = extractColorChannels(from: modelOutput) let colorizedImage = reconstructRGBImage(l: lightness, a: aChannel, b: bChannel) return colorizedImage }
-
7:41 - Extract lightness from RGB image using Core Image
import CoreImage.CIFilterBuiltins func extractLightness(from inputImage: CIImage) -> CIImage { let rgbToLabFilter = CIFilter.convertRGBtoLab() rgbToLabFilter.inputImage = inputImage rgbToLabFilter.normalize = true let labImage = rgbToLabFilter.outputImage let matrixFilter = CIFilter.colorMatrix() matrixFilter.inputImage = labImage matrixFilter.rVector = CIVector(x: 1, y: 0, z: 0) matrixFilter.gVector = CIVector(x: 1, y: 0, z: 0) matrixFilter.bVector = CIVector(x: 1, y: 0, z: 0) let lightness = matrixFilter.outputImage! return lightness }
-
8:31 - Create two color channel CIImages from model output
func extractColorChannels(from output: ColorizerOutput) -> (CIImage, CIImage) { let outA: [Float] = output.output_aShapedArray.scalars let outB: [Float] = output.output_bShapedArray.scalars let dataA = Data(bytes: outA, count: outA.count * MemoryLayout<Float>.stride) let dataB = Data(bytes: outB, count: outB.count * MemoryLayout<Float>.stride) let outImageA = CIImage(bitmapData: dataA, bytesPerRow: 4 * 256, size: CGSize(width: 256, height: 256), format: CIFormat.Lh, colorSpace: CGColorSpaceCreateDeviceGray()) let outImageB = CIImage(bitmapData: dataB, bytesPerRow: 4 * 256, size: CGSize(width: 256, height: 256), format: CIFormat.Lh, colorSpace: CGColorSpaceCreateDeviceGray()) return (outImageA, outImageB) }
-
8:51 - Reconstruct RGB image from Lab images
func reconstructRGBImage(l lightness: CIImage, a aChannel: CIImage, b bChannel: CIImage) -> CIImage { guard let kernel = try? CIKernel.kernels(withMetalString: source)[0] as? CIColorKernel, let kernelOutputImage = kernel.apply(extent: lightness.extent, arguments: [lightness, aChannel, bChannel]) else { fatalError() } let labToRGBFilter = CIFilter.convertLabToRGBFilter() labToRGBFilter.inputImage = kernelOutputImage labToRGBFilter.normalize = true let rgbImage = labToRGBFilter.outputImage! return rgbImage }
-
9:08 - Custom CIKernel to combine L, a* and b* channels.
let source = """ #include <CoreImage/CoreImage.h> [[stichable]] float4 labCombine(coreimage::sample_t imL, coreimage::sample_t imA, coreimage::sample_t imB) { return float4(imL.r, imA.r, imB.r, imL.a); } """
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.