-
ModelArts 训练作业是否支持定时或周期调用?
-
ModelArts 训练作业一直在等待中很长时间怎么办?
-
ModelArts 训练作业常用文件路径是什么?
-
ModelArts训练从本地导入的算法有哪些格式要求?
-
安装远端插件时不稳定,需尝试多次方法一:离线包安装方式(推荐)到VS Code插件官网vscode_marketplace搜索待安装的Python插件,Python插件路径。单击进入Python插件的Version History页签后,下载该插件的离线安装包,如图所示。图1 Python插件离线安装包在本地VS Code环境中,将下载好的.vsix文件拖动到远端Notebook中。右键点击该文件,选择Install Extension VSIX。方法二:设置远端默认安装的插件在VS Code的配置文件settings.json中添加remote.SSH.defaultExtensions参数,如自动安装Python和Maven插件,可配置如下。 "remote.SSH.defaultExtensions": [ "ms-python.python", "vscjava.vscode-maven" ],其中,插件名称可以点击VS Code的某个插件后获取,如下所示。方法三:VS Code官网排查方式cid:link_1小技巧(按需调整远端连接的相关参数): "remote.SSH.connectTimeout": 10, "remote.SSH.maxReconnectionAttempts": null, "remote.downloadExtensionsLocally": true, "remote.SSH.useLocalServer": false, "remote.SSH.localServerDownload": "always",
-
本文为用户提供如何将本地的自定义算法通过简单的代码适配,实现在ModelArts上进行模型训练与部署的全流程指导。场景描述本案例用于指导用户使用PyTorch1.8实现手写数字图像识别,示例采用的数据集为MNIST官方数据集。通过学习本案例,您可以了解如何在ModelArts平台上训练作业、部署推理模型并预测的完整流程。操作流程开始使用如下样例前,请务必按前提条件指导完成必要操作。Step1 准备训练数据:下载MNIST数据集。Step2 准备训练文件和推理文件:编写训练与推理代码。Step3 创建OBS桶并上传文件:创建OBS桶和文件夹,并将数据集和训练脚本,推理脚本,推理配置文件上传到OBS中。Step4 创建训练作业:进行模型训练。Step5 推理部署:训练结束后,将生成的模型导入ModelArts用于创建 AI 应用,并将AI应用部署为在线服务。Step6 预测结果:上传一张手写数字图片,发起预测请求获取预测结果。Step7 清除资源:运行完成后,停止服务并删除OBS中的数据,避免不必要的扣费。前提条件已注册华为云帐号,且在使用ModelArts前检查帐号状态,帐号不能处于欠费或冻结状态。Step1 准备训练数据本案例使用的数据是MNIST数据集,您可以从MNIST官网下载数据集至本地,以下4个文件均要下载。“train-images-idx3-ubyte.gz”:训练集的压缩包文件。训练集,共包含60000个样本。“train-labels-idx1-ubyte.gz”:训练集标签的压缩包文件。训练集标签,共包含60000个样本的类别标签。“t10k-images-idx3-ubyte.gz”:验证集的压缩包文件。验证集,共包含10000个样本。“t10k-labels-idx1-ubyte.gz”:验证集标签的压缩包文件。验证集标签,共包含10000个样本的类别标签。Step2 准备训练文件和推理文件针对此案例,ModelArts提供了需使用的训练脚本、推理脚本和推理配置文件。请参考如下文件内容。训练脚本“train.py”内容如下:# base on https://github.com/pytorch/examples/blob/main/mnist/main.pyfrom __future__ import print_functionimport osimport gzipimport codecsimport argparsefrom typing import IO, Unionimport numpy as npimport torchimport torch.nn as nnimport torch.nn.functional as Fimport torch.optim as optimfrom torchvision import datasets, transforms from torch.optim.lr_scheduler import StepLRimport shutilclass Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 32, 3, 1) self.conv2 = nn.Conv2d(32, 64, 3, 1) self.dropout1 = nn.Dropout(0.25) self.dropout2 = nn.Dropout(0.5) self.fc1 = nn.Linear(9216, 128) self.fc2 = nn.Linear(128, 10) def forward(self, x): x = self.conv1(x) x = F.relu(x) x = self.conv2(x) x = F.relu(x) x = F.max_pool2d(x, 2) x = self.dropout1(x) x = torch.flatten(x, 1) x = self.fc1(x) x = F.relu(x) x = self.dropout2(x) x = self.fc2(x) output = F.log_softmax(x, dim=1) return output# 模型训练,设置模型为训练模式,加载训练数据,计算损失函数,执行梯度下降def train(args, model, device, train_loader, optimizer, epoch): model.train() for batch_idx, (data, target) in enumerate(train_loader): data, target = data.to(device), target.to(device) optimizer.zero_grad() output = model(data) loss = F.nll_loss(output, target) loss.backward() optimizer.step() if batch_idx % args.log_interval == 0: print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( epoch, batch_idx * len(data), len(train_loader.dataset), 100. * batch_idx / len(train_loader), loss.item())) if args.dry_run: break# 模型验证,设置模型为验证模式,加载验证数据,计算损失函数和准确率def test(model, device, test_loader): model.eval() test_loss = 0 correct = 0 with torch.no_grad(): for data, target in test_loader: data, target = data.to(device), target.to(device) output = model(data) test_loss += F.nll_loss(output, target, reduction='sum').item() pred = output.argmax(dim=1, keepdim=True) correct += pred.eq(target.view_as(pred)).sum().item() test_loss /= len(test_loader.dataset) print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format( test_loss, correct, len(test_loader.dataset), 100. * correct / len(test_loader.dataset)))# pytorch mnist# https://github.com/pytorch/vision/blob/v0.9.0/torchvision/datasets/mnist.pydef get_int(b: bytes) -> int: return int(codecs.encode(b, 'hex'), 16)def open_maybe_compressed_file(path: Union[str, IO]) -> Union[IO, gzip.GzipFile]: """Return a file object that possibly decompresses 'path' on the fly. Decompression occurs when argument `path` is a string and ends with '.gz' or '.xz'. """ if not isinstance(path, torch._six.string_classes): return path if path.endswith('.gz'): return gzip.open(path, 'rb') if path.endswith('.xz'): return lzma.open(path, 'rb') return open(path, 'rb')SN3_PASCALVINCENT_TYPEMAP = { 8: (torch.uint8, np.uint8, np.uint8), 9: (torch.int8, np.int8, np.int8), 11: (torch.int16, np.dtype('>i2'), 'i2'), 12: (torch.int32, np.dtype('>i4'), 'i4'), 13: (torch.float32, np.dtype('>f4'), 'f4'), 14: (torch.float64, np.dtype('>f8'), 'f8')}def read_sn3_pascalvincent_tensor(path: Union[str, IO], strict: bool = True) -> torch.Tensor: """Read a SN3 file in "Pascal Vincent" format (Lush file 'libidx/idx-io.lsh'). Argument may be a filename, compressed filename, or file object. """ # read with open_maybe_compressed_file(path) as f: data = f.read() # parse magic = get_int(data[0:4]) nd = magic % 256 ty = magic // 256 assert 1 <= nd <= 3 assert 8 <= ty <= 14 m = SN3_PASCALVINCENT_TYPEMAP[ty] s = [get_int(data[4 * (i + 1): 4 * (i + 2)]) for i in range(nd)] parsed = np.frombuffer(data, dtype=m[1], offset=(4 * (nd + 1))) assert parsed.shape[0] == np.prod(s) or not strict return torch.from_numpy(parsed.astype(m[2], copy=False)).view(*s)def read_label_file(path: str) -> torch.Tensor: with open(path, 'rb') as f: x = read_sn3_pascalvincent_tensor(f, strict=False) assert(x.dtype == torch.uint8) assert(x.ndimension() == 1) return x.long()def read_image_file(path: str) -> torch.Tensor: with open(path, 'rb') as f: x = read_sn3_pascalvincent_tensor(f, strict=False) assert(x.dtype == torch.uint8) assert(x.ndimension() == 3) return xdef extract_archive(from_path, to_path): to_path = os.path.join(to_path, os.path.splitext(os.path.basename(from_path))[0]) with open(to_path, "wb") as out_f, gzip.GzipFile(from_path) as zip_f: out_f.write(zip_f.read())# --- pytorch mnist# --- end# raw mnist 数据处理def convert_raw_mnist_dataset_to_pytorch_mnist_dataset(data_url): """ raw {data_url}/ train-images-idx3-ubyte.gz train-labels-idx1-ubyte.gz t10k-images-idx3-ubyte.gz t10k-labels-idx1-ubyte.gz processed {data_url}/ train-images-idx3-ubyte.gz train-labels-idx1-ubyte.gz t10k-images-idx3-ubyte.gz t10k-labels-idx1-ubyte.gz MNIST/raw train-images-idx3-ubyte train-labels-idx1-ubyte t10k-images-idx3-ubyte t10k-labels-idx1-ubyte MNIST/processed training.pt test.pt """ resources = [ "train-images-idx3-ubyte.gz", "train-labels-idx1-ubyte.gz", "t10k-images-idx3-ubyte.gz", "t10k-labels-idx1-ubyte.gz" ] pytorch_mnist_dataset = os.path.join(data_url, 'MNIST') raw_folder = os.path.join(pytorch_mnist_dataset, 'raw') processed_folder = os.path.join(pytorch_mnist_dataset, 'processed') os.makedirs(raw_folder, exist_ok=True) os.makedirs(processed_folder, exist_ok=True) print('Processing...') for f in resources: extract_archive(os.path.join(data_url, f), raw_folder) training_set = ( read_image_file(os.path.join(raw_folder, 'train-images-idx3-ubyte')), read_label_file(os.path.join(raw_folder, 'train-labels-idx1-ubyte')) ) test_set = ( read_image_file(os.path.join(raw_folder, 't10k-images-idx3-ubyte')), read_label_file(os.path.join(raw_folder, 't10k-labels-idx1-ubyte')) ) with open(os.path.join(processed_folder, 'training.pt'), 'wb') as f: torch.save(training_set, f) with open(os.path.join(processed_folder, 'test.pt'), 'wb') as f: torch.save(test_set, f) print('Done!')def main(): # 定义可以接收的训练作业运行参数 parser = argparse.ArgumentParser(description='PyTorch MNIST Example') parser.add_argument('--data_url', type=str, default=False, help='mnist dataset path') parser.add_argument('--train_url', type=str, default=False, help='mnist model path') parser.add_argument('--batch-size', type=int, default=64, metavar='N', help='input batch size for training (default: 64)') parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N', help='input batch size for testing (default: 1000)') parser.add_argument('--epochs', type=int, default=14, metavar='N', help='number of epochs to train (default: 14)') parser.add_argument('--lr', type=float, default=1.0, metavar='LR', help='learning rate (default: 1.0)') parser.add_argument('--gamma', type=float, default=0.7, metavar='M', help='Learning rate step gamma (default: 0.7)') parser.add_argument('--no-cuda', action='store_true', default=False, help='disables CUDA training') parser.add_argument('--dry-run', action='store_true', default=False, help='quickly check a single pass') parser.add_argument('--seed', type=int, default=1, metavar='S', help='random seed (default: 1)') parser.add_argument('--log-interval', type=int, default=10, metavar='N', help='how many batches to wait before logging training status') parser.add_argument('--save-model', action='store_true', default=True, help='For Saving the current Model') args = parser.parse_args() use_cuda = not args.no_cuda and torch.cuda.is_available() torch.manual_seed(args.seed) # 设置使用 GPU 还是 CPU 来运行算法 device = torch.device("cuda" if use_cuda else "cpu") train_kwargs = {'batch_size': args.batch_size} test_kwargs = {'batch_size': args.test_batch_size} if use_cuda: cuda_kwargs = {'num_workers': 1, 'pin_memory': True, 'shuffle': True} train_kwargs.update(cuda_kwargs) test_kwargs.update(cuda_kwargs) # 定义数据预处理方法 transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) # 将 raw mnist 数据集转换为 pytorch mnist 数据集 convert_raw_mnist_dataset_to_pytorch_mnist_dataset(args.data_url) # 分别创建训练和验证数据集 dataset1 = datasets.MNIST(args.data_url, train=True, download=False, transform=transform) dataset2 = datasets.MNIST(args.data_url, train=False, download=False, transform=transform) # 分别构建训练和验证数据迭代器 train_loader = torch.utils.data.DataLoader(dataset1, **train_kwargs) test_loader = torch.utils.data.DataLoader(dataset2, **test_kwargs) # 初始化神经网络模型并拷贝模型到计算设备上 model = Net().to(device) # 定义训练优化器和学习率策略,用于梯度下降计算 optimizer = optim.Adadelta(model.parameters(), lr=args.lr) scheduler = StepLR(optimizer, step_size=1, gamma=args.gamma) # 训练神经网络,每一轮进行一次验证 for epoch in range(1, args.epochs + 1): train(args, model, device, train_loader, optimizer, epoch) test(model, device, test_loader) scheduler.step() # 保存模型与适配 ModelArts 推理模型包规范 if args.save_model: # 在 train_url 训练参数对应的路径内创建 model 目录 model_path = os.path.join(args.train_url, 'model') os.makedirs(model_path, exist_ok = True) # 按 ModelArts 推理模型包规范,保存模型到 model 目录内 torch.save(model.state_dict(), os.path.join(model_path, 'mnist_cnn.pt')) # 拷贝推理代码与配置文件到 model 目录内 the_path_of_current_file = os.path.dirname(__file__) shutil.copyfile(os.path.join(the_path_of_current_file, 'infer/customize_service.py'), os.path.join(model_path, 'customize_service.py')) shutil.copyfile(os.path.join(the_path_of_current_file, 'infer/config.json'), os.path.join(model_path, 'config.json'))if __name__ == '__main__': main()推理配置文件“config.json”内容如下:{ "model_algorithm": "image_classification", "model_type": "PyTorch", "runtime": "pytorch_1.8.0-cuda_10.2-py_3.7-ubuntu_18.04-x86_64"}Step3 创建OBS桶并上传文件将训练使用的数据和代码文件、推理代码文件与推理配置文件,上传到OBS桶中。在 ModelArts 上运行训练作业时,需要从OBS桶中读取数据和代码文件。登录OBS管理控制台,按照如下示例创建OBS桶和文件夹。创建OBS桶和文件夹的操作指导请参见创建桶和新建文件夹。{OBS桶} # OBS对象桶,用户可以自定义名称,例如:test-modelarts-xx -{OBS文件夹} # OBS文件夹,自定义名称,此处举例为pytorch - mnist-data # OBS文件夹,用于存放训练数据集,可以自定义名称,此处举例为mnist-data - mnist-code # OBS文件夹,用于存放训练脚本train.py,可以自定义名称,此处举例为mnist-code - infer # OBS文件夹,用于存放推理脚本customize_service.py和配置文件config.json - mnist-output # OBS文件夹,用于存放训练输出模型,可以自定义名称,此处举例为mnist-output注意创建的OBS桶所在区域和后续使用ModelArts必须在同一个区域Region,否则会导致训练时找不到OBS桶。具体操作可参见如何查看查看OBS桶与ModelArts是否在同一区域。创建OBS桶时,桶的存储类别请勿选择“归档存储”,归档存储的OBS桶会导致模型训练失败。上传数据集到OBS中。上传文件至OBS的操作指导请参见上传文件上传训练脚本“train.py”到“mnist-code”文件夹中。上传推理脚本“customize_service.py”和推理配置文件“config.json”到“infer”文件中。Step4 创建训练作业登录ModelArts管理控制台,选择和OBS桶相同的区域。在“全局配置”中检查当前帐号是否已完成访问授权的配置。如未完成,请参考使用委托授权。针对之前使用访问密钥授权的用户,建议清空授权,然后使用委托进行授权。在左侧导航栏的“训练管理”-> “训练作业”中,单击“创建训练作业”。填写创建训练作业相关信息。“创建方式”:选择“自定义”,下拉框中选择PyTorch,pytorch_1.8.0-cuda_10.2-py_3.7-ubuntu_18.04-x86_64。“代码目录”:选择已创建的代码目录路径“/test-modelarts-xx/pytorch/mnist-code/”。“启动文件”:选择代码目录下上传的训练脚本“train.py”。“训练输入”:单击“添加”,设置训练输入的“参数名称”为“data_url”。设置数据存储位置为 “/test-modelarts-xx/pytorch/mnist-data/”。“训练输出”:单击“添加”,设置训练输出的“参数名称”为“train_url”。设置数据存储位置为 “/test-modelarts-xx/pytorch/mnist-output/”“资源类型”:选择 GPU 单卡的规格,如“GPU: 1*NVIDIA-V100(16GB) | CPU: 8 核 64GB 780GB”。单击“提交”,确认训练作业的参数信息,确认无误后单击“确定”。页面自动返回“训练作业”列表页,当训练作业状态变为“已完成”时,即完成了模型训练过程。单击训练作业名称,进入作业详情界面查看训练作业日志信息,观察日志是否有明显的Error信息,如果有则表示训练失败,请根据日志提示定位原因并解决。在训练详情页左下方单击训练输出路径,跳转到OBS目录,查看是否存在model文件夹,且model文件夹中是否有生成训练模型。如果未生成model文件夹或者训练模型,可能是训练输入数据不完整导致,请检查训练数据上传是否完整,并重新训练。Step5 推理部署模型训练完成后,可以创建AI应用,将AI应用部署为在线预测服务。在ModelArts管理控制台,单击左侧导航栏中的“AI应用管理>AI应用”,进入“我的AI应用”页面,单击“创建”。在“创建AI应用”页面,填写相关参数,然后单击“立即创建”。在“元模型来源”中,选择“从训练中选择>训练作业(New)”页签,选择Step4 创建训练作业中完成的训练作业,勾选“动态加载”。AI引擎的值是系统自动写入的,无需设置。在AI应用列表页面,当模型状态变为“正常”时,表示AI应用创建成功。单击AI应用名称左侧的小三角,打开此AI应用下的所有版本。在对应版本所在行,单击操作列“部署>在线服务”,将模型部署为在线服务。在“部署”页面,参考下图填写参数,然后根据界面提示完成在线服务创建。完成服务部署后,返回在线服务页面列表页,等待服务部署完成,当服务状态显示为“运行中”,表示服务已部署成功。Step6 预测结果在“在线服务”页面,单击在线服务名称,进入服务详情页面。单击“预测”页签,请求类型选择“multipart/form-data”,请求参数填写“image”,单击“上传”按钮上传示例图片,然后单击“预测”。预测完成后,预测结果显示区域将展示预测结果,根据预测结果内容,可识别出此图片的数字是“2”。Step7 清除资源如果不再需要使用此模型及在线服务,建议清除相关资源,避免产生不必要的费用。在“在线服务”页面,“停止”或“删除”刚创建的在线服务。在“AI应用管理”页面,“删除”刚创建的AI应用。在“训练作业New”页面,“删除”运行结束的训练作业。进入OBS,删除本示例使用的OBS 桶及文件夹,以及文件夹的文件。
-
图数据库实践--COVID-19患者轨迹追溯背景COVID-19 大流行的形势依然很严峻,新冠疫情确诊患者的轨迹信息成为疫情发展过程中大众关注的焦点,政府部门也陆续公开了部分确诊患者的非隐私信息,这部分数据为相关研究人员研究疫情的传播与防控提供了重要的数据支撑。 然而,公布的数据多为文本等非结构化数据,而且极其分散,难以直接为后续研究提供深度的支撑。患者的轨迹信息蕴含居家、出行、餐饮等丰富的接触关系,在新冠病毒人传人的特性下,如果能直观地展示这些接触信息,相信能对疫情的研究提供很大的帮助。基于此,我们利用相关技术手段从公开的病患轨迹数据抽取了病患相关的基本信息(年龄、性别等)、轨迹、病患关系等数据,并利用图数据库技术对其进行研究,尝试为政府相关部门对疫情的传播与防控工作提供有效的决策支撑。数据建模首先我们需要构建新冠疫情轨迹数据的图数据模型。图数据模型中的实体包括患者、轨迹点、交通工具等,实体关系包括居住于、逗留过等,具体的图模型见下图: 图模型中包含了四类点和六类边,元信息说明如下: 数据集我们采集了近期“旅行团疫情”公开的部分轨迹文本数据,根据设计的图模型对数据格式进行转换,转换后的数据可从此处下载。这份数据包含800+个点,1300+条边。创图我们下面将使用华为云图数据库 GES 对以上数据集进行探索和演示,我们需要先在 GES 中创图并将以上数据集导入,详细指导流程可参见华为图引擎文档-快速入门和华为云图引擎服务 GES 实战——创图。使用 GES 查询的预置条件首先通过moxing包从对象存储服务obs中下载ges4jupyter。ges4jupyter是jupyter连接GES服务的工具文件。文件中封装了使用 GES 查询的预置条件,包括配置相关参数和对所调用 API 接口的封装,如果你对这些不感兴趣,可直接运行而不需要了解细节,这对理解后续具体查询没有影响。import moxing as mox mox.file.copy('obs://obs-aigallery-zc/GES/ges4jupyter/beta/ges4jupyter.py', 'ges4jupyter.py') mox.file.copy('obs://obs-aigallery-zc/GES/ges4jupyter/beta/ges4jupyter.html', 'ges4jupyter.html')GESConfig的参数都是与调用 GES 服务有关的参数,依次为“公网访问地址”、“项目ID”、“图名”、“终端节点”、“IAM 用户名”、“IAM 用户密码”、“IAM 用户所属账户名”、“所属项目”,其获取方式可参考调用 GES 服务业务面 API 相关参数的获取。这里通过read_csv_config方法从配置文件中读取这些信息。如果没有配置文件,可以根据自己的需要补充下列字段。对于开启了https安全模式的图实例,参数port的值为443。from ges4jupyter import GESConfig, GES4Jupyter, read_csv_config eip = '' project_id = '' graph_name = '' iam_url = '' user_name = '' password = '' domain_name = '' project_name = '' port = 80 eip, project_id, graph_name, iam_url, user_name, password, domain_name, project_name, port = read_csv_config('cn_north_4_graph.csv') config = GESConfig(eip, project_id, graph_name, iam_url = iam_url, user_name = user_name, password = password, domain_name = domain_name, project_name = project_name, port = port) ges_util = GES4Jupyter(config, True);创建好图之后,就可以对其进行查询和分析了。GES 支持 cypher和gremlin 两种查询语言,这里的查询示例以cypher举例。 在使用 cypher 查询之前,我们先创建点索引和边索引(可直接点击GES画布右下角“创建索引”按钮)。print('开始创建点索引:') job_id = ges_util.build_vertex_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('点索引创建完成')print('开始创建边索引:') job_id = ges_util.build_edge_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('边索引创建完成')查询演示首先查询这份图数据的统计信息,对全图的点边数目有所了解:import json print('统计信息:') result = ges_util.summary() format_result = json.dumps(result, indent=4) print(format_result)使用查询语言获取前10条边以及其顶点,对数据有个粗略的了解(建议使用nodebook打开并连接GES服务,可以看到运行后数据可视化的效果):cypher_result = ges_util.cypher_query("match (n)-[r]->(m) return n,r,m limit 10",formats=['row','graph']); ges_util.format_cypher_result(cypher_result)对于数据中的城市,采集了部分城市的防疫政策。了解其他城市的防疫政策,有助于规划出行。print('查看各个城市的防疫政策:') statement = "match (m:city) return id(m), m.防疫政策" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result在现实中,随着采集到更多的数据,不免对原有的数据进行更新,比如新发现关联患者,就涉及到节点和边的增加操作。假设北京新增病例40,其与原有北京病例39是亲属关系。print('数据更新:') print('先查询点是否存在:') vertex_id = '北京病例40' statement = "match (p) where id(p) ="' + vertex_id + '" return p" result = ges_util.cypher_query(statement) if len(result) == 0: print('该节点不存在,增加该节点:') label = 'patient' property_name = '性别' value = '男' ges_util.add_vertex(vertex_id, label, property_name, value) print('再次查询该节点:') statement = "match (p) where id(p) ='" + vertex_id + "' return p" result = ges_util.cypher_query(statement) format_vertex_detail = json.dumps(result[0]['data'], indent=4, ensure_ascii=False) print(format_vertex_detail) print('增加关联边:') source = '北京病例39' target = '北京病例40' label = 'relation' property_name = '类型' value = '家人' ges_util.add_edge(source, target, label, property_name, value) statement = "match (p)-[r]-(m) where id(p)='" + source + "' and id(m)='" + target + "' return r" result = ges_util.cypher_query(statement) format_edge_detail = json.dumps(result[0]['data'], indent=4, ensure_ascii=False) print(format_edge_detail) else: print('该节点已存在')对数据做宏观把控,往往有助于判断疫情趋势。可以对数据从不同维度做统计。 下面会从空间(城市地区风险)、时间(增长趋势)、年龄(患者年龄分布)等维度做统计。首先通过查询语句查看本轮疫情已经波及到的城市及每个城市疫情的严重程度。print('查看有确诊病例的城市并按确诊人数排序:') statement = "match (m:city)<-[r:comfirm]-(p:patient) with m, count(p) as patientNum return id(m), patientNum order by patientNum desc" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result目前中高风险区域的判断,是以区域确诊病例数目划分的。统计不同地域的确诊病例数目,可以判断各个区域的风险。print('将图数据库中的轨迹点,按涉及的确诊病例数目多少进行统计,并从按病例数目从多到少排序:') statement = "match (m:place)<-[s:stay]-(p:patient) with m, count(p) as patientNum return id(m), patientNum order by patientNum desc limit 10" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result此外对此轮疫情患者的年龄做一个调查,以了解此轮疫情中病毒传播的特点。print('查看全国患者年龄构成并按人数排序:') statement = "match (p:patient) where p.年龄 is not null return p.年龄, count(*) as m order by m desc limit 10" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result从时间维度统计确诊病例数目,往往会反映疫情的趋势,疫情是否得到了有效的控制。city_id = '石家庄市' print('查看{}每天确诊人数:'.format(city_id)) statement = "match (p:patient)-[r:comfirm]->(m:city) where id(m)='" + city_id + "' return r.时间, count(*) order by r.时间 asc" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result除了宏观上的统计,我们会关注重点区域(如景区、机场)等,是否和确诊病例发生交集。statement = "match (n)-[r]-(m) where id(n)='胡杨林景区' return n,r,m" result = ges_util.cypher_query(statement,formats=['row','graph']) ges_util.format_cypher_result(result)此外,我们可以对重点人员进行探索,分析其接触史,查询与该患者有直接关联关系的其他患者或地点等,探查可能的传播路径和圈定可疑人群。statement = "match (n)-[r]-(m) where id(n)='额济纳旗病例1' return n,r,m" result = ges_util.cypher_query(statement,formats=['row','graph']) ges_util.format_cypher_result(result)patient_id = '额济纳旗病例1' print('查看{}的活动轨迹:'.format(patient_id)) statement = "match (p:patient)-[r]->(m:place) where id(p)='" + patient_id + "' return id(p), r.时间, type(r), id(m) order by r.时间 asc" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_resultpatient_id = '额济纳旗病例1' print('查看与{}有直接关联的患者:'.format(patient_id)) print('要么同时到过某地点:') statement = "match path = (p:patient)-[r1]-(m:place)-[r2]-(n:patient) where id(p)='" + patient_id + "' and id(p) <> id(n) and r1.时间 = r2.时间 return id(p), id(m), id(n), r1.时间, path" result = ges_util.cypher_query(statement,formats=['row','graph']) format_result = ges_util.format_cypher_result(result) format_resultprint('要么是某种亲密关系:') patient_id = '额济纳旗病例1' statement = "match path = (p:patient)-[r]-(n:patient) where id(p)='" + patient_id + "' return id(p), id(n), r.类型, path" result = ges_util.cypher_query(statement,formats=['row','graph']) format_result = ges_util.format_cypher_result(result) format_result此轮疫情源头是在额济纳旗,那么疫情又是怎么传到北京的呢?statement = "match path=(p:patient)-[]-(m:city) where id(m)='额济纳旗' and not (tostring(id(p)) contains '额济纳旗') return path" statement += " union match path=(p:patient)-[]-(m:city) where id(m)='北京' and not (tostring(id(p)) contains '北京') return path" result = ges_util.cypher_query(statement,formats=['row','graph']) ges_util.format_cypher_result(result)可以枚举所有在北京确诊的患者,通过最短路算法,检查其与额济纳旗之间的关系。import copy statement = "match (p:patient)-[r:comfirm]-(m:city) where id(m)='北京' return collect(distinct id(p))" result = ges_util.cypher_query(statement) print('北京所有的患者:') vertex_list = result["results"][0]['data'][0]['row'][0] print(vertex_list) print('查看此轮疫情入京可能的传播链:') source = '额济纳旗' chain_vid = [] for vid in vertex_list: target = vid avoid_ids = copy.deepcopy(vertex_list) avoid_ids.remove(vid) result = ges_util.filtered_shortest_path(source, target, avoid_ids) if len(result) != 0: chain_vid.append(target) path = '' for vtx_id in result: path = path + vtx_id + '-' print(path[:-1])可以对可能传播链上的节点进行一跳查询,分析传播链之间的关系。result = ges_util.cypher_query('match p=(n)--(m) where id(n) in $idlist and not (m:city) and not(m:patient and not(id(m) in $idlist)) return id(n),p',formats=['row','graph'], param={"idlist":chain_vid}) ges_util.format_cypher_result(result)我们分析了此轮疫情入京可能的传播链,会发现病例24和25是一同去额济纳旗旅游,而病例33、34、35、36和37也是一起去额济纳旗旅游的,另外病例39是因为和外省确诊患者同乘车被感染的,我们等于得到了三条独立的传播链。我们已经知道了疫情的源头(额济纳旗),通过路径搜索功能,可以探查某个患者可能的感染路径。vertex_id = '北京病例2' print('查看{}可能的感染路径:'.format(vertex_id)) result = ges_util.path_query({ "repeat": [ { "operator": "bothV", "vertex_filter": { "property_filter": { "leftvalue": { "id": "" }, "predicate": "NOTIN", "rightvalue": { "value": ["北京"] } } } } ], "until": [ { "vertex_filter": { "property_filter": { "leftvalue": { "id": "" }, "predicate": "=", "rightvalue": { "value": ["额济纳旗"] } } } } ], "times": 5, "queryType": "Tree", "vertices": ["北京病例2"] }) ges_util.format_path_query(result)import time print('连通性分析:') job_id = ges_util.connected_component() result = ges_util.get_job(job_id) if 'errorCode' not in result: for i in range(1000): if result['status'] == 'success': break else: time.sleep(1) result = ges_util.get_job(job_id) com_dict = {} for v_dict in result['data']['outputs']['community']: for key, value in v_dict.items(): statement = "match (p) where id(p)='" + key + "' return labels(p)" v_label = ges_util.cypher_query(statement)['results'][0]['data'][0]['row'][0] if v_label in ['city', 'patient']: com_dict.setdefault(value, []).append(key) print('连通分支个数 : {}'.format(len(com_dict))) for key, value in com_dict.items(): print('连通分支 ' + key + ' 中的点(仅关注城市和患者):') print(value)通过连通性分析,我们暂未发现此轮大连的疫情与额济纳旗有什么关联,可能来自不同的源头。
-
利用图数据库研究 COVID-19 论文数据集COVID-19 大流行的形势依然很严峻,为应对 COVID-19 的传播及其对我们的影响,AI2等提供了一份 COVID-19 开放研究数据集(CORD-19)。CORD-19 数据集是关于冠状病毒的文献集,提供了超过50万篇学术论文的相关信息。我们研究了这份数据集,并用图数据库去组织和挖掘这份数据集蕴含的信息。针对 CORD-19,我们设计了以下的图模型。图模型中包含了两类点,分别是 paper 和 author,也包含了两类边,分别是 write 和 reference,下面的表格说明了它们的详细情况。数据集原始数据集位于COVID-19 开放研究数据集,根据我们设计的图模型重新组织的数据可从此处下载,这份图数据包含70万+ paper 点,173万+ author 点,67万+ reference 边,443万+ write 边。创图利用华为云图数据库 GES 对以上数据集进行探索,需要先在 GES 上创图,GES 创图的详细流程见华为云图引擎服务 GES 实战——创图。使用 GES 查询的预置条件下面封装了使用 GES 查询的预置条件,包括配置相关参数和对所调用 API 接口的封装,如果你对这些不感兴趣,可直接运行而不需要了解细节,这对理解后续具体查询没有影响。配置相关参数的定义。import requests import json import time import pandas as pd pd.set_option('display.max_rows', None) pd.set_option('display.max_columns', None) pd.set_option('display.width', None) pd.set_option('display.max_colwidth', -1) class config(object): def __init__(self, iam_url, user_name, password, domain_name, project_name, eip, project_id, graph_name): self.iam_url = iam_url self.user_name = user_name self.password = password self.domain_name = domain_name self.project_name = project_name self.eip = eip self.project_id = project_id self.graph_name = graph_name self.token = self.get_token() def get_token(self): url = ('https://{}/v3/auth/tokens').format(self.iam_url) headers = {'Content-Type': 'application/json;charset=utf8'} body = json.dumps({"auth": { "identity": { "methods": ["password"], "password": { "user": { "name": self.user_name, "password": self.password, "domain": { "name": self.domain_name } } } }, "scope": { "project": { "name": self.project_name } } } }) r = requests.post(url=url, data=body, verify=False, headers=headers) return r.headers['x-subject-token']config类的参数都是与调用 GES 服务有关的参数,依次为“终端节点”、“IAM 用户名”、“IAM 用户密码”、“IAM 用户所属账户名”、“项目名称”、“公网访问地址”、“项目ID”、“token值”,其获取方式可参考调用 GES 服务业务面 API 相关参数的获取。下面定义了与查询相关的接口。class GESFunc(object): def __init__(self, eip, project_id, graph_name, token): self.eip = eip self.project_id = project_id self.graph_name = graph_name self.headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'} def build_vertex_index(self): url = ('http://{}/ges/v1.0/{}/graphs/{}/indices').format(self.eip, self.project_id, self.graph_name) body = json.dumps({ "indexName": "cypher_vertex_index", "indexType": "GlobalCompositeVertexIndex", "hasLabel": "true", "indexProperty": [] }) r = requests.post(url=url, data=body, headers=self.headers) return r.json()['jobId'] def build_edge_index(self): url = ('http://{}/ges/v1.0/{}/graphs/{}/indices').format(self.eip, self.project_id, self.graph_name) body = json.dumps({ "indexName": "cypher_edge_index", "indexType": "GlobalCompositeEdgeIndex", "hasLabel": "true", "indexProperty": [] }) r = requests.post(url=url, data=body, headers=self.headers) return r.json()['jobId'] def get_job(self, job_id): url = ('http://{}/ges/v1.0/{}/graphs/{}/jobs/{}/status').format(self.eip, self.project_id, self.graph_name, job_id) r = requests.get(url=url, headers=self.headers) output = r.json() return output def summary(self): url = ('http://{}/ges/v1.0/{}/graphs/{}/summary?label_details=true').format(self.eip, self.project_id, self.graph_name) r = requests.get(url=url, headers=self.headers) output = r.json()['data'] return output def cypher_query(self, statement): url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=execute-cypher-query').format(self.eip, self.project_id, self.graph_name) body = json.dumps({ "statements": [ { "statement": statement, "parameters": {}, "resultDataContents": [ "row" ], "includeStats": False } ] }) r = requests.post(url=url, data=body, headers=self.headers) output = r.json()['results'] return output def format_cypher_result(self, json_obj): for x in json_obj: columns = x["columns"] data = x["data"] rows = [] for y in data: rows.append(y["row"]) return pd.DataFrame(rows, columns=columns)创建好图之后,就可以对其进行查询和分析了。# 需填入参数 iam_url = '' user_name = '' password = '' domain_name = '' project_name = '' eip = '' project_id = '' graph_name = '' config = config(iam_url, user_name, password, domain_name, project_name, eip, project_id, graph_name) ges_util = GESFunc(config.eip, config.project_id, config.graph_name, config.token)/home/ma-user/anaconda3/lib/python3.6/site-packages/urllib3/connectionpool.py:858: InsecureRequestWarning: Unverified HTTPS request is being made. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings InsecureRequestWarning)GES 支持 cypher 查询语言,后续的查询示例使用的是 cypher 查询语言。在使用 cypher 查询之前,我们先创建点索引和边索引。print('开始创建点索引:') job_id = ges_util.build_vertex_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('点索引创建完成')开始创建点索引: 点索引创建完成print('开始创建边索引:') job_id = ges_util.build_edge_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('边索引创建完成') 开始创建边索引: 边索引创建完成查询演示下面,我们可以书写并运行标准的 cypher 语言,对这份图数据进行探索了。以下是部分图数据的可视化展示。查询这份图数据的统计信息:print('统计信息:') result = ges_util.summary() res_str = json.dumps(result, indent=4) print(res_str)统计信息: { "vertexNum": 2449792, "labelDetails": { "labelInVertex": { "paper": 712464, "author": 1737328 }, "labelInEdge": { "reference": 670627, "write": 4434198 } }, "edgeNum": 5104825 }列举若干 paper 的信息:print('查询 papers:') statement = "match (n:paper) return n.title limit 5" paper_result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(paper_result) format_result查询 papers:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }n.title0Clinical features of culture-proven Mycoplasma pneumoniae infections at King Abdulaziz University Hospital, Jeddah, Saudi Arabia1Nitric oxide: a pro-inflammatory mediator in lung disease?2Surfactant protein-D and pulmonary host defense3Role of endothelin-1 in lung disease4Gene expression in epithelial cells in response to pneumovirus infection查询某 paper 的 authors:print('查询某 paper 的 authors:') paper = paper_result[0]['data'][1]['row'][0] statement = "match (n:paper)<--(m:author) WHERE n.title = '" + paper + "' return id(m)" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询某 paper 的 authors:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }id(m)0Vliet Albert van der1Eiserich Jason P2Cross Carroll E查询某 paper 被引用的次数:print('查询某 paper 被引用次数:') paper = paper_result[0]['data'][2]['row'][0] statement = "match (n:paper)<--(m:paper) WHERE n.title = '" + paper + "' return count(m)" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询某 paper 被引用次数:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }count(m)05查询某 paper 与哪些 paper 被联合引用及其次数:print('查询某 paper 与哪些 paper 被联合引用及其次数:') paper = paper_result[0]['data'][3]['row'][0] statement = "match (n:paper)<--(p:paper)-->(m:paper) WHERE n.title = '" + paper + "' return m.title, count(*) as p order by p desc limit 5" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询某 paper 与哪些 paper 被联合引用及其次数:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }m.titlep0Clinical course and risk factors for mortality of adult inpatients with COVID-19 in Wuhan, China: a retrospective cohort study21The use of corticosteroid as treatment in SARS was associated with adverse outcomes: a retrospective cohort study12Covid-19: ibuprofen should not be used for managing symptoms, say doctors and scientists13SARS-CoV2: should inhibitors of the renin-angiotensin system be withdrawn in patients with COVID-19?14Network-based drug repurposing for novel coronavirus 2019-nCoV/SARS-CoV-21查询标题带关键字"Virus"的 paper 数量:print('查询标题带关键字"Virus"的 paper 数量:') statement = "MATCH (p:paper) WHERE p.title IS NOT NULL AND p.title CONTAINS('Virus') RETURN count(p)" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询标题带关键字"Virus"的 paper 数量:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }count(p)08354查询标题带关键字"Virus"的 paper,并按发表日期排序:print('查询标题带关键字"Virus"的 paper,并按发表日期排序:') statement = "MATCH (p:paper) WHERE p.publish_time IS NOT NULL AND p.title IS NOT NULL AND p.title " \ "CONTAINS('Virus') RETURN p.title, p.publish_time ORDER BY p.publish_time DESC LIMIT 5" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询标题带关键字"Virus"的 paper,并按发表日期排序:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }p.titlep.publish_time0Rates of Coinfection Between SARS-CoV-2 and Other Respiratory Viruses in Korea20221Predict Mortality in Patients Infected with COVID-19 Virus Based on Observed Characteristics of the Patient using Logistic Regression2021-12-312P140 TeCC (TeleMedicine, Cystic Fibrosis, Corona-Virus) study in a previous telemedicine-naive centre: clinical challenges, outcomes, and user experience in the first six months of a global pandemic2021-12-313Virus structure and structure-based antivirals2021-12-314Virus-associated ribozymes and nano carriers against COVID-19.2021-12-01查询 paper 被引用的次数并以此排序:print('查询 paper 被引用的次数并以此排序:') statement = "match (m:paper)<--(p:paper) with m, count(p) as citedNum return m.title, citedNum order by citedNum desc limit 5" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询 paper 被引用的次数并以此排序:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }m.titlecitedNum0Publisher's Note54361Clinical course and risk factors for mortality of adult inpatients with COVID-19 in Wuhan, China: a retrospective cohort study38242A pneumonia outbreak associated with a new coronavirus of probable bat origin36773Epidemiological and clinical characteristics of 99 cases of 2019 novel coronavirus pneumonia in Wuhan, China: a descriptive study30914A new coronavirus associated with human respiratory disease in China1975列举若干 author 的信息:print('查询 authors:') statement = "match (n:author) return id(n) limit 5" author_result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(author_result) format_result查询 authors:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }id(n)0Madani Tariq A1Al-Ghamdi Aisha A2Vliet Albert van der3Eiserich Jason P4Cross Carroll E查询某 author 的合作者及合作次数:print('查询某 author 的合作者及合作次数:') author = author_result[0]['data'][0]['row'][0] statement = "match (n:author)-->(p:paper)<--(m:author) WHERE id(n) = '" + author + "' return id(m), count(*) as p order by p desc limit 5" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询某 author 的合作者及合作次数:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }id(m)p0Petersen Eskild41Zumla Alimuddin42Drosten Christian43Hui David S44I Azhar Esam4查询某 author 论文被引用次数:print('查询某 author 论文被引用次数:') author = author_result[0]['data'][0]['row'][0] statement = "match (m:author)-->(p:paper)<--(n:paper) where id(m) = '" + author + "' return count(n)" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询某 author 论文被引用次数:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }count(n)09查询论文互相引用情况:print('查询论文互相引用情况:') statement = "match (m:paper)-->(p:paper)-->(n:paper) where id(m) <> id(p) and id(m) = id(n) return m,p limit 10" result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) format_result查询论文互相引用情况:.dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }mp0{'journal': 'Rev Mal Respir', 'publish_time': '2008-01-03', 'title': 'Chronique pour une pandémie grippale annoncée'}{'journal': 'Revue des Maladies Respiratoires', 'publish_time': '2004-12-31', 'title': 'La communication sur le SRAS : un outil essentiel de santé publique'}1{'journal': 'Intensive Care Med', 'publish_time': '2020-03-23', 'title': 'Acute respiratory distress syndrome-attributable mortality in critically ill patients with sepsis'}{'journal': 'J Biomed Sci', 'publish_time': '2003-10-18', 'title': 'Acute respiratory distress syndrome'}2{'journal': None, 'publish_time': '2020-11-19', 'title': 'Curvature domains in V4 of macaque monkey'}{'journal': None, 'publish_time': '2020-11-19', 'title': 'Curvature-processing domains in primate V4'}3{'journal': None, 'publish_time': '2020-11-19', 'title': 'Curvature-processing domains in primate V4'}{'journal': None, 'publish_time': '2020-11-19', 'title': 'Curvature domains in V4 of macaque monkey'}4{'journal': 'Theor Med Bioeth', 'publish_time': '2020-12-17', 'title': 'Philosophical investigations into the essence of pediatric suffering'}{'journal': 'Theor Med Bioeth', 'publish_time': '2021-01-05', 'title': 'What we talk about when we talk about pediatric suffering'}5{'journal': 'Theor Med Bioeth', 'publish_time': '2020-12-17', 'title': 'Philosophical investigations into the essence of pediatric suffering'}{'journal': 'Theor Med Bioeth', 'publish_time': '2020-12-07', 'title': 'Relational suffering and the moral authority of love and care'}6{'journal': 'Theor Med Bioeth', 'publish_time': '2020-12-17', 'title': 'Philosophical investigations into the essence of pediatric suffering'}{'journal': 'Theor Med Bioeth', 'publish_time': '2020-12-07', 'title': 'Our suffering and the suffering of our time'}7{'journal': 'PLoS One', 'publish_time': '2021-04-01', 'title': 'Prevalence of depression, anxiety and associated factors among school going adolescents in Bangladesh: Findings from a cross-sectional study'}{'journal': 'BMC Psychiatry', 'publish_time': '2021-05-25', 'title': 'Prevalence and correlates of anxiety and depression in frontline healthcare workers treating people with COVID-19 in Bangladesh'}8{'journal': 'PLoS Comput Biol', 'publish_time': '2020-09-21', 'title': 'Fast estimation of time-varying infectious disease transmission rates'}{'journal': 'PLoS Biol', 'publish_time': '2020-12-21', 'title': 'Patterns of smallpox mortality in London, England, over three centuries'}9{'journal': 'Commun Biol', 'publish_time': '2021-04-22', 'title': 'Structure and assembly of double-headed Sendai virus nucleocapsids'}{'journal': 'PLoS Pathog', 'publish_time': '2021-07-16', 'title': 'CryoEM structure of the Nipah virus nucleocapsid assembly'}
-
VSCode一键接入Notebook体验算法套件快速完成水表读数本示例围绕真实AI需求场景,介绍VSCode一键接入Notebook体验算法套件快速完成水表读数的使用流程。算法开发套件中目前提供自研(ivg系列)和开源(mm系列)共两套算法资产,可应用于分类、检测、分割和OCR等任务中。本示例中将组合使用自研分割算法(ivgSegmentation)和开源OCR算法(mmOCR)完成水表读数识别项目,并使用算法开发套件将其部署为华为云在线服务。说明:本案例教程仅适用于“华北-北京四”区域,新版Notebook。准备数据登录OBS控制台,创建OBS对象桶,区域选择“华北-北京四”。登录ModelArts控制台,选择控制台区域为“华北-北京四”。在“全局配置”页面查看是否已经配置授权,允许ModelArts访问OBS。如果没有配置授权,请参考配置访问授权(全局配置)添加授权。分别下载本案例的数据集,水表表盘分割数据集和水表表盘读数OCR识别数据集到OBS桶中,OBS路径示例如下obs://{OBS桶名称}/water_meter_segmentation 水表表盘分割数据集obs://{OBS桶名称}/water_meter_crop 水表表盘读数OCR识别数据集说明:从AIGallery下载数据集免费,但是数据集存储在OBS桶中会收取少量费用,具体计费请参见OBS价格详情页,案例使用完成后请及时清除资源和数据。准备开发环境在“ModelArts控制台 > 开发环境 > Notebook(New)”页面中,创建基于pytorch1.4-cuda10.1-cudnn7-ubuntu18.04镜像,类型为GPU的Notebook,具体操作请参见创建Notebook实例章节。本案例需要使用VS Code 远程连接Notebook,需要开启SSH远程开发。图1 创建Notebook实例 1.实例的密钥文件需要下载至本地的如下目录或其子目录中:Windows:C:\Users{{user}}Mac/Linux: Users/{{user}}2.在ModelArts控制台->开发环境 Notebook,单击“操作”列的“更多 > VS Code接入”。 如果本地已安装VS Code,请单击“打开”,进入“Visual Studio Code”页面。如果本地未安装VS Code,请根据实际选择“win”或“其他”下载并安装VS Code。VS Code安装请参考安装VS Code软件。如果用户之前未安装过ModelArts VS Code插件,此时会弹出安装提示,请单击“Install and Open”进行安装;如果之前已经安装过插件,则不会有该提示,请跳过此步骤,直接执行后面步骤安装过程预计1~2分钟,安装完成后右下角会弹出对话框,请单击“Reload Window and Open”。在弹出的提示中,勾选“Don't ask again for this extension”,然后单击"Open"。3.远程连接Notebook实例。远程连接执行前,会自动在(Windows:C:\Users{{user}}.ssh或者downloads,Mac/Linux: Users/{{user}}/.ssh或者downloads)目录下根据密钥名称查找密钥文件,如果找到则直接使用该密钥打开新窗口并尝试连接远程实例,此时无需选择密钥。如果未找到会弹出选择框,请根据提示选择正确的密钥。如果密钥选择错误,则弹出提示信息,请根据提示信息选择正确密钥。当弹出提醒实例连接失败,请关闭弹窗,并查看OUTPUT窗口的输出日志,请查看FAQ并排查失败原因。使用算法套件进行开发Step1 创建算法工程成功接入之后,在VS Code页面点击文件->打开文件夹,选择如下文件夹打开 新建终端 在work目录下执行ma-cli createproject命令创建工程,根据提示输入工程名称,例如:water_meter。然后直接回车选择默认参数,并选择跳过资产安装步骤(选择6)。执行以下命令进入工程目录。cd water_meter执行以下命令拷贝项目数据到Notebook中。python manage.py copy --source {obs_dataset_path} --dest ./data/raw/water_meter_crop python manage.py copy --source {obs_dataset_path} --dest ./data/raw/water_meter_segmentation说明:{obs_dataset_path}路径为Step1 准备数据中下载到OBS中的数据集,比如“obs://{OBS桶名称}/water_meter_segmentation”和“obs://{OBS桶名称}/water_meter_crop”Step2 使用deeplabv3完成水表区域分割任务首先安装ivgSegmentation套件。python manage.py install algorithm ivgSegmentation==1.0.2如果提示ivgSegmentation版本不正确,可以通过命令python manage.py list algorithm查询版本。安装ivgSegmentation套件后,在界面左侧的工程目录中进入“./algorithms/ivgSegmentation/config/sample”文件夹中查看目前支持的分割模型,以sample为例(sample默认的算法就是deeplabv3),文件夹中包括config.py(算法外壳配置)和deeplabv3_resnet50_standard-sample_512x1024.py(模型结构)。表盘分割只需要区分背景和读数区域,因此属于二分类,需要根据项目所需数据集对配置文件进行修改,如下所示:修改./algorithms/ivgSegmentation/config/sample/config.py文件。# config.py alg_cfg = dict( ... data_root='data/raw/water_meter_segmentation', # 修改为真实路径本地分割数据集路径 ... )修改完后按Ctrl+S保存。修改./algorithms/ivgSegmentation/config/sample/deeplabv3_resnet50_standard-sample_512x1024.py文件。# deeplabv3_resnet50_standard-sample_512x1024.py gpus=[0] ... data_cfg = dict( ... num_classes=2, # 修改为2类 ... ... train_scale=(512, 512), # (h, w)#size全部修改为(512, 512) ... train_crop_size=(512, 512), # (h, w) ... test_scale=(512, 512), # (h, w) ... infer_scale=(512, 512), # (h, w) )修改完按Ctrl+S保存。在water_meter工程目录下,安装deeplabv3预训练模型。python manage.py install model ivgSegmentation:deeplab/deeplabv3_resnet50_cityscapes_512x1024训练分割模型。(推荐使用GPU进行训练)# shell python manage.py run --cfg algorithms/ivgSegmentation/config/sample/config.py --gpus 0训练好的模型会保存在指定位置中,默认为output/deeplabv3_resnet50_standard-sample_512x1024/checkpoints/中。验证模型效果。模型训练完成后,可以在验证集上计算模型的指标,首先修改配置文件的模型位置。修改./algorithms/ivgSegmentation/config/sample/config.py。# config.py alg_cfg = dict( ... load_from='./output/deeplabv3_resnet50_standard-sample_512x1024/checkpoints/checkpoint_best.pth.tar', # 修改训练模型的路径 ... )# shell python manage.py run --cfg algorithms/ivgSegmentation/config/sample/config.py --pipeline evaluate模型推理。模型推理能够指定某一张图片,并且推理出图片的分割区域,并进行可视化,首先需要指定需要推理的图片路径。修改./algorithms/ivgSegmentation/config/sample/config.pyalg_cfg = dict( ... img_file = './data/raw/water_meter_segmentation/image/train_10.jpg' # 指定需要推理的图片路径 ... )执行如下命令推理模型效果。# shell python manage.py run --cfg algorithms/ivgSegmentation/config/sample/config.py --pipeline infer推理输出的图片路径在./output/deeplabv3_resnet50_standard-sample_512x1024下。导出SDK。算法开发套件支持将模型导出成一个模型SDK,方便进行模型部署等下游任务。# shell python manage.py export --cfg algorithms/ivgSegmentation/config/sample/config.py --is_deployStep3 水表读数识别首先安装mmocr套件。python manage.py install algorithm mmocr安装mmocr套件后,./algorithms/mmocr/config/textrecog文件夹中包括config.py(算法外壳配置),需要根据所需算法和数据集路径修改配置文件。以下以robust_scanner算法为例。修改./algorithms/mmocr/algorithm/configs/textrecog/robustscanner_r31_academic.py,# robustscanner_r31_academic.py ... train_prefix = 'data/raw/water_meter_crop/' # 修改数据集路径改为水表ocr识别数据集路径 train_img_prefix1 = train_prefix + 'train' train_ann_file1 = train_prefix + 'train.txt' test_prefix = 'data/raw/water_meter_crop/' test_img_prefix1 = test_prefix + ‘val’ test_ann_file1 = test_prefix + ‘val.txt’安装robust_scanner预训练模型。python manage.py install model mmocr:textrecog/robust_scanner/robustscanner_r31_academic训练OCR模型。初次使用mmcv时需要编译mmcv-full,该过程较慢,可以直接使用官方预编译的依赖包。预编译包URL: cid:link_9pip install https://download.openmmlab.com/mmcv/dist/cu101/torch1.6.0/mmcv_full-1.3.8-cp37-cp37m-manylinux1_x86_64.whl将./algorithms/mmocr/config/textrecog/config.py中的epoch(迭代数量)改为2,如下图所示:python manage.py run --cfg algorithms/mmocr/config/textrecog/config.py训练好的模型会保存在指定位置中,默认为output/${algorithm}中。验证模型效果。模型训练完成后,可以在验证集上计算模型的指标,首先修改配置文件的模型位置。修改./algorithms/mmocr/config/textrecog/config.py# config.py ... model_path = './output/robustscanner_r31_academic/latest.pth' ...# shell python manage.py run --cfg algorithms/mmocr/config/textrecog/config.py --pipeline evaluate模型推理。模型推理能够指定某一张图片,并且推理出图片的分割区域,并进行可视化。首先需要指定待推理的图片路径,修改algorithms/mmocr/config/textrecog/config.py文件,具体如下。修改./algorithms/mmocr/algorithm/configs/textrecog/robust_scanner/config.py... infer_img_file='./data/raw/water_meter_crop/val/train_10.jpg' # 指定需要推理的图片路径 ...# shell python manage.py run --cfg algorithms/mmocr/config/textrecog/config.py --pipeline infer推理输出的图片路径在output/robustscanner_r31_academic/vis下导出SDK。# shell python manage.py export --cfg algorithms/mmocr/config/textrecog/config.pyStep4 部署为在线服务本次展示仅部署OCR服务, 包括本地部署和线上部署, 部署上线后调用部署服务进行本地图片的推理,获取水表的预测读数。部署在线服务,需要指定OBS桶以便保存部署所需要的文件。1.在algorithms/mmocr/config/textrecog/config.py文件中配置OBS桶,即obs_bucket=。 2.执行下述命令:python manage.py export --cfg algorithms/mmocr/config/textrecog/config.py --is_deploy # 导出部署模型 python manage.py deploy --cfg algorithms/mmocr/config/textrecog/config.py # 本地部署 python manage.py deploy --cfg algorithms/mmocr/config/textrecog/config.py --launch_remote#在线部署,会耗时一会儿,请耐心等待点击此处,查看部署成功的在线服务Step5 清除资源和数据通过此示例学习完成创建算法套件流程后,如果不再使用,建议您清除相关资源,避免造成资源浪费和不必要的费用。停止Notebook:在“Notebook”页面,单击对应实例操作列的“停止”。删除数据:点击此处,前往OBS控制台,删除上传的数据,然后删除文件夹及OBS桶。
-
电商风控图模板互联网电商为扩张业务、推广产品,会采用奖励、返点、打折等手段实现用户和商户裂变。例如,商品的链接在商户或客户间转发时将会附带一标识ID,若商品被购买,具有该标识ID的商户或用户均会得到一笔奖励。而在裂变过程中,存在薅羊毛、虚开账号等损害平台或商家利益的行为。基于图模型的互联网电商风控,将提供客群管控、羊毛党发现等解决方案,有效帮助客户降低损失。 图模型以下是电商风控图模型元数据。 创建图引擎实例创建图引擎实例的详细流程可参考链接:cid:link_1Notebook源码配置参数的定义。config类的属性依次为“终端节点”、“IAM 用户名”、“IAM 用户密码”、“IAM 用户所属账户名”、“项目名称”、“公网访问地址”、“项目ID”、“token值”,其获取方式可参考链接:https://bbs.huaweicloud.com/blogs/296930。import requests import json import time class config(object): def __init__(self, iam_url, user_name, password, domain_name, project_name, eip, project_id, graph_name): self.iam_url = iam_url self.user_name = user_name self.password = password self.domain_name = domain_name self.project_name = project_name self.eip = eip self.project_id = project_id self.graph_name = graph_name self.token = self.get_token() def get_token(self): url = ('https://{}/v3/auth/tokens').format(self.iam_url) headers = {'Content-Type': 'application/json;charset=utf8'} body = json.dumps({"auth": { \ "identity": { \ "methods": ["password"], \ "password": { \ "user": { \ "name": self.user_name, \ "password": self.password, \ "domain": { \ "name": self.domain_name \ } \ } \ } \ }, \ "scope": { \ "project": { \ "name": self.project_name \ } \ } \ } \ }) r = requests.post(url=url, data=body, verify=False, headers=headers) return r.headers['x-subject-token']调用API接口的定义。class GESFunc(object): def __init__(self, eip, project_id, graph_name, token): self.graph_name = graph_name self.headers = {'X-Auth-Token': token, 'Content-Type': 'application/json'} self.project_id = project_id self.eip = eip def connected_component(self): url = ('http://{}/ges/v1.0/{}/graphs/{}/subgraphs/action?action_id=execute-algorithm').format(self.eip, self.project_id, self.graph_name) subgraph_creator = { "name": "filtered", "parameters": { "vertex_filter": { "property_filter": { "leftvalue": { "label_name": "labelName" }, "predicate": "=", "rightvalue": { "value": "store" } } } }} parameters = {} body = json.dumps({"algorithmName": "connected_component", "graphName": self.graph_name, "subgraphCreator": subgraph_creator, "parameters": parameters}) r = requests.post(url=url, data=body, headers=self.headers) return r.json()['jobId'] def get_job(self, job_id): url = ('http://{}/ges/v1.0/{}/graphs/{}/jobs/{}/status').format(self.eip, self.project_id, self.graph_name, job_id) r = requests.get(url=url, headers=self.headers) output = r.json()['data']['outputs'] return output def delete_edge(self, source_vertex, target_vertex): url = ('http://{}/ges/v1.0/{}/graphs/{}/edges?source={}&target={}').format(self.eip, self.project_id, self.graph_name, source_vertex, target_vertex) r = requests.delete(url=url, headers=self.headers) output = r.json()['result'] return output def add_edge(self, source_vertex, target_vertex, label): url = ('http://{}/ges/v1.0/{}/graphs/{}/edges').format(self.eip, self.project_id, self.graph_name) body = json.dumps({"source": source_vertex, "target": target_vertex, "label": label }) time.sleep(2) r = requests.post(url=url, data=body, headers=self.headers) time.sleep(2) output = r.json()['result'] return output def get_cypher(self, statement, idList): url = ('http://{}/ges/v1.0/{}/graphs/{}/action?action_id=execute-cypher-query').format(self.eip, self.project_id, self.graph_name) statements = [{ "statement": statement, "resultDataContents": ["row"], "parameters": {"idList": idList}, "includeStats": False }] body = json.dumps({"statements": statements}) r = requests.post(url=url, data=body, headers=self.headers) output = r.json()['results'] return output def get_community_info(self, output): community_list = output['community'] community_dict = {} for community in community_list: vertex_id = list(community.keys())[0] community_id = community[vertex_id] if community_id not in community_dict: component = set() component.add(vertex_id) community_dict[community_id] = component else: community_dict[community_id].add(vertex_id) return community_dict def community_change_info(self, monitor_id, old_community_info, new_community_info): monitor_set = set() new_monitor_set = set() change_id_list = [] for community_id in old_community_info: if monitor_id in old_community_info[community_id]: monitor_set = old_community_info[community_id] for community_id in new_community_info: if monitor_id in new_community_info[community_id]: new_monitor_set = new_community_info[community_id] for id in monitor_set: if id not in new_monitor_set: change_id_list.append(id) return change_id_list创建好图之后,就可以对其进行查询和分析了。config = config('', '', '', '', '', '', '', '') test = GESFunc(config.eip, config.project_id, config.graph_name, config.token)羊毛党发现在电商中,可能存在客户“薅羊毛”的行为。例如,电商平台为用户裂变,对新注册账号会有一些奖励活动,用户为获得更多奖励会在平台上注册多个账号。然而,过多的虚假账号会造成电商平台成本上升,利润下降。在本案例中,首先,通过电话号码(1XX725080460)和地址ID(0f1061e6-e903-11eb-8084-643e8cb8138c)找出马甲客户,该客户为给定电话号码和地址ID的共同邻居,共找出两个节点VRG 7和VRG 48。然后,通过VRG 7和VRG 48反方向查询可找到客户节点VRG 8和VRG 49。通过对VRG 7、VRG 48、VRG 8和VRG 49相互关联性的分析,可研判VRG 8和VRG 49是否是马甲客户的帮凶,还是普通受欺诈的无辜百姓。idList = []statement = "match (a)-[r1]->(common)<-[r2]-(b) where id(a)='0f1061e6-e903-11eb-8084-643e8cb8138c' and id(b)='1XX725080460' return common"result = test.get_cypher(statement, idList)通过地址和电话信息发现羊毛党可能为:print(result)[{'columns': ['common'], 'data': [{'row': [{'billing_street': '李新庄镇', 'billing_province': '山东省', 'billing_city': '菏泽市', 'email_address': 'anonymized@corporation.com', 'identity_id': '623062109986XXXX', 'last_activity_date': '2018-10-24', 'customer_name': 'VRG 7', 'billing_district': '单县', 'created_date': '2018-10-24'}], 'meta': [{'id': '0011R00001z10NgQAI', 'type': 'node', 'labels': ['customer']}]}, {'row': [{'billing_street': '李新庄镇', 'billing_province': '山东省', 'billing_city': '菏泽市', 'email_address': 'anonymized@corporation.com', 'identity_id': '623062109986XXXX', 'last_activity_date': '2018-10-24', 'customer_name': 'VRG 48', 'billing_district': '单县', 'created_date': '2018-10-24'}], 'meta': [{'id': '0013600000FSw9KAAT', 'type': 'node', 'labels': ['customer']}]}]}]statement = "match (a)-[r1]->(common)<-[r2]-(b), (common)-[r3]->(c) where id(a)='0f1061e6-e903-11eb-8084-643e8cb8138c' and id(b)='1XX725080460' return c"result = test.get_cypher(statement, idList)与该羊毛党具有较大相关性的客户为:print(result)[{'columns': ['c'], 'data': [{'row': [{'billing_street': '东二营镇', 'billing_province': '天津市', 'billing_city': '市辖区', 'email_address': 'anonymized@corporation.com', 'identity_id': '623062573189XXXX', 'last_activity_date': '2018-10-24', 'customer_name': 'VRG 8', 'billing_district': '蓟州区', 'created_date': '2018-10-24'}], 'meta': [{'id': '0011R00001z1109QAA', 'type': 'node', 'labels': ['customer']}]}, {'row': [{'billing_street': '四平市铁东区城东乡', 'billing_province': '吉林省', 'billing_city': '四平市', 'email_address': 'anonymized@corporation.com', 'identity_id': '6226632105938XXXX', 'last_activity_date': '2018-08-02', 'customer_name': 'VRG 49', 'billing_district': '铁东区', 'created_date': '2016-04-10'}], 'meta': [{'id': '0013600000FSwALAA1', 'type': 'node', 'labels': ['customer']}]}]}]time.sleep(5)社群管控商户的裂变呈现树状结构,较为顶层的商户往往体量较大,收益较高。然而,底层商户的频繁变动可能对顶层商户的收益产生影响。平台希望管控好底层商户从而实现顶层商户的稳定。本案例中,针对给定的顶层商户(0031R00001vSBTkQAO),监控其发展的商户是否较为稳定。首先,检测出给定商户所在的社群,然后,通过删边操作模拟随时间变化的下线流失,并再次检测给定商户社群,最后,给出变化的商户的名单。通过检测发现,0031R00001vRlHbQAK、0031R00001vRlYiQAK、0031R00001uqjUQQAY、0031R00001uoQr5QAE、0031R00001vS9WcQAK、0031R00001vSB7DQAW这六个下线脱离了与给定顶层商户的联系并自立门户,将对给定顶层商户产生一定的经济损失。job_id = test.connected_component()result = test.get_job(job_id)old_community_info = test.get_community_info(result)monitor_id = '0031R00001vSBTkQAO'删边模拟顶层客户下线的流失:result = test.delete_edge('Samantha-Mars', '0031R00001uoQr5QAE')job_id = test.connected_component()result = test.get_job(job_id)test.add_edge('Samantha-Mars', '0031R00001uoQr5QAE', 'transfer')new_community_info = test.get_community_info(result)change_id_list = test.community_change_info(monitor_id, old_community_info, new_community_info)变化的商户为:print(change_id_list)['0031R00001vRlYiQAK', '0031R00001vSB7DQAW', '0031R00001uqjUQQAY', '0031R00001vS9WcQAK', '0031R00001vRlHbQAK', '0031R00001uoQr5QAE']
-
教育知识图谱背景为了解决教师在教学过程中知识框架梳理、组卷策略等问题, 以及学生在学习过程中认知过载、学习迷航等问题,知识图谱被引入教育行业。针对这些需求,本文以高中数学学科知识图谱为例,提供了教育图谱在知识导航、组卷策略等方面的应用示例。数据建模首先我们需要构建教育图谱的图数据模型。图数据模型中的实体包括知识点和题目,实体关系包括知识点之间的包含关系以及知识点和题目之间的关联关系,具体的图模型见下图:图模型中包含了两类点和两类边,元信息说明如下: 数据集本文采集了高中数学几乎所有的知识点和部分题目信息,根据设计的图模型对数据格式进行转换,转换后的数据可从此处下载。这份数据包含7W+个点,14W+条边。创图本文将使用华为云图数据库 GES 对以上数据集进行探索和演示,我们需要先在 GES 中创图并将以上数据集导入,详细指导流程可参见华为云图引擎服务 GES 实战——创图。使用 GES 查询的预置条件下面文件封装了使用 GES 查询的预置条件,包括配置相关参数和对所调用 API 接口的封装。import moxing as mox mox.file.copy('obs://obs-aigallery-zc/GES/edu_knowledge_graph/config.py', 'config.py')config类的参数都是与调用 GES 服务有关的参数,依次为“终端节点”、“IAM 用户名”、“IAM 用户密码”、“IAM 用户所属账户名”、“项目名称”、“公网访问地址”、“项目ID”、“token值”,其获取方式可参考调用 GES 服务业务面 API 相关参数的获取。下面文件封装了与查询相关的接口。mox.file.copy('obs://obs-aigallery-zc/GES/edu_knowledge_graph/ges_func.py','ges_func.py')创建好图之后,就可以对其进行查询和分析了。import time import json from config import config from ges_func import GESFunc # 需填入参数 iam_url = '' user_name = '' password = '' domain_name = '' project_name = '' eip = '' project_id = '' graph_name = '' config = config(iam_url, user_name, password, domain_name, project_name, eip, project_id, graph_name) ges_util = GESFunc(config.eip, config.project_id, config.graph_name, config.token)GES 支持 cypher 查询语言,后续的查询示例使用到 cypher 查询语言。在使用 cypher 查询之前,我们先创建点索引和边索引(可直接点击GES画布右下角“创建索引”按钮)。print('开始创建点索引:') job_id = ges_util.build_vertex_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('点索引创建完成')print('开始创建边索引:') job_id = ges_util.build_edge_index() job_result = ges_util.get_job(job_id) if 'errorCode' not in job_result: for i in range(100): if job_result['status'] == 'success': break else: time.sleep(1) job_result = ges_util.get_job(job_id) print('边索引创建完成')查询演示查询这份图数据的统计信息:print('统计信息:') result = ges_util.summary() format_result = json.dumps(result, indent=4) print(format_result)统计信息: { "vertexNum": 70528, "labelDetails": { "labelInVertex": { "knowledge_point": 2163, "exercises": 68365 }, "labelInEdge": { "include": 2145, "related": 138742 } }, "edgeNum": 140887 }可以看到,这份知识图谱包含2000多个知识点和接近七万的题目。知识点图谱输入某个知识点,将展示该知识点所有下游知识点。knowledge_point = '集合的基本运算' statement = "g.V('{}').repeat(outE().otherV().hasLabel('knowledge_point')).emit().path()".format(knowledge_point) result = ges_util.gremlin_query(statement)['results'] format_result = json.dumps(result, indent=4, ensure_ascii=False) # print(format_result)题目知识点追踪输入题目,将追踪到该题目考察的所有知识点。exercises_id = '1' statement = "g.V('{}').repeat(inE().otherV().hasLabel('knowledge_point')).emit().path()".format(exercises_id) result = ges_util.gremlin_query(statement)['results'] format_result = json.dumps(result, indent=4, ensure_ascii=False) # print(format_result)知识点关系查询knowledge_point_1 = "集合" knowledge_point_2 = "空集" result = ges_util.n_paths(knowledge_point_1, knowledge_point_2) format_result = json.dumps(result, indent=4, ensure_ascii=False) # print(format_result)某知识点包含哪些知识点knowledge_point = "集合" statement = "match (n)-->(m:knowledge_point) where id(n) = '{}' return id(m)".format(knowledge_point) result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) print(format_result)与某知识点相关的题目的数量knowledge_point = "描述法表示集合" statement = "match (n)-->(m:exercises) where id(n) = '{}' return count(m)".format(knowledge_point) result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) print(format_result)包含某知识点的题型分布knowledge_point = "描述法表示集合" statement = "match (n)-->(m:exercises) where id(n) = '{}' return m.type, count(*)".format(knowledge_point) result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) print(format_result) m.type count(*) 0 解答题 127 1 单选题 277 2 多选题 15 3 填空题 118 4 双空题 13 5 概念填空 4某知识点会和哪些其他知识点一起考察knowledge_point = "描述法表示集合" statement = "match (n)-->(m:exercises)<--(p:knowledge_point) where id(n) = '{}' return id(p), count(*)".format(knowledge_point) result = ges_util.cypher_query(statement) format_result = ges_util.format_cypher_result(result) print(format_result)自动组卷为了考察学生的学习情况,老师需要组织一套试卷,该例子中,限制了考察的知识点范围,以及各个题型的题目数量,skip的含义是在所有满足条件的试卷中选择某一套试卷。knowledge_point = "描述法表示集合" single_choice_num = 4 multiple_choice_num = 2 gap_filling_num = 2 free_response_question_num = 2 skip = 1 exercises_list = ges_util.test_paper_composition(single_choice_num, multiple_choice_num, gap_filling_num, free_response_question_num, knowledge_point, skip) format_result = ges_util.format_cypher_result(exercises_list) print(format_result)拿到一份试卷后,我们可以看看这套试卷整体的难易程度,当然也可以将试卷的难易程度作为组织试卷的限制条件。print('试题的平均通过率(难度):') ave_pass_rate = ges_util.ave_pass_rate(exercises_list, 10) print(ave_pass_rate)
-
1 ModelBox社区案例 - 使用YOLOX做垃圾分类本案例将使用YOLOX模型,实现一个简单的垃圾分类应用,最终效果如下所示:本案例所需资源(代码、模型、测试数据等)均可从garbage_det下载1.1 模型训练与转换模型采用的是YOLOX网络结构,YOLOX是YOLO系列的优化版本,引入了解耦头、数据增强、无锚点以及标签分类等目标检测领域的优秀进展,拥有较好的精度表现,同时对工程部署友好。训练使用的是“华为云杯”生活垃圾图片分类数据集,该数据集包含一次性快餐盒、果皮果肉、旧衣服等44个类别,共14964张图片。其中,训练集与验证集划分比例为4/1,下图为模型迭代个300个Epoch取得的结果:ModelArts提供了包括数据标注,训练环境,预置算法在内的丰富的功能,甚至可以通过订阅预置算法实现0代码的模型训练工作。当然你也可以在本地训练自己的模型。我们假设你现在已经拥有了训练好的模型,接下来我们需要将训练好的模型转换成为可以在开发板上运行的模型。我们发布了开发板模型转换案例,参见RK3568模型转换验证案例:在这个案例中我们演示了从环境适配到模型的转换验证的全流程样例代码,开发者可以通过“Run in ModelArts”一键将Notebook案例在ModelArts控制台快速打开、运行以及进行二次开发等操作。1.2 应用开发打开VS Code,连接到ModelBox sdk所在目录或者远程开发板,开始进行垃圾分类应用的开发。下面以RK3568版本为例进行说明,其他版本与之类似。1.2.1 1)下载模板执行python solution.py -l可看到当前公开的技能模板:███ $ python solution.py -l...Solutions name:mask_det_yolo3…hand_det_yoloxhand_tracking_yoloxsingle_hand_pose_yolox_mbv2multi_hand_pose_yolox_mbv2结果中的hand_det_yolox即为手部检测应用模板,可使用如下命令下载模板:███ $ python solution.py -s hand_det_yolox...solution.py工具的参数中,-l 代表list,即列出当前已有的模板名称;-s 代表solution-name,即下载对应名称的模板。下载下来的模板资源,将存放在ModelBox核心库的solution目录下。1.2.2 2)创建工程在ModelBox sdk目录下使用create.py创建garbage_det工程,末尾-s参数,表示将使用后面参数值代表的模板创建工程,而不是创建空的工程。███/modelbox$ python create.py -t server -n garbage_det -s hand_det_yoloxsdk version is modelbox-xxxsuccess: create garbage_det in ███/modelbox/workspaceworkspace目录下将创建出garbage_det工程,工程内容如下所示:garbage_det|--bin│ |--main.bat:应用执行入口│ |--mock_task.toml:应用在本地执行时的输入输出配置,此应用默认使用本地视频文件为输入源,最终结果输出到另一本地视频文件,可根据需要修改|--CMake:存放一些自定义CMake函数|--data:存放应用运行所需要的图片、视频、文本、配置等数据│ |--hand.mp4:手部检测测试用视频文件—>替换为自己的视频|--dependence│ |--modelbox_requirements.txt:应用运行依赖的外部库在此文件定义|--etc│ |--flowunit:应用所需的功能单元存放在此目录│ │ |--cpp:存放C++功能单元编译后的动态链接库,此应用没有C++功能单元│ │ |--yolox_post:手部检测使用的是YOLOX模型,此处即为后处理功能单元(修改toml文件的类别参数和py文件的draw函数)|--flowunit_cpp:存放C++功能单元的源代码,此应用没有C++功能单元|--graph:存放流程图│ |--garbage_det.toml:默认流程图,使用本地视频文件作为输入源│ |--garbage_det_camera.toml:摄像头输入对应的流程图│ |--modelbox.conf:modelbox相关配置|--hilens_data_dir:存放应用输出的结果文件、日志、性能统计信息|--model:推理功能单元目录│ |--detect_hand:手部检测推理功能单元│ │ |--detect_hand.toml:手部检测推理功能单元的配置文件│ │ |--yolox_hand.onnx:手部检测onnx模型—>更改为自己的模型|--build_project.sh:应用构建脚本|--CMakeLists.txt|--rpm:打包rpm时生成的目录,将存放rpm包所需数据|--rpm_copyothers.sh:rpm打包时的辅助脚本1.2.3 3)修改后处理功能单元 yolox_posta. 修改yolox_post.toml流程图,将其内容修改为(以Windows版ModelBox为例):b. 修改yolox_post.py的draw函数实现如下: def draw(self, img, bboxes): h, w, c = img.shape thickness = 2 font_scale = 1 text_font = cv2.FONT_HERSHEY_SIMPLEX clss_to_text = { 0: "Disposable snack box", 1: "Books and papers", 2: "Power bank", 3: "Leftovers", 4: "Package", 5: "Trash can", 6: "Plastic utensils", 7: "Plastic toys", 8: "Plastic coat hanger", 9: "Big Bones", 10: "Dry battery", 11: "Express paper bag", 12: "Plug wire", 13: "Old clothes", 14: "The can", 15: "Pillow", 16: "Skin and pulp", 17: "Stuffed animal", 18: "Defacing plastic", 19: "Soiled paper", 20: "Toiletries", 21: "Cigarette butts", 22: "Toothpick", 23: "Glassware", 24: "Block", 25: "Chopsticks", 26: "Carton carton", 27: "Pot", 27: "Tea residue", 29: "Vegetable help vegetable leaf", 30: "Shell", 31: "The spice bottle", 32: "Paste", 33: "Expired drugs", 34: "Bottle", 35: "Metal kitchenware", 36: "Metal ware", 37: "Metal food cans", 38: "Pot", 39: "Ceramic vessels", 40: "Shoes", 41: "Edible oil drum", 42: "Beverage bottle", 43: "Bones" } for box in bboxes: x1, y1, x2, y2, score, clss = box cv2.putText(img, clss_to_text[int(clss)]+': '+"{:.3}".format(score*100)+'%', (int(x1 * w)+10, int(y1 * h)+30),text_font, font_scale, (0, int(clss+1)*5, 0), thickness) cv2.rectangle(img, (int(x1 * w), int(y1 * h)), (int(x2 * w), int(y2 * h)), (0, int(clss+1)*5, 0), 3)1.2.4 4)修改输入输出配置我们需要准备一个mp4文件拷贝到data文件夹下,我们使用测试视频garbage.mp4,然后打开工程目录下bin/mock_task.toml文件,修改其中的任务输入和任务输出配置为如下内容:[input]type = "url"url = "../data/garbage.mp4"[output]type = “local”url = “…/hilens_data_dir/garbage_detection_result.mp4”该流程图在本地运行时的逻辑过程是:data_source_parser解析bin/mock_task.toml文件中输入配置的data/garbage.mp4文件,video_demuxer和video_decoder对该文件进行解码,resize、packed_planar_transpose、normalize对原始图像进行缩放、转码、归一化等预处理,然后detect_garbage在预处理后的图像上进行垃圾检测,yolox_post从推理结果中解码出检测框,并把检测框画到原始图像上,最后video_out将图像输出到bin/mock_task.toml文件中输出配置的hilens_data_dir/garbage_detection_result.mp4文件中。1.2.5 5)用启动脚本执行应用启动应用前执行build_project.sh进行工程构建,该脚本将编译自定义的C++功能单元(本应用不涉及)、将应用运行时会用到的配置文件转码为Unix格式(防止执行过程中的格式错误):███$ ./build_project.shdos2unix: converting file xxx.toml to Unix format......build success: you can run main.bat in ./bin folderPress ‘p’ to pause…, any key to exit然后执行bin/main.bat运行应用:███$ ./bin/main.bat…运行结束后在hilens_data_dir目录下生成了garbage_detection_result.mp4文件,可以打开查看:1.2.6 6)用摄像头检测打开工程目录下bin/mock_task.toml文件,修改其中的任务输入和任务输出配置为如下内容:[input]type = "url"url = "0" # 表示0号摄像头,即PC自带摄像头,若PC无摄像头需外接USB摄像头[output]type = “local”url = “0:garbage_det” # 表示名为garbage_det的本地窗口即使用编号为0的摄像头(默认为PC自带的摄像头),输出画面显示到名为garbage_det的本地屏幕窗口中。1.2.7 7)运行应用执行bin/main.bat camera运行应用,将会自动弹出实时的垃圾分类检测画面:1.3 打包部署1.3.1 打包调试完成后,同样可以通过create.py脚本将应用打包发布:python ./create.py -t rpm -n garbage_det控制台中输出:sdk version is modelbox-win10-x64-1.1.0.5call mb-pkg-tool pack [folder] > [rpm file] to building rpm, waiting...success: create garbage_det.rpm in D:\modelbox-win10-x64-1.1.0.5/workspace/garbage_det等待稍许,可以看到项目工程下已经生成了rpm文件夹和打包好的应用:1.3.2 部署将打包好的应用上传至华为云账号下的obs桶中:在专业版设备管理中选择一个开发板,点击创建部署:最后添加作业:这样我们就已经完成了一个AI应用,从模型训练到转换到开发到部署的全部流程。关于ModelBox核心概念、功能单元和流程图开发的更多介绍,可查看ModelBox手册。
-
ModelArts 是面向开发者的一站式 AI 平台,为机器学习与深度学习提供海量数据预处理及交互式智能标注、大规模分布式训练、自动化模型生成,及端-边-云模型按需部署能力,帮助用户快速创建和部署模型,管理全周期 AI 工作流。点击Run in ModelArts,将会进入到ModelArts CodeLab中,这时需要你登录华为云账号,如果没有账号,则需要注册一个,且要进行实名认证,参考 https://developer.huaweicloud.com/develop/aigallery/article/detail?id=4ce709d6-eb25-4fa4-b214-e2e5d6b7919c 即可完成账号注册和实名认证。 登录之后,等待片刻,即可进入到CodeLab的运行环境Stable Diffusion文字生成图像 🎨Stable Diffusion 是由 CompVis、Stability AI 和 LAION 共同开发的一个文本转图像模型,它通过 LAION-5B 子集大量的 512x512 图文模型进行训练,我们只要简单的输入一段文本,Stable Diffusion 就可以迅速将其转换为图像,同样我们也可以置入图片或视频,配合文本对其进行处理Stable Diffusion Pipeline使用方法本案例可以使用GPU,也可以使用CPU来运行,GPU生成单张图片约20秒,CPU需6分钟
-
Sunrise, colorful morning glow, clear river, reflecting the morning glow and the sun, modern, beautiful artistic conception, film, dramatic lighting -- AR9:16--update
-
ModelBox开发案例 - 隔空作画本案例将使用YOLOX、SCNet两个模型,实现一个简单的隔空作画趣味应用,使用🖐手势作画,使用🤟手势取食指处色块换色,使用✊手势清屏。最终效果如下所示: 案例所需资源(代码、模型、测试数据等)均可从obs桶下载。模型训练我们使用面向开发者的一站式AI开发平台ModelArts进行模型的训练:ModelArts提供了包括数据标注,训练环境,预置算法在内的丰富的功能,甚至可以通过订阅预置算法实现0代码的模型训练工作。当然你也可以在本地训练自己的模型。我们假设你现在已经拥有了训练好的模型,接下来我们需要将训练好的模型转换成为可以在开发板上运行的模型。模型转换我们发布了开发板模型转换案例,参见RK3568模型转换验证案例 :在这个案例中我们演示了从环境适配到模型的转换验证的全流程样例代码,开发者可以通过“Run in ModelArts”一键将Notebook案例在ModelArts控制台快速打开、运行以及进行二次开发等操作。开发环境部署使用开发板进行ModelBox AI应用开发有两种方式,一是开发板连接显示器和键盘鼠标,安装Ubuntu桌面,直接在开发板上进行开发;二是使用远程连接工具(如VS Code中的Remote-SSH)从PC端登录开发板进行开发。这里我们推荐第二种方式,因为PC端可以使用功能更丰富、界面更友好的IDE。1.配置网络PC连接开发板需要知道开发板的ip,但是开发板默认没有固定ip,我们提供了ModelBox PC Tool,可以自动为开发板配置ip,也可以在推理阶段很方便的进行视频推流拉流。PC Tool位于SDK的connect_wizard目录中:双击connect_wizard.exe,在页面中可以看到有两种开发板连接方式,我们使用网线连接开发板的方式:按照指引断开或连接网线:等待一小段时间,可以看到来到了第三步,此时开发板已设置为默认ip:192.168.2.111,PC使用该ip即可SSH登录:2. 远程连接开发板我们推荐在PC端使用VS Code远程连接开发板来对设备操作。使用VS Code连接开发板可以参考我们发布的ModelBox 端云协同AI开发套件(RK3568)上手指南。同时,上手指南也介绍了如何将开发板注册到HiLens管理控制台进行更方便的在线管理。应用开发接下来我们会以隔空作画demo为例,介绍如何使用ModelBox开发一个AI应用。1. 创建工程SDK提供了工程脚本create.py,可以使用./create.py -h查看脚本帮助:ModelBox提供了可视化图编排工具:Editor,可以使用./create.py -t editor开启图编排服务:服务默认ip即为192.168.2.111,如需配置其他ip或端口,可以通过-i ip:port参数进行配置。点击链接即可进入可视化编辑界面,我们点击编排进入工程开发界面,如果进一步了解ModelBox相关内容,可以点击右上角帮助:进入编排界面,点击右上角新建项目:项目路径填写workspace,项目名称填写hand_painting, 确认:可以看到我们已经拥有了一个带有http收发单元的默认图:其中,区域1为SDK预置的高性能通用流单元,区域2为可视化编排界面,区域3为对应的图配置文件内容。同时,VS Code对应目录下也出现了hand_painting项目:2. 创建推理功能单元接下来,我们创建推理流单元:对于手检测模型,我们将流单元命名为hand_detection,模型文件名即为转换好的检测模型名:yolox_hand.rknn,此模型输入为image,输出为feature map,所以我们添加int类型的输入端口与float类型的输出端口。关于开发板的推理流单元创建,在处理类型时我们选择cuda,即为npu推理,推理引擎可选任意一款,目前开发板SDK可以自动进行识别转换。最后将功能单元分组修改为inference,点击确认,即可看到,在右侧inference页签下出现了:同时,在VS Code工程model目录下可以看到创建好的推理流单元:同样的,我们创建pose_detection推理流单元:3. 创建后处理功能单元除了推理流单元外,隔空作画demo还需要一些通用功能单元:检测后处理单元、感兴趣区域提取单元、作画单元,我们新建三个python功能单元来满足上述需求。对于检测后处理单元,我们希望通过原图和hand_detection的输出解码出手检测框,所以该单元应该有两个输入。此外,对于画幅中有手或者没有检测到手两种状态,我们希望该功能单元分情况处理,检测到手时,将检测结果送入感兴趣区域提取单元,没有检测到手时,直接返回,因此功能单元类型选择:IF_ELSE。新建单元如下:同样的,根据输入输出与功能单元状态,我们创建extract_roi和painting两个功能单元:4. 流程图编排拖拽需要的功能单元全部创建好后,我们可以着手编排流程图,我们编排一个视频处理的图,暂时不需要http收发单元,可以删除不需要的单元:⚠️⚠️⚠️:后续版本会对input单元进行校验,该单元名称必须为input1,请将下文图文中所有input0替换为input1。在Generic列表下将虚拟输入单元input和我们刚刚创建的三个功能单元拖入画布:在Image列表下将模型推理需要用到的预处理单元resize拖入画布,因为我们需要两个resize单元,所以重复拖入:值得注意的是,resize单元需要配置参数,需要点击该单元进行配置:在Input列表下拖入输入解析单元data_source_parser:在Video列表下拖入视频处理需要的单元video_demuxer、video_decoder、video_out:最后,在Inference列表下拖入我们创建的两个推理单元:编排将功能单元按照处理逻辑进行连接:虚拟输入input连接输入解析data_source_parser,解析后送入视频解包与解码单元:解码输出送入预处理后可直接进行推理:推理后处理需要输入原图与推理结果,没有结果则直接连接视频输入单元,有结果则连接感兴趣区域提取单元:提取结果送入预处理与推理:最后,得到的关键点结果与原图送入作画单元,作画结果送入视频输出单元进行保存:这样,我们就完成了流程图的编排,可以看到在GraphViz区域也出现了完整的图表述:保存项目,转到VS Code进行每个单元的代码实现:5. 代码补全可视化编排中,创建的推理单元位于项目的model目录下,通用单元位于etc/flowunit目录下,流程图位于graph目录下,可以看到创建的单元与图都已同步过来:其中,video_decoder需要指定类型:video_decoder7 [ type=flowunit flowunit=video_decoder device=rknpu, deviceid="0", pix_fmt=bgr label="{{<in_video_packet> in_video_packet}|video_decoder7|{<out_video_frame> out_video_frame}}" ]推理单元首先完善推理单元,对于推理功能单元,只需要提供独立的toml配置文件,指定推理功能单元的基本属性即可,目录结构为:[flowunit-name] |---[flowunit-name].toml #推理功能单元配置 |---[model].rknn #模型文件 |---[infer-plugin].so #推理自定义插件ModelBox框架在初始化时,会扫描目录中的toml后缀的文件,并读取相关的推理功能单元信息。[infer-plugin].so是推理所需插件,推理功能单元支持加载自定义插件,开发者可以实现自定义算子。将模型拷入对应文件夹,以hand_detection为例我们看一下推理功能单元配置文件:配置文件中有一些单元类型、模型名称、输入输出的基本配置,可以酌情修改。通用单元Python通用单元需要提供独立的toml配置文件,指定python功能单元的基本属性。一般情况,目录结构为:[FlowUnitName] |---[FlowUnitName].toml |---[FlowUnitName].py |---xxx.py相较于推理单元而言,通用单元不但有配置文件,还需要完善具体的功能代码,以yolox_post为例,首先是功能单元配置文件:# Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved. # Basic config [base] name = "yolox_post" # The FlowUnit name device = "cpu" # The flowunit runs on cpu version = "1.0.0" # The version of the flowunit type = "python" # Fixed value, do not change description = "description" # The description of the flowunit entry = "yolox_post@yolox_postFlowUnit" # Python flowunit entry function group_type = "generic" # flowunit group attribution, change as input/output/image ... # Flowunit Type stream = false # Whether the flowunit is a stream flowunit condition = true # Whether the flowunit is a condition flowunit collapse = false # Whether the flowunit is a collapse flowunit collapse_all = false # Whether the flowunit will collapse all the data expand = false # Whether the flowunit is a expand flowunit # The default Flowunit config [config] item = "value" # Input ports description [input] [input.input1] # Input port number, the format is input.input[N] name = "in_image" # Input port name type = "uint8" # input port data type ,e.g. float or uint8 device = "cpu" # input buffer type [input.input2] # Input port number, the format is input.input[N] name = "in_feat" # Input port name type = "uint8" # input port data type ,e.g. float or uint8 device = "cpu" # input buffer type # Output ports description [output] [output.output1] # Output port number, the format is output.output[N] name = "has_hand" # Output port name type = "float" # output port data type ,e.g. float or uint8 [output.output2] # Output port number, the format is output.output[N] name = "no_hand" # Output port name type = "float" # output port data type ,e.g. float or uint8Basic config是一些单元名等基本配置,Flowunit Type是功能单元类型,yolox_post是一个条件单元,所以可以看到condition为true,此外还有一些展开、归拢等性质,可以在AI Gallery ModelBox)板块下看到更多案例。config为单元需要配置的一些属性,如本单元需要一些特征图size、阈值等信息,所以在配置文件中修改config为:[config] net_h = 320 net_w = 320 num_classes = 2 conf_threshold = 0.5 iou_threshold = 0.5此外,输入输出type根据实际逻辑可能进行一些修改:# Input ports description [input] [input.input1] # Input port number, the format is input.input[N] name = "in_image" # Input port name type = "uint8" # input port data type ,e.g. float or uint8 device = "cpu" # input buffer type [input.input2] # Input port number, the format is input.input[N] name = "in_feat" # Input port name type = "float" # input port data type ,e.g. float or uint8 device = "cpu" # input buffer type # Output ports description [output] [output.output1] # Output port number, the format is output.output[N] name = "has_hand" # Output port name type = "uint8" # output port data type ,e.g. float or uint8 [output.output2] # Output port number, the format is output.output[N] name = "no_hand" # Output port name type = "uint8" # output port data type ,e.g. float or uint8接下来,我们查看yolox_post.py,可以看到创建单元时已经生成了基本接口:# Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved. #!/usr/bin/env python # -*- coding: utf-8 -*- import _flowunit as modelbox class yolox_postFlowUnit(modelbox.FlowUnit): # Derived from modelbox.FlowUnit def __init__(self): super().__init__() def open(self, config): # Open the flowunit to obtain configuration information return modelbox.Status.StatusCode.STATUS_SUCCESS def process(self, data_context): # Process the data in_data = data_context.input("in_1") out_data = data_context.output("out_1") # yolox_post process code. # Remove the following code and add your own code here. for buffer in in_data: response = "Hello World " + buffer.as_object() result = response.encode('utf-8').strip() add_buffer = modelbox.Buffer(self.get_bind_device(), result) out_data.push_back(add_buffer) return modelbox.Status.StatusCode.STATUS_SUCCESS def close(self): # Close the flowunit return modelbox.Status() def data_pre(self, data_context): # Before streaming data starts return modelbox.Status() def data_post(self, data_context): # After streaming data ends return modelbox.Status() def data_group_pre(self, data_context): # Before all streaming data starts return modelbox.Status() def data_group_post(self, data_context): # After all streaming data ends return modelbox.Status()如果功能单元的工作模式是stream = false时,功能单元会调用open、process、close接口;如果功能单元的工作模式是stream = true时,功能单元会调用open、data_group_pre、data_pre、process、data_post、data_group_post、close接口;用户可根据实际需求实现对应接口。根据单元性质,我们主要需要完善open、process接口:import _flowunit as modelbox import numpy as np from yolox_utils import postprocess, expand_bboxes_with_filter, draw_color_palette class yolox_postFlowUnit(modelbox.FlowUnit): # Derived from modelbox.FlowUnit def __init__(self): super().__init__() def open(self, config): self.net_h = config.get_int('net_h', 320) self.net_w = config.get_int('net_w', 320) self.num_classes = config.get_int('num_classes', 2) self.num_grids = int((self.net_h / 32) * (self.net_w / 32)) * (1 + 2*2 + 4*4) self.conf_thre = config.get_float('conf_threshold', 0.3) self.nms_thre = config.get_float('iou_threshold', 0.4) return modelbox.Status.StatusCode.STATUS_SUCCESS def process(self, data_context): modelbox.info("YOLOX POST") in_image = data_context.input("in_image") in_feat = data_context.input("in_feat") has_hand = data_context.output("has_hand") no_hand = data_context.output("no_hand") for buffer_img, buffer_feat in zip(in_image, in_feat): width = buffer_img.get('width') height = buffer_img.get('height') channel = buffer_img.get('channel') img_data = np.array(buffer_img.as_object(), copy=False) img_data = img_data.reshape((height, width, channel)) feat_data = np.array(buffer_feat.as_object(), copy=False) feat_data = feat_data.reshape((self.num_grids, self.num_classes + 5)) ratio = (self.net_h / height, self.net_w / width) bboxes = postprocess(feat_data, (self.net_h, self.net_w), self.conf_thre, self.nms_thre, ratio) box = expand_bboxes_with_filter(bboxes, width, height) if box: buffer_img.set("bboxes", box) has_hand.push_back(buffer_img) else: draw_color_palette(img_data) img_buffer = modelbox.Buffer(self.get_bind_device(), img_data) img_buffer.copy_meta(buffer_img) no_hand.push_back(img_buffer) return modelbox.Status.StatusCode.STATUS_SUCCESS def close(self): # Close the flowunit return modelbox.Status()可以看到,在open中我们进行了一些参数获取,process进行逻辑处理,输入输出可以通过data_context来获取,值得注意的是输出时我们返回的是图,在检测到手时为图附加了检测框信息,该信息可以被下一单元获取。同样的,完善其余通用功能单元,具体可以参考我们提供的代码。应用运行我们需要准备一个mp4文件拷贝到data文件夹下,我们提供了测试视频hand.mp4,然后打开工程目录下bin/mock_task.toml文件,修改其中的任务输入和任务输出配置为如下内容:# 任务输入,mock模拟目前仅支持一路rtsp或者本地url # rtsp摄像头,type = "rtsp", url里面写入rtsp地址 # 其它用"url",比如可以是本地文件地址, 或者httpserver的地址,(摄像头 url = "0") [input] type = "url" url = "../data/hand.mp4" # 任务输出,目前仅支持"webhook", 和本地输出"local"(输出到屏幕,url="0", 输出到rtsp,填写rtsp地址) # (local 还可以输出到本地文件,这个时候注意,文件可以是相对路径,是相对这个mock_task.toml文件本身) [output] type = "local" url = "../hilens_data_dir/paint.mp4"配置好后在工程路径下执行build_project.sh进行工程构建:rock@rock-3a:~/lxy/examples$ cd workspace/hand_painting/ rock@rock-3a:~/lxy/examples/workspace/hand_painting$ ./build_project.sh dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/graph/hand_painting.toml to Unix format... dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/graph/modelbox.conf to Unix format... dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/etc/flowunit/extract_roi/extract_roi.toml to Unix format... dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/etc/flowunit/painting/painting.toml to Unix format... dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/etc/flowunit/yolox_post/yolox_post.toml to Unix format... dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/model/hand_detection/hand_detection.toml to Unix format... dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/model/pose_detection/pose_detection.toml to Unix format... dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/bin/mock_task.toml to Unix format... build success: you can run main.sh in ./bin folder rock@rock-3a:~/lxy/examples/workspace/hand_painting$ 构建完成后运行项目:rock@rock-3a:~/lxy/examples/workspace/hand_painting$ ./bin/main.sh 等待稍许即可以在hilens_data_dir文件夹下看到运行结果:除了mp4外我们也支持很多其他类型的输入输出,ModelBox PC TOOL也提供了推流与拉流功能,选择输入实时视频流,启动:运行程序时配置输出地址为推流地址,即可在本机网页中查看到运行结果:如果需要对应用进行性能评估,只需要在流程图配置文件中开启profile:[profile] profile=true # 启用profile trace=true # 启用traceing dir="/tmp/modelbox/perf" # 设置跟踪文件路径配置启动后,启动运行流程图,profile会每隔60s记录一次统计信息,trace会在任务执行过程中和结束时,输出统计信息。运行流程图后,会生成性能相关的json文件,通过将json文件加载到浏览器中即可查看timeline信息。打开chrome浏览器。浏览器中输入chrome://tracing/。点击界面中的Load按钮,加载trace的json文件。加载成功后,将看到类似下面的timeline视图:打包部署打包调试完成后,同样可以通过create.py脚本将应用打包发布:./create.py -t rpm -n hand_painting控制台中输出:sdk version is modelbox-rk-aarch64-1.0.8.8 call mb-pkg-tool pack [folder] > [rpm file] to building rpm, waiting... success: create hand_painting.rpm in /home/rock/lxy/examples/workspace/hand_painting等待稍许,可以看到项目工程下已经生成了rpm文件夹和打包好的应用:部署将打包好的应用上传至华为云账号下的obs桶中:在专业版设备管理中找到开发板,点击创建部署:技能包选择刚刚上传的应用:如果需要,可以配置一些启动参数,否则默认完成即可:这样我们就已经完成了一个AI应用,从模型训练到转换到开发到部署的全部流程。关于ModelBox核心概念、功能单元和流程图开发的更多介绍,可查看ModelBox手册。
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签