使用ClickHouse UDF与OpenAI模型集成

图片

本文字数:14683;估计阅读时间:37 分钟

作者:Dale McDiarmid

审校:庄晓东(魏庄)

本文在公众号【ClickHouseInc】首发

图片

Meetup活动

ClickHouse Shenzhen User Group第1届 Meetup 火热报名中,详见文末海报!

介绍

在ClickHouse中,用户可以直接在其SQL中调用AI模型。这可以在插入数据时丰富数据的形式,或者在查询时补充特定的结果。虽然许多用户习惯于训练自己的领域特定模型,但对于较小的团队或用例,这通常是不切实际的。在这些情况下,预构建的“即插即用”模型或服务通常就足够,并且可以在付出最小努力的情况下获得良好的结果。

在本文中,我们演示了:

  • 如何使用ClickHouse用户定义的函数(UDF)轻松集成第三方API,这些API提供“AI as a Service”

  • 如何在ClickHouse中,直接调用这些“即插即用”模型完成特定任务,如情感分析,并针对这些结果进行聚合,以计算有关给定主题的正面和负面帖子数量等指标

鉴于OpenAI最近的知名度和备受关注的ChatGPT服务,我们以OpenAI为例。然而,这种方法的简单性意味着可以轻松地适应其他AI服务。

用户定义的函数(UDF)

ClickHouse中的UDF(用户定义的函数)有几种形式。在最近的一篇文章中,我们分享了:如何使用ClickHouse SQL定义的函数查询Hugging Face中托管的外部数据集。虽然诸如此类的SQL定义的函数对于概括常见的SQL任务非常有用,但有时用户需要熟悉的编程语言的全部功能。为此,ClickHouse支持可执行的UDF。这为开发人员提供了调用任何外部可执行程序或脚本来处理数据的灵活性。在我们下面的简单示例中,我们将使用这个功能来调用简单的Bash和Python脚本,这些脚本将查询OpenAI API。我们将展示API响应如何自动丰富由ClickHouse插入或查询的数据。

图片

使用OpenAI

大多数用户通过流行的ChatGPT服务熟悉OpenAI,该服务已经改变了工作行为和日常任务。OpenAI为企业提供了访问ChatGPT模型的REST API,以在现有服务和自动化流程中使用。这些服务提供从聊天和嵌入生成到图像生成和语音转文本的所有方式。我们的示例专注于聊天完成,这可以用于更通用的任务,如实体提取和情感标记。

注意:访问OpenAI服务的所有请求都需要一个令牌 - 在下面的示例中作为环境变量OPENAI_API_KEY传递。用户可以注册试用版,并获得足够的免费积分来执行这里的示例。

除了能够充当聊天机器人外,OpenAI的完成(completion)服务还支持其他任务,如情感分析和结构提取。对于这些任务,开发人员必须通过系统角色提供相关说明,以描述期望的行为。执行情感分析的示例REST API请求可能如下所示。在这里,我们要求服务对论坛帖子进行分类。请注意,我们需要提供明确的说明,以仅返回指定情感的单个令牌:

curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
  "model": "gpt-3.5-turbo",
  "messages": [
  {
    "role": "system",
    "content": "You are an AI language model trained to analyze and detect the sentiment of forum comments."
  },
  {
    "role": "user",
    "content": "Analyze the following hackernews comment and determine if the sentiment is: positive, negative or neutral. Return only a single word, either POSITIVE, NEGATIVE or NEUTRAL: I can say for BigQuery and Databricks from personal experience.<p>BigQuery is much slower and is much more expensive for both storage and query.<p>Databricks (Spark) is even slower than that (both io and compute), although you can write custom code&#x2F;use libs.<p>You seem to underestimate how heavily ClickHouse is optimized (e.g. compressed storage)."
  }
  ],
  "temperature": 0,
  "max_tokens": 256
}'

{
  "id": "chatcmpl-7vOWWkKWGN7McODMXJzQB6zzDcx0r",
  "object": "chat.completion",
  "created": 1693913320,
  "model": "gpt-3.5-turbo-0613",
  "choices": [
  {
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "NEGATIVE"
    },
    "finish_reason": "stop"
  }
  ],
  "usage": {
  "prompt_tokens": 147,
  "completion_tokens": 2,
  "total_tokens": 149
  }
}

请注意,这里我们使用更具成本效益的gpt-3.5-turbo模型,而不是最新的gpt-4模型。前者对于示例目的已足够。我们将其性能水平的评估留给读者。

相同的服务也可以用于提取结构。假设我们希望从上述文本中提取所提到的技术,作为字符串值的列表。我们需要稍微修改一下说明:

curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
  "model": "gpt-3.5-turbo",
  "messages": [
  {
    "role": "system",
    "content": "You are an AI language model trained to extract entities from forum comments"
  },
  {
    "role": "user",
    "content": "From the following text extract the many technologies mentioned as a comma seperated list: I can say for BigQuery and Databricks from personal experience.<p>BigQuery is much slower and is much more expensive for both storage and query.<p>Databricks (Spark) is even slower than that (both io and compute), although you can write custom code&#x2F;use libs.<p>You seem to underestimate how heavily ClickHouse is optimized (e.g. compressed storage)."
  }
  ],
  "temperature": 0,
  "max_tokens": 20
}'

{
  "id": "chatcmpl-7vOdLnrZWeax3RxjeUNelCTdGvr8q",
  "object": "chat.completion",
  "created": 1693913743,
  "model": "gpt-3.5-turbo-0613",
  "choices": [
  {
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "BigQuery, Databricks, Spark, ClickHouse"
    },
    "finish_reason": "stop"
  }
  ],
  "usage": {
  "prompt_tokens": 122,
  "completion_tokens": 11,
  "total_tokens": 133
  }
}

关于上述请求参数的一些建议:

  • 我们将temperature设置为0,以从响应中删除任何随机性。对于这些用例,我们不需要创造性的文本,只需要确定性的文本分析。

  • 在两种情况下,我们设置max_tokens以确定响应的长度。一个token大约是3/4个词。因此,我们调整了我们的请求。

数据集

对于我们的示例数据集,我们使用了Hacker News的帖子。这个数据集在我们的公共play环境中可用,包含从2006年到2023年8月的流行Hacker News论坛上的所有帖子和评论,约有3700万行。表模式如下所示。

我们对titletext列感兴趣。我们将探索这个数据集的任务留给读者,如果希望将这个数据集的最新版本加载到你自己的ClickHouse实例中,可以按照这里的说明操作。或者,我们提供了一个在S3上的Parquet文件,可以使用如下所示的s3函数加载:

CREATE TABLE hackernews
(
  `id` UInt32,
  `deleted` UInt8,
  `type` Enum8('story' = 1, 'comment' = 2, 'poll' = 3, 'pollopt' = 4, 'job' = 5),
  `by` LowCardinality(String),
  `time` DateTime,
  `text` String,
  `dead` UInt8,
  `parent` UInt32,
  `poll` UInt32,
  `kids` Array(UInt32),
  `url` String,
  `score` Int32,
  `title` String,
  `parts` Array(UInt32),
  `descendants` Int32
)
ENGINE = MergeTree
ORDER BY id

INSERT INTO hackernews SELECT * FROM s3('https://datasets-documentation.s3.eu-west-3.amazonaws.com/hackernews/2023-08-18.parquet')

添加情感分析

对于我们的示例,假设我们希望为存储在ClickHouse中的Hacker News数据添加情感分析。为此,我们需要通过ClickHouse UDF调用之前的OpenAI REST API。这个请求的简单性意味着:即使是一个简单的bash脚本可能也足够,如下所示(下面需要jq)。稍后,我们将演示如何直接在Python中执行此操作。

#!/bin/bash

while read read_data; do
  sentiment=$(curl -s https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <insert>" \
  -d "{
  \"model\": \"gpt-3.5-turbo\",
  \"messages\": [
      {
      \"role\": \"system\",
      \"content\": \"You are an AI language model trained to analyze and detect the sentiment of forum comments.\"
      },
      {
      \"role\": \"user\",
      \"content\": \"Analyze the following Hacker News comment and determine if the sentiment is: positive, negative or neutral. Return only a single word, either POSITIVE, NEGATIVE or NEUTRAL: ${read_data}\"
      }
  ],
  \"temperature\": 0,
  \"max_tokens\": 2,
  \"temperature\": 0
}" | jq -r '.choices[0].message.content')
  printf "$sentiment";
done

这个脚本应该保存在ClickHouse的user_scripts目录中,命名为sentiment.sh,并设置为可执行。还应该在一个文件openai_functions.xml中添加以下条目,并保存到ClickHouse配置目录(通常是/etc/clickhouse-server/)。

<functions>
       <function>
           <name>sentiment</name>
           <type>executable</type>
           <format>TabSeparated</format>
           <return_type>String</return_type>
           <argument>
             <type>String</type>
           </argument>
           <command>sentiment.sh</command>
           <command_read_timeout>10000</command_read_timeout>
           <command_write_timeout>10000</command_write_timeout>
           <max_command_execution_time>10000</max_command_execution_time>
       </function>
</functions>

这个配置使得UDF可用于ClickHouse。除了在这里修改超时以允许OpenAI请求的延迟之外,我们提供了一个函数名sentiment,并指定了输入和返回类型。

有了上述配置,用户可以通过简单的函数调用请求文本片段的情感,例如:

SELECT sentiment('Learn about the key differences between ClickHouse Cloud and Snowflake and how ClickHouse Cloud outperforms Snowflake across the critical dimensions for real-time analytics: query latency and and cost.') AS sentiment

┌─sentiment─┐
│ POSITIVE  │
└───────────┘

1 row in set. Elapsed: 0.433 sec.

虽然上面的例子可以让我们开始,但可能需要一个更健壮的解决方案,具有错误处理的功能。为此,我们可能希望将上述内容转换为Python。下面的Python脚本添加了基本的错误处理和带有回退的重试。后者是为了特别解决OpenAI速率限制的挑战 - 请参阅处理延迟和速率限制以获取更多详细信息。

请注意,需要openai和tenacity库来处理API请求和速率限制。

#!/usr/bin/python3
import sys
import openai
from tenacity import (
   retry,
   stop_after_attempt,
   wait_random_exponential,
)

openai.api_key = "<INSERT>"
request_timeout = 3

@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(20))
def completion_with_backoff(**kwargs):
   return openai.ChatCompletion.create(**kwargs)

def extract_sentiment(text):
   if text == "":
       return "NEUTRAL"
   messages = [{"role": "system",
                "content": "You are an AI language model trained to analyze and detect the sentiment of hackernews forum comments."},
               {
                   "role": "user",
                   "content": f"Analyze the following hackernews comment and determine if the sentiment is: positive, negative or neutral. "
                              f"Return only a single word, either POSITIVE, NEGATIVE or NEUTRAL: {text}"
               }]
   try:
       response = completion_with_backoff(model="gpt-3.5-turbo", messages=messages, max_tokens=30, temperature=0, request_timeout=request_timeout)
       return response.choices[0].message.content
   except:
       return "ERROR"

for size in sys.stdin:
   # collect a batch for performance
   for row in range(0, int(size)):
       print(extract_sentiment(sys.stdin.readline().strip()))
   sys.stdout.flush()

服务的基于聊天的性质使得在单个请求中评估多个文本片段的情感变得具有挑战性。为了使这些示例保持简单,我们每行进行一次请求。一个更优化的解决方案可能会对请求进行批处理,并要求端点评估一组文本。

上述假设:从ClickHouse传递的任何输入,都包括一个行数的前缀。这用于确定后续输入要迭代的次数。这可以使Python脚本内的操作进行批处理以获得更高的性能。

除了定义唯一名称sentiment_p之外,上述函数的配置还有一些额外的设置。我们将type设置为可执行池,以提高吞吐性能。这将启动命令N次(下面是10次),允许多个并发的调用。设置send_chunk_header确保在任何输入之前有一个表示要处理的行数的数字标题。我们增加了超时设置,以防传递大块的行。

<functions>
    <function>
    <name>sentiment_p</name>
      <type>executable_pool</type>
      <pool_size>10</pool_size>
      <send_chunk_header>true</send_chunk_header>
      <format>TabSeparated</format>
      <return_type>String</return_type>
      <argument>
        <type>String</type>
      </argument>
      <command>sentiment.py</command>
      <command_read_timeout>10000000</command_read_timeout>
         <command_write_timeout>10000000</command_write_timeout>
         <max_command_execution_time>1000000</max_command_execution_time>
    </function>
</functions>

我们可以将上述任何一个函数应用于列的一组行。在下面的示例中,我们请求包含单词ClickHouse的10行的标题和文本的情感。

SELECT text, sentiment_p(text) AS sentiment
FROM hackernews WHERE text LIKE '%ClickHouse%' OR title LIKE '%ClickHouse%'
ORDER BY time DESC
LIMIT 2
FORMAT Vertical
Row 1:
──────
text:    Yeah ClickHouse is definitely the way to go here. Its ability to serve queries with low latency and high concurrency is in an entirely different league from Snowflake, Redshift, BigQuery, etc.
sentiment: POSITIVE

Row 2:
──────
text:    There are other databases today that do real time analytics (ClickHouse, Apache Druid, StarRocks along with Apache Pinot).  I&#x27;d look at the ClickHouse Benchmark to see who are the competitors in that space and their relative performance.
sentiment: POSITIVE

2 rows in set. Elapsed: 2.763 sec. Processed 37.17 million rows, 13.30 GB (13.46 million rows/s., 4.82 GB/s.)

只有在最终结果被整理后,UDF才会执行 - 这意味着只需要两个请求。这种方法是理想的,因为对OpenAI的请求的延迟通常比ClickHouse评估查询的时间要长得多。

进一步地,我们可以通过简单的聚合来计算ClickHouse的正面和负面帖子的数量。这会产生更多的开销,因为我们需要对OpenAI API进行1600多次调用。这反映在最终的计时中。

SELECT
  count(),
  sentiment
FROM hackernews
WHERE (text LIKE '%ClickHouse%') OR (title LIKE '%ClickHouse%')
GROUP BY sentiment_p(text) AS sentiment
FORMAT PrettyCompactMonoBlock

┌─count()─┬─sentiment─┐
│   192 │ NEGATIVE  │
│   628 │ NEUTRAL   │
│   857 │ POSITIVE  │
└─────────┴───────────┘

3 rows in set. Elapsed: 203.695 sec. Processed 37.17 million rows, 13.28 GB (182.48 thousand rows/s., 65.21 MB/s.)

处理延迟和速率限制

OpenAI API的实用性受到两个因素的限制:延迟和它施加的速率限制。请注意,这些变量将取决于所选择的“即插即用”模型。在我们的示例中,我们使用OpenAI。还有许多其他选择,每个选择都有其自己的权衡。

延迟将影响查询的最小响应时间。虽然OpenAI允许多个并发查询以确保这不会影响吞吐量,但速率限制将更具限制性。因此,我们建议用户仅将这些API用于自发分析,其中函数仅在结果的小子集上使用(例如我们之前的2行示例),或者在插入时丰富数据。在展示后者的示例之前,让我们探讨一下延迟和速率限制的限制。

我们可以通过修改我们的情感curl请求来使用一个简单的格式文件来评估响应的延迟:

curl -w "@curl-format.txt" -o /dev/null -s  https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
  "model": "gpt-3.5-turbo",
  "messages": [
      {
      "role": "system",
      "content": "You are an AI language model trained to analyze and detect the sentiment of forum comments."
      },
      {
      "role": "user",
      "content": "Analyze the following hackernews comment and determine if the sentiment is: positive, negative or neutral. Return only a single word, either POSITIVE, NEGATIVE or NEUTRAL: I can say for BigQuery and Databricks from personal experience.<p>BigQuery is much slower and is much more expensive for both storage and query.<p>Databricks (Spark) is even slower than that (both io and compute), although you can write custom code&#x2F;use libs.<p>You seem to underestimate how heavily ClickHouse is optimized (e.g. compressed storage)."
      }
  ],
  "temperature": 0,
  "max_tokens": 256,
  "temperature": 0
}'

time_namelookup:  0.081196s
time_connect:  0.084907s
time_appconnect:  0.095853s
time_pretransfer:  0.095937s
time_redirect:  0.000000s
time_starttransfer:  0.095942s
----------
time_total:  0.650401s

这里的总延迟为0.65秒,限制了我们的查询响应时间,但由于我们有一个命令池(上面是10),ClickHouse的并行执行管道可以利用它。然而,这种并行化又受到OpenAPI速率限制的限制。

OpenAI受到每分钟请求数和每分钟令牌数的限制。对于我们的gpt-3.5-turbo模型,这是每分钟90,000个令牌(TPM)和每分钟3,500个请求(RPM)。速率限制因模型和帐户类型而异 - 进一步的细节请参阅此处。

为了解决这个问题,我们在我们的UDF中添加了基本的速率限制。API返回头中包含速率限制信息(即下一分钟剩余的令牌数和请求数)。虽然我们可以开发一个使用这些信息的速率限制函数,但OpenAI建议使用几个专为通过指数回退解决此问题而设计的库。这样做的好处是我们不需要跟踪跨多个线程的请求和令牌使用情况。

我们对聚合查询的上述计时(203.695秒)表明,我们可能没有充分利用我们的10个UDF命令池,或者正在受到速率限制的限制。假设平均延迟为0.65 *,完全并行化,我们预计我们的总执行时间在这里更接近100秒(1600/10 * 0.65 = 104秒)。

*我们假设Open AI API可以保持这种延迟,而不受变量内容长度等因素的影响(一些评论可能比其他评论更长)。

这种性能(100秒)之所以没有实现,是因为查询受到OpenAI API的速率限制的限制 - 具体来说是令牌限制。每个Hacker News评论平均约为330个单词,如下所示,或约80个令牌(每个令牌约4个字符)。但这并不包括我们的提示和系统文本,其中还增加了额外的60个令牌。我们的与ClickHouse相关的子集的平均令牌长度也较高,为136。

SELECT
  round(avg(length(text))) AS num_chars,
  round(num_chars * 0.25) AS num_tokens
FROM hackernews

┌─num_chars─┬─num_tokens─┐
│     333 │       83 │
└───────────┴────────────┘

1 row in set. Elapsed: 1.656 sec. Processed 37.17 million rows, 12.72 GB (22.44 million rows/s., 7.68 GB/s.)

SELECT
  round(avg(length(text))) AS num_chars,
  round(num_chars * 0.25) AS num_tokens
FROM hackernews
WHERE (title LIKE '%ClickHouse%') OR (text LIKE '%ClickHouse%')

┌─num_chars─┬─num_tokens─┐
│     546 │      136 │
└───────────┴────────────┘

1 row in set. Elapsed: 1.933 sec. Processed 37.17 million rows, 13.28 GB (19.23 million rows/s., 6.87 GB/s.)
Peak memory usage: 73.49 MiB.

虽然每个评论都需要一个请求,导致总共1600个请求(低于每分钟3500的限制),但我们总共有900k个字符或229k个令牌。考虑到我们的提示文本,这增加到了329k个令牌(每个请求额外增加60个)。这远远超过了每分钟90k的限制。尽管如此,如果这项工作被完美地安排,我们预计这个请求将在我们经历的200秒内完成(329/90〜3.65分钟〜200秒)。

尽管更好的速率限制实现(例如基于通用单元速率算法的实现)可能会更有效地利用Open AI API资源,但请求的延迟最终将受到令牌限制的约束。我们只能使用前N个令牌,其中N的选择基于一个限制,该限制将确保完全利用每分钟3500次的完整请求限制,即90000/3500〜25个令牌。然而,在我们的示例中,这不太可能足以确定情感上述技术。 

在插入时进行提取

鉴于速率限制和延迟,使用API进行查询的更可取的方法是在数据插入时分配情感列。借助其命令池,Python函数更适合进行此类型的批处理处理。下面,在通过INSERT INTO加载行时提取情感。在这个例子中,我们将所有与ClickHouse相关的行插入一个专用表中,为每个计算一个情感列。这种处理方式非常理想,因为新行插入时,Hacker News数据集每分钟接收约8-10行新数据。一旦分配了列,我们就可以在情感列上享受ClickHouse查询速度,而无需进行API请求。

INSERT INTO hackernews_v2 SELECT
  *,
  sentiment_p(text) AS sentiment
FROM hackernews
WHERE (text LIKE '%ClickHouse%') OR (title LIKE '%ClickHouse%')

0 rows in set. Elapsed: 185.452 sec. Processed 37.17 million rows, 13.54 GB (200.44 thousand rows/s., 73.00 MB/s.)

SELECT count(), sentiment
FROM hackernews_v2
GROUP BY sentiment

┌─count()─┬─sentiment─┐
│   193 │ NEGATIVE  │
│   850 │ POSITIVE  │
│   634 │ NEUTRAL   │
└─────────┴───────────┘

3 rows in set. Elapsed: 0.003 sec. Processed 1.68 thousand rows, 1.68 KB (531.10 thousand rows/s., 531.10 KB/s.)
Peak memory usage: 72.90 KiB.

提取结构

为了完整起见,让我们也将之前的OpenAI请求转换为提取我们的帖子中的技术。以下是bash Python脚本:

#!/usr/bin/python3
import sys
import openai
from tenacity import (
   retry,
   stop_after_attempt,
   wait_random_exponential,
)

openai.api_key = "<INSERT>"
request_timeout = 3

@retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(20))
def completion_with_backoff(**kwargs):
   return openai.ChatCompletion.create(**kwargs)

def extract_topics(text):
   if text == "":
       return ""
   messages = [{
                   "role": "system",
                   "content": "You are an AI language model trained to extract entities from Hacker News forum comments."},
               {
                   "role": "user",
                   "content": f"From the following text extract the technologies mentioned as a comma separated list with no spaces. Return an empty string if there are no technologies: {text}"
               }]
   try:
       response = completion_with_backoff(model="gpt-3.5-turbo", messages=messages, max_tokens=30, temperature=0,
                                          request_timeout=request_timeout)
       return response.choices[0].message.content.strip()
   except Exception as e:
       return f"ERROR - {e}"

for size in sys.stdin:
   # collect a batch for performance
   for row in range(0, int(size)):
       print(",".join([tech.strip() for tech in extract_topics(sys.stdin.readline().strip()).split(",")]))
   sys.stdout.flush()

配置此函数时,使用与情感UDF相同的参数,只是将名称更改为extract_techs,我们可以在Hacker News上使用ClickHouse识别提到的顶级技术。

WITH results AS (
      SELECT extract_techs(text) as techs
      FROM hackernews
      WHERE (text LIKE '%ClickHouse%') OR (title LIKE '%ClickHouse%')
)
SELECT
    arrayJoin(splitByChar(',', techs)) AS tech,
    count() AS c
FROM results
GROUP BY tech
HAVING tech NOT ILIKE '%ClickHouse%' AND tech != ''
ORDER BY c DESC
LIMIT 5

┌─tech────────┬───c─┐
│ Postgres  │  78 │
│ PostgreSQL  │  65 │
│ SQL       │  63 │
│ TimescaleDB │  54 │
│ MySQL     │  51 │
└─────────────┴─────┘

5 rows in set. Elapsed: 211.358 sec. Processed 37.17 million rows, 13.28 GB (175.87 thousand rows/s., 62.85 MB/s.)
Peak memory usage: 931.95 MiB.

结论

本文展示了如何使用UDF将ClickHouse直接集成到模型提供商,以丰富和添加结构到现有数据中。虽然我们在示例中使用了OpenAI,但类似的“即插即用”模型服务应该同样容易集成。

Meetup 活动报名通知

好消息:ClickHouse Shenzhen User Group第1届 Meetup 已经开放报名了,将于2024年1月6日在深圳南山区海天二路33号腾讯滨海大厦举行,扫码免费报名

图片

​​联系我们

手机号:13910395701

邮箱:Tracy.Wang@clickhouse.com

满足您所有的在线分析列式数据库管理需求

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机提示找不到vcruntime140.dll,无法继续执行代码怎么办?如何修复

“找不到vcruntime140.dll&#xff0c;无法继续执行代码”。这个问题可能会让你感到困惑&#xff0c;不知道如何解决。那么&#xff0c;vcruntime140.dll是什么文件&#xff1f;它为什么会丢失&#xff1f;又该如何解决这个问题呢&#xff1f;本文将为你详细介绍vcruntime140.d…

教师未来前景发展

教师是一个光荣而重要的职业&#xff0c;他们承担着培养下一代的责任和使命。随着社会的不断发展和变化&#xff0c;教师的前景也在不断扩大和改变。本文将探讨教师未来的前景发展&#xff0c;并提供一些思考和建议。 首先&#xff0c;教师的就业前景将继续扩大。随着人口的增长…

自定义Springboot项目启动横幅⭐️ 附平平淡淡的周末日常

2023/12/24 天气晴 温度适宜 一觉睡到九点半&#xff0c;谁是神仙&#xff0c;我是神仙日常三联&#xff0c;喂鸡&#xff0c;刷博&#xff0c;肝任务今阳光甚好&#xff0c;遂寻吾之莆田&#xff0c;翻其面&#xff0c;光得以入之&#xff0c;余卧炕&#xff0…

单片机原理及应用

一、任务说明 1.主要任务 本实践环节“51单片机商用电子计价秤设计”要求收集市场电子秤的应用场景的功能列表&#xff0c;给出本系统各功能的参数范围&#xff0c;分析质量检测功能的实现方法&#xff0c;设计单片机仿真系统并通过Proteus进行测试&#xff0c;电子秤是利用物…

注意:国内发生多起Oracle 勒索病毒!

摘要&#xff1a;近期&#xff0c;国内发生多起针对Oracle 数据库的勒索病毒案例&#xff0c;通过分析&#xff0c;该勒索病毒通过网络流传的“PL/SQLDeveloper破解版”进行传播。 1.病毒发起的原因及问题现象 近期&#xff0c;国内发生多起针对Oracle 数据库的勒索病毒案例&…

池化层(pooling)

目录 一、池化层 1、最大池化层 2、平均池化层 3、总结 二、代码实现 1、最大池化与平均池化 2、填充和步幅(padding和strides) 3、多个通道 4、总结 一、池化层 1、最大池化层 2、平均池化层 3、总结 池化层返回窗口中最大或平均值环节卷积层对位置的敏感性同样有窗口…

每日一题——LeetCode888

方法一 个人方法&#xff1a; 交换后要达到相同的数量&#xff0c;那么意味着这个相同的数量就是两个人总数的平均值&#xff0c;假设A总共有4个&#xff0c;B总共有8个&#xff0c;那么最后两个人都要达到6个&#xff0c;如果A的第一盒糖果只有1个&#xff0c;那么B就要给出6…

祝福各位CSDN的小伙伴圣诞快乐

1.源码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>圣诞树&#x1f384;</title><link rel"stylesheet" href"https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/n…

分布式事务2PC二阶段提交详解

文章目录 概述和概念执行过程和工作流程特点优劣势应用场景总结demo代码样例 概述和概念 二阶段提交&#xff08;2PC&#xff09;是一种用于确保在分布式系统中的所有节点在进行事务提交时保持一致性的算法 二阶段提交&#xff08;Two-Phase Commit&#xff0c;2PC&#xff09…

身为Java“搬砖”程序员,你掌握了多线程吗?

摘要&#xff1a;互联网的每一个角落&#xff0c;无论是大型电商平台的秒杀活动&#xff0c;社交平台的实时消息推送&#xff0c;还是在线视频平台的流量洪峰&#xff0c;背后都离不开多线程技术的支持。在数字化转型的过程中&#xff0c;高并发、高性能是衡量系统性能的核心指…

做到这两条,破解35岁中年危机

最近我在看吴军老师的《富足》这本书&#xff0c;其中有一篇文章讲的是如何破解35岁中年危机&#xff0c;我觉得讲清楚了这个问题的本质&#xff0c;我在这里分享给你&#xff0c;以下内容大部分摘抄自《破解35岁中年危机》一章。 35岁中年危机的原因 35岁中年危机的说法好像…

Navicat for mysql备份与恢复

文章目录 一、Navicat for mysql备份1.打开navicat&#xff0c;找到备份2.点击新建备份&#xff0c;直接点备份3.备份完成 二、恢复数据1.删除表2.点击备份&#xff0c;选中备份文件&#xff0c;点击还原备份3.还原完成 三、其他命令四、视频演示总结 一、Navicat for mysql备份…

ZLMediaKit中的RingBuffer

前面的文章讲到ZLMediaKit转流&#xff0c;提到过RingBuffer&#xff0c;它是比较核心的数据结构。这篇文章就来讲讲RingBuffer的用法。 RingBuffer的类体系 RingBuffer是由多个类组成&#xff0c;分为两大功能&#xff1a;存储和数据分发。 存储功能由类RingStorage实现&…

图形图像处理车牌识别系统设计matlab

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;车牌识别 获取完整源码源文件论文报告 一、 摘要: 随这图形图像技术的发展&#xff0c;现在的车牌识别技术准确率越来越高&#xff0c;识别速度越来越快。无论何种形式的车牌识别系统&#xff0c;它们都是由触发、图像采…

【JavaWeb学习笔记】15 - jQuery

项目代码 https://github.com/yinhai1114/JavaWeb_LearningCode/tree/main/jquery 目录 零、官方文档 一、jQuery基本介绍 1.基本介绍 2.原理图 二、JQuery入门使用 1.下载JQuery 2.jQuery快速入门 三、jQuery对象 1.什么是jQuery对象? 2.DOM对象转换成jQuery对象 …

电子电器架构(E/E)演化 —— 主流主机厂域集中架构概述

电子电器架构(E/E)演化 —— 主流主机厂域集中架构概述 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。…

2008-2021年商业银行数据(农商行、城商行、国有行、股份制银行)

2008-2021年商业银行数据&#xff08;农商行、城商行、国有行、股份制银行&#xff09; 1、时间&#xff1a;2008-2021年 2、范围&#xff1a;1700银行 3 、指标&#xff1a;证券简称、year、证券代码、资产总计、负债合计、所有者权益合计、利润总额、净利润、贷款总额、存…

【六大排序详解】中篇 :选择排序 与 堆排序

选择排序 与 堆排序 选择排序 选择排序 与 堆排序1 选择排序1.1 选择排序原理1.2 排序步骤1.3 代码实现 2 堆排序2.1 堆排序原理2.1.1 大堆与小堆2.1.2 向上调整算法2.1.3 向下调整算法 2.2 排序步骤2.3 代码实现 3 时间复杂度分析 Thanks♪(&#xff65;ω&#xff65;)&#…

你真的理解了阻塞和非阻塞、同步和异步吗?

阻塞和非阻塞是一种状态&#xff0c;关键要看调用线程有没有被挂起。以处理I/O为例&#xff0c;如果是调用线程处理阻塞型I/O&#xff0c;那么调用线程会被挂起&#xff0c;此时调用线程就是阻塞的&#xff1b;如果调用线程处理的是非阻塞I/O&#xff0c;调用线程开启了I/O之后…

【Spring】15 MessageSourceAware 接口

文章目录 1. 简介2. 功能3. 使用3.1 创建并实现接口3.2 配置 Bean 信息3.3 资源文件3.4 创建启动类3.5 启动 4. 应用场景总结 Spring 框架为开发者提供了丰富的扩展点&#xff0c;其中之一是 Bean 生命周期中的回调接口。本文将专注介绍一个与国际化相关的接口 MessageSourceAw…