基于PaddleNLP信息抽取,uie微调打造自己专属的信息抽取模型
基于PaddleNLP信息抽取,uie微调打造自己专属的信息抽取模型
上一篇<<基于PaddleOCR + NLP实现证件识别>> 用PaddleOCR实现OCR文字识别,再通过PaddleNLP实现文字提取关键信息
#关键代码
schema = ['姓名', '毕业院校', '职位', '月收入', '身体状况']
ie = Taskflow('information_extraction', schema=schema)
ie('你需要信息提取的文章字段')
UIE模型简介
UIE(Universal Information Extraction):Yaojie Lu等人在ACL-2022中提出了通用信息抽取统一框架UIE。该框架实现了实体抽取、关系抽取、事件抽取、情感分析等任务的统一建模,并使得不同任务间具备良好的迁移和泛化能力。为了方便大家使用UIE的强大能力,PaddleNLP借鉴该论文的方法,基于ERNIE 3.0知识增强预训练模型,训练并开源了首个中文通用信息抽取模型UIE。该模型可以支持不限定行业领域和抽取目标的关键信息抽取,实现零样本快速冷启动,并具备优秀的小样本微调能力,快速适配特定的抽取目标
UIE优势
使用简单:用户可以使用自然语言自定义抽取目标,无需训练即可统一抽取输入文本中的对应信息。实现开箱即用,并满足各类信息抽取需求。
降本增效:以往的信息抽取技术需要大量标注数据才能保证信息抽取的效果,为了提高开发过程中的开发效率,减少不必要的重复工作时间,开放域信息抽取可以实现零样本(zero-shot)或者少样本(few-shot)抽取,大幅度降低标注数据依赖,在降低成本的同时,还提升了效果。
效果领先:开放域信息抽取在多种场景,多种任务上,均有不俗的表现。
应用示例
UIE不限定行业领域和抽取目标,以下是一些零样本行业示例
- 医疗场景-专病结构化
- 法律场景-判决书抽取
- 金融场景-收入证明、招股书抽取
- 公安场景-事故报告抽取
- 旅游场景-宣传册、手册抽取
UIE开箱即用
paddlenlp.Taskflow
提供通用信息抽取、评价观点抽取等能力,可抽取多种类型的信息,包括但不限于命名实体识别(如人名、地名、机构名等)、关系(如电影的导演、歌曲的发行时间等)、事件(如某路口发生车祸、某地发生地震等)、以及评价维度、观点词、情感倾向等信息。用户可以使用自然语言自定义抽取目标,无需训练即可统一抽取输入文本中的对应信息。实现开箱即用,并满足各类信息抽取需求
UIE适用抽取示例
命名实体识别(Named Entity Recognition,简称NER)
例如抽取的目标实体类型是"时间"、“选手"和"赛事名称”, schema构造如下
>>> from pprint import pprint
>>> from paddlenlp import Taskflow
>>> schema = ['时间', '选手', '赛事名称'] # Define the schema for entity extraction
>>> ie = Taskflow('information_extraction', schema=schema)
>>> pprint(ie("2月8日上午北京冬奥会自由式滑雪女子大跳台决赛中中国选手谷爱凌以188.25分获得金牌!")) # Better print results using pprint
[{'时间': [{'end': 6,
'probability': 0.9857378532924486,
'start': 0,
'text': '2月8日上午'}],
'赛事名称': [{'end': 23,
'probability': 0.8503089953268272,
'start': 6,
'text': '北京冬奥会自由式滑雪女子大跳台决赛'}],
'选手': [{'end': 31,
'probability': 0.8981548639781138,
'start': 28,
'text': '谷爱凌'}]}]
在上例中我们已经实例化了一个Taskflow
对象,这里可以通过set_schema
方法重置抽取目标。
>>> schema = ['肿瘤的大小', '肿瘤的个数', '肝癌级别', '脉管内癌栓分级']
>>> ie.set_schema(schema)
>>> pprint(ie("(右肝肿瘤)肝细胞性肝癌(II-III级,梁索型和假腺管型),肿瘤包膜不完整,紧邻肝被膜,侵及周围肝组织,未见脉管内癌栓(MVI分级:M0级)及卫星子灶形成。(肿物1个,大小4.2×4.0×2.8cm)。"))
[{'肝癌级别': [{'end': 20,
'probability': 0.9243267447402701,
'start': 13,
'text': 'II-III级'}],
'肿瘤的个数': [{'end': 84,
'probability': 0.7538413804059623,
'start': 82,
'text': '1个'}],
'肿瘤的大小': [{'end': 100,
'probability': 0.8341128043459491,
'start': 87,
'text': '4.2×4.0×2.8cm'}],
'脉管内癌栓分级': [{'end': 70,
'probability': 0.9083292325934664,
'start': 67,
'text': 'M0级'}]}]
关系抽取(Relation Extraction,简称RE)
关系抽取是指从文本中识别实体并抽取实体之间的语义关系,进而获取三元组信息,即<主体,谓语,客体>。
- 例如以"竞赛名称"作为抽取主体,抽取关系类型为"主办方"、“承办方"和"已举办次数”, schema构造如下:
{
'竞赛名称': [
'主办方',
'承办方',
'已举办次数'
]
}
>>> schema = {'竞赛名称': ['主办方', '承办方', '已举办次数']} # Define the schema for relation extraction
>>> ie.set_schema(schema) # Reset schema
>>> pprint(ie('2022语言与智能技术竞赛由中国中文信息学会和中国计算机学会联合主办,百度公司、中国中文信息学会评测工作委员会和中国计算机学会自然语言处理专委会承办,已连续举办4届,成为全球最热门的中文NLP赛事之一。'))
[{'竞赛名称': [{'end': 13,
'probability': 0.7825402622754041,
'relations': {'主办方': [{'end': 22,
'probability': 0.8421710521379353,
'start': 14,
'text': '中国中文信息学会'},
{'end': 30,
'probability': 0.7580801847701935,
'start': 23,
'text': '中国计算机学会'}],
'已举办次数': [{'end': 82,
'probability': 0.4671295049136148,
'start': 80,
'text': '4届'}],
'承办方': [{'end': 39,
'probability': 0.8292706618236352,
'start': 35,
'text': '百度公司'},
{'end': 72,
'probability': 0.6193477885474685,
'start': 56,
'text': '中国计算机学会自然语言处理专委会'},
{'end': 55,
'probability': 0.7000497331473241,
'start': 40,
'text': '中国中文信息学会评测工作委员会'}]},
'start': 0,
'text': '2022语言与智能技术竞赛'}]}]
事件抽取 (Event Extraction, 简称EE)
事件抽取 ,是指从自然语言文本中抽取预定义的事件触发词(Trigger)和事件论元(Argument),组合为相应的事件结构化信息。
- 例如抽取的目标是"地震"事件的"地震强度"、“时间”、"震中位置"和"震源深度"这些信息,schema构造如下
{
'地震触发词': [
'地震强度',
'时间',
'震中位置',
'震源深度'
]
}
#触发词的格式统一为`触发词`或``XX触发词`,`XX`表示具体事件类型,上例中的事件类型是`地震`,则对应触发词为`地震触发词`。
>>> schema = {'地震触发词': ['地震强度', '时间', '震中位置', '震源深度']} # Define the schema for event extraction
>>> ie.set_schema(schema) # Reset schema
>>> ie('中国地震台网正式测定:5月16日06时08分在云南临沧市凤庆县(北纬24.34度,东经99.98度)发生3.5级地震,震源深度10千米。')
[{'地震触发词': [{'text': '地震', 'start': 56, 'end': 58, 'probability': 0.9987181623528585, 'relations': {'地震强度': [{'text': '3.5级', 'start': 52, 'end': 56, 'probability': 0.9962985320905915}], '时间': [{'text': '5月16日06时08分', 'start': 11, 'end': 22, 'probability': 0.9882578028575182}], '震中位置': [{'text': '云南临沧市凤庆县(北纬24.34度,东经99.98度)', 'start': 23, 'end': 50, 'probability': 0.8551415716584501}], '震源深度': [{'text': '10千米', 'start': 63, 'end': 67, 'probability': 0.999158304648045}]}}]}]
评论观点抽取
评论观点抽取,是指抽取文本中包含的评价维度、观点词。
例如抽取的目标是文本中包含的评价维度及其对应的观点词和情感倾向,schema构造如下:
{
'评价维度': [
'观点词',
'情感倾向[正向,负向]'
]
}
>>> schema = {'评价维度': ['观点词', '情感倾向[正向,负向]']} # Define the schema for opinion extraction
>>> ie.set_schema(schema) # Reset schema
>>> pprint(ie("店面干净,很清静,服务员服务热情,性价比很高,发现收银台有排队")) # Better print results using pprint
[{'评价维度': [{'end': 20,
'probability': 0.9817040258681473,
'relations': {'情感倾向[正向,负向]': [{'probability': 0.9966142505350533,
'text': '正向'}],
'观点词': [{'end': 22,
'probability': 0.957396472711558,
'start': 21,
'text': '高'}]},
'start': 17,
'text': '性价比'},
{'end': 2,
'probability': 0.9696849569741168,
'relations': {'情感倾向[正向,负向]': [{'probability': 0.9982153274927796,
'text': '正向'}],
'观点词': [{'end': 4,
'probability': 0.9945318044652538,
'start': 2,
'text': '干净'}]},
'start': 0,
'text': '店面'}]}]
情感倾向分类
句子级情感倾向分类,即判断句子的情感倾向是“正向”还是“负向”,schema构造如下:
>>> schema = '情感倾向[正向,负向]' # Define the schema for sentence-level sentiment classification
>>> ie.set_schema(schema) # Reset schema
>>> ie('这个产品用起来真的很流畅,我非常喜欢')
[{'情感倾向[正向,负向]': [{'text': '正向', 'probability': 0.9988661643929895}]}]
跨任务抽取
[
"法院",
{
"原告": "委托代理人"
},
{
"被告": "委托代理人"
}
]
>>> schema = ['法院', {'原告': '委托代理人'}, {'被告': '委托代理人'}]
>>> ie.set_schema(schema)
>>> pprint(ie("北京市海淀区人民法院\n民事判决书\n(199x)建初字第xxx号\n原告:张三。\n委托代理人李四,北京市 A律师事务所律师。\n被告:B公司,法定代表人王五,开发公司总经理。\n委托代理人赵六,北京市 C律师事务所律师。")) # Better print results using pprint
[{'原告': [{'end': 37,
'probability': 0.9949814024296764,
'relations': {'委托代理人': [{'end': 46,
'probability': 0.7956844697990384,
'start': 44,
'text': '李四'}]},
'start': 35,
'text': '张三'}],
'法院': [{'end': 10,
'probability': 0.9221074192336651,
'start': 0,
'text': '北京市海淀区人民法院'}],
'被告': [{'end': 67,
'probability': 0.8437349536631089,
'relations': {'委托代理人': [{'end': 92,
'probability': 0.7267121388225029,
'start': 90,
'text': '赵六'}]},
'start': 64,
'text': 'B公司'}]}]
模型选择
>>> from paddlenlp import Taskflow
>>> ie = Taskflow('information_extraction',
schema="",
schema_lang="zh",
batch_size=1,
model='uie-base',
position_prob=0.5,
precision='fp32',
use_fast=False)
schema
:定义任务抽取目标,可参考开箱即用中不同任务的调用示例进行配置。schema_lang
:设置schema的语言,默认为zh
, 可选有zh
和en
。因为中英schema的构造有所不同,因此需要指定schema的语言。该参数只对uie-m-base
和uie-m-large
模型有效。batch_size
:批处理大小,请结合机器情况进行调整,默认为1。model
:选择任务使用的模型,默认为uie-base
,可选有uie-base
,uie-medium
,uie-mini
,uie-micro
,uie-nano
和uie-medical-base
,uie-base-en
。position_prob
:模型对于span的起始位置/终止位置的结果概率在0~1之间,返回结果去掉小于这个阈值的结果,默认为0.5,span的最终概率输出为起始位置概率和终止位置概率的乘积。precision
:选择模型精度,默认为fp32
,可选有fp16
和fp32
。fp16
推理速度更快,支持GPU和NPU硬件环境。如果选择fp16
,在GPU硬件环境下,请先确保机器正确安装NVIDIA相关驱动和基础软件,确保CUDA>=11.2,cuDNN>=8.1.1,初次使用需按照提示安装相关依赖。其次,需要确保GPU设备的CUDA计算能力(CUDA Compute Capability)大于7.0,典型的设备包括V100、T4、A10、A100、GTX 20系列和30系列显卡等。更多关于CUDA Compute Capability和精度支持情况请参考NVIDIA文档:GPU硬件与支持精度对照表。use_fast
: 使用C++实现的高性能分词算子FastTokenizer进行文本预处理加速。需要通过pip install fast-tokenizer-python
安装FastTokenizer库后方可使用。默认为False
。更多使用说明可参考FastTokenizer文档。
UIE定制训练
对于简单的抽取目标可以直接使用paddlenlp.Taskflow
实现零样本(zero-shot)抽取,对于细分场景我们推荐使用轻定制功能(标注少量数据进行模型微调)以进一步提升效果。下面通过来往内地通行证(非中国籍)
的例子展示如何通过5条训练数据进行UIE模型微调
代码结构:
├── utils.py # 数据处理工具
├── model.py # 模型组网脚本
├── doccano.py # 数据标注脚本
├── doccano.md # 数据标注文档
├── finetune.py # 模型微调、压缩脚本
├── evaluate.py # 模型评估脚本
└── README.md
数据标注
我们推荐使用数据标注平台doccano
(doccano github社区)进行数据标注,本示例也打通了从标注到训练的通道,即doccano导出数据后可通过doccano.py脚本轻松将数据转换为输入模型时需要的形式,实现无缝衔接。标注方法的详细介绍请参考doccano数据标注指南。
Step 1. 本地安装doccano(请勿在AI Studio内部运行,本地测试环境python=3.8)
pip install doccano
Step 2. 初始化数据库和账户(用户名和密码可替换为自定义值)
doccano init
doccano createuser --username my_admin_name --password my_password
Step 3. 启动doccano
#在一个窗口启动doccano的WebServer,保持窗口
doccano webserver --port 8000
#在另一个窗口启动doccano的任务队列
doccano task
Step 4. 运行doccano来标注实体和关系
打开浏览器(推荐Chrome),在地址栏中输入http://0.0.0.0:8000/后回车即得以下界面
登陆账户。点击右上角的LOGIN,输入Step 2中设置的用户名和密码登陆。
创建项目。点击左上角的CREATE
,跳转至以下界面。
勾选序列标注(Sequence Labeling)
填写项目名称(Project name)
等必要信息
勾选允许实体重叠~~(Allow overlapping entity)~~ 、使用关系标注(Use relation labeling)
创建完成后,项目首页视频提供了从数据导入到导出的七个步骤的详细说明
~设置标签。在Labels一栏点击Actions,Create Label手动设置或者Import Labels从文件导入。
最上边Span表示实体标签,Relation表示关系标签,需要分别设置。
导入数据。在Datasets一栏点击Actions、Import Dataset从文件导入文本数据。
~根据文件格式(File format)给出的示例,选择适合的格式导入自定义数据文件。TextLine每一行为一条数据,Text是一个文件一个数据
导入成功后即跳转至数据列表。
导入文件:gatText.txt
~标注数据。点击每条数据最右边的Annotate按钮开始标记。标记页面右侧的标签类型(Label Types)开关可在实体标签和关系标签之间切换。
~实体标注:直接用鼠标选取文本即可标注实体。
~关系标注:首先点击待标注的关系标签,接着依次点击相应的头尾实体可完成关系标注。
~导出数据: 在Datasets一栏点击Actions、Export Dataset导出已标注的数据。
导出dev_doccaon_ext.jsonl
文件 文件名称改为json doccano_ext.json
将标注数据转化成UIE训练所需数据
将doccano平台的标注数据保存在./data/目录。对于快递单信息抽取的场景,可以直接下载标注好的数据。
python doccano.py --doccano_file ./data/doccano_ext.json --splits 1 0 0
doccano_file: 从doccano导出的数据标注文件。
save_dir: 训练数据的保存目录,默认存储在data目录下。
negative_ratio: 最大负例比例,该参数只对抽取类型任务有效,适当构造负例可提升模型效果。负例数量和实际的标签数量有关,最大负例数量 = negative_ratio * 正例数量。该参数只对训练集有效,默认为5。为了保证评估指标的准确性,验证集和测试集默认构造全负例。
splits: 划分数据集时训练集、验证集所占的比例。默认为[0.8, 0.1, 0.1]表示按照8:1:1的比例将数据划分为训练集、验证集和测试集。
task_type: 选择任务类型,可选有抽取和分类两种类型的任务。
options: 指定分类任务的类别标签,该参数只对分类类型任务有效。
prompt_prefix: 声明分类任务的prompt前缀信息,该参数只对分类类型任务有效。
is_shuffle: 是否对数据集进行随机打散,默认为True。
seed: 随机种子,默认为1000.
执行成功后会在data文件夹生成3个文件
dev.txt,test.txt,train.txt
进行数据微调:
python finetune.py --device cpu --logging_steps 100 --save_steps 100 --eval_steps 100 --seed 42 --model_name_or_path uie-base --output_dir ./checkpoint/model_best --train_path ./data/train.txt --dev_path ./data/dev.txt --max_seq_length 512 --per_device_eval_batch_size 100 --per_device_train_batch_size 100 --num_train_epochs 100 --learning_rate 1e-5 --label_names "start_positions" "end_positions" --do_train --do_eval --do_export --export_model_dir ./checkpoint/model_best --overwrite_output_dir --disable_tqdm True --metric_for_best_model eval_f1 --load_best_model_at_end True --save_total_limit 1
train_path: 训练集文件路径。
dev_path: 验证集文件路径。
save_dir: 模型存储路径,默认为./checkpoint。
learning_rate: 学习率,默认为1e-5。
batch_size: 批处理大小,请结合显存情况进行调整,若出现显存不足,请适当调低这一参数,默认为16。
max_seq_len: 文本最大切分长度,输入超过最大长度时会对输入文本进行自动切分,默认为512。
num_epochs: 训练轮数,默认为100。
model: 选择模型,程序会基于选择的模型进行模型微调,可选有uie-base和uie-tiny,默认为uie-base。
seed: 随机种子,默认为1000.
logging_steps: 日志打印的间隔steps数,默认10。
valid_steps: evaluate的间隔steps数,默认100。
device: 选用什么设备进行训练,可选cpu或gpu
save_dir: 模型存储路径,默认为./checkpoint。
调用方式:
schema = ["英文名", "中文名", "出生日期", "性别", "国籍", "证件号码"]
ie = Taskflow('information_extraction', schema=schema, task_path='./checkpoint/model_best')
res = ie(allStr)
效果还是可以的,目前英文名和证件号码提取还需要在调整下。
最终效果:
很完美!!!
记录下过程中bug处理思路
问题1:提示该问题是转换dcocan文件,3个文件中数据为空的问题,需要检查下生成文件dev.txt/test.txt/train.txt是否没有数据问题。在执行finetune会指定训练文件这个要注意。这个还是灵感一闪找出问题所在
问题2:如果你下载的转换代码不是对应paddleNLP版本的就会提示这个。因为不匹配的版本会生成uie-base你删掉他重新执行就可以了
【已解决】问题1:ValueError: num_samples should be a positive integer value, but got num_samples=0
【已解决】问题2:ValueError: The state dictionary of the model you are trying to load is corr
辅助文档
doccano部署文档:https://github.com/doccano/doccano
需要更多的模型工具使用示例,部署问题解决方案同学请移步谢谢支持!!!!
回复 paddle-demo
基于PaddleNLP信息抽取,uie微调打造自己专属的信息抽取模型的更多相关文章
- 基于Label studio实现UIE信息抽取智能标注方案,提升标注效率!
基于Label studio实现UIE信息抽取智能标注方案,提升标注效率! 项目链接见文末 人工标注的缺点主要有以下几点: 产能低:人工标注需要大量的人力物力投入,且标注速度慢,产能低,无法满足大规模 ...
- KoaHub平台基于Node.js开发的Koa 连接支付宝插件代码信息详情
KoaHub平台基于Node.js开发的Koa 链接支付宝插件代码信息详情 easy-alipay alipay payment & notification APIs easy-alipay ...
- 基于MVC框架layui分页控件实现前端分页信息写法
详细链接:https://shop499704308.taobao.com/?spm=a1z38n.10677092.card.11.594c1debsAGeak@{ ViewBag.Title = ...
- 第十二章——SQLServer统计信息(1)——创建和更新统计信息
原文:第十二章--SQLServer统计信息(1)--创建和更新统计信息 简介: 查询的统计信息: 目前为止,已经介绍了选择索引.维护索引.如果有合适的索引并实时更新统计信息,那么优化器会选择有用的索 ...
- 持久化的基于L2正则化和平均滑动模型的MNIST手写数字识别模型
持久化的基于L2正则化和平均滑动模型的MNIST手写数字识别模型 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献Tensorflow实战Google深度学习框架 实验平台: Tens ...
- django模板中变更数据库信息后,如何把变更后的信息同步更新到数据库
我们在基于django开发项目的过程中,经常会遇到数据库表字段增加,删除,或者修改的情况,以及字段属性更改的情况,因为django基于ORM模式来操作数据库的, 传统上如果django项目中的数据库m ...
- 微调torchvision 0.3的目标检测模型
微调torchvision 0.3的目标检测模型 本文将微调在 Penn-Fudan 数据库中对行人检测和分割的已预先训练的 Mask R-CNN 模型.它包含170个图像和345个行人实例,说明如何 ...
- Atitit.dwr3 不能显示错误详细信息的解决方案,控件显示错误详细信息的解决方案 java .net php
Atitit.dwr3 不能显示错误详细信息的解决方案,控件显示错误详细信息的解决方案 java .net php 1. Keyword/subtitle 1 2. 使用dwr3的异常convert处 ...
- 学习笔记_Java get和post区别(转载_GET一般用于获取/查询资源信息,而POST一般用于更新资源信息)
转载自:[hyddd(http://www.cnblogs.com/hyddd/)] 总结一下, Get是向服务器发索取数据的一种请求 而Post是向服务器提交数据的一种请求,在F ...
- Sqlserver查询表结构信息-字段说明、类型、长度等信息
Sqlserver 中查询表结构信息-字段说明.类型.长度等信息综合语法. SELECT 表名 = d.name,--case when a.colorder=1 then d.name else ' ...
随机推荐
- 【ClickHouse】6:clickhouse集群高可用
背景介绍: 有四台CentOS7服务器安装了ClickHouse HostName IP 安装程序 程序端口 shard(分片) replica(备份) centf8118.sharding1.db ...
- 在linux系统中,对标准输出(stdout,文件描述符为 1)和标准错误(stderr,文件描述符为 2)重定向到文件
请参考:2>/dev/null和>/dev/null 2>&1和2>&1>/dev/null的区别 下面的是本人浅尝辄止了,并非全貌,还是上面的文章说的比 ...
- SpringCloud注册中心切换nacos
SpringBoot与nacos版本对应关系 https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF ...
- 如何让 3D 数字孪生场景闪闪发光
今日图扑软件功能分享:我们将探讨 HT 系统如何通过分组管理灯光.裁切体和流光,以提高场景光影效果的精准度和整体可控性. HT 中的灯光.裁切体.流光是会影响它所在区域一定范围内的其他节点的表现,如 ...
- 3. set 的使用
3. set 的使用 因为在实际工程中,我们不会将具体文件全部写出来,这是一件费力不讨好的事情. 3.1 set 定义变量 在 CMake 文件中,默认的变量数据类型是字符串,如果要用别的类型,需要进 ...
- CSS+JS 实现动态曲线进度条
由于系统UI风格升级,产品童鞋和UI童鞋总是想要搞点儿事情出来,项目页面上的进度条从直线变成了曲线,哈哈,好吧,那就迎难而上 实现效果: 1.简单搞一搞 CSS , 此处代码有折叠 .process ...
- [oeasy]python0139_尝试捕获异常_ try_except_traceback
- 不但要有自己的报错 - 还要保留系统的报错 - 有可能吗? ### 保留报错 ! ...
- oeasy教您玩转python - 010 - # 不换行输出
不换行输出 回忆上次内容 \n 就是换行 他对应着 ascii 字符的代码是(10)10进制 他的英文是 LF,意思是Line Feed 这样我就可以自由的控制哪里换行了! 可以做下面这个框架标题吗? ...
- 关于android的图像视图的基本了解
最好直接复制进去而不是拖进去 图片直接导入最好用小写字母命名,数字与字母之间要用_,而且数字好像不可以连用 centerInside,fitCenter,center的区别: centerInside ...
- 支付宝退款和结果查询接口简单实现(.Net 7.0)
〇.前言 支付宝对 .Net 的支持还是比较充分的,在每个接口文档中都有关于 C# 语言的示例,这样就大大降低了对接的难度,很容易上手. 官方接口文档地址:退款-alipay.trade.refund ...