From c5440ec24be153cf47c9ac62edb5390e50f08b53 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 9 Jan 2024 17:42:46 +0800 Subject: [PATCH] refactor: update docs and adjust pages (#17) * new doc * refactor: update docs * refactor: update pages --------- Co-authored-by: fuyu.bfy --- .dumi/components/Redirect.tsx | 18 + .dumi/pages/openkg.en-US.tsx | 9 + .dumi/pages/openkg.tsx | 11 + .dumi/theme/layouts/GlobalLayout.tsx | 37 - .../enterprise-supply-chain/builder.en-US.md | 6 +- .../enterprise-supply-chain/builder.md | 4 +- .../enterprise-supply-chain/data.en-US.md | 1 - .../enterprise-supply-chain/data.md | 0 .../enterprise-supply-chain/index.en-US.md | 0 .../enterprise-supply-chain/index.md | 0 .../enterprise-supply-chain/model.en-US.md | 24 +- .../enterprise-supply-chain/model.md | 21 +- .../enterprise-supply-chain/query.en-US.md | 19 +- .../enterprise-supply-chain/query.md | 5 +- docs/{example => _example}/index.en-US.md | 0 docs/{example => _example}/index.md | 0 .../medical/index.en-US.md | 0 docs/{example => _example}/medical/index.md | 0 .../risk-mining/index.en-US.md | 0 .../risk-mining/index.md | 0 .../contribution.en-US.md | 16 +- .../contribution.md | 16 +- .../index.en-US.md | 0 docs/{quick-start => _quick-start}/index.md | 0 .../install.en-US.md | 49 +- docs/{quick-start => _quick-start}/install.md | 117 +- docs/{tutorial => _tutorial}/index.en-US.md | 0 docs/{tutorial => _tutorial}/index.md | 0 .../knext/index.en-US.md | 0 docs/{tutorial => _tutorial}/knext/index.md | 0 .../spg2lpg/index.en-US.md | 0 docs/{tutorial => _tutorial}/spg2lpg/index.md | 0 .../spgreasoner/index.en-US.md | 0 .../spgreasoner/index.md | 0 .../spgschema/index.en-US.md | 0 .../spgschema/index.md | 0 docs/examples/medicine/medicine.md | 139 ++ docs/examples/riskmining/riskmining.md | 191 +++ docs/examples/supplychain/builder.md | 137 ++ docs/examples/supplychain/data.md | 111 ++ docs/examples/supplychain/model.md | 189 +++ docs/examples/supplychain/query.md | 317 +++++ docs/examples/supplychain/supplychain.md | 151 ++ docs/introduction/builder.md | 67 + docs/introduction/general.md | 49 + docs/introduction/knext.md | 31 + docs/introduction/reasoner.md | 67 + docs/introduction/schema.md | 139 ++ docs/tutorial/installation/contribution.md | 84 ++ docs/tutorial/installation/installation.md | 84 ++ docs/tutorial/knext/command.md | 647 +++++++++ docs/tutorial/knext/nn4k.md | 107 ++ docs/tutorial/knext/operator.md | 297 ++++ docs/tutorial/reasoner/dsl.md | 1223 +++++++++++++++++ docs/tutorial/reasoner/rdg.md | 137 ++ docs/tutorial/schema/best_practice.md | 208 +++ docs/tutorial/schema/dsl.md | 301 ++++ docs/tutorial/schema/logical.md | 160 +++ package.json | 36 +- 59 files changed, 5031 insertions(+), 194 deletions(-) create mode 100644 .dumi/components/Redirect.tsx create mode 100644 .dumi/pages/openkg.en-US.tsx create mode 100644 .dumi/pages/openkg.tsx rename docs/{example => _example}/enterprise-supply-chain/builder.en-US.md (99%) rename docs/{example => _example}/enterprise-supply-chain/builder.md (99%) rename docs/{example => _example}/enterprise-supply-chain/data.en-US.md (99%) rename docs/{example => _example}/enterprise-supply-chain/data.md (100%) rename docs/{example => _example}/enterprise-supply-chain/index.en-US.md (100%) rename docs/{example => _example}/enterprise-supply-chain/index.md (100%) rename docs/{example => _example}/enterprise-supply-chain/model.en-US.md (95%) rename docs/{example => _example}/enterprise-supply-chain/model.md (95%) rename docs/{example => _example}/enterprise-supply-chain/query.en-US.md (95%) rename docs/{example => _example}/enterprise-supply-chain/query.md (99%) rename docs/{example => _example}/index.en-US.md (100%) rename docs/{example => _example}/index.md (100%) rename docs/{example => _example}/medical/index.en-US.md (100%) rename docs/{example => _example}/medical/index.md (100%) rename docs/{example => _example}/risk-mining/index.en-US.md (100%) rename docs/{example => _example}/risk-mining/index.md (100%) rename docs/{quick-start => _quick-start}/contribution.en-US.md (94%) rename docs/{quick-start => _quick-start}/contribution.md (94%) rename docs/{quick-start => _quick-start}/index.en-US.md (100%) rename docs/{quick-start => _quick-start}/index.md (100%) rename docs/{quick-start => _quick-start}/install.en-US.md (76%) rename docs/{quick-start => _quick-start}/install.md (52%) rename docs/{tutorial => _tutorial}/index.en-US.md (100%) rename docs/{tutorial => _tutorial}/index.md (100%) rename docs/{tutorial => _tutorial}/knext/index.en-US.md (100%) rename docs/{tutorial => _tutorial}/knext/index.md (100%) rename docs/{tutorial => _tutorial}/spg2lpg/index.en-US.md (100%) rename docs/{tutorial => _tutorial}/spg2lpg/index.md (100%) rename docs/{tutorial => _tutorial}/spgreasoner/index.en-US.md (100%) rename docs/{tutorial => _tutorial}/spgreasoner/index.md (100%) rename docs/{tutorial => _tutorial}/spgschema/index.en-US.md (100%) rename docs/{tutorial => _tutorial}/spgschema/index.md (100%) create mode 100644 docs/examples/medicine/medicine.md create mode 100644 docs/examples/riskmining/riskmining.md create mode 100644 docs/examples/supplychain/builder.md create mode 100644 docs/examples/supplychain/data.md create mode 100644 docs/examples/supplychain/model.md create mode 100644 docs/examples/supplychain/query.md create mode 100644 docs/examples/supplychain/supplychain.md create mode 100644 docs/introduction/builder.md create mode 100644 docs/introduction/general.md create mode 100644 docs/introduction/knext.md create mode 100644 docs/introduction/reasoner.md create mode 100644 docs/introduction/schema.md create mode 100644 docs/tutorial/installation/contribution.md create mode 100644 docs/tutorial/installation/installation.md create mode 100644 docs/tutorial/knext/command.md create mode 100644 docs/tutorial/knext/nn4k.md create mode 100644 docs/tutorial/knext/operator.md create mode 100644 docs/tutorial/reasoner/dsl.md create mode 100644 docs/tutorial/reasoner/rdg.md create mode 100644 docs/tutorial/schema/best_practice.md create mode 100644 docs/tutorial/schema/dsl.md create mode 100644 docs/tutorial/schema/logical.md diff --git a/.dumi/components/Redirect.tsx b/.dumi/components/Redirect.tsx new file mode 100644 index 0000000..7d541b7 --- /dev/null +++ b/.dumi/components/Redirect.tsx @@ -0,0 +1,18 @@ +import { Spin } from 'antd'; +import { useLocale } from 'dumi'; +import { useEffect } from 'react'; + +export const Redirect = ({ url }: { url: string }) => { + const { id: lang } = useLocale(); + + useEffect(() => { + window.location.replace(url); + }, []); + + return ( +
+ + {lang === 'zh-CN' ? '跳转中...' : 'Redirecting...'} +
+ ); +}; diff --git a/.dumi/pages/openkg.en-US.tsx b/.dumi/pages/openkg.en-US.tsx new file mode 100644 index 0000000..cc14e3e --- /dev/null +++ b/.dumi/pages/openkg.en-US.tsx @@ -0,0 +1,9 @@ +/** + * title: OpenKG + * nav: + * order: 5 + */ + +import OpenKG from './openkg'; + +export default OpenKG; diff --git a/.dumi/pages/openkg.tsx b/.dumi/pages/openkg.tsx new file mode 100644 index 0000000..0f688c5 --- /dev/null +++ b/.dumi/pages/openkg.tsx @@ -0,0 +1,11 @@ +/** + * title: OpenKG + * nav: + * order: 5 + */ + +import { Redirect } from '../components/Redirect'; + +export default () => { + return ; +}; diff --git a/.dumi/theme/layouts/GlobalLayout.tsx b/.dumi/theme/layouts/GlobalLayout.tsx index f464223..4b3a128 100644 --- a/.dumi/theme/layouts/GlobalLayout.tsx +++ b/.dumi/theme/layouts/GlobalLayout.tsx @@ -1,48 +1,11 @@ import { useLocale, useOutlet, usePrefersColor } from 'dumi'; import { Provider } from 'dumi-theme-openfinai'; -import { useEffect, useRef } from 'react'; - -// insert github and openKG in navbar -const createLink = (name: string, link: string, index: number) => { - const nav = document.getElementsByClassName('dumi-default-navbar')?.[0]; - if (!nav) return; - - const xpath = `//*[text()='${name}']`; - const element = document.evaluate( - xpath, - nav, - null, - XPathResult.FIRST_ORDERED_NODE_TYPE, - null, - ).singleNodeValue; - if (element) return; - - const li = document.createElement('li'); - const a = document.createElement('a'); - a.innerText = name; - a.href = link; - a.target = '_blank'; - a.rel = 'noopener noreferrer'; - li.appendChild(a); - nav.insertBefore(li, nav.childNodes[index]); - - return true; -}; export default () => { const { id: lang } = useLocale(); - const createLinkIntervalRef = useRef(); const outlet = useOutlet(); const [prefersColor] = usePrefersColor(); - useEffect(() => { - createLinkIntervalRef.current = window.setInterval(() => { - createLink('GitHub', 'https://github.com/OpenSPG/openspg', 1); - const result = createLink('OpenKG', 'http://openkg.cn/', 5); - if (result) clearInterval(createLinkIntervalRef.current); - }, 50); - }, []); - return ( > mapping >> sink -``` +```` To submit the task, execute the following command: diff --git a/docs/example/enterprise-supply-chain/builder.md b/docs/_example/enterprise-supply-chain/builder.md similarity index 99% rename from docs/example/enterprise-supply-chain/builder.md rename to docs/_example/enterprise-supply-chain/builder.md index a5ee706..6a425cc 100644 --- a/docs/example/enterprise-supply-chain/builder.md +++ b/docs/_example/enterprise-supply-chain/builder.md @@ -19,7 +19,7 @@ CSF0000000254,北大*药*份限公司,"医疗器械批发,医药批发,制药, 导入company的代码如下,详细内容如注释: -```python +````python # -*- coding: utf-8 -*- from knext.core.builder.pipeline.builder_job import BuilderJob @@ -85,7 +85,7 @@ class CompanyFundTrans(BuilderJob): return source >> mapping >> sink -``` +```` 在knext中执行如下命令提交任务: diff --git a/docs/example/enterprise-supply-chain/data.en-US.md b/docs/_example/enterprise-supply-chain/data.en-US.md similarity index 99% rename from docs/example/enterprise-supply-chain/data.en-US.md rename to docs/_example/enterprise-supply-chain/data.en-US.md index 09392a7..cd90e2a 100644 --- a/docs/example/enterprise-supply-chain/data.en-US.md +++ b/docs/_example/enterprise-supply-chain/data.en-US.md @@ -35,7 +35,6 @@ CSF0000002238,三角*胎股*限公司,"轮胎,全钢子午线轮胎" - name: The name of the company. - products: The products produced by the company, separated by ','. - ## 3 Transfer relation between companys (Company_fundTrans_Company.csv) ``` diff --git a/docs/example/enterprise-supply-chain/data.md b/docs/_example/enterprise-supply-chain/data.md similarity index 100% rename from docs/example/enterprise-supply-chain/data.md rename to docs/_example/enterprise-supply-chain/data.md diff --git a/docs/example/enterprise-supply-chain/index.en-US.md b/docs/_example/enterprise-supply-chain/index.en-US.md similarity index 100% rename from docs/example/enterprise-supply-chain/index.en-US.md rename to docs/_example/enterprise-supply-chain/index.en-US.md diff --git a/docs/example/enterprise-supply-chain/index.md b/docs/_example/enterprise-supply-chain/index.md similarity index 100% rename from docs/example/enterprise-supply-chain/index.md rename to docs/_example/enterprise-supply-chain/index.md diff --git a/docs/example/enterprise-supply-chain/model.en-US.md b/docs/_example/enterprise-supply-chain/model.en-US.md similarity index 95% rename from docs/example/enterprise-supply-chain/model.en-US.md rename to docs/_example/enterprise-supply-chain/model.en-US.md index 8401ee1..cf49f50 100644 --- a/docs/example/enterprise-supply-chain/model.en-US.md +++ b/docs/_example/enterprise-supply-chain/model.en-US.md @@ -5,7 +5,7 @@ order: 1 ## 1 Schema details -Schema tutorial of OpenSPG, please refer to [spgschema_tutorial](../../tutorial/spgschema/index.en-US.md).
+Schema tutorial of OpenSPG, please refer to [spgschema_tutorial](../../tutorial_deprecated/spgschema/index.en-US.md).
For modeling of the Enterprise Supply Chain Knowledge Graph, please refer to the documentation:[schema description](https://github.com/OpenSPG/openspg/blob/master/python/knext/examples/supplychain/schema/supplychain.schema).
Execute the following script to complete schema creation:
@@ -48,7 +48,7 @@ Product(产品): EntityType name(产品名): Text relations: isA(上位产品): Product - + Company(企业): EntityType relations: product(经营产品): Product @@ -66,8 +66,8 @@ CSF0000000254,北大*药*份限公司,其他化学药品 This approach has two disadvantages:
-1) The raw data needs to be cleaned and converted into multiple rows.
-2) It requires adding and maintaining relation data. When the original data changes, the existing relations need to be deleted and new data needs to be added, which can lead to data errors.
+1. The raw data needs to be cleaned and converted into multiple rows.
+2. It requires adding and maintaining relation data. When the original data changes, the existing relations need to be deleted and new data needs to be added, which can lead to data errors.
#### 2.1.3 Modeling based on SPG semantic attributes @@ -77,7 +77,7 @@ The modeling can be done as follows:
```yaml Product(产品): ConceptType hypernymPredicate: isA - + Company(企业): EntityType properties: product(经营产品): Product @@ -151,7 +151,7 @@ OpenSPG distinguishes between concepts and entities to decouple semantics from d ```yaml Product(产品): ConceptType hypernymPredicate: isA - + Company(企业): EntityType properties: product(经营产品): Product @@ -167,12 +167,12 @@ The representation of events with multiple elements is indeed a type of lossless ```yaml Event(事件): - properties: - eventTime(发生时间): Long - subject(涉事主体): Text - object(客体): Text - place(地点): Text - industry(涉事行业): Text + properties: + eventTime(发生时间): Long + subject(涉事主体): Text + object(客体): Text + place(地点): Text + industry(涉事行业): Text ``` This representation method is unable to capture the multidimensional associations of real events. OpenSPG provides event modeling that enables the association of multiple elements in an event, as shown below. diff --git a/docs/example/enterprise-supply-chain/model.md b/docs/_example/enterprise-supply-chain/model.md similarity index 95% rename from docs/example/enterprise-supply-chain/model.md rename to docs/_example/enterprise-supply-chain/model.md index c7bf4f1..01676a0 100644 --- a/docs/example/enterprise-supply-chain/model.md +++ b/docs/_example/enterprise-supply-chain/model.md @@ -5,7 +5,7 @@ order: 1 ## 1 建模文件 -建模描述文档参见[spg schema](../../tutorial/spgschema/index.md)。 +建模描述文档参见[spg schema](../../tutorial_deprecated/spgschema/index.md)。 建模参考文件[企业供应链图谱schema](https://github.com/OpenSPG/openspg/blob/master/python/knext/examples/supplychain/schema/supplychain.schema)。 @@ -49,7 +49,7 @@ Product(产品): EntityType name(产品名): Text relations: isA(上位产品): Product - + Company(企业): EntityType relations: product(经营产品): Product @@ -71,7 +71,6 @@ CSF0000000254,北大*药*份限公司,其他化学药品 2)需要新增维护关系数据,当原始数据发生变更时,需要删除原有关系,再新增数据,容易导致数据错误。 - #### 2.1.3 基于SPG语义属性建模 SPG支持语义属性,可简化知识构建,如下: @@ -79,7 +78,7 @@ SPG支持语义属性,可简化知识构建,如下: ```yaml Product(产品): ConceptType hypernymPredicate: isA - + Company(企业): EntityType properties: product(经营产品): Product @@ -153,7 +152,7 @@ SPG区分了概念和实体,用于解耦语义和数据,如下: ```yaml Product(产品): ConceptType hypernymPredicate: isA - + Company(企业): EntityType properties: product(经营产品): Product @@ -170,12 +169,12 @@ Company(企业): EntityType ```yaml Event(事件): - properties: - eventTime(发生时间): Long - subject(涉事主体): Text - object(客体): Text - place(地点): Text - industry(涉事行业): Text + properties: + eventTime(发生时间): Long + subject(涉事主体): Text + object(客体): Text + place(地点): Text + industry(涉事行业): Text ``` 这种表达方式,是无法体现真实事件的多元关联性,SPG提供了事件建模,可实现事件多元要素的关联,如下: diff --git a/docs/example/enterprise-supply-chain/query.en-US.md b/docs/_example/enterprise-supply-chain/query.en-US.md similarity index 95% rename from docs/example/enterprise-supply-chain/query.en-US.md rename to docs/_example/enterprise-supply-chain/query.en-US.md index b250bdd..398de5e 100644 --- a/docs/example/enterprise-supply-chain/query.en-US.md +++ b/docs/_example/enterprise-supply-chain/query.en-US.md @@ -7,11 +7,11 @@ order: 4 Requirement: In enterprise credit rating, the following decision factors are needed:
-1) Primary supplier relations
-2) Industry of the products produced by the enterprise
-3) Transfer transaction records of funds for the past 1 month, 3 months, and 6 months
-4) Difference in funds flow for the past 1 month, 3 months, and 6 months
-5) Information on related companies controlled by the ultimate beneficial owner
+1. Primary supplier relations
+2. Industry of the products produced by the enterprise
+3. Transfer transaction records of funds for the past 1 month, 3 months, and 6 months
+4. Difference in funds flow for the past 1 month, 3 months, and 6 months
+5. Information on related companies controlled by the ultimate beneficial owner
However, in the original knowledge graph, only fund transfer transactions and legal representative information are available, making it impossible to directly obtain the above features. This example demonstrates how to use OpenSPG to obtain these 5 features. @@ -189,8 +189,8 @@ Obtaining specific features of a particular company through GQL using the follow MATCH (s:SupplyChain.Company) RETURN - s.id, s.fundTrans1Month, s.fundTrans3Month, - s.fundTrans6Month, s.fundTrans1MonthIn, s.fundTrans3MonthIn, + s.id, s.fundTrans1Month, s.fundTrans3Month, + s.fundTrans6Month, s.fundTrans1MonthIn, s.fundTrans3MonthIn, s.fundTrans6MonthIn, s.cashflowDiff1Month, s.cashflowDiff3Month, s.cashflowDiff6Month ``` @@ -218,6 +218,7 @@ RETURN ## Scenario 2: Change in the company's supply chain Suppose that there is a change in the products produced by the company: + ``` "钱****份限公司"发布公告,生产产品“三轮摩托车,二轮摩托车”变更为“两轮摩托车”,则"三角**轮胎股份"和钱"****份限公司"的主供应链关系自动断裂,"三角**轮胎股份"和"钱****份限公司"不再具有主供应链关系 ``` @@ -267,7 +268,7 @@ Butadiene rubber costs rise, classified as an event of price increase in the sup The logical rule expression is as follows:
``` -// When the attributes of ProductChainEvent satisfy the condition of price increase, +// When the attributes of ProductChainEvent satisfy the condition of price increase, // the event is classified as a price increase event. Define (e:ProductChainEvent)-[p:belongTo]->(o:`TaxonofProductChainEvent`/`价格上涨`) { Structure { @@ -287,7 +288,7 @@ Define (s:`TaxonofProductChainEvent`/`价格上涨`)-[p:leadTo]->(o:`TaxonofComp Structure { //1. Find the subject of the supply chain event, which is butadiene rubber in this case //2. Identify the downstream products of butadiene rubber, which are bias tires in this case - //3. Identify all the companies that produce bias tires, which is "Triangle** Tire Co., Ltd." in this case + //3. Identify all the companies that produce bias tires, which is "Triangle** Tire Co., Ltd." in this case (s)-[:subject]->[prod:Product]-[:hasSupplyChain]->(down:Product)<-[:product]-(c:Company) } Constraint { diff --git a/docs/example/enterprise-supply-chain/query.md b/docs/_example/enterprise-supply-chain/query.md similarity index 99% rename from docs/example/enterprise-supply-chain/query.md rename to docs/_example/enterprise-supply-chain/query.md index 6050724..67e966f 100644 --- a/docs/example/enterprise-supply-chain/query.md +++ b/docs/_example/enterprise-supply-chain/query.md @@ -183,8 +183,8 @@ Define (s:Compnay)-[p:sameLegalReprensentative]->(o:Company) { MATCH (s:SupplyChain.Company) RETURN - s.id, s.fundTrans1Month, s.fundTrans3Month, - s.fundTrans6Month, s.fundTrans1MonthIn, s.fundTrans3MonthIn, + s.id, s.fundTrans1Month, s.fundTrans3Month, + s.fundTrans6Month, s.fundTrans1MonthIn, s.fundTrans3MonthIn, s.fundTrans6MonthIn, s.cashflowDiff1Month, s.cashflowDiff3Month, s.cashflowDiff6Month ``` @@ -212,6 +212,7 @@ RETURN ## 场景2:企业供应链发生变化 假设供应链发生如下变化:
+ ``` "钱****份限公司"发布公告,生产产品“三轮摩托车,二轮摩托车”变更为“两轮摩托车”,则"三角**轮胎股份"和钱"****份限公司"的主供应链关系自动断裂,"三角**轮胎股份"和"钱****份限公司"不再具有主供应链关系 ``` diff --git a/docs/example/index.en-US.md b/docs/_example/index.en-US.md similarity index 100% rename from docs/example/index.en-US.md rename to docs/_example/index.en-US.md diff --git a/docs/example/index.md b/docs/_example/index.md similarity index 100% rename from docs/example/index.md rename to docs/_example/index.md diff --git a/docs/example/medical/index.en-US.md b/docs/_example/medical/index.en-US.md similarity index 100% rename from docs/example/medical/index.en-US.md rename to docs/_example/medical/index.en-US.md diff --git a/docs/example/medical/index.md b/docs/_example/medical/index.md similarity index 100% rename from docs/example/medical/index.md rename to docs/_example/medical/index.md diff --git a/docs/example/risk-mining/index.en-US.md b/docs/_example/risk-mining/index.en-US.md similarity index 100% rename from docs/example/risk-mining/index.en-US.md rename to docs/_example/risk-mining/index.en-US.md diff --git a/docs/example/risk-mining/index.md b/docs/_example/risk-mining/index.md similarity index 100% rename from docs/example/risk-mining/index.md rename to docs/_example/risk-mining/index.md diff --git a/docs/quick-start/contribution.en-US.md b/docs/_quick-start/contribution.en-US.md similarity index 94% rename from docs/quick-start/contribution.en-US.md rename to docs/_quick-start/contribution.en-US.md index 6124d21..4db5a56 100644 --- a/docs/quick-start/contribution.en-US.md +++ b/docs/_quick-start/contribution.en-US.md @@ -27,7 +27,6 @@ Python code style generally requires adherence to the PEP 8 standard. If you are using PyCharm for development, you can use the [BlackConnect](https://black.readthedocs.io/en/stable/integrations/editors.html) plugin to format your code. - #### Docstring Use the Google style format for docstrings. @@ -52,7 +51,8 @@ For issues related to functionality optimization, feature expansion, bug fixes, ## Development Process -* Switch to your development branch +- Switch to your development branch + ```bash git checkout -b your-branch .... @@ -60,27 +60,27 @@ For issues related to functionality optimization, feature expansion, bug fixes, git commit -m "xxx" ``` -* Develop your feature +- Develop your feature -* Add unit tests +- Add unit tests -* Submit a Pull Request (PR) +- Submit a Pull Request (PR) -* Handle conflicts +- Handle conflicts ```bash git checkout your-branch git rebase master # Make sure your local master branch is up to date ``` -* Code review +- Code review The code you submitted requires a code review to be mergeed into the master branch. Please be patient and wait. We will assign relevant colleagues for code review. If there is no response to your PR within two working days, please mention the relevant colleagues in your PR. The comments related to the code review will be directly posted on the relevant PR or issue. If you find the suggestions reasonable, please update your code accordingly. -* Merge into the master branch +- Merge into the master branch After the code review, we will arrange for further review by another colleague to ensure that each PR has at least two approvals before merging into the master. During this process, there may be some suggestions for modifications. Please be patient and make the necessary changes. diff --git a/docs/quick-start/contribution.md b/docs/_quick-start/contribution.md similarity index 94% rename from docs/quick-start/contribution.md rename to docs/_quick-start/contribution.md index 3a8579b..21cec7c 100644 --- a/docs/quick-start/contribution.md +++ b/docs/_quick-start/contribution.md @@ -51,7 +51,8 @@ python 的 code style 总体上要求符合pep8的标准。 ## 开发流程 -* 切换到你的开发分支 +- 切换到你的开发分支 + ``` git checkout -b your-branch .... @@ -59,23 +60,24 @@ python 的 code style 总体上要求符合pep8的标准。 git commit -m "xxx" ``` -* 开发你的功能 -* 添加单测 -* 提交PR -* 处理冲突 +- 开发你的功能 +- 添加单测 +- 提交PR +- 处理冲突 ``` git checkout your-branch git rebase master # 确保本地 master 是最新的 ``` -* Code review + +- Code review 你提交的代码需要通过 code review 才能合入到 master, 请耐心等待。 我们将分配相关同学进行 code review。 如果相关同学2个工作日仍然没有回应你的PR,请在你的PR中 @ 相关同学。 code review的相关评论会直接贴到相关的 PR 或 issue 中。 如果你觉得相关建议是合理的,请更新你的代码。 -* 合入到 master +- 合入到 master code review 通过后,我们会安排新的同学进一步review, 确保每个 PR 至少有两个同意后才能合入到主线。 这个过程中可能也会出现一些需要修改的意见,请耐心修改。 diff --git a/docs/quick-start/index.en-US.md b/docs/_quick-start/index.en-US.md similarity index 100% rename from docs/quick-start/index.en-US.md rename to docs/_quick-start/index.en-US.md diff --git a/docs/quick-start/index.md b/docs/_quick-start/index.md similarity index 100% rename from docs/quick-start/index.md rename to docs/_quick-start/index.md diff --git a/docs/quick-start/install.en-US.md b/docs/_quick-start/install.en-US.md similarity index 76% rename from docs/quick-start/install.en-US.md rename to docs/_quick-start/install.en-US.md index 27aa38c..baf83c2 100644 --- a/docs/quick-start/install.en-US.md +++ b/docs/_quick-start/install.en-US.md @@ -9,17 +9,16 @@ OpenSPG is developed based on Java & Python, the server side developed using the The server side of OpenSPG can be quickly installed using `Docker Compose`. The relevant image addresses can be found at [`Docker Images`](https://hub.docker.com/repositories/baifuyu), which includes the following two images: - -| Module Name | Image Name | Description | -|---------------|------------------------------|-----------------------------------| -| openspg | baifuyu/openspg:latest | The server image of OpenSPG, developed based on Java | +| Module Name | Image Name | Description | +| ------------- | ---------------------------- | ----------------------------------------------------------------------------------- | +| openspg | baifuyu/openspg:latest | The server image of OpenSPG, developed based on Java | | openspg-mysql | baifuyu/openspg-mysql:latest | The database image of OpenSPG that initializes some tables and data, based on Mysql | In addition to the above 2 Docker images, to start the OpenSPG service, you also need 2 additional Docker images: -| Module Name | Image Name | Description | -|---------------|---------------------------------------|-------------------------| -| tugraph | tugraph/tugraph-runtime-centos7:4.0.1 | The graph storage image of OpenSPG, used for storing graph data | +| Module Name | Image Name | Description | +| ------------- | ------------------------------------- | ------------------------------------------------------------------ | +| tugraph | tugraph/tugraph-runtime-centos7:4.0.1 | The graph storage image of OpenSPG, used for storing graph data | | elasticsearch | elasticsearch:8.5.3 | The search engine image of OpenSPG, used for indexing the SPG data | The OpenSPG client can be installed using the `pip` package management tool for `Python`, with a minimum required version of `Python` being >=3.8. @@ -42,7 +41,7 @@ knext --version ### 2.2 Server Installation -1. Install `Docker` on your local machine.
+1. Install `Docker` on your local machine.
You can refer to the official documentation for the environment setup: [https://www.docker.com/products/docker-desktop/](https://www.docker.com/products/docker-desktop/) 2. The `Docker Compose` configuration file is as follows. Copy the following content and save it as `docker-compose.yml` on your local machine. Then, use the following command to start the installation: @@ -54,23 +53,24 @@ docker-compose -f docker-compose.yml up -d The content of `the docker-compose.yml` file is as follows: ```yaml -version: "3.7" +version: '3.7' services: openspg: restart: always image: baifuyu/openspg:latest container_name: release-openspg ports: - - "8887:8887" + - '8887:8887' depends_on: - mysql - tugraph - elasticsearch - command: [ - '--cloudext.repository.impl.jdbc.host=mysql', - '--builder.operator.python.exec=/usr/bin/python3.8', - '--builder.operator.python.paths=/usr/lib/python3.8/site-packages;/usr/local/lib/python3.8/dist-packages;' - ] + command: + [ + '--cloudext.repository.impl.jdbc.host=mysql', + '--builder.operator.python.exec=/usr/bin/python3.8', + '--builder.operator.python.paths=/usr/lib/python3.8/site-packages;/usr/local/lib/python3.8/dist-packages;', + ] environment: - PYTHONPATH=/usr/lib/python3.8/site-packages:/usr/local/lib/python3.8/dist-packages @@ -82,27 +82,28 @@ services: TZ: Asia/Shanghai LANG: C.UTF-8 ports: - - "3306:3306" - command: [ - '--character-set-server=utf8mb4', - '--collation-server=utf8mb4_general_ci' - ] + - '3306:3306' + command: + [ + '--character-set-server=utf8mb4', + '--collation-server=utf8mb4_general_ci', + ] tugraph: image: tugraph/tugraph-runtime-centos7:4.0.1 container_name: release-openspg-tugraph # default username is admin and default password is 73@TuGraph ports: - - "7070:7070" - - "9090:9090" + - '7070:7070' + - '9090:9090' command: lgraph_server elasticsearch: image: elasticsearch:8.5.3 container_name: test-openspg-elasticsearch ports: - - "9200:9200" - - "9300:9300" + - '9200:9200' + - '9300:9300' environment: - discovery.type=single-node - xpack.security.enabled=false diff --git a/docs/quick-start/install.md b/docs/_quick-start/install.md similarity index 52% rename from docs/quick-start/install.md rename to docs/_quick-start/install.md index 60a480a..2887cd3 100644 --- a/docs/quick-start/install.md +++ b/docs/_quick-start/install.md @@ -10,16 +10,16 @@ order: 2 `OpenSPG`服务端可以通过`Docker Compose`快速安装,相关的镜像地址请[点击](https://hub.docker.com/repositories/baifuyu),该链接中包含了以下2个镜像: -| 模块名 | 镜像名 | 说明 | -|---------------|------------------------------|-----------------------------------| -| openspg | baifuyu/openspg:latest | OpenSPG服务端镜像,基于Java开发 | +| 模块名 | 镜像名 | 说明 | +| ------------- | ---------------------------- | ------------------------------------------------ | +| openspg | baifuyu/openspg:latest | OpenSPG服务端镜像,基于Java开发 | | openspg-mysql | baifuyu/openspg-mysql:latest | OpenSPG Mysql镜像,基于Mysql初始化了一些表和数据 | 除了以上2个Docker镜像,要启动OpenSPG服务,还需要另外2个Docker镜像: -| 模块名 | 镜像名 | 说明 | -|---------------|---------------------------------------|-------------------------| -| tugraph | tugraph/tugraph-runtime-centos7:4.0.1 | OpenSPG图存储镜像,用于存储图谱数据 | +| 模块名 | 镜像名 | 说明 | +| ------------- | ------------------------------------- | ------------------------------------ | +| tugraph | tugraph/tugraph-runtime-centos7:4.0.1 | OpenSPG图存储镜像,用于存储图谱数据 | | elasticsearch | elasticsearch:8.5.3 | OpenSPG搜索存储镜像,用于索引SPG数据 | `OpenSPG`客户端通过`Python`包管理工具`pip`工具安装,`Python`版本要求>=3.8; @@ -54,58 +54,60 @@ docker-compose -f docker-compose.yml up -d 其中`docker-compose.yml`文件内容: ```yaml -version: "3.7" +version: '3.7' services: - openspg: - restart: always - image: baifuyu/openspg:latest - container_name: release-openspg - ports: - - "8887:8887" - depends_on: - - mysql - - tugraph - - elasticsearch - command: [ - '--cloudext.repository.impl.jdbc.host=mysql', - '--builder.operator.python.exec=/usr/bin/python3.8', - '--builder.operator.python.paths=/usr/lib/python3.8/site-packages;/usr/local/lib/python3.8/dist-packages;' - ] - environment: - - PYTHONPATH=/usr/lib/python3.8/site-packages:/usr/local/lib/python3.8/dist-packages - - mysql: - restart: always - image: baifuyu/openspg-mysql:latest - container_name: release-openspg-mysql - environment: - TZ: Asia/Shanghai - LANG: C.UTF-8 - ports: - - "3306:3306" - command: [ - '--character-set-server=utf8mb4', - '--collation-server=utf8mb4_general_ci' - ] - - tugraph: - image: tugraph/tugraph-runtime-centos7:4.0.1 - container_name: release-openspg-tugraph - # default username is admin and default password is 73@TuGraph - ports: - - "7070:7070" - - "9090:9090" - command: lgraph_server - - elasticsearch: - image: elasticsearch:8.5.3 - container_name: test-openspg-elasticsearch - ports: - - "9200:9200" - - "9300:9300" - environment: - - discovery.type=single-node - - xpack.security.enabled=false + openspg: + restart: always + image: baifuyu/openspg:latest + container_name: release-openspg + ports: + - '8887:8887' + depends_on: + - mysql + - tugraph + - elasticsearch + command: + [ + '--cloudext.repository.impl.jdbc.host=mysql', + '--builder.operator.python.exec=/usr/bin/python3.8', + '--builder.operator.python.paths=/usr/lib/python3.8/site-packages;/usr/local/lib/python3.8/dist-packages;', + ] + environment: + - PYTHONPATH=/usr/lib/python3.8/site-packages:/usr/local/lib/python3.8/dist-packages + + mysql: + restart: always + image: baifuyu/openspg-mysql:latest + container_name: release-openspg-mysql + environment: + TZ: Asia/Shanghai + LANG: C.UTF-8 + ports: + - '3306:3306' + command: + [ + '--character-set-server=utf8mb4', + '--collation-server=utf8mb4_general_ci', + ] + + tugraph: + image: tugraph/tugraph-runtime-centos7:4.0.1 + container_name: release-openspg-tugraph + # default username is admin and default password is 73@TuGraph + ports: + - '7070:7070' + - '9090:9090' + command: lgraph_server + + elasticsearch: + image: elasticsearch:8.5.3 + container_name: test-openspg-elasticsearch + ports: + - '9200:9200' + - '9300:9300' + environment: + - discovery.type=single-node + - xpack.security.enabled=false ``` 第一次使用需要下载以上4个服务的Docker镜像,速度会有点慢,请耐心等待~。 @@ -118,4 +120,3 @@ services: 2. [企业供应链图谱](../example/enterprise-supply-chain/index.md) 3. [黑产挖掘图谱](../example/risk-mining/index.md) 4. [医疗知识图谱](../example/medical/index.md) - diff --git a/docs/tutorial/index.en-US.md b/docs/_tutorial/index.en-US.md similarity index 100% rename from docs/tutorial/index.en-US.md rename to docs/_tutorial/index.en-US.md diff --git a/docs/tutorial/index.md b/docs/_tutorial/index.md similarity index 100% rename from docs/tutorial/index.md rename to docs/_tutorial/index.md diff --git a/docs/tutorial/knext/index.en-US.md b/docs/_tutorial/knext/index.en-US.md similarity index 100% rename from docs/tutorial/knext/index.en-US.md rename to docs/_tutorial/knext/index.en-US.md diff --git a/docs/tutorial/knext/index.md b/docs/_tutorial/knext/index.md similarity index 100% rename from docs/tutorial/knext/index.md rename to docs/_tutorial/knext/index.md diff --git a/docs/tutorial/spg2lpg/index.en-US.md b/docs/_tutorial/spg2lpg/index.en-US.md similarity index 100% rename from docs/tutorial/spg2lpg/index.en-US.md rename to docs/_tutorial/spg2lpg/index.en-US.md diff --git a/docs/tutorial/spg2lpg/index.md b/docs/_tutorial/spg2lpg/index.md similarity index 100% rename from docs/tutorial/spg2lpg/index.md rename to docs/_tutorial/spg2lpg/index.md diff --git a/docs/tutorial/spgreasoner/index.en-US.md b/docs/_tutorial/spgreasoner/index.en-US.md similarity index 100% rename from docs/tutorial/spgreasoner/index.en-US.md rename to docs/_tutorial/spgreasoner/index.en-US.md diff --git a/docs/tutorial/spgreasoner/index.md b/docs/_tutorial/spgreasoner/index.md similarity index 100% rename from docs/tutorial/spgreasoner/index.md rename to docs/_tutorial/spgreasoner/index.md diff --git a/docs/tutorial/spgschema/index.en-US.md b/docs/_tutorial/spgschema/index.en-US.md similarity index 100% rename from docs/tutorial/spgschema/index.en-US.md rename to docs/_tutorial/spgschema/index.en-US.md diff --git a/docs/tutorial/spgschema/index.md b/docs/_tutorial/spgschema/index.md similarity index 100% rename from docs/tutorial/spgschema/index.md rename to docs/_tutorial/spgschema/index.md diff --git a/docs/examples/medicine/medicine.md b/docs/examples/medicine/medicine.md new file mode 100644 index 0000000..20e3e28 --- /dev/null +++ b/docs/examples/medicine/medicine.md @@ -0,0 +1,139 @@ +--- +title: 医疗图谱 +--- + +## 从文本构建医疗图谱 + +本示例旨在展示如何基于SPG-Schema的定义,利用大模型实现对图谱实体和关系的抽取和构建到图谱。 +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*-PKySKstgy8AAAAAAAAAAAAADtmcAQ/original#id=NGInL&originHeight=1374&originWidth=2876&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +## 1 Quick Start + +### Step1:进入案例目录 + +```shell + cd /openspg/python/knext/knext/examples/medicine/ +``` + +### Step2:项目初始化 + +先对项目进行初始化动作 + +```shell +knext project create --prj_path . +``` + +### Step3:知识建模 + +schema文件已创建好[医疗SPG Schema模型](https://github.com/OpenSPG/openspg/blob/master/python/knext/knext/examples/medicine/schema/medicine.schema) +,可执行如下命令提交 + +```shell +knext schema commit +``` + +```shell +# 提交人体部位和医院部门概念导入任务 +knext builder execute BodyPart,HospitalDepartment +``` + +### step4:知识抽取构建 + +该图谱中“Disease”实体类型,需要从非结构化的本文数据中抽取,最终得到结构化的知识。 +输入原始数据参考[Disease原始文本](https://github.com/OpenSPG/openspg/blob/master/python/knext/knext/examples/medicine/builder/job/data/Disease.csv) +,如下面例子所示: + +```shell +甲状腺结节是指在甲状腺内的肿块,可随吞咽动作随甲状腺而上下移动,是临床常见的病症,可由多种病因引起。临床上有多种甲状腺疾病,如甲状腺退行性变、炎症、自身免疫以及新生物等都可以表现为结节。甲状腺结节可以单发,也可以多发,多发结节比单发结节的发病率高,但单发结节甲状腺癌的发生率较高。患者通常可以选择在普外科,甲状腺外科,内分泌科,头颈外科挂号就诊。有些患者可以触摸到自己颈部前方的结节。在大多情况下,甲状腺结节没有任何症状,甲状腺功能也是正常的。甲状腺结节进展为其它甲状腺疾病的概率只有1%。有些人会感觉到颈部疼痛、咽喉部异物感,或者存在压迫感。当甲状腺结节发生囊内自发性出血时,疼痛感会更加强烈。治疗方面,一般情况下可以用放射性碘治疗,复方碘口服液(Lugol液)等,或者服用抗甲状腺药物来抑制甲状腺激素的分泌。目前常用的抗甲状腺药物是硫脲类化合物,包括硫氧嘧啶类的丙基硫氧嘧啶(PTU)和甲基硫氧嘧啶(MTU)及咪唑类的甲硫咪唑和卡比马唑。 +``` + +下面的抽取示例中,我们通过抽取算子,调用了“gpt-3.5”模型完成抽取任务,具体步骤: +**第一步:配置模型服务,配置文件参考**[**builder/model/openai_infer.json +**](https://github.com/OpenSPG/openspg/blob/master/python/knext/knext/examples/medicine/builder/model/openai_infer.json) +**,这里使用openai的gpt-3.5模型,内容格式如下。** + +```shell +{ + "nn_name": "gpt-3.5-turbo", + "openai_api_key": "***input your api key***", + "openai_api_base": "***input your service addr****", + "openai_max_tokens": 1000 +} +``` + +**第二步:编写Disease的构建任务代码,本案例的代码可以参考**[**builder/job/disease.py**]()**。** +**在Disease的BuildJob代码中,使用了LLMBasedExtractor算子:** + +```shell +from nn4k.invoker import NNInvoker + +from knext.api.component import CSVReader, LLMBasedExtractor, SPGTypeMapping, KGWriter +from knext.api.auto_prompt import REPrompt +from knext.client.model.builder_job import BuilderJob + + +from schema.medicine_schema_helper import Medicine + + +class Disease(BuilderJob): + def build(self): + # 数据源 + source = CSVReader( + local_path="builder/job/data/Disease.csv", + columns=["input"], + start_row=1, + ) + + # 使用默认的LLMBasedExtractor抽取算子 + # NNInvoker封装了对gpt模型服务的调用 + # REPrompt根据schema自动生成prompt + extract = LLMBasedExtractor( + llm=NNInvoker.from_config("builder/model/openai_infer.json"), + prompt_ops=[ + REPrompt( + spg_type_name=Medicine.Disease, + property_names=[ + Medicine.Disease.complication, + Medicine.Disease.commonSymptom, + Medicine.Disease.applicableDrug, + Medicine.Disease.department, + Medicine.Disease.diseaseSite, + ], + relation_names=[(Medicine.Disease.abnormal, Medicine.Indicator)], + ) + ], + ) + + #抽取结果与schema的映射 + mappings = [ + SPGTypeMapping(spg_type_name=Medicine.Disease), + SPGTypeMapping(spg_type_name=Medicine.BodyPart), + SPGTypeMapping(spg_type_name=Medicine.Drug), + SPGTypeMapping(spg_type_name=Medicine.HospitalDepartment), + SPGTypeMapping(spg_type_name=Medicine.Symptom), + SPGTypeMapping(spg_type_name=Medicine.Indicator), + ] + + sink = KGWriter() + + return source >> extract >> mappings >> sink +``` + +**第三步:提交知识抽取任务** + +```bash +knext builder execute Disease +``` + +### step5:执行图谱任务 + +SPG支持ISO GQL写法,可用如下命令行执行查询任务 + +```cypher +knext reasoner execute --dsl " +MATCH + (s:Medical.Disease)-[p]->(o) +RETURN + s +" +``` diff --git a/docs/examples/riskmining/riskmining.md b/docs/examples/riskmining/riskmining.md new file mode 100644 index 0000000..e1aa637 --- /dev/null +++ b/docs/examples/riskmining/riskmining.md @@ -0,0 +1,191 @@ +--- +title: 黑产挖掘 +--- + +## 黑产挖掘图谱 + +关键词:语义属性,实体动态多分类,面向业务知识和事实数据分层下的知识应用 + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*KGmMRJvQEdQAAAAAAAAAAAAADtmcAQ/original#id=x3LCX&originHeight=1518&originWidth=2874&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +## Quick Start + +### step1:进入案例目录 + +```shell + cd /openspg/python/knext/knext/examples/riskmining/ +``` + +### step2:项目初始化 + +先对项目进行初始化动作 + +```cypher +knext project create --prj_path . +``` + +### step3:知识建模 + +schema文件已创建好[黑产SPG Schema模型](https://github.com/OpenSPG/openspg/blob/master/python/knext/knext/examples/riskmining/schema/riskmining.schema) +,可执行如下命令提交 + +```shell +knext schema commit +``` + +```shell +# 提交风险用户、风险APP的分类概念 +knext builder execute TaxOfRiskUser,TaxOfRiskApp +knext schema reg_concept_rule --file ./schema/concept.rule +``` + +### step4:知识构建 + +提交知识导入任务 + +```bash +knext builder execute Cert,Company,CompanyHasCert +knext builder execute App,Device,Person,PersonFundTrans,PersonHasDevice,PersonHoldShare +``` + +### step5:执行图谱任务 + +SPG支持ISO GQL写法,可用如下命令行执行查询任务 + +```cypher +knext reasoner execute --dsl "${ql}" +``` + +#### 场景1:语义属性对比文本属性 + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*uKcjRqTdy7cAAAAAAAAAAAAADtmcAQ/original#id=OtwWE&originHeight=450&originWidth=1314&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +电话号码:标准属性 vs 文本属性。 + +编辑dsl_task.txt,输入如下内容: + +``` +MATCH + (phone:STD.ChinaMobile)<-[:hasPhone]-(u:RiskMining.Person) +RETURN + u.id,phone.id +``` + +执行脚本: + +``` +knext reasoner execute --file ./reasoner/dsl_task.txt +``` + +#### 场景2:实体动态多类型 + +注意:本节定义的分类规则已经在前面的“step3:知识建模”章节里通过命令`knext schema reg_concept_rule`提交。 + +以下规则的详细内容也可以在:[黑产分类概念规则](https://github.com/OpenSPG/openspg/blob/master/python/knext/knext/examples/riskmining/schema/concept.rule) +中查看。 + +**赌博App的分类** + +``` +Define (s:App)-[p:belongTo]->(o:`TaxOfRiskApp`/`赌博应用`) { + Structure { + (s) + } + Constraint { + R1("风险标记为赌博") s.riskMark like "%赌博%" + } +} +``` + +王武为赌博应用开发者,李四为赌博应用老板,两个用户实体对应了不同的概念类型。 + +赌博开发者认定规则: + +rule: 用户存在大于5台设备,且这些设备中安装了相同的APP,则存在开发关系。 + +``` +Define (s:Person)-[p:developed]->(o:App) { + Structure { + (s)-[:hasDevice]->(d:Device)-[:install]->(o) + } + Constraint { + deviceNum = group(s,o).count(d) + R1("设备超过5"): deviceNum > 5 + } +} +``` + +``` +Define (s:Person)-[p:belongTo]->(o:`TaxOfRiskUser`/`赌博App开发者`) { + Structure { + (s)-[:developed]->(app:`TaxOfRiskApp`/`赌博应用`) + } + Constraint { + } +} +``` + +**认定赌博APP老板** + +规则1:人和APP存在发布关系。 + +``` +Define (s:Person)-[p:release]->(o:App) { + Structure { + (s)-[:holdShare]->(c:Company), + (c)-[:hasCert]->(cert:Cert)<-[useCert]-(o) + } + Constraint { + } +} +``` + +规则2:用户给该赌博App开发者转账,并且存在发布赌博应用行为。 + +``` +Define (s:Person)-[p:belongTo]->(o:`TaxOfRiskApp`/`赌博App老板`) { + Structure { + (s)-[:release]->(a:`TaxOfRiskApp`/`赌博应用`), + (u:Person)-[:developed]->(a), + (s)-[:fundTrans]->(u) + } + Constraint { + } +} +``` + +#### 场景3:面向业务知识和事实数据分层下的知识应用 + +基于GQL获取黑产应用对应的团伙信息。 + +**获取所有的赌博应用** + +编辑dsl_task1.txt,输入如下内容: + +``` +MATCH (s:`RiskMining.TaxOfRiskApp`/`赌博应用`) RETURN s.id +``` + +执行脚本: + +``` +knext reasoner execute --file ./reasoner/dsl_task1.txt +``` + +**获取赌博APP背后的开发者和老板** + +编辑dsl_task2.txt,输入如下内容: + +``` +MATCH + (u:`RiskMining.TaxOfRiskUser`/`赌博App开发者`)-[:developed]->(app:`RiskMining.TaxOfRiskApp`/`赌博应用`), + (b:`RiskMining.TaxOfRiskUser`/`赌博App老板`)-[:release]->(app) +RETURN + u.id, b.id ,app.id +``` + +执行脚本: + +``` +knext reasoner execute --file ./reasoner/dsl_task2.txt +``` diff --git a/docs/examples/supplychain/builder.md b/docs/examples/supplychain/builder.md new file mode 100644 index 0000000..a09d7e9 --- /dev/null +++ b/docs/examples/supplychain/builder.md @@ -0,0 +1,137 @@ +--- +title: 知识构建 +order: 3 +--- + +本例中数据均为结构化数据,本例中主要需要两个部分能力: + +- 结构化Mapping:原始数据和schema定义表字段并不完全一致,需要定义数据字段映射过程 +- 实体链指:在关系构建中,实体链指是非常重要的建设手段,本例演示一个简单case,实现公司的链指能力 + +## 1 源数据到SPG数据的Mapping能力 + +以导入company数据为例: + +```yaml +id,name,products +CSF0000000254,北大*药*份限公司,"医疗器械批发,医药批发,制药,其他化学药品" +``` + +导入company的代码如下,详细内容如注释: + +````python +class Company(BuilderJob): + ``` + 返回构建任务pipeline,每个导入任务都需要有一个source节点,一个sink节点 + 这里mapping节点为一个结构化数据映射节点 + ``` + def build(self): + source = CSVReader( + # 指定数据源地址 + local_path="./builder/task/data/Company.csv", + columns=["id", "name", "products"], + start_row=2 + ) + + # spg_type_name代表是向哪个数据类型导入 + # add_field表示从数据源的哪个字段导入到schema中定义的哪个字段 + mapping = SPGTypeMapping( + spg_type_name="SupplyChain.Company" + ).add_property_mapping("id", "id") \ + .add_property_mapping("name", "name") \ + .add_property_mapping("products", "product") + + # sink节点,使用系统的图谱节点 + sink = KGWriter() + + return source >> mapping >> sink + + +# 导入公司间的资金关系 +class CompanyFundTrans(BuilderJob): + + ``` + 和基本信息导入类似,此处RelationMappingComponent代表关系隐射节点 + ``` + def build(self): + source = CSVReader( + local_path="./builder/task/data/Company_fundTrans_Company.csv", + columns=["src", "dst", 'transDate', 'transAmt'], + start_row=2 + ) + + # 关系映射节点需要指定是哪条具体关系 + # add_field表示从数据源的哪个字段导入到schema中定义的哪个字段 + mapping = RelationMapping( + subject_name='DEFAULT.Company', + predicate_name='fundTrans', + object_name='DEFAULT.Company' + ).add_sub_property_mapping("src", "srcId") \ + .add_sub_property_mapping("dst", "dstId") \ + .add_sub_property_mapping("transDate", 'transDate') \ + .add_sub_property_mapping('transAmt', 'transAmt') + + sink = KGWriter() + + return source >> mapping >> sink + +```` + +在knext中执行如下命令提交任务: + +```shell +knext builder execute Company,CompanyFundTrans +``` + +一般情况下这种映射关系基本能够满足结构化数据导入,但在一些场景下可能需要对数据进行部分数据才能满足要求,此时就需要实现自定义算子来处理问题。 + +## 2 自定义算子实现链指能力 + +例如如下数据: + +```python +id,name,age,legalRep +0,路**,63,"新疆*花*股*限公司,三角*胎股*限公司,传化*联*份限公司" +``` + +legalRep字段为公司名字,但在系统中已经将公司id设置成为主键,直接通过公司名是无法关联到具体公司,假定存在一个搜索服务,可将公司名转换为id,此时需要自定开发一个链指算子,实现该过程的转换。 + +算子放在如下目录: + +```python +|_event + |_builder + |_operator + |_company_operator.py +``` + +person构建只需按照正常mapping流程即可,如下person代码: + +```python +# -*- coding: utf-8 -*- +class Person(BuilderJob): + + def build(self): + source = CSVReader( + local_path="./builder/job/data/Person.csv", + columns=["id", 'name', 'age', 'legalRep'], + start_row=2 + ) + + mapping = SPGTypeMapping( + spg_type_name=SupplyChain.Person + ).add_property_mapping("id", SupplyChain.Person.id) + .add_property_mapping("name", SupplyChain.Person.name) + .add_property_mapping("age", SupplyChain.Person.age) + .add_property_mapping("legalRep", SupplyChain.Person.legalRepresentative) + + sink = KGWriter() + + return source >> mapping >> sink +``` + +最后提交person任务即可: + +```shell +knext builder execute Person +``` diff --git a/docs/examples/supplychain/data.md b/docs/examples/supplychain/data.md new file mode 100644 index 0000000..d1b3f2d --- /dev/null +++ b/docs/examples/supplychain/data.md @@ -0,0 +1,111 @@ +--- +title: 数据介绍 +order: 2 +--- + +## 1 数据目录 + +``` +|_supplychain + |_builder + |_job + |__data + |_Company.csv // 存放公司数据 + |_Company_fundTrans_Company.csv //公司转账关系 + |_Person.csv //人相关数据 + |_Industry.csv //行业类目概念数据 + |_Product.csv //产品类目概念数据 + |_Index.csv //指标类目概念数据 + |_Trend.csv //趋势类目概念数据 + |_ProductChainEvent.csv //产业链事件 + |_TaxOfCompanyEvent.csv //公司事件分类数据 + |_TaxOfProdEvent.csv //产业链产品事件分类数据 +``` + +分别抽样部分数据进行介绍。 + +## 2 公司数据(Company.csv) + +``` +id,name,products +CSF0000002238,三角*胎股*限公司,"轮胎,全钢子午线轮胎" +``` + +- id:公司在系统中的唯一id +- name:公司名 +- products:公司生产的产品,使用逗号分隔 + +## 3 公司资金转账(Company_fundTrans_Company.csv) + +``` +src,dst,transDate,transAmt +CSF0000002227,CSF0000001579,20230506,73 +``` + +- src:转出方 +- dst:转入方 +- transDate:转账日期 +- transAmt:转账总金额 + +## 4 法人代表(Person.csv) + +``` +id,name,age,legalRep +0,路**,63,"新疆*花*股*限公司,三角*胎股*限公司,传化*联*份限公司" +``` + +- id:自然人在系统中唯一标识 +- name:姓名 +- age:年龄 +- legalRep:法人代表公司名字 + +## 5 产业类目概念(Industry.csv) + +``` +fullname +能源 +能源-能源 +能源-能源-能源设备与服务 +能源-能源-能源设备与服务-能源设备与服务 +能源-能源-石油、天然气与消费用燃料 +``` + +产业只有名字,其中段横线代表其上位概念,例如“能源-能源-能源设备与服务”的上位概念是“能源-能源”;“能源-能源-能源设备与服务-能源设备与服务”的上位概念为“能源-能源-能源设备与服务”。 + +## 6 产品类目概念(Product.csv) + +``` +fullname,belongToIndustry,hasSupplyChain +商品化工-橡胶-合成橡胶-顺丁橡胶,原材料-原材料-化学制品-商品化工,"化工商品贸易-化工产品贸易-橡塑制品贸易,轮胎与橡胶-轮胎,轮胎与橡胶-轮胎-特种轮胎,轮胎与橡胶-轮胎-工程轮胎,轮胎与橡胶-轮胎-斜交轮胎,轮胎与橡胶-轮胎-全钢子午线轮胎,轮胎与橡胶-轮胎-半钢子午线轮胎" +``` + +- fullname:产品名,同样通过段横线分隔上下位 +- belongToIndustry:所归属的行业,例如本例中,顺丁橡胶属于商品化工 +- hasSupplyChain:是其下游产业,例如顺丁橡胶下游产业有轮胎、橡塑制品贸易等 + +## 7 产业链事件(ProductChainEvent.csv) + +``` +id,name,subject,index,trend +1,顺丁橡胶成本上涨,商品化工-橡胶-合成橡胶-顺丁橡胶,价格,上升 +``` + +- id:事件的id +- name:事件的名字 +- subject:事件的主体,本例为顺丁橡胶 +- index:指标,本例为价格 +- trend:趋势,本例为上升 + +## 8 指标(Index.csv)和趋势(Trend.csv) + +趋势、指标作为原子概念类目,可组合成产业链事件和公司事件。 + +- 趋势,值域为:上涨、下跌 +- 指标,值域为:价格、成本、利润 + +## 9 事件分类(TaxOfCompanyEvent.csv、TaxOfProdEvent.csv) + +事件分类包括产业链事件分类和公司事件分类,数据为: + +- 产业链事件分类,值域:价格上涨 +- 公司事件分类,值域:成本上升 diff --git a/docs/examples/supplychain/model.md b/docs/examples/supplychain/model.md new file mode 100644 index 0000000..734c2c9 --- /dev/null +++ b/docs/examples/supplychain/model.md @@ -0,0 +1,189 @@ +--- +title: 知识建模 +order: 1 +--- + +## 1 建模文件 + +建模参考文件[企业供应链图谱schema](https://github.com/OpenSPG/openspg/blob/master/python/knext/knext/examples/supplychain/schema/supplychain.schema)。 + +执行以下脚本,完成schema创建: + +``` +knext schema commit +``` + +## 2 SPG建模方法 vs 属性图建模方法 + +本节对比 SPG语义建模 和 普通建模的差异。 + +### 2.1 语义属性 vs 文本属性 + +假定存在如下公司信息:"北大*药*份限公司"生产的产品有四个"医疗器械批发,医药批发,制药,其他化学药品"。 + +``` +id,name,products +CSF0000000254,北大*药*份限公司,"医疗器械批发,医药批发,制药,其他化学药品" +``` + +#### 2.1.1 基于文本属性建模 + +```yaml +//文本属性建模 +Company(企业): EntityType + properties: + product(经营产品): Text +``` + +此时经营产品只为文本,不包含语义信息,是无法得到“北大*药*份限公司”的上下游产业链相关信息,极不方便维护也不方便使用。 + +#### 2.1.2 基于关系建模 + +若期望能对经营产品进行较好的维护管理,一般是将产品作为实体存在,将公司和产品通过关系关联起来。 + +```yaml +Product(产品): EntityType + properties: + name(产品名): Text + relations: + isA(上位产品): Product + +Company(企业): EntityType + relations: + product(经营产品): Product +``` + +但如此建模,则需要将数据分为3列: + +``` +id,name,product +CSF0000000254,北大*药*份限公司,医疗器械批发 +CSF0000000254,北大*药*份限公司,医药批发 +CSF0000000254,北大*药*份限公司,制药 +CSF0000000254,北大*药*份限公司,其他化学药品 +``` + +这种方式也存在两个缺点: + +1)原始数据需要做一次清洗,转换成多行。 + +2)需要新增维护关系数据,当原始数据发生变更时,需要删除原有关系,再新增数据,容易导致数据错误。 + +#### 2.1.3 基于SPG语义属性建模 + +SPG支持语义属性,可简化知识构建,如下: + +```yaml +Product(产品): ConceptType + hypernymPredicate: isA + +Company(企业): EntityType + properties: + product(经营产品): Product + constraint: MultiValue +``` + +企业中具有一个经营产品属性,且该属性的类型为Product类型,只需将如下数据导入,可自动实现属性到关系的转换。 + +``` +id,name,products +CSF0000000254,北大*药*份限公司,"医疗器械批发,医药批发,制药,其他化学药品" +``` + +### 2.2 逻辑表达的属性、关系 vs 数据表达的属性、关系 + +假定需要得到企业所在行业,根据当前已有数据,可执行如下查询语句: + +``` +MATCH + (s:Company)-[:product]->(o:Product)-[:belongToIndustry]->(i:Industry) +RETURN + s.id, i.id +``` + +该方式需要熟悉图谱schema,对人员上手要求比较高,故也有一种实践是将这类属性重新导入图谱,如下: + +```yaml +Company(企业): EntityType + properties: + product(经营产品): Product + constraint: MultiValue + relations: + belongToIndustry(所在行业): Industry +``` + +新增一个关系类型,来直接获取公司所属行业信息。 + +这种方式缺点主要有两个: + +- 需要用户手动维护新增关系数据,增加使用维护成本 +- 由于新关系和图谱数据存在来源依赖,非常容易导致图谱数据出现不一致问题 + +针对上述缺点,SPG支持逻辑表达属性和关系,如下建模方式: + +```yaml +Company(企业): EntityType + properties: + product(经营产品): Product + constraint: MultiValue + relations: + belongToIndustry(所在行业): Industry + rule: [[ + Define (s:Company)-[p:belongToIndustry]->(o:Industry) { + Structure { + (s)-[:product]->(c:Product)-[:belongToIndustry]->(o) + } + Constraint { + } + } + ]] +``` + +具体内容可参见[查询任务](./query.md)场景一、场景二的示例。 + +### 2.3 概念体系 vs 实体体系 + +现有图谱方案也有常识图谱,例如ConceptNet等,但在业务落地中,不同业务有各自体现业务语义的类目体系,基本上不存在一个常识图谱可应用到所有业务场景,故常见的实践为将业务领域体系创建为实体,和其他实体数据混用,这就导致在同一个分类体系上,既要对schema的扩展建模,又要对语义上的细分类建模,数据结构定义和语义建模的耦合,导致工程实现及维护管理的复杂性,也增加了业务梳理和表示(认知)领域知识的困难。 + +SPG区分了概念和实体,用于解耦语义和数据,如下: + +```yaml +Product(产品): ConceptType + hypernymPredicate: isA + +Company(企业): EntityType + properties: + product(经营产品): Product + constraint: MultiValue +``` + +产品被定义为概念,公司被定义为实体,相互独立演进,两者通过SPG提供的语义属性进行挂载关联,用户无需手动维护企业和产品之间关联。 + +### 2.4 事件时空多元表达 + +事件多要素结构表示也是一类超图(HyperGrpah)无损表示的问题,它表达的是时空多元要素的时空关联性,事件是各要素因某种行为而产生的临时关联,一旦行为结束,这种关联也随即消失。在以往的属性图中,事件只能使用实体进行替代,由文本属性表达事件内容,如下类似事件: + +![事件表达](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*pUlGS6-E3lEAAAAAAAAAAAAADtmcAQ/original) + +```yaml +Event(事件): + properties: + eventTime(发生时间): Long + subject(涉事主体): Text + object(客体): Text + place(地点): Text + industry(涉事行业): Text +``` + +这种表达方式,是无法体现真实事件的多元关联性,SPG提供了事件建模,可实现事件多元要素的关联,如下: + +```yaml +CompanyEvent(公司事件): EventType + properties: + subject(主体): Company + index(指标): Index + trend(趋势): Trend + belongTo(属于): TaxOfCompanyEvent +``` + +上述的事件中,属性类型均为已被定义类型,没有基本类型表达,SPG基于此申明实现事件多元要素表达,具体应用示例可见[查询任务](./query.md)中场景3的具体描述。 diff --git a/docs/examples/supplychain/query.md b/docs/examples/supplychain/query.md new file mode 100644 index 0000000..c37de18 --- /dev/null +++ b/docs/examples/supplychain/query.md @@ -0,0 +1,317 @@ +--- +title: 查询任务 +order: 4 +--- + +## 场景1:企业信用评级特征生成 + +需求:在企业信用评级中,假定需要得到如下决策因子
+1)主供应商关系
+2)企业生产产品所在行业
+3)企业资金近1月、3月、6月转账流水
+4)企业资金近1月、3月、6月流水差
+5)实控人相关公司信息
+但在原有图谱中,只有资金转账、法人代表信息,无法直接获取上述特征,本例演示如果通过SPG完成如上5个特征获取。
+特征定义在schema文件中,可点击查看[企业供应链图谱schema](https://github.com/OpenSPG/openspg/blob/master/python/knext/knext/examples/supplychain/schema/supplychain.schema)。
+ +**特征1:先定义企业和企业间的主供应链关系,规则定义如下** + +``` +Define (s:Compnay)-[p:mainSupply]->(o:Company) { + Structure { + (s)-[:product]->(upProd:Product)-[:hasSupplyChain]->(downProd:Product)<-[:product]-(o), + (o)-[f:fundTrans]->(s) + (otherCompany:Company)-[otherf:fundTrans]->(s) + } + Constraint { + // 计算公司o的转入占比 + otherTransSum("总共转入金额") = group(s).sum(otherf.transAmt) + targetTransSum("o转入的金额总数") = group(s,o).sum(f.transAmt) + transRate = targetTransSum*1.0/(otherTransSum + targetTransSum) + R1("占比必须超过50%"): transRate > 0.5 + } +} +``` + +**特征2:企业生成产品所在行业** + +``` +Define (s:Compnay)-[p:belongToIndustry]->(o:Industry) { + Structure { + (s)-[:product]->(c:Product)-[:belongToIndustry]->(o) + } + Constraint { + } +} +``` + +**特征3:企业资金近1月、3月、6月转账流水** + +``` +// 近1个月流出金额 +Define (s:Compnay)-[p:fundTrans1Month]->(o:Int) { + Structure { + (s)-[f:fundTrans]->(c:Company) + } + Constraint { + R1("近1个月的流出资金"): date_diff(from_unix_time(now(), 'yyyyMMdd'),f.transDate) < 30 + totalOut = group(s).sum(transAmt) + o = totalOut + } +} + +// 近3个月流出金额 +Define (s:Compnay)-[p:fundTrans3Month]->(o:Int) { + Structure { + (s)-[f:fundTrans]->(c:Company) + } + Constraint { + R1("近4个月的流出资金"): date_diff(from_unix_time(now(), 'yyyyMMdd'),f.transDate) < 90 + totalOut = group(s).sum(transAmt) + o = totalOut + } +} + +// 近6个月流出金额 +Define (s:Compnay)-[p:fundTrans6Month]->(o:Int) { + Structure { + (s)-[f:fundTrans]->(c:Company) + } + Constraint { + R1("近5个月的流出资金"): date_diff(from_unix_time(now(), 'yyyyMMdd'),f.transDate) < 180 + totalOut = group(s).sum(transAmt) + o = totalOut + } +} + +// 近1个月流入金额 +Define (s:Compnay)-[p:fundTrans1MonthIn]->(o:Int) { + Structure { + (s)<-[f:fundTrans]-(c:Company) + } + Constraint { + R1("近1个月的流入资金"): date_diff(from_unix_time(now(), 'yyyyMMdd'),f.transDate) < 30 + totalOut = group(s).sum(transAmt) + o = totalOut + } +} + +// 近3个月流入金额 +Define (s:Compnay)-[p:fundTrans3MonthIn]->(o:Int) { + Structure { + (s)<-[f:fundTrans]-(c:Company) + } + Constraint { + R1("近3个月的流入资金"): date_diff(from_unix_time(now(), 'yyyyMMdd'),f.transDate) < 90 + totalOut = group(s).sum(transAmt) + o = totalOut + } +} + + +// 近6个月流入金额 +Define (s:Compnay)-[p:fundTrans6MonthIn]->(o:Int) { + Structure { + (s)<-[f:fundTrans]-(c:Company) + } + Constraint { + R1("近6个月的流入资金"): date_diff(from_unix_time(now(), 'yyyyMMdd'),f.transDate) < 180 + totalOut = group(s).sum(transAmt) + o = totalOut + } +} +``` + +**特征4:企业资金近1月、3月、6月流水差** + +``` +// 近1个月资金流水差 +Define (s:Company)-[p:cashflowDiff1Month]->(o:Integer) { + Structure { + (s) + } + Constraint { + // 此处引用特征3中的规则 + fundTrans1Month = rule_value(s.fundTrans1Month == null, 0, s.fundTrans1Month) + fundTrans1MonthIn = rule_value(s.fundTrans1MonthIn == null, 0, s.fundTrans1MonthIn) + o = fundTrans1Month - fundTrans1MonthIn + } +} + +// 近3个月资金流水差 +Define (s:Company)-[p:cashflowDiff3Month]->(o:Integer) { + Structure { + (s) + } + Constraint { + // 此处引用特征3中的规则 + fundTrans3Month = rule_value(s.fundTrans3Month == null, 0, s.fundTrans3Month) + fundTrans3MonthIn = rule_value(s.fundTrans3MonthIn == null, 0, s.fundTrans3MonthIn) + o = fundTrans3Month - fundTrans3MonthIn + } +} + +// 近6个月资金流水差 +Define (s:Company)-[p:cashflowDiff6Month]->(o:Integer) { + Structure { + (s) + } + Constraint { + fundTrans6Month = rule_value(s.fundTrans6Month == null, 0, s.fundTrans6Month) + fundTrans6MonthIn = rule_value(s.fundTrans6MonthIn == null, 0, s.fundTrans6MonthIn) + o = fundTrans6Month - fundTrans6MonthIn + } +} +``` + +**特征5:同实控人公司** + +``` +// 定义同法人关系 +Define (s:Compnay)-[p:sameLegalReprensentative]->(o:Company) { + Structure { + (s)<-[:legalReprensentative]-(u:Person)-[:legalReprensentative]->(o) + } + Constraint { + } +} +``` + +通过如下GQL执行得到某个公司的具体特征: + +``` +MATCH + (s:SupplyChain.Company) +RETURN + s.id, s.fundTrans1Month, s.fundTrans3Month, + s.fundTrans6Month, s.fundTrans1MonthIn, s.fundTrans3MonthIn, + s.fundTrans6MonthIn, s.cashflowDiff1Month, s.cashflowDiff3Month, s.cashflowDiff6Month +``` + +``` +MATCH + (s:SupplyChain.Company)-[:mainSupply]->(o:SupplyChain.Company) +RETURN + s.id, o.id +``` + +``` +MATCH + (s:SupplyChain.Company)-[:belongToIndustry]->(o:SupplyChain.Industry) +RETURN + s.id, o.id +``` + +``` +MATCH + (s:SupplyChain.Company)-[:sameLegalRepresentative]->(o:SupplyChain.Company) +RETURN + s.id, o.id +``` + +## 场景2:企业供应链发生变化 + +假设供应链发生如下变化:
+ +``` +"钱****份限公司"发布公告,生产产品“三轮摩托车,二轮摩托车”变更为“两轮摩托车”,则"三角**轮胎股份"和钱"****份限公司"的主供应链关系自动断裂,"三角**轮胎股份"和"钱****份限公司"不再具有主供应链关系 +``` + +变更后的数据存在CompanyUpdate.csv: + +```cypher +id,name,products +CSF0000001662,浙江**摩托**限公司,"汽车-摩托车制造-二轮摩托车" +``` + +重新提交任务: + +```cypher +knext builder execute CompanyUpdate +``` + +执行完成后再次查询,只会返回二轮摩托车,而三轮摩托车不再被关联: + +``` +MATCH + (s:SupplyChain.Company)-[:product]->(o:SupplyChain.Product) +WHERE + s.id = "CSF0000001662" +RETURN + s.id, o.id +``` + +## 场景3:产业链影响 + +事件内容如下: + +``` +id,name,subject,index,trend +1,顺丁橡胶成本上涨,商品化工-橡胶-合成橡胶-顺丁橡胶,价格,上涨 +``` + +提交事件数据: + +``` +knext builder execute ProductChainEvent +``` + +传导链路如下:
+![image.png](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*mJWRQJek1BsAAAAAAAAAAAAADtmcAQ/original)
+顺丁橡胶成本上升,被分类为产业链价格上涨事件,如下DSL: + +``` +// ProductChainEvent为一个具体的事件实例,当其属性满足价格上涨条件时,该事件分类为价格上涨事件 +Define (e:ProductChainEvent)-[p:belongTo]->(o:`TaxonofProductChainEvent`/`价格上涨`) { + Structure { + } + Constraint { + R1: e.index == '价格' + R2: e.trend == '上涨' + } +} +``` + +产业链价格上涨,在如下条件下,会导致特定公司的成本上升 + +``` +// 定义了价格上涨和企业成本上升的规则 +Define (s:`TaxonofProductChainEvent`/`价格上涨`)-[p:leadTo]->(o:`TaxonofCompanyEvent`/`成本上涨`) { + Structure { + //1、找到产业链事件的主体,本例中为顺丁橡胶 + //2、找到顺丁橡胶的下游产品,本例中为斜交轮胎 + //3、找到生成斜交轮胎的所有企业,本例中为三角**轮胎股份 + (s)-[:subject]->[prod:Product]-[:hasSupplyChain]->(down:Product)<-[:product]-(c:Company) + } + Constraint { + } + Action { + // 创建一个公司成本上升事件,主体为查询得到的三角**轮胎股份 + downEvent = createNodeInstance( + type=CompanyEvent, + value={ + subject=c.id + trend="上涨" + index="成本" + } + ) + // 由于这个事件是通过产业链价格上涨引起,故在两者之间增加一条边 + createEdgeInstance( + src=s, + dst=downEvent, + type=leadTo, + value={ + } + ) + } +} +``` + +可通过如下查询语句查出某个事件产生的影响 + +```cypher +MATCH + (s:SupplyChain.ProductChainEvent)-[:leadTo]->(o:SupplyChain.CompanyEvent) +RETURN + s.id,s.subject,o.subject,o.name +``` diff --git a/docs/examples/supplychain/supplychain.md b/docs/examples/supplychain/supplychain.md new file mode 100644 index 0000000..402e303 --- /dev/null +++ b/docs/examples/supplychain/supplychain.md @@ -0,0 +1,151 @@ +--- +title: 企业供应链 +nav: + order: 3 + title: 案例 +--- + +## 1 背景 + +信贷机构对企业的财务状况、经营状况、市场地位、管理能力等进行综合分析,给予企业一个评级等级,反映其信用状况的好坏,以便支撑信贷业务。在实践中基本依赖被评估企业自身提供的信息,例如企业年报、各类资质文件、资产证明等,这一类信息只能围绕企业自身提供微观层面的信息,不能体现企业在整个产业链上下游市场情况,也无法得到证明之外的信息。 + +本例基于SPG构建产业链企业图谱,挖掘出企业之间基于产业链的深度信息,支持企业信用评级。 + +## 2 总览 + +建模参考文件[企业供应链图谱schema](https://github.com/OpenSPG/openspg/blob/master/python/knext/knext/examples/supplychain/schema/supplychain.schema),如下图实例 + +![产业链企业图谱深度语义关联](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*J_NpRoNbO-YAAAAAAAAAAAAADtmcAQ/original) + +
图1:产业链企业图谱深度语义关联
+ +概念知识维护着产业链相关数据,包括上下位层级、供应关系;实体实例仅有法人代表、转账信息,公司实例通过生产的产品属性和概念中的产品节点挂载,实现了公司实例之间的深度信息挖掘,例如供应商、同行业、同法人代表等关系。基于深度上下文信息,可提供更多的信用评估因子。 + +![产业链事件归纳演绎 ](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*X2TES7hf9ycAAAAAAAAAAAAADtmcAQ/original) + +
图2:产业链事件归纳演绎
+ +产业链中建立了产品和公司事件类别,该类别属于指标和趋势的一种组合,例如价格上涨,是由指标:价格,趋势:上涨两部分构成。 +事理知识设定了产品价格上涨引起公司利润下降及公司成本上涨事件,当发生某个具体事件时,例如“橡胶价格大涨事件”,被归类在产品价格上涨,由于事理知识中定义产品价格上涨会引起公司利润下降/公司成本上涨两个事件类型,会产出新事件:“三角\*\*轮胎公司成本上涨事件”、“三角\*\*轮胎公司利润下跌”。 + +## 3 Quick Start + +### Step1:进入案例目录 + +```shell + cd /openspg/python/knext/examples/supplychain/ +``` + +### Step2:项目初始化 + +先对项目进行初始化动作 + +```cypher +knext project create --prj_path . +``` + +### Step3:知识建模 + +schema文件已创建好,可执行如下命令提交 + +```shell +# 提交schema +knext schema commit +``` + +```shell +# 提交导致关系逻辑规则 +knext schema reg_concept_rule --file ./schema/concept.rule +``` + +schema建模详细内容可参见 [基于SPG建模的产业链企业图谱](./model.md) + +### Step4:知识构建 + +知识构建将数据导入到系统中,数据介绍参见文档[产业链案例数据介绍](./data.md)。 + +本例主要为结构化数据,故演示结构化数据转换和实体链指,具体细节可参见文档[产业链案例知识构建](./builder.md)。 + +**提交知识导入任务** + +```shell +# 提交公司事件分类数据 +knext builder execute TaxOfCompanyEvent +# 提交产品事件分类数据 +knext builder execute TaxOfProdEvent +``` + +```shell +# 提交剩余的数据 +knext builder execute Index,Trend +knext builder execute Industry,Product,ProductHasSupplyChain +knext builder execute Company,CompanyFundTrans,Person +``` + +最后提交事件 + +```shell +knext builder submit ProductChainEvent +``` + +### Step5:执行图谱任务 + +SPG支持ISO GQL写法,可用如下命令行执行查询任务 + +```cypher +knext reasoner execute --dsl "${ql}" +``` + +具体任务详情可参见文档[产业链企业信用图谱查询任务](./query.md)。 + +信用评级因子获取: + +```cypher +knext reasoner execute --dsl " +MATCH + (s:SupplyChain.Company) +RETURN + s.id, s.name, s.fundTrans1Month, s.fundTrans3Month, + s.fundTrans6Month, s.fundTrans1MonthIn, s.fundTrans3MonthIn, + s.fundTrans6MonthIn, s.cashflowDiff1Month, s.cashflowDiff3Month, + s.cashflowDiff6Month +" +``` + +```cypher +knext reasoner execute --dsl " +MATCH + (s:SupplyChain.Company)-[:mainSupply]->(o:SupplyChain.Company) +RETURN + s.name, o.name +" +``` + +```cypher +knext reasoner execute --dsl " +MATCH + (s:SupplyChain.Company)-[:belongToIndustry]->(o:SupplyChain.Industry) +RETURN + s.name, o.name +" +``` + +```cypher +knext reasoner execute --dsl " +MATCH + (s:SupplyChain.Company)-[:sameLegalRepresentative]->(o:SupplyChain.Company) +RETURN + s.name, o.name +" +``` + +事件影响分析: + +```cypher +knext reasoner execute --dsl " +MATCH + (s:SupplyChain.ProductChainEvent)-[:leadTo]->(o:SupplyChain.CompanyEvent) +RETURN + s.id,s.subject,o.subject,o.name +" +``` diff --git a/docs/introduction/builder.md b/docs/introduction/builder.md new file mode 100644 index 0000000..7c8b95f --- /dev/null +++ b/docs/introduction/builder.md @@ -0,0 +1,67 @@ +--- +title: 知识构建 +order: 3 +--- + +知识构建是一个从数据到知识的生产过程。 + +知识生产由一系列的组件组成,一般而言从一个数据源到知识图谱需要经过以下流程: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*32X4To9D_qwAAAAAAAAAAAAADtmcAQ/original) + +常见的知识生产组件有,用户可以根据自己的需要扩展组件以及组件能力。 + +| 组件 | 作用 | +| ---------------- | -------------------------------------- | +| SourceReader | 定义源数据类型以及如何读取源数据 | +| ExtractProcessor | 抽取组件,从非结构数据中抽取结构化数据 | +| MappingProcessor | 映射组件,将结构化数据映射图谱schema | +| SinkWriter | 写入图谱数据 | + +## 知识抽取(Extract) + +知识抽取作用是从非结构化的数据中抽取出结构化数据。当数据源数据已经是结构化数据时,可以不使用该组件。 + +以大模型抽取实体子图为例,定义如下抽取流程: + +```python +# 构造以Company为中心的关系抽取prompt,抽取该公司的企业证件号码、经营范围、注册区域等 +# 其中Company是知识图谱schema的一个实体类型,企业证件号码、经营范围等是它的属性 +prompt = REPrompt( + spg_type_name=Company, + property_names=[ + Company.orgCertNo, + Company.regArea, + Company.businessScope, + Company.establishDate, + Company.legalPerson, + Company.regCapital, + ], + relation_names=[], +) + +# 定义大模型抽取组件,传入大模型配置以及prompt +extract = LLMBasedExtractor( + llm=NNInvoker.from_config("builder/model/openai_infer.json"), + prompt_ops=[prompt], +) +``` + +输入一段文本:阿里巴巴(中国)有限公司是一家从事企业管理,计算机系统服务,电脑动画设计等业务的公司,成立于2007年03月26日,公司坐落在浙江省......。该组件会从该非结构化文本中抽取出Company的结构化知识: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*v_3_RZ2Eb2MAAAAAAAAAAAAADtmcAQ/original) + +## 知识映射(Mapping) + +知识映射作用是将结构化数据映射到图谱schema,执行图谱schema上绑定的各类算子。知识映射组件需要经过: + +| 步骤 | 说明 | +| ---------------- | ------------------------------------------------------------ | +| 映射(Mapping) | 将结构化数据映射到图谱schema | +| 链指(Linking) | 执行属性或关系上的链指算子,将属性或关系链指到对应实例 | +| 预测(Predicting) | 执行属性或关系上的预测算子,基于已知的知识预测新的属性或关系 | +| 融合(Fusing) | 执行类型上绑定的融合算子,将当前的实体与已存在的实体进行融合 | + +继续以Company为例,下图展示了这些流程: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*Kdd9TZisU74AAAAAAAAAAAAADtmcAQ/original) diff --git a/docs/introduction/general.md b/docs/introduction/general.md new file mode 100644 index 0000000..387c303 --- /dev/null +++ b/docs/introduction/general.md @@ -0,0 +1,49 @@ +--- +title: 总体介绍 +order: 1 +nav: + title: 介绍 + second: + order: 1 +--- + +[OpenSPG](https://spg.openkg.cn)是蚂蚁集团结合多年金融领域多元场景知识图谱构建与应用业务经验的总结,并**与OpenKG联合推出**的基于SPG(Semantic-enhanced Programmable Graph)框架研发的知识图谱引擎。 + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*x89HQrQy5AMAAAAAAAAAAAAADtmcAQ/original#id=OiWGg&originHeight=932&originWidth=1500&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +## SPG背景 + +SPG(Semantic-enhanced Programmable Graph) +:语义增强可编程框架,是蚂蚁知识图谱平台经过多年金融领域业务的支撑,沉淀的一套基于属性图的语义表示框架。它创造性地融合了LPG结构性与RDF语义性,既克服了RDF/OWL语义复杂无法工业落地的问题,又充分继承了LPG结构简单与大数据体系兼容的优势。该框架从三个方面来定义和表示知识语义。首先,SPG明确定义了" +知识" +的形式化表示和可编程框架,使其可定义、可编程,机器可理解和处理。其次,SPG实现了知识层级间的兼容递进,支持工业级场景下非完备数据状态的图谱构建和持续迭代演化。最后,SPG有效衔接大数据与AI技术体系,支持对海量数据进行高效的知识化转换,帮助提高数据价值和应用价值。通过SPG框架,我们可以更加高效地构建和管理图谱数据,同时可以更好地支持业务需求和应用场景。由于SPG框架具有良好的可扩展性和灵活性,新的业务场景可以通过扩展领域知识模型及开发新算子,快速构建其领域模型和解决方案。 + +SPG的详细介绍请参考**蚂蚁集团和OpenKG联合发布**的[《SPG白皮书》](https://spg.openkg.cn/)。 + +## 能力模型 + +OpenSPG是以SPG框架为基础设计和实现的知识图谱开放引擎,它为领域图谱构建提供了明确的语义表示、逻辑规则定义、算子框架( +构建、推理)等能力,支持各厂商可插拔的适配基础引擎、算法服务,构建自定义的解决方案。 + +OpenSPG核心能力模型包括: + +- **SPG-Schema语义建模** + - 负责属性图语义增强的Schema框架设计,如主体模型、演化模型、谓词模型等。 +- **SPG-Builder知识构建** + - 支持结构化和非结构化知识导入。 + - 与大数据架构兼容衔接,提供了知识构建算子框架,实现从数据到知识的转换。 + - 抽象了知识加工SDK框架,提供实体链指、概念标化和实体归一等算子能力,结合自然语言处理(Natural Language Processing, NLP) + 和深度学习算法,提高单个类型(Class)中不同实例(Instance)的唯一性水平,支持领域图谱的持续迭代演化。 +- **SPG-Reasoner逻辑规则推理** + - 抽象了KGDSL(Knowledge Graph Domain Specific Language),为逻辑规则提供可编程的符号化表示。 + - 以机器可理解的符号表示支持下游规则推理、神经/符号融合学习、KG2Prompt联动LLM知识抽取/知识推理等。 + - 通过谓词语义和逻辑规则来定义知识之间的依赖和传递,并且支持对复杂的业务场景的建模和分析。 +- **可编程框架KNext** + - KNext作为图谱可编程框架,提供了一套可扩展,流程化,对用户友好的组件化能力; + - 抽象了图谱核心能力,沉淀为组件化、框架化、引擎内置的能力; + - 实现引擎与业务逻辑、领域模型的隔离,方便业务快速定义图谱解决方案; + - 构建以OpenSPG引擎为基础,知识驱动的可控AI技术栈,链接LLM、GraphLearning等深度学习能力。 +- **云适配层Cloudext** + - 业务系统通过SDK对接开放引擎,构建自身特色的业务前端 + - 可扩展/适配自定义的图存储/图计算引擎 + - 可扩展/适配适合自身业务特点的机器学习框架 diff --git a/docs/introduction/knext.md b/docs/introduction/knext.md new file mode 100644 index 0000000..b95ecd6 --- /dev/null +++ b/docs/introduction/knext.md @@ -0,0 +1,31 @@ +--- +title: KNext +order: 5 +--- + +KNext是OpenSPG可编程框架,期望提供一套可扩展,流程化,对用户友好的组件化能力。 + +KNext包含以下几个功能模块: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*W1roTaX50QMAAAAAAAAAAAAADtmcAQ/original) + +其中,各模块详细功能说明如下: + +| 功能模块 | 作用 | +| ---------------------- | ----------------------------------------- | +| Python SDK | OpenSPG的Python SDK | +| Programmable Framework | OpenSPG的可编程框架,包含各种组件能力抽象 | +| Command Line Tools | OpenSPG命令行工具,基于命令与图谱做交互 | + +更进一步,可编程框架包括以下几个抽象: + +| 可编程框架抽象 | 作用 | +| --------------- | -------------------------------------------------------- | +| Model(模型) | 抽象模型微调服务等流程,提供基于OpenSPG的模型外挂能力 | +| Operator(算子) | 提供抽取、链指、预测、融合等算子接口,用户可以自定义实现 | +| Component(组件) | 对图谱能力的抽象,比如推理、映射、抽取等 | +| Chain(组件链) | 一系列组件组成的流程 | + +基于KNext可编程框架,整体的OpenSPG架构如下: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*4JptQaPtPjEAAAAAAAAAAAAADtmcAQ/original) diff --git a/docs/introduction/reasoner.md b/docs/introduction/reasoner.md new file mode 100644 index 0000000..19efc31 --- /dev/null +++ b/docs/introduction/reasoner.md @@ -0,0 +1,67 @@ +--- +title: 知识推理 +order: 4 +--- + +知识推理是OpenSPG框架中的逻辑规则执行引擎,它将图谱的事实知识抽象并关联到具有实际的商业价值的逻辑知识。 + +如下图所示的知识分层结构中,在基础事实知识的基础上,我们可以通过Reasoner表达逻辑谓词、超图事件、时空关联,以及它们之间形成的逻辑规则链条,满足业务上动态分类,事理归因等推理需求。Reasoner也提供高性能的执行引擎,满足实时分析和批量推理计算等各种计算需求。 + +![image.png](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*I6TMTrDULT4AAAAAAAAAAAAADtmcAQ/original) + +## 规则定义 + +有业务需求:找出至少使用两部相同手机的用户对。已有Schema定义如下: + +``` +User(自然人): EntityType + properties: + age(年龄): Integer + hasPhone(电话号码): STD.ChinaMobile +``` + +那么,我们可以在Schema实体类型User上创建一条逻辑边,其中的规则定义如下: + +``` +Define (s:User)-[p:samePhone]->(o:User) { + GraphStructure { + (s)-[:useDevice]->(p:STD.ChinaMobile)<-(:useDevice)-(o) + } + Rule { + use_device_num("使用设备数目") = group(s, o).count(p.id) + R1("使用设备至少超过两个"): use_device_num >= 2 + R2("双方年龄都大于 18 岁"): s.age > 18 and o.age > 18 + p.num = use_device_num // 在边上定义属性num + } +} +``` + +通过这样的定义,我们在Schema上添加了一条逻辑边。 + +## 规则推理 + +通过上一节的规则定义,我们在Schema上创建了逻辑边,逻辑边的使用与数据边相同。 +Schema变更后,如下图所示,在图中可以看到新的逻辑边。 + +![image.png](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*aUXYS4FAAOMAAAAAAAAAAAAADtmcAQ/original) + +假设我需要查询用户张三是否与其他用户共用了手机号,可以通过如下查询语句获取。 + +``` +MATCH + (u1:User)-[p:samePhone]->(u2:User) +WHERE + u1.name == '张三' +RETURN + u1.name, u2.name, p.num +``` + +数据如下: + +![image.png](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*IX6vQLVH7V0AAAAAAAAAAAAADtmcAQ/original) + +我可以得到结果: + +| u1_name | u2_name | p_num | +| ------- | ------- | ----- | +| 张三 | 王五 | 2 | diff --git a/docs/introduction/schema.md b/docs/introduction/schema.md new file mode 100644 index 0000000..7e7f2d0 --- /dev/null +++ b/docs/introduction/schema.md @@ -0,0 +1,139 @@ +--- +title: 知识建模 +order: 2 +--- + +知识建模是开始一个图谱项目的第一步,是对该领域业务的抽象建模过程。 + +SPG在属性图(LPG)的基本节点类型和边类型之上扩展并引入更多主体分类模型对节点类型进行扩充以兼容更加多元的知识表示,扩展的主体类型分为实体类型、事件类型、概念类型和标准类型。同时SPG还支持了属性的可传播能力,即在实体类型和事件类型上,可以定义其属性的类型为实体类型、概念类型或者标准类型。按此方式定义,类型除了具备这些属性以外,还会自动产生跟属性同名的关系,这样建模的时候无须再纠结是建成属性还是关系,定义了属性自然也就定义了关系,在导入数据时只需导入实体,关系就会自动创建。这也是在SPG里推荐的建模方式,以属性的定义替代简单关系的定义。 + +SPG也可以单独定义关系。SPG中的关系需在其头节点类型下定义,不同的关系是依赖(头节点类型,关系名称,尾节点类型)这样的三元组进行区分,也就是不同的关系可以保持关系名称相同,但是头节点类型和尾节点类型不能完全相同。关系支持再定义属性,但关系属性的类型只能是文本、整数或浮点数这样的基本类型。单独定义的关系,在SPG里还支持逻辑规则的表达,即定义的关系实例可以通过规则计算得到而无须导入数据实例。定义的方式可以参见声明式Schema章节,规则的语法可以参见知识推理的KGDSL语法章节。 + +### 实体类型 (EntityType) + +实体类型,定义了具有共同数据结构(特征)的一类实例的集合,是一种多元要素的复合节点类型。通常实体类型用于描述客观世界中的一个具体事物,比如公司、学校、书籍等等。再以学校这个实体类型举例,它的实例可以有“xxx市第一小学”、“xxx大学”等。 +实体类型的定义主要由属性和关系组成,属性的值域可以是文本、整数和浮点数这样的基础类型,也可以是概念、实体等高级类型。使用了高级类型做值域的属性,会同时产生从自身指向高级类型的关系。这也是SPGSchema的一个重要特性。这里需要解释一下,在SPG里如何实现属性到关系的转换的,主要是两种策略: + +- 属性值与目标实体的主键相等。在类型上没有绑定链指算子的时候,就会按这个策略执行。SPG会自动产生如下三元组的关系实例:(实体,属性名称,属性值) + +属性值经过目标实体类型上的链指算子运算后得到目标实体。相比上一种情况,这里的属性值通常不是目标实体的主键,而可能是目标实体的某个属性值,比如名称等。关系的建立需要知道目标实体的主键,所以需要依靠在目标类型上绑定的链指算子,来对属性值进行相应策略的计算(比如通过文本搜索)得到目标实体的主键,最终生成如下三元组关系实例:(实体,属性名称,目标实体主键) + +关系的定义需要在起点实体类型上定义,方向始终为出边方向。关系上也可以再定义属性,但值域只允许是基础类型。 +实体类型会包含几个默认属性,在知识建模中无须额外定义: + +- id (主键,必填) +- name (实体名称) +- description (实体描述) + +以学校举例,实体类型的属性可以有如下: + +```yaml +enName(英文名): Text +shortName(简称): Text +founder(创办人): Person +foundDate(创立日期): STD.Date +category(类别): TaxonomySchool +address(地址): Text +``` + +那学校实体类型的实例可以是: + +```json +{ + "id": "zjdx", + "name": "浙江大学", + "enName": "Zhejiang University", + "shortName": "浙大,ZJU", + "founder": "林启", + "foundDate": "18970521", + "category": "公立大学", + "address": "西溪校区:杭州市西湖区天目山路148号" +} +``` + +以上实例导入后会形成如下子图: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*eHxoRJiGqUoAAAAAAAAAAAAADtmcAQ/original) + +### 事件类型 (EventType) + +在实体类型的基础上,加入主体、客体、时间、空间这四类要素,是对动态行为的建模,旨在反映在不同空间、时间区间上事物的状态。例如政策事件、行业事件、用户行为事件,都属于事件类型。其中主体要素是必须具备的,其余要素是可选的。 +事件类型跟实体类型都是对客观事物的抽象描述,但实体类型包含的是相对静态的客观属性和关系,缺少了随时间、空间的发展而产生的动态变化,从而存在片面性,缺乏了深层语义信息。例如表达“XX公司在10月28日在深交所挂牌上市”。 +以企业事件类型举例,其属性可以有如下: + +```yaml +subject(主体): Company +object(客体): Text +time(时间): STD.Date +location(地点): Text +behavior(行为): Behavior +``` + +那学校实体类型的实例可以是: + +```json +{ + "id": "2023100820394930", + "name": "XX公司在10月28日在深交所挂牌上市", + "subject": "XX公司", + "object": "深交所", + "time": "20231028", + "location": "深交所", + "behavior": "上市" +} +``` + +实例导入后形成如下子图: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*YxBRSoAp2vMAAAAAAAAAAAAADtmcAQ/original) + +### 概念类型 (ConceptType) + +概念是对一类具有共同特征的实体的抽象化描述,通常是用于描述实体/事件类型的分类。比如学校分类概念:小学、中学、大学。概念跟实体的区别是概念无法指代一个客观世界的具体事物,它是对一类事物的总结概括型描述。 +同时,概念和概念之间还默认具备上位关系(可以在建模时候选择上位关系谓词),通过该关系形成往上泛化、往下具化的抽象描述。比如学校分类概念的“初级中学”、“高级中学”的上位关系是“中学”。概念类型不允许创建念之间语义谓词以外的属性/关系,具体有哪些概念之间的语义见第二章。 +实体类型和概念类型的区别,实体类型往往跟业务强相关,比如电商业务的用户、商户和商品等。而概念类型则通常和行业共识、领域常识强相关,比如学生、白领、公务员这些个概念是从职业角度对用户的分类概括,又比如五星商户、高信用商户这些概念是对商户的分类概括,再比如数码产品、母婴产品是对商品的分类概括。 +概念类型在创建时,会默认包含如下属性: + +- name (概念名称,必填) +- alias (概念别名,选填) +- stdId (标准ID,选填) + +:::warning +除了默认属性外,概念类型不允许再创建属性,如果建模中发现确实需要属性描述的可以额外再创建一个实体类型。 +::: +概念在导入的时候,使用短横-符号作为上位关系分隔符,每个概念需要完整给出所有的上位关系。 以学校分类概念举例概念实例: + +```json +[ + { + "name": "公立院校", + "alias": "公立,公立学校" + }, + { + "name": "公立院校-公立大学", + "alias": null + }, + { + "name": "公立院校-公立大学-公立综合类大学", + "alias": "公立综合大学" + } +] +``` + +导入后形成如下子图: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*xijrRptZW1YAAAAAAAAAAAAADtmcAQ/original) + +### 标准类型 (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})$ | 否 | diff --git a/docs/tutorial/installation/contribution.md b/docs/tutorial/installation/contribution.md new file mode 100644 index 0000000..c5f338c --- /dev/null +++ b/docs/tutorial/installation/contribution.md @@ -0,0 +1,84 @@ +--- +title: 参与贡献 +order: 2 +--- + +## 提交 Bug + +如果您在OpenSPG中发现了一个 非安全问题相关的 Bug, 请先到 Issues 中搜索,以防止该 Bug 已被提交。 + +如果找不到,请创建一个Issue来描述这个Bug。 + +## 提交安全性问题 + +如果您在OpenSPG中发现了一个 安全性问题,请不要公开,通过邮件联系 owner, 并在邮件中详细描述该安全问题。 + +## 解决现有问题 + +通过查看仓库 Issues 列表发现需要处理的问题信息,可以尝试解决其中的某个问题。 + +## 代码规范 + +### Python + +#### Code Style + +python 的 code style 总体上要求符合pep8的标准。 + +如果使用 PyCharm开发,可以通过 [BlackConnect](https://black.readthedocs.io/en/stable/integrations/editors.html) 插件进行format。 + +#### Docstring + +使用 Google style format。 + +如果使用 Pycharm 开发,可以在 `Preferences -> Tools -> Python Integrated Tools -> Docstrings` 进行配置。 + +### Java + +#### Code style + +使用 Google Style。 + +如果使用 Intellij 开发,通过如下方式进行进行配置和 format: + +(1) 下载 [intellij-java-google-style.xml](https://github.com/google/styleguide/blob/gh-pages/intellij-java-google-style.xml)。 + +(2) 在 Intellij 中, `Settings -> Editor -> Code Style -> import schemes-> intellij idea code style xml` 进行配置。 + +## Issue 与 PR + +对于功能优化,功能扩展,Bug fix 等方面的问题, 我们非常欢迎进行讨论和相应的修改。 + +## 开发流程 + +- 切换到你的开发分支 + + ``` + git checkout -b your-branch + .... + git add xxx + git commit -m "xxx" + ``` + +- 开发你的功能 +- 添加单测 +- 提交PR +- 处理冲突 + + ``` + git checkout your-branch + git rebase master # 确保本地 master 是最新的 + ``` + +- Code review + + 你提交的代码需要通过 code review 才能合入到 master, 请耐心等待。 + 我们将分配相关同学进行 code review。 + 如果相关同学2个工作日仍然没有回应你的PR,请在你的PR中 @ 相关同学。 + code review的相关评论会直接贴到相关的 PR 或 issue 中。 如果你觉得相关建议是合理的,请更新你的代码。 + +- 合入到 master + + code review 通过后,我们会安排新的同学进一步review, 确保每个 PR 至少有两个同意后才能合入到主线。 + 这个过程中可能也会出现一些需要修改的意见,请耐心修改。 + 都通过后,PR将会合入到 master。 diff --git a/docs/tutorial/installation/installation.md b/docs/tutorial/installation/installation.md new file mode 100644 index 0000000..38ae901 --- /dev/null +++ b/docs/tutorial/installation/installation.md @@ -0,0 +1,84 @@ +--- +title: 快速安装 +nav: + order: 2 + title: 安装&教程 + second: + order: 1 +--- + +## 1 安装服务端 + +服务端基于docker compose部署,主要包含4个镜像: + +| 镜像 | 说明 | +| -------------- | -------------- | +| openspg-server | 提供schema服务 | +| openspg-mysql | 存储schema数据 | +| tugraph | 存储图谱数据 | +| elasticsearch | 索引图谱数据 | + +下载 [docker-compose.yml](https://github.com/OpenSPG/openspg/blob/master/dev/release/docker-compose.yml) +文件,并在当前目录下执行以下命令,等待命令执行完成即完成服务端启动: + +```bash +docker-compose -f docker-compose.yml up -d +``` + +## 2 安装客户端 + +客户端也提供了docker镜像,直接执行以下命令会拉取该镜像: + +```bash +docker pull --platform linux/x86_64 openspg/openspg-python:latest +``` + +### 案例体验 + +拉取完镜像后,如果想体验OpenSPG提供的案例,可以启动容器后,进入案例的目录执行相应knext命令: + +```bash +# 启动容器 +docker run --rm --net=host \ + -it openspg/openspg-python-amd64:latest \ + "/bin/bash" + +# 容器启动后,可以直接进入openspg提供的案例目录,就可以按照教程体验例案例了 +cd /openspg/python/knext/knext/examples + +# 参考案例教程,执行相应的knext命令,比如 +knext project create --prj_path . +knext schema commit + +knext builder execute ... +knext reasoner execute ... +``` + +### 项目开发 + +如果期望基于OpenSPG新建项目,可以使用下面的命令启动容器 + +```bash +# 启动容器,并将本地的项目目录挂载到docker的/code目录中 +docker run --rm --net=host -v ${project_dir}:/code \ + -it openspg/openspg-python-amd64:latest \ + "/bin/bash" + +# 容器启动后,可以直接进入/code目录 +cd /code + +# 创建OpenSPG项目 +knext project create --name ${项目名} --namespace ${项目命名空间} + +# 上述命令执行成功后,会在/code下创建一个名称为${项目命名空间}的knext工程目录 +# 进入该目录后,可以看到schema、builder、reasoner三个目录 +cd ${项目命名空间} +``` + +如果使用PyCharm等IDE来开发该knext工程,也可以将${project_dir}目录下的${项目命名空间}拖到PyCharm中。 + +同时本地安装openspg-knext进入开发。由于本目录已挂在到docker中,开发完成后在docker中执行knext命令即可 + +```bash +pip install openspg-knext +``` diff --git a/docs/tutorial/knext/command.md b/docs/tutorial/knext/command.md new file mode 100644 index 0000000..c64de8d --- /dev/null +++ b/docs/tutorial/knext/command.md @@ -0,0 +1,647 @@ +--- +title: KNext +order: 1 +nav: + second: + order: 4 + title: KNext +--- + +## 1 快速开始 + +该示例可以帮助快速开始一个简单的图谱数据导入和分析推理。 + +### 1.1 设置服务端地址 + +执行以下命令,设置OpenSPG服务端地址(默认为本地地址 [http://127.0.0.1:8887](http://127.0.0.1:8887) ): + +```bash +knext config set --global host_addr=http://127.0.0.1:8887 +``` + +### 1.2 创建一个示例项目 + +执行以下命令,创建一个项目,会在当前目录下生成示例项目文件夹,文件夹名为namespace的小写: + +```bash +knext project create --prj_path 示例项目 --namespace Prj --desc 这是一个示例项目 +``` + +### 1.3 切换项目 + +执行以下命令,进入项目目录,所有对于该项目的操作需要在项目目录内进行: + +```bash +cd prj +``` + +项目内包含: + +- 一个示例实体 `Prj.Demo`,声明在 `/schema/prj.schema`,用于创建项目schema: + +``` +namespace Prj + +Demo(示例实体): EntityType + properties: + demoProperty(示例属性): Text +``` + +- 一个构建任务 `Demo`,定义在`builder/job/demo.py` ,用于导入 `Prj.Demo`实体: + +```python +# -*- coding: utf-8 -*- +from knext.api.component import ( + CSVReader, + KGWriter, +) +from knext.api.component import SPGTypeMapping +from knext.client.model.builder_job import BuilderJob +from schema.prj_schema_helper import Prj + + +class Demo(BuilderJob): + + def build(self): + source = CSVReader( + local_path="./builder/job/data/Demo.csv", + columns=["id", 'prop'], + start_row=2 + ) + + mapping = ( + SPGTypeMapping(spg_type_name=Prj.Demo) + .add_property_mapping("id", Prj.Demo.id) + .add_property_mapping("prop", Prj.Demo.demoProperty) + ) + + sink = KGWriter() + + return source >> mapping >> sink +``` + +- 一个抽取算子 `DemoExtractOp`,定义在 `builder/operator/demo_extract.py` : + +```python +# -*- coding: utf-8 -*- +import requests +from typing import List, Dict +from knext.api.operator import ExtractOp +from knext.api.record import SPGRecord + + +class DemoExtractOp(ExtractOp): + + def __init__(self, params: Dict[str, str] = None): + super().__init__(params) + + def invoke(self, record: Dict[str, str]) -> List[SPGRecord]: + # 实现user defined抽取逻辑 + return [SPGRecord()] +``` + +- 一个DSL查询语句,声明在 `reasoner/demo.dsl`,用于查询所有 `Prj.Demo`实体: + +``` +MATCH (s:Prj.Demo) +RETURN s.id, s.demoProperty +``` + +### 1.4 创建schema + +```bash +knext schema commit +``` + +- 执行此命令后,会将`/schema/prj.schema`内的schema声明,提交到服务端。 + +### 1.5 发布算子 + +```bash +knext operator publish DemoExtractOp +``` + +- 执行此命令后,会扫描 `builder/operator`下的所有算子,并将 `DemoExtractOp` 发布到服务端。 + +### 1.6 知识加工 + +```bash +knext builder execute Demo +``` + +- 执行此命令后,会扫描 `builder/job`下的所有加工任务(需要继承`BuilderJob`类),并将 `Demo`提交到服务端。 + +### 1.7 dsl查询 + +```bash +knext reasoner execute --file reasoner/demo.dsl +``` + +- 执行此命令后,会将 `--file` 指定的文件中的dsl查询语句,提交到服务端进行查询,并同步返回查询结果。 + +## 2 命令行工具 + +通过`knext`命令行工具,以及定义的多个子命令,实现完整的图谱构建与使用流程。 + +```bash +Usage: knext [OPTIONS] COMMAND [ARGS]... + +Options: + --version Show the version and exit. + --help Show this message and exit. + +Commands: + builder Builder client. + config Knext config. + operator Operator client + project Project client. + reasoner Reasoner client. + schema Schema client. +``` + +### 2.1 config + +```bash +Usage: knext config [OPTIONS] COMMAND [ARGS]... + + Knext config. + +Options: + --help Show this message and exit. + +Commands: + list List global and local knext configs. + set Edit global or local configs. +``` + +#### 2.1.1 修改配置 + +```bash +knext config set [--help] + [--global key=value] + [--local key=value] +``` + +- 【选填】--global 全局配置设置,将在`~/.config`下生成`.knext.cfg`配置文件,并将`key=value`配置写入文件中。该文件配置对所有项目生效。 +- 【选填】--local 项目配置设置,将在项目目录下生成`.knext.cfg`配置文件,并将`key=value`配置写入文件中。该文件配置仅对当前项目生效。 + +使用实例: + +```bash +knext config set --global host_addr=http://127.0.0.1:8887 +``` + +结果: +执行`cat ~/.config/.knext.cfg`展示全局配置信息: + +```bash +[global] +host_addr = http://127.0.0.1:8887 +``` + +#### 2.1.2 展示配置 + +```bash +knext config list [--help] +``` + +使用实例: + +```bash +knext config list +``` + +结果: + +```bash +[global] +host_addr = http://127.0.0.1:8887 +[local] +``` + +### 2.2 project + +```bash +Usage: knext project [OPTIONS] COMMAND [ARGS]... + + Project client. + +Options: + --help Show this message and exit. + +Commands: + create Create new project with a demo case. + list List all project information. +``` + +#### 2.2.1 创建项目 + +```bash +knext project create [--help] + [--name name] + [--namespace namespace] + [--desc desc] + [--prj_path prj_path] +``` + +- 【必填】--name 项目名 +- 【必填】--namespace 项目内schema的前缀,限制以大写字母开头,仅包含字母或数字,长度最大为16。 +- 【选填】--desc 项目介绍 +- 【选填】--prj_path 项目路径,用于根据本地项目目录在服务端恢复项目。 + +使用实例: + +```bash +knext project create --name 示例项目 --namespace Prj --desc 这是一个示例项目 +``` + +结果: +执行成功后会在当前目录下创建出prj目录,执行`cd prj`进入示例项目。 + +``` +. +└── prj + ├── builder + │ ├── model + │ ├── operator + │ │ └── demo_extract_op.py + │ └── job + │ ├── data + │ │ └── Demo.csv + │ └── demo.py + ├── reasoner + │ └── demo.dsl + ├── schema + │ ├── prj.schema + └── README.md +``` + +#### 2.2.2 展示所有项目 + +```bash +knext project list [--help] +``` + +执行该命令,会列出当前所有已创建的项目信息。 +使用实例: + +```bash +knext project list +``` + +结果: + +```bash +| ID | Name | Namespace | Description | +|------|----------------|-------------|----------------| +| 1 | defaultProject | DEFAULT | defaultProject | +| 2 | 示例项目 | Prj | 这是一个示例项目 | +``` + +### 2.3 schema + +```bash +Usage: knext schema [OPTIONS] COMMAND [ARGS]... + + Schema client. + +Options: + --help Show this message and exit. + +Commands: + commit Commit local schema and generate schema helper. + diff Print differences of schema between local and server. + reg_concept_rule Register a concept rule according to DSL file. +``` + +#### 2.3.1 提交schema + +```bash +knext schema commit [--help] +``` + +使用实例: + +```bash +knext schema commit +``` + +结果: + +``` +Create type: Prj.Demo +Schema is successfully committed. +SchemaHelper is created in schema/prj_schema_helper.py. +``` + +#### 2.3.2 展示schema diff(不提交) + +```bash +knext schema diff [--help] +``` + +#### 2.3.3 提交概念规则 + +```bash +knext schema reg_concept_rule [--help] + [--file file] +``` + +- 【必填】--file concept rule文件路径 + +使用实例: +schema/concept.rule + +``` +namespace DEFAULT + +`TaxOfRiskApp`/`赌博应用`: + rule: [[ + ... + ]] +``` + +执行命令: + +```bash +knext schema reg_concept_rule --file schema/concept.rule +``` + +结果: + +``` +Defined belongTo rule for ... +... +Concept rule is successfully registered. +``` + +### 2.4 operator + +```bash +Usage: knext operator [OPTIONS] COMMAND [ARGS]... + + Operator client + +Options: + --help Show this message and exit. + +Commands: + list List all server-side operators. + publish Publish an operator to server. +``` + +#### 2.4.1 发布算子 + +```bash +knext operator publish [OP_NAMES] +``` + +- 【必填】OP_NAMES 发布的算子名,多个算子间用`,`分隔开。所有算子必须实现在 `builder/operator/`下,且需要继承`BaseOp` + 的子类,算子名默认为类名。 + +使用实例: +builder/operator/demo_extract_op.py + +```python +... + + +class DemoExtractOp(KnowledgeExtractOp): + + +... +``` + +执行命令: + +```bash +knext operator publish DemoExtractOp +``` + +结果: + +```bash +Operator [DemoExtractOp] has been successfully published. The latest version is 1. +``` + +#### 2.4.2 展示所有算子 + +```bash +knext operator list [--help] +``` + +使用实例: + +```bash +knext operator list +``` + +### 2.5 builder + +```bash +Usage: knext builder [OPTIONS] COMMAND [ARGS]... + + Builder client. + +Options: + --help Show this message and exit. + +Commands: + get Query submitted job status. + execute Submit asynchronous builder jobs to server by providing job names. +``` + +#### 2.5.1 提交构建任务 + +```bash +knext builder execute [JOB_NAMES] +``` + +- 【必填】JOB_NAMES 提交的构建任务名,多个任务间用`,`分隔开。所有任务必须实现在 `builder/job/`下,且需要继承`BuilderJob` + ,任务名默认为类名。 + +使用实例: +builder/job/demo.py + +```python +... + + +class Demo(BuilderJob): + + +... +``` + +执行命令: + +```bash +knext builder execute Demo +``` + +结果: + +```bash +Operator [DemoExtractOp] has been successfully published. The latest version is 1. +``` + +#### 2.5.2 查询构建任务 + +```bash +knext builder get [--help] + [--id id] +``` + +- 【必填】--id 查询的任务id(成功提交任务后会返回),结果返回单个任务实例。 + +### 2.6 reasoner + +```bash +Usage: knext reasoner [OPTIONS] COMMAND [ARGS]... + + Reasoner client. + +Options: + --help Show this message and exit. + +Commands: + get Query submitted reasoner job status. + execute Query dsl by providing a string or file. + submit Submit asynchronous reasoner jobs to server by providing DSL file or string. +``` + +#### 2.6.1 DSL查询 + +提交DSL查询任务,结果同步返回,查询任务耗时超过3分钟会报错。 + +```bash +knext reasoner execute [--help] + [--file file] + [--dsl file] +``` + +- 【二选一】--file 查询的dsl文件。 +- 【二选一】--dsl 查询的dsl语法,用双引号括起来。 + +使用实例: +reasoner/demo.dsl: + +```bash +MATCH (s:Prj.Demo) +RETURN s.id, s.demoProperty +``` + +执行命令: + +```bash +knext reasoner execute --file reasoner/demo.dsl +``` + +结果: + +```bash +| s_id | s_demoProperty | +|--------|------------------| +| 00 | demo | +``` + +#### 2.6.2 提交DSL推理任务 + +提交查询任务,结果异步生成。 + +```bash +knext reasoner execute [--help] + [--file file] + [--dsl file] +``` + +- 【二选一】--file 查询的dsl文件。 +- 【二选一】--dsl 查询的dsl语法,用双引号括起来。 + +#### 2.6.3 查询推理任务 + +```bash +knext reasoner get [--help] + [--id id] +``` + +【必填】--id 查询的任务id(成功提交任务后会返回),结果返回单个任务实例。 + +## 3 默认项目结构 + +``` +. +└── riskmining + ├── builder # 知识加工 + │ ├── model # 算法模型目录 + │ ├── operator # 算子目录 + │ | ├── demo_link_op.py + │ | ├── demo_extract_op.py + │ │ └── ... + │ └── job # 加工任务目录 + │ ├── demo1.py + │ ├── demo2.py + │ ├── data # 数据目录 + │ │ ├── Demo1.csv + │ │ ├── Demo2.csv + │ │ └── ... + │ └── error_record # 错误信息目录 + │ ├── spgbuilder_Demo1_1_errorRecord.csv + │ ├── spgbuilder_Demo1_2_errorRecord.csv + │ └── ... + ├── reasoner # 规则推理 + │ ├── demo.dsl + │ └── result + │ ├── spgreasoner_job_1_result.csv + │ ├── spgreasoner_job_2_result.csv + │ └── ... + ├── schema # schema定义 + │ ├── riskmining.schema + │ ├── riskmining_schema_helper.py + │ └── concept.rule + ├── README.md + └── .knext.cfg +``` + +- `.knext.cfg` + 文件为整个项目的配置文件。配置信息大部分情况下固定,当调整项目目录结构时,需要通过`knext config set --local key=value` + 命令修改配置。 在新建项目时会自动生成,并写入默认配置如下: + +```bash +[local] +project_name = 风险挖掘 +description = 风险挖掘项目 +project_id = 2 +namespace = RiskMining +project_dir = riskmining +schema_dir = schema +schema_file = riskmining.schema +builder_dir = builder +builder_operator_dir = builder/operator +builder_record_dir = builder/error_record +builder_job_dir = builder/job +builder_model_dir = builder/model +reasoner_dir = reasoner +reasoner_result_dir = reasoner/result +``` + +- `project_name` 项目名,新建项目时通过[--name]参数指定,不可修改。 +- `description` 项目介绍,新建项目时通过[--desc]参数指定。 +- `project_id` 项目id,新建项目时自动分配。若使用`knext project create [--prj_pth]`命令,会生成新的id并覆盖原id。 +- `namespace` 项目schema前缀,新建项目时通过[--namespace]参数指定,不可修改。 +- `project_dir` 项目根目录,新建项目时默认为`namespace.lower()`。若文件夹重命名,需要修改此配置。 +- `schema_dir` schema目录,新建项目时默认为`schema`。 +- `schema_file` schema声明文件名,新建项目时默认为 `${namespace.lower()}.schema`。若文件重命名,需要修改此配置。 +- `builder_dir` 加工任务目录,新建项目时默认为 `builder`。若目录变动,需要修改此配置。 +- `builder_operator_dir` 算子目录,新建项目时默认为 `builder/operator`。若目录变动,需要修改此配置。 +- `builder_record_dir` 加工任务记录目录,新建项目时默认为 `builder/record`。若目录变动,需要修改此配置。 +- `builder_job_dir` 算子目录,新建项目时默认为 `builder/job`。若目录变动,需要修改此配置。 +- `builder_model_dir` 算法模型目录,新建项目时默认为 `builder/model`。若目录变动,需要修改此配置。 +- `reasoner_dir` 规则推理目录,新建项目时默认为 `reasoner`。若目录变动,需要修改此配置。 +- `reasoner_result_dir` 规则推理结果目录,新建项目时默认为 `reasoner/result`。若目录变动,需要修改此配置。 +- `builder` 目录用来保存所有知识加工任务以及依赖的源数据、自定义算子、算法模型、执行错误记录。 +- `reasoner` 目录用来保存规则推理相关的DSL语法文件和执行结果。 + - DSL语法文件以`.dsl`为结尾,用来保存DSL查询语句。 +- `schema` 目录用来保存项目schema声明和概念规则。 + - 项目schema声明文件以`.schema`为结尾,通过`knext schema commit`解析schema文件,并提交到服务端。每个项目只允许唯一的schema声明文件。 + - 概念规则以`.rule` 为结尾,通过`knext schema reg_concept_rule [--file]`,将文件中定义的规则注册到对应概念上。 diff --git a/docs/tutorial/knext/nn4k.md b/docs/tutorial/knext/nn4k.md new file mode 100644 index 0000000..1cffc01 --- /dev/null +++ b/docs/tutorial/knext/nn4k.md @@ -0,0 +1,107 @@ +--- +title: NN4K介绍 +order: 3 +--- + +NN4K(Neural Network for Knowledge Graph)是一个神经网络模型的开发、管理、服务框架,为OpenSPG提供简单易用、模式统一的大模型服务。 +NN4K主要分为三大部分: + +1.模型Executor。模型的生产侧,主要针对不同的基座模型,由模型生产者提供模型使用逻辑,包括sft、inference等能力。executor是NN4K中对大模型的可操作的功能封装,而非module本身,在结构层次上,executor " +has a" module。因此executor不会包含模型层面的操作,诸如forward、backward之类的方法,而是提供更高层次的方法抽象封装。2. 模型Invoker。统一的模型服务接入方法。用户只需要提供基本的模型使用参数,以极小的成本切换模型基座、使用模型服务,而无需关心模型内部细节。3. 模型Hub(可选)。在一些场景下,用户有统一管理模型的需求。在这里预留模型Hub的定义和接口,方便用户基于不同的平台管理模型。 + +通过NN4K,用户可以一键切换大模型基座,一键唤起大模型服务,同时可以很容易的把大模型服务嵌入OpenSPG的算子中,降低开发和使用难度。 +整体类架构: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*DQ_2RZ1lVrkAAAAAAAAAAAAADtmcAQ/original) + +## 发布规划 + +第一版NN4K以支持SPG知识自动化抽取为核心,自动化的接入openai服务,用户无需担心大模型的使用细节。 +大模型所扮演的角色,自然远远不止一个openai服务这么简单。大模型在预期的设计中,会和SPG图谱联动起来,从图谱获取数据用于训练模型、可控生成答案等。之后会对一些主流的常用模型,例如huggingface注册的llama,llama2,包括一些中文模型,例如baichuan等模型做一些诸如sft、rhlf的默认实现。用户在没有特殊需求的情况下,可以依赖我们的默认实现,仅需提供数据+配置文件,就可以轻松在全流程享受大模型带来的便利。 +随着模型支持的增多,以及用户sft后的模型增加,模型的管理了分享慢慢会成为一个基础需求。NN4K预留了Hub接口并会提供一些基础实现。这些接口会充分考虑复杂场景下的模型管理和使用。这是一个可选模块,用户可自由选择何时使用、怎么使用。这个将在主流模型的默认实现提供完成后,发布默认的实现。 + +## 如何开发 + +### Invoker + +如果你只是一个是模型的使用者,invoker是你唯一需要关心的程序入口。一般来说,如果需要使用NN4K提供的默认大模型实现任务,使用NNInvoker就已经足够了。 +Invoker是执行、提交大模型任务的统一入口,提供和对应executor配套的submit_sft,submit_inference等方法。通常来说,invoker会提供提交任务到本地和提交任务到远程集群的能力,用户也可以自行实现invoker以提交任务到不同的环境。 + +#### 使用具备默认实现的Invoker + +NN4K现在提供了调用远端inference服务的invoker默认实现,即OpenAIInvoker。如果需要使用OpenAIInvoker调用OpenAI服务,你只需: + +1. 准备一个config文件,例如:remote_inference.json + +```yaml +{ + 'nn_name': 'gpt-3.5-turbo', + 'openai_api_key': 'EMPTY', + 'openai_api_base': 'http://127.0.0.1:38080/v1', + 'openai_max_tokens': 2000, +} +``` + +其中 openai_api_base 指定 OpenAI API 服务的 URL,若要使用 OpenAI 官方的 API,应将 openai_api_base +设为 `https://api.openai.com/v1`,并将 openai_api_key 设为从 [API keys](https://platform.openai.com/api-keys) 获取的 key,类似 ` +sk-xxx`。 + +2. 在任意python文件中调用NNInvoker + +```python +NNInvoker.from_config("remote_inference.json").remote_inference("你的输入") +``` + +NN4K框架程序会自动判断并且使用OpenAIInvoker来执行remote_inference方法。如果需要知道更多细节,可以参阅 nn4k.invoker +中的 [openai_invoker.py](https://github.com/OpenSPG/openspg/blob/master/python/nn4k/nn4k/invoker/openai.py) 了解详情。 +OpenAIInvoker的使用比较简单。未来对于复杂的Invoker和参数,我们都会有详细的example和文档说明,用户可以在已有的example基础上做少量的修改,所以不必过分担忧。 + +#### 开发自定义Invoker + +有些时候,你可能需要开发自己的invoker来满足你的自定义需求。一旦你选择开发自己的invoker,你就可以完全控制代码的执行流程和推理/训练使用框架。我们强烈建议你从NNInvoker或者LLMInvoker( +如果你开发的是基于大模型的任务)来继承开发你的自定义Invoker,这样你的Invoker得以更简单的和他人分享。同时,使用你自己的的invoker也只需要在from_config方法的config参数文件(参见上一节的remote_inference.json文件)中加上nn_invoker配置即可。 + +```json +{ + ... + "nn_invoker": "package.path.to.your.Invoker" + ... +} +``` + +##### NNInvoker + +是所有Invoker的基类,这里简单描述一下每个方法的作用: + +###### from_config + +必须实现的方法。原则上所有的初始化动作都需要通过此方法完成。此方法中主要完成不同格式的输入args的解析和记录,同时,如果发现自身类无法完成目标invoker的任务(例如NNInvoker无法完成OpenAIInvoker的具体任务),则会把调用代理到其他可处理的Invoker中去执行。 + +###### submit_inference + +发送批量inference任务到集群中运行 + +###### remote_inference + +发送input到远端inference服务来执行inference。 + +###### local_inference + +在本地装载模型并且在本地执行inference任务。一般来说,大模型的本地任务需要本地高性能显卡。 + +###### warmup_local_model + +在起一个大模型服务时,有时需要一些warmup的动作。如果需要,则在这个方法中统一执行。 + +##### LLMInvoker + +继承自NNInvoker。除了NNInvoker具备的能力,还具备大模型训练具备的一些能力定义。例如: + +###### submit_sft + +提交一个大模型sft任务。 + +###### submit_rl_tuning + +提交一个rl_turing任务。 +如上所述,我们将会很快提供一些常用模型的sft和rlhf训练的默认实现,敬请期待。 diff --git a/docs/tutorial/knext/operator.md b/docs/tutorial/knext/operator.md new file mode 100644 index 0000000..c9b162b --- /dev/null +++ b/docs/tutorial/knext/operator.md @@ -0,0 +1,297 @@ +--- +title: 算子介绍 +order: 2 +--- + +如下表所示,knext提供共5类算子接口,用户可以通过继承对应基类,实现算子接口,来自定义算子执行逻辑。所有算子必须依赖组件执行,依赖关系参考 +knext能力模型。 + +| 算子名称 | 伪代码表示 | 算子说明 | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ExtractOp | class CompanyBuild(BuilderJob):
 def build(self):
  source = CSVReader(...)
  extract = UserDefinedExtractor(extract_op=TestExtractOp())
  mappings = [...]
  sink = KGWriter()
  return source >> extract >> mappings >> sink | 定义从非结构化文本中抽取得到结构化要素的
算法逻辑,绑定在UserDefinedExtractor组件上,
基于LLMBasedExtractor无需绑定ExtractOp。 | +| LinkOp | Person(人): EntityType
 properties:
  workAt(工作所在公司): Company

class CompanyLinkOp(LinkOp):
 # LinkOp需要绑定到实体/概念/事件类型上
 bind_to = Finance.Company
 ...

class PersonBuild(BuilderJob):
 def build(self):
  source = CSVReader(...)
  person_mapping = (
   SPGTypeMapping(spg_type_name=ns.Person)
   .add_property_mapping("id", ns.Person.id)
   .add_property_mapping("workAt", ns.Person.workAt)
  )
 ; sink = KGWriter()

 ; return source >> mappings >> sink | LinkOp只能绑定到特定的实体、事件、概念、
标准类型上(比如type1), 当type1被定义为
其他类型(如type2)的属性类型或关系的otype时,
在type2的构建阶段会自动激活对type1 LinkOP的调用。
如伪代码所示,CompanyLinkOp 绑定到了
Company类型上,在用户的构建任务中会自动
激活对CompanyLinkOp的调用。 | +| FuseOp | Company(公司): EntityType
 properties:
 ...

class CompanyFuseOp(P):
 #LinkOp需要绑定到实体/概念/事件类型上
 bind_to = Finance.Company
 ...
class CompanyBuild(BuilderJob):
 def build(self):
  source = CSVReader(...)
  person_mapping = (
  SPGTypeMapping(spg_type_name=ns.Company)
   .add_property_mapping("id", ns.Company.id)
   .add_property_mapping("name", ns.Company.name)
  )
  sink = KGWriter()

  return source >> mappings >> sink | LinkOp只能绑定到特定的实体、事件、概念、标准类型
上(比如type1), 它的作用是实现对type1实例的去重合,
在type1的构建阶段会自动激活对type1
FuseOp的调用。如伪代码所示,CompanyFuseOp
绑定到了Company类型上,在Person的构建任务
中会自动激活对CompanyFuseOp的调用,
若图谱中存在相同/相似的实例,自动触发合并,
并更新写入到图谱中。 | +| PredictOp | Company(公司): EntityType
 properties:
  IND#belongTo TaxoOfCompany
class CompanyTypePredict(PredictOp):
 #LinkOp需要绑定到实体/概念/事件类型上
 bind_to = (Finance.Company, "IND#belongTo", Finance.TaxoOfCompany)
 ...

class CompanyBuild(BuilderJob):
 def build(self):
  source = CSVReader(...)
  person_mapping = (
   SPGTypeMapping(spg_type_name=ns.Company)
   .add_property_mapping("id", ns.Company.id)
   .add_property_mapping("name", ns.Company.name)
   .add_predicting_relation("IND#belongTo", Finance.TaxoOfCompany)
  )
  sink = KGWriter()

  return source >> mappings >> sink | PredictOp只能绑定到(SType, p, Otype)三元组上,
它的作用是预测符合(SType, p)要求的Otype实例,
在type1的构建阶段会自动激活对PredictOp的调用。
如伪代码所示,CompanyTypePredict 绑定到了
(Finance.Company, "IND#belongTo", Finance.TaxoOfCompany)
上,在Company的构建任务中会自动激活对
CompanyTypePredict的调用,找到符合要求
的目标Otype实例后,生成三元组实例,并更新写入
到图谱中。它和链指算子的区别是,
链指算子解决的是文本与图谱实体对齐的问题,
预测算子则是预测新的类型实例。 | +| PromptOp | | 用于与大模型交互,定义特定任务的提示
,以及大模型输出的结果解析为SPGRecord的逻辑。 | + +下面给出各个算子的接口定义,以及示例实现。 + +### ExtractOp + +```python +class ExtractOp: + """ + 【针对场景】用于将非结构化数据转成结构化 + 【使用方法】设置在 UserDefinedExtractor 的 extract_op 参数中 + 【bind_to】无 + 【输入】非结构化数据 + 【输出】抽取出的结构化数据 + """ + + def invoke(self, record: Dict[str, str]) -> List[SPGRecord]: + pass +``` + +下面是一个ExtracOp的示例: + +```python +class TestExtractOp(ExtractOp): + def invoke(self, record: Dict[str, str]) -> List[SPGRecord]: + spg_type = record["type"] + properties = json.loads(record["properties"]) + return [SPGRecord(spg_type, properties)] + +``` + +该抽取算子可以从以下record中构造SPGRecord: + +```python + record = { + "type": "Company", + "properties": '{"phone": "+86-12345678", "addr": "China"}', +} + +``` + +### LinkOp + +```python +class LinkOp: + """ + 【针对场景】导入s时,根据o的属性值,链指o节点,用于拉边 + 【使用方法】绑定在spg_type上,在mapping组件的property_normalizer阶段调用 + 【bind_to】实体/概念/事件类型 + 【输入】otype的属性值,stype的所有属性 + 【输出】链指到的otype实例 + """ + + bind_to: str + + def invoke(self, property: str, subject_record: SPGRecord) -> List[SPGRecord]: + pass +``` + +下面是一个LinkOp的示例: + +```python +class CompanyLinkOp(LinkOp): + # LinkOp需要绑定到实体/概念/事件类型上 + bind_to = "Company" + + pass + +``` + +### FuseOp + +```python +class FuseOp: + """ + 【针对场景】属性融合/实体去重 + 【使用方法】 绑定在spg_type上,在mapping组件的spg_type_fuser阶段调用 + 【bind_to】实体/概念/事件类型 + 【输入】新增待融合实体,可以为多个 + 【输出】融合后的实体,可以为多个 + """ + + bind_to: str + + def link(self, subject_record: SPGRecord) -> SPGRecord: + pass + + def merge(self, subject_record: SPGRecord, linked_record: SPGRecord) -> SPGRecord: + pass + +``` + +下面是一个FuseOP的示例: + +```python + +class CompanyFuseOp(FuseOp): + #FuseOp需要绑定到实体/概念/事件类型上 + bind_to = "Company" + + def jaccard_similarity(self, a: str, b: str): + terms_reference = list(b.replace(" ", "")) + terms_model = list(a.replace(" ", "")) + grams_reference = set(terms_reference) + grams_model = set(terms_model) + temp = 0 + for i in grams_reference: + if i in grams_model: + temp = temp + 1 + denominator = len(grams_model) + len(grams_reference) - temp + if denominator == 0: + return 0 + return float(temp / denominator) + + def link(self, subject_record: SPGRecord) -> SPGRecord: + name = subject_record.get_property("name") + linked_record = self.search_client.fuzzy_search(subject_record, "name") + + return output + + def merge( + self, subject_record: SPGRecord, linked_record: SPGRecord + ) -> SPGRecord: + + return linked_record + +``` + +### PredictOp + +```python +class PredictOp: + """ + 【针对场景】关系生成/概念上下位挂载 + 【使用方法】绑定在spo三元组上,在mapping组件的link_predictor阶段调用 + 【bind_to】实体/概念/事件类型 + 【输入】stype的所有属性 + 【输出】预测的otype实例 + """ + + bind_to: Tuple(str, str, str) + + def invoke(self, subject_record: SPGRecord) -> List[SPGRecord]: + pass + +``` + +下面是一个PredictoOp的示例: + +```python +class TestPredictOp(PredictOp): + # PredictOp需要绑定在spo三元组上 + bind_to = ("Company", "isSubsidiaryOf", "Company") + + def invoke(self, subject_record: SPGRecord) -> List[SPGRecord]: + name = subject_record.get_property("name") + # 构造一些新的SPGRecord,与subject_record一起构成SPO三元组 + output = [] + for i in range(self.num_outputs): + record = copy.deepcopy(subject_record) + new_name = f"{name}{i + 1}" + record.update_property("name", new_name) + record.update_property("index", str(i + 1)) + output.append(record) + return output + +``` + +### PromptOp + +knext针对RE/NER/EE任务,在`knext.api.operator`模块内置了对应的PromptOp。支持在组件内引入对应的PromptOp,设置抽取目标(SPG类型和属性名),自动通过schema信息进行模版渲染。 + +#### REPrompt + +默认模版: + +```python + template: str = """ +已知SPO关系包括:[${schema}]。从下列句子中提取定义的这些关系。最终抽取结果以json格式输出。 +input:${input} +输出格式为:{"spo":[{"subject":,"predicate":,"object":},]} +"output": + """ +``` + +使用示例: + +```python +from knext.api.operator import REPrompt + +prompt_op = REPrompt( + spg_type_name="Medical.Disease", + property_names=["commonSymptom", "applicableDrug"]) +``` + +算子初始化后,会通过以下规则生成schema信息并替换掉模版中的${schema}占位符: +[subject_type(subject description)-predicate(predicate description)-object_type(object description)] + +- subject_type、predicate、object_type使用SPG的中文名 +- subject/object description优先用类型的描述,如果描述为空,则取类型的中文名称 +- predicate description优先用属性的描述,如果描述为空,则取属性的中文名称 + +> **示例** > [产品-同类(产品是同一类别)-产品,产品-上游(上游指产业链的上游原料、设备、技术服务等供应)-产品, ...] + +#### NERPrompt + +默认模版: +待补充 +使用示例: + +```python +from knext.api.operator import REPrompt + +prompt_op = REPrompt( + spg_type_name="Medical.Disease", + property_names=["commonSymptom", "applicableDrug"]) +``` + +算子初始化后,会通过以下规则生成schema信息并替换掉模版中的${schema}占位符: +[["entity_type1":"desc1", "entity_type2":"desc2", "entity_type3":"desc3"] + +- entity_type使用SPG的英文名 +- desc优先用类型的描述,如果描述为空,则取类型的中文名称 + +> **示例** > ["Medical.Disease": "疾病", "Medical.Symptom": "疾病过程中机体内的一系列机能、代谢和形态结构异常变化所引起的病人主观上的异常感觉或某些客观病态改变"] + +#### EEPrompt + +默认模版: +待补充 +使用示例: +待补充 +算子初始化后,会通过以下规则生成schema信息并替换掉模版中的${schema}占位符: +[{event_type:eventTypeName1(eventTypeDesc1),arguments:[arg1_name(desc1), ...]}, ...] + +- event_type的值结构为:英文名(中文名),如果事件类型描述不为空,则使用描述取到括号内内容 +- arguments是属性列表,英文名(中文名),如果属性的描述不为空,则使用描述取到括号内内容 + +> **示例** +> [{event_type:StockTransEvent(股份股权转让事件) +> ,arguments:[subject_company(股份股权转让公司),subject_person(股份股权转让人), ...]},] + +#### 自定义Prompt + +需要继承PromptOp基类,并实现以下接口: + +```python +class PromptOp: + """ + 【针对场景】封装LLM需要的prompt模版和前置后置处理逻辑 + 【使用方法】设置在 LLMBasedExtractor 的 prompt_ops 参数中 + 【bind_to】无 + 【输入】非结构化数据 + 【输出】抽取出的结构化数据 + """ + + template: str + + def build_prompt(self, variables: Dict[str, str]) -> str: + """根据variables填充prompt。""" + pass + + def parse_response(self, response: str) -> List[SPGRecord]: + """将模型结果转化成生产链路所需的SPGRecord格式""" + pass + + def build_next_variables(self, response: str, variables: Dict[str, str]) -> List[Dict[str, str]]: + """生成variables,作为下一个promptOp的build_prompt的入参""" + pass + +``` + +### 一个案例 + +我们以一个经济指标抽取的任务为例,具体介绍上述几个Op完成的工作。该任务的目的是从研报、财经新闻中抽取经济指标并导入指标关系图谱。假设我们有一个可用的大模型服务,如ChatGPT,我们可以基于该服务完成抽取任务。 +经济指标抽取的示例代码参见:[https://github.com/OpenSPG/openspg/tree/master/python/knext/knext/examples/finance](https://github.com/OpenSPG/openspg/tree/master/python/knext/knext/examples/finance) +该任务Operator相关代码在builder/operator目录,相关的ExtractOp、LinkOp、FuseOp、PredictOp、PromptOp分别执行如下工作: + +- ExtractOp:从原始文本中执行NER,抽取指标实体 +- LinkOp:定义其他实体属性挂载到Indicator的方式 +- FuseOp:将抽取出的指标与图谱内指标融合(去重) +- PredictOp:构造Indicator的上下位关系 +- PromptOp:构造不同任务的提示,并解析大模型输出结果 diff --git a/docs/tutorial/reasoner/dsl.md b/docs/tutorial/reasoner/dsl.md new file mode 100644 index 0000000..1b422c8 --- /dev/null +++ b/docs/tutorial/reasoner/dsl.md @@ -0,0 +1,1223 @@ +--- +title: KGDSL语法 +order: 1 +nav: + second: + order: 3 + title: 分析推理 +--- + +注:KGDSL不区分大小写 + +## 1 保留关键词 + +### 1.1 常用关键词 + +| 关键词 | 描述 | 作用范围 | +| ---------- | -------------- | ---------- | +| Define | 定义关键词 | 全局 | +| Structure | 子图描述关键词 | 全局 | +| Constraint | 规则描述关键词 | 全局 | +| Action | 后置动作关键词 | 全局 | +| / | 概念引用分隔符 | 全局 | +| group | 图分组关键词 | Constraint | + +| sum/filter/find/sort/slice +/count/max/min/avg/concat | 图聚合操作算子 | Constraint的group后 | +| and/or/not/xor/optional | 逻辑计算算子 | 全局 | + +### 1.2 特殊关键词 + +| 关键词 | 描述 | 作用范围 | +| ------------------ | ------------------------------- | ----------------- | +| **start** | 起点标志 | Structure | +| **per_node_limit** | 边限制标志 | Structure | +| **label** | 得到点边的类型 | Constraint/Action | +| **property_map** | 将图节点或者边的属性生成map对象 | Constraint/Action | +| **path** | 得到满足的路径 | Constraint/Action | +| **id** | 图谱点内部id(全局唯一) | Constraint/Action | +| **from** | 图谱边的起点内部id | Constraint/Action | +| **to** | 图谱边的终点内部id | Constraint/Action | + +## 2 数据类型 + +### 2.1 基本数据类型 + +| 数据类型 | 描述 | 示例 | +| -------- | -------- | ---------- | +| int | 整型 | 1,2,3 | +| float | 浮点型 | 23.11 | +| string | 字符串 | "abcdef" | +| bool | 布尔类型 | true/false | +| null | 空 | null | + +### 2.2 复杂数据类型 + +| 数据类型 | 描述 | 示例 | +| ------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------- | +| list | 数组类型 | [1,2,3] | +| multi_version | 多版本属性 | {
  "20220111":value,
  "20220112":value,
} | +| date | 日期类型 | / | +| node | 点类型 | {
  "id":123456,
  "label":"Film",
  "property":{"name":"Titanic"}
} | +| edge | 边类型 | {
  "from":1234,
  "to":4321,
  "label":"starOfFilm",
  "property":{"year":1989}
} | + +## 3 表达式算子 + +### 3.1 表达式风格 + +我们表达式采用过程式+链式两种方式混合表达 + +> 链式思想:是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。 +> 过程式思想:通过多行的方式,描述一段计算内容 + +链式风格非常适用于数据计算,如下例,我们需要计算一个表达式(1 + 2) \* 3 - 4,约束为一次只能计算一次,则过程式如下 + +> a = 1+2 +> b = a \*3 +> d = b -4 + +使用链式风格 + +> add(1,2).multiply(3).subtract(4) + +一行可以表达一个完整的计算流,我们可以在做数据计算时使用该风格 + +### 3.2 计算运算符 + +| 符号 | 示例 | 含义 | 备注 | +| ---- | ---- | -------- | -------- | +| + | a+b | 加法 | | +| - | a-b | 减法 | | +| \* | a\*b | 乘法 | | +| / | a/b | 除法 | 不可除0 | +| % | a%b | 取模 | b不可为0 | +| = | a=b | 赋值操作 | | + +### 3.3 逻辑运算符 + +| 符号 | 示例 | 含义 | 备注 | +| -------- | --------------------- | -------------- | ---------------------------------------- | +| and | a and b | 且 | | +| or | a or b | 或 | | +| not,! | not a, !a | 非 | not可作用于全局,但!只能作用于Constraint | +| xor | a xor b | 异或 | | +| optional | optional (a)-[e]->(b) | 对路径表示可选 | 只在Structure中对路径生效 | + +### 3.4 比较运算符 + +| 符号 | 示例 | 含义 | 备注 | +| ---- | -------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------ | +| == | a == b | 判等 | 只可比较int、float、string、node、edge,
其中,node、edge以id判断为准 | +| > | a > b | 大于 | | +| >= | a>=b | 大于等于 | | +| < | a < b | 小于 | | +| <= | a<=b | 小于等于 | | +| != | a != b | 不等 | | +| in | a in [1,2,3] | 包含 | | +| BT | a bt [1,5] | +| BT | a bt [1,5]
a bt (1,5) | between运算符,表示a在1,5之间,
方括号表示闭区间,
圆括号表示开区间 | 只可比较int、float、string | + +### 3.5 字符串运算符 + +| 符号 | 示例 | 含义 | 返回值 | 备注 | +| -------------- | ------------------------------------------------------------------------- | ----------------------------------------------- | ------ | ---------------------- | +| contains | contains(a,b) | 判断a字符串是否包含b字符串 | bool | | +| like,not like | a like b,a not like b | 字符串匹配判断,%为通配符 | bool | "abc" like "a%" 为true | +| concat,+ | concat(a,b),a+b,
concat(a,b,c),a+b+c
concat(a,...,f), a+...+f | 字符串拼接,concat支持n个入参,
也可用+处理 | string | 暂未实现 | +| length | length(a) | 返回字符串长度 | int | | +| strstr | strstr(str,start)
strstr(str,start,end) | 得到字符串子串,从1开始 | string | 暂未实现 | +| lower | lower(a) | 全部转换成小写 | string | 暂未实现 | +| upper | upper(a) | 全部转换成大写 | string | 暂未实现 | +| is_not_blank | is_not_blank(a) | 字符串不为空:"" | bool | 暂未实现 | + +### 3.6 类型转换运算符 + +| 符号 | 示例 | 含义 | 支持情况 | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | +| cast(a, 'int'/'float'/'string') | cast(1,'string') //转化成为str | +| cast('1', 'int') //转换成int | 将基本类型转换成为其他基本类型 | | +| to_date(time_str,format) | to_date('20220101', 'yyMMdd') //转换成为日期 | 将字符串转换成为日志类型
format可以为如下组合
时间类型
- s,秒//unix时间戳起
- m,分
- h,小时
- d,天
- M,月
- y,年
可组合,各种合理格式
- yyMMdd 年月日类型
- yyMMdd hh:mm:ss 为简化使用,支持
数字@日期方式初始化时间 | 暂未实现 | +| window(version_express, unit) | A.cost_every_day //A为用户,cost_every_day为一个多版本属性,表示每日的花销
A.cost_every_day.window(cur in [1@M,2@M,3@M], M) //获取1月、2月、3月的数据,按月取数据
A.cost_every_day.window(start > -30@d, d) //取近30天的数据,按天取数据
A.cost_every_day.window(end <-15@d, d) //取15天天前的所有数据,,按天取数据
A.cost_every_day.window(start > -30@d and end <-15@d, d) //取30天前,到15天前的数据,按天取数据,可进行组合
A.cost_every_day.window( (start > -30@d and end <-15@d) and (start > -7@d), d) //取30天前,到15天前的数据,以及近7天的数据,按天取数据,可进行组合 | 将多版本类型(multi_version)转换成list,方便参与计算。**version_express**包含三个关键词
- start,起始版本号
- end,终点版本号
- cur,当前版本号表达式为逻辑表达式,可通过and/or进行组合
**unit** 为属性单位,有如下类型
- M,按月获取数据
- d,按天获取数据
- seq,默认值,按照序列取数据,当没有unit时,按照seq来处理
注意:若是按月或者按天获取数据,则需要存在按天和按月聚合的数据 | 暂未实现 | + +### 3.7 list运算符(未实现) + +由于list可以支持的类型为有int、float、string、node、edge,故list支持的运算符按照不同类型进行区分。 +针对list对象,我们采用链式风格对列表进行计算。 +假设定义一个数组: + +``` +array = [{age:10},{age:20},{age:30}] +``` + +对该数组的操作预算符用法如下: + +| 符号表示 | 示例 | 含义 | 输入类型 | 输出类型 | 元素类型 | +| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------- | ---------------- | ------------------------------------------------------------------ | +| max(alias_name) | array.mark_alias(a).max(a.age)
//输出为30 | 取最大值 | list | int/float/string | 支持int、float、string,但node和edge对象的属性为基础类型的可以支持 | +| min(alias_name) | array.mark_alias(a).min(a.age)
//输出为10 | 取最小值 | list | int/float/string | 支持int、float、string,但node和edge对象的属性为基础类型的可以支持 | +| sum(alias_name) | array.mark_alias(a).sum(a.age)
// 输出为60 | 对数组进行累加 | list | int/float | 支持int、float、string,但node和edge对象的属性为基础类型的可以支持 | +| avg(alias_name) | array.mark_alias(a).avg(a.age)
// 输出为20 | 取均值 | list | int/float | 支持int、float、string,但node和edge对象的属性为基础类型的可以支持 | +| count() | array.count()
//输出为3 | 取数组大小 | list | int | 支持所有类型 | +| filter(operator_express) | array.mark_alias(a).filter(a.age <18)
//输出为[{age:10}] | 对数组进行过滤,返回新的数组 | list | list | 支持所有类型 | +| sort(alias_name, 'desc'/'asc') | array.mark_alias(a).sort(a.age, 'desc')
//输出为[{age:30},{age:20},{age:10}] | 排序 | list | list | 支持所有类型 | +| slice(start_index,end_index) | array.mark_alias(a).slice(1,2)
//获取第一个到第二个的内容,输出为[{age:10},{age:20}] | 切片,从指定起点index到终点index,起点为1,取闭区间
- start_index,起点的index
- end_index,终止的index | | | 支持所有类型 | +| get(index) | array.mark_alias(a).get(1)
//获取第一个到第二个的内容,输出为{age:10} | 获取第index个元素,从1开始
若超过大小,则返回null | | | | +| str_join(alias_name, tok) | array.mark_alias(a).str_join(cast(a.age, 'string'), ',')
//将年龄转换成字符串,并且通过逗号生成字符串,输出为"10,20,30" | 字符串连接
- alias_name,数组中元素别名
- tok,连接符 | | | 只支持string | +| accumlate(operator, alias_name) | array.mark_alias(a).accumlate('\*', a.age) //累乘,结果为6000
array.mark_alias(a).accumlate('+', a.age) //累加等同于sun,输出为60 | 累计计算算子
- operator,为\*/
- alias_name,数组中元素别名 | | | 支持\*,+ | + +### 3.8 图聚合运算符 + +由于常常存在对图的聚合计算,此处定义一个图聚合算子,可以将一个子图按照指定模式聚合,并且根据别名进行数组计算,注意 + +| 符号 | 示例 | 含义 | 输入类型 | 输出类型 | 备注 | +| ------- | -------------------- | ---------------------------- | -------- | ---------------------------- | ------------------------------------------ | +| group() | group(a),group(a,b) | 将点或者边进行聚合,返回数组 | 图类型 | 后面需待具体算子,输出为数组 | 输入只能是点类型或者边类型,且必须带上起点 | + +图分组解释,假设我们存在如下数据: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*72A8QLOzeEcAAAAAAAAAAAAADtmcAQ/original#id=SoPfM&originHeight=682&originWidth=908&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +查询的子图模式为: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*oomuTKurb6UAAAAAAAAAAAAADtmcAQ/original#id=wTEWB&originHeight=474&originWidth=922&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +不同的group表达的结果如下: + +#### 3.8.1 示例1:group(A) + +此时对A进行分组,则如下操作返回的值为 + +> 返回类型为列表,由于整个子图分组成为了1个,所以返回的列表长度为1,后续结果只能输出1行数据 + +group(A).count(e1) // 对e1边进行计数,应当返回[2] + +group(A).count(B) // 对子图的B类型进行统计计数,应当返回[2] + +group(A).count(C) // 对子图的C类型进行统计计数,应当返回[4] + +group(A).count(e2) //对e2的边进行计数,应当返回[5], 因为有5条边 + +#### 3.8.2 实例2:group(A,B) + +被分组的图数据变成 + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*oomuTKurb6UAAAAAAAAAAAAADtmcAQ/original#id=KyFfB&originHeight=474&originWidth=922&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +> 返回类型为列表,由于整个子图分组成为了2个,所以返回的列表长度为2,后续结果只能输出2行数据 + +group(A,B).count(A) // 返回[1,1] + +group(A,B),count(B) // 返回[1,1] + +group(A,B).count(C) // 返回[3,1] + +group(A,B).count(e1) // 返回[1,1] + +group(A,B).count(e2) //返回[3,2] + +#### 3.8.3 实例3:group(A,B,C) + +被分组的图数据变成如下 + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*P7rpSZfyFdoAAAAAAAAAAAAADtmcAQ/original#id=SdUqY&originHeight=584&originWidth=840&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +> 返回类型为列表,由于整个子图被分为了4个子图,所以返回的列表长度为4,后续结果只能输出4行数据 + +group(A,B,C).count(C) // 返回[1,1,1,1] + +group(A,B,C).count(e2) // 返回[1,1,1,2] + +注:由于子图可能被划分成为多个子图,并不对最终返回的数组保证顺序 + +#### 3.8.4 约束和限制 + +##### 约束1:不允许不包含起点 + +- group(B)/group(C) ,不允许分组时不包含起点,因为这样分组不能保证正确性 + +##### 约束2:不允许按边分组 + +- group(A, e1)/group(A, e2),不允许按照边进行分组,因为存在两个相同点之间多边场景,如果允许按边聚合,则会出现大量的重复节点,导致后续计算消耗激增,且目前尚未看到有必须按边聚合场景 + +##### 约束3:若使用了多个group,则不允许后出现的group点比前出现的group点多 + +``` +//结果不可预期 +bNum = group(A).count(B) +eNum = group(A,B).count(e1) +``` + +``` +//结果符合预期 +eNum = group(A,B).count(e1) +bNum = group(A).count(B) +``` + +原因为,当group(A)分组后,B会折叠,此时在对A,B进行group会导致结果不正确,这里主要是考虑实现因素的约束 + +### 3.9 取数操作符 + +为方便对图进行取数据,设定算子如下 + +| 符号 | 示例 | 含义 | 备注 | +| ------------- | --------------- | ---------- | ---- | +| . | A.id | 取属性 | | +| **label** | A.**label** | 返回类型 | | +| **from** | e.**from** | 返回起点id | | +| **to** | e.**to** | 返回终点id | | +| **direction** | e.**direction** | 取边的方向 | | + +由于KGDSL中不支持if语法,所以需要针对逻辑判断部分,使用类条件运算符算子代替 + +**rule_value** + +- 范式:rule_value (rule_name, true_value, false_value) +- 作用:将规则真值转换为指定值,如果rule_name的规则运算结果为true,则返回true_value,如果为false,则返回falsevalue + 举例: + +``` +//如果OnlineDiscount这个规则的运算结果为true,则返回1,否则返回null。 +rule_value("OnlineDiscount", 1, null) +``` + +**get_first_notnull** + +- 范式:get_first_notnull (value1, value2, ..., valueN) +- 作用:表示返回参数里第一个不为null的值,参数区为可变长度,可实现优先级的结果获取 + +``` +Share10("分享超10笔"): rakeBackCount > 10 +Share100("分享超100笔"): rakeBackCount > 100 +Price("定价")= get_first_notnull(rule_value("Share100", 0.5, null), rule_value("Share10", 0.8, null)) +``` + +通过上面两个udf组合,可实现任意if-else组合 + +### 3.10 日期操作符(未实现) + +日期类型支持如下计算操作 + +| 符号表示 | 示例 | 含义 | 输入类型 | 输出类型 | +| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------- | -------- | +| + | date1 = to_date('20220212', 'yyMMdd') //将字符串转换成日期
date2 = to_date('5','d') //将字符串转换成日期
date3 = date1 + date2 //相加,等于20220217 | 增加日期 | date | date | +| - | date1 = to_date('20220212', 'yyMMdd')
date2 = to_date('5','d')
date3 = date1 - date2 //相加,等于20220207 | 减去日志 | date | date | + +#### 3.10.1 日期简化形式 + +由于返回date类型均需要to_date进行转换,为了简化描述,可按照如下格式简化日期初始化 + +> 日期/单位 + +示例1:初始化日期简化模式 + +```sql +1@d +1@h +1@M +20221011@yyMMdd +``` + +示例2:取当前时间的相对时间 +由于存在大量的近30天、近7天等表达需求,故简化now()获取当前时间,示例如下 + +```sql ++1@d +-1@d ++1@M +``` + +此外,还有其他日期函数作为补充 + +#### 3.10.2 now + +- 范式:now() +- 作用:日期计算函数,用户返回当前日期 + +#### 3.10.3 date_format + +- 范式:date_format(time, to_format)/date_format(time, from_format, to_format) +- 作用:日期格式化函数,将日期转换成为指定格式字符串,默认为yyyy-MM-dd HH:mm:ss / yyyyMMdd HH:mm:ss + 举例: + +``` +date1 = to_date('20220101', 'yyMMdd') +date_format(date1, 's') //转换成unix时间戳,值为 1640966400 +date_format(date1, 'yyMMdd hh:mm:ss') //转换成为指定格式,应当为 20220101 00:00:00 +``` + +## 4 基本语法 + +本章节使用场景进行语法介绍和应用 + +### 4.1 示例场景和需求 + +#### 4.1.1 示例schema + +假定schema如下 + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*hlQmQIuBBvwAAAAAAAAAAAAADtmcAQ/original#id=Ajy1t&originHeight=412&originWidth=930&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +**User属性** + +| 属性名 | 类型 | 说明 | +| ------ | -------- | -------- | +| id | string | 主键id | +| name | string | 姓名 | +| age | int | 年龄 | +| gender | 性别概念 | 性别属性 | + +**Shop属性** + +| 属性名 | 类型 | 说明 | +| -------- | -------- | ------------ | +| id | string | 主键id | +| name | string | 店铺名 | +| category | 分类概念 | 店铺经验分类 | + +**(User)-[pay]->(User) 用户向用户转账** + +| 属性名 | 类型 | 说明 | +| ------ | ----- | -------- | +| amount | float | 转账数目 | + +**(User)-[visit]->(Shop) 用户浏览过某个商店** + +| 属性名 | 类型 | 说明 | +| --------- | ---- | ---------------- | +| timestamp | int | 浏览商店的时间戳 | + +**(User)-[own]->(Shop) 用户拥有某个商店** + +无属性 + +**(User)-[consume]->(Shop) 用户消费过某个商店** + +| 属性名 | 类型 | 说明 | +| --------- | ----- | ------------ | +| amount | float | 消费金额 | +| timestamp | int | 消费的时间戳 | + +#### 4.1.2 需求列表 + +| 需求编号 | 需求描述 | +| -------- | --------------------------------------------------- | +| 1 | 判断一个User是否是店主 | +| 3 | 统计一个Shop近7天、30天被浏览的次数 | +| 4 | 根据Shop近7天的次数,分层高关注量Shop和低关注量Shop | +| 5 | 根据Shop的近7天销量,得到消费最高的top3用户 | +| 6 | 判断一个用户转账是否收大于支 | +| 7 | 判断一个用户是否自己给自己转账 | +| 8 | 得到一个用户最近7天转过账的其他用户 | +| 9 | 用户拥有自己的商店,且在自己商店消费 | +| 10 | 统计User近7天有消费或者浏览过的店铺数目 | +| 11 | 统计每个User在某一Shop的消费总额 | + +### 4.2 整体语法描述 + +逻辑规则采用三段式语法表示,其语法结构,如下: + +``` +#Structure:定义匹配的子图结构。 +Structure { + // path desciption +} +#Constraint:定义上述Struct中,对实体和关系的约束条件、以及规则计算的表达式。 +Constraint { + // rule express +} +#Action:指定了对符合Structure和Constraint的结果进行的后置处理。 +Action { + // action desciption +} +``` + +定义新的逻辑谓词的语法结构,如下: + +``` +#Define用于定义新的逻辑谓词。它允许您创建符合特殊Structure和Constraint限制的自定义谓词。 +Define (s:sType)-[p:pType]->(o:oType) { + Structure { + // path desciption + } + Constraint { + // rule express + } +} +``` + +下面的章节里,我们将对Structure、Constraint、Action、Define的用法进行详细介绍。 + +### 4.3 Structure定义 + +该部分结构主要描述路径 + +#### 4.3.1 路径定义 + +路径的基本单元是边,多种边组合起来的连通图成为路径,Structure中可以描述多个路径,方便在不同场景下使用 + +> 在线业务存在多种非连通图需求,离线批量计算场景较少 + +路径描述按照ISO GQL方式进行描述,即如下三种示例 + +``` +Structure { + (s:User)-[p:own]->(o:Shop) +} +``` + +``` +Structure { + (s:User)-[p:own]->(o:Shop), (s)-[c:consume]->(o) +} +``` + +> 注意:别名的类型定义只能在一处定义,通过逗号表示两个边都必须存在 + +``` +Structure { + (s:User)-[p:own|consume]->(o:Shop) +} +``` + +#### 4.3.2 路径别名 + +Structure中主要目的是简化路径描述,多数场景下,我们需要对路径的存在性进行判定,为方面后续的规则计算,我们使用路径别名作为Constraint的路径存在性判断参数,如下 + +``` +Structure { + path: (s:User)-[p:own]->(o:Shop) +} +``` + +> 当s这个用户拥有一家店铺时,path为true,否则为false + +``` +Structure { + path: (s:User)-[p:own]->(o:Shop), (s)-[c:consume]->(o) +} +``` + +> 当s这个用户拥有一家店铺,且在这个店铺进行了消费时,path为true,否则为false + +``` +Structure { + path: (s:User)-[p:own|consume]->(o:Shop) +} +``` + +> 当s这个用户没有在任何一家店铺消费,也不拥有任何一家店铺时,path为false,否者为true + +别名的优势在于可以简化path路径的描述,上述两个可以改成如下描述 + +``` +Structure { + ownPath: (s:User)-[p:own]->(o:Shop) + consumePath: (s)-[c:consume]->(o) +} +``` + +申明两个path + +- "用户拥有自己的商店,且在自己商店消费" 可以表达成为ownPath and consumePath +- "用户拥有自己的商店或者在商店消费" 可以表达成为ownPath or consumePath + +#### 4.3.3 路径运算符 + +路径定义时,可以要求Structure中路径不是必须存在,ISO GQL的路径表达中已经对且、或、可选、非做了表达,我们和ISO GQL保持一致 + +###### 1)路径且 + +``` +Structure { + path: (s:User)-[p:own]->(o:Shop), (s)-[c:consume]->(o) +} +``` + +###### 2)路径或 + +``` +Structure { + path: (s:User)-[p:own|consume]->(o:Shop) +} +``` + +###### 3)路径否(未实现) + +``` +Structure { + not path: (s:User)-[p:own]->(o:Shop) +} +``` + +###### 4)可选路径 + +``` +Structure { + (s:User)-[p:own]->?(o:Shop) +} +``` + +注意:在可选路径中,若对其中别名进行规则判定,则必须预先使用exists函数进行判断,否则会出现不可预期行为,例如 + +``` +Structure { + (s:User)-[p:own]->?(o:Shop) +} +Constraint { + // 需要预先对p边进行判断 + R1: exists(p) and o.product in ["bread", "card"] +} +``` + +``` +Structure { + (s:User)-[p:own]->?(o:Shop) +} +Constraint { + // 此时可选边逻辑和过滤规则冲突 + R1: o.product in ["bread", "card"] +} +``` + +###### 5)重复路径 + +``` +Structure { + (s:Company)-[p:hasShare]->{0,*}(o:Company) +} +``` + +参考ISO GQL语法,使用花括号的两种参数表达重复的范围,在ISO GQL之上,根据实际应用中的需求,配套总结了几类使用函数: + +- 计算类 + +(1)获取重复路径中的最后某个元素 (未实现) + +``` +Structure { +(s:Company)-[p:hasShare]->{0,\*}(o:Company) +} +Constraint { +// 获取最后一个公司 +lastCompany = p.nodes().get(-1) +// 获取第一个公司 +firstCompany = p.nodes().get(0) + + // 获取最后一个公司的控股比例 + lastShareRate = p.edges().get(-1).rate + +} +``` + +(2)数值计算(均值、极值、累加、str join等) (未实现) + +``` +Structure { + (s:Company)-[p:hasShare]->{0,*}(o:Company) +} +Constraint { + // 极值 + maxRate = p.edges().max(rate) + // 连乘 + realRate = e.edges().accumlate(rate, "*") +} +``` + +(3)reduce操作 + +``` +Structure { +(s:Company)-[p:hasShare]->{0,_}(o:Company) +} +Constraint { +// 极值 +maxRate = p.edges().reduce((res, cur) => rule_value(res > cur.rate, res, cur.rate), 0) +// 连乘 +realRate = e.edges().reduce((res, cur) => cur.rate _ res, 1) +} + +``` + +- 约束类 + +(1)重复路径节点/边间约束 + +``` +Structure { + (s:Company)-[p:hasShare]->{0,*}(o:Company) +} +Constraint { + R1("必须属于同类型公司"): p.nodes().constraint((pre,cur) => pre.type == cur.type) +} +``` + +(2)重复路径节点的常量约束 + +``` +Structure { +(s:Company)-[p:hasShare]->{0,\*}(o:Company) +} +Constraint { +R1("必须是上市公司"): p.nodes().constraint((pre,cur) => cur.type == "上市公司") +} + +``` + +(3)重复路径节点和外部常量约束 + +``` +Structure { + (u:Person)-[:hasShare]->(s:Company), + (s)-[p:hasShare]->{0,*}(o:Company) +} +Constraint { + R1("公司的重要人物中包含股东"): p.nodes().constraint((pre,cur) => contains(cur.management, u.name)) +} +``` + +- 结果处理 + +(1)只保留最长/最短的路径 + +``` +Structure { +(u:Person)-[:hasShare]->(s:Company), +(s)-[p:hasShare]->{0,\*}(o:Company) +} +Constraint { +R1("保留最长路径"): group(s,o).keep_longest_path(p) +} +``` + +(2)只保留最短的路径 + +``` +Structure { + (u:Person)-[:hasShare]->(s:Company), + (s)-[p:hasShare]->{0,*}(o:Company) +} +Constraint { + R1("保留最短路径"): group(s,o).keep_shortest_path(p) +} +``` + +###### 6)函数边 + +``` +Structure { + (s:Park)-[e:nearby(s.boundary, o.center, 10.1)]->(o:Subway) +} +``` + +支持-[]-中支持函数传参申明,进行任意关联 + +### 4.4 Constraint语法 + +#### 4.4.1 单规则语法 + +Constraint中每一行作为一个规则,规则分为如下几类 + +- **逻辑规则** + +以 **规则英文名("规则说明"): 表达式** 这种格式进行表达,输出为布尔值。常用运算符有>、<、==、>=、<=、!=、+、-、\*、/、%等,运算符可以进行扩展。 + +- **计算规则** + +以 **规则英文名("规则说明")= 表达式** 进行表达,输出结果为数字或者文本,取决于表达式内容。 + +- **赋值规则** + +以 **别名.属性名=** **表达式** 没有规则名,仅允许Define中定义的别名进行属性赋值表达。此类规则仅在特定谓词的规则定义中有效。 +以4.3中ownPath和consumePath为例 + +``` +Structure { + ownPath: (s:User)-[p:own]->(o:Shop) + consumePath: (s)-[c:consume]->(o) +} +Constraint { +} +``` + +``` +Structure { + optional ownPath: (s:User)-[p:own]->(o:Shop) + optional consumePath: (s)-[c:consume]->(o) +} +Constraint { + ownAndConsumeUser("用户拥有自己的商店或者在商店消费"): exist(ownPath) or exist(consumePath) +} +``` + +``` +Structure { + (s:Shop)<-[p:visit]-(o:User) +} +Constraint { + R1("7天内是否访问") : p.timestamp >= -7@d +} +``` + +> s可能存在很多个user访问,但是我们只处理7天内的边,第五行中不满足的o会被终止 + +#### 4.4.2 规则组语法 + +规则组可以将逻辑规则进行组合,主要目的是将逻辑计算层次化 + +``` +Structure { + (s:User) +} +Constraint { + R1("成年人"): s.age > 18 + + R2("男性"): s.gender == "男" + + // 下面这句是正确的,R3由R1和R2组合而成,就被视为是一条规则组 + R3("成年男性"): R1 and R2 + + // 下面这句是错误的,规则组里不允许有非规则的变量 + R3("成年男性"): R1 and s.gender == "男" +} +``` + +#### 4.4.3 聚合语法 + +支持聚合的算子有如下特性和限制 + +- 算子的输入必须为list类型 +- group语句可以将图进行分组,将若干条相同模式的路径进行聚合分组,当使用group时,聚合算子对整个图分组的点边进行聚合操作 +- 聚合算子只能针对以一个起点产生的子图进行聚合计算,**若需要一批起点产生的子图进行计算,则不在该文档支持范围内** + +由于需求中存在多种聚合类需求,4.1.2中存在统计需求列表如下 + +| 需求编号 | 需求描述 | +| -------- | --------------------------------------------------- | +| 3 | 统计一个Shop近7天、30天被浏览的次数 | +| 4 | 根据Shop近7天的次数,分层高关注量Shop和低关注量Shop | +| 5 | 根据Shop的近7天销量,得到消费最高的top3用户 | +| 6 | 判断一个用户转账是否收大于支 | +| 10 | 统计User近7天有消费或者浏览过的店铺数目 | +| 11 | 统计每个User在某一Shop的消费总额 | + +**示例1:需求10、需求3、需求4** + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*FROwTLCV4HcAAAAAAAAAAAAADtmcAQ/original#id=yzghA&originHeight=506&originWidth=926&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +假设当前时间为2023.1.10日,那么Alice近7天有消费或者浏览过的店铺数目应当为2,语法表达如下 + +``` +Structure { + (s:User)-[p:visit|consume]->(o:Shop) +} +Constraint { + R1("近7天内有访问或消费") : p.timestamp >= -7@d + // 忽略group(s)场景 + visitOrConsumeShopNum("统计User近7天有消费或者浏览过的店铺数目") = count(o) + + //显示表达 + visitOrConsumeShopNum("统计User近7天有消费或者浏览过的店铺数目") = group(s).count(o) +} +``` + +**示例2:判断用户是否收大于支** + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*kJMiRbdw0BYAAAAAAAAAAAAADtmcAQ/original#id=DDwGS&originHeight=322&originWidth=852&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +上图中,Jobs、Alice、Mike属于收大于支,Bob属于支大于收,那么规则如下表示 + +``` +Structure { + outPath: (s:User)-[outP:pay]->(outU:User) + inPath: (inU:User)-[inP:pay]->(s) +} +Constraint { + // inPath不存在,则返回0,否则进行聚合计算 + inAmount("收入") = rule_value(inPath, group(s).sum(inP.amount), 0) + // outPath不存在,则返回0,否则进行聚合计算 + outAmount("支出") = rule_value(outPath, group(s).sum(outP.amount), 0) + + R2("收大于支"): inAmount > outAmount +} +``` + +**示例3:根据Shop的近7天销量,得到消费最高的top3用户(未实现)** + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*e6ijSZWfOUEAAAAAAAAAAAAADtmcAQ/original#id=GHpMU&originHeight=518&originWidth=602&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +上面数据实例中,top3为:Jobs、Mike、Alice + +``` +Structure { + (s:Shop)<-[p:consume]-(o:User) +} +Constraint { + R1("7天内消费"): p.timestamp >= -7@d + R2("top3的用户"): group(s).desc(p.amount).limit(3) //注意,此时只会保留Jobs、Mike、Alice节点 +} +``` + +**示例4:统计每个User在某一Shop的销售量** +假定数据如下 + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*PsPlR40fUAEAAAAAAAAAAAAADtmcAQ/original#id=k3OML&originHeight=546&originWidth=658&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +该里需要统计Shop近7天浏览的次数,值得注意的是,Bob存在2条边,这两个需要进行聚合统计 + +``` +Structure { + (s:Shop)<-[p:consume]-(o:User) +} +Constraint { + R("7天内消费"): p.timestamp >= -7@d + // 用户消费总额上赋值 + userConsumeAmount('店铺销售总额') = group(s,o).sum(p.amount) //注意,此时会输出4个结果 +} +Action { + get(s.name, userConsumeAmount) +} +``` + +### 4.5 Define谓词规则语法 + +前面几个章节主要目的为路径、规则描述,本节主要目标是对谓词进行定义。谓词主要分为三个场景进行表达 + +- 实体类型和概念的归纳语义定义 +- 实体类型之间的逻辑谓词定义 +- 实体类型和基本类型之间的逻辑谓词定义 + +#### 4.5.1 实体类型和概念的归纳语义定义 + +关于实体和概念的介绍可以参见 [schema建模手册中的说明](./spgschema_tutorial.md)。 +归纳语义(Induction),是指从一类有共同特征的实体中得出对这些实体概括性的概念,这种个体和概念之间的关系就是归纳关系。 +实体类型和概念间的归纳语义,通过语法规则表达如下: + +``` +Define (s:TypeA)-[p:TypeP]->(o:TaxonomyOfTypeA/ConceptA) { + Structure { + // path desciption + } + Constraint { + // rule express + } +} +``` + +ConceptA是属于TaxonomyOfTypeA类型,上述规则表达含义为,TypeA类型的s在满足上述规则表达的前提下,可以通过TypeP谓词链接到ConceptA概念上。下面以示例举例: +根据4.1.2的需求,我们可以将如下需求转化成为概念进行定义 + +| 需求编号 | 需求描述 | +| -------- | --------------------------------------------------- | +| 1 | 判断一个User是否是店主 | +| 4 | 根据Shop近7天的次数,分层高关注量Shop和低关注量Shop | +| 7 | 判断一个用户是否自己给自己转账 | + +示例1:判断一个User是否是店主 +判断是否是店主主要看名下是否有店铺 +假定已经按照概念建模创建了ShopKeeper概念,如下 + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*LY8cSqBhYF8AAAAAAAAAAAAADtmcAQ/original#id=ej8hQ&originHeight=566&originWidth=1062&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +从实例图上可以看出,bob没有店不属于ShopKeeper,Alice有一个Hotel,所以应该属于ShopKeeper,我们可以通过语法将Alice归纳为ShopKeeper用户类别 + +``` +Define (s:User)-[p:belongTo]->(o:TaxonomyOfUser/ShopKeeper) { + Structure { + path: (s)-[ownP:own]->(shop:Shop) + } + Constraint { + R1("拥有店铺"): path + } +} +``` + +通过如上规则,则可以将概念和实体实例建立挂载关系 +示例2:根据Shop近7天的次数,分层高关注量Shop和低关注量Shop + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*RvHESr9YexsAAAAAAAAAAAAADtmcAQ/original#id=x8GOd&originHeight=578&originWidth=1040&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +上图中Hotel被访问的较多,Drug Store访问很少,我们需要按照业务要求将他们和PopularShop和NamelessShop分别挂载上 + +``` +Define (s:Shop)-[p:belongTo]->(o:TaxonomyOfShop/PopularShop) { + Structure { + path: (s)<-[vP:visit]-(u:User) + } + Constraint { + R1("7天内消费"): vP.timestamp >= -7@d + // 当路径不存在时,浏览次数为0,否则对u进行统计 + visitsNum("浏览次数") = rule_value(path, group(s).count(u),0) + R2("热点商户"): visitsNum > ${hot_shop_threashold} + } +} +``` + +> 注:${hot_shop_threashold} 为阈值参数,需要在谓词使用时将具体值填入 + +``` +Define (s:Shop)-[p:belongTo]->(o:TaxonomyOfShop/NamelessShop) { + Structure { + path: (s)<-[vP:visit]-(u:User) + } + Constraint { + R1("7天内消费"): vP.timestamp >= -7@d + // 当路径不存在时,浏览次数为0,否则对u进行统计 + visitsNum("浏览次数") = rule_value(path, group(s).count(u),0) + R2("低关注量商户"): visitsNum < ${nameless_shop_threashold} + } +} +``` + +> 注:${nameless_shop_threashold} 为阈值参数,需要在谓词使用时将具体值填入 + +#### 4.5.2 实体类型之间逻辑谓词定义 + +可使用类型之间定义需求如下 + +| 需求编号 | 需求描述 | +| -------- | ----------------------------------- | +| 7 | 判断一个用户是否自己给自己转账 | +| 8 | 得到一个用户最近7天转过账的其他用户 | +| 11 | 统计每个User在某一Shop的销售量 | + +基本定义和4.5.1中基本一致,按照需求新增schema + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*3c--RL7N-9gAAAAAAAAAAAAADtmcAQ/original#id=WOoBU&originHeight=522&originWidth=788&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +要三种 + +- (s:User)-[p:transSelf]->(s) 自己向自己转账 +- (s:User)-[p:trans7Days]->(o:User) 7天内有转账的用户 +- (s:Shop)-[p:consumeAmount]->(o:User) 商铺某个用户的销售额 + +示例1:判断一个用户是否自己给自己转账 + +``` +Define (s:User)-[p:transSelf]->(s) { + Structure { + path: (s)-[pp:pay]->(s) + } + Constraint { + R1("自己向自己转账"): path + } +} +``` + +示例2:7天内有转账的用户 + +``` +Define (s:User)-[p:trans7Days]->(o:User) { + Structure { + path: (s)-[pp:pay]->(o) + } + Constraint { + R1("7天内消费"): p.timestamp > -7@d + R2("存在转账"): path + } +} +``` + +示例3:商铺对一个用户的销售总额 + +``` +Define (s:Shop)-[p:consumeAmount]->(o:User) { + Structure { + path: (s)<-[cp:consume]-(o) + } + Constraint { + R1("存在交易用户"): path + p.amount = group(s,o).sum(cp.amount) //统计所有的交易额 + } +} +``` + +#### 4.5.3 实体类型和基本类型之间逻辑谓词定义 + +前两章节主要是和实体类型和概念之间语义链接,实际上存在部分需求,和任何其他类型没有交互,例如如下需求 + +| 需求编号 | 需求描述 | +| -------- | --------------------------------------- | +| 1 | 判断一个User是否是店主 | +| 3 | 统计一个Shop近7天、30天被浏览的次数 | +| 6 | 判断一个用户转账是否收大于支 | +| 7 | 判断一个用户是否自己给自己转账 | +| 9 | 用户拥有自己的商店,且在自己商店消费 | +| 10 | 统计User近7天有消费或者浏览过的店铺数目 | + +如上需求,我们可以增加User属性 + +| 属性名 | 类型 | 说明 | +| -------------------------- | ------- | ------------------------------- | +| isShopOwner | boolean | 是否是店主 | +| isIncomeLargeOutcome | boolean | 是否收大于支 | +| 7daysVisitOrConsumeShopNum | int | 近7天有消费或者浏览过的店铺数目 | + +Shop可以增加属性 + +| 属性名 | 类型 | 说明 | +| -------------- | ---- | -------------- | +| 7daysVisitNum | int | 近7天浏览人数 | +| 30daysVisitNum | int | 近30天浏览人数 | + +这些新增属性,可通过规则进行定义,避免出现实际的数据新导入 +示例1:近7天有消费或者浏览过的店铺数目 + +``` +Define (s:User)-[p:7daysVisitOrConsumeShopNum]->(o:int) { + Structure { + path: (s)-[vc:visit|consume]->(shop:Shop) + } + Constraint { + R("7天内消费or浏览"): p.timestamp > -7@d + o = group(s).count(shop) //赋值 + } +} +``` + +示例2:近7天浏览店铺的用户 + +``` +Define (s:Shop)-[p:7daysVisitNum]->(o:int) { + Structure { + path: (s)<-[p:visit]-(u:User) + } + Constraint { + R("7天内浏览"): p.timestamp > -7@d + o = group(s).count(u) //赋值 + } +} +``` + +示例3:近30天浏览店铺的用户 + +``` +Define (s:Shop)-[p:30daysVisitNum]->(o:int) { + Structure { + path: (s)<-[p:visit]-(u:User) + } + Constraint { + R("30天内浏览"): p.timestamp > -30@d + o = group(s).count(u) //赋值 + } +} +``` + +### 4.6 Action语法 + +Action中支持多种操作: + +- createNodeInstance/createEdgeInstance:用于因果的逻辑结果的语义表达 +- get :输出匹配的结果,包括实体、关系以及属性等内容。 + +具体用法如下面的例子展示: + +#### 4.6.1 Causal logic semantics + +在事理图谱中,因果关系基本需要在满足一定条件才成立,本例引用SPG白皮书中金融事理图谱章节的案例进行表述。事理描述如下图: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*Kt8mQpZQpfYAAAAAAAAAAAAADtmcAQ/original#id=bXbqU&originHeight=2051&originWidth=1903&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=) + +#### 4.6.1.1 createNodeInstance + +当概念之间满足因果语义的条件时,createNodeInstance将创建一个新的实例。本例中创建新的事件实例,具体使用方式如下: + +``` +Define (s: `ProductChain.TaxonomyOfCompanyAccident`/`周期性行业头部上市公司停产事故`)-[p: leadTo]->(o: `ProductChain.TaxonomyOfIndustryInfluence`/`成本上升`) { + Structure { + (s)-[:subject]->(c:ProductChain.Company) + (c)-[:belongIndustry]->(d:ProductChain.Industry) + (d)-[:downstream]->(down:ProductChain.Industry) + } + Constraint { + // 这里没有定义约束条件 + } + Action { + downEvent = createNodeInstance( + type=ProductChain.IndustryInfluence, + value={ + subject=down.id + objectWho="上升" + influenceDegree="上升" + indexTag="成本" + } + ) + } +} +``` + +**createNodeInstance参数说明:** + +- type:这里需要指定我们创建一个什么样的实体类型实例 +- + +value:此处为实例的具体属性值,由kv对构成,k为schema中定义的属性名,v为具体值,可为常量,也可以为Structure和Constraint中的各种变量。注意:若k不存在于schema中或者值不满足schema定义,则为非法值 +**返回值:** + +- 具体实例别名,不能和Structure、Constraint中的变量重合 + +本例中创建一个新的事件实例downEvent,该事件类型为ProductChain.IndustryInfluence,主体为Structure中的down实体,属性代表该产业成本上升 + +##### 4.6.1.2 createEdgeInstance + +也可以通过createEdgeInstance创建一条新的关系,可将触发的事件实例和具有因果关系的事件实例进行关联。具体使用方式如下: + +``` +Define (s: `ProductChain.TaxonomyOfCompanyAccident`/`周期性行业头部上市公司停产事故`)-[p: leadTo]->(o: `ProductChain.TaxonomyOfIndustryInfluence`/`成本上升`) { + Structure { + (s)-[:subject]->(c:ProductChain.Company)-[:belongIndustry]->(d:ProductChain.Industry)-[:downstream]->(down:ProductChain.Industry) + } + Constraint { + + } + Action { + downEvent = createNodeInstance( + type=ProductChain.IndustryInfluence, + value={ + subject=down.id + objectWho="上升" + influenceDegree="上升" + indexTag="成本" + } + ) + #在事件s和新生成的downEvent建立一条leadTo边,代表一个事件实例导致了另外一个事件实例 + createEdgeInstance( + src=s, + dst=downEvent, + type=leadTo, + value={} + ) + } +} +``` + +**createEdgeInstance参数说明:** + +- type:指定边类型 +- src:起点的别名,必须存在Structure中,或者是Action中通过createNodeInstance创建的实例 +- dst:终点的别名,同样满足src的约束 +- value:边的属性值,也为kv对,可为空 + +**返回值:** + +- 无,主要原因为,在Action中,边实例不会被再次引用 + +#### 4.6.2 数据输出 + +get的作用是获取Structure或者Constraint中的实体、关系、属性或者临时变量,具体使用方式如下: + +``` +//可通过get获取Structure或者Constraint中的属性或者临时变量 + +Structure { + path: (s:Shop)<-[vP:visit]-(u:User) +} +Constraint { + R("7天内消费"): vP.timestamp >= -7@d + visitsNum("浏览次数") = rule_value(path, group(s).count(u),0) + R2("热点商铺"): visitsNum > 1000 +} +Action { + // 获取shop和user点的id,并且返回Constraint中的visitsNum变量 + get(s.id, u.id, visitsNum) +} +``` diff --git a/docs/tutorial/reasoner/rdg.md b/docs/tutorial/reasoner/rdg.md new file mode 100644 index 0000000..aa18d25 --- /dev/null +++ b/docs/tutorial/reasoner/rdg.md @@ -0,0 +1,137 @@ +--- +title: 扩展RDG +order: 2 +--- + +## 1. RDG简介 + +RDG(Resilient Distributed Graph)的设计灵感来源于Apache Spark的核心编程抽象——弹性分布式数据集RDD(Resilient Distributed +Dataset)。RDG模型继承了RDD的设计理念,通过将复杂的图操作抽象化为一系列标准算子(如Map、Filter、ReduceByKey等)来简化图数据操作的表达和处理。 +RDG是一个高级的图计算抽象层,旨在提供一个统一的接口以对接多种物理执行引擎。通过适配RDG接口,可以将LPG转化为SPG,极大的提高已有系统的应用价值。 + +![image.png](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*ZUG-S7YafPsAAAAAAAAAAAAADtmcAQ/original) + +## 2. 适配方法 + +将OpenSPG适配到一个新的物理引擎,最主要的工作是实现自己的RDG类。下面我先介绍RDG的核心接口: + +### 1. RDG接口 + +RDG接口代码可参考[链接](https://github.com/OpenSPG/openspg/blob/master/reasoner/lube-physical/src/main/scala/com/antgroup/kg/reasoner/lube/physical/rdg/RDG.scala) +,接口说明如下: + +| 接口 | 参数 | 说明 | +| ---------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------- | +| patternScan | pattern: Pattern | 通过给定起点,匹配图结构。其中Pattern可以是点、边也可以是一度子图。 | +| expandInto | target: PatternElement | +| pattern: Pattern | 扩展当前图中间结果。target是当前图结构与待匹配结构中重叠的点 | +| filter | expr: Rule | 按照Rule表达式过滤图中间结果 | +| groupBy | by: List[Var] | +| aggregations: Map[Var, Aggregator] | 聚合计算。 | +| 与表数据聚合不同,图数据聚合需要安装子图结构进行点边去重。 | +| addFields | fields: Map[Var, Expr] | 计算表达式,将结果添加到图中 | +| dropFields | fields: Set[Var] | 去除不需要的数据和属性,以降低中间结果大小 | +| orderBy | groupKey: List[Var] | + +sortItems: List[SortItem] +limit: Int | 聚合后排序并截断 | +| linkedExpand | pattern: EdgePattern[LinkedPatternConnection] | 逻辑链接。 +在推理过程中,某些边是通过逻辑计算或模型预测得到的。该接口常用于时空推理和概念树挂载中。 | +| cache | 无 | 缓存当前RDG数据。 +一般用于挂起当前推理任务,进入子推理任务时。 | +| join | other: RDG +joinType: JoinType +onAlias: List[(String, String)] +lhsSchemaMapping: Map[Var, Var] +rhsSchemaMapping: Map[Var, Var] | 将两个RDG结果join起来。 +需要支持多种JoinType,同时由于命名空间重叠的可能,还需要对别名进行映射。 | +| ddl | ddlOps: List[DDLOp] | 产出新的知识,包括点,边和属性。 | +| select | cols: List[Var], as: List[String] | 从子图结构中选择感兴趣的信息,输出为表数据结构。 | +| fold和unfold | mapping: List[(RichVar, List[Var]) | 用于repeat场景中,将repeat边进行合并与展开。 | + +### 2. PropertyGraph和LocalReasonerSession + +当你完成了RDG代码的实现后,可以通过PropertyGraph类创建RDG,并通过LocalReasonerSession关联到自己的图存储。你需要实现的接口如下: + +| 接口 | 参数 | 说明 | +| --------------------------- | ------------------------------------ | ------------------------------------------ | +| KGReasonerSession.loadGraph | graphLoaderConfig: GraphLoaderConfig | 加载属性图,返回PropertyGraph | +| PropertyGraph.createRDG | alias: String | +| types: Set[String] | 给定推理起点类型,创建RDG | +| PropertyGraph.createRDG | alias: String, rdg: RDG | 给定RDG以及起点别名,创建一个子推理任务RDG | + +## LocalRunner示例 + +LocalRunner是一个在单机上实现的,具备完整推理能力的引擎。其代码已经开源,具体可以参考: + +| 模块 | 实现链接 | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| LocalRDG | [https://github.com/OpenSPG/openspg/blob/master/reasoner/runner/local-runner/src/main/java/com/antgroup/openspg/reasoner/runner/local/rdg/LocalRDG.java](https://github.com/OpenSPG/openspg/blob/master/reasoner/runner/local-runner/src/main/java/com/antgroup/openspg/reasoner/runner/local/rdg/LocalRDG.java) | +| LocalPropertyGraph | [https://github.com/OpenSPG/openspg/blob/master/reasoner/runner/local-runner/src/main/java/com/antgroup/openspg/reasoner/runner/local/impl/LocalPropertyGraph.java](https://github.com/OpenSPG/openspg/blob/master/reasoner/runner/local-runner/src/main/java/com/antgroup/openspg/reasoner/runner/local/impl/LocalPropertyGraph.java) | +| LocalReasonerSession | [https://github.com/OpenSPG/openspg/blob/master/reasoner/runner/local-runner/src/main/java/com/antgroup/openspg/reasoner/runner/local/impl/LocalReasonerSession.java](https://github.com/OpenSPG/openspg/blob/master/reasoner/runner/local-runner/src/main/java/com/antgroup/openspg/reasoner/runner/local/impl/LocalReasonerSession.java) | + +## 基于Spark适配RDG(伪代码) + +在Spark的适配中,我们像GraphX一样,使用RDD存储图数据的方案。推理中间结果,我们也使用RDD存储。下面写一些伪代码来展示适配过程。 + +### 1. SparkRDG + +``` +public class SparkRDG extends RDG { + private SparkContext sc; + private RDD rstRdd; + private RDD startIds; + + public SparkRDG(SparkContext sc, RDD ids) { + this.sc = sc; + this.startIds = ids; + } + + @Override + public SparkRDG patternScan(Pattern pattern) { + rstRdd = startIds.map( + // 实现map,匹配对应的pattern + ) + } + + /** + * expand graph pattern + */ + @Override + public SparkRDG expandInto(PatternElement target, Pattern pattern) { + rstRdd = rstRdd.keyBy( + // 将之前匹配的子图,keyBy target,以便进行下一轮匹配 + ).map( + // 实现匹配函数,完成pattern的匹配 + ) + } +} +``` + +### 2. SparkPropertyGraph + +``` +public class SparkPropertyGraph implements PropertyGraph { + private SparkContext sc; + private RDD graph; //VertexGraph是一个自定义的图结构表示 + + public class SparkPropertyGraph(SparkContext sc) { + this.sc = sc; + } + + public void loadGraph(Map config) { + this.graph = ... //图加载,可以考虑像graphx一样,使用RDD存储图 + this.graph.persist(); + } + + @Override + public SparkRDG createRDG(Set types) { + // 从graph中寻找类型为types的点构造RDG + } + + @Override + public SparkRDG createRDG(Object id, String alias) { + // 从其他RDG中寻找某个alias作为起点,构造RDG + } +} +``` diff --git a/docs/tutorial/schema/best_practice.md b/docs/tutorial/schema/best_practice.md new file mode 100644 index 0000000..0cdc74d --- /dev/null +++ b/docs/tutorial/schema/best_practice.md @@ -0,0 +1,208 @@ +--- +title: 建模最佳实践 +order: 2 +nav: + second: + order: 2 + title: 知识建模 +--- + +为了方便大家更好地理解和应用OpenSPG构建知识图谱,我们从SPGSchema建模的最佳实践中总结出了7个原则,每个原则都搭配了相关示例进行说明。期望通过这些原则能解决大家在建模时的疑惑,能充分发挥OpenSPG高效且强大的知识表达能力。 + +# 1、类型选择原则 + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*l1nISolnG3AAAAAAAAAAAAAADtmcAQ/original) + +### 原则1:复杂多元结构用实体类型或事件类型 + +:::info +**解释:**当一个事物需要丰富的属性来进行描述,比如某个“公司”,不光是只用一个名称,还要借助经营范围、企业证件号码、注册地址等信息来描述,就适合使用实体类型进行建模。 +::: + +![image.png](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*3GntTLnB4f4AAAAAAAAAAAAADtmcAQ/original) + +### 原则2:扁平化的分类标签用概念类型 + +:::info +**解释:**当目标建模对象仅仅是语言或文本意义上的分类标签,重点表达标签之间的上位、包含、因果等关系时,适合使用概念类型来进行建模,比如“矿石开采”、“矿石冶炼”这类表达对产业分类的标签。概念和实体、事件的区别是: + +1. **多元结构 vs 一元结构: ** 如公司类型,有所在行政区域、所属行业、经营产品类目等多元属性定义,每个属性关联一个具体的概念类型。 + 而行政区域、行业类目、产品类目都是一元结构的概念分类,重点表达的是类目之间的上下位关系。 +2. **同名建模冲突处理机制:** + 比如杭州市,若描述其特产、人口、区号等信息时应使用实体类型进行建模,但同时杭州市也可以作为一个行政区划的角度来使用,主要说明杭州市位于浙江省,浙江省又位于中国,此时杭州市、浙江省、中国都属于行政区划的一个实例,它们重点表达相互间的地理上的位于关系。 + 这个时候,建议创建一个实体类型的同时又再创建一个概念类型,杭州市作为这两种类型的实例分别导入。 + ::: + 以如下Schema为例: + +```yaml +namespace CKG + +AdministractiveArea(行政区划): ConceptType + hypernymPredicate: locateAt + +... ... + +City(城市): EntityType + properties: + localProducts(特产): Product + constraint: MultiValue + population(人口数量): Integer + areaCode(区号): Text + region(行政区划): AdministractiveArea +``` + +``` +“City(城市)”的实体: +{ + "id": "hz", + "name": "杭州市", + "areaCode": "0571", + "region": "中国-浙江省-杭州市" +} + +“AdministractiveArea(行政区划)”的概念: + 中国 <-locateAt- 浙江省 <-locateAt- 杭州市 +``` + +### 原则3:实体/事件多类型使用动态分类原则 + +:::info +**解释:** +客观世界对同一个事物会有多种分类,比如表示商铺的分类有:超市、便利店、加油站、洗车店等,如果按照这些不同分类视角分别进行实体类型建模,会造成图谱的类型爆炸,在数据构建和推理应用上会变得非常麻烦。SPG使用类型定义 + +分类概念的方式实现多分类来解决这个问题。 +::: +以商铺为例子。建模时先创建一个“商铺”的实体类型,然后再为这个实体类型创建一个“商铺分类”的概念类型用于分类。注意定义上要把概念类型放到实体类型的前面: + +```yaml +namespace World + +TaxonomyOfShop(商铺分类): ConceptType + hypernymPredicate: isA + +Shop(商铺): EntityType + desc: 诸如便利店和加油站等 + properties: + locateArea(所在区域): AdministractiveArea + address(地址): Text + contactPhone(联系电话): STD.ChinaTelCode + IND#belongTo(属于): TaxonomyOfShop +``` + +然后准备“商铺分类”的概念数据作导入用: + +``` +超市 +便利店 +加油站 +洗车店 +``` + +导入“Shop(商铺)”的实例数据的时候,如下所示: + +``` +id,name,locateArea,address,contactPhone,belongTo + +1,罗森便利店,杭州市,浙江省杭州市西湖区西溪路588号1层,0571-85801525,便利店 + +2,中国石化(杭州古荡加油站),杭州市,浙江省杭州市西湖区天目山路331号,0571-85220839,加油站 +``` + +通过belongTo属性的值会自动产生从实例到概念的边,实现对实体的分类。 + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*grXfT7FsX-0AAAAAAAAAAAAADtmcAQ/original) + +在查询时,我们可以直接用概念类型“TaxonomyOfShop”指代类型“Shop”,实现按概念召回对应的实体。实现跟使用类型分别建模的效果,但是大幅简化数据的查询和维护。 + +```graphql +MATCH (s:`TaxonomyOfShop`/`便利店`) RETURN s +# 返回id为1的"罗森便利店"实体 + +MATCH (s:`TaxonomyOfShop`/`加油站`) RETURN s +# 返回id为1的"中国石化(杭州古荡加油站)"实体 +``` + +另外,SPGSchema也支持以规则作为概念挂载依据,即通过规则对实体的属性进行运算得出要挂载的概念名称。 +比如我们可以通过定义如下的概念规则,根据实体的名称里的关键词自动分类: + +```graphql +Define (s:Shop)-[p:belongTo]->(o:`TaxonomyOfShop`/`便利店`) { + Structure { + (s) + } + Constraint { + R1("是便利店"): s.name like "%便利店%" + } +} + +Define (s:Shop)-[p:belongTo]->(o:`TaxonomyOfShop`/`加油站`) { + Structure { + (s) + } + Constraint { + R1("是加油站"): s.name like "%加油站%" + } +} +``` + +导入“Shop(商铺)”的实例数据的时候,不再导入belongTo属性: + +``` +id,name,city,address,contactPhone + +1,罗森便利店,杭州市,浙江省杭州市西湖区西溪路588号1层,0571-85801525 + +2,中国石化(杭州古荡加油站),杭州市,浙江省杭州市西湖区天目山路331号,0571-85220839 +``` + +数据导入完成后,查询可得到belongTo属性的值和关系: + +```graphql +MATCH (s:Shop WHERE id="1")-[p:belongTo]-(o) RETURN s,p,o +``` + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*w9dWT7gk7FMAAAAAAAAAAAAADtmcAQ/original) + +:::warning +**注意:** +如果概念定义了归纳规则,但在数据导入时又导入了belongTo属性值,会以规则运算结果优先。 +::: + +### **原则4:概念类型不继承** + +:::info +**解释:**概念类型的父类有且只有Thing,不能继承其他的类型。因为概念本身默认会有上下位关系,就隐含继承的语义。如果概念类型再继承,在语义上就会有冲突。 +::: + +# 2、属性/关系的选择原则 + +### 原则5:关系的指向遵守由动到静原则,反之被禁止 + +:::info +**解释:**事件类型可指向任意类型,实体类型不可指向事件类型,概念类型只能指向概念类型,反之被禁止。 + +1. **事件允许指向任意类型: **事件都是独立发生的行为动作,实体/概念/标准类型是被动跟发生的事件关联,所以是事件类型指向任意类型,反之禁止。 +2. **实体禁止指向事件类型:**实体都是多元要素的复杂对象,它可以和其他实体主动产生关联,但概念类型、标准类型只能做为实体的属性类型。因为实体本身不自动产生事件,所以实体禁止直接建立和事件的关系。 +3. **概念禁止指向多元类型:**概念是扁平化的分类标签,它抽象描述一类共同特征的实体集合,所以它只能被事件或实体用作属性类型的定义,反之禁止。 + ::: + +![](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*OyptTbv1_FMAAAAAAAAAAAAADtmcAQ/original) + +### 原则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:属性尽量标准化(推荐但不强制约束) + +:::info +**解释:** +尽可能的使用概念类型、标准类型和实体类型对属性进行标准化。因为SPGSchema会自动根据属性生成等价的关系,简化关系的创建和数据维护。尤其在变更实体的属性值后,关系实例会自动根据属性值同步,让属性等价的关系始终保持跟属性值的描述一致。SPGSchema建议尽量使用属性来替代关系的创建,只有确实需要在关系上配置属性,或者定义逻辑关系的时候再使用关系的创建。 +::: +![image.png](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*A3GfQJSXCv8AAAAAAAAAAAAADtmcAQ/original) diff --git a/docs/tutorial/schema/dsl.md b/docs/tutorial/schema/dsl.md new file mode 100644 index 0000000..3f95f13 --- /dev/null +++ b/docs/tutorial/schema/dsl.md @@ -0,0 +1,301 @@ +--- +title: 声明式Schema +order: 1 +--- + +在声明式Schema里不定义算子,算子由KNext的发布来绑定(算子开发参考[KNext教程](../knext/operator.md))。 + +### 关键字 + +``` +namespace + +EntityType, ConceptType, EventType, ->, STD.*, Text, Float, Integer + +desc, constraint, value, properties, relations, rule, hypernymPredicatem, autoRelate, spreadable, regular + +NotNull, MultiValue, Enum, Regular +``` + +> -> 用于表达类型的继承关系,A -> B +> STD.\*表示以STD.开头的都是预留关键字,作标准类型名称 + +### 基础句法 + +类似YAML,以缩进作为作用域的表示。缩进建议使用4个空格(Tab符会被当做两个空格) + +- **A(B): C** + - A为类型/属性的英文名 + - B为类型/属性的中文名称 + - C为取值 +- **A(B)->P** + - A为类型的英文名 + - B为类型的中文名称 + - P为要继承自的父类型 +- **namespace A** + - A表示项目前缀,在Schema文件的第一行必须出现。项目前缀会在Schema提交的时候自动拼接到实体类型名称的前面 +- **[[...]]** + - 规则脚本的定界符,仅用于rule的定义,类似于Python的"""用法 + +声明式Schema脚本采用逐行解析的方式,定义上要遵循顺序原则,即父类型要在子类型之前定义、属性上使用的类型也需要在其所属类型定义之前先定义好。 + +### 语法结构 + +总共分类6层缩进,按缩进的多少依次为: + +- 第一层(无缩进):定义类型、命名空间 +- 第二层:定义类型的元信息,比如描述、属性、关系等 +- 第三层:定义属性/关系的名称和类型 +- 第四层:定义属性/关系的元信息,比如约束、子属性、逻辑规则等 +- 第五层:定义子属性的名称和类型 +- 第六层:定义子属性的元信息,比如描述、约束 + +```yaml +namespace DEFAULT + +TypeA(实体类型A): EntityType + desc: 实体类型描述 + properties: + property1(属性1): STD.ChinaMobile + desc: 属性1的描述 + constraint: NotNull, MultiValue + properties: + property2(属性1的子属性): Text + desc: 属性1的子属性,枚举约束 + constraint: NotNull, Enum="A,B,C" + property3(属性1的子属性): Text + desc: 属性1的子属性,正则约束 + constraint: Regular="^abc[0-9]+$" + property4(属性4): Text + rule: [ [ + Define property4... + ] ] + relations: + relation1(关系1): TypeA + desc: 关系1的描述 + properties: + confidence(置信度): Float + rule: [ [ + Define relation1... + ] ] + +TypeB(实体类型B) -> TypeA: + desc: 这是实体类型A的子类型 +``` + +#### 定义实体类型 + +```yaml +# 以下定义一个公司的实体类型 +Company(公司): EntityType + +# 以下是定义一个继承自公司的实体类型 +ListedCompany(上市公司) -> Company: +``` + +##### 定义属性和关系 + +```yaml +Company(公司): EntityType + # 这里是公司的描述 + desc: 公司的描述 + properties: + # 这里定义属性 + address(地址): Text + # 这里定义地址属性为非空约束,除此还可以定义MultiValue(多值,英文逗号分割)、Enum(枚举)和Regular(正则) + constraint: NotNull + industry(行业): Industry + # 每个类型会默认创建id、name和description属性,都是Text类型 + # id(主键): Text + # name(名称): Text + # description(描述): Text + relations: + # 这里定义关系 + subCompany(子公司): Company +``` + +##### 定义子属性 + +```yaml +Company(公司): EntityType + desc: 公司的描述 + properties: + address(地址): Text + # 这里定义地址的子属性置信度 + confidence(置信度): Float + industry(行业): Industry +``` + +##### 定义谓词逻辑 + +```yaml +Company(公司): EntityType + desc: 公司的描述 + relations: + risk(风险关联): Company + # 这里定义关系的谓词逻辑,使用 [[ 和 ]] 作为逻辑规则的定界符 + rule: [ [ + Define (s:Comapny)-[ p:risk ]->(o:Company) { + ... ... + } + ] ] +``` + +如果是定义从实体类型到同个概念类型下的不同概念实例的逻辑,请在同个rule关键字里写多段Define的脚本。 + +#### 定义概念类型 + +```yaml +Industry(公司行业分类): ConceptType + # 这里定义概念的上下位关系谓词,默认为isA,可以指定isA和locateAt + hypernymPredicate: isA +``` + +概念类型的关系只允许创建在7大类里定义的谓词,这里可以通过autoRelate一键创建7大类的所有关系: + +```yaml +Industry(公司行业分类): ConceptType + autoRelate: Industry +``` + +#### 定义事件类型 + +```yaml +CompanyRiskEvent(公司风险事件): EventType + properties: + # 这里定义事件类型的主体为公司,事件类型必须定义主体subject + subject: Company +``` + +### 建模示例 + +```yaml +namespace Medical + +Symptom(症状): EntityType + +Drug(药品): EntityType + +Indicator(医学指征): EntityType + +BodyPart(人体部位): ConceptType + hypernymPredicate: isA + +HospitalDepartment(医院科室): ConceptType + hypernymPredicate: isA + +Disease(疾病): EntityType + properties: + complication(并发症): Disease + constraint: MultiValue + + commonSymptom(常见症状): Symptom + constraint: MultiValue + + applicableDrug(适用药品): Drug + constraint: MultiValue + + department(就诊科室): HospitalDepartment + constraint: MultiValue + + diseaseSite(发病部位): BodyPart + constraint: MultiValue + + relations: + abnormal(异常指征): Indicator + properties: + range(指标范围): Text + color(颜色): Text + shape(性状): Text +``` + +### SchemaHelper类 + +每次Schema变更发布后,根据项目前缀生成一个python类,比如medical例子: +knext/examples/medical/schema/medical_schema_helper.py + +> 类名为项目前缀名称 +> 类的成员变量为该项目下的类型名称 + +```python +class Medical: + def __init__(self): + self.Disease = self.Disease() + self.Symptom = self.Symptom() + self.Drug = self.Drug() + self.BodyPart = self.BodyPart() + self.HospitalDepartment = self.HospitalDepartment() + pass + + class Disease: + __typename__ = "Medical.Disease" + id = "id" + name = "name" + commonSymptom = "commonSymptom" + complication = "complication" + applicableDrug = "applicableDrug" + department = "department" + diseaseSite = "diseaseSite" + + def __init__(self): + pass + + class Symptom: + __typename__ = "Medical.Symptom" + id = "id" + name = "name" + + def __init__(self): + pass + + class Drug: + __typename__ = "Medical.Drug" + id = "id" + name = "name" + + def __init__(self): + pass + + class BodyPart: + __typename__ = "Medical.BodyPart" + id = "id" + name = "name" + alias = "alias" + stdId = "stdId" + + def __init__(self): + pass + + class HospitalDepartment: + __typename__ = "Medical.HospitalDepartment" + id = "id" + name = "name" + alias = "alias" + stdId = "stdId" + + def __init__(self): + pass +``` + +用户使用的时候,代码如此写: + +> 需要先import项目schema类 + +```python +from knext.examples.medical.schema.medical_schema_helper import Medical + +if __name__ == '__main__': + disease_name = Medical.Disease.name + ...... + common_symptom = Medical.Disease.commomSymptom + ...... + + # 加工链路映射组件定义 + mapping_disease = EntityMappingComponent( + spg_type=MEDICAL.Disease + ).add_field("id", "id") + .add_field("name", Medical.Disease.name) + .add_field("commonSymptom", Medical.Disease.commonSymptom) + .add_field("applicableDrug", Medical.Disease.applicableDrug) + .add_field("complication", Medical.Disease.complication) +``` diff --git a/docs/tutorial/schema/logical.md b/docs/tutorial/schema/logical.md new file mode 100644 index 0000000..34773bf --- /dev/null +++ b/docs/tutorial/schema/logical.md @@ -0,0 +1,160 @@ +--- +title: 概念语义分类 +order: 3 +--- + +概念之间只允许定义以下7大类的语义谓词(属性和关系),具体定义形式为:分类简写#谓词,比如HYP#isA + +![image.png](https://mdn.alipayobjects.com/huamei_xgb3qj/afts/img/A*Gm4WTbt1xVcAAAAAAAAAAAAADtmcAQ/original) + +### 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 的一个步骤。比如:“竞争”→“赢” diff --git a/package.json b/package.json index ee08966..b728584 100644 --- a/package.json +++ b/package.json @@ -3,16 +3,14 @@ "version": "0.0.1", "private": true, "description": "Official website of OpenSPG", + "repository": "git@github.com:OpenSPG/OpenSPG.github.io.git", + "license": "MIT", "scripts": { - "start": "npm run dev", - "dev": "dumi dev", "build": "dumi build", - "prepare": "husky install && dumi setup" + "dev": "dumi dev", + "prepare": "husky install && dumi setup", + "start": "npm run dev" }, - "authors": [ - "Aaron " - ], - "license": "MIT", "commitlint": { "extends": [ "@commitlint/config-conventional" @@ -24,21 +22,19 @@ ] }, "devDependencies": { - "@ant-design/icons": "^5.2.5", - "@commitlint/cli": "^18.0.0", - "@commitlint/config-conventional": "^18.0.0", - "@types/node": "^20.8.8", - "@types/react": "^18.2.21", - "@types/react-dom": "^18.2.7", + "@ant-design/icons": "^5.2.6", + "@commitlint/cli": "^17.1.2", + "@commitlint/config-conventional": "^17.1.0", "antd": "^5.8.4", - "dumi": "^2.2.14", + "dumi": "^2.2.0", "dumi-theme-openfinai": "^0.0.7", - "husky": "^8.0.3", - "lint-staged": "^15.0.2", - "prettier": "^3.0.3", + "husky": "^8.0.1", + "lint-staged": "^13.0.3", + "prettier": "^2.7.1", "prism-react-renderer": "^1.3.5", - "prismjs": "^1.29.0", - "typescript": "^5.1.6" + "prismjs": "^1.29.0" }, - "repository": "git@github.com:OpenSPG/OpenSPG.github.io.git" + "authors": [ + "Aaron " + ] }