最近在开发一个基于文本的情感分析系统时遇到了一个棘手的问题如何准确识别和处理文本中带有强烈情感色彩和特殊语气的表达比如一些网络用语或特定场景下的感叹词。这类文本往往结构松散包含大量非标准词汇和重复符号给传统的自然语言处理NLP模型带来了不小的挑战。本文将从实际项目需求出发分享一套完整的解决方案涵盖从数据预处理、特征工程到模型选型与调优的全流程并提供可复现的代码示例。无论你是刚接触NLP的新手还是希望优化现有情感分析系统的开发者都能从中找到实用的思路和代码。1. 背景与核心概念非标准文本情感分析在社交媒体、游戏聊天、小说评论等场景中用户生成的文本User-Generated Content, UGC常常包含丰富的非标准表达。例如像输入标题中“啊那里不可以哦齁齁”这样的句子它并非语法规范的陈述句而是通过语气词啊、哦、重复符号、和拟声词齁齁来传递一种强烈、戏剧化的情感可能是害羞、抗拒、调侃或兴奋。对于传统的情感分析模型如基于词典的方法或早期的机器学习模型这类文本是典型的“脏数据”。它们难以被准确分词情感极性正面/负面模糊且严重依赖上下文。因此我们需要一套专门的处理流程其核心目标不是进行严格的语法分析而是抽取情感信号。核心挑战与解决思路分词困难语气词、拟声词和特殊符号可能被错误切割或忽略。思路使用更灵活的分词工具或结合正则表达式进行预处理。特征稀疏非标准词汇在训练语料中出现频率极低。思路采用字符级Char-level或子词级Subword模型如BERT或者使用文本向量化方法如TF-IDF结合N-gram特征。语境依赖同样的词在不同语境下情感不同。思路使用基于上下文的预训练模型如ERNIE、RoBERTa或引入注意力机制。符号与重复感叹号、波浪线、重复字符本身是重要的情感强度指示器。思路在预处理阶段将这些符号转化为可量化的特征。本文将围绕一个具体的实战项目展示如何构建一个能够较好处理此类文本的情感分析系统。2. 环境准备与版本说明本项目主要使用Python生态下的工具库。建议使用Python 3.8及以上版本以保证库的兼容性。操作系统Windows 10/11, macOS, 或 Linux (如Ubuntu 20.04) 均可。Python版本3.8.10主要依赖库数据处理与科学计算pandas, numpy文本处理与机器学习scikit-learn, jieba (中文分词)深度学习框架PyTorch 或 TensorFlow (本文以PyTorch为例)预训练模型与NLP工具transformers (Hugging Face), zhconv (简繁转换)可视化matplotlib, seaborn (用于分析数据分布)版本管理建议强烈建议使用虚拟环境如venv或conda来管理依赖避免包冲突。创建环境与安装依赖# 创建并激活虚拟环境 (以venv为例) python -m venv nlp_sentiment_env # Windows: nlp_sentiment_env\Scripts\activate # Linux/macOS: source nlp_sentiment_env/bin/activate # 升级pip pip install --upgrade pip # 安装核心依赖 pip install pandas numpy scikit-learn jieba zhconv matplotlib seaborn # 安装PyTorch (请根据你的CUDA版本前往PyTorch官网获取对应命令) # 例如对于无GPU或CUDA 11.7的环境 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu # 安装transformers pip install transformers项目结构预览sentiment_analysis_project/ │ ├── data/ │ ├── raw/ # 存放原始数据 │ ├── processed/ # 存放处理后的数据 │ └── stopwords.txt # 停用词表 │ ├── src/ │ ├── preprocess.py # 数据预处理模块 │ ├── feature_engineer.py # 特征工程模块 │ ├── model.py # 模型定义 │ ├── train.py # 训练脚本 │ └── predict.py # 预测脚本 │ ├── notebooks/ # Jupyter notebook用于探索性分析 ├── models/ # 保存训练好的模型 ├── config.yaml # 配置文件 ├── requirements.txt # 依赖列表 └── README.md3. 核心处理流程与原理拆解面对非标准文本我们的处理管道Pipeline需要比传统流程更加精细和健壮。3.1 数据预处理清洗与标准化预处理的目标是将“脏”文本转化为相对干净、规整的文本同时尽可能保留情感信号。关键步骤文本清洗移除无关噪声如HTML标签、URL、邮箱、用户等。特殊符号处理情感符号如❤️, 和标点如, ~~~需要特殊对待。我们可以选择保留并标准化将连续多个感叹号“”替换为“_REPEAT3”作为一个特征。转化为文本描述将表情符号转化为如“[爱心]”、“[笑哭]”的标签。完全移除对于某些分析可能选择简单移除。繁体转简体中文UGC内容常繁简混杂统一转为简体便于处理。分词使用适合网络用语的分词工具。jieba可以添加自定义词典来识别网络新词。停用词过滤谨慎处理对于情感分析“啊”、“哦”、“呀”等语气词可能是重要的不应盲目移除。需要定制情感分析专用的停用词表。代码示例src/preprocess.pyimport re import zhconv import jieba from typing import List, Optional class TextPreprocessor: def __init__(self, stopwords_path: Optional[str] None): self.stopwords set() if stopwords_path: with open(stopwords_path, r, encodingutf-8) as f: for line in f: self.stopwords.add(line.strip()) # 可以在这里为jieba添加自定义词典 # jieba.load_userdict(data/custom_dict.txt) def clean_text(self, text: str) - str: 基础清洗 # 移除URL text re.sub(rhttp\S|www\.\S, , text) # 移除提及和话题标签根据需求 text re.sub(r\w|#\w, , text) # 处理连续标点将超过2个的重复标点标准化 text re.sub(r([!?。]){2,}, r\1_REPEAT, text) # 繁体转简体 text zhconv.convert(text, zh-cn) # 去除多余空白字符 text re.sub(r\s, , text).strip() return text def segment(self, text: str, remove_stopwords: bool False) - List[str]: 分词可选择是否过滤停用词 words jieba.lcut(text) if remove_stopwords: words [w for w in words if w not in self.stopwords and w.strip()] return words def process_pipeline(self, text: str) - str: 完整的预处理管道清洗 - 分词 - 重组用于词袋模型 cleaned self.clean_text(text) words self.segment(cleaned, remove_stopwordsFalse) # 情感分析不过滤停用词 # 返回用空格连接的分词结果适用于后续的TF-IDF或CountVectorizer return .join(words) # 使用示例 if __name__ __main__: preprocessor TextPreprocessor(stopwords_pathdata/stopwords.txt) sample_text “圣采儿啊那里不可以哦齁齁笑死我了http://example.com” processed preprocessor.process_pipeline(sample_text) print(f原始文本: {sample_text}) print(f处理后: {processed}) # 输出可能类似于”圣 采儿 啊 那里 不可以 _REPEAT 哦 齁齁 _REPEAT 笑死 我 了 ”3.2 特征工程从文本到数值清洗后的文本需要转化为机器学习模型可以理解的数值特征。常用方法词袋模型Bag-of-Words与TF-IDF原理将文本表示为词汇表中单词出现频率的向量。TF-IDF在此基础上降低了常见词的权重。优点简单可解释。缺点忽略词序无法处理未登录词OOV对于“齁齁”这类词特征稀疏。改进使用字符级N-gram如2-gram或3-gram。对于“哦齁齁”字符3-gram可能是“哦齁”、“齁齁”。这能有效捕捉非标准词汇和部分词序信息。词向量Word Embedding原理如Word2Vec、GloVe将单词映射到稠密向量空间语义相似的词向量接近。缺点同样无法处理未登录词。上下文词向量Contextual Embedding原理如BERT、ERNIE等预训练模型能够根据上下文动态生成每个词的向量表示。这是处理非标准文本和歧义的最强有力工具。优点能理解“那里不可以”在不同语境下的情感差异并能对“齁齁”这种词生成合理的向量基于其组成字符和上下文。代码示例src/feature_engineer.py展示TF-IDF与字符N-gram结合from sklearn.feature_extraction.text import TfidfVectorizer import pandas as pd class FeatureExtractor: def __init__(self, use_char_ngram: bool True, ngram_range(1, 3)): :param use_char_ngram: 是否使用字符级N-gram :param ngram_range: N-gram范围(1,1)为单词(1,3)为单词字符1-3gram self.use_char_ngram use_char_ngram self.vectorizer None self.ngram_range ngram_range def build_vocabulary(self, corpus: list): 在训练集上拟合TF-IDF向量化器 # 如果使用字符N-gram需要先将文本转化为字符序列用空格隔开每个字符 if self.use_char_ngram: # 将句子如“我 爱 中国” - “我 爱 中 国” corpus [ .join(list(doc.replace( , ))) for doc in corpus] # 此时analyzerchar会对字符序列进行ngram划分 self.vectorizer TfidfVectorizer(analyzerchar, ngram_rangeself.ngram_range, max_features5000) else: self.vectorizer TfidfVectorizer(ngram_rangeself.ngram_range, max_features5000) self.vectorizer.fit(corpus) def transform(self, text_list: list) - pd.DataFrame: 将文本列表转化为特征矩阵 if self.use_char_ngram: text_list [ .join(list(doc.replace( , ))) for doc in text_list] features self.vectorizer.transform(text_list) return pd.DataFrame(features.toarray(), columnsself.vectorizer.get_feature_names_out()) # 使用示例 if __name__ __main__: # 假设我们有预处理后的训练文本 train_texts [ “圣 采儿 啊 那里 不可以 _REPEAT”, “哈哈 太好 笑 了 吧”, “真 的 很 无聊 …” ] labels [1, 1, 0] # 1代表正面0代表负面 extractor FeatureExtractor(use_char_ngramTrue, ngram_range(2,4)) # 使用字符2-4gram extractor.build_vocabulary(train_texts) train_features extractor.transform(train_texts) print(特征维度:, train_features.shape) print(部分特征名:, train_features.columns[:10])3.3 模型选择从传统机器学习到深度学习传统机器学习模型适用于TF-IDF特征逻辑回归Logistic Regression简单、可解释性强是很好的基线模型。支持向量机SVM在高维特征空间表现良好。朴素贝叶斯Naive Bayes计算快适合文本分类但特征独立性假设较强。深度学习模型适用于词向量或直接端到端学习TextCNN使用卷积神经网络捕捉文本中的局部特征如N-gram特征。LSTM/GRU循环神经网络能捕捉文本序列的长期依赖关系。预训练模型微调Fine-tuning当前的最优解。使用BERT等模型的[CLS] token输出或所有token输出的平均/最大池化接一个全连接层进行分类。对于非标准文本情感分析推荐路径是快速基线TF-IDF含字符N-gram 逻辑回归/SVM。追求性能预训练模型如bert-base-chinese,ernie-3.0-base-zh 微调。4. 完整实战案例基于BERT微调的情感分析系统我们将使用Hugging Face的transformers库微调一个中文预训练模型来完成情感二分类任务。4.1 数据准备假设我们有一个CSV文件data/raw/sentiment_data.csv包含两列text原始文本和label0/1。# notebooks/01_data_exploration.ipynb 或 src/data_prep.py import pandas as pd from sklearn.model_selection import train_test_split # 加载数据 df pd.read_csv(data/raw/sentiment_data.csv) print(df.head()) print(f数据量: {len(df)}) print(f标签分布:\n{df[label].value_counts()}) # 应用预处理 from src.preprocess import TextPreprocessor preprocessor TextPreprocessor() df[processed_text] df[text].apply(preprocessor.process_pipeline) # 划分训练集、验证集、测试集 train_df, temp_df train_test_split(df, test_size0.3, random_state42, stratifydf[label]) val_df, test_df train_test_split(temp_df, test_size0.5, random_state42, stratifytemp_df[label]) print(f训练集: {len(train_df)}, 验证集: {len(val_df)}, 测试集: {len(test_df)}) # 保存处理后的数据 train_df[[processed_text, label]].to_csv(data/processed/train.csv, indexFalse) val_df[[processed_text, label]].to_csv(data/processed/val.csv, indexFalse) test_df[[processed_text, label]].to_csv(data/processed/test.csv, indexFalse)4.2 构建PyTorch Dataset与DataLoader# src/dataset.py import torch from torch.utils.data import Dataset, DataLoader from transformers import BertTokenizer class SentimentDataset(Dataset): def __init__(self, dataframe, tokenizer, max_len): self.data dataframe self.tokenizer tokenizer self.max_len max_len def __len__(self): return len(self.data) def __getitem__(self, index): text str(self.data.iloc[index][processed_text]) label int(self.data.iloc[index][label]) encoding self.tokenizer.encode_plus( text, add_special_tokensTrue, max_lengthself.max_len, paddingmax_length, truncationTrue, return_attention_maskTrue, return_tensorspt, ) return { input_ids: encoding[input_ids].flatten(), attention_mask: encoding[attention_mask].flatten(), labels: torch.tensor(label, dtypetorch.long) } def create_data_loader(df, tokenizer, max_len, batch_size, shuffleTrue): dataset SentimentDataset(df, tokenizer, max_len) return DataLoader(dataset, batch_sizebatch_size, shuffleshuffle)4.3 定义模型# src/model.py import torch.nn as nn from transformers import BertModel class SentimentClassifier(nn.Module): def __init__(self, n_classes, model_namebert-base-chinese): super(SentimentClassifier, self).__init__() self.bert BertModel.from_pretrained(model_name) self.drop nn.Dropout(p0.3) # Dropout层防止过拟合 self.out nn.Linear(self.bert.config.hidden_size, n_classes) def forward(self, input_ids, attention_mask): bert_output self.bert( input_idsinput_ids, attention_maskattention_mask ) # 使用[CLS] token的输出作为句子表示 pooled_output bert_output.pooler_output output self.drop(pooled_output) return self.out(output)4.4 训练脚本# src/train.py import torch import torch.nn as nn from torch.optim import AdamW from transformers import get_linear_schedule_with_warmup from sklearn.metrics import accuracy_score, classification_report import pandas as pd from tqdm import tqdm import warnings warnings.filterwarnings(ignore) from src.model import SentimentClassifier from src.dataset import create_data_loader def train_epoch(model, data_loader, loss_fn, optimizer, device, scheduler, n_examples): model model.train() losses [] correct_predictions 0 for batch in tqdm(data_loader, descTraining): input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) labels batch[labels].to(device) outputs model(input_idsinput_ids, attention_maskattention_mask) _, preds torch.max(outputs, dim1) loss loss_fn(outputs, labels) correct_predictions torch.sum(preds labels) losses.append(loss.item()) loss.backward() nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) # 梯度裁剪 optimizer.step() scheduler.step() optimizer.zero_grad() return correct_predictions.double() / n_examples, sum(losses) / len(losses) def eval_model(model, data_loader, loss_fn, device, n_examples): model model.eval() losses [] correct_predictions 0 all_preds [] all_labels [] with torch.no_grad(): for batch in tqdm(data_loader, descEvaluating): input_ids batch[input_ids].to(device) attention_mask batch[attention_mask].to(device) labels batch[labels].to(device) outputs model(input_idsinput_ids, attention_maskattention_mask) _, preds torch.max(outputs, dim1) loss loss_fn(outputs, labels) correct_predictions torch.sum(preds labels) losses.append(loss.item()) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) accuracy correct_predictions.double() / n_examples report classification_report(all_labels, all_preds, target_names[负面, 正面], output_dictTrue) return accuracy, sum(losses) / len(losses), report def main(): # 配置参数 BATCH_SIZE 16 MAX_LEN 128 EPOCHS 4 MODEL_NAME bert-base-chinese LEARNING_RATE 2e-5 device torch.device(cuda if torch.cuda.is_available() else cpu) print(fUsing device: {device}) # 加载数据 train_df pd.read_csv(data/processed/train.csv) val_df pd.read_csv(data/processed/val.csv) # 初始化Tokenizer和DataLoader from transformers import BertTokenizer tokenizer BertTokenizer.from_pretrained(MODEL_NAME) train_data_loader create_data_loader(train_df, tokenizer, MAX_LEN, BATCH_SIZE, shuffleTrue) val_data_loader create_data_loader(val_df, tokenizer, MAX_LEN, BATCH_SIZE, shuffleFalse) # 初始化模型 model SentimentClassifier(n_classes2, model_nameMODEL_NAME) model model.to(device) # 优化器与学习率调度器 optimizer AdamW(model.parameters(), lrLEARNING_RATE, correct_biasFalse) total_steps len(train_data_loader) * EPOCHS scheduler get_linear_schedule_with_warmup( optimizer, num_warmup_steps0, num_training_stepstotal_steps ) loss_fn nn.CrossEntropyLoss().to(device) # 训练循环 history {train_acc: [], train_loss: [], val_acc: [], val_loss: []} best_accuracy 0 for epoch in range(EPOCHS): print(fEpoch {epoch 1}/{EPOCHS}) print(- * 30) train_acc, train_loss train_epoch( model, train_data_loader, loss_fn, optimizer, device, scheduler, len(train_df) ) print(fTrain loss {train_loss:.4f} accuracy {train_acc:.4f}) val_acc, val_loss, report eval_model( model, val_data_loader, loss_fn, device, len(val_df) ) print(fVal loss {val_loss:.4f} accuracy {val_acc:.4f}) print(fClassification Report:\n{classification_report(report[负面], report[正面])}) history[train_acc].append(train_acc) history[train_loss].append(train_loss) history[val_acc].append(val_acc) history[val_loss].append(val_loss) # 保存最佳模型 if val_acc best_accuracy: torch.save(model.state_dict(), models/best_model_state.bin) best_accuracy val_acc print(fBest model saved with accuracy: {best_accuracy:.4f}) # 在测试集上评估最佳模型 print(\nEvaluating on test set...) test_df pd.read_csv(data/processed/test.csv) test_data_loader create_data_loader(test_df, tokenizer, MAX_LEN, BATCH_SIZE, shuffleFalse) model.load_state_dict(torch.load(models/best_model_state.bin)) test_acc, test_loss, test_report eval_model(model, test_data_loader, loss_fn, device, len(test_df)) print(fTest accuracy: {test_acc:.4f}) print(classification_report(test_report[负面], test_report[正面])) if __name__ __main__: main()4.5 预测脚本# src/predict.py import torch import torch.nn.functional as F from transformers import BertTokenizer from src.model import SentimentClassifier from src.preprocess import TextPreprocessor class SentimentPredictor: def __init__(self, model_pathmodels/best_model_state.bin, model_namebert-base-chinese, max_len128): self.device torch.device(cuda if torch.cuda.is_available() else cpu) self.tokenizer BertTokenizer.from_pretrained(model_name) self.model SentimentClassifier(n_classes2, model_namemodel_name) self.model.load_state_dict(torch.load(model_path, map_locationself.device)) self.model self.model.to(self.device) self.model.eval() self.max_len max_len self.preprocessor TextPreprocessor() # 使用相同的预处理 def predict(self, raw_text): # 1. 预处理 processed_text self.preprocessor.process_pipeline(raw_text) # 2. Tokenize encoding self.tokenizer.encode_plus( processed_text, add_special_tokensTrue, max_lengthself.max_len, paddingmax_length, truncationTrue, return_attention_maskTrue, return_tensorspt, ) # 3. 预测 with torch.no_grad(): input_ids encoding[input_ids].to(self.device) attention_mask encoding[attention_mask].to(self.device) outputs self.model(input_idsinput_ids, attention_maskattention_mask) probabilities F.softmax(outputs, dim1) _, prediction torch.max(outputs, dim1) # 4. 返回结果 sentiment 正面 if prediction 1 else 负面 confidence probabilities[0][prediction].item() return { text: raw_text, processed_text: processed_text, sentiment: sentiment, confidence: confidence, probabilities: { negative: probabilities[0][0].item(), positive: probabilities[0][1].item() } } if __name__ __main__: predictor SentimentPredictor() test_texts [ “圣采儿啊那里不可以哦齁齁”, “这个功能太好用了点赞”, “完全没效果浪费我时间。” ] for text in test_texts: result predictor.predict(text) print(f输入: {result[text]}) print(f情感: {result[sentiment]} (置信度: {result[confidence]:.2%})) print(f负面概率: {result[probabilities][negative]:.4f}, 正面概率: {result[probabilities][positive]:.4f}) print(- * 50)5. 常见问题与排查思路在实际部署和训练过程中你可能会遇到以下问题问题现象可能原因排查思路与解决方案准确率低模型学不会1. 数据量太少或质量差标签噪声大。2. 类别极度不平衡。3. 预处理过于激进丢失了情感词。4. 模型太大数据太少导致过拟合。1.检查数据可视化标签分布人工抽查一些样本的标签是否正确。2.数据增强对文本进行回译、同义词替换、随机删除等需谨慎避免改变情感。3.调整预处理尝试不移除语气词和感叹号或保留原始符号作为特征。4.简化模型先使用逻辑回归TF-IDF作为基线或使用更小的预训练模型如bert-tiny。5.调整超参数降低学习率增加Dropout率使用早停Early Stopping。预测结果全部为一个类别1. 训练数据类别严重不平衡。2. 损失函数或权重初始化有问题。3. 学习率太高模型无法收敛。1.重采样对少数类过采样如SMOTE或对多数类欠采样。2.类别权重在损失函数中为少数类设置更高的权重nn.CrossEntropyLoss(weightclass_weights)。3.检查数据流确保DataLoader正确打乱了数据且标签与文本对应无误。4.监控训练过程观察每个epoch的训练/验证损失和准确率看模型是否在学习。显存不足CUDA out of memory1.batch_size或max_len设置过大。2. 模型参数量太大。1.减小batch_size这是最直接有效的方法如从16降到8或4。2.减小max_len分析文本长度分布大多数中文短文本128或256足矣。3.梯度累积通过多次前向传播累积梯度再一次性更新参数模拟大batch效果。4.混合精度训练使用torch.cuda.amp自动混合精度减少显存占用并加速训练。5.使用更小的模型如bert-small或albert-base。推理速度慢1. 模型太大。2. 未使用GPU或批处理预测。1.模型蒸馏或剪枝将大模型的知识迁移到小模型。2.使用ONNX Runtime或TensorRT将模型转换为优化后的格式进行推理。3.批处理在predict.py中支持一次预测多条文本充分利用GPU并行能力。4.使用更快的Tokenizer如BertTokenizerFast。对特定网络用语如“齁齁”识别差1. 训练数据中未出现或极少出现该词。2. 预训练词表中没有该词被拆分为子词。1.扩充训练数据主动收集包含此类用语的样本进行标注。2.字符级模型如前所述在特征工程阶段加入字符N-gram让模型从字符组合中学习。3.继续预训练Continue Pre-training在领域文本如小说评论、社交文本上对预训练模型进行额外的无监督预训练使其适应领域语言风格。6. 最佳实践与工程建议数据是王道质量高于数量1000条标注准确的数据远胜于10000条噪声数据。在项目初期应投入精力进行高质量的数据标注和清洗。构建领域词典针对你的业务场景如游戏、动漫、电商维护一个情感词词典正面词、负面词、程度副词、否定词可用于规则校验或特征补充。持续迭代数据将线上预测错误的案例收集起来进行标注并加入训练集不断优化模型。预处理策略因场景而异保留情感信号对于情感分析感叹号、问号、波浪线、重复字符、表情符号都是重要的强度或情感指示器不要轻易丢弃。可以考虑将其转化为数值特征如感叹号个数。统一归一化将全角符号转为半角统一繁体简体处理数字和英文大小写保证一致性。模型选择与评估从简单开始不要一上来就用巨型预训练模型。先用TF-IDF逻辑回归建立强基线了解数据的可分性。交叉验证在小数据集上使用交叉验证来更稳健地评估模型性能和选择超参数。关注混淆矩阵准确率可能具有欺骗性。通过混淆矩阵分析模型在哪些类别上容易出错针对性改进。工程化部署模型服务化使用Flask、FastAPI等框架将模型封装成RESTful API服务方便其他系统调用。缓存与批处理对于高频重复查询可以引入缓存。对于批量预测任务务必使用批处理以提升效率。监控与日志记录模型的预测结果、响应时间和输入分布监控线上效果衰减Concept Drift。伦理与安全偏见审查检查模型是否对不同群体、性别、文化背景的用户存在偏见。例如某些语气词可能在不同语境下有不同含义。隐私保护确保训练和预测的文本数据符合隐私保护规定避免存储敏感个人信息。应用边界明确告知用户系统的能力和局限性情感分析是辅助工具不应作为唯一决策依据。处理像“圣采儿啊那里不可以哦齁齁”这类充满网络文化和情感张力的文本是NLP落地实践中的一个有趣挑战。其核心在于理解情感往往不依赖于严谨的语法而是通过词汇、符号、语气和语境的复杂组合来传递。通过本文介绍的数据预处理、字符级特征、以及基于BERT的上下文建模你可以构建一个相对鲁棒的系统。记住没有一劳永逸的模型持续的数据迭代、场景化的预处理和细致的错误分析才是提升系统效果的关键。建议从本文提供的代码框架开始在你的特定数据上进行实验和调优逐步探索出最适合你业务场景的解决方案。