mcpField
N
natural-language

natural-language

github.com/dpearson2699開発1.2k3.7k 回インストール13 週間前に更新
リポジトリ

natural-language について

Apple の NaturalLanguage と Translation フレームワークを使うアプリ開発者向けのガイドです。言語タグ付けや埋め込みなどのテキスト分析と、アプリ内翻訳を扱います。

主な機能

  1. 01単語・文・段落のトークン化
  2. 02言語識別と言語タグ付け
  3. 03固有表現と感情の分析
  4. 04単語と文の埋め込み
  5. 05アプリ内翻訳と一括翻訳

活用シーン

  1. 01テキストの分割と言語の識別
  2. 02名前や品詞の抽出
  3. 03感情スコアの算出や意味的類似度の比較
  4. 04アプリ内でのテキスト翻訳

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill natural-language

インストール後、Claude Code で/natural-languageを入力して読み込まれたか確認します。

同梱ファイルevals/evals.jsonreferences/translation-patterns.md

詳細

NaturalLanguage + Translation

自然言語テキストのトークン化、品詞タグ付け、固有表現認識、感情分析、言語識別、単語・文の埋め込みを分析します。Translation フレームワークを使って、言語間でテキストを翻訳します。

このスキルでは、関連する 2 つのフレームワークを扱います。オンデバイスでテキストを分析する NaturalLanguage(NLTokenizer、NLTagger、NLEmbedding)と、言語を翻訳する Translation(TranslationSession、LanguageAvailability)です。

対象範囲の境界: テキストを取得した後に、このスキルを使用してください。トークン化、言語識別、品詞・固有表現のタグ付け、感情分析、埋め込み、カスタムの NLModel 分類器・タグ付け器、アプリ内翻訳を扱います。OCR は vision-framework に、音声からテキストへの変換は speech-recognition に、UI 文字列とロケールの書式設定は ios-localization に、生成 AI による要約や 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 のクラス(NLTokenizer、NLTagger)はスレッドセーフではありません。各インスタンスは、一度に 1 つのスレッドまたはディスパッチキューから使用してください。

トークン化

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("Number: \(text[range])")
    }
    return true // continue enumeration
}

言語識別

NLLanguageRecognizer を使って、文字列の主要な言語を検出します。

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

// Multiple hypotheses with confidence scores
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 values: .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("Translate") {
                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
                }
            }
        }
    }
}
バッチ翻訳

1 回のセッションで複数の文字列を翻訳します。

.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 {
        // Handle cancellation, unsupported languages, or download refusal.
    }
}
言語の利用可能状況の確認
let availability = LanguageAvailability()
let status = await availability.status(
    from: Locale.Language(identifier: "en"),
    to: Locale.Language(identifier: "ja")
)
switch status {
case .installed: break    // Ready to translate offline
case .supported: break    // Needs download
case .unsupported: break  // Language pair not available
}

よくある間違い

してはいけないこと:NLTagger/NLTokenizer をスレッド間で共有する

これらのクラスはスレッドセーフではないため、誤った結果が生じたり、クラッシュしたりする可能性があります。

// WRONG
let sharedTagger = NLTagger(tagSchemes: [.lexicalClass])
DispatchQueue.concurrentPerform(iterations: 10) { _ in
    sharedTagger.string = someText  // Data race
}

// CORRECT
await withTaskGroup(of: Void.self) { group in
    for _ in 0..<10 {
        group.addTask {
            let tagger = NLTagger(tagSchemes: [.lexicalClass])
            tagger.string = someText
            // process...
        }
    }
}
してはいけないこと:NaturalLanguage と Core ML を混同する

NaturalLanguage は、組み込みの言語分析機能を提供します。カスタム学習済みモデルには Core ML を使用します。NLModel を介して両者を連携させることができます。

// WRONG: Trying to do NER with raw Core ML
let coreMLModel = try MLModel(contentsOf: modelURL)

// CORRECT: Use NLTagger for built-in NER
let tagger = NLTagger(tagSchemes: [.nameType])

// Or load a custom Core ML model via NLModel
let nlModel = try NLModel(mlModel: coreMLModel)
tagger.setModels([nlModel], forTagScheme: .nameType)
してはいけないこと:すべての言語で埋め込みが利用できると思い込む

すべての言語で、デバイス上の単語または文の埋め込みが利用できるとは限りません。

// WRONG: Force unwrap
let embedding = NLEmbedding.wordEmbedding(for: .japanese)!

// CORRECT: Handle nil
guard let embedding = NLEmbedding.wordEmbedding(for: .japanese) else {
    // Embedding not available for this language
    return
}
してはいけないこと:トークンごとに新しいタグ付け器を作成する

タグ付け器の作成と設定にはコストがかかります。同じテキストには再利用してください。

// WRONG: New tagger per word
for word in words {
    let tagger = NLTagger(tagSchemes: [.lexicalClass])
    tagger.string = word
}

// CORRECT: Set string once, enumerate
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文字未満)の言語検出は信頼性が低くなります。 精度を高めるには、制約またはヒントを設定してください。

// WRONG: Detect language of a single word
let lang = NLLanguageRecognizer.dominantLanguage(for: "chat")  // French or English?

// CORRECT: Provide context
let recognizer = NLLanguageRecognizer()
recognizer.languageHints = [.english: 0.8, .french: 0.2]
recognizer.processString("chat")

確認チェックリスト

  • NLTokenizer と NLTagger のインスタンスは、単一のスレッドから使用する
  • タグ付け器はトークンごとではなく、テキストごとに一度だけ作成する
  • 短いテキストの言語検出には、制約/ヒントを使用する
  • 使用前に NLEmbedding の利用可否を確認する(利用できない場合は nil を返す)
  • 翻訳を試みる前に LanguageAvailability を確認する
  • .translationTask() は SwiftUI ビュー階層内で使用する
  • バッチ翻訳では clientIdentifier を使い、応答をリクエストと照合する
  • 感情スコアはオプショナルとして扱う(未対応の言語では nil が返される場合がある)
  • NER では .joinNames オプションを使い、複数語の名前が分割されないようにする
  • カスタム ML モデルは、生の Core ML ではなく NLModel を介して読み込む

参考資料

コメント