natural-language について
Apple の NaturalLanguage と Translation フレームワークを使うアプリ開発者向けのガイドです。言語タグ付けや埋め込みなどのテキスト分析と、アプリ内翻訳を扱います。
主な機能
- 01単語・文・段落のトークン化
- 02言語識別と言語タグ付け
- 03固有表現と感情の分析
- 04単語と文の埋め込み
- 05アプリ内翻訳と一括翻訳
活用シーン
- 01テキストの分割と言語の識別
- 02名前や品詞の抽出
- 03感情スコアの算出や意味的類似度の比較
- 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を介して読み込む