转载公众号 | SPG知识图谱


01

前言

在OpenSPG最新发布的0.0.2 版本中,为了方便大家更好地理解和应用OpenSPG构建知识图谱,发布了知识建模最佳实践的 7 个指导原则。本文我们结合蚂蚁域内的多个业务场景,举例说明结合SPG规范的结构与语义解耦的知识建模及schema设计方法。

🌟 OpenSPG GitHub:

https://github.com/OpenSPG/openspg,欢迎大家 Star 关注~

在未来的文章中,我们还会分基础篇、进阶篇、高阶篇,针对不同业务场景的建模需求,由浅及深讲解基于SPG的知识建模的方法和案例,并涉及术语的解释。

02

背景

知识建模是解决将真实世界中的海量信息转化为符合计算机处理模式的结构化数据,其中包括对真实世界中事物的属性特征及其关系的共性的抽象,制定表示的规范,同时兼顾对常识或领域概念及概念层级体系的语义理解,即体现了对知识的认知过程。

相关工作

96e03dc0359f34886c56f1a88043c048.png

图1 知识建模方法回顾

方法

特点

关系型数据库

用实体及其属性(关系也体现为外键)对数据结构化,不包括语义的建模,容易存在大量的数据冗余,多跳跨表查复杂、效率低。

知识工程

本体表示:将知识表示为辑符号的形式,构建特定领域概念类别及其层级细分关系,并用

一阶谓词逻辑、生产式规则表示领域专家经验,支持知识的符合推理。

框架(frame)表示:强调将所描述的每类事物都抽象为出特定的slot-value的结构化表示(例如目前百科词条就是frame表示)。

弊端:人工构建成本太高,知识获取困难。

语义网

出发点是用文档中的数据关联+逻辑描述,让计算机能认识和理解世界万物。语义网发展过程中先后制定了基于描述逻辑的DAML、RDF、OWL、OWL2等语言,语义完备性的强调,成为逻辑学家的游戏,无法工业化落地。

知识图谱

以基本的SPO三元组,表示实体间的事实关系;但SPO对由多个要素(>2)共同决定的多元关系表示存在缺陷;图谱schema的设计是主观的,不同图谱的异构导致知识难以对齐融合。

事理图谱

将事件以及事件之间的关系抽取并抽象出来,构建描述事件之间演化规律和模式的事理逻辑知识库。事件有frame框架表示、verb+nound表示等流派。

常识概念图谱

围绕常识性概念建立的实体以及实体之间的关系,帮助自然语言文本的理解。涵盖“是什么”的概念Taxonomy体系结构,“什么样”的概念属性关系,“给什么”的概念承接关系。

知识超图

超图(Hypergraph):就是每一个边可以包含两个以上的点所构成的图,解决多元知识的表示。

表1 知识建模方法对比

业务问题

建模问题

现有解决方案及不足

商家资产等实体-关系schema设计

02c4077ab17985dc5e9748cc10eda052.png

  • 无论是ER建模、本体、rdf、owl,都只是语法定义,不解决“设计模式”本身的问题。

  • schema设计启动难,难以决策属性/关系的设计、实体类型的划分。

  • schema的设计是主观的,导致不同图谱间知识的异构性(数据结构不同),阻碍知识的复用。

常识、领域结构化语义理解

d20679e75ee5234feb0de70ca8e00424.png

  • 不同业务有各自体现业务语义的类目体系,同时蚂蚁域内的场景也涉及对常识的理解

  • 传统的本体建模,在同一个分类体系上,既要对schema的扩展建模,又要对语义上的细分类建模,数据结构定义和语义建模的耦合,导致工程实现及维护管理的复杂性,也增加了业务梳理和表示(认知)领域知识的困难。

跨图谱融合场景

b1459db09906c9a7bd6c44e12ab25204.png

  • 不同业务部门构建图谱时先专注于自身领域的知识建模,但随着业务的开展,需要引入其他领域的知识。

  • 跨图谱融合,解决提高数据的复用性、提升数据治理能力,减少数据冗余重复及帮助发掘业务价值拓展。

  • 需要对领域内有共有的实体,如:用户、商家、POI等,提供统一的schema规范,并对域内常识或公用类目,如:行政区划、mcc类目等,沉淀为通用语义资产。

保险、黑产等业务逻辑表达需求

0edc9971a89ce4588f56c2727fa248b4.png

  • 保险产品运营、保险健告、黑产洞察等场景有着丰富的业务逻辑、业务规则沉淀

  • 需要支持业务规则、专家经验的形式化表达及推理能力

  • 一阶谓词、dsl等,对用户有门槛,业务规则较多时,需要有更简洁、快速的对规则建模的方法

用户行为建模场景

7da05ad7d186ad702a968f304ad469a0.png

bade0379daa6066c667bb6317b8e6dda.png

多元关系建模问题

  • 用户行为、金融事件、业务状态流,都可以抽象为多元时空行为事件建模

  • spo三元组表达多元关系是有损的

  • 超图结构的理解,对于非算法用户有学习门槛,难以直接可视化

  • 行为时间事件之间,本身存在着时序、因果、共现的等关系

事理图谱及事理关系建模

31529e38a424e42310a342ce2dee596c.png

已有的研究或工作,都只解决了事件图谱、事理(概念)图谱或事理常识中特定一类的表示,蚂蚁场景中需要对从实例到概念,从事实到常识的整体架构

  • 每种事件的实例需要frame表示

  • 事件概念体系需要本体表示

  • 事件实例之间的事实关系需要spo表示

  • 事件概念之间的顺承、因果等需要逻辑表达

表2 蚂蚁域内常见建模问题

03

基于OpenSPG的解决方案

我们从SPGSchema建模的最佳实践中总结出了7个原则,每个原则都搭配了相关示例进行说明。以充分发挥OpenSPG高效且强大的知识表达能力,解决前面提到的业务中的建模难点问题。

1、类型选择原则

8ec0b933f116598b209d9af33964acb3.png

原则 1:复杂多元结构用实体类型或事件类型

解释:当一个事物需要丰富的属性来进行描述,比如某个“公司”,不光是只用一个名称,还要借助经营范围、企业证件号码、注册地址等信息来描述,就适合使用实体类型进行建模。

37d2e6ef624b1792483c9938bb983889.png

原则 2:扁平化的分类标签用概念类型

解释:当目标建模对象仅仅是语言或文本意义上的分类标签,重点表达标签之间的上位、包含、因果等关系时,适合使用概念类型来进行建模,比如“矿石开采”、“矿石冶炼”这类表达对产业分类的标签。概念和实体、事件的区别是:

  • 多元结构 vs 一元结构: 如公司类型,有所在行政区域、所属行业、经营产品类目等多元属性定义,每个属性关联一个具体的概念类型。而行政区域、行业类目、产品类目都是一元结构的概念分类,重点表达的是类目之间的上下位关系。

  • 同名建模冲突处理机制:比如杭州市,若描述其特产、人口、区号等信息时应使用实体类型进行建模,但同时杭州市也可以作为一个行政区划的角度来使用,主要说明杭州市位于浙江省,浙江省又位于中国,此时杭州市、浙江省、中国都属于行政区划的一个实例,它们重点表达相互间的地理上的位于关系。这个时候,建议创建一个实体类型的同时又再创建一个概念类型,杭州市作为这两种类型的实例分别导入。

以如下Schema为例:

1namespace CKG
 2AdministractiveArea(行政区划): ConceptType
 3  hypernymPredicate: locateAt
 4
 5... ...
 6City(城市): EntityType
 7  properties:
 8    localProducts(特产): Product
 9      constraint: MultiValue
10    population(人口数量): Integer
11    areaCode(区号): Text
12    region(行政区划): AdministractiveArea

“City(城市)”的实体:

1{
2  "id": "hz",
3  "name": "杭州市",
4  "areaCode": "0571",
5  "region": "中国-浙江省-杭州市"
6}

“AdministractiveArea(行政区划)”的概念:中国 <-locateAt- 浙江省 <-locateAt- 杭州市

原则 3:实体/事件多类型使用动态分类原则

解释:客观世界对同一个事物会有多种分类,比如表示商铺的分类有:超市、便利店、加油站、洗车店等,如果按照这些不同分类视角分别进行实体类型建模,会造成图谱的类型爆炸,在数据构建和推理应用上会变得非常麻烦。SPG使用类型定义 + 分类概念的方式实现多分类来解决这个问题。

以商铺为例子。建模时先创建一个“商铺”的实体类型,然后再为这个实体类型创建一个“商铺分类”的概念类型用于分类。注意定义上要把概念类型放到实体类型的前面:

1namespace World
 2TaxonomyOfShop(商铺分类): ConceptType
 3  hypernymPredicate: isA
 4Shop(商铺): EntityType
 5  desc: 诸如便利店和加油站等
 6  properties:
 7     locateArea(所在区域): AdministractiveArea
 8     address(地址): Text     
 9     contactPhone(联系电话): STD.ChinaTelCode
10     IND#belongTo(属于): TaxonomyOfShop

然后准备“商铺分类”的概念数据作导入用:

1超市
2便利店
3加油站
4洗车店

导入“Shop(商铺)”的实例数据的时候,如下所示:

1id,name,locateArea,address,contactPhone,belongTo
2
31,罗森便利店,杭州市,浙江省杭州市西湖区西溪路588号1层,0571-85801525,便利店
4
52,中国石化(杭州古荡加油站),杭州市,浙江省杭州市西湖区天目山路331号,0571-85220839,加油站

通过belongTo属性的值会自动产生从实例到概念的边,实现对实体的分类。

224d4f00eecd2c866d4d96a6049beff6.png

在查询时,我们可以直接用概念类型“TaxonomyOfShop”指代类型“Shop”,实现按概念召回对应的实体。实现跟使用类型分别建模的效果,但是大幅简化数据的查询和维护。

1MATCH (s:`TaxonomyOfShop`/`便利店`) RETURN s
2# 返回id为1的"罗森便利店"实体
3
4MATCH (s:`TaxonomyOfShop`/`加油站`) RETURN s
5# 返回id为1的"中国石化(杭州古荡加油站)"实体

另外,SPGSchema也支持以规则作为概念挂载依据,即通过规则对实体的属性进行运算得出要挂载的概念名称。

比如我们可以通过定义如下的概念规则,根据实体的名称里的关键词自动分类:

1Define (s:Shop)-[p:belongTo]->(o:`TaxonomyOfShop`/`便利店`) {
 2    Structure {
 3        (s)
 4    }
 5    Constraint {
 6        R1("是便利店"): s.name like "%便利店%"
 7    }
 8}
 9
10Define (s:Shop)-[p:belongTo]->(o:`TaxonomyOfShop`/`加油站`) {
11    Structure {
12        (s)
13    }
14    Constraint {
15        R1("是加油站"): s.name like "%加油站%"
16    }
17}

导入“Shop(商铺)”的实例数据的时候,不再导入belongTo属性:

1id,name,city,address,contactPhone
2
31,罗森便利店,杭州市,浙江省杭州市西湖区西溪路588号1层,0571-85801525
4
52,中国石化(杭州古荡加油站),杭州市,浙江省杭州市西湖区天目山路331号,0571-85220839

数据导入完成后,查询可得到belongTo属性的值和关系:

1MATCH (s:Shop WHERE id="1")-[p:belongTo]-(o) RETURN s,p,o

f80eb784842a8c60fac3b7a379084017.png

注意:如果概念定义了归纳规则,但在数据导入时又导入了belongTo属性值,会以规则运算结果优先。

原则 4:概念类型不继承

解释:概念类型的父类有且只有Thing,不能继承其他的类型。因为概念本身默认会有上下位关系,就隐含继承的语义。如果概念类型再继承,在语义上就会有冲突。

2、属性/关系的选择原则

原则 5:关系的指向遵守由动到静原则,反之被禁止

解释:事件类型可指向任意类型,实体类型不可指向事件类型,概念类型只能指向概念类型,反之被禁止。

  1. 事件允许指向任意类型:事件都是独立发生的行为动作,实体/概念/标准类型是被动跟发生的事件关联,所以是事件类型指向任意类型,反之禁止。

  2. 实体禁止指向事件类型:实体都是多元要素的复杂对象,它可以和其他实体主动产生关联,但概念类型、标准类型只能做为实体的属性类型。因为实体本身不自动产生事件,所以实体禁止直接建立和事件的关系。

  3. 概念禁止指向多元类型:概念是扁平化的分类标签,它抽象描述一类共同特征的实体集合,所以它只能被事件或实体用作属性类型的定义,反之禁止。

6b537db364f615a22e8caead83e120b9.png

原则 6:概念类型之间只允许系统指定的7大类语义关系

具体参见附录2

  • HYP: 上位关系(Hypernym),是指一种更广泛或更一般的概念包含或包括另一种更具体或更特定的概念的关系。目前可用的谓词为isA、locateAt等。

  • SYNANT: 同义反义关系(Synonymy/Antonymy),表达概念之间是同义还是反义的关系。目前可用的谓词有synonym、antonym等。

  • CAU: 因果关系(Causal),表示指一个事件或行为(原因)导致另一个事件或行为(结果)发生的一类关系。目前可用的谓词有leadTo等。

  • SEQ: 顺承关系(Sequential),是连续发生的事情或动作,这些事情或动作有先后顺序。目前可用的谓词有happenedBefore等。

  • IND: 归纳关系(Induction),是指从一类有共同特征的实体中得出对这些实体概括性的概念,这种个体和概念之间的关系就是归纳关系。目前可用的谓词有belongTo等。

  • INC: 包含关系(Inclusion),表达部分与整体的关系。目前可用的谓词有isPartOf等。

  • USE: 用途关系(Usage),表达作用/用途的关系。

原则 7:属性尽量标准化(推荐但不强制约束)

解释:尽可能的使用概念类型、标准类型和实体类型对属性进行标准化。因为SPGSchema会自动根据属性生成等价的关系,简化关系的创建和数据维护。尤其在变更实体的属性值后,关系实例会自动根据属性值同步,让属性等价的关系始终保持跟属性值的描述一致。SPGSchema建议尽量使用属性来替代关系的创建,只有确实需要在关系上配置属性,或者定义逻辑关系的时候再使用关系的创建。

d057a99383546ba3b5a4b3b5a531f1e6.png

04

附录 1:Schema 类型

实体类型(EntityType)

实体类型,定义了具有共同数据结构(特征)的一类实例的集合,是一种多元要素的复合节点类型。通常实体类型用于描述客观世界中的一个具体事物,比如公司、学校、书籍等等。再以学校这个实体类型举例,它的实例可以有“xxx市第一小学”、“xxx大学”等。

实体类型的定义主要由属性和关系组成,属性的值域可以是文本、整数和浮点数这样的基础类型,也可以是概念、实体等高级类型。使用了高级类型做值域的属性,会同时产生从自身指向高级类型的关系。这也是SPGSchema的一个重要特性。

关系的定义需要在起点实体类型上定义,方向始终为出边方向。关系上也可以再定义属性,但值域只允许是基础类型。

实体类型会包含几个默认属性,在知识建模中无须额外定义:

  • id (主键,必填)

  • name (实体名称)

  • description (实体描述)

以学校举例,实体类型的属性可以有如下:

1enName(英文名): Text
2shortName(简称): Text
3founder(创办人): Person
4foundDate(创立日期): STD.Date
5category(类别): TaxonomySchool
6address(地址): Text

那学校实体类型的实例可以是:

1{
 2  "id": "zjdx",
 3  "name": "浙江大学",
 4  "enName": "Zhejiang University",
 5  "shortName": "浙大,ZJU",
 6  "founder": "林启",
 7  "foundDate": "18970521",
 8  "category": "公立大学",
 9  "address": "西溪校区:杭州市西湖区天目山路148号"
10}

以上实例导入后会形成如下子图:

d148d13c36a8daa19c432260286c8b21.png

事件类型

(EventType)

在实体类型的基础上,加入主体、客体、时间、空间这四类要素,是对动态行为的建模,旨在反映在不同空间、时间区间上事物的状态。例如政策事件、行业事件、用户行为事件,都属于事件类型。其中主体要素是必须具备的,其余要素是可选的。

事件类型跟实体类型都是对客观事物的抽象描述,但实体类型包含的是相对静态的客观属性和关系,缺少了随时间、空间的发展而产生的动态变化,从而存在片面性,缺乏了深层语义信息。例如表达“XX公司在10月28日在深交所挂牌上市”。

以企业事件类型举例,其属性可以有如下:

1subject(主体): Company
2object(客体): Text
3time(时间): STD.Date
4location(地点): Text
5behavior(行为): Behavior

那学校实体类型的实例可以是:

1{
2  "id": "2023100820394930",
3  "name": "XX公司在10月28日在深交所挂牌上市",
4  "subject": "XX公司",
5  "object": "深交所",
6  "time": "20231028",
7  "location": "深交所",
8  "behavior": "上市"
9}

实例导入后形成如下子图:

382770fa34683e338c9f22cd719e11ca.png

概念类型 (Concept Type)

概念是对一类具有共同特征的实体的抽象化描述,通常是用于描述实体/事件类型的分类。比如学校分类概念:小学、中学、大学。概念跟实体的区别是概念无法指代一个客观世界的具体事物,它是对一类事物的总结概括型描述。

同时,概念和概念之间还默认具备上位关系(可以在建模时候选择上位关系谓词),通过该关系形成往上泛化、往下具化的抽象描述。比如学校分类概念的“初级中学”、“高级中学”的上位关系是“中学”。概念类型不允许创建念之间语义谓词以外的属性/关系,具体有哪些概念之间的语义见第二章。

实体类型和概念类型的区别,实体类型往往跟业务强相关,比如电商业务的用户、商户和商品等。而概念类型则通常和行业共识、领域常识强相关,比如学生、白领、公务员这些个概念是从职业角度对用户的分类概括,又比如五星商户、高信用商户这些概念是对商户的分类概括,再比如数码产品、母婴产品是对商品的分类概括。

概念类型在创建时,会默认包含如下属性:

  • name (概念名称,必填)

  • alias (概念别名,选填)

  • stdId (标准ID,选填)

概念在导入的时候,使用短横-符号作为上位关系分隔符,每个概念需要完整给出所有的上位关系。以学校分类概念举例概念实例:

1[
 2  {
 3    "name": "公立院校",
 4    "alias": "公立,公立学校"
 5  },
 6  {
 7    "name": "公立院校-公立大学",
 8    "alias": null
 9  },
10  {
11    "name": "公立院校-公立大学-公立综合类大学",
12    "alias": "公立综合大学"
13  }
14]

导入后形成如下子图:

1902c06e4f4c3a0fcf8eecd3377d0847.png

标准类型

(StandardType)

标准类型是系统内置的一种用于描述属性类型的特殊定义,通过正则约束对属性进行标准化,并且部分标准类型可以实现可传播效果,即标准类型的实例是单独的节点,属性值会被自动转换成节点,并跟实体形成连通关系。

标准类型英文名

标准类型中文名

正则约束

可传播

STD.ChinaMobile

国内手机号

^((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(16[5,6])|(17[0-8])|(18[0-9])|(19[1,5,8,9]))[0-9]{8}$

STD.Email

电子邮箱

^([a-zA-Z0-9]*[-_.]?[a-zA-Z0-9]+)*@([a-zA-Z0-9]*[-_]?[a-zA-Z0-9]+)+[.][A-Za-z]{2,3}([.][A-Za-z]{2})?$

STD.IdCardNo

身份证号

^[1-9]{1}[0-9]{5}(19|20)[0-9]{2}((0[1-9]{1})|(1[0-2]{1}))((0[1-9]{1})|([1-2]{1}[0-9]{1}|(3[0-1]{1})))[0-9]{3}[0-9xX]{1}$

STD.MacAddress

MAC地址

([A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2}

STD.Date

数字日期

[1,2][0-9][0-9][0-9](0[1-9]|1[0-2])(0[1-9]|[1,2][0-9]|3[0,1])

STD.ChinaTelCode

国内通讯号

^(400[0-9]{7})|(800[0-9]{7})|(0[0-9]{2,3}-[0-9]{7,8})|((13[0-9])|(14[5,7,9])|(15([0-3]|[5-9]))|(16[5,6])|(17[0-8])|(18[0-9])|(19[1,5,8,9]))[0-9]{8}$

STD.Timestamp

时间戳

^(\d{10})|(\d{13})$

05

附录 2:概念间语义分类

概念之间只允许定义以下7大类的语义谓词(属性和关系),具体定义形式为:分类简写#谓词,比如HYP#isA

14dafcdb8fb44e2c825c8430933af51b.png

HYP: 上位关系(Hypernym)

是指一种更广泛或更一般的概念包含或包括另一种更具体或更特定的概念的关系。

  • isA:是...一种

  • locateAt:位于

  • mannerOf:A 是 B 的一种特定实现方式。类似于 "IsA",但用于动词。比如 “拍卖” → “销售”

SYNANT: 同义反义关系(Synonymy/Antonymy)

表达概念之间是同义还是反义的关系。

  • synonym:表达同义词

  • antonym:表达反义词

  • symbolOf:A 象征性地代表了 B。比如:“红色” → “热情”

  • distinctFrom:A 和 B 是一个集合中不同的成员,是 A 的东西绝对不是 B。比如:“八月”→ “九月”

  • definedAs:A 和 B 在意义上有相当大的重叠,但 B 是 A 的更具解释性的版本。比如:“和平”→ “没有战争”

  • locatedNear:A 和 B 通常会被发现靠近彼此。比如:“椅子” → “桌子”

  • similarTo:A 和 B 相似。比如:“搅拌器” → “食物处理器”

  • etymologicallyRelatedTo:A和B有共同的来源。比如:“folkmusiikki” → “folk music”

CAU:因果关系(Causal)

表示指一个事件或行为(原因)导致另一个事件或行为(结果)发生的一类关系。

  • leadTo:表达事件通过逻辑规则实现传递,比如A事件实例会在满足指定规则的前提下生成一个B事件实例。此谓词在系统上会被识别为实例生成的意图,用于实现事件的实例传递。

  • causes:表达恒定的因果关系,没有条件约束

  • obstructedBy:A 是一个可能被 B 阻止的目标,B 是阻碍 A 实现的障碍。比如:“睡觉” → “噪音”

  • causesDesire:A使人想要B,其中A的状态或事件激发了对B的欲望或需求。比如:“没吃的”→“去商店”

  • createdBy:B 是一个创造 A 的过程或者动因。比如:“蛋糕”→“烘焙”

SEQ: 顺承关系(Sequential)

是连续发生的事情或动作,这些事情或动作有先后顺序。

  • happendedBefore:A先于B发生

  • hasSubevent:A和B是事件,B作为A的子事件发生。比如:“吃” → “咀嚼”

  • hasFirstSubevent:A 是一个以子事件 B 开始的事件。比如:“睡觉” → “闭眼”

  • hasLastSubevent:A 是一个以子事件 B 结束的事件。比如:“烹饪” → “收拾厨房”

  • hasPrerequisite:为了让 A 发生,需要发生 B;B 是 A 的前提条件。比如:“做梦” → “睡觉”

IND: 归纳关系(Induction)

是指从一类有共同特征的实体中得出对这些实体概括性的概念,这种个体和概念之间的关系就是归纳关系。

  • belongTo:该关系一般在SPG里用于实体类型到概念类型的分类关系描述,比如“公司事件” → “公司事件分类”

INC: 包含组成关系 (Inclusion)

表达部分与整体的关系。

  • isPartOf:A是B的一个部分

  • hasA:B 属于 A,作为固有部分或由于占有的社会构造。HasA 往往是 PartOf 的反向关系。比如:“鸟”→“翅膀”

  • madeOf:A是由B组成的。比如:“瓶子”→“塑料”

  • derivedFrom:A衍生/源自于B,用于表达组合概念

  • hasContext:A 是在 B 上下文中使用的一个词,B 可以是一个主题领域、技术领域或区域方言。比如:“astern”→“ship”

USE: 用途关系(Usage)

表达作用/用途的关系。

  • usedFor:A 被用于 B,A 的目的是 B。比如:“桥”→“通过水域”

  • capableOf:A 通常能做的事是 B。比如:“刀”→“切割”

  • receivesAction:B可以对A做的动作。比如:“按钮”→“按”

  • motivatedByGoal:某人做 A 是因为他们想要结果 B;A 是实现目标 B 的一个步骤。比如:“竞争”→“赢”


OpenKG

OpenKG(中文开放知识图谱)旨在推动以中文为核心的知识图谱数据的开放、互联及众包,并促进知识图谱算法、工具及平台的开源开放。

35065fcbcdf68cefacf37b11798ad619.png

点击阅读原文,进入 OpenKG 网站。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐