NLP 与文本处理完全指南:从 BERT 微调到 RAG 系统、多语言处理
自然语言处理(NLP)是让计算机理解和生成人类语言的核心 AI 技术。本指南从文本预处理的基础出发,系统覆盖 BERT 微调、RAG 流水线构建、多语言处理等内容,并配有实战代码。
目录
- 文本预处理与分词
- 子词分词:BPE、WordPiece、SentencePiece
- 词嵌入:Word2Vec、FastText、GloVe
- 序列模型的演进:从 RNN 到 Transformer
- BERT 系列模型与微调
- 文本任务实战
- 构建 RAG 系统
- 多语言 NLP 与韩语的特殊性
- 小测验
1. 文本预处理与分词
1.1 韩语形态素分析(KoNLPy、MeCab)
韩语是一种黏着语(agglutinative language),词干与各种词缀结合来表达含义。因此英语那种基于空格的简单分词方式对韩语并不适用。KoNLPy 是韩语形态素分析的代表性 Python 库。
from konlpy.tag import Okt, Mecab, Komoran
okt = Okt()
text = "자연어 처리는 인공지능의 핵심 분야입니다."
# 形态素分析
morphs = okt.morphs(text)
print("形态素:", morphs)
# ['자연어', '처리', '는', '인공지능', '의', '핵심', '분야', '입니다', '.']
# 词性标注
pos_tags = okt.pos(text)
print("词性标注:", pos_tags)
# [('자연어', 'Noun'), ('처리', 'Noun'), ('는', 'Josa'), ...]
# 名词提取
nouns = okt.nouns(text)
print("名词:", nouns)
# ['자연어', '처리', '인공지능', '핵심', '분야']
# 使用 MeCab(更快、更准确的分析)
# mecab = Mecab()
# mecab_morphs = mecab.morphs(text)
MeCab 是将日语形态素分析器移植到韩语环境的工具,在速度和准确度方面表现出色。处理大规模数据时推荐使用 MeCab。
# 使用 KoNLPy 的韩语文本预处理流水线
import re
from konlpy.tag import Okt
def preprocess_korean(text: str) -> list[str]:
"""韩语文本预处理流水线"""
# 去除特殊字符(仅保留韩文、英文、数字)
text = re.sub(r'[^가-힣a-zA-Z0-9\s]', '', text)
# 去除多余空白
text = re.sub(r'\s+', ' ', text).strip()
okt = Okt()
# 仅提取名词、动词、形容词
tokens = [
word for word, pos in okt.pos(text)
if pos in ['Noun', 'Verb', 'Adjective'] and len(word) > 1
]
return tokens
sample = "자연어 처리 기술이 빠르게 발전하고 있습니다!"
print(preprocess_korean(sample))
1.2 停用词处理与文本归一化
# 韩语停用词处理
KOREAN_STOPWORDS = [
'이', '가', '을', '를', '은', '는', '의', '에', '에서',
'로', '으로', '와', '과', '도', '만', '까지', '부터'
]
def remove_stopwords(tokens: list[str], stopwords: list[str]) -> list[str]:
return [token for token in tokens if token not in stopwords]
# 文本归一化
def normalize_text(text: str) -> str:
# 去除重复字符: 'ㅋㅋㅋㅋ' -> 'ㅋㅋ'
text = re.sub(r'(.)\1{2,}', r'\1\1', text)
# 转换为英文小写
text = text.lower()
return text
2. 子词分词:BPE、WordPiece、SentencePiece
2.1 BPE(字节对编码)
BPE 通过反复合并频繁出现的字符对来构建词表,能有效解决 OOV(Out-of-Vocabulary,词表外词)问题,被 GPT 系列模型采用。
from tokenizers import Tokenizer, trainers, pre_tokenizers, models, decoders
# 训练 BPE 分词器
tokenizer = Tokenizer(models.BPE())
tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=True)
tokenizer.decoder = decoders.ByteLevel()
trainer = trainers.BpeTrainer(
vocab_size=30000,
min_frequency=2,
special_tokens=["[UNK]", "[PAD]", "[BOS]", "[EOS]"]
)
# 训练数据文件路径列表
files = ["train_corpus.txt"]
tokenizer.train(files, trainer)
# 分词示例
encoding = tokenizer.encode("자연어 처리는 fascinating합니다!")
print("Token:", encoding.tokens)
print("ID:", encoding.ids)
# 保存与加载
tokenizer.save("bpe_tokenizer.json")
loaded_tokenizer = Tokenizer.from_file("bpe_tokenizer.json")
2.2 WordPiece 与 SentencePiece
WordPiece 是 BERT 使用的子词分词方式,通过给子词加上 ## 前缀来表示连续性。SentencePiece 则是一种与语言无关的方式,无需按语言边界即可处理原始文本。
import sentencepiece as spm
# 训练 SentencePiece 模型
spm.SentencePieceTrainer.train(
input='corpus.txt',
model_prefix='sp_model',
vocab_size=32000,
character_coverage=0.9995, # 韩语/日语建议设置较高的值
model_type='bpe', # 或 'unigram'
pad_id=0,
unk_id=1,
bos_id=2,
eos_id=3
)
sp = spm.SentencePieceProcessor()
sp.load('sp_model.model')
text = "자연어 처리는 NLP의 핵심입니다."
tokens = sp.encode_as_pieces(text)
ids = sp.encode_as_ids(text)
print("SentencePiece Token:", tokens)
# ['▁자연어', '▁처리', '는', '▁NLP', '의', '▁핵심', '입니다', '.']
| 方式 | 使用模型 | 特点 |
|---|---|---|
| BPE | GPT-2、RoBERTa | 基于频率合并,结果确定 |
| WordPiece | BERT、DistilBERT | 似然最大化,## 前缀 |
| SentencePiece | T5、mBART、XLM-R | 语言无关,处理空白字符 |
| Unigram LM | Albert、mBERT | 基于概率模型,切分方式灵活 |
3. 词嵌入:Word2Vec、FastText、GloVe
3.1 Word2Vec
Word2Vec 是一种基于神经网络的词嵌入方法,包含 CBOW(Continuous Bag of Words)和 Skip-gram 两种架构。
from gensim.models import Word2Vec
from konlpy.tag import Okt
# 准备韩语语料
okt = Okt()
corpus = [
"자연어 처리는 인공지능의 핵심 분야입니다",
"BERT는 양방향 트랜스포머 모델입니다",
"텍스트 분류와 감성 분석에 BERT를 활용합니다",
]
# 形态素分析后进行分词
tokenized_corpus = [okt.morphs(sentence) for sentence in corpus]
# 训练 Word2Vec(Skip-gram)
model = Word2Vec(
sentences=tokenized_corpus,
vector_size=100, # 嵌入维度
window=5, # 上下文窗口大小
min_count=1,
sg=1, # 1: Skip-gram, 0: CBOW
workers=4,
epochs=100
)
# 查找相似词
similar_words = model.wv.most_similar('자연어', topn=5)
print("相似词:", similar_words)
# 提取词向量
vector = model.wv['자연어']
print("向量 shape:", vector.shape) # (100,)
# 词类比(king - man + woman ≈ queen)
result = model.wv.most_similar(
positive=['왕', '여자'],
negative=['남자'],
topn=3
)
3.2 FastText 与 GloVe
FastText 将词表示为字符 n-gram 之和,因而也能处理 OOV 词。GloVe 则是利用全局共现矩阵的一种嵌入方法。
from gensim.models import FastText
import numpy as np
# 训练 FastText
ft_model = FastText(
sentences=tokenized_corpus,
vector_size=100,
window=5,
min_count=1,
sg=1,
min_n=2, # 最小 n-gram 长度
max_n=6, # 最大 n-gram 长度
epochs=100
)
# 也能处理 OOV 词
# 训练数据中没有的词也可以通过 n-gram 生成向量
oov_vector = ft_model.wv['자연언어처리'] # 即使训练时未出现过也能处理
# 加载预训练的韩语 FastText 嵌入
from gensim.models import KeyedVectors
# cc.ko.300.vec: 基于 Common Crawl 的韩语 FastText(由 fasttext.cc 提供)
# ko_fasttext = KeyedVectors.load_word2vec_format('cc.ko.300.vec')
3.3 使用 Sentence Transformers 的句子嵌入
这是一种克服词级嵌入局限、以句子为单位进行嵌入的方法。
from sentence_transformers import SentenceTransformer
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
# 加载支持多语言的模型
model = SentenceTransformer('paraphrase-multilingual-mpnet-base-v2')
# 韩语专用模型
ko_model = SentenceTransformer('snunlp/KR-ELECTRA-discriminator')
sentences = [
"자연어 처리는 인공지능의 한 분야입니다.",
"NLP는 AI의 중요한 영역입니다.",
"오늘 날씨가 맑고 좋네요.",
]
# 生成句子嵌入
embeddings = model.encode(sentences)
print("嵌入 shape:", embeddings.shape) # (3, 768)
# 计算余弦相似度
sim_matrix = cosine_similarity(embeddings)
print("相似度矩阵:")
print(sim_matrix)
# 语义搜索
query = "언어 모델이란 무엇인가?"
query_embedding = model.encode([query])
similarities = cosine_similarity(query_embedding, embeddings)[0]
for sent, score in zip(sentences, similarities):
print(f"相似度 {score:.4f}: {sent}")
4. 序列模型的演进:从 RNN 到 Transformer
4.1 从 RNN 到 LSTM、GRU 的演进
import torch
import torch.nn as nn
# 基于 LSTM 的文本分类器
class LSTMClassifier(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
self.lstm = nn.LSTM(
embed_dim, hidden_dim,
num_layers=2,
batch_first=True,
dropout=0.3,
bidirectional=True
)
self.classifier = nn.Sequential(
nn.Linear(hidden_dim * 2, hidden_dim),
nn.ReLU(),
nn.Dropout(0.3),
nn.Linear(hidden_dim, num_classes)
)
def forward(self, x):
embedded = self.embedding(x) # (batch, seq_len, embed_dim)
output, (hidden, cell) = self.lstm(embedded)
# 拼接双向的最后一个 hidden state
final_hidden = torch.cat([hidden[-2], hidden[-1]], dim=1)
return self.classifier(final_hidden)
# GRU 参数比 LSTM 少,训练更快
class GRUClassifier(nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, num_classes):
super().__init__()
self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0)
self.gru = nn.GRU(
embed_dim, hidden_dim,
num_layers=2,
batch_first=True,
bidirectional=True
)
self.fc = nn.Linear(hidden_dim * 2, num_classes)
def forward(self, x):
embedded = self.embedding(x)
_, hidden = self.gru(embedded)
out = torch.cat([hidden[-2], hidden[-1]], dim=1)
return self.fc(out)
4.2 Attention 机制
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
self.d_model = d_model
self.num_heads = num_heads
self.d_k = d_model // num_heads
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
def scaled_dot_product_attention(self, Q, K, V, mask=None):
scores = torch.matmul(Q, K.transpose(-2, -1)) / (self.d_k ** 0.5)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attn_weights = torch.softmax(scores, dim=-1)
return torch.matmul(attn_weights, V), attn_weights
def forward(self, query, key, value, mask=None):
batch_size = query.size(0)
Q = self.W_q(query).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
K = self.W_k(key).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
V = self.W_v(value).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
attn_output, _ = self.scaled_dot_product_attention(Q, K, V, mask)
attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
return self.W_o(attn_output)
5. BERT 系列模型与微调
5.1 BERT 的预训练任务
BERT(Bidirectional Encoder Representations from Transformers)使用两种预训练任务。
- MLM(Masked Language Model,掩码语言模型):将输入 token 中的 15% 替换为
[MASK],并预测原始 token - NSP(Next Sentence Prediction,下一句预测):对两个句子是否实际连续进行二分类
5.2 使用 KLUE-BERT 进行韩语文本分类
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from transformers import TrainingArguments, Trainer
from datasets import Dataset
import torch
import numpy as np
from sklearn.metrics import accuracy_score, f1_score
# 加载 KLUE-BERT
model_name = "klue/bert-base"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(
model_name,
num_labels=5 # 情感分类: 非常负面/负面/中立/正面/非常正面
)
# 准备数据
train_data = {
"text": [
"이 영화 정말 재미있어요! 강력 추천합니다.",
"서비스가 너무 불친절하고 품질도 별로예요.",
"평범한 수준이네요. 특별한 점은 없었어요.",
],
"label": [4, 0, 2]
}
def tokenize_function(examples):
return tokenizer(
examples["text"],
max_length=128,
padding="max_length",
truncation=True
)
dataset = Dataset.from_dict(train_data)
tokenized_dataset = dataset.map(tokenize_function, batched=True)
# 训练配置
training_args = TrainingArguments(
output_dir="./klue-bert-sentiment",
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=32,
warmup_ratio=0.1,
weight_decay=0.01,
learning_rate=2e-5,
logging_dir="./logs",
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
metric_for_best_model="f1",
fp16=True, # 节省 GPU 显存
)
def compute_metrics(eval_pred):
logits, labels = eval_pred
predictions = np.argmax(logits, axis=-1)
return {
"accuracy": accuracy_score(labels, predictions),
"f1": f1_score(labels, predictions, average="weighted")
}
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
eval_dataset=tokenized_dataset,
compute_metrics=compute_metrics,
)
trainer.train()
5.3 KoELECTRA NER 微调
from transformers import AutoTokenizer, AutoModelForTokenClassification
from transformers import pipeline
# 基于 KoELECTRA 的 NER
model_name = "monologg/koelectra-base-finetuned-ner"
tokenizer = AutoTokenizer.from_pretrained(model_name)
ner_model = AutoModelForTokenClassification.from_pretrained(model_name)
# NER 流水线
ner_pipeline = pipeline(
"ner",
model=ner_model,
tokenizer=tokenizer,
aggregation_strategy="simple"
)
text = "삼성전자 이재용 회장이 서울 강남구에서 기자회견을 열었습니다."
entities = ner_pipeline(text)
for entity in entities:
print(f"实体: {entity['word']}, 类型: {entity['entity_group']}, "
f"得分: {entity['score']:.4f}")
# 实体: 삼성전자, 类型: ORG, 得分: 0.9987
# 实体: 이재용, 类型: PER, 得分: 0.9923
# 实体: 서울 강남구, 类型: LOC, 得分: 0.9856
5.4 RoBERTa、DeBERTa 对比
| 模型 | 主要改进点 | 特点 |
|---|---|---|
| BERT | 基准模型 | MLM + NSP |
| RoBERTa | 移除 NSP,使用更多数据 | 动态掩码,更大批次 |
| DeBERTa | 分离注意力机制 | 位置与内容分开处理 |
| KLUE-BERT | 韩语专用 | 使用韩语维基百科/新闻训练 |
| KoELECTRA | ELECTRA 方式 | Generator-Discriminator 结构 |
6. 文本任务实战
6.1 文本分类与情感分析
# 用于各种 NLP 任务的 HuggingFace 流水线
from transformers import pipeline
# 情感分析
sentiment = pipeline(
"sentiment-analysis",
model="snunlp/KR-FinBert-SC"
)
result = sentiment("이 제품은 가격 대비 품질이 매우 훌륭합니다.")
print(result) # [{'label': 'positive', 'score': 0.9876}]
# 文本摘要
summarizer = pipeline(
"summarization",
model="gogamza/kobart-summarization"
)
long_text = """
자연어 처리(NLP)는 컴퓨터가 인간의 언어를 이해하고 처리할 수 있도록 하는
인공지능의 한 분야입니다. 최근 트랜스포머 기반 모델의 등장으로 NLP 분야는
급격한 발전을 이루었습니다. BERT, GPT, T5 등의 모델은 다양한 NLP 태스크에서
인간 수준에 근접한 성능을 보여주고 있습니다.
"""
summary = summarizer(long_text, max_length=50, min_length=20)
print(summary[0]['summary_text'])
# 问答(QA)
qa_pipeline = pipeline(
"question-answering",
model="monologg/koelectra-base-v3-finetuned-korquad"
)
context = "서울은 대한민국의 수도이며 인구 약 950만 명이 거주하는 대도시입니다."
question = "서울의 인구는 얼마입니까?"
answer = qa_pipeline(question=question, context=context)
print(f"答案: {answer['answer']}, 得分: {answer['score']:.4f}")
6.2 机器翻译(Neural Machine Translation)
from transformers import MarianMTModel, MarianTokenizer
# 基于 Helsinki-NLP 的翻译模型
model_name = "Helsinki-NLP/opus-mt-ko-en"
tokenizer = MarianTokenizer.from_pretrained(model_name)
model = MarianMTModel.from_pretrained(model_name)
def translate(text: str, src_lang: str = "ko", tgt_lang: str = "en") -> str:
inputs = tokenizer(text, return_tensors="pt", padding=True)
translated = model.generate(**inputs, num_beams=5)
return tokenizer.decode(translated[0], skip_special_tokens=True)
korean_text = "인공지능 기술이 우리의 삶을 크게 변화시키고 있습니다."
english_translation = translate(korean_text)
print(f"翻译结果: {english_translation}")
7. 构建 RAG 系统
7.1 RAG 流水线概览
RAG(Retrieval-Augmented Generation,检索增强生成)是一种基于检索、利用外部知识来提升 LLM 回答质量的技术。大致由文档分块 → 嵌入 → 向量存储 → 检索 → 重排序 → 生成等阶段构成。
7.2 使用 LangChain 构建 RAG 流水线
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import PGVector
from langchain_community.chat_models import ChatOllama
from langchain.chains import RetrievalQA
from langchain.schema import Document
# 第一步: 文档分块
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=50,
separators=["\n\n", "\n", "。", ".", " ", ""],
length_function=len,
)
documents = [
Document(
page_content="BERT는 2018년 Google이 발표한 양방향 트랜스포머 모델입니다...",
metadata={"source": "nlp_guide.txt", "page": 1}
)
]
chunks = text_splitter.split_documents(documents)
print(f"分块数量: {len(chunks)}")
# 第二步: 设置嵌入模型(支持韩语)
embeddings = HuggingFaceEmbeddings(
model_name="jhgan/ko-sroberta-multitask",
model_kwargs={"device": "cuda"},
encode_kwargs={"normalize_embeddings": True}
)
# 第三步: 存入 pgvector
CONNECTION_STRING = "postgresql+psycopg2://user:password@localhost:5432/ragdb"
COLLECTION_NAME = "nlp_documents"
vectorstore = PGVector.from_documents(
documents=chunks,
embedding=embeddings,
collection_name=COLLECTION_NAME,
connection_string=CONNECTION_STRING,
)
# 第四步: 配置检索器
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 5}
)
# 第五步: 构建 RAG 链
llm = ChatOllama(model="llama3.1:8b-instruct-q4_K_M")
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True,
verbose=True,
)
query = "BERT의 사전 학습 방법은 무엇인가요?"
result = qa_chain.invoke({"query": query})
print(result["result"])
7.3 两阶段检索:Bi-encoder + Cross-encoder
from sentence_transformers import SentenceTransformer, CrossEncoder
import numpy as np
# 阶段 1: 用 Bi-encoder 检索候选文档(速度快)
bi_encoder = SentenceTransformer('jhgan/ko-sroberta-multitask')
# 阶段 2: 用 Cross-encoder 重排序(精度高)
cross_encoder = CrossEncoder('cross-encoder/mmarco-mMiniLMv2-L12-H384-v1')
def two_stage_retrieval(query: str, corpus: list[str], top_k: int = 5) -> list[str]:
"""Bi-encoder + Cross-encoder 两阶段检索"""
# 阶段 1: 快速初步检索(取前 20 个)
query_emb = bi_encoder.encode(query, convert_to_tensor=True)
corpus_emb = bi_encoder.encode(corpus, convert_to_tensor=True)
from sentence_transformers.util import cos_sim
scores = cos_sim(query_emb, corpus_emb)[0]
top_indices = scores.argsort(descending=True)[:20].tolist()
candidates = [corpus[i] for i in top_indices]
# 阶段 2: 用 Cross-encoder 精细重排序
pairs = [[query, doc] for doc in candidates]
rerank_scores = cross_encoder.predict(pairs)
# 返回最终的 top_k
ranked = sorted(
zip(candidates, rerank_scores),
key=lambda x: x[1],
reverse=True
)
return [doc for doc, _ in ranked[:top_k]]
# 使用示例
corpus = [
"BERT는 마스크 언어 모델로 사전 학습됩니다.",
"GPT는 자동회귀 방식으로 텍스트를 생성합니다.",
"오늘 날씨는 맑고 기온은 20도입니다.",
]
results = two_stage_retrieval("BERT 학습 방법", corpus)
for doc in results:
print(f"- {doc}")
8. 多语言 NLP 与韩语的特殊性
8.1 mBERT 与 XLM-R
from transformers import AutoTokenizer, AutoModel
import torch
# XLM-RoBERTa: 支持 100 种语言
xlmr_tokenizer = AutoTokenizer.from_pretrained("xlm-roberta-base")
xlmr_model = AutoModel.from_pretrained("xlm-roberta-base")
# 处理多语言句子
sentences = {
"ko": "자연어 처리는 인공지능의 핵심입니다.",
"en": "NLP is the core of artificial intelligence.",
"ja": "自然言語処理はAIの核心です。",
}
for lang, sentence in sentences.items():
inputs = xlmr_tokenizer(
sentence,
return_tensors="pt",
max_length=128,
padding=True,
truncation=True
)
with torch.no_grad():
outputs = xlmr_model(**inputs)
# 使用 [CLS] token 的嵌入作为句子表示
sentence_embedding = outputs.last_hidden_state[:, 0, :]
print(f"{lang}: embedding shape = {sentence_embedding.shape}")
8.2 CJK(中日韩)分词的特殊性
CJK(Chinese、Japanese、Korean)语言的空格分词效果不佳,需要特殊处理。
from transformers import BertTokenizer
# BERT 默认分词器对 CJK 的处理
tokenizer = BertTokenizer.from_pretrained("bert-base-multilingual-cased")
texts = {
"韩语": "자연어처리", # 黏着语: 助词/词尾附着在词干上
"中文": "自然语言处理", # 孤立语: 每个字都是独立的意义单位
"日语": "自然言語処理", # 黏着语 + 汉字/平假名/片假名混用
"英语": "NaturalLanguageProcessing",
}
for lang, text in texts.items():
tokens = tokenizer.tokenize(text)
print(f"{lang}: {tokens}")
8.3 韩语专用预处理
# 考虑韩语黏着语特性的预处理
# 词节 = 词干 + 词缀(可有无穷变化)
# 例: '먹다'、'먹어'、'먹었다'、'먹을'、'먹히다'、'먹이다'
from konlpy.tag import Mecab
def extract_korean_features(text: str) -> dict:
"""从韩语文本中提取语言学特征"""
mecab = Mecab()
pos_tags = mecab.pos(text)
features = {
"nouns": [], # 名词(主要信息单元)
"verbs": [], # 动词原形
"adjectives": [], # 形容词原形
"entities": [] # 专有名词
}
for word, tag in pos_tags:
if tag.startswith('NN'): # 名词类
features["nouns"].append(word)
elif tag.startswith('VV'): # 动词
features["verbs"].append(word)
elif tag.startswith('VA'): # 形容词
features["adjectives"].append(word)
elif tag == 'NNP': # 专有名词
features["entities"].append(word)
return features
text = "삼성전자가 새로운 인공지능 반도체를 개발했다고 발표했습니다."
features = extract_korean_features(text)
print(features)
小测验
Q1. BPE 分词是如何解决 OOV 问题的?
答案: BPE 通过反复合并频繁出现的字符对来构建子词词表。即便是未见过的词,也能被分解为已学到的子词单元,从而将 OOV 降到最低。
解析: 传统的基于词的词表,对训练时未出现过的词会处理为 [UNK],这就是 OOV 问题。BPE 从字符层级出发,合并频繁出现的字符对,因此新造词或复合词也都能用已有子词的组合来表示。例如即使训练词表中没有"한국어NLP",也能被分解为"한국어"、"NLP"或更小的单位。
Q2. BERT 的 MLM 和 NSP 预训练任务分别是什么?
答案: MLM 是将输入 token 中的 15% 随机掩码,并预测原始 token 的任务;NSP 是对两个句子是否实际连续进行二分类的任务。
解析: MLM 让模型学习双向上下文,使 BERT 能够深入理解语境。被掩码的 15% 中,80% 替换为 [MASK],10% 替换为随机 token,10% 保持原始 token 不变。NSP 让模型学习句子间的关系,但后来 RoBERTa 等模型发现 NSP 对性能提升没有贡献,因而将其移除。
Q3. 在 RAG 中同时使用 bi-encoder 和 cross-encoder 的两阶段检索有什么好处?
答案: Bi-encoder 独立地对查询和文档进行编码,可以实现快速的 ANN 检索;Cross-encoder 则将查询-文档对一起处理,计算出精确的相关性得分。将两个阶段结合,就能同时兼顾速度和准确度。
解析: 只使用 Bi-encoder 虽然快,但无法捕捉查询与文档之间的交互,准确度较低。Cross-encoder 虽然准确,但需要对每一对文档都进行计算,因而速度较慢。Two-stage 方式先用 bi-encoder 筛选出候选集(20-100 个),再用 cross-encoder 进行重排序,在准确度和效率之间取得平衡。
Q4. 为什么 SBERT 在计算句子相似度时比 BERT 更高效?
答案: SBERT 用 Siamese/Triplet 网络把句子编码为固定大小的向量,因此计算相似度时只需对预先算好的向量做余弦相似度计算即可。而 BERT 需要把两个句子拼接起来,每次都要重新做一次前向传播。
解析: 要计算 n 个句子两两之间的相似度,BERT 需要 n(n-1)/2 次前向传播(约需 65 小时),而 SBERT 只需 n 次编码,之后只做向量运算即可(约需 5 秒),快了数千倍。这在语义搜索、聚类等需要大规模两两比较的任务中尤其实用。
Q5. 为什么韩语 NLP 中的黏着语特性要求与英语不同的处理方式?
答案: 韩语是词干与助词、词尾、词缀结合而成的黏着语,词形变化非常丰富。'먹다'、'먹어요'、'먹었습니다' 都派生自同一个词干,因此必须通过形态素分析提取出词干,才能将它们识别为同一个概念。
解析: 英语的词形变化相对简单,如 'eat'、'eats'、'eating',用简单的词干提取即可处理。而韩语的一个词干可以与数百种词缀组合,因此形态素分析器(MeCab、Okt 等)必不可少。此外,按空格切分时助词会附着在词干上,导致 '인공지능의'、'인공지능이'、'인공지능을' 被当作三个不同的词处理,这也是一个问题。
结语
本指南结合实战代码,覆盖了从文本预处理、BPE 分词、Word2Vec/FastText 词嵌入、BERT 微调、RAG 流水线到多语言处理等 NLP 的核心主题。韩语 NLP 的关键在于结合黏着语特性做形态素分析;而在 RAG 系统中,采用两阶段检索(bi-encoder + cross-encoder)可以同时兼顾速度和准确度。
현재 단락 (1/473)
自然语言处理(NLP)是让计算机理解和生成人类语言的核心 AI 技术。本指南从文本预处理的基础出发,系统覆盖 BERT 微调、RAG 流水线构建、多语言处理等内容,并配有实战代码。