[譯] iOS 11:機(jī)器學(xué)習(xí)人人有份

Core ML

本文同步發(fā)布于公眾號(hào):AIMaster。在這里,我們一起學(xué)習(xí) AI.

WWDC 2017 使一件事情變得非常清楚,那就是:Apple 正在全力以赴地支持「設(shè)備上的機(jī)器學(xué)習(xí)」了。

他們希望 App 的開發(fā)者們能夠盡可能的簡(jiǎn)單的加入他們的行列中。

Apple 去年發(fā)布了可以用于創(chuàng)建基本的卷積神經(jīng)網(wǎng)的 Metal CNN 和 BNNS 框架。今年,Metal 得到了進(jìn)一步擴(kuò)展,增加了一個(gè)全新的計(jì)算機(jī)視覺框架,以及 Core ML:一個(gè)能夠輕松地將機(jī)器學(xué)習(xí)集成到 App 中的工具包。

在這片文章中,我將就 iOS 11 和 macOS 10.13 中這些新推出的機(jī)器學(xué)習(xí)的內(nèi)容,分享我自己的一些想法和經(jīng)驗(yàn)。

Core ML

Core ML 在 WWDC 上獲得了極大的關(guān)注度,原因很簡(jiǎn)單:大部分開發(fā)者希望能夠在他們的 App 中使用這個(gè)框架。

Core ML 的 API 非常簡(jiǎn)單。你只能用它做這些事情:

  1. 加載一個(gè)訓(xùn)練好的模型
  2. 做出預(yù)測(cè)
  3. 收益?。。?/li>

這看起來好像很有限,但實(shí)際上你一般只會(huì)在 App 中加載模型和做出預(yù)測(cè)這兩件事。

在 Core ML 之前,加載訓(xùn)練好的模型是非常困難的 —— 實(shí)際上,我寫過一個(gè)框架來減輕這種痛苦。所以現(xiàn)在我對(duì)這一個(gè)簡(jiǎn)單的兩步過程感到非常高興。

模型被包含在了一個(gè) .mlmodel 的文件中。這是一種新的開源文件格式,用于描述模型中的 layer、輸入輸出、標(biāo)簽,以及需要在數(shù)據(jù)上產(chǎn)生的任何預(yù)處理過程。它還包括了所有的學(xué)習(xí)參數(shù)(權(quán)重和偏置)。

使用模型所需的一切都在這一個(gè)文件里面了。

你只需要將 mlmodel 文件放入你的項(xiàng)目中,Xcode 將會(huì)自動(dòng)生成一個(gè) Swift 或 Objective-C 的包裝類,使你能簡(jiǎn)單的使用這個(gè)模型。

舉個(gè)例子,如果你把文件 ResNet50.mlmodel 添加到你的 Xcode 項(xiàng)目中,那么你就可以這么寫來實(shí)例化這個(gè)模型:

let model = ResNet50()

然后做出預(yù)測(cè):

let pixelBuffer: CVPixelBuffer = /* your image */if let prediction = try? model.prediction(image: pixelBuffer) {
  print(prediction.classLabel)
}

這差不多就是所有要寫的東西了。你不需要編寫任何代碼來加載模型,或者將其輸出轉(zhuǎn)換成可以從 Swift 直接使用的內(nèi)容 —— 這一切都將由 Core ML 和 Xcode 來處理。

注意: 要了解背后發(fā)生了什么,可以在 Project Navigator 里選擇 mlmodel 文件,然后點(diǎn)擊 Swift generated source 右邊的箭頭按鈕,就能夠查看生成的幫助代碼了。

Core ML 將決定自己到底是在 CPU 上運(yùn)行還是 GPU 上運(yùn)行。這使得它能夠充分的利用可以用的資源。Core ML 甚至可以將模型分割成僅在 GPU 上執(zhí)行的部分(需要大量計(jì)算的任務(wù))以及 CPU 上的其他部分(需要大量?jī)?nèi)存的任務(wù))。

Core ML 使用 CPU 的能力對(duì)于我們開發(fā)者來說另一個(gè)很大的好處是:你可以從 iOS 模擬器運(yùn)行它,從而運(yùn)行那些對(duì)于 Metal 來說做不到,同時(shí)在單元測(cè)試中也不太好的任務(wù)。

Core ML 支持什么模型?

上面的 ResNet50 例子展示的是一個(gè)圖像分類器,但是 Core ML 可以處理幾種不同類型的模型,如:

  • 支持向量機(jī) SVM
  • 諸如隨機(jī)森林和提升樹的決策樹集成
  • 線性回歸和 logistic 回歸
  • 前饋神經(jīng)網(wǎng)、卷積神經(jīng)網(wǎng)、遞歸神經(jīng)網(wǎng)

所有這些模型都可以用于回歸問題和分類問題。此外,你的模型可以包含這些典型的機(jī)器學(xué)習(xí)預(yù)處理操作,例如獨(dú)熱編碼(one-hot encoding)、特征縮放(feature scaling)、缺失值處理等等。

Apple 提供了很多已經(jīng)訓(xùn)練好的模型可供下載,例如 Inception v3、ResNet50 和 VGG16 等,但你也可以使用 Core ML Tools 這個(gè) Python 庫(kù)來轉(zhuǎn)換自己的模型。

目前,你可以轉(zhuǎn)換使用 Keras、Caffe、scikit-learn、XGBoost 和 libSVM 訓(xùn)練的模型。轉(zhuǎn)換工具只會(huì)支持具體指定的版本,比如 Keras 支持 1.2.2 但不支持 2.0。辛運(yùn)的是,該工具是開源的,所以毫無疑問它將來會(huì)支持更多的訓(xùn)練工具包。

如果這些都不行,你還是可以隨時(shí)編寫自己的轉(zhuǎn)換器。mlmodel 文件格式是開源且可以直接使用的(由 Apple 制定發(fā)布的一種 protobuf 格式)

局限

如果你想在你的 App 上馬上運(yùn)行一個(gè)模型, Core ML 很不錯(cuò)。然而使用這樣一個(gè)簡(jiǎn)單的 API 一定會(huì)有一些限制。

  • 僅支持有監(jiān)督學(xué)習(xí)的模型,無監(jiān)督學(xué)習(xí)和增強(qiáng)學(xué)習(xí)都是不行的。(不過有一個(gè)「通用」的神經(jīng)網(wǎng)絡(luò)類型支持,因此你可以使用它)
  • 設(shè)備上不能進(jìn)行訓(xùn)練。你需要使用離線工具包來進(jìn)行訓(xùn)練,然后將它們轉(zhuǎn)換到 Core ML 格式。
  • 如果 Core ML 不支持某種類型的 layer,那么你就不能使用它。在這一點(diǎn)上,你不能使用自己的 kernel 來擴(kuò)展 Core ML。在使用 TensorFlow 這樣的工具來構(gòu)建通用計(jì)算圖模型時(shí),mlmodel 文件格式可能就不那么靈活了。
  • Core ML 轉(zhuǎn)換工具只支持特定版本的數(shù)量有限的訓(xùn)練工具。例如,如果你在 TensorFLow 中訓(xùn)練了一個(gè)模型,則無法使用此工具,你必須編寫自己的轉(zhuǎn)換腳本。正如我剛才提到的:如果你的 TensorFlow 模型具有一些 mlmodel 不支持的特性,那么你就不能在 Core ML 上使用你的模型。
  • 你不能查看中間層的輸出,只能獲得最后一層網(wǎng)絡(luò)的預(yù)測(cè)值。
  • 我感覺下載模型更新會(huì)造成一些問題,如果你不想每次重新訓(xùn)練模型的時(shí)候都重寫一個(gè)新版本的 App,那么 Core ML 不適合你。
  • Core ML 對(duì)外屏蔽了它是運(yùn)行在 CPU 上還是 GPU 上的細(xì)節(jié) —— 這很方便 —— 但你必須相信它對(duì)你的 App 能做出正確的事情。即便你真的需要,你也不能強(qiáng)迫 Core ML 運(yùn)行在 GPU 上。

如果你能夠忍受這些限制,那么 Core ML 對(duì)你來說就是正確的選擇。

否則的話,如果你想要完全的控制權(quán),那么你必須使用 Metal Performance Shader 或 Accelerate 框架 —— 甚至一起使用 —— 來驅(qū)動(dòng)你的模型了!

當(dāng)然,真正的黑魔法不是 Core ML,而是你的模型。如果你連模型都沒有,Core ML 是沒有用的。而設(shè)計(jì)和訓(xùn)練一個(gè)模型就是機(jī)器學(xué)習(xí)的難點(diǎn)所在……

一個(gè)快速示例程序

我寫了一個(gè)使用了 Core ML 的簡(jiǎn)單的示例項(xiàng)目,和往常一樣,你可以在 GitHub 上找到源碼。

這個(gè)示例程序使用了 MobileNet 架構(gòu)來分類圖片中的貓。

最初這個(gè)模型是用 Caffe 訓(xùn)練得出的。我花了一點(diǎn)時(shí)間來搞清楚如何將它轉(zhuǎn)換到一個(gè) mlmodel 文件,但是一旦我有了這個(gè)轉(zhuǎn)換好的模型,便很容易集成到 App 中了(轉(zhuǎn)換腳本包含在 GitHub 中)。

雖然這個(gè) App 不是很有趣 —— 它只輸出了一張靜態(tài)圖片的前五個(gè)預(yù)測(cè)值 —— 但卻展示了使用 Core ML 是多么的簡(jiǎn)單。幾行代碼就夠了。

注意: 示例程序在模擬器上工作正常,但是設(shè)備上運(yùn)行就會(huì)崩潰。繼續(xù)閱讀來看看為什么會(huì)發(fā)生這種情況 ;-)

當(dāng)然,我想知道發(fā)生了什么事情。事實(shí)證明 mlmodel 實(shí)際上被編譯進(jìn)應(yīng)用程序 bundle 的 mlmodelc 文件夾中了。這個(gè)文件夾里包含了一堆不同的文件,一些二進(jìn)制文件,一些 JSON文件。所以你你可以看到 Core ML 是如何將 mlmodel 在實(shí)際部署到應(yīng)用中之前進(jìn)行轉(zhuǎn)換的。

例如,MobileNet Caffe 模型使用了批量歸一化(Batch Normalization)層,我驗(yàn)證了這些轉(zhuǎn)換也存在于 mlmodel 文件中。但是在編譯的 mlmodelc 中,這些批量歸一化 layer 似乎就被移除了。這是個(gè)好消息:Core ML 優(yōu)化了該模型。

盡管如此,它似乎可以更好的優(yōu)化該模型的結(jié)構(gòu),因?yàn)?mlmodelc 仍然包含一些不必要的 scaling layer。

當(dāng)然,我們還處在 iOS 11 beta 1 的版本,Core ML 可能還會(huì)改進(jìn)。也就是說,在應(yīng)用到 Core ML 之前,還是值得對(duì)模型進(jìn)一步優(yōu)化的 —— 例如,通過「folding」操作對(duì) layer 進(jìn)行批量歸一化(Batch Normalization) —— 但這是你必須對(duì)你的特性模型進(jìn)行測(cè)量和比較的東西。

還有其他一些你必須檢查的:你的模型是否在 CPU 和 GPU 上運(yùn)行相同。我提到 Core ML 將選擇是否在 CPU 上運(yùn)行模型(使用 Accelerate 框架)或 GPU(使用 Metal )。事實(shí)證明,這兩個(gè)實(shí)現(xiàn)可能會(huì)有所不同 —— 所以你兩個(gè)都需要測(cè)試!

例如,MobileNet 使用所謂的「depthwise」卷積層。原始模型在 Caffe 中進(jìn)行訓(xùn)練,Caffe 通過使正常卷積的 groups 屬性等于輸出通道的數(shù)量來支持 depthwise 卷積。所得到的 MobileNet.mlmodel 文件也一樣。這在 iOS 模擬器中工作正常,但它在設(shè)備上就會(huì)崩潰!

發(fā)生這一切的原因是:模擬器使用的是 Accelerate 框架,但是該設(shè)備上使用的卻是 Metal Performance Shaders。由于 Metal 對(duì)數(shù)據(jù)進(jìn)行編碼方式的特殊性, MPSCNNConvolution 內(nèi)核限制了:不能使 groups 數(shù)等于輸出通道的數(shù)量。噢嚯!

我向 Apple 提交了一個(gè) bug,但是我想說的是:模型能在模擬器上運(yùn)行正常并不意味著它在設(shè)備上運(yùn)行正常。一定要測(cè)試!

有多快?

我沒有辦法測(cè)試 Core ML 的速度,因?yàn)槲业娜?10.5 寸 iPad Pro 下個(gè)星期才能到(呵呵)。

我感興趣的是我自己寫的 Forge 庫(kù)和 Core ML (考慮到我們都是一個(gè)早期的測(cè)試版)之間運(yùn)行 MobileNets 之間的性能差異。

敬請(qǐng)關(guān)注!當(dāng)我有數(shù)據(jù)可以分享時(shí),我會(huì)更新這一節(jié)內(nèi)容。

Vision

下一個(gè)要討論的事情就是全新的 Vision 框架。

你可能已經(jīng)從它的名字中猜到了,Vision 可以讓你執(zhí)行計(jì)算機(jī)視覺任務(wù)。在以前你可能會(huì)使用 OpenCV,但現(xiàn)在 iOS 有自己的 API 了。

Vision 可以執(zhí)行的任務(wù)有以下幾種:

  • 在圖像中尋找人臉。然后對(duì)每個(gè)臉給出一個(gè)矩形框。
  • 尋找面部的詳細(xì)特征,比如眼睛和嘴巴的位置,頭部的形狀等等。
  • 尋找矩形形狀的圖像,比如路標(biāo)。
  • 追蹤視頻中移動(dòng)的對(duì)象。
  • 確定地平線的角度。
  • 轉(zhuǎn)換兩個(gè)圖像,使其內(nèi)容對(duì)齊。這對(duì)于拼接照片非常有用。
  • 檢測(cè)包含文本的圖像中的區(qū)域。
  • 檢測(cè)和識(shí)別條形碼。

Core Image 和 AVFoundation 已經(jīng)可以實(shí)現(xiàn)其中的一些任務(wù),但現(xiàn)在他們都集成在一個(gè)具有一致性 API 的框架內(nèi)了。

如果你的應(yīng)用程序需要執(zhí)行這些計(jì)算機(jī)視覺任務(wù)之一,再也不用跑去自己實(shí)現(xiàn)或使用別人的庫(kù)了 - 只需使用 Vision 框架。你還可以將其與 Core Image 框架相結(jié)合,以獲得更多的圖像處理能力。

更好的是:你可以使用 Vision 驅(qū)動(dòng) Core ML,這允許你使用這些計(jì)算機(jī)視覺技術(shù)作為神經(jīng)網(wǎng)絡(luò)的預(yù)處理步驟。例如,你可以使用 Vision 來檢測(cè)人臉的位置和大小,將視頻幀裁剪到該區(qū)域,然后在這部分的面部圖像上運(yùn)行神經(jīng)網(wǎng)絡(luò)。

事實(shí)上,任何時(shí)候當(dāng)你結(jié)合圖像或者視頻使用 Core ML 時(shí),使用 Vision 都是合理的。原始的 Core ML 需要你確保輸入圖像是模型所期望的格式。如果使用 Vision 框架來負(fù)責(zé)調(diào)整圖像大小等,這會(huì)為你節(jié)省不少力氣。

使用 Vision 來驅(qū)動(dòng) Core ML 的代碼長(zhǎng)這個(gè)樣子:

// Core ML 的機(jī)器學(xué)習(xí)模型
let modelCoreML = ResNet50()
// 將 Core ML 鏈接到 Vision
let visionModel = try? VNCoreMLModel(for: modelCoreML.model)
let classificationRequest = VNCoreMLRequest(model: visionModel) {
  request, error iniflet observations = request.results as? [VNClassificationObservation] {
    /* 進(jìn)行預(yù)測(cè) */
  }
}

let handler = VNImageRequestHandler(cgImage: yourImage)
try? handler.perform([classificationRequest])

請(qǐng)注意,VNImageRequestHandler 接受一個(gè)請(qǐng)求對(duì)象數(shù)組,允許你將多個(gè)計(jì)算機(jī)視覺任務(wù)鏈接在一起,如下所示:

try? handler.perform([faceDetectionRequest, classificationRequest])

Vision 使計(jì)算機(jī)視覺變得非常容易使用。 但對(duì)我們機(jī)器學(xué)習(xí)人員很酷的事情是,你可以將這些計(jì)算機(jī)視覺任務(wù)的輸出輸入到你的 Core ML 模型中。 結(jié)合 Core Image 的力量,批量圖像處理就跟玩兒一樣!

Metal Performance Shaders

我最后一個(gè)想要討論的話題就是 Metal —— Apple 的 GPU 編程 API。

我今年為客戶提供的很多工作涉及到使用 Metal Performance Shaders (MPS) 來構(gòu)建神經(jīng)網(wǎng)絡(luò),并對(duì)其進(jìn)行優(yōu)化,從而獲得最佳性能。但是 iOS 10 只提供了幾個(gè)用于創(chuàng)建神經(jīng)網(wǎng)絡(luò)的基本 kernel。通常需要編寫自定義的 kernel 來彌補(bǔ)這個(gè)缺陷。

所以我很開心使用 iOS 11,可用的 kernel 已經(jīng)增長(zhǎng)了許多,更好的是:我們現(xiàn)在有一個(gè)用于構(gòu)建圖的 API 了!

注意: 為什么要使用 MPS 而不是 Core ML?好問題!最大的原因是當(dāng) Core ML 不支持你想要做的事情時(shí),或者當(dāng)你想要完全的控制權(quán)并獲得最大運(yùn)行速度時(shí)。

MPS 中對(duì)于機(jī)器學(xué)習(xí)來說的最大的變化是:

遞歸神經(jīng)網(wǎng)絡(luò)。你現(xiàn)在可以創(chuàng)建 RNN,LSTM,GRU 和 MGU 層了。這些工作在 MPSImage 對(duì)象的序列上,但也適用于 MPSMatrix 對(duì)象的序列。這很有趣,因?yàn)樗衅渌?MPS layer 僅處理圖像 —— 但顯然,當(dāng)你使用文本或其他非圖像數(shù)據(jù)時(shí),這不是很方便。

更多數(shù)據(jù)類型。以前的權(quán)重應(yīng)該是 32 位浮點(diǎn)數(shù),但現(xiàn)在可以是 16 位浮點(diǎn)數(shù)(半精度),8 位整數(shù),甚至是 2 進(jìn)制數(shù)。卷積和 fully-connected 的 layer 可以用 2 進(jìn)制權(quán)重和 2 進(jìn)制化輸入來完成。

更多的層。到目前為止,我們不得不采用普通的常規(guī)卷積、最大池化和平均池化,但是在 iOS 11 MPS 中,你可以進(jìn)行擴(kuò)張卷積(Dilated Convolution)、子像素卷積(Subpixel Convolution)、轉(zhuǎn)置卷積(Transposed Convolution)、上采樣(Upsampling)和重采樣(Resampling)、L2 范數(shù)池化(L2-norm pooling)、擴(kuò)張最大池化(dilated max pooling),還有一些新的激活函數(shù)。 MPS 還沒有所有的 Keras 或 Caffe layer 類型,但差距正在縮小...

更方便。使用 MPSImages 總是有點(diǎn)奇怪,因?yàn)?Metal 每次以 4 個(gè)通道的片段組織數(shù)據(jù)(因?yàn)閳D像由 MTLTexture 對(duì)象支持)。但是現(xiàn)在,MPSImage 有用于讀取和寫入數(shù)據(jù)的方法,這些數(shù)據(jù)不會(huì)讓你感到困惑。

MPSCNNConvolutionDescriptor 還有一個(gè)新方法,可以讓你在 layer 上設(shè)置批量歸一化參數(shù)。這意味著你不再需要將批量歸一化到卷積層中,而 MPS 會(huì)為你處理這些事情。非常方便!

性能改進(jìn)。現(xiàn)有的內(nèi)核變得更快。這總是好消息。 ??

圖 API。這是我最關(guān)心的消息。手動(dòng)創(chuàng)建所有 layer 和(臨時(shí))圖像總是令人討厭的。現(xiàn)在你可以描述一個(gè)圖,就像你在Keras 中一樣。 MPS 將自動(dòng)計(jì)算出圖像需要多大,如何處理填充,如何設(shè)置 MPS 內(nèi)核的 offset 等等。甚至可以通過融合不同的 layer 來優(yōu)化整個(gè)圖。

看起來所有的 MPS 內(nèi)核都可以使用 NSSecureCoding 進(jìn)行序列化,這意味著你可以將圖保存到文件中,然后將其還原。并且使用這個(gè)圖來推斷現(xiàn)在只是一個(gè)單一的方法調(diào)用。它不像 Core ML 那么簡(jiǎn)單,但使用 MPS 絕對(duì)比以前好用得多。

有一件事情我目前還不太清楚,那就是我不知道你是否可以編寫自己的 kernel 并在這個(gè)圖中使用。在我客戶的工作中,我發(fā)現(xiàn)通常需要使用 Metel Shading 語(yǔ)言編寫的自定義著色器來進(jìn)行預(yù)處理步驟。據(jù)我所知,似乎沒有一個(gè)「MPSNNCustomKernelNode」類。這還要再多研究一下!

結(jié)論:用于機(jī)器學(xué)習(xí)的 Metal Performance Shaders 已經(jīng)在 iOS 11 中變得更加強(qiáng)大,但是大多數(shù)開發(fā)人員應(yīng)該轉(zhuǎn)而使用 Core ML(對(duì)于那些使用MPS的來說)。

注意:新的圖 API 使我的 Forge 庫(kù)基本上過時(shí)了,除非你希望在 App 中繼續(xù)支持 iOS 10。我將盡快將示例應(yīng)用移植到新的圖 API 上,然后將寫一個(gè)更詳細(xì)的博客文章。

雜項(xiàng)

還有一些其他的更新:

Accelerate 框架: 似乎 Accelerate 框架中的 BNNS 并沒有獲得太多功能上的更新。它終于有了 Softmax 層,但 MPS 卻沒有新的 layer 類型。也許無關(guān)緊要:使用 CPU 進(jìn)行深層神經(jīng)網(wǎng)絡(luò)可能不是一個(gè)好主意。也就是說,我喜歡 Accelerate,它有很多好玩的東西。而今年,它確實(shí)獲得了對(duì)稀疏矩陣的更多支持,很棒。

自然語(yǔ)言處理: Core ML不僅僅只能處理圖像,它還可以處理大量不同類型的數(shù)據(jù),包括文本。 使用的 API NSLinguisticTagger 類已經(jīng)存在了一段時(shí)間,但是與 iOS 11 相比變得更加有效了。NSLinguisticTagger 現(xiàn)在已經(jīng)能進(jìn)行語(yǔ)言鑒別,詞法分析,詞性標(biāo)注,詞干提取和命名實(shí)體識(shí)別。

我沒有什么 NLP 的經(jīng)驗(yàn),所以我沒辦法比較它與其他 NLP 框架的區(qū)別,但NSLinguisticTagger 看起來相當(dāng)強(qiáng)大。 如果要將 NLP 添加到 App 中,此 API 似乎是一個(gè)好的起點(diǎn)。

都是好消息嗎?

Apple 向我們開發(fā)者提供所有的這些新工具都非常的好,但是大多數(shù) Apple API 都有一些很重要的問題:

  1. 閉源
  2. 有局限
  3. 只有在新 OS 發(fā)布時(shí)候才會(huì)更新

這三個(gè)東西加在一起意味著蘋果的 API 總會(huì)落后于其他工具。如果 Keras 增加了一個(gè)很炫酷的新的 layer 類型,那么在 Apple 更新其框架和操作系統(tǒng)之前,你都沒辦法將它和 Core ML 一起使用了。

如果某些 API 得到的計(jì)算結(jié)果并不是你想要的,你沒辦法簡(jiǎn)單的進(jìn)去看看到底是 Core ML 的問題還是模型的問題,再去修復(fù)它 —— 你必須繞開 Core ML 來解決這個(gè)問題(并不總是可能的);要么就只能等到下一個(gè) OS 發(fā)布了(需要你所有的用戶進(jìn)行升級(jí))。

當(dāng)然我不希望 Apple 放棄他們的秘密武器,但是就像其他大多數(shù)機(jī)器學(xué)習(xí)工具開源一樣,為什么不讓 Core ML 也開源呢? ??

我知道這對(duì)于 Apple 來說不可能馬上發(fā)生,但當(dāng)你決定在 App 中使用機(jī)器學(xué)習(xí)時(shí),要記住上面的這些內(nèi)容。

Matthijs Hollemans 于 2017 年 6 月 11 日

我希望這篇文章對(duì)你有所幫助!歡迎通過 Twitter @mhollemans 或 Email matt@machinethink.net 聯(lián)系我。


掘金翻譯計(jì)劃 是一個(gè)翻譯優(yōu)質(zhì)互聯(lián)網(wǎng)技術(shù)文章的社區(qū),文章來源為 掘金 上的英文分享文章。內(nèi)容覆蓋 Android、iOS、React前端、后端、產(chǎn)品、設(shè)計(jì) 等領(lǐng)域,想要查看更多優(yōu)質(zhì)譯文請(qǐng)持續(xù)關(guān)注 掘金翻譯計(jì)劃

aimaster.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容