news 2026/10/17 5:33:06

AI搜索与RAG:从关键词匹配到意图理解的技术拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI搜索与RAG:从关键词匹配到意图理解的技术拆解

搜索框统治互联网二十多年,大家早就习惯了“输入两三个词、回车、从上到下翻结果”的路径。但这几年风向变了。AI搜索引擎的迭代,表面看是从“列表”变成了“对话”,实际上是整个信息获取范式在换底盘。你不再靠关键词去猜网页,而是让模型理解你的真实意图,去资料库里把相关内容捞出来,读完了再组织成一段人话讲给你听,连带着背后做搜索的团队、写内容的人、用搜索的用户,全都被卷进了这轮变化里。

这篇文章是站在我一个多年从业者的角度,把“AI搜索引擎迭代下”这件事拆开讲。我会说到它相比传统搜索到底改了什么、当前主流AI搜索链路长什么样、你自己要搭一个能用的AI搜索问答系统需要做哪些事,以及这个过程中常见的坑和排查思路。适合正在做检索增强生成(RAG)相关项目的开发者、被AI搜索改变流量逻辑的内容运营者,以及想搞明白“现在的搜索为什么突然变聪明了”的普通用户。我会尽量用做事的视角来讲,不堆概念。

1. 整体设计与思路拆解:AI搜索到底在改什么

1.1 范式转变:从关键词匹配到意图理解

传统搜索的核心是“倒排索引 + 排序”。系统先把网页切词、建索引,用户输入关键词后,在索引里找包含这些词的文档,再用一套复杂的排序公式(权重、点击率、时效性、域名质量等)把结果排出来。这套体系运转了二十多年,成熟、稳定、可解释性强,但有一个根上的问题:它匹配的是“词”,不是“意思”。

你搜“苹果价格暴跌的原因”,传统搜索引擎的理解是:页面里必须出现“苹果”“价格”“暴跌”“原因”这些词,或者它们的变体。至于你是想问水果行情还是数码产品行情,它只能靠上下文猜,猜错了也没办法。AI搜索不是这么干的。它先把你的问题转化成一组向量——可以理解成把语义压成一个高维空间里的坐标——然后在整个资料库里找坐标相近的内容。哪怕一段文章从头到尾没出现“苹果”这两个字,但它在讲农产品供需和天气影响,依然会被捞出来,因为它和你的问题在语义上足够接近。

这个转变的实质,是把“检索”从字面匹配升级成了“语义匹配”。我个人的理解是:传统搜索是图书馆管理员给你指一排书架,剩下自己翻;AI搜索是一个读过很多书的助手,听到问题后自己去书架找,找到相关段落读一遍,再用你自己的话说给你听,最后告诉你这句话出自哪本书哪一页。两个模式解决的是同一个需求,但体验路径完全不同。

1.2 三层链路:检索、增强、生成各自承担什么

当前主流的AI搜索实现,基本都跑在“检索-增强-生成”的链路上,也就是常说的RAG(Retrieval-Augmented Generation,检索增强生成)。这条链路拆成三层来看:

  • 检索层:从海量资料里快速捞出候选内容。通常同时用两类方案——基于关键词的稀疏检索(典型算法是BM25)和基于向量的稠密检索。
  • 增强层:对捞出来的候选做重排、过滤、去重,把最相关的排在最前面,剔除跟问题无关的干扰项,最后拼成一段可控长度的上下文。
  • 生成层:把筛选后的内容和用户问题一起交给大模型,让模型基于这些材料生成回答,并在回答中标出引用来源。

为什么必须拆成三层?因为每一层解决的是完全不同的问题。检索层解决的是“潜在相关的别漏掉”,宁可多捞一点,把Top 50甚至Top 100的都拿出来;增强层解决的是“最相关的要排前面、不相关的要清走”,把候选收敛到Top 5左右;生成层解决的是“怎么把材料变成有条理、可信、带引用的回答”。如果把三层合并成一步——比如只靠向量检索捞5条就直接喂给模型——那么召回内容里只要混进一两条语义沾边但实际无关的段落,模型就可能被带偏,开始答非所问。

1.3 比“答得准”更重要的是“引用可信”

很多人只盯着AI搜索“能不能答对”,但我做了几个项目之后,一个更深的体会是:AI搜索引擎迭代的核心价值不是“答得准”,而是“答得有依据且可核对”。传统搜索把信息来源直接摊在你面前,你自己判断谁可信;第一代AI搜索则倾向直接给结论,但用户没法知道结论是从哪来的,于是出现了大量“模型一本正经地胡说八道”的翻车现场。

新一代AI搜索的迭代方向,就是把这个信任问题补回来:回答里必须带引用,每个关键论断后面都有来源链接,点击就能跳到原文段落。这一点对整个技术链路的选型影响很大——比如用生成模型时,不能随便让它凭记忆回答,必须强制它只基于检索回来的上下文回答,找不到答案就明确说“找不到”;再比如召回时宁可多召回一段相关文本,也要保证包含正确答案的那一段不会被漏掉,因为一旦漏了,模型就会开始脑补。后面第3节、第4节我会具体讲这些约束怎么落到代码和提示词里。

2. 核心细节解析与实操要点

2.1 向量检索:嵌入模型选择和相似度计算的讲究

向量检索是整个AI搜索的物理基础。它的核心是把文本变成一串浮点数,也就是嵌入向量。这一步选择什么嵌入模型,基本决定了整个系统的上限。

嵌入模型的选择有几个关键参数要注意:向量维度、支持的最大文本长度、语义能力、以及中文场景的适配程度。维度高(比如1536维)表征能力强,但存储和计算开销也大;维度低(比如384维)轻量,但语义区分度可能不够。我个人的经验是,通用场景下优先选768维或1024维的中文优化模型,在效果和成本之间比较平衡。另外一定要看模型支持的最大输入长度,有些老模型只支持512个token,超过会被截断,长文档就得提前切好再向量化。

相似度计算也有讲究。常见的是余弦相似度,取值范围在-1到1之间,数值越大表示越相关。很多人直接拿这个分数当“相关性阈值”用,但这里有个坑:不同嵌入模型的分数分布完全不一样。同样是“高度相关”,有的模型能打到0.85,有的模型0.7就到顶了。所以绝对不能套用一个全局阈值,必须先跑一批样本,统计自己数据上的分数分布,再定阈值。

我们可以用一段简单的伪代码示意整个检索流程:

# 伪代码:展示AI搜索检索层的基本流程 query = "苹果价格暴跌的原因" query_vec = embedding_model.encode(query) # 把问题转成向量 results = vector_store.search(query_vec, top_k=50) # 向量召回Top 50 keyword_results = bm25_index.search(query, top_k=50) # 关键词召回Top 50 merged = merge_and_deduplicate(results, keyword_results) # 合并去重 final = reranker.rerank(query, merged, top_n=5) # 重排后取Top 5 response = llm.generate(query, final, with_citations=True) # 生成带引用的回答

这段代码看着简单,实际每个环节都有大量细节。向量存储选型方面,既可以用专门的向量数据库方案,也可以直接在内存或传统数据库的向量插件上做,数据量在百万级以内时后者的开发成本更低、调试更方便。我一般先上轻量方案跑通链路,数据量大了再迁移。

2.2 混合检索:为什么不能只靠向量

只靠向量检索会出现两类让人头疼的问题。第一类是专有名词、编号、型号这类必须精确匹配的场景,向量往往表现不佳。比如用户搜“A3-2024号文件”,嵌入模型很可能把它当成普通语义,召回一些“文件”“2024”相关的泛内容,而关键词检索能精确命中“A3-2024”。第二类是罕见词和长尾表达,向量模型在训练时见过这些词的次数太少,很难把它们编码到正确的位置上,但BM25这种基于精确词频的算法反而稳定。

所以成熟的做法是混合检索:向量召回和关键词召回并行跑,各取Top 50,合并去重后再统一重排。关键词检索这边,BM25依然是事实标准。它比原始词频统计好的地方在于,会同时考虑“词在文档里出现得多不多”和“这个词在整个语料库里见得多不多”——一个词在文档里出现次数越多越相关,但如果在所有文档里都烂大街(比如“的”“是”),它的区分价值就低,权重会被压低。

混合检索的合并策略也值得说一下。最简单的做法是“取并集再去重”,然后靠后面的重排器统一排序。不要试图在合并阶段手动调权重,比如“向量占0.7、关键词占0.3”,这种做法看着合理,实际调参成本极高,而且两个检索引擎的分数压根不在一个量纲上。让重排器来学“哪种来源的候选更值得排在前面”,效果比手工调权重好一个量级。

2.3 重排模型:把好答案从候选里捞出来的关键一步

召回阶段追求“全”,重排阶段追求“准”。重排器(Reranker)的任务是:拿到一组候选文档后,逐对计算“这个文档和当前问题的相关程度”,然后重新排序。这个环节的存在是因为向量检索的分数在大规模候选上的区分度不够,尤其当Top 50里混着大量“语义沾边但实际无用”的内容时,必须有一个更强的模型来做二次筛选。

一个重要的概念区别:嵌入模型是双塔结构,问题和文档各自编码成向量再算相似度,速度快但精度有限;重排器通常是交叉编码器结构,把问题和文档拼成一句话一次性过模型,能看到两者之间更细的交互信息,精度更高,但速度慢得多。所以两者的使用方式完全不同——嵌入模型负责在海量数据里粗筛,重排器只负责在几百条候选里精排。

实操中,重排器最常用的场景是“召回Top 50,重排后取Top 5”。Top 5这个数字不是拍脑袋定的:生成模型能有效利用的上下文长度有限,塞太多候选进去,一是超出模型的注意力有效范围,二是无关内容一多,模型就容易被干扰。我测试下来,中文场景下5到8段是兼顾“信息量”和“聚焦度”的甜区。这里有一个容易被忽略的细节:重排之后不要只保留分数,还要把原始检索的分数、来源文档ID、段落位置一并传给生成层,因为生成层做引用标注时需要这些元数据。

2.4 生成与引用:给大模型喂料的艺术

检索链路做得再好,最后“说人话”的还是大模型。这一步的关键不是模型多大,而是你怎么约束它。一个常见的做法是,先生成回答,再事后去核对引用;更好的做法是,让模型在生成过程中就把引用带上——每一个事实性陈述后面紧跟一个引用标记,对应着上下文里具体的段落编号。

要让模型做到这点,提示词模板就得写得很死。我常用的一种做法是:在系统提示里明确告诉模型,“你只能依据提供的资料回答,资料中没有的信息不要编造;每个关键信息后面必须用[1]、[2]这样的编号标注来源,编号对应资料列表;如果资料不足,直接回答‘根据现有资料无法确认’”。资料列表则按编号排列在用户消息里,每段前面标明来源标题。

你是资料问答助手。请严格按照以下规则回答: 1. 只能依据《背景资料》中的内容回答,禁止使用资料之外的常识或记忆。 2. 每个事实性陈述后必须用[编号]标注来源,编号对应背景资料前的序号。 3. 若资料不足以回答问题,直接回复:“根据现有资料无法确认。”不得编造。 4. 回答控制在300字以内,分点列出。 背景资料: [1] 资料标题A:正文内容…… [2] 资料标题B:正文内容…… [3] 资料标题C:正文内容…… 用户问题:苹果价格暴跌的原因

这个模板看起来朴素,但里面每条规则都有实际意义。第1条防幻觉;第2条保证可溯源;第3条是最后一道防编造闸门;第4条控制生成长度,防止模型把5段资料强行扩写成一篇万字长文。我踩过的一个坑是:一开始没写第3条,结果模型在资料不全时频繁给出“可能是……也可能……”这种和稀泥的回答,看似圆滑,实际上是在编造。明确允许它说“不知道”之后,幻觉率明显下降。

3. 实操过程与核心环节实现

3.1 文档切分:128、256还是512

AI搜索的检索单元,不能是整篇文档,而是切分后的段落。因为文档太长了,向量化之后语义会被“稀释”——一篇讲水果市场的文章里,关于“苹果价格”的信息可能只有一两段,却要跟整篇文章的向量做相似度比较,命中精度自然差。所以文档切分是整个系统里最容易被低估的环节。

切分粒度怎么选?没有绝对标准,但有一个基本逻辑:段落长度要跟“问题的常见答案长度”匹配。如果用户的问题是“这个设备的保修政策是什么”,答案通常是一两句话,那么检索单元就应该控制在100到300字之间;如果用户的问题是“总结这份报告的核心观点”,答案需要横跨多个章节,那检索单元就需要更完整一些,甚至要引入“父子分块”策略——检索时用小的子块提高精度,生成时把子块所在的父级章节一起喂给模型。

实际项目中我常用的策略是:优先按文档的自然结构(标题、章节、段落)切分,而不是死板地按固定字数切。自然段落本身有语义边界,切出来的块可读性和语义完整性都更好。如果某一段实在太长,再进一步切成固定大小并加重叠区域,重叠量一般控制在块大小的10%到20%,避免一个完整的信息被拦腰截断。比如512字的块,相邻块之间重叠50到100字,这样“苹果价格暴跌”这种跨句信息就不会被切散。

切完之后要做的第二件事是清洗和去重。网页正文里经常混着导航、页脚、广告、评论,这些噪声不清理,后面检索到的全是垃圾上下文。清洗的规则因网站而异,但我建议至少做三件事:去掉无意义的导航区块、合并重复段落、保留段落标题作为元数据。段落标题很重要——把它存成字段,检索命中后把这个标题显示给用户,引用就更有说服力。

3.2 索引构建与检索参数设置

索引构建阶段,我习惯把数据模型设计成三个字段:文档ID、段落ID、文本内容、来源URL、标题、章节路径。向量只存文本内容对应的嵌入,其他字段作为元数据挂在一旁。这样设计的好处是,检索命中后可以直接用元数据做过滤——比如限定时间范围、限定某个站点、限定文档类型,而不需要重新查一次库。

检索参数里,Top K的设定是最核心的。召回阶段我一般取50,重排后取5到8。这里有一个业务层面的权衡:Top K取太小,容易漏掉正确答案;取太大,向量检索的耗时和重排器的计算量都会上来。50这个值是我在多数项目中验证过的平衡点,但如果你处理的是法律文书、医疗文献这种“正确答案表述多样”的语料,可能需要加大到100,因为这个场景下同一个问题能被几十种方式问出来,而每种问法对应的答案句可能完全不同。

相似度阈值方面,前面我强调过不能直接套通用值。正确做法是准备一组已知“相关”和“不相关”的样本对,分别计算它们的相似度分数,看两个分布的重叠区间在哪里,阈值就设在重叠区间的中间。比如统计下来相关样本最低分数是0.72,不相关样本最高分数是0.61,那阈值可以设在0.66左右。注意阈值的作用是“过滤明显不相关的内容”,不是“确保所有内容都相关”,召回阶段宁可松一点,把过滤压力交给重排器。

3.3 离线评估夹:先建一套小规模验证集

很多人把系统搭完直接上线,等用户投诉了才意识到效果不行。我建议花半天时间先建一个离线验证集,这比任何优化技巧都重要。验证集不需要很大,50到100条就够了,但要有结构:每一条是一个真实用户问题,配上对应的正确答案段落和来源文档。有些问题可以故意设计成“资料里没有答案”的负样本,用来测模型的拒答能力。

离线评估跑起来之后,你会发现所有“感觉上还不错”的优化,都能在数字上分出高下。常用的指标有两个:召回命中率(正确答案有没有出现在重排后的Top 5里)和端到端回答质量(引用是否准确、有没有幻觉、回答是否完整)。前者可以用脚本自动算,后者需要人工抽检。我习惯每次改动后跑一遍验证集,把结果记在一个表里对比。这个表的另一个作用是,当你跟团队讨论“要不要换一个嵌入模型”时,能用数据说话,而不是靠争辩。

我觉得做这一类系统,最核心的工程素养是“可回测”——每次改动能被量化验证,整个系统的演进才是可控的。没有验证集的所谓优化,本质上都是自我感觉良好。

4. 常见问题与排查技巧实录

4.1 模型一本正经地胡说八道

幻觉(Hallucination)是AI搜索落在用户面前最直观的问题。模型对资料里没有的内容侃侃而谈,还煞有介事地标了引用,被用户点开一看,引用页面里根本没有那句话。这一下,信任就崩了。

我遇到的大多数幻觉问题,根因不在模型,而在上游:要么是检索到的上下文里混入了“看似相关、实则无关”的段落,模型被带偏了;要么是提示词没有把“只基于资料回答”的约束写死。排查顺序应该是:先看模型生成回答时拿到的上下文是什么——是不是确实包含了足够信息?有没有混入垃圾段落?再看提示词——是否明确禁止编造?是否给了“拒答”这个出口?

一个实用的排查技巧是把每次问答的“检索中间结果”都记录到日志里。出问题后,打开日志看:模型拿到的那几段资料到底是什么。我踩过最典型的一次坑是,用户问“某产品支持哪些支付方式”,检索出来的资料里有一篇另一产品的文档,里面碰巧也提到了“支付”二字,模型就把两个产品的信息混在一起回答,还说“以上信息均来自官方文档”。问题不在模型,在于检索没有对“产品型号”这个维度做过滤。解决方式是给检索加上元数据约束——必须限定同一型号的产品资料。

4.2 召回结果和问题对不上

另一种高频问题是:答案明明在资料库里,但检索就是捞不出来。这通常有三个原因。第一,切分粒度不对——正确答案被切碎了,一半在上一块、一半在下一块,嵌入模型对每一块都算不出高相关度。检查方法是:直接拿几篇已知包含答案的文档,切分之后看答案所在的那几块单独跟问题算相似度,如果分数都很低,大概率是切碎问题。第二,嵌入模型的领域能力不足——同样一句话,通用模型在垂直领域里的语义区分度可能不够。换一个在目标领域数据上微调过的嵌入模型,往往会有明显提升。第三,检索逻辑缺少同义词扩展——用户说“跌价”,文档里写的是“回落”,语义上两者接近,但向量模型未必把它们映射到足够近的位置。解决方案就是前面提过的混合检索,让BM25用关键词兜底。

这里有个很容易自我怀疑的点:纯靠调相似度阈值是救不了召回问题的。阈值只能在“已经召回但不够准”的时候起作用。如果一个本应命中的段落,检索阶段根本就没被召回,你再怎么调阈值也看不见它。所以排查方向一定是向上游看,先看召回列表里到底有没有正确答案的“影子”——哪怕排名很低,说明检索还认识它,只是被排后面了,那是重排的问题;如果影子都没有,那就是召回或切分环节的问题。

4.3 响应太慢、成本太高

AI搜索的在线链路比传统搜索重得多。传统搜索是毫秒级返回,AI搜索要做到几秒钟内完成“检索 + 重排 + 生成”,每一步都在跟时间赛跑。常见的时间开销分布大概是:向量检索几十到几百毫秒,重排器几百毫秒到一秒多,大模型生成是最大的开销,取决于输出长度和模型规格。

如果用户反馈太慢,优先动生成环节:一是精简提示词和输出长度限制,回答越长,生成越慢;二是选择响应更快的模型规格,端侧小模型在简单问答场景下的质量已经足够。不要一上来就动检索,检索层的优化空间相对有限,把重排Top 50降成Top 20倒是可以考虑,但必须先验证召回率没掉。

成本方面,最大的隐患是“每个问题都把所有候选喂给大模型”。如果重排后保留了8个段落,而每段平均300字,那每次问答的输入就是2400字,一个月百万次问答,token消耗相当可观。优化思路有两个:一是尽量精简重排输出,能保留5段就不要保留8段;二是在进入大模型之前,加一道“段落压缩”逻辑——用轻量模型把每段压成摘要,或者只提取与问题最相关的句子。这样既能控制成本,又能减少上下文中的干扰信息。

4.4 没有标注数据怎么做效果评估

多数项目起步时是没有任何标注数据的。这时可以先做“弱标注”:拿自己团队里的真实问题,靠人工判断在库里找出正确答案段落。不用多,50条就是很好的起步。如果没有专门的人力,还有更轻的办法:把现有资料里的问答对、FAQ条目直接整理成验证集,这些条目天然就是“问题 + 标准答案”的成对数据,拿来评估召回质量非常合适。

人工抽检的频率我建议是每次上线前抽20条,重点看三类问题:引用是否真实存在、回答是否完整覆盖问题的多个子问、有没有出现资料之外的信息。把这些抽检结果按月汇总,你会很快发现自己系统里的问题集中在哪个环节。比如连续三次都是“引用指向的段落和回答内容对不上”,那问题基本出在引用编号和上下文段落对应关系上,而不在检索也不在生成。

做这类评估一定要记住一点:效果是持续迭代出来的,不是一次测出来的。AI搜索系统的每个组件都在快速变化,嵌入模型、重排器、生成模型、切分策略,任何一个换掉,整体效果都可能大起大落。唯一的应对方式就是把这套验证集一直养着,每次改动都跑一遍,让数字告诉你答案。

5. 一些我踩过坑之后的体感总结

写了这么多,最后分享几个我在实际项目中反复验证过的体会。第一个是:AI搜索的工程难点不在任何一个单一环节,而在“环节之间的衔接”。检索、重排、生成每一层单独拿出来都有成熟方案,但把它们串联起来之后,各种信息损耗和错误传递才会暴露出来——检索返回了正确段落,重排却把它压到第9名;重排把正确段落顶上来了,提示词却让模型忽略了第3段。每次排查问题,都建议从链路整体去看,而不是只盯着某一段。

第二个体感是:所有参数都不要照搬别人的经验值,包括我这篇里写的。Top K取50、重排取5、重叠区10%到20%,这些是我在项目里验证过的起点,不是终点。你的语料结构、用户提问习惯、业务对准确率和召回率的偏好都不一样,一定要在离线验证集上重新标定一遍。

第三个体感是:用户体验层面的细节往往比技术指标更能决定成败。同样一套检索生成链路,回答后面有没有清晰的引用来源、引用能不能一键跳到原文、答不上来时有没有老老实实说“不知道”,这三个细节直接决定了用户是“信任它”还是“觉得它像个玩具”。我见过不少团队把精力全花在刷指标上,结果上线后用户反馈“不敢用”,问题就出在信任感没建起来。

AI搜索引擎的迭代还会继续,但有一点短时间内不会变:用户要的不是一个更聪明的满嘴跑火车的聊天工具,而是一个“说出来的每句话都有出处、找不到就坦诚说找不到”的信息助手。谁能先把这件事做到位,谁就能在这一轮迭代里站稳脚跟。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 5:33:01

VMware虚拟机安装CentOS 7完整教程:从镜像选择到初始化配置

很多人第一次接触Linux服务器,都绕不开一个动作:在自己电脑上用VMware搭一台CentOS 7虚拟机。这个标题看起来简单,就是装个系统而已,但真正动起手来,里面能踩的坑比想象中多得多——镜像版本选错、磁盘分区脑子一热全给…

作者头像 李华
网站建设 2026/10/17 5:32:40

DCDC电源纹波测试五层过滤法:从探头接地到LISN验证

简介:本资源是一份面向电源工程师、硬件测试人员及电子设计从业者的DC-DC电源纹波测量技术指南,聚焦高频开关电源中输出电压纹波的精准捕获与分析难题,解决Vcore供电敏感场景下纹波超标导致系统不稳定等实际问题。资料为Richtek(立…

作者头像 李华
网站建设 2026/10/17 5:32:17

景区评论采集与情感分析实战:从爬虫到SnowNLP应用

龙湖古寨评论采集工具,这个项目名字听起来像个小练手,实际做完我才发现,它就是一套典型的数据采集加文本分析流程:把散落在旅游平台上的游客评论抓回来,再用 SnowNLP 给每条评论算出一个情感倾向分,最后汇总…

作者头像 李华
网站建设 2026/10/17 5:31:57

Docker实战从入门到部署:容器化镜像与Compose编排指南

先问你一个问题:你身边有没有发生过这种场景——开发同学在本地跑得好好的代码,一扔到测试环境就各种报错;运维同学熬夜部署新版本,结果发现环境配置跟文档里写的不一样;新来的同事搭了一整天的开发环境,最…

作者头像 李华
网站建设 2026/10/17 5:31:55

HarmonyOS AVSession全局播控开发:从原理到避坑指南

这个系列写到现在,越写越觉得鸿蒙多媒体这块有味道。前面几篇咱们把音频渲染、焦点管理这些底层能力都过了一遍,这篇终于轮到全局播控了。搞 HarmonyOS 多媒体开发的,早晚都会碰到这个东西:AVSession。先别嫌名字拗口,…

作者头像 李华