natural-language

natural-language

熱門

使用 Apple 的 NaturalLanguage 框架對自然語言文字進行斷詞、標記與分析,並透過 Translation 框架進行語言翻譯。適用於在 iOS/macOS/visionOS 應用程式中加入語言辨識、情感分析、命名實體辨識、詞性標記、文字嵌入或應用程式內翻譯。

936星標
47分支
更新於 2026/7/15
SKILL.md
唯讀
名稱
natural-language
描述

使用 Apple 的 NaturalLanguage 框架對自然語言文字進行斷詞、標記與分析,並透過 Translation 框架進行語言翻譯。適用於在 iOS/macOS/visionOS 應用程式中加入語言辨識、情感分析、命名實體辨識、詞性標記、文字嵌入或應用程式內翻譯。

NaturalLanguage + Translation

分析自然語言文字,進行斷詞、詞性標記、命名實體辨識、情感分析、語言辨識以及詞/句嵌入。使用 Translation 框架在不同語言之間翻譯文字。

此技能涵蓋兩個相關框架:NaturalLanguageNLTokenizerNLTaggerNLEmbedding)用於裝置端文字分析,以及 TranslationTranslationSessionLanguageAvailability)用於語言翻譯。

範圍界定: 請在已取得文字後使用此技能。它負責斷詞、語言辨識、詞性/命名實體標記、情感分析、嵌入、自訂 NLModel 分類器/標記器以及應用程式內翻譯。OCR 請交給 vision-framework,語音轉文字請交給 speech-recognition,UI 字串與地區格式請交給 ios-localization,生成式摘要或 Apple Intelligence 工作流程請交給 apple-on-device-ai

目錄

設定

匯入 NaturalLanguage 進行文字分析,匯入 Translation 進行語言翻譯。NaturalLanguage 不需要特殊權限或功能。Translation 的可用性有區分:系統翻譯介面需要 iOS 17.4+ / macOS 14.4+,而 TranslationSession.translationTask()LanguageAvailability 以及批次翻譯需要 iOS 18+ / macOS 15+。
直接使用 TranslationSession(installedSource:target:) 是無 UI 的選項,但僅限於來源與目標語言已安裝在裝置上時。

import NaturalLanguage
import Translation

NaturalLanguage 類別(NLTokenizerNLTagger非執行緒安全。每個實例請在單一執行緒或 dispatch queue 中使用。

斷詞

使用 NLTokenizer 將文字分割成詞、句子或段落。

import NaturalLanguage

func tokenizeWords(in text: String) -> [String] {
    let tokenizer = NLTokenizer(unit: .word)
    tokenizer.string = text

    let range = text.startIndex..<text.endIndex
    return tokenizer.tokens(for: range).map { String(text[$0]) }
}

斷詞單位

單位 說明
.word 個別詞彙
.sentence 句子
.paragraph 段落
.document 整份文件

列舉並取得屬性

使用 enumerateTokens(in:using:) 來偵測數字或表情符號等 token。

let tokenizer = NLTokenizer(unit: .word)
tokenizer.string = text

tokenizer.enumerateTokens(in: text.startIndex..<text.endIndex) { range, attributes in
    if attributes.contains(.numeric) {
        print("數字:\(text[range])")
    }
    return true // 繼續列舉
}

語言辨識

使用 NLLanguageRecognizer 偵測字串的主要語言。

func detectLanguage(for text: String) -> NLLanguage? {
    NLLanguageRecognizer.dominantLanguage(for: text)
}

// 多個假設與信心分數
func languageHypotheses(for text: String, max: Int = 5) -> [NLLanguage: Double] {
    let recognizer = NLLanguageRecognizer()
    recognizer.processString(text)
    return recognizer.languageHypotheses(withMaximum: max)
}

限制辨識器只考慮預期語言,可提升短文字的準確度。

let recognizer = NLLanguageRecognizer()
recognizer.languageConstraints = [.english, .french, .spanish]
recognizer.processString(text)
let detected = recognizer.dominantLanguage

詞性標記

使用 NLTagger 辨識名詞、動詞、形容詞及其他詞類。

func tagPartsOfSpeech(in text: String) -> [(String, NLTag)] {
    let tagger = NLTagger(tagSchemes: [.lexicalClass])
    tagger.string = text

    var results: [(String, NLTag)] = []
    let range = text.startIndex..<text.endIndex
    let options: NLTagger.Options = [.omitPunctuation, .omitWhitespace]

    tagger.enumerateTags(in: range, unit: .word, scheme: .lexicalClass, options: options) { tag, tokenRange in
        if let tag {
            results.append((String(text[tokenRange]), tag))
        }
        return true
    }
    return results
}

常見標記方案

方案 輸出
.lexicalClass 詞性(名詞、動詞、形容詞)
.nameType 命名實體類型(人物、地點、組織)
.nameTypeOrLexicalClass 合併命名實體辨識與詞性標記
.lemma 詞彙的原形
.language 每個 token 的語言
.sentimentScore 情感極性分數

命名實體辨識

擷取人物、地點與組織。

func extractEntities(from text: String) -> [(String, NLTag)] {
    let tagger = NLTagger(tagSchemes: [.nameType])
    tagger.string = text

    var entities: [(String, NLTag)] = []
    let options: NLTagger.Options = [.omitPunctuation, .omitWhitespace, .joinNames]

    tagger.enumerateTags(
        in: text.startIndex..<text.endIndex,
        unit: .word,
        scheme: .nameType,
        options: options
    ) { tag, tokenRange in
        if let tag, tag != .other {
            entities.append((String(text[tokenRange]), tag))
        }
        return true
    }
    return entities
}
// NLTag 值:.personalName、.placeName、.organizationName

情感分析

為文字情感評分,範圍從 -1.0(負面)到 +1.0(正面)。

func sentimentScore(for text: String) -> Double? {
    let tagger = NLTagger(tagSchemes: [.sentimentScore])
    tagger.string = text

    let (tag, _) = tagger.tag(
        at: text.startIndex,
        unit: .paragraph,
        scheme: .sentimentScore
    )
    return tag.flatMap { Double($0.rawValue) }
}

文字嵌入

使用 NLEmbedding 衡量詞彙或句子之間的語義相似度。

func wordSimilarity(_ word1: String, _ word2: String) -> Double? {
    guard let embedding = NLEmbedding.wordEmbedding(for: .english) else { return nil }
    return embedding.distance(between: word1, and: word2, distanceType: .cosine)
}

func findSimilarWords(to word: String, count: Int = 5) -> [(String, Double)] {
    guard let embedding = NLEmbedding.wordEmbedding(for: .english) else { return [] }
    return embedding.neighbors(for: word, maximumCount: count, distanceType: .cosine)
}

句子嵌入可比較整個句子。

func sentenceSimilarity(_ s1: String, _ s2: String) -> Double? {
    guard let embedding = NLEmbedding.sentenceEmbedding(for: .english) else { return nil }
    return embedding.distance(between: s1, and: s2, distanceType: .cosine)
}

翻譯

系統翻譯覆蓋層

使用 .translationPresentation() 顯示內建翻譯 UI。

import SwiftUI
import Translation

struct TranslatableView: View {
    @State private var showTranslation = false
    let text = "Hello, how are you?"

    var body: some View {
        Button { showTranslation = true } label: {
            Text(text)
        }
        .buttonStyle(.plain)
        .translationPresentation(
            isPresented: $showTranslation,
            text: text
        )
    }
}

程式化翻譯

在視圖環境中使用 .translationTask() 進行程式化翻譯。

struct TranslatingView: View {
    @State private var translatedText = ""
    @State private var translationErrorMessage: String?
    @State private var configuration: TranslationSession.Configuration?

    var body: some View {
        VStack {
            Text(translatedText)
            Button("翻譯") {
                configuration = .init(source: Locale.Language(identifier: "en"),
                                      target: Locale.Language(identifier: "es"))
            }
        }
        .translationTask(configuration) { session in
            do {
                let response = try await session.translate("Hello, world!")
                await MainActor.run {
                    translatedText = response.targetText
                    translationErrorMessage = nil
                }
            } catch {
                let message = error.localizedDescription
                await MainActor.run {
                    translationErrorMessage = message
                }
            }
        }
    }
}

批次翻譯

在單一 session 中翻譯多個字串。

.translationTask(configuration) { session in
    do {
        let requests = texts.enumerated().map { index, text in
            TranslationSession.Request(sourceText: text,
                                       clientIdentifier: "\(index)")
        }
        let responses = try await session.translations(from: requests)
        for response in responses {
            print("\(response.sourceText) -> \(response.targetText)")
        }
    } catch {
        // 處理取消、不支援的語言或下載拒絕。
    }
}

檢查語言可用性

let availability = LanguageAvailability()
let status = await availability.status(
    from: Locale.Language(identifier: "en"),
    to: Locale.Language(identifier: "ja")
)
switch status {
case .installed: break    // 可離線翻譯
case .supported: break    // 需要下載
case .unsupported: break  // 語言配對不可用
}

常見錯誤

不要:跨執行緒共用 NLTagger/NLTokenizer

這些類別非執行緒安全,會產生錯誤結果或崩潰。

// 錯誤
let sharedTagger = NLTagger(tagSchemes: [.lexicalClass])
DispatchQueue.concurrentPerform(iterations: 10) { _ in
    sharedTagger.string = someText  // 資料競爭
}

// 正確
await withTaskGroup(of: Void.self) { group in
    for _ in 0..<10 {
        group.addTask {
            let tagger = NLTagger(tagSchemes: [.lexicalClass])
            tagger.string = someText
            // 處理...
        }
    }
}

不要:混淆 NaturalLanguage 與 Core ML

NaturalLanguage 提供內建的語言分析。自訂訓練模型請使用 Core ML。兩者可透過 NLModel 互補。

// 錯誤:嘗試用原始 Core ML 做命名實體辨識
let coreMLModel = try MLModel(contentsOf: modelURL)

// 正確:使用 NLTagger 進行內建命名實體辨識
let tagger = NLTagger(tagSchemes: [.nameType])

// 或透過 NLModel 載入自訂 Core ML 模型
let nlModel = try NLModel(mlModel: coreMLModel)
tagger.setModels([nlModel], forTagScheme: .nameType)

不要:假設所有語言都有嵌入

並非所有語言在裝置上都有詞彙或句子嵌入可用。

// 錯誤:強制解包
let embedding = NLEmbedding.wordEmbedding(for: .japanese)!

// 正確:處理 nil
guard let embedding = NLEmbedding.wordEmbedding(for: .japanese) else {
    // 此語言無嵌入可用
    return
}

不要:為每個 token 建立新的標記器

建立與設定標記器成本較高。請對同一段文字重複使用。

// 錯誤:每個詞建立新標記器
for word in words {
    let tagger = NLTagger(tagSchemes: [.lexicalClass])
    tagger.string = word
}

// 正確:設定一次字串,然後列舉
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = fullText
tagger.enumerateTags(in: fullText.startIndex..<fullText.endIndex,
                     unit: .word, scheme: .lexicalClass, options: []) { tag, range in
    return true
}

不要:忽略短文字的語言提示

對短字串(約少於 20 個字元)的語言偵測不可靠。請設定限制或提示以提高準確度。

// 錯誤:偵測單一詞彙的語言
let lang = NLLanguageRecognizer.dominantLanguage(for: "chat")  // 法文還是英文?

// 正確:提供上下文
let recognizer = NLLanguageRecognizer()
recognizer.languageHints = [.english: 0.8, .french: 0.2]
recognizer.processString("chat")

審查清單

  • [ ] NLTokenizerNLTagger 實例在單一執行緒中使用
  • [ ] 標記器對每段文字只建立一次,而非每個 token
  • [ ] 語言偵測對短文字使用限制或提示
  • [ ] 使用 NLEmbedding 前檢查可用性(若不可用則回傳 nil)
  • [ ] 嘗試翻譯前先檢查 LanguageAvailability
  • [ ] .translationTask() 在 SwiftUI 視圖層級內使用
  • [ ] 批次翻譯使用 clientIdentifier 來配對回應與請求
  • [ ] 情感分數視為可選值(不支援的語言可能回傳 nil)
  • [ ] 命名實體辨識使用 .joinNames 選項以保留多詞名稱
  • [ ] 自訂 ML 模型透過 NLModel 載入,而非原始 Core ML

參考資料