natural-language

natural-language

热门

使用 Apple 的 NaturalLanguage 框架对自然语言文本进行分词、标注和分析,并使用 Translation 框架进行语言间翻译。适用于为 iOS/macOS/visionOS 应用添加语言识别、情感分析、命名实体识别、词性标注、文本嵌入或应用内翻译功能。

936Star
47Fork
更新于 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不是线程安全的。每个实例应仅在一个线程或调度队列中使用。

分词

使用 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:) 检测数字或表情符号标记。

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 每个标记的语言
.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() 显示内置翻译界面。

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: "zh-Hans"))
            }
        }
        .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
                }
            }
        }
    }
}

批量翻译

在单个会话中翻译多个字符串。

.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
}

不要:为每个标记创建新的标注器

创建和配置标注器开销很大。对同一文本应重复使用。

// 错误:每个单词新建标注器
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 实例在单个线程中使用
  • [ ] 标注器为每段文本创建一次,而非每个标记
  • [ ] 语言检测对短文本使用约束/提示
  • [ ] 使用 NLEmbedding 前检查可用性(不可用时返回 nil)
  • [ ] 尝试翻译前检查 LanguageAvailability
  • [ ] .translationTask() 在 SwiftUI 视图层次结构中使用
  • [ ] 批量翻译使用 clientIdentifier 匹配响应与请求
  • [ ] 情感分数作为可选值处理(对不支持的语言可能返回 nil)
  • [ ] 命名实体识别中使用 .joinNames 选项以保持多词名称完整
  • [ ] 自定义 ML 模型通过 NLModel 加载,而非原始 Core ML

参考