大模型应用测试提供最需要的就是query改写,也有叫问题改写,query扩写等等,其实都是为了在有限的测试数据之下快速扩充测试数据,让测试数据更加丰富。 queryrewrite项目(https://github.com/crisschan/queryrewrite)是就是一个提供如上功能的Python库,它提供了多种方法来rewrite和验证查询,以提高搜索和检索系统的性能。该项目支持使用大型语言模型(LLM)、词汇表替换和同义词替换等方法进行查询重写,并提供多种验证方法来确保重写后的查询保持原始语义。queryrewrite特别优化了对中文文本的处理,包括分词和相似度计算。
安装
# 克隆仓库 git clone https://github.com/yourusername/queryrewrite.git cd queryrewrite # 安装依赖 pip install -e .
或则
pip install queryrewrite
query改写方法
使用多种方法重写原始查询,生成语义相似但表达不同的查询变体
- LLM改写 (RewriteMethod.LLM):利用大型语言模型(如Qwen、Llama等)重写查询。这种方法可以生成语义相似但表达方式不同的查询变体,有助于捕获用户意图的不同表达方式。
- 词汇表改写 (RewriteMethod.GLOSSARY):基于预定义的词汇表进行查询重写。词汇表包含同义词组,系统会使用这些同义词替换原始查询中的关键词,生成多个查询变体。这种方法适用于有明确领域词汇的场景。
- 同义词改写 (RewriteMethod.SYNONYM):使用LLM生成原始查询中关键词的同义词,然后用这些同义词替换原始查询中的关键词,生成多个查询变体。这种方法结合了LLM的语义理解能力和词汇替换的精确性。
改写query验证和筛选
使用多种方法验证改写后的query,确保它们与原始query保持语义一致性,去除质量较差的改写
- 无验证 (ValidationMethod.NONE):不进行任何验证,直接返回所有重写后的查询。适用于已经确信重写质量的场景,或者需要最大化查询多样性的情况。
- ROUGE-L和BLEU归一化验证 (ValidationMethod.ROUGE_L_BLEU_NORMALIZED):使用ROUGE-L和BLEU分数来评估改写的query与原始query的相似度,并选择最佳平衡点。这种方法既考虑了语义保留又考虑了表达多样性(BLEU)。
- 帕累托最优 (ValidationMethod.PARETO_OPTIMAL):应用帕累托最优原则,选择在ROUGE-L和BLEU分数上都不被其他查询支配的查询。这种方法可以找到语义保留和表达多样性的最佳平衡点。
- 最详细 (ValidationMethod.MOST_DETAILED):选择最详细(通常是最长)的重写查询。这种方法假设更详细的查询通常包含更多信息,可能更好地捕获用户意图。
- ROUGE-L + BLEU阈值过滤 (ValidationMethod.FILTER_BY_ROUGE_L_BLEU_THRESHOLDS):根据预设的ROUGE-L和BLEU阈值过滤重写查询。只有同时满足ROUGE-L最小阈值(确保语义相似性)和BLEU最大阈值(确保表达多样性)的查询才会被保留。
- LLM语义相似度 (ValidationMethod.LLM_SEMANTIC_SIMILARITY):利用LLM评估改写query与原始query的语义相似度。这种方法利用LLM的语义理解能力,可以捕获更复杂的语义关系,但计算成本较高。
LLM扩展
支持与多种大型语言模型集成,如Ollama提供的模型,openai的api key等,本项目设计了灵活的LLM接口,可以轻松扩展支持不同的大型语言模型。以下是如何添加OpenAI支持的示例。扩展方法如下:
创建新的LLM实现类
首先,创建一个新的Python文件,例如<font style="color:rgb(31, 35, 40);">openai.py</font>
,实现<font style="color:rgb(31, 35, 40);">LLMBase</font>
抽象类:
# queryrewrite/llm/openai.py from langchain_openai import ChatOpenAI from queryrewrite.llm.base import LLMBase class OpenAILLM(LLMBase): """LLM implementation for OpenAI models.""" def __init__(self, model: str = "gpt-3.5-turbo", api_key: str = None, temperature: float = 0.7): """ Initializes the OpenAILLM. Args: model: The name of the OpenAI model to use. api_key: Your OpenAI API key. temperature: Controls randomness. Higher values make output more random, lower values make it more deterministic. """ self.model = model self.api_key = api_key self.temperature = temperature self.llm = ChatOpenAI( model=self.model, openai_api_key=self.api_key, temperature=self.temperature ) def invoke(self, prompt: str) -> str: """Invoke the OpenAI model with a given prompt.""" response = self.llm.invoke(prompt) return response.content
安装必要的依赖
确保安装了OpenAI的依赖:
pip install langchain-openai openai
使用新的LLM实现
现在,您可以在代码中使用OpenAI模型:
from queryrewrite.llm.openai import OpenAILLM from queryrewrite.rewriting.base import rewrite, RewriteMethod from queryrewrite.utils.data_models import Query # 初始化OpenAI LLM llm = OpenAILLM( model="gpt-3.5-turbo", api_key="your-openai-api-key", # 替换为您的API密钥 temperature=0.7 ) # 定义查询 query = {"query": "如何测试一个大型语言模型?", "reference": "大型语言模型的测试是一个复杂的过程,涉及多个层面。"} # 使用OpenAI进行查询重写 rewritten_queries = rewrite(method=RewriteMethod.LLM, query=query, llm=llm)
扩展其他LLM提供商
您可以使用相同的模式扩展支持其他LLM提供商,如百度文心一言、讯飞星火等。只需:
- 创建一个新的类,继承
<font style="color:rgb(31, 35, 40);">LLMBase</font>
- 实现
<font style="color:rgb(31, 35, 40);">__init__</font>
和<font style="color:rgb(31, 35, 40);">invoke</font>
方法 - 安装相应的依赖
- 在您的代码中使用新的LLM实现
使用示例
以下是使用queryrewrite库的完整示例:
#!/usr/bin/env python # -*- coding: utf-8 -*- """ This demo showcases the usage of the queryrewrite library. """ from queryrewrite.llm.ollama import OllamaLLM from queryrewrite.rewriting.base import rewrite, RewriteMethod from queryrewrite.validation.base import validate, ValidationMethod from queryrewrite.utils.data_models import Query, Glossary def main(): """Main function to run the demo.""" # 1. 初始化LLM # 这里假设你已经运行了Ollama实例,并加载了qwen3:8b模型 try: llm = OllamaLLM(model="qwen3:8b") except Exception as e: print(f"Error initializing Ollama LLM: {e}") print("Please ensure Ollama is running and the model is available.") return # 2. 定义输入查询 query: Query = {"query": "如何测试一个大型语言模型?", "reference": "大型语言模型的测试是一个复杂的过程,涉及多个层面。"} # 3. 演示重写方法 print("--- Demonstrating Rewriting Methods ---") # 方法1: LLM重写器 print("\n--- Method 1: LLM Rewriter ---") try: llm_rewritten = rewrite(method=RewriteMethod.LLM, query=query, llm=llm,thinking='/no_think') print(f"LLM Rewritten Queries: {llm_rewritten}") except Exception as e: print(f"Error during LLM rewriting: {e}") # 方法2: 词汇表重写器 print("\n--- Method 2: Glossary Rewriter ---") glossary: Glossary = [ ["测试", "评估", "评测"], ["大型语言模型", "大模型", "LLM"], ] glossary_rewritten = rewrite(method=RewriteMethod.GLOSSARY, query=query, glossary=glossary) print(f"Glossary Rewritten Queries: {glossary_rewritten}") # 方法3: 同义词重写器 print("\n--- Method 3: Synonym Rewriter ---") try: synonym_rewritten = rewrite(method=RewriteMethod.SYNONYM, query=query, llm=llm,thinking='/no_think') print(f"Synonym Rewritten Queries: {synonym_rewritten}") except Exception as e: print(f"Error during synonym rewriting: {e}") # 4. 演示验证方法 print("\n--- Demonstrating Validation Methods ---") # 我们将使用glossary_rewritten列表进行验证示例 rewritten_queries = glossary_rewritten # 方法1: 无验证 print("\n--- Validation Method 1: No Validation ---") no_validation_result = validate( method=ValidationMethod.NONE, rewritten_queries=rewritten_queries, original_query=query["query"] ) print(f"No Validation Result: {no_validation_result}") # 方法2: ROUGE-L + BLEU归一化 print("\n--- Validation Method 2: ROUGE-L + BLEU Normalized ---") rouge_bleu_result = validate( method=ValidationMethod.ROUGE_L_BLEU_NORMALIZED, rewritten_queries=rewritten_queries, original_query=query["query"] ) print(f"ROUGE-L + BLEU Normalized Result: {rouge_bleu_result}") # 方法3: 帕累托最优 print("\n--- Validation Method 3: Pareto Optimal ---") pareto_result = validate( method=ValidationMethod.PARETO_OPTIMAL, rewritten_queries=rewritten_queries, original_query=query["query"] ) print(f"Pareto Optimal Result: {pareto_result}") # 方法4: 最详细 print("\n--- Validation Method 4: Most Detailed ---") detailed_result = validate( method=ValidationMethod.MOST_DETAILED, rewritten_queries=rewritten_queries, original_query=query["query"] ) print(f"Most Detailed Result: {detailed_result}") # 方法5: 根据ROUGE-L + BLEU阈值过滤 print("\n--- Validation Method 5: Filter by ROUGE-L + BLEU Thresholds ---") filtered_result = validate( method=ValidationMethod.FILTER_BY_ROUGE_L_BLEU_THRESHOLDS, rewritten_queries=rewritten_queries, original_query=query["query"] ) print(f"Filtered Result: {filtered_result}") # 方法6: LLM语义相似度 print("\n--- Validation Method 6: LLM Semantic Similarity ---") try: llm_similarity_result = validate( method=ValidationMethod.LLM_SEMANTIC_SIMILARITY, rewritten_queries=rewritten_queries, original_query=query["query"], llm=llm, thinking='/no_think' ) print(f"LLM Semantic Similarity Result: {llm_similarity_result}") except Exception as e: print(f"Error during LLM semantic similarity validation: {e}") if __name__ == "__main__": main()
一些理论依据
ROUGE-L
计算
ROUGE-L聚焦在最长公共子序LCS(Longest Common SubSequence)。LCS不需要连续匹配,只需保持词序一致,适合捕捉句子结构和关键内容的保留程度。
$ ROUGE-L=(1+β^2){Precison_{LCS}}{Recall_{LCS}}/(β^2*{Precison_{LCS}}+{Recall_{LCS}}) $
其中:
- ROUGE-L通常使用F1分数(即β=1,平衡Precision和Recall)。如果需偏重Precision或Recall,可调整β参数。
- R是参考文本,m是R的词的数量。
- H是生成文本,n是H的词的数量。
- LCS 最长公共子序列词的数量。
举个例子:
- R=我爱学习,H=我喜欢学习数学。R的m是3,H的n是4。
- LCS 是“我,学习”
- LCS(R.H)=2
- β=1
- 那么Precicion = 2/3,Recall=2/4,F1=(22/32/4)/(2/3+2/5)=0.571,所以ROUGE-L=0.571
实现
import jieba def lcs_length(x, y): """计算两个序列的最长公共子序列长度""" m, n = len(x), len(y) dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): if x[i-1] == y[j-1]: dp[i][j] = dp[i-1][j-1] + 1 else: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) return dp[m][n] # 输入文本 reference = "我爱学习自然语言处理" hypothesis = "我喜欢学习算法和自然语言处理" # 分词 jieba.add_word("自然语言处理") ref_tokens = list(jieba.cut(reference, cut_all=False)) hyp_tokens = list(jieba.cut(hypothesis, cut_all=False)) # 调试:打印分词结果 print(f"Reference tokens: {ref_tokens}") print(f"Hypothesis tokens: {hyp_tokens}") # 计算 LCS 长度 lcs_len = lcs_length(ref_tokens, hyp_tokens) # 计算 ROUGE-L m, n = len(ref_tokens), len(hyp_tokens) precision = lcs_len / n if n > 0 else 0 recall = lcs_len / m if m > 0 else 0 f1 = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0 # 输出结果 print(f"ROUGE-L Precision: {precision:.3f}") print(f"ROUGE-L Recall: {recall:.3f}") print(f"ROUGE-L F1: {f1:.3f}")
输出:
Reference tokens: ['我', '爱', '学习', '自然语言处理'] Hypothesis tokens: ['我', '喜欢', '学习', '算法', '和', '自然语言处理'] ROUGE-L Precision: 0.500 ROUGE-L Recall: 0.750 ROUGE-L F1: 0.600
中文需要分词,否则是按字符计算的,影响正确的评级ROUGE-L只关注词序、结构,不关注语义,可以结合BLEU Score、人工评估。如果有多个参考文本的时候,ROUGE-L的分数取最高LCS分数。
ROUGE-L结果好坏的评价
ROUGE-L 得分的好坏需要结合具体任务、数据集和上下文来判断,以下是一些通用的评价标准:
- 任务类型的影响:
- 机器翻译:好的 ROUGE-L 分数通常在 0.5 以上(F1 分数)。顶级模型在特定数据集(如 WMT)上可能达到 0.6 或更高。
- 文本摘要:由于摘要任务中生成文本通常比参考文本短,且表达方式可能多样,ROUGE-L 分数通常较低。0.3–0.5 可能被认为是合理的,0.5 以上通常表示高质量。
- 对话生成:对话生成任务中参考答案可能有多种表达形式,因此 ROUGE-L 分数可能偏低,0.2–0.4 可能已是不错的表现。
- 数据集和领域的差异
- 不同数据集的 ROUGE-L 分数可比性有限。例如,在新闻摘要数据集(如 CNN/DailyMail)上,ROUGE-L 分数通常在 0.3–0.5 之间,而在更复杂或多样化的数据集上,分数可能较低。
- 领域特定任务(如医学、法律)可能因为术语和表达的特殊性,分数整体偏低。
- 与其他指标结合
- ROUGE-L 只衡量序列相似度,无法完全捕捉语义或语法质量。结合其他指标(如 BLEU、BERTScore 或人工评估)可以更全面地评价生成文本质量。
- 如果 ROUGE-L 分数高,但生成文本语义不准确,可能需要进一步检查。
- 具体分数范围的参考
- 0.0–0.2:生成文本与参考文本几乎无重叠,可能完全偏离或质量极差。
- 0.2–0.4:生成文本与参考文本有一定相似性,但在许多任务中仍被认为较差,可能遗漏关键信息或包含较多无关内容。
- 0.4–0.6:中等偏上的表现,生成文本与参考文本有较多重叠,适用于许多实际应用场景。
- 0.6–0.8:高质量生成,通常表示生成文本在内容和结构上与参考文本高度一致。
- 0.8 以上:极高质量,生成文本几乎完全匹配参考文本,但在实际任务中较少见,因为参考文本可能有多种合理表达。
Bleu Score
BLEU (Bilingual Evaluation Understudy)Socre最初是用于评估机器翻译质量的指标,通过比较机器翻译结果与参考翻译的相似度来计算。其核心思想是基于 n-gram 的精确匹配,并结合长度惩罚。
n-gram 精确度
定义
- 计算机器翻译输出(候选翻译)中与参考翻译匹配的 n-gram 的比例。对于不同阶数的 n-gram(通常取 1-gram 到 4-gram),计算如下:
- 统计候选翻译中每个 n-gram 出现的次数。
- 对于每个 n-gram,检查参考翻译中该 n-gram 的最大出现次数(以防止过度匹配)。
- 计算匹配的 n-gram 数量,除以候选翻译中总的 n-gram 数量,得到该阶 n-gram 的精确度 Pn P_n Pn。
- 公式:
$ P_n = \frac{\sum_{\text{n-gram} \in \text{候选翻译}} \min(\text{计数}{\text{候选}}, \text{最大计数}{\text{参考}})}{\sum_{\text{n-gram} \in \text{候选翻译}} \text{计数}_{\text{候选}}} $
计算例子
- 候选翻译:“The cat is on the mat”
- 参考翻译:“The cat sits on the mat”
1-gram精度计算
那么,1-gram的精度计算(1-gram 就是单个词),候选翻译的1-gram是6,参考翻译的1-gram也是6。匹配过程:
- 检查候选翻译的每个 1-gram 是否出现在参考翻译中,并取匹配次数的最小值。
- The:候选翻译中出现 2 次,参考翻译中出现 2 次,匹配 2 次。
- cat:候选翻译中出现 1 次,参考翻译中出现 1 次,匹配 1 次。
- is:候选翻译中出现 1 次,参考翻译中出现 0 次,匹配 0 次。
- on:候选翻译中出现 1 次,参考翻译中出现 1 次,匹配 1 次。
- mat:候选翻译中出现 1 次,参考翻译中出现 1 次,匹配 1 次。
因此,匹配的 1-gram 总数:2 (The) + 1 (cat) + 0 (is) + 1 (on) + 1 (mat) = 5。因此1-gram 精确度 P1:
****
2-gram精度计算
那么,2-gram 是相邻的两个词组成的序列。
- 候选翻译2-gram的词序列为The, cat, is, on, the, mat,因此2-gram为{The cat, cat is, is on, on the, the mat},共 5 个 2-gram(因为 6 个词产生 6−1=5 6-1 = 5 6−1=5 个 2-gram)。
- 参考翻译的 2-gram的词序列:The, cat, sits, on, the, mat,因此2-gram为{The cat, cat sits, sits on, on the, the mat},共 5 个 2-gram。
那么,匹配过程如下:
- 对于候选翻译的每个 2-gram,检查它在参考翻译中出现的次数,并取匹配次数的最小值。
- The cat:候选翻译中出现 1 次,参考翻译中出现 1 次,匹配 1 次。
- cat is:候选翻译中出现 1 次,参考翻译中出现 0 次(因为参考翻译是“cat sits”),匹配 0 次。
- is on:候选翻译中出现 1 次,参考翻译中出现 0 次(因为参考翻译是“sits on”),匹配 0 次。
- on the:候选翻译中出现 1 次,参考翻译中出现 1 次,匹配 1 次。
- the mat:候选翻译中出现 1 次,参考翻译中出现 1 次,匹配 1 次。
因此,匹配的 2-gram 总数:1 (The cat) + 0 (cat is) + 0 (is on) + 1 (on the) + 1 (the mat) = 3。候选翻译的 2-gram 总数:5,因此2-gram精度P2:
几何平均精确度和长度惩罚
几何平均精度
对不同阶 n-gram 的精确度取几何平均值,通常使用 1-gram 到 4-gram:
注:为了平衡不同 n-gram 的贡献,通常使用等权重(1/4)。
长度惩罚(Brevity Penalty, BP)
如果候选翻译的长度比参考翻译短,可能会人为提高精确度,因此需要引入长度惩罚。
- 其中:
- c:候选翻译的长度(词数)。
- r:参考翻译的长度(词数)。
如果有多条参考翻译,通常选择与候选翻译长度最接近的参考翻译长度作为 r。
BLEU计算
综合几何平均精确度和长度惩罚,
BLEU也就说等于简短惩罚因子乘以N-gram的几何平均精确度。exp是指数函数,主要作用是将对数(ln)运算的结果还原回去,ln()是自然对数。
计算例子
- 候选翻译:“The cat is on the mat”
- 参考翻译:“The cat sits on the mat”
- 假设:
- P1=1.0 , P2=0.6 , P3=0.5, P4=0.25
- 候选长度c=6,参考长度r=6,则 BP=1
BLEU 分数约为 52.3。
代码
from sumeval.metrics.bleu import BLEUCalculator # 初始化 BLEU 计算器 bleu_calc = BLEUCalculator() # 英文示例 print("\n=== 英文 BLEU 计算示例 ===") reference_en = "I love studying natural language processing" hypothesis_en = "I like studying algorithms and natural language processing" print(f"Reference: {reference_en}") print(f"Hypothesis: {hypothesis_en}") # 计算 BLEU 分数 - 使用列表格式 bleu_score_en = bleu_calc.bleu(summary=[hypothesis_en], references=[[reference_en]]) print(f"BLEU Score: {bleu_score_en:.4f}") # 多个参考文本的示例 print("\n=== 多参考文本 BLEU 计算示例 ===") references_multi = [ "I love studying natural language processing", "I enjoy learning natural language processing", "I like to study NLP" ] hypothesis_multi = "I like studying algorithms and natural language processing" print(f"References: {references_multi}") print(f"Hypothesis: {hypothesis_multi}") # 计算多参考 BLEU 分数 - 使用列表格式 bleu_score_multi = bleu_calc.bleu(summary=[hypothesis_multi], references=[references_multi]) print(f"BLEU Score (multi-ref): {bleu_score_multi:.4f}")
输出:
=== 英文 BLEU 计算示例 === Reference: I love studying natural language processing Hypothesis: I like studying algorithms and natural language processing BLEU Score: 8.7836 === 多参考文本 BLEU 计算示例 === References: ['I love studying natural language processing', 'I enjoy learning natural language processing', 'I like to study NLP'] Hypothesis: I like studying algorithms and natural language processing BLEU Score (multi-ref): 3.3030
结果如何评价好坏
BLEU 分数的好坏需要结合具体任务、数据集和语言对来判断。以下是一些通用的评价标准:
任务类型的影响
- 机器翻译:
- 0.0–0.2:翻译质量很差,几乎无意义或与参考文本严重不符。
- 0.2–0.4:翻译有一定重叠,但可能遗漏关键信息或包含较多错误,质量较低。
- 0.4–0.6:翻译质量中等,适用于许多实际场景,顶级模型在标准数据集(如 WMT)上可能达到此范围。
- 0.6–0.8:翻译质量很高,生成文本与参考文本高度一致,接近人类翻译水平。
- 0.8 以上:接近完美翻译,但在实际任务中极少见,因为参考翻译可能有多种合理表达。
- 文本摘要:由于摘要任务中生成文本通常更短且表达多样,BLEU 分数通常较低,0.2–0.4 可能已是不错的表现。
- 对话生成:对话任务中参考答案多样性高,BLEU 分数可能在 0.1–0.3 之间,0.3 以上通常表示较好表现。
数据集和语言对的差异
- 数据集:不同数据集的 BLEU 分数可比性有限。例如,在 WMT 机器翻译数据集上,顶级模型的 BLEU 分数可能在 0.4–0.6,而在更复杂的数据集(如新闻或口语)上,分数可能较低。
- 语言对:BLEU 分数受语言对影响较大。例如,英语-法语等相近语言对的 BLEU 分数通常高于英语-中文,因为后者词序和语法差异更大。
- 领域特定性:在技术性领域(如医学、法律),由于术语和表达的特殊性,BLEU 分数可能偏低。
与其他指标结合
- BLEU 主要衡量字面上的 n-gram 重叠,无法完全捕捉语义、语法或语境的准确性。建议结合其他指标(如 ROUGE、METEOR、BERTScore)或人工评估来综合判断。
- 高 BLEU 分数可能对应过于保守的翻译(过于贴近参考文本),而低分可能仍具有语义正确性。
具体分数范围的参考
- 0.0–0.1:生成文本与参考文本几乎无重叠,质量极差。
- 0.1–0.3:生成文本与参考文本有少量相似性,但在大多数任务中被认为较差。
- 0.3–0.5:中等表现,生成文本与参考文本有一定重叠,适用于许多实际应用。
- 0.5–0.7:高质量生成,通常表示生成文本在内容和结构上与参考文本高度一致。
- 0.7 以上:极高质量,接近人类水平,但在实际任务中较少见。
总结
- 多参考翻译:如果有多个参考翻译,候选翻译的 n-gram 会与所有参考翻译比较,取最大匹配次数。
- 范围:BLEU 分数范围为 [0, 1],通常乘以 100 表示(0 到 100 分)。高分表示翻译质量更好。
- 局限性:
- BLEU 只关注 n-gram 的精确匹配,忽略语义和语法结构。
- 对短翻译可能过于敏感。
- 不适合评估非字面翻译或创造性翻译。
ROUGE-L分数高,BLEU分数低
当 ROUGE-L 分数高而 BLEU 分数低时,这通常意味着生成文本在内容上与参考文本相似,但句法结构或具体用词可能存在较大差异。高 ROUGE-L 分数意味着你的生成文本成功捕捉到了参考文本中的关键信息点,并且这些信息点的顺序大致正确。低 BLEU 分数意味着你的生成文本在词汇选择和句子结构上与参考文本有很大的不同。即使生成文本的意思是正确的,但如果它使用了不同的词语或完全不同的句法结构来表达相同的意思,BLEU 分数就会很低。
ROUGE-L 高但 BLEU 低,说明:
- 使用了同义词或不同的表达方式:生成文本传达了与参考文本相同的信息,但使用了不同的措辞。
- 改变了句子结构:虽然核心信息都在,但句子被重新组织或重写,导致n-gram匹配度低。
简而言之,就是“说什么”方面做得很好(高ROUGE-L),但在“怎么说”方面与参考文本有较大偏差(低BLEU)。