Section 6/201 menit
6. Natural Language Framework: NLP
6. Natural Language Framework: NLP
Language Detection dan Tokenization
swift
// Natural Language: dasar NLP pipeline
import NaturalLanguage
final class TextAnalyzer {
// MARK: — Language Detection
func detectLanguage(of text: String) -> NLLanguage? {
let recognizer = NLLanguageRecognizer()
recognizer.processString(text)
return recognizer.dominantLanguage
}
func detectAllLanguages(of text: String) -> [(NLLanguage, Double)] {
let recognizer = NLLanguageRecognizer()
recognizer.processString(text)
return recognizer
.languageHypotheses(withMaximum: 5)
.sorted { $0.value > $1.value }
.map { ($0.key, $0.value) }
}
// MARK: — Tokenization
func tokenize(_ text: String, unit: NLTokenUnit = .word) -> [String] {
var tokens: [String] = []
let tokenizer = NLTokenizer(unit: unit)
tokenizer.string = text
tokenizer.enumerateTokens(in: text.startIndex..<text.endIndex) { range, _ in
tokens.append(String(text[range]))
return true
}
return tokens
}
// MARK: — Named Entity Recognition (NER)
func extractEntities(from text: String) -> [NamedEntity] {
var entities: [NamedEntity] = []
let tagger = NLTagger(tagSchemes: [.nameType])
tagger.string = text
tagger.setLanguage(.english, range: text.startIndex..<text.endIndex)
tagger.enumerateTags(in: text.startIndex..<text.endIndex, unit: .word, scheme: .nameType) { tag, range in
guard let tag,
[.personalName, .organizationName, .placeName].contains(tag) else {
return true
}
entities.append(NamedEntity(
text: String(text[range]),
type: tag,
range: range
))
return true
}
return entities
}
// MARK: — Part of Speech Tagging
func tagPartsOfSpeech(in text: String) -> [(String, NLTag)] {
var result: [(String, NLTag)] = []
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = text
tagger.enumerateTags(in: text.startIndex..<text.endIndex, unit: .word, scheme: .lexicalClass) { tag, range in
if let tag {
result.append((String(text[range]), tag))
}
return true
}
return result
}
// MARK: — Sentiment Analysis
func analyzeSentiment(of text: String) -> Sentiment {
let tagger = NLTagger(tagSchemes: [.sentimentScore])
tagger.string = text
let (tag, _) = tagger.tag(at: text.startIndex, unit: .paragraph, scheme: .sentimentScore)
let score = Double(tag?.rawValue ?? "0") ?? 0
switch score {
case 0.2...: return .positive(score)
case ..<(-0.2): return .negative(score)
default: return .neutral(score)
}
}
}
struct NamedEntity {
let text: String
let type: NLTag
let range: Range<String.Index>
}
enum Sentiment {
case positive(Double)
case negative(Double)
case neutral(Double)
var score: Double {
switch self {
case .positive(let s), .negative(let s), .neutral(let s): return s
}
}
}
NLEmbedding untuk Semantic Search
swift
// Natural Language: semantic similarity menggunakan word embeddings
import NaturalLanguage
final class SemanticSearchEngine {
private let embedding: NLEmbedding
init?() {
guard let embedding = NLEmbedding.sentenceEmbedding(for: .english) else {
return nil
}
self.embedding = embedding
}
// Hitung similarity antara dua kalimat (0.0 = tidak mirip, 1.0 = identik)
func similarity(between text1: String, and text2: String) -> Double {
guard let vector1 = embedding.vector(for: text1),
let vector2 = embedding.vector(for: text2) else {
return 0
}
return cosineSimilarity(vector1, vector2)
}
// Cari dokumen yang paling mirip dengan query
func search(query: String, in documents: [String], topK: Int = 5) -> [(document: String, score: Double)] {
guard let queryVector = embedding.vector(for: query) else { return [] }
let scored = documents.compactMap { document -> (String, Double)? in
guard let docVector = embedding.vector(for: document) else { return nil }
let score = cosineSimilarity(queryVector, docVector)
return (document, score)
}
return scored
.sorted { $0.1 > $1.1 }
.prefix(topK)
.map { (document: $0.0, score: $0.1) }
}
// Cosine similarity: dot product / (magnitude1 * magnitude2)
private func cosineSimilarity(_ v1: [Double], _ v2: [Double]) -> Double {
guard v1.count == v2.count, !v1.isEmpty else { return 0 }
let dotProduct = zip(v1, v2).reduce(0) { $0 + $1.0 * $1.1 }
let magnitude1 = sqrt(v1.reduce(0) { $0 + $1 * $1 })
let magnitude2 = sqrt(v2.reduce(0) { $0 + $1 * $1 })
guard magnitude1 > 0 && magnitude2 > 0 else { return 0 }
return dotProduct / (magnitude1 * magnitude2)
}
// Cluster teks berdasarkan similarity
func findNeighbors(of text: String, in corpus: [String], threshold: Double = 0.7) -> [String] {
guard let queryVector = embedding.vector(for: text) else { return [] }
return corpus.filter { document in
guard let docVector = embedding.vector(for: document) else { return false }
return cosineSimilarity(queryVector, docVector) >= threshold
}
}
}