BoostSeacher

news2024/12/26 14:15:28

前言:

基于Boost库的搜索引擎

为何基于Boost库?

  1. 从技术上说:这个项目用了很多Boost库的接口
  2. 从搜索引擎存储内说:存储的内容是Boost库的内容预期效果

预期效果:用户在浏览器输入关键词,浏览器显示相关结果

STEP1:导入Boos库数据到服务器

由于我们是将Boost库中的数据作为服务器的数据源,所以我们要把Boost库相关数据拉取到服务器上。

1.导入数据源到服务器

我们选择的是Boost 库中html文件作为数据源

数据源url:Index of main/release/1.78.0/source

boost官网下载文件,导入文件数据到Linux中,使用rz指令

2.解压文件   

使用指令 tar xzf 压缩包名称,得到解压后的文件夹

但这个文件夹内,有非常多的内容

我们选择doc路径下html文件夹中的内容作为数据源(里面存放的都是html文件)

建立文件夹data/input,用于存放doc/html下的文件内容

mkdir data/input

cp -r boost_1_78_0/doc/html/* data/input/

数据源准备工作完毕

STEP2:处理数据模块

在处理数据之前,需要明确,我们的数据源现在是存储在文件上的,我们想要使用它,必须把它加载到内存中,所以第一步,我们需要存放他们的文件路径

1.存放文件路径

//src_path="data/input" --存放html文件的路径
//files_list --用于保存文件路径的容器
bool enumfile(const std::string &src_path, std::vector<std::string> *files_list)
{
    // 引入boost开发库 因为c++对文件系统的支持不是很好

    // 展开boost的命名空间
    namespace fs = boost::filesystem;
    //path是一个用于处理文件操作的类
    fs::path root_path(src_path);
    //  判断路径是否存在
    if (!fs::exists(root_path))
    {
        std::cerr << "file not exists" <<  std::endl;
        return false;
    }
    // 存在 递归遍历 recursive_directory_iterator end == nullptr
    fs::recursive_directory_iterator end;
    // 筛选文件
    for (fs::recursive_directory_iterator iter(root_path); iter != end; iter++)
    {
        // is_regular_file是否为普通文件 eg:png false
        if (!fs::is_regular_file(*iter))
            continue;//跳过本次循环,筛选下一个文件
        // 是否为html文件 path.extension()
        if (iter->path().extension() != ".html")
            continue;//跳过本次循环,筛选下一个文件
        // html文件,将文件路径导入到容器中
        files_list->push_back(iter->path().string());
    }
    return true;
}

tips:

boost::filesystem::path

filesystem是一个模块,提供了许多与文件处理相关的组件

path是一个类,包含了许多与文件处理相关的接口,

例如,获取文件扩展名-->path::extension()

2.处理文件内容

我们已经获取到了想要的文件路径了,接下来就可以使用文件操作的相关接口,打开文件内容,并对文件内容做相关的处理——提取标题、内容、url

//files_list --存放文件路径的容器
//results --用于存放提取出来的文件内容的容器
//ns_util::fileutil::readfile --读取文件内容的接口
//docinfo_t 定义如下:
typedef struct docinfo
{
    std::string title;
    std::string content;
    std::string url;
} docinfo_t;

static bool parsehtml(const  std::vector< std::string> &files_list,std::vector<docinfo_t> *results)
{
    // 解析文件
    // file--本地文件路径
    for (const  std::string &file : files_list)
    {
         std::string result;
        // 1.读取文件信息
        ns_util::fileutil::readfile(file, &result);
        docinfo_t doc;
        // 2.解析文件的title
        if (!parsertitle(result, &doc.title))
        {
            continue;
        }
        // 3.解析文件的content
        if (!parsercontent(result, &doc.content))
        {
            continue;
        }
        // 4.解析文件的url
        if (!parserurl(file, &doc.url))
        {
            continue;
        }
        //解析好的内容存入容器,使用移动构造提高效率 
        results->push_back(std::move(doc));
    }
    return true;
}

提取标题

在html文件中,标题是以<title>出现</title>结尾的

举个例子:

以下html代码中,<title></title>间的白字部分就是标题

可以根据上述特性编写代码:

//file --文件内容
//title --提取的标题存放进的容器
static bool parsertitle(const  std::string &file,  std::string *title)
{
    size_t begin = file.find("<title>");//寻找title出现的位置
    if (begin ==  std::string::npos)
    {
        return false;
    }
    size_t end = file.find("</title>");//寻找</title>出现的位置
    if (end ==  std::string::npos)
    {
        return false;
    }
    if (begin > end)
    {
        return false;
    }
    begin +=  std::string("<title>").size();
    *title = file.substr(begin, end - begin);//截取标题内容
    return true;
}

 

提取content

content是以'>'开始标志的,是以'<'为结尾标志的,<>xxxx<> xxxx就是content

举个例子:下面HTML代码中标出来的白字部分就是content内容

但请注意:不是说只要出现'>',后面就是content,例如

<a name="xpressive.legal"></a><p>

'>'后出现的是'<',这是HTML语言的标签

只要'>'出现的不是'<',那就是content

根据上述规则,可以编写代码

        

//file --存放文件内容的容器
//content --存放提取内容(content)的容器
static bool parsercontent(const  std::string &file,  std::string *content)
{
    // 去标签 
    enum status
    {
        Lable,
        Content
    };
    enum status s = Lable;
    for (const char c : file)//按字符读取文件内容
    {
        switch (s)
        {
        case Lable://是标签
            if (c == '>')//内容开始的标志
                s = Content;//切换状态
            break;
        case Content:
            if (c == '<')//不是内容
                s = Lable;
            else//是内容
            {
                if (c == '\n')//将\n置为空字符,原因后文会提到
                    c == ' ';
                content->push_back(c);
            }
            break;
        default:
            break;
        }
    }

    return true;
}

提取url

这里更准确的说法,应该是拼接url,按照我们预期的效果,页面应该要显示搜索内容所在的url

我们的数据源皆来自https://www.boost.org/doc/libs/1_78_0/doc/html/

我们的容器中存放的文件路径是data/input/具体的文件名

所以我们要如此拼接:https://www.boost.org/doc/libs/1_78_0/doc/html/+具体的文件名

//file_path --文件路径
//url --用于存放拼接好的url的容器
//src_path --data/input
static bool parserurl(const  std::string &file_path,  std::string *url)
{
     std::string url_head = "https://www.boost.org/doc/libs/1_78_0/doc/html";
     std::string url_tail = file_path.substr(src_path.size());
    *url = url_head + url_tail;
    return true;
}

3.保存处理好的文件内容

我们已经将每一个文件所对应的内容存放在vector<docinfo_t>中了,接下来需要对一个个的docinfo_t进行格式化处理,并将其写入磁盘,以待使用

为什么要进行格式化处理?方便内容提取,在后文中会有具体体现

如何格式化?以特定字符作为内容内title content url的分隔符,以特定字符作为内容与内容之间的分隔符

将vector<docinfo_t>中的内容作格式化处理

title\3content\3url\n-->一个完整的内容

写到data/raw_html/raw.txt

//results --存放结构体数据的容器
//output --写入磁盘的文件路径
bool savehtml(const  std::vector<docinfo_t> &results, const  std::string &output)
{
#define sep '\3'    // title\3content\3url\n
    // 按照二进制方式写入
     std::ofstream out(output,  std::ios::out |  std::ios::binary);
    if (!out.is_open())
    {
         std::cerr << "open " << output << "failed!" <<  std::endl;
        return false;
    }

    for (const docinfo_t &item : results)//读取每一个结构体信息
    {
         std::string out_string;
        out_string = item.title;
        out_string += sep;    //title\3
        out_string += item.content;    //title\3content
        out_string += sep;    //title\3content\3
        out_string += item.url;    //title\3content\3url
        out_string += '\n';    //title\3content\3url\n
        out.write(out_string.c_str(), out_string.size());
    }
    out.close();
    return true;
}

STEP3:构建索引模块

何为索引?即搜索引擎的查找规则

举个例子:当我们在浏览器输入“hello world”时,浏览器会显示大量页面,从hello world 到 页面,使这一过程发生的就是索引

索引规则有如下2种:

  1. 正排索引:根据文档id找到文档内容,所以它的底层是vector<docinfo_t>,下标就是文档id,里面存的就是文档内容
  2. 倒排索引:根据关键词找到文档id 并通过文档id找到文档内容,他是根据关键词在文章中出现的权重为基础,构建索引的

我们要对谁构建索引?存在磁盘上的格式化的数据源

构建正排索引

//line --存放文件内容的容器
//out --存放切分结果的容器
//forwardindex --正排索引  类型vector<docinfo>
  struct docinfo
    {
        std::string title;
        std::string content;
        std::string url;
        uint64_t docid;
    };
//...文件读取操作
docinfo *bulidforwardindex(const std::string &line)
        {
            // 对line进行 title content url 的分词 
            std::vector<std::string> out;
            const std::string sep = "\3";    //以'\3'为分割标志
            ns_util::stringutil::split(line, &out, sep);    //调用切分字符的接口
            if(out.size()!=3){
                return nullptr;
            }
            docinfo doc;
            doc.title = out[0];
            doc.content = out[1];
            doc.url = out[2];
            doc.docid = forwardindex.size();
            forwardindex.push_back(std::move(doc));
            //std::cout<<(forwardindex[forwardindex.size()-1].url)<<std::endl;//表明正派建立成功
            return &forwardindex.back();    //返回构建好的一组数据,供建立倒排索引使用
        }

构建倒排索引

//wordmap --unordered_map<string,wordcnt>类型 用于存储被划分词在标题与内容中出现的次数 
//invertedindex --unordered_map<string,invertedlist>类型 用于表示关键词与网页间的对应关系 
//ilist --invertedlist类型,typedef vector<invertedelem> invertedlist 
//item --invertedelem类型
  struct invertedelem
    {
        uint64_t docid;
        std::string word;
        int weight;
        invertedelem() : weight(0)
        {
        }
    };
bool buildinvertedindex(const docinfo &doc)

        {
            struct wordcnt    //用于计算被划分的词在标题/内容出现的次数
            {
                int titlecnt;    //用于计算被划分的标题词在标题中出现的次数    
                int contentcnt;    //用于计算被划分的内容词在内容中出现的次数
                wordcnt() : titlecnt(0), contentcnt(0)
                {
                }
            };
            std::string title = doc.title;    //取出完整的标题
            std::string content = doc.content;    //取出完整的内容

            // jieba分词--title
            std::vector<std::string> titlecut;
            ns_util::jiebautil::cutstring(title, &titlecut);
            // 拿到了jieba为我们分好的词 --title
            std::unordered_map<std::string, wordcnt> wordmap; 
            for (auto &s : titlecut)    //遍历被划分的标题词
            {
                boost::to_lower(s);    //不区分大小写
                wordmap[s].titlecnt++;    //记录标题词在标题出现次数
            }

            // jieba分词--content
            std::vector<std::string> contentcut;
            ns_util::jiebautil::cutstring(content, &contentcut);
            for (auto &s : contentcut)    //遍历被划分的内容词
            {
                boost::to_lower(s);
                wordmap[s].contentcnt++;    //记录内容词在内容出现次数
            }

            // word -> id word weight
#define X 10
#define Y 1
            //构建倒排索引    被划分的词才是主角
            for (auto &wmap : wordmap)
            {
                invertedelem item;
                item.docid = doc.docid;
                item.word = wmap.first;
                //构建各个词在此"网页"中的权重 --标题:10/次 内容:1/次
                item.weight = X * wmap.second.titlecnt + Y * wmap.second.contentcnt; 
                //构建被划分的词与"网页"的关系
                invertedlist &ilist = invertedindex[wmap.first];
                // std::cout<<"invert success"<<std::endl;//表明创建倒排成功
                ilist.push_back(std::move(item));
            }
            return true;
        }

现在,forwardindex 与invertedlist都已经按照各自的索引规则存储好了数据,上层想要调用使用这里面的数据,还需要我们提供两个接口

//正排索引的调用接口  
//docid --想要查找的内容对应的id  
docinfo *getforwardindex(uint64_t docid)
        {
            if (docid >= forwardindex.size())
            {
                std::cerr << "no expected doc" << std::endl;
                return nullptr;
            }
            return &forwardindex[docid];
        }

//倒排索引的调用接口
//word: 以word关键字为key值对相应内容做检索
invertedlist *getinvertedlist(const std::string &word)
        {
            auto iter = invertedindex.find(word);
            if (invertedindex.end() == iter)
            {
                return nullptr;
            }
            return &(iter->second);
        }

STEP4:编写服务端模块

服务端的作用是:接受客户端传过来的关键词,并对关键词进行分词,利用构建好的索引,并返回相关页面

工作流程:

InitSearcher --初始化工作

创建单例,构建索引(文件-->内存)

    //Index --Index对象
    void initsearcher()
    {
        Index = ns_index::index::getinstance();    //获取单例
        if(Index==nullptr)
        {
            std::cerr<<"getinstance fail"<<std::endl;
            exit(1);
        }
        std::cout<<"get instance success"<<std::endl;    
        Index->buildindex(input);    //创建索引
    }

Search --搜索工作

对用户的关键词进行分词,然后查倒排,将结果存放到inverted_list_all当中去

对查找结果按照权重进行排序

根据文档id,查询相关结果

为了达到像真实网页一般显示,我们要对内容做摘要

利用 Json对结果进行序列化,返回给客户端

//query --用户传进来的关键词
//jsonstring --要返回的结果   
void search(const  std::string &query,  std::string *jsonstring)
    {
        // 对query进行分词
         std::vector< std::string> cutwords;
        ns_util::jiebautil::cutstring(query, &cutwords);
        ns_index::invertedlist inverted_list_all;

        for (auto word : cutwords)
        {
             boost::to_lower(word);
            ns_index::invertedlist *inverted_list;
            inverted_list = Index->getinvertedlist(word);
            if (inverted_list == nullptr)
            {
                std::cerr<<"get inverted_list err"<<std::endl;
                continue;
            }
            inverted_list_all.insert(inverted_list_all.end(), inverted_list->begin(), inverted_list->end());
        }
        // 按照相关性对内容进行排序
        std::sort(inverted_list_all.begin(), inverted_list_all.end(), [](const ns_index::invertedelem &e1, const ns_index::invertedelem &e2)
                  { return e1.weight > e2.weight; });

        //利用Json对结果进行反序列化,并将结果返回给上层     
        Json::Value root;
        for (auto &item : inverted_list_all)
        {
            ns_index::docinfo *doc = Index->getforwardindex(item.docid);
            if (nullptr == doc)
            {
                std::cerr<<"get doc fail"<<std::endl;
                continue;
            }
            Json::Value elem;
            elem["title"] = doc->title;
            elem["desc"] = getdesc(doc->content, item.word);    //对内容做摘要
            elem["url"] = doc->url;
            //here~~!!

            //for debug
            elem["weight"]=item.weight;
            elem["docid"]=(int)doc->docid;

            root.append(elem);

        }
        Json::StyledWriter writer;
        *jsonstring = writer.write(root);
    }

std:: string getdesc(const std:: string &content, const  std::string &word)
    {
        int prev_step = 50;
        int next_step = 100;
        //int pos = content.find(word);//大小写问题 split->Split
        auto iter=std::search(content.begin(),content.end(),word.begin(),word.end(),[](int x,int y){return std::tolower(x)==std::tolower(y);});
        if(iter==content.end())
        {
            return "none1";
        }
        int pos=std::distance(content.begin(),iter);
        int start = 0;
        int end = content.size() - 1;
        if (pos - prev_step > start)
        {
            start = pos - prev_step;
        }
        if (pos + next_step < end)
        {
            end = pos + next_step;
        }
        if (start > end)
            return "none2";
        std:: string desc = content.substr(start, end - start);
        return desc;
    }

这里的服务端模块,其实就是大量的在调用索引模块的接口,服务端是上层,索引模块是下层

为方便理解,下图简单勾勒出了二者关系

server-index关系图:

STEP5:编写http服务模块

http服务模块,位于应用层,是整个服务器的最上层,具体工作是:启动服务器,完成socket编程(创建套接字、绑定套接字、监听套接字、等待连接),接受客户端请求,返回服务器结果。

#include "searcher.hpp"
#include "cpphttplib/httplib.h"
const std::string root_path = "./wwwroot";

int main()
{
    ns_searcher::searcher sear;
    sear.initsearcher();
        httplib::Server svr;
        svr.set_base_dir(root_path.c_str());
        svr.Get("/s", [&sear](const httplib::Request &req, httplib::Response &rsp)
                {
                    if (!req.has_param("word"))
                    {
                        rsp.set_content("必须要有搜索关键字!", "text/plain; charset=utf-8");
                        return;
                    }
                    std::string word = req.get_param_value("word");              
                    std::string json_string;
                    sear.search(word, &json_string);
                    rsp.set_content(json_string, "application/json");
                });
        svr.listen("0.0.0.0", 18088);
        return 0;
}

STEP6:部署日志到服务器中

部署日志信息是为了监控服务器状态,方便对服务端的管理。

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

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

相关文章

问题记录:idea中的目标字节码版本总是自动更改为1.5

问题描述&#xff1a; 图中画圈的地方应该是1.8 但是总是自动被还原到1.5 解决方法&#xff1a;在pom文件中加入,注意加入到内中&#xff0c;作为子节点 <build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><version…

Python编程异步爬虫实战案例

aiohttp异步爬取实战 案例介绍 链接为https://spa5.scrape.center&#xff0c;页面如下图所示&#xff1a; 这是一个图书网站&#xff0c;整个网站包含数千本图书信息&#xff0c;网站数据是JavaScript渲染而得的&#xff0c;数据可以通过Ajax接口获取&#xff0c;并且接口没…

关于YOLOv9项目中使用已有模块自由改进的教程

专栏介绍&#xff1a;YOLOv9改进系列 | 包含深度学习最新创新&#xff0c;助力高效涨点&#xff01;&#xff01;&#xff01; 1. 文件说明 在YOLOv5-v9&#xff0c;模型的结构是以yaml文件的存储。我们可以在原有的yaml基础上增、减、改模块&#xff0c;创作我们自己的模型。 …

蓝桥杯刷题-串的处理

串的处理 代码 s input().split() l_new [] for i in s:i list(i)new""for j in range(len(i)-1): # 遍历newi[j]if i[j].isdigit() and i[j1].isalpha(): # 在字母和数字之间添加“_”new_if i[j].isalpha() and i[j1].isdigit(): # 同上new_newi[-1]l_new.appe…

高德地图加遮罩灯光效果

最近做大屏展示&#xff0c;UI突然有一个大胆的想法&#xff0c;他想把地图做成那种有点灯光照在上面的感觉&#xff0c;经过几番尝试后终于实现了相关效果&#xff0c;特此分享&#xff1b; 效果图 实现方式-css /* 高德地图加载成功有一个amap-layers类名的标签&#xff0c;…

论文不再难写:ChatGPT带你飞

目录 一、ChatGPT&#xff1a;您的个人写作顾问 1.打造个性化的论文框架 2.提升写作质量与效率 3.丰富表达方式&#xff0c;增强文章吸引力 二、结语&#xff1a;开启写作新篇章 在这个信息迅速发展、日新月异的时代里&#xff0c;写作已成为我们表达自我、在学术界或职场…

406. 根据身高重建队列(力扣LeetCode)

文章目录 406. 根据身高重建队列题目描述贪心算法代码 406. 根据身高重建队列 题目描述 假设有打乱顺序的一群人站成一个队列&#xff0c;数组 people 表示队列中一些人的属性&#xff08;不一定按顺序&#xff09;。每个 people[i] [hi, ki] 表示第 i 个人的身高为 hi &…

瑞萨杯(一)

基础信息 RA6M5&#xff1a;ARM V8架构&#xff0c;24MHz外置晶振&#xff0c;200MHz主频 SCI&#xff08;Serial Communications Interface&#xff09;&#xff0c;意为串行通信接口 参考链接&#xff1a; 【瑞萨RA系列FSP库开发】RASCKeil的环境搭建_瑞萨ra mdk-CSDN博客…

24 OpenCV直方图反向投影

文章目录 参考反向投影作用calceackProject 反向投影mixchannels 通道图像分割示例 参考 直方图反向投影 反向投影 反向投影是反映直方图模型在目标图像中的分布情况简单点说就是用直方图模型去目标图像中寻找是否有相似的对象。通常用HSV色彩空间的HS两个通道直方图模型 作用…

Excel表格中函数CEILING的用法

这里写目录标题 CEILING函数将参数Number向上舍入 CEILING函数将参数Number向上舍入 CEILING函数是将参数Number向上舍入&#xff0c;沿绝对值增大的方向&#xff0c;为最接近的 significance 的倍数。其语言表达为&#xff1a;CEILING(number, significance)

[flask]cookie的基本使用/

彻底理解 Cookie - 知乎 (zhihu.com) 是什么 cookie是当你浏览某个网站的时候&#xff0c;由web服务器存储在你的机器硬盘上的一个小的文本文件。它其中记录了你的用户名、密码、浏览的网页、停留的时间等等信息。当你再次来到这个网站时&#xff0c;web服务器会先看看有没有…

简析:创业老隋推荐的蓝海项目到底好不好做?

在当前的创业浪潮中&#xff0c;网红创业凭借其独特的优势和影响力&#xff0c;成为了不少年轻人追逐的梦想。老隋&#xff0c;作为网红创业领域的佼佼者&#xff0c;凭借其丰富的经验和敏锐的洞察力&#xff0c;经常为粉丝们推荐一些看似前景广阔的蓝海项目。那么&#xff0c;…

吴恩达2022机器学习专项课程(一) 3.3 成本函数的公式

问题预览 模型的参数&#xff08;w和b&#xff09;有什么作用&#xff1f;不同的w和b对线性回归模型有什么影响&#xff1f;训练集里的y和线性回归模型预测的y&#xff08;y帽&#xff09;的区别是什么&#xff1f;成本函数的作用是什么&#xff1f;成本函数的公式是什么&…

众邦科技CRMEB商城商业版任意文件写入getshell 0day

代码审计 接口&#xff1a;/adminapi/system/crud 处理的代码如下 public function save(SystemCrudDataService $service, $id 0){$data $this->request->postMore([[pid, 0],//上级菜单id[menuName, ],//菜单名[tableName, ],//表名[modelName, ],//模块名称[table…

代码随想录训练营第55天 | LeetCode 583. 两个字符串的删除操作、​​​​​​LeetCode 72. 编辑距离、总结

目录 LeetCode 583. 两个字符串的删除操作 文章讲解&#xff1a;代码随想录(programmercarl.com) 视频讲解&#xff1a;LeetCode&#xff1a;583.两个字符串的删除操_哔哩哔哩_bilibili 思路 ​​​​​​LeetCode 72. 编辑距离 文章讲解&#xff1a;代码随想录(programm…

恢复 Linux 上已删除的文件:extundelete 、PhotoRec (***)

为什么Linux的命令 rm 没有回收站呢&#xff1f;Trash-Cli&#xff1a;Linux 命令行回收站工具 &#xff08;***&#xff09; https://blog.csdn.net/ken2232/article/details/136981360 后悔药 使用回收站&#xff0c;这是第一次的后悔药&#xff1b; 使用 extundelete 等&a…

网络电视盒子哪个品牌好?2024畅销电视盒子排行榜

电视盒子的品牌和产品非常多&#xff0c;让新手在选购时难度增大&#xff0c;大部分消费者在此时会选择参考销量排名情况&#xff0c;小编这次结合各个电商平台的销量和用户评价整理了电视盒子排行榜&#xff0c;想买电视盒子不知道网络电视盒子哪个品牌好可以收藏。 TOP 1.泰捷…

如何做试卷的ocr识别算法?

要实现试卷的OCR识别算法&#xff0c;可以采取以下步骤&#xff1a; 数据预处理&#xff1a;首先需要对试卷图片进行预处理&#xff0c;包括图像降噪、灰度化、二值化等操作&#xff0c;以便提高后续文字识别的准确性。 文字检测&#xff1a;利用文字检测算法定位试卷图片中的…

.NET Framework 服务实现监控可观测性最佳实践

环境信息 系统环境&#xff1a;Windows Server开发语言&#xff1a;.NET Framework > 4.6.1APM探针包&#xff1a;ddtrace 准备工作 安装 Datakit 主机部署&#xff1a; 主机安装 - 观测云文档 打开采集 APM 采集器 Windows 主机配置 # 到如下路径&#xff0c;把ddtr…

【探索多模态视觉问答】数据集概览及特点分析

【探索多模态视觉问答】数据集概览及特点分析 引言 在计算机视觉和自然语言处理领域&#xff0c;视觉问答&#xff08;VQA&#xff09;是一个重要的任务&#xff0c;旨在让计算机理解图像内容并回答关于图像的问题。为了促进和评估多模态模型在视觉问答任务上的表现&#xff…