TwKit
正在载入
TwKit

搜索引擎是如何工作的? 直觉上来看,搜索引擎就像一个不知疲倦的图书馆员,拿着我们搜索的关键词,去机房里一本本电子书,从第一页翻到最后一页,逐字逐句找到相同的… · @realchendahuang · TwKit

@realchendahuang

搜索引擎是如何工作的? 直觉上来看,搜索引擎就像一个不知疲倦的图书馆员,拿着我们搜索的关键词,去机房里一本本电子书,从第一页翻到最后一页,逐字逐句找到相同的段落,但是这样消耗的资源太大了,根本行不通。 互联网上的文章和数据以百亿计。如果每次有人搜索,系统都要把这几百亿字从头到尾扫一遍,别说零点几秒返回结果,哪怕把全世界上万台服务器的硬盘全读冒烟,一次查询也得等上几分钟。 为了能在千分之一秒内找到内容,搜索引擎在把文章存进硬盘的第一刻起,就用了一种完全不同的组织方式。这个基础结构,在计算机里叫做倒排索引。 一本厚厚的教科书,最前面是常规目录,上面写着第一章在第十页、第二章在第五十页,这是从文章找内容。 但在专业书的最后一页,往往还附着一份名词索引表。那里把全书出现过的关键术语按拼音或者字母排好:看到某个术语,后面直接标着它在第十五页、第七十二页、第一百三十页出现过。 读者想查某个术语,根本不需要从第一页翻起,直接去书末索引表里找到这个词,顺着后面的页码就能一把抓出所有对应的段落。 搜索引擎干的事情完全一样。它把一整篇长文拆碎,为每一个细小零件建立一张专属名单,上面记着它都在哪几篇文章里露过面。 当你在搜索框里输入关键词时,系统直接去翻这张零件名单,瞬间就能定位到包含这个词的所有文章编号。 但这立刻引出了一个新的现实难题:文章是由连贯的长句子组成的,机器到底该怎么把句子拆成合格的零件? 在搜索引擎的世界里,这些被拆解出来、准备写进索引名单的最小零件单元,叫做 Token,中文通常翻译成词元。 如果只是简单地把一段话按标点符号切开,检索系统立刻就会陷入瘫痪。 设想一个最常见的生活场景: 你想找一家法式咖啡馆,在搜索框里输入的是键盘上随手打出来的 cafe;但正规文章里写的是带有法文重音符号的字符 café。 在计算机底层,带有音标的字符和普通英文字母是完全不同的两串二进制编码。如果系统直接硬碰硬比对,哪怕只差了一个小音标,这个咖啡馆你也搜不出来。 又或者用户搜的是小写的 database,正文里写的是句首大写的 Database;用户搜的是正在跳跃 jumping,文章里写的是过去完成时 jumped。 自然语言里充满了大小写、时态、单复数以及各种语境变化。如果机器不做任何预处理,直接拿原话去对账,搜索就会大面积失灵。 为了把人类随心所欲写出来的自然语言,规整成机器可以稳定比对的标准零件,所有现代搜索引擎在录入文章和处理搜索词时,都必须严格走完一条被称为分词流水线的加工工序。 第一道工序,是字符清洗与折叠。 这一步的目标是在正式切词之前,先把每个文字都整成统一的标准脸。 最标准的操作是把所有大写英文字母统统压成小写,同时把字母头顶上的各种声调、变音小帽子全部摘掉。比如法文里的 café 和简历 résumé,头顶上的斜杠一律被削平,统一折叠成英文字母表里最普通的基础字母 cafe 和 resume。 把大写字母一律转成小写,有时候会带来误伤。比如英文里大写的 Olive 本来是一个女孩子的名字,转成小写之后,就会和做沙拉吃的橄榄 olive 混为一谈。 但在搜索引擎的设计哲学里,宁可承担极小概率的同名误伤,也绝不能让用户因为忘记加音标或者没注意大小写就彻底搜不到内容。 脸型整好之后,文本进入第二道工序:切词。 切词的任务是把一整串连在一起的文字,切成一粒一粒独立的词元零件。负责干这个活的软件模块,就叫分词器。 在英文世界里,切词看起来相对容易,因为单词之间天然就有空格隔开。但即使在英文里,遇到标点符号依然会让不同的底层引擎犯难。 比如遇到像 it is 缩写成带有撇号的特殊词汇,老牌搜索引擎 Lucene 倾向于把它们当成一个不可分割的整体,完整保留为一个词元;而用 Rust 编写的高性能引擎 Tantivy,则会毫不犹豫地把它们切分成前后两个独立的词。 到了中文、日文和韩文这里,情况就变得复杂得多。 中文句子之间根本没有天然的空格,一长串汉字连在一起,计算机仅凭标点符号根本不知道哪里才算一个独立的词。 比如遇到南京市长江大桥这几个字,到底是应该切成南京市和长江大桥,还是切成南京、市长和江大桥?如果切错了位置,搜市长的人就会莫名其妙搜出跨江大桥的建设新闻。 所以处理东方语言时,分词器必须在后台挂载一套庞大的语言学词典,像一把解剖刀一样,对照着真实语法去推断汉字之间的词语边界。 文字切成独立的词元之后,第三道工序开始清理语言里的废话,也就是过滤停用词。 任何一种语言里,都存在大量高频出现、但本身几乎没有具体含义的辅助虚词。在中文里是 的、了、呢、吧,在英文里是 the、and、of、is。 这些词几乎会在每一页文章里密集出现。如果把它们也堂而皇之地写进倒排索引,这张名单将会庞大到无法想象,甚至比正文本身还要占地方。 在很多底层的数据库系统里,比如 PostgreSQL 自带的全文检索,系统必须狠下心把这些停用词全部丢进垃圾桶,一张名单都不给它们建,否则服务器的磁盘空间很快就会被塞满。 但彻底删掉停用词,又会引发新的尴尬。 比如有人想搜英国著名的摇滚乐队 The Who,或者搜著名的名句 To be or not to be。如果系统把 the、to、be 统统当成垃圾删掉,用户输入这几个词按下回车,得到的结果只能是一片空白。 现代搜索引擎为了破解这个两难困境,引入了一套被称为 BM25 的相关性排序算法。 BM25 的核心机制非常通俗,工程上叫逆文档频率。用生活常识来打比方:全天下所有人都在说的话,就不值钱;全天下极少有人提到的生僻专业词,在文章里露一次面就含金量极高。 算法会在后台自动统计每个词在全网的普遍程度。像 the 这种烂大街的词,算法会自动给它打上一个无限接近于零的权重分;而遇到特定专有名词时,算法会赋予极高的分值。 有了这套自动打分机制,现代引擎甚至不需要强行删除停用词,因为它们就算留在索引里,也翻不起什么浪花。不仅如此,把它们留在原处,还能精准记录每个词在句子里的第几位,让用户搜索特定完整短语或者近邻排布时依然能够准确命中。 第四道工序,是词干提取。 人类在阅读时,看到 jump、jumps、jumped、jumping,大脑能一秒反应过来这都是在说跳跃这个动作。但对计算机来说,这完全是四个互不相识的字符串。 为了让搜跳跃的人能够顺畅找到包含跳过了的文章,系统必须把各种单复数、时态的后缀剥掉,让它们回到一个统一的核心。 最经典的工具是 1980 年诞生的 Porter 词干算法。这个算法不讲究语言学的美感,它就像一把按部就班的机械切纸刀,纯粹按照一套写死的机械规则削掉单词末尾的字符。 在切纸刀底下,jumped 被削成了 jump,lazy 被削成了 lazi,就连 database 都会被切成看起来少了个字母的 databas。 虽然切出来的结果看起来像拼错的词,但工程上不在乎单词好看不好看,在乎的是绝对的规则一致性。只要用户搜索时切出来的是 databas,文章入库时切出来的也是 databas,两者就能在倒排索引里稳稳当当握上手。 其实语言学里还有一种更讲究的做法,叫做词元化。词元化会严格分析单词在上下文里是名词还是动词,再翻开一本几百兆的语言字典,把单词工工整整还原成字典里的标准原型。 但在每秒处理几十万次并发检索的工业系统里,为每个单词查语法字典的算力消耗实在太重了。相比之下,机械切纸刀虽然粗暴,但几微秒就能削完一个单词,用极小的计算代价换取了立等可取的检索一致性。 把经过折叠、切分、打分过滤和削减词干后的几个干净词元收集起来,整套分词流水线才算大功告成。 而这套系统最优雅的地方,在于它在搜索时的完全对称。 当用户在前端输入 databases are jumping 发起搜索时,这串字符根本不会被直接拿去跟文章全文做比对。 在几毫秒之内,这串搜索词会在内存里把上面的流水线完整重跑一遍:大写字母转成小写,切成独立的词,算低 are 的权重,再用切纸刀把时态削掉。 原本的一长串句子,在瞬间也被压缩成了 databas 和 jump 这两个干净的零件。 紧接着,搜索引擎拿着这两个零件,直奔书末的倒排索引名单,翻出同时包含这两个词的页面列表,交叉比对并计算相关度分数,毫秒之间把结果呈现到屏幕上。 在当下的技术讨论里,大家总喜欢把目光投向万亿参数的神经网络和大模型,很少有人会驻足打量这套看似朴素的字符流水线。 但正是这套在后台默默运转的分词工序,把人类纷繁复杂的语言习惯,翻译成了机器能够以极低能耗高速运算的规整世界。它是全文检索技术历经几十年演进依然无法替代的物理支柱。

曝光 551 · 评论 1 · 点赞 2 · 书签 3

查看原帖