基于向量数据库的文档检索实战

news2024/11/30 8:38:19

在这里插入图片描述

推荐:用 NSDT编辑器 快速搭建可编程3D场景

在过去的六个月里,我一直在 A 系列初创公司 Voxel51 工作,该公司是开源计算机视觉工具包 FiftyOne 的创建者。 作为一名机器学习工程师和开发人员布道者,我的工作是倾听我们的开源社区的声音,并为他们提供他们所需要的东西——新功能、集成、教程、研讨会,等等。

几周前,我们在 FiftyOne 中添加了对矢量搜索引擎和文本相似性查询的原生支持,以便用户可以通过简单的自然语言在其(通常是庞大的——包含数百万或数千万个样本)数据集中找到最相关的图像查询。

这让我们陷入了一个奇怪的境地:现在使用开源 FiftyOne 的人们可以轻松地通过自然语言查询来搜索数据集,但使用我们的文档仍然需要传统的关键字搜索。

我们有很多文档,它们各有利弊。 作为一名用户,我有时发现,鉴于文档数量巨大,准确找到我要查找的内容需要比我想要的更多时间。

我不会让它飞……所以我在业余时间构建了这个:

在这里插入图片描述

从命令行按语义搜索公司的文档

因此,以下是我如何将我们的文档转变为语义可搜索的矢量数据库的方法:

  • 将所有文档转换为统一格式
  • 将文档分割成块并添加一些自动清理
  • 计算每个块的嵌入
  • 从这些嵌入生成向量索引
  • 定义索引查询
  • 将所有内容封装在用户友好的命令行界面和 Python API 中

你可以在 voxel51/fiftyone-docs-search 存储库中找到本文的所有代码,并且可以使用 pip install -e 轻松在编辑模式下在本地安装软件包。

更好的是,如果你想使用这种方法为自己的网站实现语义搜索,你可以跟着做! 以下是你需要的操作:

  • 安装 openai Python 包并创建一个帐户:你将使用此帐户将文档和查询发送到推理端点,该端点将为每段文本返回一个嵌入向量。
  • 安装 qdrant-client Python 包并通过 Docker 启动 Qdrant 服务器:你将使用 Qdrant 为文档创建本地托管的向量索引,并将针对该索引运行查询。 Qdrant 服务将在 Docker 容器内运行。

1、将文档转换为统一格式

我公司的文档均以 HTML 文档形式托管在网站上。 一个自然的起点是使用 Python 的 requests 库下载这些文档并使用 Beautiful Soup 解析文档。

然而,作为一名开发人员(以及我们许多文档的作者),我认为我可以做得更好。 我的本地计算机上已经有 GitHub 存储库的工作克隆,其中包含用于生成 HTML 文档的所有原始文件。 我们的一些文档是用 Sphinx ReStructured Text (RST) 编写的,而其他文档(例如教程)则从 Jupyter Notebook 转换为 HTML。

我(错误地)认为我越接近 RST 和 Jupyter 文件的原始文本,事情就会越简单。

1.1 RST文档

在 RST 文档中,节由仅由 =、- 或 _ 字符串组成的行来划分。 例如,以下是《FiftyOne 用户指南》中的一份文档,其中包含所有三个描述符:
在这里插入图片描述

来自开源 FiftyOne Docs 的 RST 文档

然后我可以删除所有 RST 关键字,例如 toctree、 code-block 和 button_link(还有更多),以及伴随关键字(新块的开头)的 :、 :: 和 … ,或块描述符。

链接也很容易处理:

no_links_section = re.sub(r"<[^>]+>_?","", section)

当我想从 RST 文件中提取部分锚点时,事情开始变得危险。 我们的许多部分都明确指定了锚点,而其他部分则在转换为 HTML 期间进行推断。

这是一个例子:

.. _brain-embeddings-visualization:

Visualizing embeddings
______________________

The FiftyOne Brain provides a powerful
:meth:`compute_visualization() <fiftyone.brain.compute_visualization>` method
that you can use to generate low-dimensional representations of the samples
and/or individual objects in your datasets.

These representations can be visualized natively in the App's
:ref:`Embeddings panel <app-embeddings-panel>`, where you can interactively
select points of interest and view the corresponding samples/labels of interest
in the :ref:`Samples panel <app-samples-panel>`, and vice versa.

.. image:: /images/brain/brain-mnist.png
   :alt: mnist
   :align: center

There are two primary components to an embedding visualization: the method used
to generate the embeddings, and the dimensionality reduction method used to
compute a low-dimensional representation of the embeddings.

Embedding methods
-----------------

The `embeddings` and `model` parameters of
:meth:`compute_visualization() <fiftyone.brain.compute_visualization>`
support a variety of ways to generate embeddings for your data:

在我们的用户指南文档的 Brain.rst 文件中(上面复制了其中的一部分),可视化嵌入部分有一个由 … _brain-embeddings-visualization: 指定的锚点 #brain-embeddings-visualization, 然而,紧随其后的嵌入方法小节给出了一个自动生成的锚点。

另一个很快出现的困难是如何处理 RST 中的表。 列表相当简单。 例如,下面是我们的 View Stages 备忘单中的列表:

.. list-table::

   * - :meth:`match() <fiftyone.core.collections.SampleCollection.match>`
   * - :meth:`match_frames() <fiftyone.core.collections.SampleCollection.match_frames>`
   * - :meth:`match_labels() <fiftyone.core.collections.SampleCollection.match_labels>`
   * - :meth:`match_tags() <fiftyone.core.collections.SampleCollection.match_tags>`

另一方面,网格表很快就会变得混乱。 它们为文档编写者提供了极大的灵活性,但同样的灵活性也使解析它们变得很痛苦。 从我们的过滤备忘单中获取此表:

+-----------------------------------------+-----------------------------------------------------------------------+
| Operation                               | Command                                                               |
+=========================================+=======================================================================+
| Filepath starts with "/Users"           |  .. code-block::                                                      |
|                                         |                                                                       |
|                                         |     ds.match(F("filepath").starts_with("/Users"))                     |
+-----------------------------------------+-----------------------------------------------------------------------+
| Filepath ends with "10.jpg" or "10.png" |  .. code-block::                                                      |
|                                         |                                                                       |
|                                         |     ds.match(F("filepath").ends_with(("10.jpg", "10.png"))            |
+-----------------------------------------+-----------------------------------------------------------------------+
| Label contains string "be"              |  .. code-block::                                                      |
|                                         |                                                                       |
|                                         |     ds.filter_labels(                                                 |
|                                         |         "predictions",                                                |
|                                         |         F("label").contains_str("be"),                                |
|                                         |     )                                                                 |
+-----------------------------------------+-----------------------------------------------------------------------+
| Filepath contains "088" and is JPEG     |  .. code-block::                                                      |
|                                         |                                                                       |
|                                         |     ds.match(F("filepath").re_match("088*.jpg"))                      |
+-----------------------------------------+-----------------------------------------------------------------------+

在表中,行可以占据任意行数,列的宽度可以不同。 网格表单元格内的代码块也很难解析,因为它们占用多行空间,因此它们的内容散布在其他列的内容中。 这意味着在解析过程中需要有效地重建这些表中的代码块。

不是世界末日。 但也并不理想。

1.2 Jupyter

Jupyter Notebook 的解析相对简单。 我能够将 Jupyter 笔记本的内容读入字符串列表,每个单元格一个字符串:

import json
ifile = "my_notebook.ipynb"
with open(ifile, "r") as f:
    contents = f.read()
contents = json.loads(contents)["cells"]
contents = [(" ".join(c["source"]), c['cell_type'] for c in contents]

此外,这些部分由以 # 开头的 Markdown 单元格划分。

尽管如此,考虑到 RST 带来的挑战,我决定转向 HTML 并平等对待我们所有的文档。

1.3 HTML文档

我使用 bashgenerate_docs.bash 从本地安装构建了 HTML 文档,并开始使用 BeautifulSoup 解析它们。 然而,我很快意识到,当 RST 代码块和带有内联代码的表格转换为 HTML 时,尽管它们正确呈现,但 HTML 本身却非常笨拙。 以我们的过滤备忘单为例。

当在浏览器中呈现时,过滤备忘单的日期和时间部分之前的代码块如下所示:

在这里插入图片描述

开源 FiftyOne 文档中备忘单的屏幕截图

然而,原始 HTML 看起来像这样:
在这里插入图片描述

RST 备忘单转换为 HTML

这并非不可能解析,但也远非理想。

1.4 Markdown

幸运的是,我能够通过使用 markdownify 将所有 HTML 文件转换为 Markdown 来克服这些问题。 Markdown 具有一些关键优势,使其最适合这项工作。

  • 比 HTML 更干净:代码格式从 Spaghetti 字符串简化为前后用单个 ` 标记的内联代码片段,并且代码块在前后用三引号 `` 标记。 这也使得拆分为文本和代码变得容易。
  • 仍然包含锚点:与原始 RST 不同,此 Markdown 包含节标题锚点,因为隐式锚点已经生成。 这样,我不仅可以链接到包含结果的页面,还可以链接到该页面的特定部分或子部分。
  • 标准化:Markdown 为初始 RST 和 Jupyter 文档提供了基本统一的格式,使我们能够在矢量搜索应用程序中对其内容进行一致的处理。

你们中的一些人可能知道用于使用 LLM 构建应用程序的开源库 LangChain,并且可能想知道为什么我不只使用 LangChain 的文档加载器和文本拆分器。 答案是:我需要更多的控制!

2、文档处理

将文档转换为 Markdown 后,我开始清理内容并将它们分成更小的部分。

2.1 清理

清理主要是去除不必要的元素,包括:

  • 页眉和页脚
  • 表格行和列脚手架 - 例如 |select()| 中的 | select_by()|
  • 额外的换行符
  • 链接
  • 图片
  • Unicode字符
  • 粗体 — 即 文本 → 文本

我还删除了文档中具有特殊含义的字符转义的转义字符: _ 和 *。 前者用在许多方法名称中,后者像往常一样用在乘法、正则表达式模式和许多其他地方:

document = document.replace("\_", "_").replace("\*", "*")

2.2 将文档拆分为语义块

清理完文档的内容后,我开始将文档分成小块。

首先,我将每个文档分成几个部分。 乍一看,这似乎可以通过查找任何以 # 字符开头的行来完成。 在我的应用程序中,我没有区分 h1、h2、h3 等(#、##、###),因此检查第一个字符就足够了。 然而,当我们意识到 # 也被用来允许在 Python 代码中添加注释时,这种逻辑就会给我们带来麻烦。

为了绕过这个问题,我将文档分为文本块和代码块:

text_and_code = page_md.split('```')
text = text_and_code[::2]
code = text_and_code[1::2]

然后我用 # 标识新部分的开头,以在文本块中开始一行。 我从这一行中提取了部分标题和锚点:

def extract_title_and_anchor(header):
    header = " ".join(header.split(" ")[1:])
    title = header.split("[")[0]
    anchor = header.split("(")[1].split(" ")[0]
    return title, anchor

并将每个文本或代码块分配给适当的部分。

最初,我还尝试将文本块拆分为段落,假设由于一个部分可能包含有关许多不同主题的信息,因此整个部分的嵌入可能与仅涉及其中一个主题的文本提示的嵌入不同。 然而,这种方法导致大多数搜索查询的顶级匹配不成比例地是单行段落,结果证明这作为搜索结果并没有提供太多信息。

你可以查看随附的 GitHub 存储库,了解这些方法的实现,也可以在自己的文档中尝试这些方法!

3、使用 OpenAI 嵌入文本和代码块

通过转换、处理文档并拆分为字符串,我为每个块生成了一个嵌入向量。 由于大型语言模型本质上是灵活且通常具有能力的,因此我决定将文本块和代码块与文本片段同等对待,并将它们嵌入到相同的模型中。

我使用 OpenAI 的 text-embedding-ada-002 模型,因为它易于使用,在所有 OpenAI 嵌入模型中实现了最高的性能(在 BEIR 基准上),而且也是最便宜的。 事实上,它非常便宜(0.0004 美元/1K tokens),为 FiftyOne 文档生成所有嵌入仅花费几美分! 正如 OpenAI 自己所说,“我们建议在几乎所有用例中使用 text-embedding-ada-002。 它更好、更便宜、使用更简单。”

通过此嵌入模型,你可以生成表示任何输入提示的 1536 维向量,最多 8,191 个标记(大约 30,000 个字符)。

首先,你需要创建一个 OpenAI 帐户,在 这里 生成 API 密钥,然后将此 API 密钥导出为环境变量:

export OPENAI_API_KEY="<MY_API_KEY>"

你还需要安装 openai Python 库:

pip install openai

我为 OpenAI 的 API 封装了一个函数,它接受文本提示并返回一个嵌入向量:

MODEL = "text-embedding-ada-002"

def embed_text(text):
    response = openai.Embedding.create(
        input=text,
        model=MODEL
    )
    embeddings = response['data'][0]['embedding']
    return embeddings

要为所有文档生成嵌入,我们只需将此函数应用于所有文档中的每个小节(文本和代码块)。

4、创建 Qdrant 矢量索引

有了嵌入,我创建了一个向量索引来搜索。 我选择使用 Qdrant 的原因与我们选择向 FiftyOne 添加原生 Qdrant 支持的原因相同:它是开源、免费且易于使用的。

要开始使用 Qdrant,你可以提取预构建的 Docker 映像并运行容器:

docker pull qdrant/qdrant
docker run -d -p 6333:6333 qdrant/qdrant

此外,你还需要安装 Qdrant Python 客户端:

pip install qdrant-client

我创建了 Qdrant 集合:

import qdrant_client as qc
import qdrant_client.http.models as qmodels

client = qc.QdrantClient(url="localhost")
METRIC = qmodels.Distance.DOT
DIMENSION = 1536
COLLECTION_NAME = "fiftyone_docs"

def create_index():
    client.recreate_collection(
    collection_name=COLLECTION_NAME,
    vectors_config = qmodels.VectorParams(
            size=DIMENSION,
            distance=METRIC,
        )
    )

然后为每个小节(文本或代码块)创建一个向量:

import uuid
def create_subsection_vector(
    subsection_content,
    section_anchor,
    page_url,
    doc_type
    ):

    vector = embed_text(subsection_content)
    id = str(uuid.uuid1().int)[:32]
    payload = {
        "text": subsection_content,
        "url": page_url,
        "section_anchor": section_anchor,
        "doc_type": doc_type,
        "block_type": block_type
    }
    return id, vector, payload

对于每个向量,你可以提供额外的上下文作为负载的一部分。 在本例中,我包含了可以找到结果的 URL(和锚点)、文档类型,以便用户可以指定是否要搜索所有文档,或者仅搜索某些类型的文档以及内容 生成嵌入向量的字符串的。 我还添加了块类型(文本或代码),因此如果用户正在寻找代码片段,他们可以根据该目的定制搜索。

然后我将这些向量添加到索引中,一次一页:

def add_doc_to_index(subsections, page_url, doc_type, block_type):
    ids = []
    vectors = []
    payloads = []
    
    for section_anchor, section_content in subsections.items():
        for subsection in section_content:
            id, vector, payload = create_subsection_vector(
                subsection,
                section_anchor,
                page_url,
                doc_type,
                block_type
            )
            ids.append(id)
            vectors.append(vector)
            payloads.append(payload)
    
    ## Add vectors to collection
    client.upsert(
        collection_name=COLLECTION_NAME,
        points=qmodels.Batch(
            ids = ids,
            vectors=vectors,
            payloads=payloads
        ),
    )

5、查询索引

创建索引后,可以通过使用相同的嵌入模型嵌入查询文本,然后在索引中搜索相似的嵌入向量来完成对索引文档的搜索。 借助 Qdrant 矢量索引,可以使用 Qdrant 客户端的 search() 命令执行基本查询。

为了使我公司的文档可搜索,我希望允许用户按文档的部分以及编码的块类型进行过滤。 用向量搜索的术语来说,过滤结果同时仍确保返回预定数量的结果(由 top_k 参数指定)被称为预过滤。

为了实现这一目标,我编写了一个过滤器:

def _generate_query_filter(query, doc_types, block_types):
    """Generates a filter for the query.
    Args:
        query: A string containing the query.
        doc_types: A list of document types to search.
        block_types: A list of block types to search.
    Returns:
        A filter for the query.
    """
    doc_types = _parse_doc_types(doc_types)
    block_types = _parse_block_types(block_types)

    _filter = models.Filter(
        must=[
            models.Filter(
                should= [
                    models.FieldCondition(
                        key="doc_type",
                        match=models.MatchValue(value=dt),
                    )
                for dt in doc_types
                ],
        
            ),
            models.Filter(
                should= [
                    models.FieldCondition(
                        key="block_type",
                        match=models.MatchValue(value=bt),
                    )
                for bt in block_types
                ]  
            )
        ]
    )

    return _filter

内部的 _parse_doc_types() 和 _parse_block_types() 函数处理参数为字符串或列表值或为 None 的情况。

然后我编写了一个函数 query_index(),它接受用户的文本查询、预过滤、搜索索引,并从有效负载中提取相关信息。 该函数返回(url、内容、分数)形式的元组列表,其中分数表示结果与查询文本的匹配程度。

def query_index(query, top_k=10, doc_types=None, block_types=None):
    vector = embed_text(query)
    _filter = _generate_query_filter(query, doc_types, block_types)
    
    results = CLIENT.search(
        collection_name=COLLECTION_NAME,
        query_vector=vector,
        query_filter=_filter,
        limit=top_k,
        with_payload=True,
        search_params=_search_params,
    )

    results = [
        (
            f"{res.payload['url']}#{res.payload['section_anchor']}",
            res.payload["text"],
            res.score,
        )
        for res in results
    ]

    return results

6、封装搜索功能

最后一步是为用户提供一个干净的界面,以便对这些“矢量化”文档进行语义搜索。

我编写了一个函数 print_results(),它接受查询、 query_index() 的结果和得分参数(是否打印相似度分数),并以易于解释的方式打印结果。 我使用丰富的 Python 包来格式化终端中的超链接,以便在支持超链接的终端中工作时,单击超链接将在默认浏览器中打开页面。 如果需要的话,我还使用网络浏览器自动打开顶部结果的链接。

在这里插入图片描述

使用丰富的超链接显示搜索结果

对于基于 Python 的搜索,我创建了一个类 FiftyOneDocsSearch 来封装文档搜索行为,以便一旦实例化 FiftyOneDocsSearch 对象(可能使用搜索参数的默认设置):

from fiftyone.docs_search import FiftyOneDocsSearch
fosearch = FiftyOneDocsSearch(open_url=False, top_k=3, score=True)

你可以通过调用此对象在 Python 中进行搜索。 例如,要查询文档“如何加载数据集”,只需运行:

fosearch(“How to load a dataset”)

在这里插入图片描述

在 Python 流程中语义搜索公司的文档

我还使用 argparse 来通过命令行提供此文档搜索功能。 安装该软件包后,可以通过 CLI 搜索文档:

fiftyone-docs-search query "<my-query>" <args 

只是为了好玩,因为搜索查询有点麻烦,我向我的 .zsrch 文件添加了一个别名:

alias fosearch='fiftyone-docs-search query'

使用此别名,可以使用以下命令从命令行搜索文档:

fosearch "<my-query>" args

7、结束语

进入这个阶段,我已经将自己打造成公司开源 Python 库 FiftyOne 的高级用户。 我已经编写了许多文档,并且每天都使用(并将继续使用)该库。 但是将我们的文档转变为可搜索数据库的过程迫使我更深入地了解我们的文档。 当你为别人构建一些东西时,这总是很棒的,而且它最终也会帮助你!

这是我学到的:

  • Sphinx RST 很麻烦:它制作了漂亮的文档,但解析起来有点痛苦
  • 不要疯狂地进行预处理:OpenAI 的 text-embeddings-ada-002 模型非常适合理解文本字符串背后的含义,即使它的格式稍微不典型。 词干和煞费苦心地删除停用词和杂项字符的日子已经一去不复返了。
  • 具有语义意义的小片段是最好的:将文档分成尽可能小的有意义的片段,并保留上下文。 对于较长的文本片段,搜索查询与索引中的部分文本之间的重叠更有可能被片段中不太相关的文本所掩盖。 如果将文档分解得太小,则会面临索引中许多条目包含很少语义信息的风险。
  • 矢量搜索功能强大:只需最小的提升,并且无需任何微调,我就能够显着增强文档的可搜索性。 根据初步估计,这种改进的文档搜索返回相关结果的可能性是旧的关键字搜索方法的两倍多。 此外,这种矢量搜索方法的语义本质意味着用户现在可以使用任意短语、任意复杂的查询进行搜索,并保证获得指定数量的结果。

如果你发现自己(或其他人)不断挖掘或筛选文档宝库以获取特定的信息核心,我鼓励你根据自己的用例调整此过程。 你可以修改它以适用于个人文档或公司档案。 如果这样做了,我保证你将会以新的眼光看待您的文档!

可以通过以下几种方法将其扩展为自己的文档!

  • 混合搜索:将矢量搜索与传统关键词搜索相结合
  • 走向全球:使用Qdrant Cloud在云端存储和查询集合
  • 合并网络数据:使用请求直接从网络下载 HTML
  • 自动更新:每当底层文档发生变化时,使用 Github Actions 触发嵌入的重新计算
  • 嵌入:将其包装在 Javascript 元素中并将其作为传统搜索栏的替代品

用于构建该包的所有代码都是开源的,可以在 voxel51/fiftyone-docs-search 存储库中找到。


原文链接:自然语言文档检索实战 — BimAnt

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

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

相关文章

LabVIEW开发双目立体系统猪重估算

LabVIEW开发双目立体系统猪重估算 动物的活重是各种研究中的重要参考&#xff0c;例如动物生长&#xff0c;饲料转化率&#xff0c;健康状况和疾病发生。生长中的动物的体重为保持它们处于适当的营养和环境水平提供了一个有价值的参数或指标。动物的利润通常与收入和成本之间的…

Sci Immunol丨Tim-3 适配器蛋白 Bat3 是耐受性树突状细胞

今天和大家分享一篇发表于2022年3月的文章&#xff0c;题目为“Tim-3 adapter protein Bat3 acts as an endogenous regulator of tolerogenic dendritic cell function”&#xff0c;发表在《Sci Immunol》杂志上。文章主要研究了Tim-3和其适配蛋白Bat3在调节免疫应答中的作用…

2023年【R1快开门式压力容器操作】最新解析及R1快开门式压力容器操作复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 R1快开门式压力容器操作最新解析是安全生产模拟考试一点通生成的&#xff0c;R1快开门式压力容器操作证模拟考试题库是根据R1快开门式压力容器操作最新版教材汇编出R1快开门式压力容器操作仿真模拟考试。2023年【R1快…

linux 安装 elasticsearch 全教程

一、去 elasticsearch官网找到Linux版本的下载链接 地址https://www.elastic.co/cn/downloads/elasticsearch 二、在linux 中用wget下载 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-8.10.4-linux-x86_64.tar.gz三、下载成功后解压文件 tar -x…

Unity中Shader自定义cginc文件

文章目录 前言我们在使用如下场景中的小球来进行自己的 GI Shader测试一、先找到Unity自带的 cginc 库二、仿照 Unity 的 cginc 文件&#xff0c;写一个我们自己的 cginc 文件1、我们新建一个文件&#xff0c;在资源管理器中把 文件后缀名修改成 .cginc2、我们打开Unity自己的c…

【Linux】安装使用Nginx负载均衡,并且部署前端项目

目录 一、Nginx概述 1. 什么 2. 背景 3. 作用 二、Nginx负载均衡 1. 讲述 2. 使用 1. 下载 2. 安装 3. 负载均衡 三、前端部署 1. 准备 2. 部署 一、Nginx概述 1. 什么 Nginx是一个高性能的开源Web服务器和反向代理服务器。它具有轻量级、高并发、低内存消耗的…

让你笑到不行的笑话短视频接口,快来试试!

11在当今这个快节奏的社会中&#xff0c;笑话成为了许多人调节情绪的有效方法。如今&#xff0c;短视频平台已经成为了最受欢迎的娱乐方式之一&#xff0c;因此&#xff0c;将笑话和短视频结合起来&#xff0c;成为了一种很有趣的方式来带给我们欢乐。今天我们要介绍的是挖数据…

【电路笔记】-正弦波形

正弦波 文章目录 正弦波1、概述2、波形产生3、总结 在 19 世纪末的 10 年间&#xff0c;许多技术成就使得交流电的使用得以扩展&#xff0c;并克服了直流电向公众供电的局限性。 1882 年&#xff0c;法国发明了变压器&#xff0c;它简化了交流电的分配&#xff0c;正如我们将在…

【免费活动】11月4日敏捷武林上海站 | Scrum.org CEO 亲临现场

活动介绍 过去的几年里&#xff0c;外界的风云变幻为我们的生活增添了一些不一样的色彩。在VUCA世界的浪潮里&#xff0c;每一个人都成为自己生活里的冒险家。面对每一次的变化&#xff0c;勇于探索未知&#xff0c;迎接挑战&#xff0c;努力追逐更好的自己。 七月&#xff0…

gcc/g++使用格式+各种选项,预处理/编译(分析树,编译优化,生成目标代码)/汇编/链接过程(函数库,动态链接)

目录 gcc/g--编译器 介绍 使用格式 通用选项 编译选项 链接选项 程序编译过程 预处理(宏替换) 编译 (生成汇编) 分析树(parse tree) 编译优化 删除死代码 寄存器分配和调度 强度削弱 内联函数 生成目标代码 汇编 (生成二进制代码) 链接(生成可执行文件) 函…

跨境电商怎么做?欲善其事,先利其器!

当前&#xff0c;跨境电商正在以飞速的发展趋势推进&#xff0c;在未来将会朝向成熟系统化的方向发展&#xff0c;对于跨境电商从业者来说既是机遇&#xff0c;也是挑战。不少想转行的朋友对于跨境行业早已“跃跃欲试”&#xff0c;但是不了解跨境电商却又久久不敢冒险......那…

Javassist讲解1(介绍,读写字节码)

Javassist讲解1&#xff08;介绍&#xff0c;读写字节码&#xff09; 介绍一、读写字节码1.如何创建新的类2.类冻结 介绍 javassist 使Java字节码操作变得简单&#xff0c;它是一个用于在Java中编辑字节码的类库&#xff1b; 它使Java程序能够在运行时定义一个新类&#xff0c;…

JS(JavaScript) 实现延迟等待(sleep方法)

起因&#xff1a; 只使用 setTimeout 会产生嵌套等方面的问题&#xff0c;达不到想要的效果。 解决方法&#xff1a; 使用 async/await 还有 Promise 相结合的方式来解决问题。 直接上代码&#xff1a; function sleep(time) {return new Promise((resolve) > setTimeout…

基于深度学习的植物识别算法 - cnn opencv python 计算机竞赛

文章目录 0 前言1 课题背景2 具体实现3 数据收集和处理3 MobileNetV2网络4 损失函数softmax 交叉熵4.1 softmax函数4.2 交叉熵损失函数 5 优化器SGD6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的植物识别算法 ** …

交叉编译tslib

交叉编译tslib 环境&#xff1a; ubuntu16.04(虚拟机) tslib 版本&#xff1a;1.4.0 交叉编译器&#xff1a;gcc-linaro-7.5.0-2019.12-x86_64_arm-linux-gnueabihf 目标架构&#xff1a;ARM 野火imx6ull pro开发板 tslib的下载 tslib的下载地址 https://github.com/Mic…

Python数据分析(四)-- 操作Excel文件

1 操作Excel文件-多种实现方式 在实际生产中&#xff0c;经常会用到excel来处理数据&#xff0c;虽然excel有强大的公式&#xff0c;但是很多工作也只能半自动化&#xff0c;配合Python使用可以自动化部分日常工作&#xff0c;大大提升工作效率。 openpyxl&#xff1a;只允许读…

第一章 引言 【数据结构与算法】【精致版】

第一章 引言 【数据结构与算法】【精致版】 前言版权第一章 引言三个问题超市商品问题人机对弈问题多岔路口交通灯的管理问题 1.1 数据结构的概念1.2 数据结构的内容1.2.1 数据的逻辑结构1.2.2 数据的存储结构 1.3 算法1.3.1 算法的概念1.3.2 算法的评价标准1.3.3 算法的描述1.…

GAMP源码阅读:卫星位置钟差计算

原始 Markdown文档、Visio流程图、XMind思维导图见&#xff1a;https://github.com/LiZhengXiao99/Navigation-Learning 文章目录 1、satposs_rtklib()2、ephclk()1. eph2clk()&#xff1a;时钟校正参数&#xff08; a f 0 、 a f 1 、 a f 2 a_{f0}、a_{f1}、a_{f2} af0​、af…

【编程语言发展史】C语言的诞生及其影响

目录 一、C语言的历史背景 二、C语言的设计思想 三、C语言的语法特点 四、C语言的应用领域 五、C语言的影响 六、总结 C语言是一种高级计算机编程语言&#xff0c;它的诞生和发展对计算机科学和软件工程领域产生了深远的影响。本文将详细介绍C语言的诞生及其影响&#xf…

【原创】java+swing+mysql个人理财管理系统设计与实现

摘要&#xff1a; 个人理财管理系统是一款帮助用户有效管理个人财务的软件&#xff0c;本文将详细介绍该系统的设计过程&#xff0c;包括功能模块、数据库设计、界面设计等&#xff0c;系统采用javaswingmysql技术组合。 功能分析&#xff1a; 系统主要提供给管理员、用户使…