【Go语言开发】简单了解一下搜索引擎并用go写一个demo

写在前面

这篇文章我们一起来了解一下搜索引擎的原理,以及用go写一个小demo来体验一下搜索引擎。

在这里插入图片描述

简介

搜索引擎一般简化为三个步骤

  • 爬虫:爬取数据源,用做搜索数据支持。
  • 索引:根据爬虫爬取到的数据进行索引的建立。
  • 排序:对搜索的结果进行排序。

然后我们再对几个专业名词做一个简单解释

  • document:用于构建索引库的数据
  • term:将一段文本进行分词,分词之后的每个最小单元叫做 Term,比如“苹果发布会”,分词之后就是【苹果,发布会】,“苹果”和“发布会”就是最小单元的 term。
  • token:token 是 term 的一次出现,它包含 term 文本和相应的起止偏移,以及一个类型字符串。一句话中可以出现多次相同的词语,它们都用同一个 term 表示,但是用不同的 Token,每个 Token 标记该词语出现的地方。比如 token中不仅有term还有这个term在这个文档的位置。

1. 爬虫

爬虫就很简单了,不是重点,我们准备好数据源即可。
在这里插入图片描述

2. 索引

2.1 正排索引&倒排索引

索引分成正排索引和倒排索引。
正排索引:将文档按照文档顺序进行组织的索引结构。
倒排索引:根据词条来组织文档数据的索引结构。

举个例子:
假设我们有三个文档:

文档1,内容为"postman datagrip goland";
文档2,内容为"goland vscode";
文档3,内容为"pycharm goland"。

使用正排索引和倒排索引来存储这些文档:

  • 正排索引:

文档1:“postman datagrip goland”
文档2:“goland vscode”
文档3:“pycharm goland”

在正排索引中,我们按照文档的顺序存储了每个文档的完整内容。

  • 倒排索引:

postman:文档1,
datagrip:文档1,
goland:文档1,文档2,文档3
vscode:文档2
pycharm:文档3

在倒排索引中,我们将每个词条映射到包含该词条的文档集合上。例如,"postman"出现在文档1中

2.2 构建索引

  • 读取数据源

首先观察数据源,确定了我们的索引对象是第16个,也就是电影的主要内容。

读取数据源,构造索引数据

func fileOpen() []string {file, err := os.Open("movies.csv")if err != nil {fmt.Println("err", err)}defer file.Close()// 创建一个 Scanner 用来读取文件内容docx := make([]string, 0)scanner := bufio.NewScanner(file)// 逐行读取文件内容并打印for scanner.Scan() {re := make([]string, 0)line := scanner.Text()re = strings.Split(line, ",")docx = append(docx, re[16])}docx = docx[1:]return docx
}
  • 分词

当我们读取数据之后,要对数据进行分词,分成一个个的词,用作建立索引库。
但分词之前我们要先数据清洗一下,比如中文就是去掉一些语气词,标点符号;英文则是去除一些语气词,做预干转移(过去式,未来式都变成现在式,比如learning --> learn),转成小写之类的。
先定义一下StopWord,如果出现StopWord里面的词,就进行删除替换。

var StopWord = []string{",", ".", "。", "*", "(", ")", "'", "\""}

定义一个 removeShopWord 的 func,传入 word,也就是段落,先进行数据的清洗,再将word进行分词。

func removeShopWord(word string) string {for i := range StopWord {word = strings.Replace(word, StopWord[i], "", -1)}return word
}

定义一个 tokenize 进行分词操作。使用 github.com/go-ego/gse 包进行分词操作。

var gobalGse gse.Segmenterfunc InitConfig() {gobalGse, _ = gse.New()}func tokenize(text string) []string {text = removeShopWord(text)return gobalGse.CutSearch(text)}

3. 索引构建

定义一个map结构,key 是一个 term,value 是包含有 term 关键字的文档的 id 数组。

type InvertedIndex map[string][]int

构建索引

func BuildIndex(docx []string) InvertedIndex {index := make(InvertedIndex)for i, d := range docx { // 遍历所有的docxfor _, word := range tokenize(d) { // 对所有的docx进行tokenif _, ok := index[word]; !ok { // 如果index不存在这个term了index[word] = []int{i} // 初始化并放入 行数} else {index[word] = append(index[word], i) // 如果index不存在,则放入该term所在的 行数,也就是 行数}}}return index
}

这里我们的 token 和 term 是一样的了,因为token中只有term,没有定义别的东西,比如term在doc的位置等等…

3.1 搜索 排序

  • 搜索

我们定义 query 为搜索的内容,对query进行分词操作,然后再存储符合要求的docx文档的id。
那我们的search函数的传入就是 倒排索引 index,搜索词条 query,正排索引 docs

func search(index InvertedIndex, query string, docs []string) ([]string, []string) {result := make(map[int]bool)qy := tokenize(query)     // query词条进行分词for _, word := range qy { // 遍历分完词的每一个termif doc, ok := index[word]; ok {// 搜索倒排索引中,term对应的doc数组,doc数组就是存在该term词条的所有的doc idfor _, d := range doc {// 对doc数组进行遍历,获取所有的doc id,并且进行标志。result[d] = true}}}output := []string{}for d := range result {output = append(output, docs[d])// 利用正排索引,找到id对应的存储内容并返回}return output, qy
}

3.2 排序

当我们搜索完结果后,自然会有结果,但是这些结果的排序是不合理的,我们要进行重新排序。排序的规则也有很多,比如文档相似度,竞价排名等等…

那么我们这里就用 最简单的TFIDF来进行计算所搜索出来的doc和term之间的权重。

首先了解一下TFIDF:
TF(词频)指的是某个词在文档中出现的频率。在计算TF时,我们可以简单地使用词出现的次数除以文档中的总词数。
IDF(逆文档频率)指的是某个词在文档集合中的多少文档中出现过的程度。计算IDF时,我们可以将所有文档数目除以包含该词的文档数目。
TF-IDF的计算方式是将TF和IDF相乘,得到一个词在文档中的重要性分数。这个分数能够衡量一个词对于文档的重要性:如果一个词在某个文档中频繁出现,并且在整个文档集合中罕见,那么它可能是一个具有较高重要性的词。

计算TF:
term在这个文档中的出现的次数/这个document所有的分词的数量

func calculateTF(term string, document string) float64 {termCount := strings.Count(document, term)totalWords := len(tokenize(document))return float64(termCount) / float64(totalWords)
}

计算IDF:
所有文档的数量/term在所有文档中出现的次数

func calculateIDF(term string, documents []string) float64 {docWithTerm := 0for _, doc := range documents {if strings.Contains(doc, term) {docWithTerm++}}return float64(len(documents)) / float64(docWithTerm)
}

TF*IDF即可获取权重,下面这里是由于数据问题,我是乘以100的

func calculateTFIDF(term string, document string, documents []string) float64 {tf := calculateTF(term, document)idf := calculateIDF(term, documents)return tf * idf * 100.0
}

先定义好排序所需要的请求体

type SortRes struct {Docx  stringScore float64Id    int
}

具体排序:
qy为输入的query分词后的token形式,res则是搜索结构,返回值是将res排序好的结果。

func sortRes(qy []string, res []string) []*SortRes {exist := make(map[int]*SortRes)for _, v := range qy { // 遍历每一个query的分词后的token词条for i, v2 := range res { // 遍历每一个结果score := calculateTFIDF(v, v2, res)// 记录分数构成,计算每个词条对每个文档结构的scoreif _, ok := exist[i]; !ok {// 如果exist中还没存在这个词条,则进行进行初始化tmp := &SortRes{Docx:  v2,Score: score,Id:    i,}exist[i] = tmp} else {// 如果已经存在了,则进行分数的相加// 意思就是每个res中的doc对于每个token的权重之和的结果。权重的对象始终都是res中docexist[i].Score += score}}}resList := make([]*SortRes, 0)for _, v := range exist { // 构建结构体resList = append(resList, &SortRes{Docx:  v.Docx,Score: v.Score,Id:    v.Id,})}sort.Slice(resList, func(i, j int) bool { // 按照score进行排序return resList[i].Score > resList[j].Score})return resList
}
  • 演示
func TestSe(t *testing.T) {query := "王小波,徐克"InitConfig() // 初始化配置docx := fileOpen()index := BuildIndex(docx) // 创建indexres, qy := search(index, query, docx)fmt.Printf("一共%d记录,query分词结果%v\n", len(res), qy)resList := sortRes(qy, res)for i := range resList {fmt.Println(resList[i].Score, resList[i].Docx)}
}

结果:

第一行输出一共多少条搜索记录,然后是输入的query的分词结果
接着输出每一个搜索结果的score,以及对应的docx文本。

一共6记录,query分词结果[小波 王小波 徐克]
39.99999999999999 "王小波的作品《红拂夜奔》将被改编为电影,徐克执导"
20.689655172413794 "王小波经典中篇小说《绿毛水怪》将改编电影。《绿毛水怪》是王小波早期手稿作品,以天马行空的想象,极具魔幻色彩的情感脉络,独树一帜的批评、反讽,受到广大书迷的喜爱。王小波曾创作电影剧本《东宫西宫》,此后尚未有作品改编成电影。据悉,李银河将担任《绿毛水怪》电影版的文学顾问。"
8 "博纳公布新片计划 徐克将开拍《智取威虎山3D》"
3.0769230769230766 "徐克将拍摄电影版《神雕侠侣》三部曲,施南生监制。这是徐克自执导《东方不败风云再起》后,24年来再次拍摄金庸武侠作品,杨过和龙女的故事将登大银幕。自1983年香港邵氏出品制作《杨过与小龙女》电影版后,这部作品34年来都再未出现在大银幕上。"
2.222222222222222 "《抓猴》是一 部徐克导演的现代题材的3D惊悚片,剧情悬疑诡异。影片的主要故事在三个女主演身上展开,在窥视、背叛、阴谋、死亡的惊险不断中,导向一个让人意想不到的结局"
1.4906832298136643 "在去年北影节“跨界与融合—中国电影投融资高峰论坛”上,博纳副总裁丁一岚透露,徐克计划拍《智取威虎山》前传。丁一岚谈到“投资瞄准度”话题时表示博纳是一个“传统的电影公司”,“传统的电影公司会去养一个市场,不会一本万利,我们人人都期待有爆款,可爆款是建立在一将功成万骨枯的基础上,我也不指望所有的项目里面一定有爆款,所以只能按照基础的商业规则去运作每一个项目。接着我们可能启动《智取威虎山》前传,可能还是徐克来导,因为这是用一种新的方式,开发一些被大家忽略的地方。"

当然这个是一个很粗糙的demo,还有很多东西没有,比如如何merge多个倒排索引,如何存储倒排索引,分词如何更好一点,计算排名的权重如何选择和优化等等…

参考

  • https://www.jianshu.com/p/1fa4b0d9a211
  • https://www.syrr.cn/news/22044.html
  • https://chat.openai.com/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/3318.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

prometheus调整默认数据存储时间

调整kubernetes部署的prometheus数据存储时间 由于prometheus是用kuberentes部署的,没办法像传统部署方式那种直接在启动参数增加存储时间的参数。需要在configmap里或者在deployment里添加,我这里使用的方式是在deployement里添加调整存储时间的参数。…

学会在重装系统前如何备份软件,再也不怕失去珍贵的应用!

​Windows系统是电脑的重要组成部分,它不仅提供了友好的用户界面,还承担着许多关键的功能和任务,为我们提供了一个稳定、安全和效率的工作环境,使我们能够充分发挥电脑的潜力,优化工作效率和生活品质。 随着系统使…

为 GitHub 设置 SSH 密钥

1. 起因 给自己的 github 改个名,顺便就给原来 Hexo 对应的仓库也改了个名。然后发现 ubhexo clean && hexo generate && hexo deploy 失败了,报错如下: INFO Deploying: git INFO Clearing .deploy_git folder... INFO …

软件渗透测试真的很重要吗?渗透测试有哪些测试流程?

软件渗透测试是指通过模拟恶意攻击者的行为,评估软件系统中的潜在安全漏洞和弱点的活动。这种安全测试方法能够帮助开发人员和系统管理员发现并修复潜在的安全漏洞,以确保软件系统的安全性和完整性。软件渗透测试是一项高度技术性的任务,需要…

如何使用MATLAB软件完成生态碳汇涡度相关监测与通量数据分析

MATLAB MATLAB是美国MathWorks公司出品的商业数学软件,用于数据分析、无线通信、深度学习、图像处理与计算机视觉、信号处理、量化金融与风险管理、机器人,控制系统等领域。 [1] MATLAB是matrix&laboratory两个词的组合,意为矩阵工厂&a…

postgresql导入导出数据库的一些问题

新建一个数据库 别忘了添加空间数据的扩展 备份之前的数据库 注意一定要自定义表,去掉 spatial_ref_sys ,要不然需要先drop在创建,可能会报错。 一般不会去导函数,如果有个别自己创建的函数可以手动复制一下,全部导的话…

Centos 7 使用国内镜像源更新内核

内核选择参考 此博文 :https://blog.csdn.net/alwaysbefine/article/details/108931626 elrepo官网介绍的内核升级方式为: 一、按文档执行引入 elrepo库; # 1、引入公钥 rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org# 2、安…

系统学习Linux-SSH远程服务(二)

概念 安全外壳协议,提供安全可靠的远程连接 特点 ssh是工作在传输层和应用层的协议 ssh提供了一组管理命令 ssh 远程登陆 scp 远程拷贝 sftp 远程上传下载 ssh-copy-id ssh keygen 生成 提供了多种身份验证机制 身份验证机制 密码验证 需要提供密码 密…

计算机网络 day6 arp病毒 - ICMP协议 - ping命令 - Linux手工配置IP地址

目录 arp协议 arp病毒\欺骗 arp病毒的运行原理 arp病毒产生的后果: 解决方法: ICMP协议 ICMP用在哪里? ICMP协议数据的封装过程 ​编辑 为什么icmp协议封装好数据后,还要加一个ip包头,再使用ip协议再次进…

Android Hook技术实战详解

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂,风趣幽默",感觉非常有意思,忍不住分享一下给大家。 👉点击跳转到教程 前言: 什么是Android Hook技术? Android Hook技术是指在Android…

matlab重名函数调用踩坑记录

我新安装了matlab的robotics toolbox,然而调用的rotx不是我想要的函数。 我上网查了一下资料,知乎和csdn有相关的回答,但是我试了一下还是不行。它们的方法是移除路径再添加路径避免函数的冲突。相关方法放在文末的相关参考1 2。这里建议先用…

ETHERNET/IP转TCP/IP网关tcp/ip协议包含哪几层

大家好,今天我们将带大家了解一款自主研发的通讯网关,远创智控YC-EIP-TCP/IP。这是一个强大的工具,能帮助我们将ETHERNET/IP网络和TCP/IP网络连接在一起,让我们更好地管理和监控网络。 1, 首先,让我们来看看这款网关…

orbslam3 生成标定板rosrun kalibr kalibr_create_target_pdf --type

rosrun kalibr kalibr_create_target_pdf --type apriltag --nx 6 --ny 6 --tsize 0.08 --tspace 0.3小师妹要做相机视觉标定,需要制作棋盘格,无奈其电脑有些卡,对此毫无经验的博主从头开始安装(此前博主已经安装了ROS环境&#x…

leetcode 965.单值二叉树

⭐️ 题目描述 🌟 leetcode链接:单值二叉树 思路: 让当前的根节点与左孩子节点与右孩子节点判断,若相等则继续向下分治,让左孩子与右孩子当作新的根节点继续判断,直到某个节点不相等。 1️⃣ 代码&#x…

Gateway网关组件(在Spring Cloud整合Gateway(idea19版本))

Spring Cloud Gateway官网:Spring Cloud Gateway 局域网中就有网关这个概念,局域网接收数据或发送数据都要通过网关,比如使用VMware虚拟机软件搭建虚拟机集群的时候,往往我们需要选择IP段中的⼀个IP作为网关地址,网关可以对请求进行控制,提升…

【Linux系统】结合有趣的小故事让你学懂生产者消费者模型

目录 由故事引入模型故事背景供货商们的矛盾市民们和供货商之间的矛盾一市民们和供货商之间的矛盾二市民们的矛盾模型总结 生产者消费者模型为什么要使用生产者消费者模型?生产者消费者模型的特点生产者消费者模型优点 基于BlockingQueue的生产者消费者模型C queue模…

力扣 452. 用最少数量的箭引爆气球

题目来源:https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/description/ C题解1: 根据x_end排序,x_start小的在前,这样可以保证如果第 i 个球的x_end大于等于第 j 个球的x_start时,第 j 个球…

ElasticSearch文档(document)在index上的增删改查

文章目录 一、document定义:二、单条增删改查1、创建索引:2、添加文档:3、获取文档:4、更新文档:5、删除文档: 三、批量增删改查:1、批量添加文档:2、批量更新文档:3、批…

自建DNSlog服务器

DNSlog简介 在某些情况下,无法利用漏洞获得回显。但是,如果目标可以发送DNS请求,则可以通过DNS log方式将想获得的数据外带出来。 DNS log常用于以下情况: SQL盲注无回显的命令执行无回显的SSRF 网上公开提供dnslog服务有很多…

FactoryBean源码解析

文章目录 一、简介二、FactoryBean 接口的方法三、FactoryBean 与 BeanFactory 的区别四、源码解析五、实际应用 一、简介 FactoryBean 是 Spring 框架中的一个接口,用来创建特定类型的 Bean 对象。实现FactoryBean 接口就可以自定义 Bean 对象的创建过程。Factory…