# search-engine
**Repository Path**: zgz521/search-engine
## Basic Information
- **Project Name**: search-engine
- **Description**: 搜索引擎技术与原理
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2023-03-03
- **Last Updated**: 2025-01-09
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 1 搜索引擎介绍
## 1.1 搜索引擎发展史
### 1.1.1 过去
1990 年年初,虽然万维网还没有诞生,但是搜索引擎的鼻祖已经诞生,它没有响亮的名字,但作为一个搜索工具,它已经可以发现各台 FTP 主机中的文件。再到后来属于搜索引擎的各个时代来临,包括信息的人工分类时代、相关性检索时代、网页链接价值检索时代。
* 人工分类时代
搜索引擎最初的样子有点像一个导航网站,里面对网站进行分类,通过目录导航。但是由于在当时的社会中,网站数量本身就比较少,用户和搜索引擎之间没有请求交互,所以此时的搜索引擎仅仅作为信息展示的载体。在人工分类时代,搜索引擎导航目录的整个构建过程都是通过人工完成的。
* 相关性检索时代
从这个时代开始,搜索引擎具备了和用户交互的能力。用户通过入口提交搜索请求。但是搜索引擎只考虑了用户搜索词与结果的相关程度,仅将文本相关度较高的结果返回给用户。虽然这个时代的搜索引警依然比较简单,但却是现代搜索引擎的开始,或者说是搜索引擎历史上的一次重要跨越。
* 网页连接价值检索时代
这个时代的搜索引擎已经开始分析网页链接给搜索引擎带来的价值,开启了搜索效果评估的新篇章。在最初的网页链接价值检索中,以Google 为代表的搜索引擎公司在搜索效果和商业价值上取得了巨大成功,引领着更多的搜索引擎公司挖掘网页链接的深层次价值。
### 1.1.2 现在
#### 1.1.2.1 特点
过去代表着历史,但历史终将是尘埃。随着互联网技术的不断发展,现在的搜索引擎已经是具备个性化、多样化、智能化、社会化的现代搜索引擎。
* 个性化
个性化是指根据用户特征提供定制化的搜索服务,其核心在于发现和理解用户的搜索行为,以及理解隐藏的用户特征价值,从而辅助用户进行搜索,使得个性化搜索满足用户的搜索需求。
* 多样化
用户的一次搜索不再是局限于网页文档的搜索,更多的信息类型(如音乐、图片、视频等)也会根据用户的搜索需求适机展示。
* 智能化
智能化是现代搜索引擎的基本特性之一,越来越多的搜索引擎从对搜索结果数据量的重视转向对搜索结果精准智能化的重视。智能化搜索通过与用户交互最大限度地了解用户意图,并给予最佳排序结果,甚至最终答案。
* 社会化
在搜索引擎集中研究网页之间关系、链接之间关系的时候社会化试图通过加入用户,研究在搜索过程中人与人、人与信息之间的关系用户可以通过共享的方式将知识分享出来,使得搜索结果更加准确,可以通过问答、共享百科、圈子讨论等方式实现。社会化依赖具体的社交平台,通过这些社交平台进行知识分享,然后搜索引擎将这些信息进行索引。社会化搜索是在Web 2.0中诞生的产品。在Web 2.0诞生不久,各类百科网站、博客网站、问答网站、交友分享社区层出不穷,经过多年的发展,它们将互联网信息进行了高度的总结和扩展,社会化搜索是搜索引擎利用 Web 2.0中的互联网产品对搜索服务和体验做出的一次改进。随着移动互联网的兴盛,社会化搜索愈加明显。
#### 1.1.2.2 分类
现在,无论是兴盛的移动搜索,还是日渐衰落的传统PC 搜索,从技术角度来看,它们的本质并没有太多改变。当前的搜索引擎已经发展出全网搜索引擎、垂直搜索引擎、元搜索引擎三大重要分支。
* 全网搜索引擎
网民使用最多的是全网搜索引擎,它在当前几乎成为整个互联网的入口,面向所有网民。各大搜索引擎厂商都曾在全网搜索引擎领域激烈竞争,目前它们正在利用人工智能技术不断提升全网搜索的价值。
* 垂直搜索引擎
垂直搜索引擎的数据限于特定的垂直领域。垂直领域针对某一行业或者细分领域,是全网搜索引擎的子集。垂直搜索引擎的采集数据源具备针对性,面向特定领域或特定人群使用,如学术、图片、视频搜索等.
* 元搜索引擎
元搜索是指在用户输入搜索词之后,根据其他多个搜索引擎合理组织出新的数据,从而返回组织后的结果。元搜索引擎没有自己的爬虫。
#### 1.1.2.3 搜索方式
当代搜索引擎的输入方式也发生了重要变化,不仅可以通过文本输入,还可以通过图片、语音等输入。尤其是语音输入,它是当代搜索引擎在移动互联网发展下的重要变化。语音识别技术的发展给语音搜索的发展创造了机会,从而产生了更加便捷的搜索方式,尤其是在移动搜索方面。
* 文本
* 语音
* 图片
#### 1.1.2.4 存储方式
* 索引库
索引库就类似于我们查字典时候的检索表,或者是图书馆的书目检索。Google的蜘蛛在抓取网页之后,就把这些页面放到对应的索引库里面。在用户搜索的时候,只需要到相应的检索库里面搜索相应的信息,而不是从所有的页面当中。
* 知识图谱
知识图谱是对常识、领域知识等建立的一种关系图结构。知识是对信息总结性的描述。搜索引擎之所以会逐步发展到知识图谱,是因为世界并不是由文字组成的,而是通过各个实体之间的相互作用形成的,因此搜索引擎从研究文本本转向研究自然界知识实体之间的关系。每个知识实体或者概念知识都在知识图中拥有全局唯一的标识。知识图谱目前主要在互联网大数据领域、图书情报领域(如引用分析、思维导图、复杂社会网络等领域)的发展比较迅速,这些领域本上是以互联网大数据领域为基础的。如果说未来的搜索引擎是机器人的大脑那么知识图谱就是这个大脑的知识库,任何决策都依赖此知识库。当前各大互联网公司已经在大力发展知识图谱相关项目,可谓战略意义重大
* 模型
大量图的图片识别模型,如百度、Google的按图搜索,语音识别模型,命名实体识别模型等用于语音识别、图片识别、实体识别等数学模型,bing中融入ChatGPT的大语言模型。
### 1.1.3 未来
* 智能伴侣
未来的搜索引擎应当是“人工智能”化的。当前的搜索引擎在个性化、智能化、垂直化、简单化、社交化等方面都比较成熟了,这必将使得人们对信息的追求发生改变,甚至希望搜索引擎成为每个人的“智能伴侣”。因此,搜索引擎是开启未来人工智能世界的一把钥匙。未来的输入方式也不再限于文本、语音、图片等输入,还可能包括体温、温度、情感等各类生活环境输入。输出方式也会变得多种多样,具备更强的交互性,如动态媒体输出、文字描述的动画展示等。
* 超级大脑
人类之所以比其他生物聪明,是因为人类善于“归纳、总结、学习”,而这些都是当下搜索引擎试图做得更好的方法。搜索引擎将会从一个互联网工具转变为人工智能平台,在移动互联网、物联网、PC互联网领域,搜索引擎都可以与各大产业相互结合,以帮助其解决问题。未来的机器人时代定会拥有以搜索引擎为基础的大脑控制系统。以目前来看,其他一般系统很难完成,原因在于机器人和人一样都在随时学习,而学习的必然途径就是搜索引擎,通过搜索引擎发现信息、拓展知识、自我学习。俗话说,“授人以鱼,不如授人以渔”,而从机器人的角度来看,这个“渔”不正是未来的搜索引擎吗?
## 1.2 搜索引擎发分类
### 1.2.1 通用搜索引擎
* google
* 百度
* bing
* 搜狗
### 1.2.2 垂直搜索引擎
* jobui-职友集
* law-star-律之星
* 鸠摩搜索
* SEMANTIC SCHOLAR
### 1.2.3 站内搜索
* 京东
* 淘宝
* 抖音
* 亚马逊
* 当当网
* 各类门户网站
## 1.3 开源搜索引擎
### 1.3.1 开源框架 Lucene
Lucene是一个开源的全文搜索引擎库,它提供了一个简单易用的API,可以用于构建自己的搜索引擎应用程序。Lucene最初由Doug Cutting创建,现在已经成为Apache软件基金会的顶级项目之一。
1. 高性能:Lucene采用倒排索引(Inverted Index)的方式来实现全文搜索,可以快速地进行文本索引和搜索,支持高并发、高吞吐量的搜索请求。
2. 可扩展性:Lucene提供了丰富的API和插件机制,可以方便地扩展和定制搜索引擎的功能和特性,例如支持多语言、自定义分词器、自定义评分器等。
3. 全文搜索:Lucene支持全文搜索、短语搜索、模糊搜索、通配符搜索、范围搜索、近似搜索等多种搜索方式,可以满足不同场景下的搜索需求。
4. 多种数据格式:Lucene支持多种数据格式的文本文件、HTML、XML、PDF、Word等,可以方便地处理和索引各种类型的文本数据。
5. 跨平台支持:Lucene是使用Java语言编写的,可以在各种操作系统和开发环境中运行和使用。
### 1.3.2 基于Lucene的开源搜索引擎
* Apache Solr
Solr是一个高性能,采用Java5开发,基于Lucene的全文搜索服务器。文档通过Http利用XML加到一个搜索集合中。查询该集合也是通过http收到一个XML/JSON响应来实现。它的主要特性包括:高效、灵活的缓存功能,垂直搜索功能,高亮显示搜索结果,通过索引复制来提高可用性,提供一套强大Data Schema来定义字段,类型和设置文本分析,提供基于Web的管理界面等。
* Elastic Search
ElasticSearch是一个基于Lucene构建的开源,分布式,RESTful搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。支持通过HTTP使用JSON进行数据索引。
* Index Tank
IndexTank是一套基于Java的索引-实时全文搜索引擎实现,它的设计分离了相关性标记和文档内容,因为相关性标记的生命周期和文档本身是不一样的,特别是在用户创建的内容的情况下,例如分享次数,Like按钮,+1按钮等等。
* Katta
Katta是一个可扩展的、故障容错的、分布式实施访问的数据存储。Katta可用于大量、重复、索引的碎片,以满足高负荷和巨大的数据集。这些索引可以是不同的类型。当前该实现在Lucene和Hadoop mapfiles,
* Bobo Search
bobo-browse是一用java写的lucene扩展组件,通过它可以很方便在lucene上实现分组统计功能。如说搜索电脑,可以得到cpu是intel的有几条命中记录,cpu是amd的有几条命中记录。
* Compass
Compass是一个强大的,事务的,高性能的对象/搜索引擎映射(OSEM:object/search engine mapping)与一个Java持久层框架。Compass包括:搜索引擎抽象层(使用Lucene搜索引荐),OSEM(Object/Search Engine Mapping)支持,事务管理,类似于Google的简单关键字查询语言,可扩展与模块化的框架,简单的API
* Summa
Summa是一种由java开发的,快速模块化和可扩展的搜索引擎。Summa有如下特点:综合搜索Summa能够同时访问许多不同的数据和资料来源,并以一个统一的接口公开模块化设计Summa搜索系统由一系列独立模块组成,这样使得它更简单容易地被维护和升级可扩展性Summa支持分布式架构而且能够按比例的扩大或缩小以处理任何数量的数据开放标准Summa基于现代web技术与标准,不包含任何私有代码或原理故障容错如果某单一数据资源或服务出错,Summa将会继续运行而不受出错部分限制
* Nutch
Nutch是一个用java实现的开源的web搜索引擎,包括爬虫crawler,索引引擎,查询引擎. 其中Nutch是基于Lucene的,Lucene为Nutch提供了文本索引和搜索的API.
### 1.3.3 其它开源框架
* Sphinx
它也是一个支持全文的免费 开源信息检索软件库,作为用 C++ 编写的独立服务器来实现,且在 Linux(RedHat、Ubuntu 等)、Windows、MacOS、Solaris、FreeBSD 和一些其他系统上运行。
* Xapian
Xapian 是另一个用 C++ 编写的开源搜索引擎库,其绑定允许使用Perl、Python 2、Python 3、PHP 5、PHP 7、Java、Tcl、C#、Ruby、Lua、Erlang、Node.js 和 R。
* Egothor
Egothor 是一个用 Java 编写的开源而高效的全文本搜索引擎。借助 Java 的跨平台特性,Egothor 能应用于任何环境的应用,既可配置为单独的搜索引擎,又能用于你的应用作为全文检索之用。
* DataparkSearch
DataparkSearch是一个用C语言实现的开源的搜索引擎. 其中网页排序是采用神经网络模型. 其中支持HTTP,HTTPS,FTP,NNTP等下载网页.包括索引引擎,检索引擎和中文分词引擎(这个也是唯一的一个开源的搜索引擎里有中文分词引擎).能个性化定制搜索结果,拥有完整的日志记录.
* Zettair
Zettair是根据Justin Zobel的研究成果为基础的全文检索实验系统.它是用C语言实现的. 其中Justin Zobel在全文检索领域很有名气,是业界第一个系统提出倒排序索引差分压缩算法的人,倒排列表的压缩大大提高了检索和加载的性能,同时空间膨胀率也缩小到相当优秀的水平. 由于Zettair是源于学术界,代码是由RMIT University的搜索引擎组织写的,因此它的代码简洁精炼,算法高效,是学习倒排索引经典算法的非常好的实例. 其中支持linux,windows,mac os等系统.
* Indri
Indri是一个用C语言和C++语言写的全文检索引擎系统,是由University of Massachusetts和Carnegie Mellon University合作推出的一个开源项目. 特点是跨平台,API接口支持Java,PHP,C++.
* Terrier
Terrier是由School of Computing Science,Universityof Glasgow用java开发的一个全文检索系统.
* Galago
Galago是一个用java语言写的关于文本搜索的工具集. 其中包括索引引擎和查询引擎,还包括一个叫TupleFlow的分布式计算框架(和google的MapReduce很像).这个检索系统支持很多Indri查询语言.
# 2 搜索引擎原理与技术
## 2.1 网络爬虫
### 2.1.1 HTTP协议
HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种用于在Web上传输数据的协议。它是Web应用程序通信的基础,允许客户端和服务器之间进行请求和响应,从而实现Web页面的传输和数据的交换。
* request 请求
| 字段 | 名称 | 说明 |
| ------- | -------- | ------------------------------------------------------------ |
| HOST | 主机 | url: http://imd.ccnu.edu.cn/xgdt/tzgg.htm
其中 imd.ccnu.edu.cn 为此url的主机地址 |
| path | 路径 | url: http://imd.ccnu.edu.cn/xgdt/tzgg.htm
其中 /xgdt/tzgg.htm 为此url的path路径 |
| headers | 请求头 | 要携带请求头,如Authorization认证信息和Cookie信息 |
| body | 请求数据 | 当使用POST请求时携带的表单数据或者文件流数据。 |
* headers 请求标头
| 字段 | 名称 | 说明 |
| ------------- | -------------- | ------------------------------------------------------------ |
| ContentType | 内容类型 | |
| method | 请求方法 | |
| user-agent | 用户浏览器类型 | 用于标识请求的客户端类型,如使用chrome浏览器则为:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 |
| referer | 来源 | 请求此网站时的上一个网址,则为由哪一个网址转入。 |
| cookie | cookie | Cookie用于记录当前浏览器绘画id,如sessionid,如果需要授权,某一个session会话授权后,下次请求使用Cookie携带,则认为为同一已授权绘画 |
| Authorization | 认证信息 | 需要携带的认证输入如:JWT等 |
* ContentType 内容类型
| 类型 | 说明 |
| ---- | ---- |
|application/x-www-form-urlencoded|表单编码方式|
|multipart/form-data|数据提交方式|
|application/json|JSON数据提交|
|text/xml|xml数据提交|
* method 请求方法
|类型|说明|
|----|----|
|get|获取资源|
|post|传输实体主体|
|delete|删除文件|
|put|传输文件|
|options|询问支持方法|
|head|获取报文头部|
* response 响应
| 响应 | 名称 | 说明 |
| ---------- | ---------- | -------------------------------------------- |
| statusCode | 响应状态码 | |
| headers | 响应标头 | 包含了响应的数据描述和数据类型及服务器的信息 |
| body | 响应内容 | 响应的主体内容 |
* statusCode 状态码
|状态码|解释|
|----|----|
|200|OK/正常|
|201|Created/已创建|
|202|Accepted/接受|
|400|Bad Request/错误请求|
|401|Unauthorized/未授权|
|403|Forbidden/禁止|
|404|Not Found/未找到|
|500|OK/正常|
* headers 响应标头
| 字段 | 名称 | 说明 |
| -------------- | ------------ | ------------------------------------ |
| Content-Length | 响应内容长度 | 包含了响应的内容的大小,以byte为单位 |
| Content-Type | 响应内容类型 | 响应内容如,html,js,文件等 |
| Cookie | Cookie | 分配本次对话的session及其它信息 |
* Content-Type 响应类型
| 响应内容值 | 说明 |
| ------------------------ | ---------------------------------- |
| text/html | 表示响应体是HTML文档 |
| text/plain | 表示响应体是纯文本 |
| application/json | 表示响应体是JSON数据 |
| application/xml | 表示响应体是XML数据 |
| image/jpeg | 表示响应体是JPEG格式的图片 |
| image/png | 表示响应体是PNG格式的图片 |
| application/octet-stream | 表示响应体是二进制流 |
| multipart/form-data | 表示响应体是由多部分组成的表单数据 |
| application/pdf | 表示响应体是PDF文档 |
| audio/mpeg | 表示响应体是MP3格式的音频文件 |
### 2.1.2 响应解析
* HTML 网页解析
| 提取内容 | 说明 |
| -------- | ------------------------------------------------------------ |
| 网页编码 | ``````
从charset可以看除网页使用的是utf-8还是gb2312 |
| 标题提取 | ```
华中科技大学信息管理学院```
提取网页的标题 |
| 链接提取 | ```【重要通知】华中师范大学信息管理学院筹备成立校友会公告(第三号)```
提取网页中a标签的href属性包含的链接 |
| 正文提取 | ```················```
提取网页中内容正文 |
* 非网页数据解析
| 数据类型 | 解析方法说明 |
| --------------------- | ------------------------------------------------------------ |
| pdf | java:使用程序包 org.apache.pdfbox
python:使用程序包 pdfplumber |
| office-word/excel/ppt | java:用程序包 org.apache.poi
python: 使用程序包 python-docx/openpyxl/python-pptx |
| txt | java:FileReader
python: 直接open |
### 2.1.3 正则表达式
正则表达式是一种用来匹配、查找和操作文本的强大工具。它被广泛应用于编程、文本编辑、搜索引擎、数据处理等领域。
正则表达式可以用来描述一类字符串的模式,例如一个电话号码、一个电子邮件地址、一个日期格式等等。通过使用正则表达式,可以快速有效地检查一个字符串是否符合某种特定的模式,并对符合条件的字符串进行操作。
正则表达式的作用包括:
1. 匹配和查找:通过正则表达式,可以快速地匹配和查找符合特定模式的字符串。
2. 替换和修改:通过正则表达式,可以快速地将符合特定模式的字符串替换为其他字符串,或者对符合条件的字符串进行修改。
3. 校验和验证:通过正则表达式,可以对输入的数据进行校验和验证,确保数据符合特定的格式和规范。
4. 分割和提取:通过正则表达式,可以将一个字符串分割成多个子字符串,或者从一个字符串中提取出符合特定模式的子字符串。
总之,正则表达式是一种强大的文本处理工具,可以帮助我们更加高效地处理和操作文本数据。
* 正则表达式提取所有a链接的URL
```
.*?
```
* 正则表达式提取标题
```
(.*?)
```
* 正则表达式提取文档编码
```
```
* 正则表达式提取正文
```
(.*?)
```
### 2.1.4 xpath
XPath(XML Path Language)是一种用于查询和选择XML文档中节点的语言。它是一种基于树形结构的路径表达语言,通过在XML文档中指定路径来定位节点,从而实现对节点的查询和选择。
XPath可以用来定位XML文档中的任何节点,包括元素、属性、命名空间、注释等。XPath使用路径表达式来描述节点的位置关系,路径表达式由若干路径段构成,每个路径段由节点名称、谓语、轴等组成。
以下是一些常用的XPath路径表达式:
- /:表示根节点,例如/Bookstore表示文档的根节点Bookstore。
- //:表示从根节点开始,查找所有符合条件的节点,例如//Book表示查找所有名为Book的节点。
- .:表示当前节点,例如./Book表示当前节点下的Book节点。
- ..:表示当前节点的父节点,例如../Book表示当前节点的父节点下的Book节点。
- @:表示属性,例如@price表示查找所有带有price属性的节点。
- []:表示谓语,用于筛选符合条件的节点,例如Book[author='John Doe']表示查找所有作者为John Doe的Book节点。
XPath可以用于各种编程语言中,例如Java、Python、JavaScript等。在XML文档处理、Web开发、数据抓取等领域都有广泛的应用。
* xpath提取所有a链接的URL
```
//a/@href
```
* xpath提取标题
```
//title/text()
```
* xpath提取文档编码
```
//meta/@charset
```
* xpath提取正文
```
//body
```
### 2.1.5 数据存储
数据存储是将爬虫获取到的数据存储于磁盘文件或者数据库中。
* python文本文件存储
```
fileName = "example.txt"
content = "Hello, World!"
with open(fileName, 'w', encoding='utf-8') as f:
f.write(content)
f.close()
```
* java文本文件存储
```
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class TextFileWriter {
public static void main(String[] args) {
String fileName = "example.txt";
String content = "Hello, World!"; // 要写入文件的内容
try {
BufferedWriter writer = new BufferedWriter(new FileWriter(fileName));
writer.write(content);
writer.close();
System.out.println("文件写入成功!");
} catch (IOException e) {
System.out.println("文件写入失败:" + e.getMessage());
}
}
}
```
* 数据库存储
```
insert into 表名(字段1, 字段2, ...) values(value1, value2, ...)
```
## 2.2 倒排索引
倒排索引(Inverted Index)是一种用于快速查找文档的数据结构,常用于全文检索引擎中。它将每个单词(或词组)在文档集合中出现的位置映射为一组文档列表,这些文档中包含了这个单词(或词组)。
具体地说,倒排索引由两部分组成:单词表和倒排列表。单词表是一个按字典序排列的单词列表,每个单词与一个唯一的编号对应。倒排列表则是每个单词出现的位置清单,它包含了所有出现该单词的文档的编号列表和出现位置。例如,假设有三个文档,它们包含的单词如下:
- 文档1:the quick brown fox jumps over the lazy dog
- 文档2:the quick brown fox jumps over the lazy dog again
- 文档3:the quick brown fox runs faster than the lazy dog
那么对于单词"quick",它的倒排列表就会包含文档1、文档2和文档3的编号以及在文档中出现的位置:
```
quick: {1:[1], 2:[1], 3:[1]}
```
这个倒排列表表示单词"quick"在文档1、文档2和文档3中均出现过,分别在文档1的第1个位置、文档2的第1个位置和文档3的第1个位置出现过。通过这样的方式,倒排索引可以快速地定位包含指定单词的文档,从而实现高效的全文检索功能。
倒排索引的优点是可以快速地进行全文检索,能够处理大规模的文本数据,并且支持词语的模糊匹配和相关性排名等功能。它在现代搜索引擎、信息检索系统和文本分析工具中得到广泛应用。
### 2.2.1 索引结构
倒排索引是搜索引擎算法中极为重要的数据结构之一,它可以在极短的时间内定位文档的具体位置。
* 示例句子
| 文档编号 | 句子内容 |
| -------- | ---------------- |
| 文档-1 | 你好,搜索引擎 |
| 文档-2 | 搜索引擎技术窥视 |
| 文档-3 | 你好,搜索技术 |
| ··· | ··· |
* 正向索引
| 文档编号 | 关键词及位置 |
| -------- | ----------------------------------- |
| 文档-1 | 您好(0),搜索引擎(2) |
| 文档-2 | 搜索引擎(0),技术(4),窥探(6) |
| 文档-3 | 你好(0),搜索(2),技术(4) |
| ··· | ··· |
* 倒排索引
| 关键词 | 文档编号 |
| -------- | ------------------------ |
| 你好 | 文档-1(0),文档-3(0) |
| 搜索引擎 | 文档-1(2),文档-2(0) |
| 技术 | 文档-2(4),文档-3(4) |
| 窥探 | 文档-2(6) |
* 索引存储结构
| 词语 | 文档 | 位置集合 | 权重 |
| ----- | ---- | -------- | ---- |
| 词语1 | 文档 | 位置集合 | 权重 |
| 词语2 | ··· | ··· | ··· |
| 词语n | 文档 | 位置集合 | 权重 |
权重是指词语对于当前文档的重要性,单词的权重通过TF-IDF或TextRank 计算得出。
* TF-IDF
$$
\text{tf-idf}(t, d, D) = \text{tf}(t,d) \times \text{idf}(t, D)
$$
其中,$t$表示单词(term),$d$表示文档(document),$D$表示文档集合。$\text{tf}(t,d)$是单词$t$在文档$d$中的词频(term frequency),$\text{idf}(t,D)$是单词$t$的逆文档频率(inverse document frequency)。
具体地,$\text{tf}(t,d)$可以表示为:
$$
\text{tf}(t,d) = \frac{n_{t,d}}{\sum_{k}n_{k,d}}
$$
其中,$n_{t,d}$表示单词$t$在文档$d$中出现的次数,$\sum_{k}n_{k,d}$表示文档$d$中所有单词的出现次数之和。
而$\text{idf}(t,D)$可以表示为:
$$
\text{idf}(t,D) = \log\frac{N}{n_t}
$$
其中,$N$表示文档集合中的文档总数,$n_t$表示包含单词$t$的文档数量。$\log$可以是任意一个对数,通常使用自然对数(以$e$为底)或者以$2$为底的对数。
TF-IDF的计算结果表示了一个单词在文档集合中的重要性,单词在单个文档中出现的次数越多,它在该文档中的权重越大;而单词在整个文档集合中出现的文档数量越少,它在文档集合中的权重越大。
* TextRank
TextRank是一种基于图的文本摘要和关键词提取算法,它利用图中节点之间的连接和权重来表示文本中的句子或单词之间的关系,并通过计算节点的PageRank值来确定其重要性。TextRank算法最初由Mihalcea和Tarau于2004年提出,后来被广泛应用于自然语言处理领域。
TextRank算法的主要思想是将文本表示成一个无向带权图,其中图的节点表示文本中的句子或单词,边表示节点之间的关系,边权重表示节点之间的相似度或相关性。然后通过迭代计算节点的PageRank值,得到文本中每个句子或单词的重要性分数,从而实现文本摘要和关键词提取。
TextRank算法的公式如下:
> 共现矩阵M:
$$
M_{i,j}=\sum_{s=1}^{n}\sum_{t=1}^{n}\frac{f(w_{i,s},w_{j,t})}{\sum_{u=1}^{n}\sum_{v=1}^{n}f(w_{u,s},w_{v,t})}
$$
其中,$f(w_{i,s},w_{j,t})$表示单词$w_i$和$w_j$在文本中的共现次数,$n$表示文本中单词的总数。
> 相似度矩阵W:
$$
W_{i,j}=\frac{M_{i,j}}{\sqrt{\sum_{k=1}^{n}M_{i,k}}\sqrt{\sum_{k=1}^{n}M_{j,k}}}
$$
其中,$\sqrt{\sum_{k=1}^{n}M_{i,k}}$和$\sqrt{\sum_{k=1}^{n}M_{j,k}}$分别表示单词$w_i$和$w_j$在文本中出现的总次数的平方根。
> PageRank值:
$$
PR(V_i)=\frac{1-d}{N}+d\sum_{V_j\in In(V_i)}\frac{w_{ji}}{\sum_{V_k\in Out(V_j)}w_{jk}}\cdot PR(V_j)
$$
其中,$PR(V_i)$表示节点$V_i$的PageRank值,$N$表示节点总数,$d$为阻尼系数,通常取值为0.85,$In(V_i)$表示指向节点$V_i$的所有节点集合,$Out(V_j)$表示从节点$V_j$指向的所有节点集合,$w_{ji}$表示从节点$V_j$到节点$V_i$的边权重。
* 二级索引
对于一些包含大量文档和单词的数据集,倒排索引的大小可能会非常大,导致查询速度变慢。此时,建立二级索引可以有效地提高查询效率和减少内存占用。
具体来说,二级索引是指在倒排索引的基础上建立的一种索引结构,用于加速查询过程。二级索引通常是基于单词的首字母或前几个字符构建的,以便快速定位包含某个前缀的单词。例如,对于单词列表["apple", "banana", "cat", "dog", "elephant"],可以建立一个以字母为索引项的二级索引,其中"b"指向"banana","c"指向"cat"和"dog","e"指向"elephant",以此类推。
通过建立二级索引,可以将查询范围缩小到包含相同前缀的单词列表中,从而减少倒排索引的查询次数和内存占用。在实际应用中,二级索引往往可以大幅提高查询效率,并且可以通过调整前缀长度等参数来平衡查询速度和内存占用。建立二级索引的方法有两种:基于哈希表及B^+^ 树。
* 基于哈希表的二级索引
如图2-1 所示为基于哈希表建立的二级索引。通过将哈希表加载到内存中,以词语作为哈希表的键。值为单词在倒排索引中的位置信息,用户的搜索请求先通过二级索引找到对应词语倒排索引存储的起始和结束位置,再通过起始和结束位置读取硬盘上对应的倒排索引即可。B^+^树索引则类似于数据库中的文件索引,针对海量数据级索引的构建采用 B^+^树的方式更加合理。
图2-1 基于hash表建立的二级索引
* 基于B^+^树的二级索引
B+树,它是一种多路平衡查找树,具有高效的插入、删除和查找操作。B+树的节点包含多个关键字和指向子节点的指针,其中叶子节点存储实际的数据记录,而非叶子节点只用于索引和导航。
其中每个节点表示一个单词或短语,节点关键字是单词或短语的词典编码或哈希值,指针指向文档ID或包含该单词或短语的文档列表。由于搜索引擎需要支持高效的模糊匹配、短语查询和排序等功能,因此B+树的设计和实现需要考虑多种因素,如节点大小、插入和删除算法、搜索算法和磁盘IO次数等。
下面是搜索引擎索引中B+树的一些特点和优点:
1. 多路平衡查找树:B+树的每个节点可以包含多个关键字和指向子节点的指针,可以高效地支持范围查询和排序等操作。
2. 叶节点存储实际数据:B+树的叶子节点包含实际的数据记录,可以减少磁盘IO次数,提高查询效率。
3. 顺序访问性能好:B+树的节点按照关键字顺序存储,可以高效地支持顺序访问,例如支持磁盘预读等操作。
4. 支持高效批量插入和删除:B+树的插入和删除算法可以高效地支持批量操作,例如通过分裂和合并节点等方式来减少磁盘IO次数。
总之,B+树是搜索引擎索引中常用的一种数据结构,它具有高效的插入、删除和查找操作,可以支持高效的模糊匹配、短语查询和排序等功能,是搜索引擎实现高效索引和快速查询的重要基础。
### 2.2.2 构建过程
1. **提取文档**。将文档从数据库中提取出来,文档包括网页标题、网页正文、网页链接、网页作者等,均为爬虫采集的结构化数据。
2. **文档分词**。对文档进行分词处理,分词过程包括停用词移除、中文分词、英文分词等,并且建立正向索引。
3. **词权重计算**。对正向索引中的词进行权重计算。
4. **索引排序**。根据正向索引生成倒排索引,并对倒排索引中的内容进行排序,包括对词排序和对文档列表排序。
为了增强倒排索引的检索效率,除对结构进行优化之外,还需要对其各个数据项进行排序。在二级索引中,记录了词语的索引起始位置和结束位置。然而并非所有词语在构建索引的过程中,起始位置和结束位置都是该词语的倒排信息,中间可能夹杂着其他词语,词语在倒排上的相互交叉现象比较多见。即使所有词语的倒排索引都在一个区域内,但在这个区域内词语的权重不一定是有序的。因此,索引排序包含词语排序和文档排序两方面。
* 词语排序是指将索引中相同的词语集中放置在一个区域内,将零散的集合变为有序的集合,便于数据检索,而不必遍历整个倒排索引。
| 排序前 | 排序后 |
| 词语2 | 文档|位置集合|权重 | 词语1 | 文档|位置集合|权重 |
| 词语3 | 文档|位置集合|权重 | 词语2 | 文档|位置集合|权重 |
| 词语4 | 文档|位置集合|权重 | 词语3 | 文档|位置集合|权重 |
| 词语2 | 文档|位置集合|权重 | 词语4 | 文档|位置集合|权重 |
* 文档排序。文档排序是指针对具体词语映射的文档集合,将每组词语的倒排索引内部对应文档集合按照权重从高到低进行排序,在同一词语中,词语所在文档权重较高的文档出现在前面。
| 排序前 | 排序后 |
| 文档1 | 权重:0.17 | 文档2 | 权重:0.58 |
| 文档2 | 权重:0.58 | 文档3 | 权重:0.36 |
| 文档3 | 权重:0.36 | 文档1 | 权重:0.17 |
5. **压缩入库**。构建二级索引并对索引数据库进行压缩,将压缩之后的倒排索引存入数据库中。
结合工程应用角度,倒排索引非常庞大且远远超过网页本身大小,在不影响快速检索的情况下,需要对倒排索引进行压缩,以减少内存和硬盘开销。可以通过文档编号差值化、词语哈希化、文档拆减等方式进行压缩。
* 文档编号差值化
文档编号差值是倒排列表中相邻的两个倒排索引项文档编号的差值。在权值有序的文档中,文档编号差值可能存在负值。原始的 5个文档编号分别是 200、208、196、270和150,通过编号差值计算,在实际存储时就转变成了 200、8、-4、70和-50。
* 词语哈希化或哈夫曼编码
* 文档拆减。顾名思义,文档拆减就是减少文档数量。有两种文档拆减方式。一种方式是通过权重极低值拆减。在现代搜索引擎中,用户访问的页面量是极少的,即使返回 100 条搜索结果,用户也许只关心靠前的20 条结果对于词语对应的权值极低的文档,可以直接移除,因为无论采用何种搜索算法都无法使其排序进入 100条搜索结果范围内。具体移除的数值取决于返回的结果数量。另一种方式是通过过滤词拆减。在索引过程中,并不是所有的词语都需要建立倒排索引的,对于某些词语,如“的”“啦”“a”“as”“of”“the”等(俗称“停用词”),并不需要建立倒排索引。停用词一般属于文档中基本上都会用到的词语,它对应的文档数量集合庞大,然而对实际搜索排序却没有实际意义。这类词语在进行中文分词的时候应尽量过滤掉,以免影响搜索结果和搜索性能。
### 2.2.3 更新策略
倒排索引是一种静态数据集合,而互联网上庞大的信息却是动态的。搜索引擎在理论上存在两种索引更新策略,分别是完全更新策略和合并更新策略。
* 完全更新策略。几乎利用索引版本将过去的索引完全替换掉,无论索引中发生变化的内容是多还是少,用新索引全部替代旧索引。这种更新策略简单、彻底,对于中小型索引而言是可取的,但是对于十亿级的网页索引进行完全更新,显得不够现实,代价过大。
* 合并更新策略。将新增网页的索引与已经存在的网页的索引进行合并这种更新策略的可行性比较高,性能代价较小;但存在的问题是,如果不是新增网页,而是对已经存在的网页的索引进行更新,则将会付出较大的代价。因为某篇文章的更新会导致以前文档中的词语需要在索引中被移除,然后插入新的索引值。
对于索引的更新,难点在于索引数据删除。更新实质上是将上一次该文栏的数据删除,然后插入新索引数据。从用户体验的角度来讲,一方面,搜索引警索引到的数据应该尽可能快地被搜索到,而不是在一天甚至一周之后才被搜索到;另一方面,已经被删除的页面,其链接已经无效,应该禁止该链接被检索出来,即使与用户的搜索具备一定的相关性,但是由于链接已经失效,用户点击也无法正常打开页面,从而严重影响用户体验。
### 2.2.4 索引存储
## 2.3 搜索服务
搜索服务器是整个在线搜索服务中非常核心的模块,关系到能否提供稳定安全、可靠、及时的搜索服务。搜索服务器接收到搜索请求之后,首先通过缓存服务器查看是否命中缓存,如果命中缓存则直接返回,否则通过索引服务器获取索引信息,然后根据索引信息获得相应的知识图谱信息及网页文档信息,最终组合成搜素结果,返回给请求源。搜索服务器不仅仅是对用户搜索的代理中专,还具备对用户请求数据的初步分析能力。
### 2.3.1 搜索语法
* 基于“intitle”的标题式搜索
搜索关键词仅限于在标题中搜索。标题般是网站主题的表现,把搜索词限定在标题中,在某些时候能够获得较好的搜索效果。搜索方式如“intitle:马云”。
* 基于“inurl”的链接内容搜索
搜索关键词仅限于在链接中搜索。链接虽然在一般情况下没有实质信息,但是对于某些特定领域,针对 URL进行搜索会得到较好的结果。例如,在搜索下载资源时,搜索方式如“inurl:.pdf”。
* 基于“site”的限定站点搜索
搜索结果仅限于指定一个或多个站点例如,“博士招生 site:imd.ccnu.edu.cn”。
* 基于“filetype”的限定文件类型搜索
指定搜索结果的文件类型。网上的媒体多种多样,有时候用户希望是特定媒体资源,如PPT之类。例如.“搜索技术filetype:ppt”。
* 基于双引号(“ ”)的文本包含搜索
通过对关键词进行双引号约束指定搜索结果返回的页面必须包含双引号中出现的所有词,顺序也必须匹配例如,搜索“"马云阿里巴巴"”。
* 逻辑与
使用 AND 语法,例如”兰州 AND 美食“
* 逻辑或
使用 OR 语法,例如”兰州 OR 美食“
* 逻辑非
使用 NOT 语法,例如”兰州 AND 美食 NOT 兰州拉面“
### 2.3.2 文本纠错
用户在搜索时,难免存在将搜索词输错的情况,即使在智能提示的作用下也会如此。因此,能够智能进行文本纠错也是对提示搜索效果而言非常重要的-环。文本纠错包含中文文本纠错和英文文本纠错。
* 中文文本纠错
例如,当用户试图搜索“彩虹桥”时,却输入成“彩红桥”。矫正用户的错误搜索关键词,需要从判定和纠正两方面来进行。整个过程可以采用 N-Gram语言模型对中文文本进行纠错。
N-Gram 语言模型基于这样一种假设:第n个字的出现仅仅与前面第n个字相关。如果一个搜索词$Q$包含$n$个字,即 $Q= \{w_1,w_2,w_3,...,w_n\}$,判定这个搜索词是否合理,可以转换为如下形式:
$$
P(q)=P(w_1,w_2,w_3,\cdot\cdot\cdot,w_n)=P(w_1) \times P(w_2|w_1) \times \cdot\cdot\cdot \times P(w_n|w_1,w_2,...w_{n-1})
$$
$P(q)$即搜索词$Q$是否合理的概率。但是如果$n$值过大,则在计算前面$n$个字时会造成矩阵过于稀疏。因此,简化N-Gram 语言模型至 Bi-Gram模型二元语言模型,即一个字 $w_i$的出现仅依赖它的前一个字 $w_{i-1}$。上述公式可简化为如下形式:
$$
P(q)=P(w_1,w_2,w_3,\cdot\cdot\cdot,w_n)=P(w_1) \times P(w_2|w_1) \times P(w_3|w_2) \times \cdot\cdot\cdot \times P(w_n|w_{n-1})
$$
而
$$
P(w_i|w_{i-1})=\frac{total(w_i|w_{i-1})}{total(w_{i-1})}
$$
其中,$total(w_{i-1})$表示字$ w_{i-1}$ 出现的总次数,$total(w_i|w_{i-1})$表示字$w_{i-1}$ 与字$w_i$相邻出现的总次数。$total(w_{i-1})$与 $total(w_i|w_{i-1})$均是通过语料库训练而来的。这里的语料库不是由人工收集的,而是由所有用户日积月累的搜索词组成的,利用用户积累的搜索词可以发现新词汇及网络词汇,如“云备胎”。
因此,基于上述模型,可以以部分数据集作为示例验证“彩红桥”是一个错误的搜索词。通过语料库统计出的 $total(w_{i-1})$ 如下表所示:
| 彩 | 红 | 桥 |
| ---- | ---- | ---- |
| 9620 | 3658 | 5564 |
$total(w_i|w_{i-1})$如下表所示:
| | 彩 | 红 | 桥 |
| ---- | ---- | ---- | ---- |
| 彩 | 162 | 99 | 269 |
| 红 | 3 | 152 | 23 |
| 桥 | 2 | 1 | 167 |
通过$total(w_{i-1})$、$total(w_i|w_{i-1})$计算$P(w_i|q_{i-1})$如下表所示:
| | 彩 | 红 | 桥 |
| ---- | ---------------------- | ---------------------- | -------------------------------------------- |
|彩|$162/9620\approx0.017$|$99/620\approx0.011$|$269/9620\approx0.028$|
|红|$3/3685\approx0.001$|$452/3685\approx0.124$|$23/658\approx0.006$|
|桥|$2/5\approx0.0$|$1/5564\approx0.0$|$362/5564\approx0.065$|
因此,综上计算$P(q)=9620/(9620+3685+5564)\times0.011\times0.006\approx0.000034$
计算出的值虽然非常小,但还不能判定它不是一个无误的搜索词。在进行语料库训练时还需要训练出一些值,即搜索词可能性最小阈值$k_i$,其中$i$表示搜索词的字符个数,不同字符个数的搜索词$k$值也不一致,在工程中$i$值最大设定为255。如果$P(q) 更多算法与实现请参考:[中文文本纠错算法原理与实现1](https://blog.csdn.net/demm868/article/details/107096661) [中文文本纠错算法原理与实现2](https://zhuanlan.zhihu.com/p/40806718?utm_id=0)
* 英文文本纠错
在用户输入过程中,不仅有中文,还有英文,英文文本纠错与中文文本纠错一样,对搜索引擎而言非常重要。英文文本错误包括单词拼写错误和单词输入错误两种情况。
1. 单词拼写错误。这是常规错误,表示单词本身不存在。例如,将“Gift”输入成“Giftt”,是因为不小心多输入了字母“f”而导致的错误。根据用户搜索历史记录统计,在用户的英文单词拼写错误中,超过一半是单词错误。而在单词错误中,大部分是因为输错了一个字母,几乎所有的错误都没有输错超过两个字母的情况。因此,通过字典匹配出错误的单词与字典中单词的编辑距离小于等于2的所有单词即可。例如,用户输入关于单词A和B的搜索,分别记作 $W_A$、$W_B$,发现 $W_B$为单词错误,通过文本编辑距离计算,得到与 $W_B$的编辑距离不超过2的词$W_{B,1},W_{B,2},\cdot\cdot\cdot,W_{B,n}$,需要确定最佳建议单词,则需要计算$ P(w_A|w_{B,i})$,即$w_A$出现在$w_B$的候选词$w_{B,1},w_{B,2},\cdot\cdot\cdot,w_{B,n}$前面的概率,将最大的概率确定为最佳建议单词。
2. 单词输入错误。用户在输入过程中的确输错了词,但是输错的词依然是一个合法的单词,例如,将“riot”输入成“rive”,两者都是字典中存在的单词,但是意义却完全不一样。非单词错误在英文中属于难度较大的错误,因为句子中输入的每个单词都有可能是错误的。因此,要解决此问题,需要关联文字的上下文。对于一个给定输入的句子$S=\{w_1,w_2,w_3,\cdot\cdot\cdot,w_n\}$,需要为里面的每个单词确定其可能的候选词,候选词通过字符串相似度获得,如表 9-10 所示。表 9-10 输入词与候选词的对应关系输入词根据下表可知
| 输入词 | 候选词 |
| :---------------: | :---------------------------------------- |
| $w_1$ | $w_{1,1},w_{1,2},w_{1,3},\cdot\cdot\cdot$ |
| $w_2$ | $w_{2,1},w_{2,2},w_{2,3},\cdot\cdot\cdot$ |
| $\cdot\cdot\cdot$ | $\cdot\cdot\cdot$ |
| $w_n$ | $w_{n,1},w_{n,2},w_{n,3},\cdot\cdot\cdot$ |
已经将问题实质转换为候选词之间的组合概率问题,可以通过维特比算法直接计算,与中文文本的处理方式极其类似。
> 更多算法请参考 [纠错算法](https://blog.csdn.net/joker_man1/article/details/129800994)
### 2.3.3 网页排序
#### 2.3.3.1 网页评价算法
* 基于PageRank的网页重要性
PageRank 是著名的网页链接评价算法,用以体现网页链接的重要性。其思想是通过网页之间的投票得出网页在互联网中的重要性,投票方式则是链接的链向关系。
> [PageRank算法](https://zhuanlan.zhihu.com/p/137561088)
* 基于HITS算法的网页权威性
网页权威性考察的是网页是否具备高质量,以及网页在互联网中的口碑情况。网页越权威,则信息越可靠。
> [HITS网页权威性算法](https://blog.csdn.net/rubinorth/article/details/52231620)
* HillTop算法
HillTop算法实质上是对 PageRank 及HITS算法的综合,依然沿用PageRanl算法的链接分析法,同时将 HITS 算法中以主题为中心的思想融合,类似于如果两个或两个以上主题相关性较强的网站同时链接到另一个网站,则认为该网站在该主题性下可靠性更高。这样做可以有效减少网站之间随意无关链接导致的链接权重误差。
> [HillTop算法](https://cloud.tencent.com/developer/article/1981952)
#### 2.3.3.2 网页作弊评价
* 基于链接的网页作弊
之所以会对链接作弊,是因为链接是网页本身价值的一种体现,通过有目的地构建网页之间的相互关系,从而提升页面在搜索引擎中的重要性评分。
* 基于内容的网页作弊
内容是搜索相关性参考的重要指标之一,作弊者通常会将一些无关正文的内容放置到网页中,并且伪装成热点词汇。
* 基于隐藏信息的网页作弊
隐藏信息的方式是欺骗搜索引擎和浏览器的方式,通过 CSS 样式控制页面内容是否显示,因此作弊者可以为所欲为地新增很多内容,但是这部分内容对于该页面没有相关性,更有作弊者甚至通过页面跳转的方式进行作弊。
* 作弊算法
> TrustRank 是斯坦福大学和 Yahoo!的一项联合研究,利用这种方法对网页进行信任值评分,需要事先确定一些信任值较高的站点作为种子网页,通过种子网页的外链,将信任值不断向外播种,直到最后对所有的网页进行信任值汇总,通过信任值的方式排除掉信任值较低的站点。TrustRank 虽然在理论上设计得比较合理,但在工程应用中却带来了新的问题。一方面,对于刚刚新建的站点,即使内容保持更新频率较高,而且均为原创,也很难拿到较高的排名;另一方面,在搜索结果中虽然大部分是权威站点,但其内容不完全是原创的。
> 与 TrustRank 相反,先找到一堆作弊的网页,然后通过这些网页的外链分析其不信任评分。不同之处在于,TrustRank 的信任分值是正向传递的,而不信任传播模型的信任分值是反向传递的。例如,网页 P已经被确定为不信任网页,则P 外链中的网页 P,比链向 P的网页 P;的信任值高这就是反向传递。
> Google 通过 Panda 算法解决内容农场导致的问题,Panda算法的核心思想在于对页面内容、链接概况及网页在搜索结果中的点击情况进行综合考虑分析。而百度则通过用户对网页的评价方式惩罚包括采用内容农场方式运营在内的各类垃圾网站。
> 可以分析作弊网页的特征,例如,分析网页的链入链接和外部链接的数量分布情况及网页外链中的死链与活链的比例,其中,死链是指服务器的地址已经改变或者网页地址已经无法访问,活链则表示可以正常访问的链接;还可以分析作网页的 URL 长度分布、语句特征等。
### 2.3.4 个性化搜索
* 用户特征模型
用户特征模型通过用户注册信息和用户搜索与点击日志建立,其中包括用户年龄、性别、学历、爱好,通过将特征模型数值化,归一化到0~1之间的值。针对每个用户选取N个特征,设定个特征的值在区间[0,1]上。例如,性别越接近1表示越可能是男性,越接近0表示越可能是女性;学历越接近 1,表示学历程度越高:某爱好越接近1,表示此爱好越强烈。
* 词语特征模型
词语特征模型是在历史搜索日志中不断积累和分析出的结果。它表示每个词语与用户模型的相关程度,与用户模型存在相同的 N个特征。
### 2.3.5 图片搜索
* 基于内容的图片搜索
基于内容的图片搜索(Content-Based Image Retrieval)的主要思想是基于图片本身所拥有的信息,在给定查询图片的情况下进行图片搜索,是“以图搜图的应用搜索。通过图片搜索获得相似图片,主要采用感知哈希算法(PerceptualHash Algorithm)实现,核心思想是对每张图片构建唯一指纹,图片中指纹越相近,则说明图片相似度越高。
* 基于文本的图片搜索
基于文本的图片搜索的基本思想是:首先获取图片附近的文本信息,这些文本信息和网页搜索的文本信息一样,被建立倒排索引,然后通过对倒排索引的使用获得对应的图片信息。基于文本的图片搜索的实质与网页搜索类似,都是对文件建立相关索引,只不过网页搜索对应的是文档集合,而图片搜索对应的是图片集合。
## 2.4 知识图谱
### 2.4.1 知识图谱概述
#### 2.4.1.1 开放知识图谱
* Freebase-混合网络数据
Freebase是一个由元数据组成的大型合作知识库,内容主要来自其社区成员的贡献。它整合了许多网上的资源,包括部分私人wiki站点中的内容。Freebase致力于打造一个允许全球所有人(和机器)快捷访问的资源库。它由美国软件公司Metaweb开发并于2007年3月公开运营。2010年7月16日被谷歌收购。
* DBpedia-维基百科
DBpedia 是一个很特殊的语义网应用范例,它从维基百科(Wikipedia)的词条里撷取出结构化的资料,以强化维基百科的搜寻功能,并将其他资料集连结至维基百科。透过这样的语义化技术的介入,让维基百科的庞杂资讯有了许多创新而有趣的应用,例如手机版本、地图整合、多面向搜寻、关系查询、文件分类与标注等等。DBpedia 同时也是世界上最大的多领域知识本体之一,也是 Linked Data 的一部分,美国科技媒体 ReadWriteWeb 也将 DBpedia 选为2009 年最佳的语义网应用服务。
目前 DBpedia包含了超过260万个实体,且每个实体具 有唯一的全局标识符.以此为基础,越来越多的数据 发布者 将 自 己 的 数 据 通 过 SameAs 关 系 链 接 到 DBpedia资源,使 DBpedia一定程度上成为多类型 数据组织的中心.目前,围绕 DBpedia的互联网数据 源网络提供了约47亿条信息,涵盖地理信息、人、基 因、药物、图书、科技出版社等多个领域.
* Data.gov-美国政府官方网站
Data.gov是美国政府官方的开放数据平台,旨在为公众、研究者、企业和政府等各方提供方便、开放、可重用的政府数据资源。Data.gov平台于2009年上线,由美国总统奥巴马签署的《开放政府指令》推动。
Data.gov平台提供了数百万份来自联邦及其分支机构的开放数据资源,包括数据集、API、工具、应用程序等。这些数据涵盖了各个领域,如能源、气象、教育、卫生、国土、交通等,可以用于研究、分析、开发应用等多种用途。
Data.gov平台的目标是促进政府透明度、参与度和创新性,让公众更好地理解政府的工作和决策,同时也为企业和研究机构提供了更多的创新机会。Data.gov平台的数据资源可以通过搜索、浏览和API等多种方式获取,并且大部分数据资源都是免费的。
除了提供数据资源外,Data.gov平台还提供了社区交流、培训、数据竞赛等各种服务,以鼓励公众和企业更加活跃地参与到开放数据的开发和利用中来。
* wiki-links-维基百科
Wiki-Links是维基百科中的一个特性,它是指维基百科页面中出现的链接。这些链接可以是指向其他维基百科页面的链接,也可以是指向外部网站或文档的链接。
Wiki-Links的作用是让读者可以方便地查看相关主题或者深入了解相关信息。当读者点击一个Wiki-Link时,会跳转到对应的页面。维基百科的编辑者们也常常使用Wiki-Links来链接到相关主题,以帮助读者更好地理解内容。
Wiki-Links的特点是开放性和互联性。维基百科的编辑者们可以自由地添加、编辑和删除Wiki-Links,从而使维基百科的内容更加丰富和完整。同时,Wiki-Links也可以链接到其他网站或文档,使维基百科与其他资源之间形成了互联互通的网络。
Wiki-Links的发展也反映了维基百科的发展历程。随着维基百科的不断壮大,Wiki-Links的数量也越来越多,链接的内容也越来越丰富。同时,维基百科也在不断改进Wiki-Links的排版和设计,以提高用户体验和可用性。
* Wolframe Alpha-计算知识
Wolfram Alpha是一款基于互联网的计算知识引擎,由物理学家和计算机科学家Stephen Wolfram开发。它可以回答各种数学、科学、工程、金融、历史、文化等领域的问题,并提供图表、图像、数据等多种形式的回答。
Wolfram Alpha的特点是它不仅仅是一个搜索引擎,而是一个计算知识引擎。它可以将输入的问题转化为可计算的形式,并通过自己的算法和数据库来计算和推理答案。这使得Wolfram Alpha可以回答更加复杂和具有挑战性的问题,例如微积分、代数、统计分析、物理学等领域的问题。
Wolfram Alpha的另一个特点是它具有高度的个性化和交互性。用户可以通过自然语言或者数学符号输入问题,Wolfram Alpha会自动识别并提供相应的答案。同时,Wolfram Alpha还提供了丰富的图表、可视化工具和数据导出功能,用户可以通过这些工具更直观地理解和分析数据。
Wolfram Alpha的应用领域非常广泛,包括教育、科学研究、工程设计、金融投资、医学诊断等。Wolfram Alpha还提供了企业版和开发者版,让企业和开发者可以将其集成到自己的应用中,以实现更加智能化的计算和分析功能。
#### 2.4.1.2 认知智能
随着人工智能技术的不断发展,各行各业已经陆陆续续地开始从信息化走向智能化升级或转型的道路。信息化已经不再是当今时代的主题,智能化开始崭露头角。当前各式各样的智能化应用不断涌现,这些智能化的需求对机器的认知智能水平提出了更高的要求,而实现机器的认知智能的核心技术之一则是依靠知识图谱。知识图谱本身的意义在于不再是传统的知识库,而是大数据时代对于海量数据的知识化表示方式,这种表示方式给机器的认知带来的是丰富而又高度总结的知识,为实现机器的认知智能提供了可能。因此,知识图谱成为各行各业进行智能化升级的重要技术基础。
#### 2.4.1.3 图数据库:NEO4j
Neo4j是由Neo4j公司开发的一个图数据库管理系统,是一个符合ACID(数据库事务正确执行的4个基本要素:原子性、一致性、隔离性、持久性)规则的事务性数据库,具有原生图存储和处理功能。Neo4j 是基于 Java 实现的图数据库,可以使用 Cypher 语言操作数据库。
> [NEO4j教程](https://www.w3cschool.cn/neo4j/)
#### 2.4.1.4 资源描述框架RDF
RDF (Resource Description Framework) 是 Web 中的一种资源描述据从本质上来看则是一种数据模型。它通过统一的标准描述了实体和资源之间的关系。从形式上来看,RDF表示的是 S (Subject)、P (Predicate)、0 (Object)三元组,其中,S为主语,为一个数据源的 URI:O为宾语,为一个数据的URI或者一段描述性文字;P为谓语,为表示主语和宾语之间关系的URI。RDF 以三元组的形式描述数据之间的关系,比传统的关系型数据库更加简单和直观。RDF 被大众认为是构建语义网络或知识图谱的比较好的标准。
> [RDF教程](https://www.w3cschool.cn/rdf/rdf-tutorial.html)
### 2.4.2 搜索引擎与知识图谱
* 精准命中答案
通过知识图谱直接找到用户最想要的信息,而不是通过一堆网页,让用户自己去慢慢地寻找答案。
* 信息有条有序
知识图谱提供了最全面的信息参考。比如用户搜索“马云”,不仅可以看到马云的身份信息,还可以看到其财富、演讲等信息。
* 兼顾搜索的深度与广度
一个完整的知识体系可以帮助用户在最短的时间内理解信息、提升效率,不必更换关键词去重新发起一次搜索请求。
### 2.4.3 可靠数据源
* 必应网典
必应网典(Bingpedia)是微软公司推出的一款基于互联网的百科全书,它汇集了来自多个领域的知识和信息,包括历史、文化、地理、科学、艺术等。必应网典旨在帮助用户更快地了解和掌握丰富多彩的知识和信息。
* 百度百科
百度百科是百度公司推出的一款基于互联网的中文百科全书,它汇集了来自多个领域的知识和信息,包括历史、文化、地理、科学、艺术等。百度百科旨在帮助用户更快地了解和掌握丰富多彩的知识和信息。
* 搜狗百科
搜狗百科是搜狗公司推出的一款基于互联网的中文百科全书,它汇集了来自多个领域的知识和信息,包括历史、文化、地理、科学、艺术等。搜狗百科旨在为用户提供简洁、易懂、准确的知识服务,让用户更快地了解和掌握相关知识。
### 2.4.4 实体抽取
* 实体标注
实体标注则是将文本中实体使用BIO的方式进行标注。为制作训练集、验证集和测试集做准备。常用的NLP标注工具如下:
| 工具 | 说明 |
| ----------------- | ------------------------------------------------------------ |
| YEDDA | 优点是安装方便,程序很小,标注方便,如果要实现给同一个实体加多个标签,也可以实现。最大标签数8,界面也还过的去。开源项目:https://github.com/jiesutd/YEDDA |
| Doccano | 支持命名实体识别,情感分类,机器翻译任务,界面比较友好。开源项目:https://github.com/doccano/doccano |
| Prodigy | 实体标注、分类标注,情感标注,都是英文的,功能最全的。网址:https://prodi.gy/docs/ |
| Chinese-Annotator | 基于prodigy的中文标注工具,开源项目:https://github.com/deepwel/Chinese-Annotator |
本文推荐使用 doccano标注平台
* 模型训练
推荐的模型训练方法,基于PaddleNLP的通用信息抽取UIE(Universal Information Extraction)进行微调,可以取得很好的效果,实体标注与训练方法详情可进入开源项目查看。
> [开源项目](https://gitee.com/paddlepaddle/PaddleNLP/tree/develop/model_zoo/uie)
* 实体预测
通过训练好的模型,加载模型权重参数,即可对句子中的实体进行预测。
### 2.4.5 关系抽取
| 时间 | 方法 | 介绍 |
| ---- | -------- | ------------------------------------------------------------ |
| 早期 | 模式匹配 | 通过指定模式关系列表,先对文本和模式进行匹配,匹配成功后,再根据模式中的关系约定直接提取。 |
| 现在 | 深度学习 | 通过机器学习算法,首先在人工标注的语料库上进行特征学习,然后将学习到的特征应用到预测数据中,最终得到期望的结果。 |
* 隐藏关系抽取
语句中没有明显标识的关系词。隐藏关系是中文语言表达方式中经常使用的方式,此类隐藏关系大多为常识性知识信息。
| 语句 | 实体 | 关系 |
| -------------------------------- | -------------- | ---- |
| 中国上海很漂亮 | 中国,上海 | 包含 |
| 重庆在长江上游 | 重庆,长江上游 | 位置 |
| 阿里巴巴公司的张云表示情况很乐观 | 阿里巴巴,张云 | 雇佣 |
* 确定关系抽取
语句中已经明确实体之间的关系,确定关系抽取是将互联网文档中已经确切说明和描述的实体关系抽取出来。
| 语句 | 实体 | 关系 |
| ------------------------------ | -------------- | ---------- |
| 阿里巴巴董事会主席马云开始演讲 | 阿里巴巴,马云 | 董事会主席 |
### 2.4.6 知识图谱检测
* 实体关系修正
在完成实体关系抽取之后,获得的数据信息并不一定完全可靠,还需要不断地在互联网信息中进行抽取,并对每个关系进行投票。
| 实体与关系 | 修正后 |
| -------------- | -------------- |
| 小张-妻子-小芳 | 小张-前妻-小芳 |
在对一篇文档进行抽取后实体“小芳”被识别为实体“小张”的妻子,但是目前两者并不存在夫妻关系,有可能是由于分析了过时的文档导致的。因此,需要通过投票的形式去纠正实体关系。通过分析当前更多关于实体“小张”与“小芳”的关系,发现绝大多数人认为实体“小芳”是实体“小张”的前妻。
* 实体关系对齐
在进行实体抽取的过程中,实体的各个属性与关系并不一定来自同一个文档。换一个角度理解,即实体具有不同的 ID,但是在现实中却代表着同一对象的实体。例如,针对知识图谱中的实体“朱自清”和“朱自华”,实质上它们指向同一实体,只是称呼不同罢了(朱自华是朱自清的原名),因此需要将这样的实体进行合并。整个实体合并的过程被称为实体对齐。
* 实体歧义分析
不同实体ID有描述同一实体的可能性。在实体对齐的过程中,可能存在这样的情况:相同的实体名称表示的不是同一个实体。实体名“李湘”的知识存在两个名为“李湘”的实体,如果这时候用户搜索“李湘生日是什么时候?”,实体的歧义就会出现。系统可以定位到两个实体,需要集合实体标签给出相似实体的差异并给出结果“主持人李湘生日是02.10,相似人物中国台湾演员李湘生日是12.04”。
### 2.4.7 知识推理与知识计算
* 知识推理
知识推理主要是针对实体对外关系所做的逻辑推理。当用户搜索“李湘女儿多大了?”时,先通过“李湘女儿”推导出“王诗龄”再推导出“6岁”,并给出精准答案。
* 知识计算
知识计算是实体之间相互关系的动态结果。目前知识计算主要运用于知识信息值计算与知识关系度计算。当用户搜索“李湘和女儿年龄差?”时,先通过“李湘女儿”推导出“王诗龄”再计算出“24岁”,并给出精准答案。
### 2.4.8 智能搜索
#### 2.4.8.1 模式匹配
模式匹配需要有较强的实用性。针对知识图谱和人工智能领域,人工智能标记语言 (ArtificialIntelligence Markup Language,AIML)拥有较好的模式配思想。通过AIML可以将用户的搜索词转换为系统内部的查询指令。AIMI是著名的人工智能机器人Alice的知识库存放形式。一个完整的AIML文件类似于如下代码:
```
你 叫 什么 名字 *
我叫Alice
我的名字是Alice
Alice
我是Alice
我是大名鼎鼎Alice机器人
你 的 名字 *
你 叫 什么 名字 呢
```
#### 2.4.8.2 知识拆解
AIML 还有很多特征标记,利用 AIML的模式匹配思想,是为了将用户的搜索转换为知识图谱可识别的查询语言,从而达到知识拆解的目的。
(1)定义知识图谱查询语言。系统以JSON 数据格式定义知识图谱查询语言。笔者定义的查询格式如下 :
```
*|nr 比 *|nr 高 吗 *
{
"return_type": "recursive",
"knowledge_query": [
{
"command": "knowledge",
"entity": "",
"parameter": "身高"
},
{
"command": "knowledge",
"entity": "",
"parameter": "身高"
}
],
"result_express": "#s1#-#s2#",
"result_script": "if(\"#result_express#\".contains(\"unknown\"){return \"未找到可以比较的数据\";}if(\"#result_express#\".contains(\"=-\"){return \"是的,高#result_express#\";}return \"不是的,相差#result_express#\"")",
"debug": "拆分消息'身高'、'身高'"
}
```
实现知识拆解。如果搜索“刘翔比刘德华高吗?”,那么首先会通过分词和词性标注得到“刘翔nr比p刘德华nr高a吗y?lw”。在上述“pattern”中,允许两个任意字符匹配,但是要求它们匹配到的词性必须是“nr”(人名)“”表示第一个“*”所指的内容。通过匹配会替换“knowledge_query”信息如下,有效地将搜索“刘翔比刘德华高吗? ”转换为“刘翔身高”和“刘德华身高”两个子知识问题,而这两个子知识问题可以通过知识图谱有接求解。
```
[
{
"command": "knowledge",
"entity": "",
"parameter": "身高"
},
{
"command": "knowledge",
"entity": "",
"parameter": "身高"
}
]
```
在上述示例中,在“pattern”里限定了匹配“*”的必须是人名,否则去查询实体的“身高”就失去了意义。侧如。用户搜索“武夷山比五台山高吗?”通过分词与词性标注得到“武夷山 \ns 比 \p 五台山 \ns 高 \a 吗 \y ? \w”,其中“ns”表示地理名,地理名的“高”则应当使用词语“海拔”,因此只需在AIML中新增相关内容即可。如果没有命中相应的“category”,则直接搜索网页即可,跳过知识图谱的搜索。
#### 2.4.8.3 合并求解
在进行知识拆解之后,会通过子知识在知识图谱中搜索答案。继续前面的不例,通过知识图谱并发查询,得知刘翔的身高为 189 cm,刘德华的身高为174m。合并求解过程需要通过表达式生成、表达式计算、结果返回三步完成。
(1)表达式生成。在定义的查询语言中,存在字段“result_expres”,即表达式生成模板。在子知识中查询到的结果会填充到"result _expres":"#s1#s2#”中,即:
```
"result_express": "189cm-174cm"
```
(2)表达式计算。在定义的查询语言中,存在字段“return_type”,表示运回的数据类型。如下代码所示,表示这是一个递归运算。
```
"return_type": "recursive",
```
(3)此时需要将“result_express”进行递归,也就是将“189cm-174cm”再进行查询转换,它们会命中另一个“category”。
```
*|m cm 加减乘除 *|m cm
{
"return_type":“result",
"knowledge_query": [],
"result_express": "",
"result_script": "returm \"#result_express#(单位: 厘米)\";",
"debug": "推理过程:"
}
```
(4)此“category”返回的结果是“result”,因此不需要再次递归。“ 加减乘除”表示“+*/”任意一个符号,因此“result_express”为“189-174”通过计算表达式,得到结果为15,即#result_express#为15。
(5)结果返回。在定义的查询语言中,字段“result script”表示返回结果的定义脚本。通过代码执行“result_script”直接返回“15(单位:cm)”,此结果将成为上一个“category”的“result_express”,因此上一个“category”的“result_script”为如下内容
```
if ("#result_express#".contains("unknown")) {
return "未找到可以比较的数据";
}
if ("#result_express#".contains("=-")) {
return "是的,高#result_express#";
}
return "不是的,相差#result_express#"
```
(6)通过执行“result_script”,最终用户搜索“刘翔比刘德华高吗?”,系统将返回“是的,高15(单位:cm)”。
#### 2.4.8.4 扩展
* 常识性智能搜索
在实际工程中,许多常识性、固定化的信息是无须通过互联网学习即可拥有的。例如,搜索“每周有多少天?”,此类搜索只需通过配置人工智能标记语言即可,如下代码所示,根据“random”中的数据,随机返回即可。
```
每周有多少天 *
七天
每周有七天
一周共七天
七天为一周
从周一一到周日,共七天
```
类似的搜索还包括“国年一年有多少天”“中国有多少个省份”“农历节日通过人工智能标记语言直接返回答案,无须向知识图讲发出请求,在性能佣户体验上都可以达到很好的效果。
* 实时信息智能搜索
使用人工智能标记语言可以有效地通过知识图谱返回相关知识,但是存在另一种情况,即知识图谱很难包含实时信息,如每天的天气变化情况、时刻变化的航班信息等,即使通过知识图谱去获取,时效性也不能得到保证。因此可以通过查询语言定义的“knowledge_query”灵活配置“command”来解决此问题。例如,搜索“北京今天天气”,配置信息如下。所有城市的“今天天气”的搜索都会经过此配置。
```
*|ns 今天 天气 *
{
"return type";“result",
"knowledge query":[{
"command":"weather",
"entity":" ",
"parameter": "今天"
}]
"result_express": "",
"result_script": "",
"debug": "城市'',查询日期:今天"
}
```
在天气请求中,直接将“command”的值变更为“weather”,传递相应参数即可。当然也可以是航班号、彩票等其他变化性较强的搜索信息,关键在于后台根据收到的“command”是否能够有效地获取数据。天气、航班号、彩票等属于开放性数据,可通过开放接口直接获得。
* 可交互式智能搜索
最初研发人工智能标记语言(AIML)的目的是用在著名的人工智能机人Alice 上面,因此,将 AIML 的原始功能保留在现有的搜索引整系统中也是可以的,只需进行相应的配置即可,对于搜索引擎来说,即交互性搜索。
```
美食菜谱
您是想知道怎么做吗
是的
您是想知道 * 怎么做吗
的做法
```
上述示例的 AIML配置信息是:当用户搜索“宫保鸡丁”时,搜索引擎除返回“宫保鸡丁”相关网页外,还会提示用户“您是想知道宫保鸡丁怎么做吗”如果用户回答“是的”,则通过知识图谱返回“宫保鸡丁”的详细做法。
可以发现,根据人工智能标记语言重新定义的智能查询语言的强大之处不仅仅在于知识图谱的信息拆解,还在于灵活的交互能力,人工智能标记语言将会成为未来人工智能发展的基础。
## 2.5 日志服务
### 2.5.1 用户搜索词
#### 2.5.1.1 搜索词的价值
* 相邻搜索词
两个搜索字符串在众多流量中都相邻被搜索,则这两个搜索字符串越相关。此类数据挖掘中最经典的当属啤酒和尿布的故事。例如,很多用户在搜索了“大数据分析”之后,都搜索了“hadoop”,则说明“hadoop”和“大数据分析”相关。在搜索引擎的相关搜索中,可以充分利用此项关联数据,当用户的搜索词无法实现精准搜索时,为用户提供其他相关搜索词推荐,以使得用户能够找到期望的信息。
* 发现近义词
一般情况下,比较用户搜索词语集合中两个搜索字符串之间的编辑距离、语义相似度,如果两个搜索字符串之间的编辑距离越短、语义相似度越高,则这两个搜索字符串越相似,为近义词的可能性比较大。发现近义词的目的是在不同搜索词下能够命中相同的优质结果,一般搜索引整会利用近义词进行搜索相关性推荐,例如,在搜索“最佳男主角”时,推荐搜索“最佳女主角”或“最佳男配角”等。
* 高流量热搜词
从用户不断搜索的搜索词中可以发现用户瘦索较为频繁的搜索热词,可以估计这些搜索热词在最近一段时间内的访问热度依然不会太低,因此,优先考虑将高流量的搜索热词进行缓存,使得其不再频繁地向后台服务器请求数据,同理也会淘汰一部分缓存数据。通过不断地计算每小时或者每天的搜索热词,可以使得搜索服务尽可能地达到较为满意的结果。
#### 2.5.1.2 不明意图的用户行为
对于单一用户而言,意图识别难度非常大。再加上用户的不规范输入、语言表达的多样性甚至方言式表达,都会使得用户意图识别难上加难。此外,用户的意图也不定是单一性的,例如用户搜索“大好时光”,用户的可能意图是“《大好时光》这部电视剧”“出去旅游享受大好时光”等,难以揣测。此外,即使相同的搜索词,在不同的时间搜索,用户的意图也会大受干扰,例如关键词“伪装者”,在多年以前搜索是一个普通角色词,在 2015 年搜索却是一部电视剧,不同的时间赋予了词语不同的含义。
* 分词歧义
如果分词本身带有歧义,则很难将用户意图揣测清楚。例如,对于用户搜索“垃圾漂上海港”,本意是标识垃圾漂到了海港旁边,但是如果分词为“垃圾/漂/上海港”,则很容易将搜索重点落在“上海港”上,而用户搜索本意与“上海港”没有任何关系。
* 未识别新词
互联网中产生的新词不能够及时识别,造成意图特征不明显。典型的例子为电视剧的名称,如“半月传”“秦时明月”, 如果能够在最短的时间内识别到该词为电视剧名称,则可以很好地进行用户意图揣测。
### 2.5.2 用户点击日志
* 时间搜索意图关系
时间是一个不断变化的因素,利用点击日志可以分析出因为时间关系导致的搜索歧义,因为不同搜索词在不同时间段的具体含义有一定的差异,这种差异性很可能导致搜索结果南辕北辙。以关键词“小时代”为例,它的歧义性在于可能搜索的是电影或者小说当用户搜索“小时代”时,结果是集中在电影上还是集中在小说上成为搜索意图分析的重点。通过在最近一个月的搜索日志中进行挖掘,发现搜索“小时代的点击日志存在流量变化。将“小时代”点击日志中被点击的文档类别进行分析大致分为两类一一“电影”相关和“小说”相关。
* 地理位置与搜索意图关系
地理位置作为搜索引擎非用户输入的基本数据之一,对搜索引擎的个性化起到非常重要的作用。因为地理位置不同,即使搜索相同的关键词,用户的搜索意图和期望也不尽相同。以关键词“交通大学”为例,它的歧义点在于可以被理解为“北京交通大学”“上海交通大学”“西安交通大学”“重庆交通大学”“大连交通大学”等,虽然都是交通大学,但都是针对“交通大学这个关键词的。
* 点击日志与同义词
对于两个不同的搜索字符串,在搜索结果中被用户点击的链接相同的越多则这两个字符串越相关。例如,搜索词A和搜索词B,在结果页中存在K条相同链接,且这K条链接中有多条均被用户点击,则说明搜索词A 和搜索词B相关。例如,搜索“中国科学技术大学”与“中国科技大学”
* 点击日志与词语权重
在网页进行相关性排序时,会根据词语的权重及词语在文档中的权重综合考虑排序序列,词语的权重源自离线计算的样本 TF-IDF值但是存在这样的情况,即样本的不同可能导致词语对应的 TF-IDF值不同,因此需要通过用户的点击日志进行校验优化。例如,针对“大数据分布式计算”的分词结果“大数据”和“分布式计算”初步获得的TF-IDF值分别为0.23 和0.23,但是在搜索“大数据分布式计算返回的结果中,综合大量用户的点击情况如下表所示。
| 结果类别 | 点击量 |
| ---------------------------------- | ------ |
| “大数据”与“分布式计算”共同相关结果 | 16万次 |
| 仅“大数据”相关结果 | 3万次 |
| 仅“大数据”分布式计算 | 1万次 |
从表中可以直观地看出,用户的最佳期望是“大数据”与“分布式计算’的共同结果,然而在不能满足最佳期望时,用户点击“大数据”相关结果的次数比点击“分布式计算”相关结果的次数多。从搜索引擎的角度来分析,在“大数据分布式计算”这个搜索词中,词语“大数据”应当比“分布式计算”更加重要,则需要更改“大数据分布式计算”中“大数据”和“分布式计算”的权重比例。
* 点击日志与新词分类
识别新词在真实的语义环境中的分类。一般情况下,可通过对分类语料库中的词语进行多分类标注,获知词语最可能的一些分类情况。但是对于刚刚出现的新词,则需要在尽可能短的时间内确定该词可能的分类,以使得下次能够精确地对搜索词进行分类,然后定位搜索领域。例如搜索某新词A,点击文档分类如下表所示:
| 点击的文档分类 | 点击次数 |
| -------------- | -------- |
| 财经 | 10万次 |
| 教育 | 2万次 |
| 音乐 | 1.5万次 |
| 互联网 | 1万次 |
在搜索新词A的搜索结果的热门点击文档中主要有4类其中包括10万次对财经类相关文档点击、2万次对教育类相关文档点击、1.5万次对音乐类相关文档点击、1万次对互联两相关文档点击,则说明新词A的分类很可能为财经类,如果以数值化表示,则可以将该分类下的点击次数与总点击次数相比。上速过程实质上也是利用众包的思想进行新词分类标注的。
* 点击日志与知识图谱
知识图谱在大多数情况下是通过对百科网站数据进行结构化和非结构化分析得到的结果,但是用户的点击日志也能够为知识图谱提供可靠的数据分析源搜索引擎不会对互联网中的所有网页进行实体抽取,但是会选择性抽取部分网页进行实体和实体关系抽取。这里的选择性指用户点击文档的标题。例如,对于搜索词“张三老婆”,在搜索引擎的知识图谱中并未找到,但是通过网页搜索得到的部分网页标题如下所示:
示例|说明
----|----
张三老婆李芳简介及家庭背景|张三>老婆>李芳
张三老婆李芳和儿子照片资料大曝光|张三>老婆>李芳
张三老婆李芳谈幸福,张三妻子、女儿、儿子照片大曝光|张三>老婆>李芳
张三妻子李芳_张三老婆是谁 ?李芳背景资料简介及照片曝光|张三>老婆>李芳
张三漂亮老婆李芳简介及珍贵照片曝光|张三>老婆>李芳
* 点击日志与网页重排序
对于搜索“北京市政府”产生的两个排序不定的文档,通过用户点击确定文档对用户的价值,在累积过程中,文档价值越高,排序越靠前。
* 点击日志与网页评价
评价某个页面是否符合用户预期,用户点击仅仅说明看起来很像,是否真实地对用户具有价值,还需要结合被跳转页面被用户浏览的驻留时间进行分析例如,用户搜索“新加坡自助游”,点击跳转到http://a.com/xinjiapo 页面,统计用户在 http://a.com/xinjiapo 页面上的行为,尤其是驻留时间,驻留的时间越长,则说明网页结果可靠性越高。针对这一点,搜索引擎利用了用户点击的时间差进行分析。
### 2.5.3 用户特征
* 用户跟踪
越是持久使用的用户,对搜索引擎的后期分析效果越好,也越利于广告推荐。再者,仅仅通过用户每天的搜索很难分析出用户的所有爱好、学历等各种信息,搜索引擎无法完整地跟踪用户行为路径。因此,搜索引擎常常以 Cookic的方式记录用户的某些行为,这些行为不仅包括搜索词,还包括在其他网站上的行为。
搜索引擎一般会在其官网中标识某用户的固定Cookie 内容,如UID-BCZ8B。在不同域名中,这个UID也属于该搜索引擎旗下的网站 A,通过套搜索引擎官方外链共享 UID。当然,在其广告联盟会员的其他网站中也会共享到 UID。
采用“用户跟踪”技术的出发点是搜索引擎能够进行有效的数据分析,但是搜索引擎还能够将这些精准的用户数据与广告结合,从而进行更加精准的广告投放,以增加广告点击率,获得较高的广告收益。从搜索引擎本身的技术角度或者搜索引整的商业价值角度考虑,进行用户跟踪具有十分重要的意义。
* 用户群体特征
要对用户的群体特征进行分析,可以根据用户的行为相似性对用户进行聚类分析。一般认为相似用户的搜索行为可能类似,将此类用户有效地聚集在起,可以有效地进行搜索行为预测,进而提升用户搜索体验。例如:
| 用户 | 搜索词 | 点击目标类型 |
| ----- | ---------- | ------------ |
| 用户A | “大话西游” | 相关电影 |
| 用户B | “大话西游” | 相关游戏 |
| 用户C | “生化危机” | 相关电影 |
| 用户D | “生化危机” | 相关游戏 |
可以分析出用户A、C属于电影迷类型,用户 B、D 属于游戏迷类型。现在有另一个用户E搜索了“波斯王子时之刃”,那么如何预测用户E 对于搜索结果是更倾向于电影还是游戏呢? 根据大多数人点击的电影还是游戏,进行权重,搜索交过根据权重交替显示。
* 用户个体特征
采用基于群体相似性的方法进行分析可以预测用户可能的结果倾向,但是同样可以通过对用户个体进行研究得到结果倾向。
例如,用户E最近一天的搜索历史记录包括“肖申克的救赎”“阿甘正传"“马来西亚旅游攻略”“辛德勒的名单”“北京自助游”“开心消消乐”等,则需要分析用户E 搜索“生化危机”的意图是相关电影还是相关游戏。对用户的历史搜索记录进行统计分析发现,有超过 60% 的搜索与电影相关,30%的拟索与旅游相关,少于 10%的搜索与游戏相关,显然搜索“生化危机”与电影相关的可能性较大。
# 3 开源搜索引擎lucene
## 3.1 索引文件结构
### 3.1.1 段(Segment)
Lucene的一个Index会由一个或多个sub-index构成,sub-index被称为Segment,每个segment中包含多个documents文件,一个segment中会有完整的正向索引和反向索引。
在搜索时,Lucene会遍历这些segments,以segments为基本单位独立搜索每个segments文件,而后再把搜索结果合并。
### 3.1.2 文档(Document)
Document表示带有Fields的虚拟文档,其中Field是一个对象,可以包含物理文档的内容,元数据等。 Analyzer只能理解文档。下面时一个完整的文档示例。
| 字段 | 内容 | 是否存储 |
| ----------- | ------------------------ | -------- |
| id | 1 | 是 |
| title | xxxxxxxxxxxx | 是 |
| abstract | xxxxxxxxxxxxxxxx··· | 是 |
| author | 张三;李四;王五 | 是 |
| journal | 情报学报 | 是 |
| publishDate | 2023-05-01 | 是 |
| keyWord | 大数据;深度学习;实体抽取 | 是 |
| unit | 华中师范大学 | 是 |
### 3.1.3 字段(Field)
字段是索引过程的最低单位或起点。 它表示键值对关系,其中键用于标识要编制索引的值。 假设用于表示文档内容的字段将具有作为“内容”的键,并且该值可以包含文档的部分或全部文本或数字内容。如一个文档包含以下字段。
| 字段 | 名称 | 类型 |
| ----------- | -------- | ---- |
| title | 档标题 | 文本 |
| abstract | 摘要 | 文本 |
| author | 作者 | 文本 |
| journal | 期刊 | 文本 |
| publishDate | 出版日期 | 日期 |
| keyWord | 关键字 | 文本 |
| unit | 单位 | 文本 |
### 3.1.4 词(Term)
词是索引的最小单位,是经过词法分析和语言处理后的字符串,上篇博客中的N维空间向量,每一个维度都是一个词。
## 3.2 分词器(Analyzer)
对搜索关键字进行分析和索引分析一样,使用Analyzer对搜索关键字进行分析、分词处理,使用分析后每个词语进行搜索。比如:搜索关键字: spring web ,经过分析器进行分词,得出: spring web拿词去索引词典表查找找到索引链接到Document,解析Document内容。可以使用如下代码加载不同的分词器:
```
Analyzer analyzer = new IKAnalyzer();
Directory directory = FSDirectory.open(Config.luceneDbPath);
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
```
### 3.2.1 StandardAnalyzer
Lucene提供的标准分词器,可以对用英文进行分词,对中文是单字分词,也就是一个宁就认为是一个词。
```
Analyzer analyzer = new StandardAnalyzer();
```
### 3.2.2 WhitespaceAnalyzer
仅仅是去掉了空格,没有其他任何操作,不支持中文。
```
Analyzer analyzer = new StandardAnalyzer();
```
### 3.2.3 SimpleAnalyzer
将除了字母以外的符号全部去除,并且将所有字母变为小写,需要注意的是这个分词器同样把数字也去除了,同样不支持中文。
```
Analyzer analyzer = new SimpleAnalyzer();
```
### 3.2.4 CJKAnalyzer
这个支持中日韩文字,前三个字母也就是这三个国家的缩写。对中文是二分法分词,去掉空格,去掉标点符号个人感觉对中文支持依旧很烂。
```
Analyzer analyzer = new CJKAnalyzer();
```
### 3.2.5 IKAnalyzer第三方中文分词器
[最新版](https://code.google.com/p/ik-analyzer/),支持Lucene 4.10从2006年12月推出1.0版开始,IKAnalyzer已经推出了4个大版本,最初,它是以开源项目Luence为应用主体的,结合词典分词和文法分析算法的中文分词组件。从3.0版本开始,IK发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。在2012版本中,IK实现了简单的分词 歧义排除算法,标志着IK分词器从单纯的词典分词向模拟语义分词行化。一下是添加依赖的的代码。
```
com.github.magese
ik-analyzer
8.5.0
```
实例化分词器代码:
```
Analyzer analyzer = new IKAnalyzer();
```
## 3.3 Directory
Directory类描述了Lucene索引的存放位置。它是一个抽象类,它的子类负责指定索引的存储路径,在前面的例子中,我们用的是FSDirectory.open方法来获取真实文件在文件系统中的存储路径,然后将他们依次传递给IndexWriter类构造方法。通过以下代码初始化一个存储位置:
```
Directory directory = FSDirectory.open(Config.luceneDbPath);
```
其中Config.luceneDbPath是配置的本地存储索引的目录路径
## 3.4 IndexWriter索引写
IndexWriter(写索引)是索引过程中的核心组件,这个类负责创建新的索引或打开已有的索引以及向索引中添加、删除、更新被索引的文档信息;IndexWriter需要开辟一定空间来存储索引,该功能可以由Directory完成。
```
/**
*@Description: 索引创建demo
*/
package com.lulei.lucene.study;
import java.io.File;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
public class IndexCreate {
public static void main(String[] args) {
//指定索引分词技术,这里使用的是标准分词
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_43);
//indexwriter 配置信息
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_43, analyzer);
//索引的打开方式,没有索引文件就新建,有就打开
indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
Directory directory = null;
IndexWriter indexWrite = null;
try {
//指定索引硬盘存储路径
directory = FSDirectory.open(new File("D://study/index/testindex"));
//如果索引处于锁定状态,则解锁
if (IndexWriter.isLocked(directory)){
IndexWriter.unlock(directory);
}
//指定所以操作对象indexWrite
indexWrite = new IndexWriter(directory, indexWriterConfig);
} catch (Exception e) {
e.printStackTrace();
}
//创建文档一
Document doc1 = new Document();
//对name域赋值“测试标题”,存储域值信息
doc1.add(new TextField("name", "测试标题", Store.YES));
//对content域赋值“测试标题”,存储域值信息
doc1.add(new TextField("content", "测试内容", Store.YES));
try {
//将文档写入到索引中
indexWrite.addDocument(doc1);
} catch (Exception e) {
e.printStackTrace();
}
//创建文档二
Document doc2 = new Document();
doc2.add(new TextField("name", "基于lucene的案例开发:索引数学模型", Store.YES));
doc2.add(new TextField("content", "lucene将一篇文档分成若干个域,每个域又分成若干个词元,通过词元在文档中的重要程度,将文档转化为N维的空间向量,通过计算两个向量之间的夹角余弦值来计算两个文档的相似程度", Store.YES));
try {
//将文档写入到索引中
indexWrite.addDocument(doc2);
} catch (Exception e) {
e.printStackTrace();
}
//将indexWrite操作提交,如果不提交,之前的操作将不会保存到硬盘
try {
//这一步很消耗系统资源,所以commit操作需要有一定的策略
indexWrite.commit();
//关闭资源
indexWrite.close();
directory.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
## 3.5 IndexSearcher搜索
IndexSearcher用于索引的搜索,这个类公开了几个搜索方法,它是连接索引的中心环节;可以将IndexSearcher类看做成一个以只读打开索引的类,关于IndexSearcher的更深层次的内容,将在后面博客中介绍。
```
/**
*@Description: 索引检索demo
*/
package com.lulei.lucene.study;
import java.io.File;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
public class SearchIndex {
public static void main(String[] args) {
Directory directory = null;
try {
//索引硬盘存储路径
directory = FSDirectory.open(new File("D://study/index/testindex"));
//读取索引
DirectoryReader dReader = DirectoryReader.open(directory);
//创建索引检索对象
IndexSearcher searcher = new IndexSearcher(dReader);
//指定分词技术,这里采用的语言处理模块要和创建索引的时候一致,否则检索的结果很不理想
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_43);
//创建查询query,搜索词为“空间向量”
QueryParser parse = new QueryParser(Version.LUCENE_43, "content", analyzer);
Query query = parse.parse("空间向量");
//检索索引,获取符合条件的前10条记录
TopDocs topDocs = searcher.search(query, 10);
if (topDocs != null) {
System.out.println("总共查找到 " + topDocs.totalHits + " 条符合条件的记录");
//循环输出记录内容
for (int i = 0; i < topDocs.scoreDocs.length; i++) {
Document doc = searcher.doc(topDocs.scoreDocs[i].doc);
System.out.println("第" + (i + 1) + "条内容为--\tname:\t" + doc.get("name") + "\tcontent:\t" + doc.get("content"));
}
}
//关闭资源
dReader.close();
directory.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
## 3.6 索引查询
* QueryParser
QueryParser主要用于对单个域搜索时创建查询query,QueryParser的构造方法需指定具体的域名和分词方法,lucene不同版本,创建的Query对象会略有不同。
```
//对单个域构建查询语句
QueryParser queryParser = new QueryParser("title", analyzer);
Query query = queryParser.parse("跨学科");
Directory directory = FSDirectory.open(Config.luceneDbPath);
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(query, 10)
```
* MultiFieldQueryParser
MultiFieldQueryParser可以想象成QueryParser的升级版,QueryParser主要用于单个域的搜索,而MultiFieldQueryParser则用于多个域的搜索,其构造方法和具体使用和QueryParser类似。
```
//对多个域创建查询语句
String[] fields = {"title", "paperAbstract"};
MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, analyzer);
Query query = queryParser.parse("跨学科");
Directory directory = FSDirectory.open(Config.luceneDbPath);
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(query, 10)
```
* TermQuery
TermQuery重要对一个Term(最小的索引块,包含一个field名和值),TermQuery可以用于对关键字域查询时Query的创建,比如分类、文档唯一ID等。
```
//词条搜索
TermQuery query = new TermQuery(new Term("title", "跨学科"));
Directory directory = FSDirectory.open(Config.luceneDbPath);
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(query, 10)
```
* PrefixQuery
PrefixQuery前缀查询字符串的构建,其效果和“abc*”这种通配符使用WildcardQuery构建Query类似;PrefixQuery只需要前缀指定的若干个字符相同即可,如PrefixQuery(new Term("", "lu")),将会匹配lucene、luke等。
```
//前缀搜索
PrefixQuery query = new PrefixQuery(new Term("title", "跨学科"));
Directory directory = FSDirectory.open(Config.luceneDbPath);
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(query, 10)
```
* PhraseQuery
PhraseQuery短语搜索,它可以指定关键词之间的最大距离,如下面demo程序中,指定了“基于”“案例”这两个词之间最大的距离为2,一旦文档中,这两个词的具体大于2就不满足条件。
```
//短语搜索
PhraseQuery query = new PhraseQuery();
//设置短语间允许的最大间隔
query.setSlop(2);
query.add(new Term("title", "跨学科"));
query.add(new Term("title", "数据抽取"));
Directory directory = FSDirectory.open(Config.luceneDbPath);
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(query, 10)
```
* WildcardQuery
WildcardQuery通配符搜索,可以想象是PrefixQuery的升级版,WildcardQuery提供了更细的控制和更大的灵活行,lucene中有* ? 这两个通配符,表示匹配任意多个字符,?表示匹配一个任意字符。如lue可以和lucene、luke匹配;lu?e可以和luke匹配,但和lucene却不匹配。
```
//通配符搜索
WildcardQuery query = new WildcardQuery(new Term("title", "跨学科?"));
Directory directory = FSDirectory.open(Config.luceneDbPath);
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(query, 10)
```
* TermRangeQuery
TermRangeQuery字符串范围搜索,在创建时一般有5个参数分别是 域名、域下限值、域上限值、是否包括下限、是否包括上限,这个和下面的NumericRangeQuery的参数含义相同。
```
//字符串范围搜索
TermRangeQuery query = TermRangeQuery.newStringRange("title", "abc", "azz", true, false);
Directory directory = FSDirectory.open(Config.luceneDbPath);
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(query, 10)
```
* NumericRangeQuery
NumericRangeQuery数字范围搜索,它针对不同的数据类型(int、float、double),提供的不同的方法,参数含义参照TermRangeQuery。
```
//int范围搜索
NumericRangeQuery query = NumericRangeQuery.newIntRange("star", 0, 3, false, false);
Directory directory = FSDirectory.open(Config.luceneDbPath);
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(query, 10)
```
* BooleanQuery
上面介绍的Query子类几乎都是针对单个域或多个域单个关键字的,那多个域不同关键字有该如何处理?多个Query对象又如何组成一个Query对象?这些BooleanQuery都可以实现,BooleanQuery可以嵌套非常复杂的查询,其和布尔运算类似,提供与(Occur.MUST)、或(Occur.SHOULD)、非(Occur.MUST_NOT)三种逻辑关系。
```
//对多个域创建查询语句
BooleanQuery query = new BooleanQuery();
query.add(new TermQuery(new Term("content", "基于")), Occur.SHOULD);
query.add(new TermQuery(new Term("name", "lucene")), Occur.MUST);
query.add(new TermQuery(new Term("star", "3")), Occur.MUST_NOT);
Directory directory = FSDirectory.open(Config.luceneDbPath);
IndexReader indexReader = DirectoryReader.open(directory);
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
TopDocs topDocs = indexSearcher.search(query, 10)
```
## 3.7 检索方法
* Collector
Collector主要用来对搜索结果做收集、自定义排序、过滤等,在Lucene4.3.1的API中,有两个搜索方法用到了Collector,但是其下面都有一句Lower-level search API(低级别的搜索API),如果没有非用不可的需求,尽量还是使用其他方法。
* Filter
Filter主要是做筛选条件的,用于指定哪些文档可以在搜索结果中,这个自己使用的并不是太多,查询了一些资料,介绍说有Filter的检索过程是先对数据源做筛选预处理(Filter中指定的),然后将筛选的结果交给查询语句,如果是这样的话,使用Filter的代价将会很大,他的查询耗时可能会提高数倍。个人认为也没有必要使用Filter,如果真的需要对结果做筛选,可以把这些筛选条件合并到Query中,而没有必要创建一个Filter对象。
* Sort
Sort在检索方法中指定排序方式,相当于数据库中的order by,创建方式如Sort sort = new Sort(new SortField("time", Type.LONG, true)),这里的SortField构造方法中的三个参数分别代表域名、域数据类型、排序方式(true降序/false升序),这里的例子只是按照一个域进行排序,如果多个域可以直接在构造方法中添加,如:
```
sort = new Sort(new SortField("time", Type.LONG, true), new SortField("star", Type.INT, false))。
```
* ScoreDoc
searchAfter方法中使用到ScoreDoc,该方法主要用在分页查询中,当然也可以用search方法替代,但是有一种情况是无法替代的,比如查询第一页10条数据,但由于推广或广告等需求,需要在其中添加几条(具体未知)其他记录,但是前端只能展示10条数据,这样该页的最后几条数据就没有办法显示,但在下一页中又想显示这几条数据,这样使用seach方法实现就有点困难。