可编程的SQL是什么样的?

article/2025/10/6 11:51:49

背景

如果你使用传统编程语言,比如Python,那么恭喜你,你可能需要解决大部分你不需要解决的问题,用Python你相当于拿到了零部件,而不是一辆能跑的汽车。你花了大量时间去组装汽车,而不是去操控汽车去抵达自己的目的地。大部分非计算机专业的同学核心要解决的是数据操作问题,无论你是摆地摊,开餐馆,或者在办公室做个小职员,在政府机构做工作,你都需要基本的数据处理能力,这本质上是信息处理能力。 但是在操作数据前,你必须要学习诸如变量,函数,线程,分布式等等各种仅仅和语言自身相关的特性,这就变得很没有必要了。操作数据我们也可以使用 Excel(以及类似的软件),但是Excel有Excel的限制,譬如你各种点点点,还是有点低效的,有很多较为复杂的逻辑也不太好做,数据规模也有限。那什么交互最快,可扩展性最好?语言。你和计算机系统约定好的一个语言,有了语言交流,总是比点点点更高效的。这个语言是啥呢?就是SQL。

但是SQL也有些毛病,首先他最早为了关系型数据库设计的,适合查询而非ETL,但是现在人们慢慢把他扩展到ETL, 流式处理,甚至AI上,他就有点吃力了。 第二个问题是,他是声明式的,导致缺乏可编程性。所谓可编程性是指,我们应该具备创建小型、可理解、可重用的逻辑片段,并且这些逻辑片段还要被测试、被命名、被组织成包,而这些包之后可以用来构造更多有用的逻辑片段,这样的工作流程才是合理又便捷的。更进一步的,这些“高阶”能力应该是可选的,我们总是希望用户一开始去使用最简单的方式来完成手头的工作而不是显摆一些高阶技巧。

所以最后的结论是,我们希望:

  1. 保留SQL的所有原有优势,简洁易懂,上手就可以干活。
  2. 允许用户进阶,提供更多可编程能力,但是以一种SQL Style的方式提供。

保留原有SQL精髓

我们仅仅对SQL做了丢丢调整,在每条SQL 语句结尾增加了一个表名,也就是任何一条SQL语句的结果集都可以命名为一张新的表。

load hive.`raw.stripe_discounts` as discounts;
load hive.`raw.stripe_invoice_items` as invoice_items;
selectinvoice_items.*,casewhen discounts.discount_type = 'percent'then amount * (1.0 - discounts.discount_value::float / 100)else amount - discounts.discount_valueend as discounted_amountfrom invoice_itemsleft outer join discountson invoice_items.customer_id = discounts.customer_idand invoice_items.invoice_date > discounts.discount_startand (invoice_items.invoice_date < discounts.discount_endor discounts.discount_end is null) as joined;selectid,invoice_id,customer_id,coalesce(discounted_amount, amount) as discounted_amount,currency,description,created_at,deleted_atfrom joined as final;select * from final as output;

大家看到,每条SQL的执行结果都被取名为一张新表,然后下一条SQL可以引用前面SQL产生的表,相比传统我们需要insert 然后再读取,会简单很多,也更自然,速度更快。而且对于数据处理,我们也无需在一条SQL语句里写复杂的嵌套子查询和Join了,我们可以将SQL展开来书写,校本化,更加易于阅读和使用。

支持更多数据源

传统SQL是假定你在一个数据源中的,因为你只能按库表方式去使用,在普通Web开发里,是你配置的数据库。而在大数据里,一般是数据仓库或者数据湖。 但是随着联邦查询越来越多,越来越普及,我们希望给SQL提供更多的加载和保存多种数据源的能力。我们通过提供load语句来完成。

load excel.`./example-data/excel/hello_world.xlsx` 
where header="true" 
as hello_world;select hello from hello_world as output;

在上面的示例可以看到,我们加载了一个excel文件,然后映射成一张表,之后可以用标准的SQL进行处理。
如果要将结果保存到数仓也很简单:

save overwrite hello_word as hive.`tmp.excel_table`;

变量

变量是一个编程语言里,一般你会接触到的第一个概念。我们也给SQL增加了这种能力。比如:

-- It takes effect since the declaration in the same cell.
set world="world";select "hello ${world}" as title 
as output;

在可编程SQL中,变量支持多种类型,诸如sql,shell,conf,defaultParam等等去满足各种需求和场景。下面是一个典型的例子:

set date=`select date_sub(CAST(current_timestamp() as DATE), 1) as dt` 
where type="sql";select "${date}" as dt as output;

后面我们会有更多变量的介绍。

调用外部模块的代码

传统编程语言如Java,Python,他们的生态都是靠第三方模块来提供的。第三方模块会被打包成诸如如Jar ,Pip 然后让其他项目引用。 原生的SQL是很难复用的,所以没有形成类似的机制,更多的是随用随写。 但是随着SQL能力的扩展,在流,在批,在机器学习上的应用越来越多,能写越来越复杂的逻辑,也慢慢有了更多的可复用诉求。

我们通过引入include 关键字,可以引入本项目或者github上的SQL代码。https://github.com/allwefantasy/lib-core 是我们使用可编程SQL写的一个第三方模块。假设我们要引用里面定义的一个UDF 函数 hello,第一步是引入模块:

include lib.`github.com/allwefantasy/lib-core`
where 
-- libMirror="gitee.com" and  -- 配置代理
-- commit="" and              -- 配置commit点
alias="libCore";

第二步就是引入相应的udf包,然后在SQL中使用:

include local.`libCore.udf.hello`;
select hello() as name as output;

是不是很酷?

宏函数

函数是代码复用的基础。几乎任何语言都有函数的概念。我们在SQL中也引入的宏函数的概念。但这个宏函数和 原生的SQL中的函数比如 split, concat 等等是不一样的。他是SQL语言级别的函数。我们来看看示例:

set loadExcel = '''
load excel.`{0}` 
where header="true" 
as {1}
''';!loadExcel ./example-data/excel/hello_world.xlsx helloTable;

在这段代码中,

  1. 我们申明了一个变量 loadExcel,并且给他设置了一段代码。
  2. loadExcel 有诸如 {0}, {1}的占位符。这些会被后续调用时的参数动态替换。
  3. 使用功能 ! 将loadExcel变量转化为宏函数进行调用。参数传递类似命令行。

我们也支持命名参数:

set loadExcel = '''
load excel.`${path}` 
where header="true" 
as ${tableName}
''';!loadExcel _ -path ./example-data/excel/hello_world.xlsx -tableName helloTable;

原生SQL函数的动态扩展

像传统关系型数据库,几乎无法扩展SQL的内置函数。在Hive/Spark中,通常需要以Jar包形式提供,可能涉及到重启应用,比较繁琐,比较重。 现在,我们把SQL UDF 书写变成和书写SQL一样。 我们来看一个例子:

register ScriptUDF.`` as arrayLast 
where lang="scala"
and code='''def apply(a:Seq[String])={a.last
}'''
and udfType="udf";select arrayLast(array("a","b")) as lastChar as output;

在上面的代码中,我们通过register语法注册了一个函数叫 arrayLast,功能是拿到数组的最后一个值。 我们使用scala代码书写这段逻辑。之后我们可以立马在SQL中使用功能这个函数。是不是随写随用?

当然,通过模块的能力,你也可以把这些函数集中在一起,然后通过include引入。

分支语法

SQL最大的欠缺就是没有分支语句,这导致了一个啥问题呢?他需要寄生在其他语言之中,利用其他语言的分支语句。现在,我们原生的给SQL 加上了这个能力。 看如下代码:

set a = "wow,jack";!if ''' split(:a,",")[0] == "jack" ''';select 1 as a as b;
!else;select 2 as a as b;
!fi;select * from b as output;

在分支语句中的条件表达式中,你可以使用一切内置、或者我们扩展的原生函数。比如在上面的例子里,我们在if 语句中使用了 split函数。
还有一个大家用得非常多的场景,就是我先查一张表,根据条件决定接着执行什么样的逻辑。这个有了分支语法以后也会变得很简单,比如:

select 1 as a as mockTable;
set b_count=`select count(*) from mockTable ` where type="sql" and mode="runtime";!if ''':b_count > 11 ''';select 1 as a from b as final_table;
!else;    select 2 as a from b as final_table;
!fi;    select * from final_table as output;

在上面的代码示例中,我们先查询 mockTable里有多少数据,如果大于11条,执行 A语句,否则执行B 语句,执行完成后的结果继续被后面的SQL 处理。

机器学习(内置算法)

SQL表达机器学习其实是比较困难的。但是别忘了我们是可编程的SQL呀。我们来看看示例,第一步我们准备一些数据:

include project.`./src/common/mock_data.mlsql`;
-- create mock/validate/test dataset.
select vec_dense(features) as features, label as label from mock_data as mock_data;
select * from mock_data as mock_data_validate;
select * from mock_data as mock_data_test;

接着我们就可以引入一个内置的算法来完成模型的训练。

train mock_data as RandomForest.`/tmp/models/randomforest` wherekeepVersion="true" and evaluateTable="mock_data_validate"and `fitParam.0.labelCol`="label"
and `fitParam.0.featuresCol`="features"
and `fitParam.0.maxDepth`="2"
;

这个语句表达的含义是什么呢? 对mock_data表的数据使用RandomForest进行训练,训练时的参数来自where语句中,训练后的模型保存在路径/tmp/models/randomforest 里。是不是非常naive!

之后你马上可以进行批量预测:

predict mock_data_test as RandomForest.`/tmp/models/randomforest`  as predicted_table;

或者将模型注册成UDF函数,使用Select语句进行预测:

register RandomForest.`/tmp/models/randomforest` as model_predict;
select vec_array(model_predict(features)) as predicted_value from mock_data as output;

Python脚本支持

在可编程SQL里, SQL是一等公民, Python只是一些字符串片段。下面是一段示例代码:

select 1 as a as mockTable;!python conf "schema=st(field(a,long))";run command as Ray.`` where 
inputTable="mockTable"
and outputTable="newMockTable"
and code='''
from pyjava.api.mlsql import RayContextray_context = RayContext.connect(globals(),None)newrows = []
for row in ray_context.collect():row["a"] = 2newrows.append(row)context.build_result(newrows)
''';select * from newMockTable as output;

这段代码,我们使用功能Ray 模块执行Python脚本,这段Python脚本会对 mockTable表加工,把a字段从1修改为2,然后处理的结果可以继续被SQL处理。是不是很酷?随时随地写Python处理数据或者做机器学习,数据获取和加工则是标准的SQL来完成。

插件

可编程SQL无论语法还是内核功能应该是可以扩展的。 比如我需要一个可以产生测试数据的功能。我只要执行如下指令就可以安装具有这个功能的插件:

!plugin app add - "mlsql-mllib-3.0";

然后我就获得了一个叫SampleDatasetExt的工具,他可以产生大量的测试数据:

run command as SampleDatasetExt.`` 
where columns="id,features,label" 
and size="100000" 
and featuresSize="100" 
and labelSize="2" 
as mockData;select * from mockData as output;

在上面的示例代码中,我们通过SampleDatasetExt 产生了一个具有三列的表,表的记录数为100000, 其中feature字段数组大小为100, label字段的数组大小为2。之后我们可以使用select语句进行查询进一步加工。

更多编程小trick

比如下面一段代码在实际生产里是常态:

select SUM( case when `id` is null or `id`='' then 1 else 0 end ) as id,
SUM( case when `diagnosis` is null or `diagnosis`='' then 1 else 0 end ) as diagnosis,
SUM( case when `radius_mean` is null or `radius_mean`='' then 1 else 0 end ) as radius_mean,
SUM( case when `texture_mean` is null or `texture_mean`='' then 1 else 0 end ) as texture_mean,
SUM( case when `perimeter_mean` is null or `perimeter_mean`='' then 1 else 0 end ) as perimeter_mean,
SUM( case when `area_mean` is null or `area_mean`='' then 1 else 0 end ) as area_mean,
SUM( case when `smoothness_mean` is null or `smoothness_mean`='' then 1 else 0 end ) as smoothness_mean,
SUM( case when `compactness_mean` is null or `compactness_mean`='' then 1 else 0 end ) as compactness_mean,
SUM( case when `concavity_mean` is null or `concavity_mean`='' then 1 else 0 end ) as concavity_mean,
SUM( case when `concave points_mean` is null or `concave points_mean`='' then 1 else 0 end ) as concave_points_mean,
SUM( case when `symmetry_mean` is null or `symmetry_mean`='' then 1 else 0 end ) as symmetry_mean,
SUM( case when `fractal_dimension_mean` is null or `fractal_dimension_mean`='' then 1 else 0 end ) as fractal_dimension_mean,
SUM( case when `radius_se` is null or `radius_se`='' then 1 else 0 end ) as radius_se,
SUM( case when `texture_se` is null or `texture_se`='' then 1 else 0 end ) as texture_se,
SUM( case when `perimeter_se` is null or `perimeter_se`='' then 1 else 0 end ) as perimeter_se,
SUM( case when `area_se` is null or `area_se`='' then 1 else 0 end ) as area_se,
SUM( case when `smoothness_se` is null or `smoothness_se`='' then 1 else 0 end ) as smoothness_se,
SUM( case when `compactness_se` is null or `compactness_se`='' then 1 else 0 end ) as compactness_se,
SUM( case when `concavity_se` is null or `concavity_se`='' then 1 else 0 end ) as concavity_se,
SUM( case when `concave points_se` is null or `concave points_se`='' then 1 else 0 end ) as concave_points_se,
SUM( case when `symmetry_se` is null or `symmetry_se`='' then 1 else 0 end ) as symmetry_se,
SUM( case when `fractal_dimension_se` is null or `fractal_dimension_se`='' then 1 else 0 end ) as fractal_dimension_se,
SUM( case when `radius_worst` is null or `radius_worst`='' then 1 else 0 end ) as radius_worst,
SUM( case when `texture_worst` is null or `texture_worst`='' then 1 else 0 end ) as texture_worst,
SUM( case when `perimeter_worst` is null or `perimeter_worst`='' then 1 else 0 end ) as perimeter_worst,
SUM( case when `area_worst` is null or `area_worst`='' then 1 else 0 end ) as area_worst,
SUM( case when `smoothness_worst` is null or `smoothness_worst`='' then 1 else 0 end ) as smoothness_worst,
SUM( case when `compactness_worst` is null or `compactness_worst`='' then 1 else 0 end ) as compactness_worst,
SUM( case when `concavity_worst` is null or `concavity_worst`='' then 1 else 0 end ) as concavity_worst,
SUM( case when `concave points_worst` is null or `concave points_worst`='' then 1 else 0 end ) as concave_points_worst,
SUM( case when `symmetry_worst` is null or `symmetry_worst`='' then 1 else 0 end ) as symmetry_worst,
SUM( case when `fractal_dimension_worst` is null or `fractal_dimension_worst`='' then 1 else 0 end ) as fractal_dimension_worst,
SUM( case when `_c32` is null or `_c32`='' then 1 else 0 end ) as _c32
from data as data_id;

写的手累?那有么有办法简化呢?当然有啦。 我们毕竟是可编程是SQL呀。

一个有意思的解决方法如下:

select 
#set($colums=["id","diagnosis","fractal_dimension_worst"])
#foreach( $column in $colums )SUM( case when `$column` is null or `$column`='' then 1 else 0 end ) as $column,
#end1 as a from newTable as output;

我们可以使用内置的 #foreach 循环。先通过set设置所有字段名称,然后通过foreach循环来生成sum语句。

这就完了?就如同茴字有好多写法,我们还有其他的玩法。

set sum_tpl = '''
SUM( case when `{0}` is null or `{0}`='' then 1 else 0 end ) as {0}
''';select ${template.get("sum_tpl","diagnosis")},
${template.get("sum_tpl","radius_mean")},
${template.get("sum_tpl","texture_mean")},
from data as output;

我们可以通过set 进行模板设置,然后在sql语句里通过template.get( 语句进行模板渲染。 对于一个很复杂的SQL 语句,里面可能存在多个类似sum /case when的重复语句,那么我们就可以使用这种方式了。而且可以做到一处修改,处处生效。不然万一你 sum里的1要改成2,那可是要改好几十个语句的。

恩,除了这些,还有非常多的好玩的玩法等待你的挖掘,SQL 再也不Boring了。

不是最后的最后

可以看到,我们给原生SQL扩展了变量,函数,多数据源支持,第三方模块,原生SQL ,原生函数动态扩展,分支语法,机器学习,python脚本支持,插件等等诸多功能。就像TypeScript给JavaScript的增强一样,大家也可以只用最基础的SQL语法。但是一旦你有需要,你就可以使用更多高阶功能满足自己的诉求。

最后

这个可编程的SQL是还在梦想中么?当然不是! 它就在这里: https://mlsql.tech 我们提供了桌面版和在线试用版本。还不快来感受下。

真的最后了

MLSQL目前支持Web版,桌面版,包括Script,Notebook等多种交互模式。参考 MLSQL 2.1.0版本的技术白皮书

现在,让我们看一段赏心悦目的MLSQL代码

  1. 下载图片tar包,并且解压
  2. 设置python环境
  3. 加载图片目录为表
  4. 使用python进行分布式图片处理
  5. 对文件名进行处理
  6. 将表以二进制图片包保存到对象存储目录中

在这里插入图片描述


http://chatgpt.dhexx.cn/article/N8HPXenq.shtml

相关文章

SQL编程语言1

1 SQL简介 SQL是一门操作关系型数据库的编程语言 SQL通用语法 SQL语句可以以单行或多行书写&#xff0c;以分号结尾。只有在读取到分号后系统才会默认你已经输入执行语句MySQL数据库的SQL语句不区分大小写&#xff0c;关键字建议使用大写注释 单行注释&#xff1a;-- 注释内…

sql数据库高级编程总结(一)

1、数学函数&#xff1a;操作一个数据&#xff0c;返回一个结果 &#xff08;1&#xff09;取上限 ceiling 如果有一个小数就取大于它的一个最小整数 列如9.5 就会取到 10 select code,name,ceiling(price) from car &#xff08;2&#xff09;取下限 floor 如果有一个小数就…

SQL基础编程

文章目录 一.SQL的环境搭载单机离线环境在线环境 二.SQL的单表操作1.sql基础三步2.sql四则运算3.limit (限制查询结果个数)4.order by&#xff08;排序&#xff09;5.where 综合条件筛选6.SQL常量7.distinct (把结果中重复的行删除)8.函数(1) 聚合函数&#xff1a;sum() ——求…

君正X1000芯片性能和处理器介绍

君正X1000芯片是针对语音识别功能做了专门定制的芯片&#xff0c;可以支持4个MIC&#xff0c;支持远场唤醒&#xff0c;功耗很低&#xff0c;适用于物联网、智能家居、智能音频、智能玩具等产品 。 X1000 芯片功能&#xff1a; 穿戴处理器级低功耗&#xff0c;待机功耗0.2mW&am…

IBM storwize V5000存储基础配置

初始帐号密码 Superuser / passw0rd 设备和系统的基本状态 首先添加配置主机&#xff0c;前提是已经连接好光纤线&#xff0c;配置好光纤交换机 这里我们使用光纤通道 系统应该可以自动识别到端口&#xff0c;主机名可以设置为主机型号或者主机应用名 两个端口完成 配置内部存…

《计算机系统概论》-第5章-习题答案

给定指令ADD、JMP、LEA、NOT&#xff0c;请判断它们分别是操作&#xff08;或运算&#xff09;指令&#xff0c;还是数据搬移指令或控制指令&#xff1f;对每一条指令&#xff0c;进一步列出该指令可以采用的寻址模式。 指令类型寻址模式ADD操作立即数、寄存器2种寻址模式JMP控…

X1000之LCD部分的翻译

1.显示大小可达640x48060Hz&#xff0c;24BBP RGB 8 8 8 RGB(256^3) 2的24次方16777216 2.支持的颜色就是我们上面计算的 3. 8080并行接口 MCU接口方式&#xff08;8080&#xff0c;6800接口&#xff09;与RGB接口主要的区别 4.支持内部DMA操作和寄存器操作&#xff08;可以…

uboot - 配置过程1(分析国产君正的ingenic-linux-kernel3.10.14-x1000-v8.2-20181116\u-boot\mkconfig脚本)

分析uboot的配置过程&#xff08;mkconfig脚本&#xff09; uboot怎么配置&#xff1f;我们在终端上执行make NAME_config时的运行过程解析&#xff01; STEP1: %_config:: unconfig$(MKCONFIG) -A $(:_config)我们执行make *_config时会运行makefile的这两行程序&#xff0c…

Opencv 以指定格式保存图片

将图像保存至本地&#xff0c;以指定的格式&#xff0c;需要用到cv::imwrite()函数 函数原型&#xff1a; bool imwrite(const string& filename, InputArray img, const vector<int>& paramsvector<int>() )参数解释&#xff1a;filename:图像保存路径&…

【OpenCV-Python】教程:1-1 图像读取显示保存

文章目录 目标代码imread接口原型参数 支持的格式 imwrite接口原型参数 imshow接口原型参数 目标 读图片显示图片保存图片 代码 ## 导入库 import cv2 import sys## 读入图片 img cv2.imread("lena.jpg")## 读入失败退出 if img is None:sys.exit("Could not…

44.Linux君正X1000-添加st7789v显示

由于板子LCD旧屏是ili9335型号的,旧屏有时候会断货,如果断货则使用一个st7789v型号的LCD 它们两个屏的区别在于初始化屏的参数不同,引脚都一样,也就是说需要使板子同时支持ili9335型号和st7789v型号 思路: 1.uboot在显示LOG(初始化屏参数)之前,通过命令来读LCD型号,来检测LCD型…

HaaS100硬件规格

硬件配置 类别 参数 CPU 型号 HaaS 1000 架构 Cortex M33 主频 300MHz 片上Flash 16MB 内存 2.5MB SRAM 16MB PSRAM 硬件接口 类别 数量 性能指标 TF 卡槽 1个 最大支持 64GB RS485 1路 波特率支持1200bps ~ 115200bps RS232 1路 波特率最高支…

X1000对于CPU Core的参数解读(MIPS Cache)

各自摘抄整合&#xff0c;大多来自互联网&#xff0c;链接已全部放出来 1.MIPS-Based XBurst cores (up to 1.0GHz) 基于MIPS的XBurst核(最高可达1.0GHz) MIPS架构  XBurst是北京君正针对移动多媒体便携产品推出的一种创新的32位嵌入式CPU技术&#xff0c;它重新定义了32位嵌…

主控芯片成功案例:搭载北京君正X1000芯片,收款计算合二为一

具体硬件方面&#xff0c;商米码利奥Mini搭载了北京君正X1000芯片&#xff0c;该芯片具有性能强、功耗低的特点&#xff0c;主频可以达到1GHz &#xff0c;支持1秒极速启动&#xff0c;内置LPDDR&#xff08;X1000&#xff1a;32MB&#xff0c;X1000E&#xff1a;64MB&#xff…

【以太网硬件九】1000base-X是什么?

&#x1f449;个人主页&#xff1a; highman110 &#x1f449;作者简介&#xff1a;一名硬件工程师&#xff0c;持续学习&#xff0c;不断记录&#xff0c;保持思考&#xff0c;输出干货内容 说完千兆以太网电口&#xff0c;我们再来研究一下千兆光口的物理层规范。 千兆以太网…

比A100性能高4.5倍!英伟达H100横扫AI推理基准测试

视学算法报道 编辑&#xff1a;武穆 【导读】NVIDIA H100 Tensor Core GPU在MLPerf行业标准AI基准测试中首次亮相&#xff0c;创下了所有工作负载推理的世界纪录&#xff0c;提供的性能比上一代GPU高4.5 倍。 不久前&#xff0c;英伟达公布了旗下的芯片&#xff0c;在MLPerf行…

君正 X1000 音频驱动架构

X1000 音频驱动架构笔记 使用开发板X1000_HALLEY2_V2.0&#xff0c;源码为北京君正官方源码V7.0版本&#xff0c;与V6.0无太多差异 项目中遇到的问题 这是内核源码中设置所导致的&#xff0c;可能官方认为X1000这样设置会最好&#xff0c;可是我们需要小于30MS 的周期设置&…

华为metro1000描述,optix metro1000参数-华讯佳科技

OptiX Metro1000传输设备(简称Metro1000&#xff09;是华为技术有限公司研发的STM-1/STM-4/STM-16级别的盒式设备。华为Metro1000光端机主要应用于城域网、本地传输网接入层&#xff0c;具备结构简洁、集成度高等许多特点。 Metro1000光端机是华为技术有限公司开发的STM-1/STM-…

扔掉老破V100、A100,英伟达新一代计算卡H100来了

本文转载自公众号“夕小瑶的卖萌屋”&#xff0c;专业带逛互联网算法圈的神操作 -----》我是传送门 关注后&#xff0c;回复以下口令&#xff1a; 回复【789】 &#xff1a;领取深度学习全栈手册&#xff08;含NLP、CV海量综述、必刷论文解读&#xff09; 回复【入群】&#xf…