你或许也想拥有专属于自己的AI模型文件格式-(2)

        紧接着上次的工作,想要回顾或者查看上次内容可以点击以下的链接,链接如下所示:

你或许也想拥有专属于自己的AI模型文件格式-(1)你或许也想拥有专属于自己的AI模型文件格式-(2)https://blog.csdn.net/Pengcode/article/details/121754272?spm=1001.2014.3001.5502        这次主要的内容是构建我们的专ai模的灵魂,也就是信息组成描述编写。通俗来说,就是定义一个数据协议,这些数据协议中包含了模型数据结构,这些数据结构是我们搭建整个模型的积木,或者说是包含了整个模型是如何定义的。

        跟着上节的步骤,我们已经搭建好了环境,安装好了对应的工具。本次我使用的工具是FlatBuffers,接下来我会使用该工具来得到我们专ai模的数据协议。

一、FlatBuffers的使用流程:

1.1 编写一个Schema.fbs数据交换协议文件:

        拿来官网的一个例子就是如下所示的文件,其文件名称为monster.fbs:

// Example IDL file for our monster's schema.
 
namespace MyGame.Sample;
 
enum Color:byte { Red = 0, Green, Blue = 2 }
 
union Equipment { Weapon } // Optionally add more tables.
 
struct Vec3 {
  x:float;
  y:float;
  z:float;
}
 
table Monster {
  pos:Vec3; // Struct.
  mana:short = 150;
  hp:short = 100;
  name:string;
  friendly:bool = false (deprecated);
  inventory:[ubyte];  // Vector of scalars.
  color:Color = Blue; // Enum.
  weapons:[Weapon];   // Vector of tables.
  equipped:Equipment; // Union.
  path:[Vec3];        // Vector of structs.
}
 
table Weapon {
  name:string;
  damage:short;
}
 
root_type Monster;

        如果了解过C语言和json文件,那或许会觉得上述的书写语法像是两者的糅合,显得陌生又熟悉。其实Schema.fbs使用的是一种被FlatBuffers定义的IDL(接口定义语言,Interface description language),IDL是一类语言的统称,主要用来描述编程的API,使用了IDL描述了一份API文件,假设目前有一份根据该API文件来生成的数据,不管在C++、JAVA还是任何其他编程语言,那么我也可以根据该API文件解释这份数据。这种情况通常发生在服务器来客户端的数据收发后的解析过程中,所以IDL经常被使用在服务器的场景中。

        因此FlatBuffers的schema是一种IDL。为了更好的解释,我就按照数据实际意义来进行说明,因为这份monster.fbs的使用场景是游戏,主要描述了一个完整的怪物的信息。我将会解读如下所示:


namespace MyGame.Sample;
// 颜色种类,用来描述怪物的颜色
enum Color:byte { Red = 0, Green, Blue = 2 }
// 护甲,是武器的一种
union Equipment { Weapon }
// 三维数据
struct Vec3 {
  x:float;
  y:float;
  z:float;
}
// 怪物
table Monster {
  pos:Vec3; //用三维数据来描述怪物的坐标点
  mana:short = 150; // 力量值,跟武力差不多
  hp:short = 100;// hp值,就是血量
  name:string;//名称
  friendly:bool = false (deprecated);//是否对玩家友好,就是是否会主动攻击
  inventory:[ubyte];  // 物品栏
  color:Color = Blue; // 颜色
  weapons:[Weapon];   // 携带武器
  equipped:Equipment; // 拥有的护甲,只能有一件
  path:[Vec3];        // 行走轨迹
}
table Weapon {
  name:string;
  damage:short;
}
// 关键字root_type声明了Monster是我们的主要类型,作为了后续序列化的主要类型,
//对于解析Json文件特别重要。
root_type Monster;

        接下来讲解一下上述的monster.fbs内部的语法和含义,为了缩小文章篇幅,我将以注解的方式来直接书写在下面:

// 首先,这里有个关键字namespace,主要是用来声明命名空间的,主要是为了C++使用方便
// 那么在C++语言中,我们将需要使用到MyGame::Sample这个命名空间来使用到IDL定义的API 
namespace MyGame.Sample;
// 关键字enum表示Color是一个枚举类型,byte表示内部的枚举值的类型是字节类型
// 注意,enum内部的枚举值只能是整型数据类型:byte、ubyte、short、ushort、int、uint、
// long、ulong
enum Color:byte { Red = 0, Green, Blue = 2 }
// 关键字union表示Equipment共用体,该概念跟C语言中的共用体的概念是一致的
union Equipment { Weapon }
// 关键字struct表示Vec3是一个结构体,跟table的区别就是:
// 1、struct内部一般只是包含其他结构体或者是内建数据类型
// 2、不能设置默认值
// 3、相较于table的速度更快、存储更小
// 4、没有added和deprecated特性
struct Vec3 {
  x:float;
  y:float;
  z:float;
}
// 关键字table,是schema中最主要的数据结构了,拥有丰富的特性和接口
// 其成员类型可以是任何的数据结构,而且可以设置默认值;
// 另外,其也可以声明列表类型,类似于向量类型,如下所示的[ubyte]、[Weapon]、[Vec3]
table Monster {
  pos:Vec3; // Struct.
  mana:short = 150; // 设置默认为150
  hp:short = 100;
  name:string;
  friendly:bool = false (deprecated);
  inventory:[ubyte];  // Vector of scalars.
  color:Color = Blue; // Enum.
  weapons:[Weapon];   // Vector of tables.
  equipped:Equipment; // Union.
  path:[Vec3];        // Vector of structs.
}
table Weapon {
  name:string;
  damage:short;
}
// 关键字root_type声明了Monster是我们的主要类型,作为了后续序列化的主要类型,
//对于解析Json文件特别重要。
root_type Monster;

        上述就是FlatBuffers的schema编写规则了,想要了解更加具体的schema编写规则,请前往该网址:schema编写规则的官网教程你或许也想拥有专属于自己的AI模型文件格式-(2)https://google.github.io/flatbuffers/flatbuffers_guide_writing_schema.html

1.2、使用flatc编译上述编写的schema.fbs文件

        flatc在安装了FlatBuffers就可以使用,是一个编译器,我更愿意称之为代码生成器。因为这个主要就是用于生成目标平台的代码,你可以把schema.fbs文件生成C++、JAVA、JavaScript、Python等目标编程语言的代码,这份被生成的代码就是对应的数据结构API了。

        如下是我使用flatc进行编译常数monster.fbs文件的过程:

$ gedit monster.fbs
$ ls
monster.fbs
$ flatc -c -o ./ monster.fbs 
$ ls
monster.fbs  monster_generated.h
$ cat monster_generated.h 
// 以下就是monster.fbs对应的C++编程语言平台对应的生成代码部分
// automatically generated by the FlatBuffers compiler, do not modify
#ifndef FLATBUFFERS_GENERATED_MONSTER_MYGAME_SAMPLE_H_
#define FLATBUFFERS_GENERATED_MONSTER_MYGAME_SAMPLE_H_

#include "flatbuffers/flatbuffers.h"

namespace MyGame {
namespace Sample {

struct Vec3;

struct Monster;
struct MonsterBuilder;

struct Weapon;
struct WeaponBuilder;
// 过长了,所以只展示了前一部分
..........
}
}
$

1.3、在对应的编成平台使用生成的代码

        这部分因为篇幅过长,而且本文还暂时未进行到这部分,所以将会在本系列的后续章节中讲述。如果想要现在了解使用方法的,请访问该官网链接:如何在编成代码中使用flatbuffers你或许也想拥有专属于自己的AI模型文件格式-(2)https://google.github.io/flatbuffers/flatbuffers_guide_tutorial.html

二、编写专ai模的schema文件

2.1、写下自己的需求

        在编写schema文件前,搞清楚对专ai模的期望,比如我想要大而全,而且扩展性好,能够一劳永逸的那种;可能你就想要小二美,对扩展性要求不高。每个人的要求不一样,写下要求期望后,再去编写schema文件将会高效而准确。

        下面我罗列自己的对专ai模的期望,用来指导自己编写schema文件。我对专ai模的要求:(1)扩展性好,也就是网络层的参数定义不写死;

(2)能够随意添加算子,特别是能够在代码中添加;

(3)写好schema后,后续不需要更改,就可以适配任何算子和模型。

2.2、正式编写

        为了减少文章篇幅,我无法展开说明,也按照上述的monster.fbs的实际含义的那种注解方式来进行讲解。大家有兴趣可以帮我看看是否存在某些遗漏之处,或者是需要更改的地方,欢迎指正。

namespace PzkModel;

attribute "priority";
file_extension "pmodelmeta";
file_identifier "PZKM";

// 用来表示时间的类结构
table time{
    year:uint32 = 2099;
    month:uint8 = 12;
    day:uint8 = 29;
    hour:uint8 = 6;
    min:uint8 = 6;
    sec:uint8 = 6;
}

// 包含了大部分的数据类型,从bool到int32等16种类型
enum DataType: byte {
    INT32 = 0,
    BOOL = 1,
    INT4 = 2,
    UINT4 = 3,
    INT8 = 4,
    UINT8 = 5,
    INT16 = 6,
    UINT16 = 7,
    FP16 = 8,
    FP32 = 9,
    QSYMMEINT4 = 10, //quantize symmetry int4
    QSYMMEINT8 = 11, //quantize symmetry int8
    QASYMMEUINT4 = 12, //quantize asymmetry uint4
    QASYMMEUINT8 = 13, // quantize asymmetry uint8
    UINT32 = 14,
    CHAR = 15,
}
// 表示运行时的张量是否是常量,也就是是否是权重还是实时数据
enum TensorType: byte {
    CONST = 0,
    DYNAMIC = 1,
}
// 数据排布方式,目前有四种
enum DataLayout: byte {
    NCHW = 0,
    NHWC = 1,
    ND = 2,
    NCD = 3,
}
// 张量的维度信息
table TensorShape{
    dimsize:ubyte;
    dims:[uint32];
}
// 权重数据
table Weights{
    ele_bytes:ubyte=0;
    ele_num:uint64=0;
    buffer:[ubyte];
}
// 张量,内部的成员包含了上面的类型
table Tensor{
    id:uint32;//张量id,也就是唯一的标识符
    name:string;
    tesor_type:TensorType;
    data_type:DataType;
    data_layout:DataLayout;
    shape:TensorShape;
    weights:Weights;
}
// 单个属性,可以用来描述类似与卷积的核大小或者步长等属性信息
table AttrMeta{
    key:string;
    require:bool = false;
    buffer_data:DataType;
    buffer_ele_num:uint32;
    buffer:[ubyte];
}
// 属性集,用来描述一个层的所有属性,拿卷积来说,就是包含了核大小、步长、成组信息等所有属性。
table Attributes{
    type:string;
    meta_num:uint32;
    meta_require_num:uint32;
    buffer:[AttrMeta];
}
// 连接信息,用来描述层和张量的连接关系。
table Connect{
    name:string;
    necessary:bool = false;
    tensor_id:uint32;
}
// 层描述,包含了上述的必要信息
table Layer{
    id:uint32;
    name:string;
    type:string;
    input_num:ubyte;//输入张量的个数
    output_num:ubyte;//输出张量的个数
    input_id:[Connect];
    output_id:[Connect];
    require_attrs:bool = false;
    attrs:Attributes;//层属性集合
}


// 模型描述,是最主要的数据结构,包含了所有的张量描述和所有的层描述、连接关系,以及附属信息
table PModel{
    author:string;//模型作者
    create_time:time;//模型创造时间
    version:string;//模型版本号
    model_name:string;//模型名称
    model_runtime_input_num:uint32;//模型输入个数
    model_runtime_output_num:uint32;//模型输出个数
    model_runtime_input_id:[uint32];
    model_runtime_output_id:[uint32];
    all_tensor_num:uint32;
    tensor_buffer:[Tensor];//所有的张量
    layer_num:uint32;
    layer_buffer:[Layer];//所有的层
}

root_type PModel;

  2.3、schema文件如上编写的意图

        根据2.1中我的需求就可以,我不可能在schema中直接写一个卷积应该怎么描述,有那些参数,因为这样只会让我陷入不断地修改卷积的table描述,这会让我的专ai模失去拓展性;也会增加工作量;甚至我们需要想到可能不久卷积又会增加新属性,那么又要修改schema文件,那么跟我的“一劳永逸”的期望就背道而驰了。

        因此,我在schema文件中描述的都是通用的层描述和张量描述,任何的网络层都可以写成上述的schema文件中的table Layer表示,我将会在下面使用我所定义的Layer来实例描述一个卷积层的描述,来说明一下这份schema的泛化性:

    {
      id: 0,
      name: "conv2d-index-1",
      type: "\"Convolution2dLayer\"",
      input_num: 3,
      input_id: [
        {
          name: "\"input\"",
          necessary: true
        },
        {
          name: "\"weights\"",
          necessary: true,
          tensor_id: 1
        },
        {
          name: "\"biases\"",
          necessary: true,
          tensor_id: 2
        }
      ],
      output_num: 1,
      output_id: [
        {
          name: "\"conv2d-output\"",
          necessary: true,
          tensor_id: 3
        }
      ],
      require_attrs: true,
      attrs: {
        type: "\"Convolution2dLayer\"-Attrs",
        meta_num: 2,
        meta_require_num: 2,
        buffer: [
          {
            key: "\"kernel_size\"",
            require: true,
            buffer_data: "CHAR",
            buffer: [12]
          },
          {
            key: "\"pad\"",
            require: true,
            buffer_data: "CHAR",
            buffer: [1]
          }
        ]
      }
    }

        上述的这个文件就是基于我的table Layer来定义的一个卷积层,可以完美地表示任何的一个层。因此这份schema基本上达到了我的需求和目的。

        后续:如何基于专ai模来生成自己的第一个模型文件,尽情期待。

上一篇:Java Web之XML基础


下一篇:XForms 数据类型