1. 这篇文章真正要解决的问题
当你第一次听说“用BERT做情感分析”时,是不是觉得这又是一个老生常谈的话题?网上教程铺天盖地,从加载预训练模型到跑通一个Demo,似乎人人都能讲。但当你真正想把手头的业务数据——比如电商评论、客服对话、社交媒体文本——训练成一个能稳定上线的分类模型时,问题就来了:为什么照着教程跑,在自己的数据集上效果就是不行?为什么训练过程慢如蜗牛,还动不动就内存溢出(OOM)?微调后的模型,部署时怎么又大又笨重?
这些问题,恰恰是“知道BERT”和“用好BERT”之间的鸿沟。很多教程止步于演示流程,却忽略了实战中最关键的工程细节和调优逻辑。本文将聚焦于基于Hugging Face Transformers库的BERT情感分析微调实战,但我们的目标远不止“跑通代码”。我们将深入解决以下几个核心痛点:
- 从“玩具数据集”到“真实业务数据”的鸿沟:如何处理格式混乱、标签不均衡、含有噪声的真实文本?
- 训练效率与资源瓶颈:如何根据你的显卡(GPU)条件,选择最合适的微调策略(如全参数微调、Layer-wise Learning Rate衰减、LoRA等)来平衡效果与速度?
- 避免过拟合与提升泛化能力:在数据量有限的情况下,有哪些切实可行的数据增强和正则化技巧?
- 从训练到部署的最后一公里:如何将训练好的模型进行优化、序列化,并集成到实际的推理服务中?
本文假设你已有基本的Python和PyTorch知识,并希望将BERT等预训练模型真正应用于解决实际问题。我们将提供一个从数据准备、模型训练、评估到模型导出的完整、可复现的流水线,并重点解释每一步背后的“为什么”。
2. 基础概念与核心原理
在开始动手之前,我们需要统一几个关键概念,这能帮助你在后续遇到问题时,快速定位到正确的解决层面。
2.1 什么是情感分析(Sentiment Analysis)?
情感分析是自然语言处理(NLP)中一项经典的文本分类任务,旨在自动识别和提取文本中的主观情感倾向,如正面、负面或中性。在业务场景中,它可能演变为更细粒度的分类,如“愤怒、高兴、失望、中立”等,或者对特定方面(Aspect)的情感进行判断。
传统方法 vs. 深度学习方法:
- 传统方法:依赖于手工特征(如词袋模型、TF-IDF)和机器学习分类器(如SVM、朴素贝叶斯)。它们难以捕捉词语间的远距离依赖关系和复杂的语义信息。
- 深度学习方法:使用神经网络(如CNN、RNN/LSTM)自动学习特征表示。而Transformer架构(特别是BERT)的出现,通过自注意力机制(Self-Attention)极大地提升了对上下文语义的理解能力,成为当前的主流方案。
2.2 BERT 的核心思想:预训练与微调
BERT(Bidirectional Encoder Representations from Transformers)的本质是一个基于Transformer Encoder架构的预训练语言模型。
- 预训练(Pre-training):在海量无标注文本(如维基百科、图书语料)上,通过两个自监督任务进行训练:
- 掩码语言模型(MLM):随机遮盖输入句子中的一些词,让模型预测被遮盖的词。这迫使模型理解双向上下文。
- 下一句预测(NSP):判断两个句子是否是连续的。这帮助模型理解句子间关系。 经过预训练,BERT的模型参数已经蕴含了丰富的语言知识。
- 微调(Fine-tuning):这是本文的重点。我们将一个在通用语料上预训练好的BERT模型,在一个特定的、有标注的任务数据(如情感分析数据集)上进行“二次训练”。在这个过程中,我们会在BERT模型顶部添加一个简单的分类层(通常是一个全连接层),然后使用任务数据更新所有模型参数(或部分参数)。微调让模型快速适应特定任务,通常只需要相对少量的标注数据就能取得很好效果。
2.3 Hugging Face Transformers:生态与价值
Hugging Face Transformers 库是一个开源库,它提供了数千种预训练模型(包括BERT、GPT、T5等)的统一、易用的API。它的核心价值在于:
- 标准化接口:无论底层是PyTorch(
torch)还是TensorFlow(tf),你都可以用几乎相同的代码加载和使用模型。 - 模型中心(Model Hub):一个庞大的社区仓库,可以轻松下载、分享模型。
- 丰富的工具集:提供了数据预处理(
Tokenizer)、训练(Trainer)、评估、流水线(Pipeline)等全套工具,极大降低了开发门槛。
理解了这个“预训练-微调”的范式以及Hugging Face的工具定位,我们就可以进入实战环节了。
3. 环境准备与前置条件
工欲善其事,必先利其器。确保你的开发环境满足以下要求。
3.1 硬件与软件环境
- 操作系统:Linux (Ubuntu/CentOS), macOS 或 Windows (建议使用WSL2以获得更好的体验)。本文命令以Linux/macOS为例。
- Python:版本 3.8 或以上。推荐使用
conda或venv创建独立的虚拟环境。 - 深度学习框架:PyTorch。这是Hugging Face生态的首选搭档。请根据你的CUDA版本(如果有NVIDIA GPU)前往 PyTorch官网 获取安装命令。例如,对于CUDA 11.8:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 - GPU(可选但强烈推荐):BERT模型参数庞大,在CPU上训练会极其缓慢。确保你的NVIDIA显卡驱动、CUDA Toolkit和cuDNN已正确安装。可以使用
nvidia-smi命令验证。
3.2 核心Python库安装
在你的虚拟环境中,安装以下必要的库:
# 安装 Hugging Face Transformers 库及其依赖 pip install transformers datasets # 安装评估指标库,我们使用 accuracy 和 f1 pip install evaluate # 安装加速库,用于混合精度训练和分布式训练(大幅提升训练速度) pip install accelerate # 安装TensorBoard用于可视化训练过程(可选但推荐) pip install tensorboard # 安装Jupyter Notebook或Lab,用于交互式开发(可选) pip install jupyter验证安装:在Python中运行以下命令,确保没有报错。
import torch import transformers print(torch.__version__) print(transformers.__version__) print(f"GPU available: {torch.cuda.is_available()}")4. 核心流程拆解
一次完整的BERT微调训练,可以拆解为以下六个核心步骤。我们将详细展开每一步。
flowchart TD A[开始: 数据准备与探索] --> B[步骤1: 加载并预处理数据] B --> C[步骤2: 加载预训练模型与分词器] C --> D[步骤3: 定义训练参数与训练器] D --> E[步骤4: 启动训练与监控] E --> F[步骤5: 模型评估与预测] F --> G[步骤6: 模型保存与导出] G --> H[结束: 获得可部署模型]接下来,我们按照这个流程,一步步实现。
5. 完整示例与代码实现
我们将使用一个经典的情感分析数据集——IMDb电影评论数据集(包含5万条影评,标记为正面/负面)作为示例。你可以轻松替换为自己的数据集。
5.1 步骤一:数据准备与探索
首先,我们加载数据并了解其结构。
# 文件:data_exploration.py from datasets import load_dataset import pandas as pd # 1. 从Hugging Face Datasets库加载IMDb数据集 # 第一次运行会自动下载 print("正在加载IMDb数据集...") dataset = load_dataset('imdb') print(dataset) # 2. 查看数据集结构 print("\n数据集结构:") print(f"训练集样本数: {len(dataset['train'])}") print(f"测试集样本数: {len(dataset['test'])}") # 3. 查看前几条训练样本 train_df = pd.DataFrame(dataset['train'][:5]) print("\n训练集前5条样本:") print(train_df[['text', 'label']].to_string(index=False)) # 4. 查看标签分布 print("\n训练集标签分布:") print(dataset['train'].features['label']) # IMDb数据集中,1代表正面,0代表负面关键点解释:
load_dataset是Hugging Face Datasets库的核心函数,支持加载本地文件(CSV, JSON, TXT等)或社区数据集。- 了解数据规模和标签含义是第一步。对于你自己的数据,可能需要先进行清洗(去重、去噪、纠正标签)。
5.2 步骤二:加载预训练模型与分词器
我们选择bert-base-uncased模型,它是一个较小的、不区分大小写的BERT版本,适合入门和快速实验。
# 文件:load_model_tokenizer.py from transformers import AutoTokenizer, AutoModelForSequenceClassification # 1. 指定预训练模型名称 model_name = "bert-base-uncased" # 2. 加载分词器 (Tokenizer) # 分词器负责将文本转换为模型能理解的数字ID(Token IDs) print(f"正在加载分词器: {model_name}") tokenizer = AutoTokenizer.from_pretrained(model_name) # 3. 加载预训练模型,并指定分类任务和标签数量 # `num_labels=2` 表示这是一个二分类任务 print(f"正在加载模型: {model_name}") model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2) # 4. 测试分词器 sample_text = "This movie is absolutely fantastic and I love it!" tokens = tokenizer(sample_text, truncation=True, padding=True, return_tensors="pt") print(f"\n样例文本: {sample_text}") print(f"分词后的输入IDs形状: {tokens['input_ids'].shape}") print(f"Attention Mask形状: {tokens['attention_mask'].shape}") # `input_ids` 是词的ID, `attention_mask` 指示哪些是真实词(1)哪些是填充符(0)关键点解释:
AutoTokenizer和AutoModelForSequenceClassification是“自动”类,能根据模型名称自动选择正确的分词器和模型结构。num_labels参数至关重要,它决定了模型顶部分类头的输出维度。对于多分类任务(如5种情感),这里就设为5。
5.3 步骤三:数据预处理(Tokenization)
我们需要将原始文本数据集转换为模型输入所需的格式。
# 文件:data_preprocessing.py def preprocess_function(examples): """ 对一批样本进行分词处理。 `examples` 是一个字典,包含 `text` 和 `label` 键。 """ # tokenizer会自动进行分词、添加特殊符号([CLS], [SEP])、截断和填充 # `truncation=True` 处理长文本 # `padding=True` 保证批内样本长度一致 # `max_length=512` BERT的最大输入长度是512 return tokenizer(examples['text'], truncation=True, padding=True, max_length=512) # 应用预处理函数到整个数据集 print("正在对数据集进行分词处理...") tokenized_datasets = dataset.map(preprocess_function, batched=True) # 查看处理后的样本 print(f"\n处理后的特征示例:") sample = tokenized_datasets['train'][0] print(f"Input IDs (前10个): {sample['input_ids'][:10]}") print(f"Attention Mask (前10个): {sample['attention_mask'][:10]}") print(f"Label: {sample['label']}") # 重命名列以符合Trainer的默认期望(可选,但更清晰) tokenized_datasets = tokenized_datasets.rename_column("label", "labels") # 设置数据格式为PyTorch张量 tokenized_datasets.set_format('torch', columns=['input_ids', 'attention_mask', 'labels'])关键点解释:
dataset.map()函数高效地对整个数据集应用预处理函数。batched=True能显著提升处理速度。max_length=512是BERT的硬性限制,超过长度的文本会被截断。对于长文档,需要考虑其他模型(如Longformer)或分段策略。
5.4 步骤四:定义训练参数与训练器
这是微调的核心配置环节。TrainerAPI 封装了训练循环、评估、保存等复杂逻辑。
# 文件:training_setup.py from transformers import TrainingArguments, Trainer import numpy as np import evaluate # 1. 加载评估指标 metric = evaluate.load("accuracy") # 我们主要看准确率 def compute_metrics(eval_pred): """ 计算评估指标的函数。 `eval_pred` 是一个包含 `predictions` 和 `label_ids` 的元组。 """ logits, labels = eval_pred # logits是模型输出的原始分数,通过argmax得到预测类别 predictions = np.argmax(logits, axis=-1) # 计算准确率 return metric.compute(predictions=predictions, references=labels) # 2. 定义训练参数 training_args = TrainingArguments( output_dir="./bert-sentiment-imdb", # 模型和日志的输出目录 evaluation_strategy="epoch", # 每个epoch结束后在验证集上评估 save_strategy="epoch", # 每个epoch结束后保存模型 learning_rate=2e-5, # 学习率,微调时通常设置较小(2e-5到5e-5) per_device_train_batch_size=8, # 每个GPU/CPU上的训练批次大小 per_device_eval_batch_size=8, # 每个GPU/CPU上的评估批次大小 num_train_epochs=3, # 训练轮数,对于微调,3-5轮通常足够 weight_decay=0.01, # 权重衰减,防止过拟合 logging_dir='./logs', # TensorBoard日志目录 logging_steps=50, # 每50步记录一次日志 load_best_model_at_end=True, # 训练结束后加载验证集上最好的模型 metric_for_best_model="accuracy", # 根据哪个指标选择最佳模型 report_to="tensorboard", # 使用TensorBoard可视化 # fp16=True, # 启用混合精度训练(如果GPU支持,可以显著提速并节省显存) ) # 3. 划分训练集和验证集 # 从原始训练集中分出一部分作为验证集 train_test_split = tokenized_datasets['train'].train_test_split(test_size=0.1) train_dataset = train_test_split['train'] eval_dataset = train_test_split['test'] test_dataset = tokenized_datasets['test'] # 保留原始测试集用于最终评估 # 4. 初始化训练器 trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset, tokenizer=tokenizer, compute_metrics=compute_metrics, )关键参数详解:
learning_rate (2e-5): BERT微调的黄金学习率范围。太大容易破坏预训练知识,太小收敛慢。per_device_train_batch_size (8): 批次大小受GPU显存限制。如果出现OOM错误,首先降低此值,或使用梯度累积(gradient_accumulation_steps)。num_train_epochs (3): 对于IMDb这种中等规模数据集,3个epoch通常能很好收敛。可通过观察验证集损失曲线决定是否早停。fp16=True:强烈建议在支持Tensor Core的NVIDIA GPU上开启。它能将训练速度提升2-3倍,并减少约一半的显存占用。
5.5 步骤五:启动训练与监控
现在,一切就绪,开始训练!
# 文件:train.py print("开始训练...") train_result = trainer.train() # 训练完成后保存最终模型 trainer.save_model() # 保存到 `output_dir` (./bert-sentiment-imdb) tokenizer.save_pretrained(training_args.output_dir) print(f"\n训练完成!模型已保存至: {training_args.output_dir}") # 在TensorBoard中查看训练曲线 # 在终端运行: tensorboard --logdir ./logs # 然后在浏览器中打开 http://localhost:6006训练过程监控:
- 控制台会打印每个epoch的训练损失、评估损失和评估指标(如准确率)。
- 运行
tensorboard --logdir ./logs可以在浏览器中看到更直观的损失曲线和指标变化图,帮助你判断模型是否过拟合或欠拟合。
5.6 步骤六:模型评估与预测
训练完成后,我们需要在保留的测试集上评估模型的最终性能,并学习如何进行单条预测。
# 文件:evaluate_and_predict.py # 1. 在测试集上进行评估 print("在测试集上进行最终评估...") eval_results = trainer.evaluate(test_dataset) print(f"测试集评估结果: {eval_results}") # 2. 加载训练好的最佳模型进行预测 from transformers import pipeline # 创建情感分析管道 sentiment_pipeline = pipeline( "text-classification", model=training_args.output_dir, # 加载我们刚训练的模型 tokenizer=training_args.output_dir, device=0 if torch.cuda.is_available() else -1 # 使用GPU如果可用 ) # 3. 对新文本进行预测 new_texts = [ "This film is a masterpiece, the acting is superb and the story is captivating.", "A complete waste of time. The plot was predictable and the characters were flat.", "It was okay, nothing special but not terrible either." ] print("\n对新文本的预测结果:") for text in new_texts: result = sentiment_pipeline(text)[0] label = "正面" if result['label'] == 'LABEL_1' else "负面" # 注意标签映射 print(f"文本: {text[:60]}...") print(f" 情感: {label}, 置信度: {result['score']:.4f}") print("-" * 50)关键点解释:
trainer.evaluate()使用训练时定义的compute_metrics函数进行评估。pipeline是Hugging Face提供的高级API,封装了预处理、模型推理和后处理,非常适合快速部署和测试。- 注意标签映射:
AutoModelForSequenceClassification输出的标签通常是LABEL_0,LABEL_1,需要根据你的数据集映射回实际含义。
6. 运行结果与效果验证
成功运行上述代码后,你应该能看到类似以下的输出:
训练过程输出片段:
***** Running training ***** Num examples = 22500 Num Epochs = 3 Instantaneous batch size per device = 8 Total train batch size = 8 Gradient Accumulation steps = 1 Total optimization steps = 8438 Number of trainable parameters = 109,483,778 [50/8438] Loss: 0.5123 [100/8438] Loss: 0.3567 ... Epoch 1/3: 100%|██████████| 2813/2813 [05:12<00:00, 9.00it/s] Train loss: 0.2155 Validation Accuracy: 0.9340 Best model saved to ./bert-sentiment-imdb/checkpoint-2813最终测试集评估结果:
测试集评估结果: {'eval_loss': 0.185, 'eval_accuracy': 0.9412, 'eval_runtime': 45.6, ...}一个在IMDb测试集上准确率达到94%+的情感分析模型就训练完成了。这显著优于传统的机器学习方法。
单条预测结果:
文本: This film is a masterpiece, the acting is superb and the story is... 情感: 正面, 置信度: 0.9987 -------------------------------------------------- 文本: A complete waste of time. The plot was predictable and the charac... 情感: 负面, 置信度: 0.9934 -------------------------------------------------- 文本: It was okay, nothing special but not terrible either.... 情感: 负面, 置信度: 0.7210 (可能被分类为负面,但置信度较低,符合“中立偏负面”的直觉)如何验证成功:
- 训练正常:训练损失应稳步下降,验证集准确率应逐步上升并趋于稳定。
- 评估合理:在测试集上的准确率应与验证集最终结果接近,差距不大(否则可能过拟合)。
- 预测符合直觉:对新文本的预测结果应与人类判断基本一致。
7. 常见问题与排查思路
在实际操作中,你几乎一定会遇到以下问题。这里提供快速排查指南。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| CUDA out of memory (OOM) | 1. 批次大小 (batch_size) 太大。2. 序列长度 ( max_length) 太长。3. 模型太大(如使用了 bert-large)。 | 1. 运行nvidia-smi观察显存占用。2. 尝试在CPU上运行小批次看是否报错。 | 1.首要降低per_device_train_batch_size(如从16降到8)。2. 缩短 max_length(如从512降到128)。3. 使用更小的模型 (如 distilbert-base-uncased)。4. 启用梯度累积 ( gradient_accumulation_steps=2) 和混合精度 (fp16=True)。 |
| 训练速度极慢 | 1. 在CPU上训练。 2. 没有启用混合精度训练。 3. 数据加载是瓶颈。 | 1. 检查torch.cuda.is_available()。2. 检查 TrainingArguments中fp16设置。 | 1. 确保使用GPU。 2.开启 fp16=True(需GPU支持)。3. 使用 datasets库的缓存和内存映射特性,确保num_workers参数合理(在DataLoader中)。 |
| 验证集准确率不升反降(过拟合) | 1. 训练数据太少。 2. 学习率太大。 3. 训练轮数太多。 | 1. 观察TensorBoard,看验证损失是否在某个epoch后开始上升。 2. 检查训练集和验证集准确率差距。 | 1. 增加数据增强(如回译、EDA)。 2.降低学习率(如从2e-5降到1e-5)。 3.启用早停 (Early Stopping)或减少 num_train_epochs。4. 增加正则化,如提高 weight_decay。 |
| 预测结果全部一样或随机 | 1. 学习率过大导致训练不稳定。 2. 数据预处理出错,标签与模型输出不对应。 3. 模型未正确加载。 | 1. 检查训练日志,初始损失是否正常(二分类交叉熵初始值约 -ln(0.5)=0.69)。 2. 检查 tokenized_datasets中的labels字段是否正确。3. 在评估集上跑一下看指标。 | 1. 使用更小的学习率重新训练。 2. 仔细检查数据加载和预处理代码,确保 num_labels设置正确。3. 加载模型后,先用几行数据做一次前向传播,检查输出是否随机。 |
transformers无法下载模型 | 1. 网络连接问题。 2. Hugging Face镜像问题。 | 1. 尝试ping huggingface.co。2. 查看错误信息。 | 1. 使用国内镜像源,设置环境变量:export HF_ENDPOINT=https://hf-mirror.com。2. 手动从镜像站下载模型文件,使用 from_pretrained(‘/本地路径’)加载。 |
8. 最佳实践与工程建议
要让你的情感分析模型从“实验玩具”变成“生产利器”,请遵循以下建议:
8.1 数据层面
- 数据质量高于数据数量:仔细清洗数据,去除无关符号、纠正拼写错误、处理缺失值。一个干净的5000条数据集可能比混乱的50000条更有效。
- 处理类别不平衡:如果正面/负面评论数量悬殊,考虑使用过采样(如SMOTE)、欠采样或为
Trainer的compute_loss函数赋予类别权重。 - 数据增强:对于文本数据,可以使用同义词替换、随机插入、随机交换、随机删除(EDA技术)或回译来有限地增加数据多样性,这对防止过拟合很有帮助。
8.2 模型与训练层面
- 模型选型:不必总用最大的BERT。
- 追求速度/轻量化:选用
DistilBERT、TinyBERT或ALBERT。 - 处理长文本:选用
Longformer或BigBird。 - 中文任务:选用
bert-base-chinese、RoBERTa-wwm-ext或ERNIE。
- 追求速度/轻量化:选用
- 分层学习率:BERT底层编码了通用语法语义知识,高层更偏向任务特定知识。可以设置较小的底层学习率和较大的顶层学习率。Hugging Face Trainer 可以通过
optimizer参数自定义优化器来实现。 - 使用验证集进行早停:监控验证集损失,当其连续几个epoch不再下降时停止训练,避免过拟合。
Trainer可以通过EarlyStoppingCallback实现。 - 超参数调优:使用
optuna或ray tune等库对学习率、批次大小、权重衰减等关键超参数进行系统搜索。
8.3 部署与优化层面
- 模型序列化:使用
model.save_pretrained()和tokenizer.save_pretrained()保存的模型,可以通过pipeline或from_pretrained轻松加载。 - 模型优化:部署前考虑对模型进行优化。
- 动态量化:使用PyTorch的
torch.quantization.quantize_dynamic对模型线性层进行量化,能在几乎不损失精度的情况下减小模型体积、提升CPU推理速度。 - ONNX导出:将模型导出为ONNX格式,可以利用ONNX Runtime进行高性能推理,并兼容多种后端。
- 使用更快的推理库:考虑使用
FastTransformer或DeepSpeed进行推理优化。
- 动态量化:使用PyTorch的
- 构建API服务:使用
FastAPI或Flask将模型封装为RESTful API,供其他服务调用。
# 示例:使用FastAPI部署模型 from fastapi import FastAPI from pydantic import BaseModel from transformers import pipeline app = FastAPI() sentiment_pipeline = pipeline("text-classification", model="./bert-sentiment-imdb") class TextRequest(BaseModel): text: str @app.post("/predict") def predict(request: TextRequest): result = sentiment_pipeline(request.text)[0] return {"sentiment": result['label'], "confidence": result['score']}9. 总结与后续学习方向
通过本文,我们完成了一次完整的、工业级的BERT情感分析微调实战。我们不仅跑通了代码,更深入探讨了数据准备、模型选择、训练调优、问题排查和部署上线的全链路细节。关键在于理解,微调不是一个黑盒,而是一个需要根据数据、任务和资源不断调整的工程过程。
下一步,你可以沿着这些方向深入:
- 尝试不同的预训练模型:将
bert-base-uncased换成roberta-base、distilbert-base-uncased或albert-base-v2,比较它们在速度和效果上的差异。 - 探索参数高效微调:当数据量很少或模型极大时,研究LoRA (Low-Rank Adaptation)或Prefix-Tuning等方法,只微调极少量参数,达到接近全参数微调的效果。
- 进行多分类或细粒度情感分析:将二分类扩展到多分类(如积极、消极、中性),甚至方面级情感分析(Aspect-Based Sentiment Analysis)。
- 部署到生产环境:学习使用Docker容器化你的模型服务,并结合Redis缓存、Nginx负载均衡,构建一个高可用的推理服务集群。
- 模型监控与更新:生产环境中,需要监控模型的预测分布变化(概念漂移),并建立数据回流管道,定期用新数据重新训练模型。
情感分析是NLP的入门任务,但其中蕴含的“数据-模型-训练-部署”方法论是通用的。掌握好这套流程,你就具备了将任何NLP预训练模型落地到实际业务场景中的核心能力。建议你将本文代码作为模板,尝试在自己的数据集上复现和优化,这才是提升的真正开始。