# Finetune
在本例中，我们将展示如何使用您的数据对baai-general-embedding进行微调。

## 环境配置
参考[环境配置](../../README.md#环境配置)

## 数据集
```
├── finetune
│    ├── toy_evaluation_data
│    └── toy_finetune_data.jsonl
```

训练数据是一个json文件，其中每一行都是这样的字典：
```
{"query": str, "pos": List[str], "neg":List[str]}
```

`query` 是查询语句, `pos` 是正文本list, `neg` 是负文本list。
如果针对查询语句，你没有负文本，你可以随机从整个语料库中选取样本作为负样本。如[toy_finetune_data.jsonl](./toy_finetune_data.jsonl)

## 微调
### Hard Negatives
Hard negatives是一种广泛使用的提高句子嵌入质量的方法，使用方法如下：
```bash
python -m FlagEmbedding.baai_general_embedding.finetune.hn_mine \
    --model_name_or_path BAAI/bge-base-en-v1.5 \
    --input_file toy_finetune_data.jsonl \
    --output_file toy_finetune_data_minedHN.jsonl \
    --range_for_sampling 2-200 \
    --negative_number 15 \
    --use_gpu_for_searching
```

主要参数解释如下：
- `input_file`: 用于微调的json数据。该脚本将为每个query检索前k个文档，并从前k个文档中随机采样负样本(不包括肯定文档)。
- `output_file`: 保存微调所需的带有mined hard negatives的JSON数据保存地址
- `Negative_number`: 采样的负样本个数
- `Range_for_sampling`: 在哪里进行负样本采样。例如,m `2-100`表示从top2-top200个文档中抽样`negative_number`个负样本。**您可以设置更大的值来降低负样本的难度(例如，将其设置为`60-300`来从top60-300篇文章中取样本)**。
- `candidate_pool`: 要检索的池。默认值为None，该脚本将从`input_file`中的所有`neg`的组合中检索。该文件的格式与[预训练数据](./examples/pretrain#2-data-format)相同。如果输入一个candidate_pool，该脚本将从该文件中检索负样本。
- `use_gpu_for_searching`: 是否使用faiss-gpu检索负样本。

### 微调方法
```bash
torchrun --nproc_per_node {number of gpus} \
    -m FlagEmbedding.baai_general_embedding.finetune.run \
    --output_dir {path to save model} \
    --model_name_or_path BAAI/bge-large-zh-v1.5 \
    --train_data ./toy_finetune_data.jsonl \
    --learning_rate 1e-5 \
    --fp16 \
    --num_train_epochs 5 \
    --per_device_train_batch_size {large batch size; set 1 for toy data} \
    --dataloader_drop_last True \
    --normlized True \
    --temperature 0.02 \
    --query_max_len 64 \
    --passage_max_len 256 \
    --train_group_size 2 \
    --negatives_cross_device \
    --logging_steps 10 \
    --save_steps 1000 \
    --query_instruction_for_retrieval ""
```

**关键参数解释**:
- `per_device_train_batch_size`: 训练的batch size。在大量的案例中，batch size越大，模型效果越好。您还可以通过增加`--fp16`, `--deepspeed ./df_config.json` (df_config.json 请参考 [ds_config.json](./ds_config.json)), `--gradient_checkpointing`等参数来扩大batch size大小。
- `train_group_size`: 训练中查询语句的的正负样本数。总是有一个正样本，所以这个参数将控制负样本的数量(#negatives=train_group_size-1)。注意，负样本设置的数量大小不可以超过数据`"neg":List[str]`的大小。除了这组的负样本，批量的负样本也会用于微调中。
- `negatives_cross_device`: 在所有gpu上共享负样本。这个参数将会扩大负样本的数量。
- `learning_rate`: 学习率。为您的模型选择一个合适的大小如：1e-5/2e-5/3e-5 分别对应模型的 large/base/small 尺寸。
- `temperature`: 影响相似度得分分布的参数。 **建议大小: 0.01-0.1.**
- `query_max_len`: 查询语句的最长长度。请根据您的数据中查询语句的平均长度来设置。
- `passage_max_len`:通道的最长长度。请根据您的数据中通道的平均长度来设置。
- `query_instruction_for_retrieval`: 每个查询语句的指令，你也可以设置成 `""`。
- `use_inbatch_neg`: 使用同一批中的通道作为否定。默认值是True。
- `save_steps`: 设定多少步保存模型。

更多的参数解释可以参考[transformers.TrainingArguments](https://huggingface.co/docs/transformers/main_classes/trainer#transformers.TrainingArguments)

### 通过[LM-Cocktail](../../LM_Cocktail)完成模型融合[optional]
更多信息可参考[LM-Cocktail](https://github.com/FlagOpen/FlagEmbedding/tree/master/LM_Cocktail).

对基本模型进行微调可以提高其在目标任务上的性能，但超出目标域也可能导致模型综合性能的严重退化(例如，c-mteb任务的性能较低)。
通过合并微调模型和基本模型，LM-Cocktail可以显著提高下游任务的效果，同时在其他不相关的任务中保持性能。

```python
from LM_Cocktail import mix_models, mix_models_with_data

# Mix fine-tuned model and base model; then save it to output_path: ./mixed_model_1
model = mix_models(
    model_names_or_paths=["BAAI/bge-large-en-v1.5", "your_fine-tuned_model"],
    model_type='encoder',
    weights=[0.5, 0.5],  # you can change the weights to get a better trade-off.
    output_path='./mixed_model_1')
```

如果你有一个新任务，并且当前没有任何数据或者资源可以用于微调，你可以试试使用`LM-Cocktail`将已有的模型进行合并（从开源社区或者其他任务上自有微调的模型）来完成一个特殊任务的模型。
使用这个方法，你只需要构造一些示例数据，并不需要去微调一个基础模型。比如，你可以合并来自[huggingface](https://huggingface.co/Shitao)上的模型，使用针对你的任务的示例数据。使用代码如下：

```python
from LM_Cocktail.LM_Cocktail import mix_models, mix_models_with_data

example_data = [
    {"query": "How does one become an actor in the Telugu Film Industry?", "pos": [" How do I become an actor in Telugu film industry?"], "neg": [" What is the story of Moses and Ramesses?", " Does caste system affect economic growth of India?"]},
    {"query": "Why do some computer programmers develop amazing software or new concepts, while some are stuck with basic programming work?", "pos": [" Why do some computer programmers develops amazing softwares or new concepts, while some are stuck with basics programming works?"], "neg": [" When visiting a friend, do you ever think about what would happen if you did something wildly inappropriate like punch them or destroy their furniture?", " What is the difference between a compliment and flirting?"]}
]

model = mix_models_with_data(
    model_names_or_paths=["BAAI/bge-base-en-v1.5", "Shitao/bge-hotpotqa", "Shitao/bge-quora"],
    model_type='encoder',
    example_ata=example_data,
    temperature=5.0,
    max_input_length=512,
    neg_number=2)
```

**尽管[这里](https://huggingface.co/Shitao)只有9个`bge-*`模型, 当您的任务与所有9个微调任务不同时，效果可能不令人满意，你可以在基础模型智商微调更多的任务，然后将他们合并使其在你的任务上达到更好的表现。**

### 加载自己的模型
微调BGE模型之后，你可以参考[here](https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/baai_general_embedding#usage)，使用同样的方法轻松的加载它。

如果在微调过程中，你将超参数`--query_instruction_for_retrieval` 设置成了不一样的值，请将参数`query_instruction_for_retrieval`替换成你设置的值。

### 验证
项目提供了[简单脚本](https://github.com/FlagOpen/FlagEmbedding/tree/master/FlagEmbedding/baai_general_embedding/finetune/eval_msmarco.py)去验证模型的效果
脚本的工作原理概述如下：
1. 通过[DataParallel](https://pytorch.org/docs/stable/generated/torch.nn.DataParallel.html)在所有可用显卡上加载模型；
2. 对语料库进行编码，并将嵌入数据卸载到`faiss` 平面索引中。`faiss`会默认将索引加载到所有可用的 GPU 上。
3. 对查询语句进行编码，为每个查询语句搜索最接近的 `100` 条邻居。
4. 计算召回和MRR指标。

#### MSMARCO 数据集
MSMARCO是默认的验证数据集，它是一个使用很广的检索基准。

你可以通过[msmarco corpus](https://huggingface.co/datasets/namespace-Pt/msmarco-corpus) 和 [evaluation queries](https://huggingface.co/datasets/namespace-Pt/msmarco) 检查数据格式。

执行脚本：
```
python -m FlagEmbedding.baai_general_embedding.finetune.eval_msmarco \
    --encoder BAAI/bge-base-en-v1.5 \
    --fp16 \
    --add_instruction \
    --k 100
```

**一些主要参数解释**
- `encoder`: 特殊的encoder模型, 可以是huggingface或者本地模型。
- `fp16`: 使用半精进行推理。
- `add_instruction`: 添加检索指令，如`Represent this sentence for searching relevant passages: `.
- `k`: 指定为每个查询检索多少个最接近的答案。

结果示例：
```python
{
    'MRR@1': 0.2330945558739255,
    'MRR@10': 0.35786976395142633,
    'MRR@100': 0.3692618036917553,
    'Recall@1': 0.22606255969436478,
    'Recall@10': 0.6412965616045848,
    'Recall@100': 0.9012774594078318
}
```

#### 自己的数据集
需要准备两个`jsonl`格式文件

- 一个是包含你想检索的文档信息的corpus_data, 如示例[toy_corpus.json](./toy_evaluation_data/toy_corpus.json)
```
{"content": "A is ..."}
{"content": "B is ..."}
{"content": "C is ..."}
{"content": "Panda is ..."}
{"content": "... is A"}
```
- 另一个是包含检索和ground truth的query_data,  如示例[toy_corpus.json](./toy_evaluation_data/toy_query.json)
```
{"query": "What is A?", "positive": ["A is ...", "... is A"]}
{"query": "What is B?", "positive": ["B is ..."]}
{"query": "What is C?", "positive": ["C is ..."]}
```

修改脚本中的两个数据地址，执行：
```bash
python -m FlagEmbedding.baai_general_embedding.finetune.eval_msmarco \
    --encoder BAAI/bge-base-en-v1.5 \
    --fp16 \
    --add_instruction \
    --k 100 \
    --corpus_data ./toy_evaluation_data/toy_corpus.json \
    --query_data ./toy_evaluation_data/toy_query.json
```
