Python 调用 C/C++ 动态库
ctypes 模块的简单使用
ctypes 模块的简单使用
关于 BatchNorm 层的原理可以详见此论文
深度卷积神经网络的训练主要是每一层神经元学习本层的输入数据的分布。但是输入数据的分布会随着上一层的计算发生变化,称为 internal covatiate shift. 给本层的学习带来了困难,在 BatchNorm 之前是通过微调学习率和初始化参数实现,学习速度很低。
引入 BatchNorm 通过对数据规范化处理避免这个问题。
小批量数据:
需要学习的参数:
$$
\mu_B \leftarrow \frac 1 m \sum_{i=1}^{m}x_i \quad \text{mini-batch mean} \
\sigma_{B}^{2} \leftarrow \frac 1 m \sum_{i=1}^{m}(x_i - \mu_B)^2 \quad \text{mini-batch variance} \
\hat{x}i \leftarrow \frac {x_i - \mu_B} {\sqrt{\sigma_B^2 + \epsilon}} \quad \text{normalize} \
y_i \leftarrow \gamma \hat{x} {i}+\beta\equiv {BN_{\gamma,\beta}(x_i)} \quad \text{scale and shift}
$$
BatchNorm 降低了数据的绝对差异,突出了相对差异,在分类任务上效果明显。
使用 python 的包管理工具安装 mmdnn
1 | pip install mmdnn |
会得到以下命令行工具
mmdownloadmmconvertmmtoir, mmtocode, mmtomodel 三者的集成,一行命令完成从一个框架模型到另一个框架模型的转换,分为三步常用于调试mmtoirjson 用于在线可视化proto|pb 用于描述网络结构模型npy 用于保存网络数值参数mmtocodemmtoir 的 pb, npy 文件转换为在特定框架下构造该网络的原始代码,以及构建网络过程中用于设置权重的参数。mmtomodelmmtocode 的输出 py 文件转换为可以直接被指定框架读取的模型文件mmvismetatensorboard 作为后端将计算图可视化目前遇到的几个问题
npy 格式存储,目前没有找到 C++ 相关的官方 API.忽然发现这是第 100 篇,mark 一下
2018-01-25
MMdnn能够实现在不同的框架之间互相转换网络模型。
其原理是将所有的模型先转化为中间表达形式(Intermediate Representation), 分析下 IR 层的表示方式。
IR 层的 proto 说明文件可见 github。
其中包含以下几个部分
描述网络模型的 graph, 内部包含若干 node, 这里的结点即对应网络模型的层,node 中的 input 即描述每一层之间的输入输出连接关系.
namename, 在 Graph 中唯一opname 唯一.inputattrattr 成员可以是 listvalue, type, shape, tensorlistbytes, int64, float, booltypeshapetensortypeshapetensornode 之间传递的 tensor描述 tensor 的维度信息
存储 tensor, 在各 node 之间传递
MacOS 下 brew 更新后总是安装各种包总是最新版的, brew install python 总是安装 python3.7, 大部分包还没有做适配,安装 tensorflow 报错,安装 pytorch 报错,无奈只能安装回 python.3.6。
formula 版本对应的 rb 文件python 为例, 在https://github.com/Homebrew/homebrew-core/commits/master/Formula/python.rbcomment 找其对应的版本信息view 后以 raw 模式打开/usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/ 下的同名文件,然后再安装这个 formula1 | brew uninstall python |
之后所有依赖 python3.7 的 formula 都会崩溃,比如 macvim, 依照相同方法找到旧版本 macvim.rb 文件重新安装,直接安装的话会更新 python.
没错,brew 放弃新版本安装旧版本就是这么麻烦。
接下来几周的任务是实现 MMdnn 中 IR 层到新框架网络模型的后端转换,mentor 先给了一个合并 prototxt 和 bin 的合并工具源码做参考(感觉不是一个层次上的东西啊),分析下这个工具的实现
prototxt 网络结构描述文件prototxt 文件形式为纯文本格式,可以直接用常用的文本编辑器打开(vscode, sublime text 等),caffe 用该类型文件保存并描述网络结构信息,所有内容明文保存。其存储格式并不适合一般人阅读,以下是 prototxt 文件描述 input 层和第一层 conv 层的格式
1 | layer { |
可以通过这个网站看到图形化的网络结构。
bin 网络参数描述文件bin 文件为二进制文件,无法直接阅读,caffe 使用该类型文件描述网络参数,如卷积层的卷积核的 weight,bias 等数值信息
经分析,合并大致流程如下
prototxt 文件bin 文件caffe::NetParameter 类型,存储其网络信息,包含各层结构和数据protobuf 中描述的输入信息构造输入层 NCHW 信息或者多维结构信息。protobuf 中描述的每一层 layer 做如下操作layer.name 找到对应 bin 文件中对应的 layer 参数信息layerlayer 参数信息中的每一个 blob 做如下操作NCHW 或者多维结构信息,并记录blob 中并添加到 layer 中NetParameter 序列化并存入到 output_model 中protobuf 是 Google 推出的语言独立,平台独立的针对序列化数据的组件,可以类比 xml, 但是比 xml 精简。
通过使用 protobuf 语法自定义想要的数据结构,接着用 protoc 命令就可以产生针对这种数据格式文件读写的各种语言(C/C++, Python, Java等)的 API.
1 | brew install protobuf |
proto 语法自定义序列化数据结构使用特殊的语法,创建 proto 文件,其包含如下内容
syntax= proto2 | proto3packagemessagefield[required | optional | repeated]typename[message]tag1 | syntax = "proto2" | "proto3“ |
message一个 message 类型表示了一个可以序列化的结构。由若干个 field 组成
field一个 field 由以下部分构成
typeproto 支持的类型可见官网, 可以是用户自定义的 messagefield numberfield 最后包含一个独特数字,这个数字在二进制格式中标记这个 field,当整个 message 投入使用后不应该再修改,这些 Number 可取的范围是 FieldDescriptor::FieldDescriptor 错误。field rulesingularmessage 中,这 field 可以出现一次或零次repeatedmessage 中,这个 field 可以出现任意次数,值出现的顺序保留。可以注明一个 field 是 reserved,可以标注其 field number 或者 name, 示例如下
1 | message Foo { |
reserved 标注意为未来可能会抛弃这个 field, 提示用户尽可能少使用这个 field, 每当 proto buffer compiler 编译这个 proto 文件生成 api 时都会给出相应的警告。
Enumerationproto 中定义枚举类型的语法与 C++ 基本相同
1 | enum Corpus { |
注意,一定要有 0 值
AnyAny 类型可以保存任意类型的信息,直接将任何序列化的 message 看做看做字节串存储
oneof类似 C++ 中的 union ,存储时只存储 oneof 域中的一个。
1 | message SampleMessage { |
当取其中一个域时,其他域自动会被删除,当使用 C++ 时要注意内存崩溃问题.
定义 Map 的语法如下
1 | map<key_type, value_type> map_field = N; |
删除 ~/Library/Developer/Xcode/DerivedData 中的所有内容
1 | rm -rf ~/Library/Developer/Xcode/DerivedData/* |
重启 Xcode。
写了两天的 bug, 心累
在 Mac OSx 上从包含 CmakeLists.txt 的文件夹生成 Xcode 工程
1 | cmake -G Xcode -H . -B build |
这行命令从当前目录读取 CMakeLists.txt 文件并创建 build 目录并在其下生成 Xcode 工程。
这次的代码在一行报了 EXC_BAD_ACCESS 异常,后来发现是这次的对象没有创建成功,虚函数调用直接失败,这里简单记下这个异常的原因及常见解决方法
当调试器报出 EXC_BAD_ACCESS 异常时,通常意味着这里向一个已经被释放的对象发送消息。
从更底层的角度上来说,在 C, Object-C 和 C++ 中,通常是由于指针的错误使用造成的,每当使用一个指针给其指向的对象发送消息时,这个指针都要解引用,实际访问其指向的内存空间,当这块内存空间并不在这个程序映射的内存中时,再次访问这块内存时系统内核就会发送一个异常(EXC), 表示程序不能访问这块内存(BAD ACCESS)
总而言之,当遇到 EXC_BAD_ACCESS 时,通常表示给一块不能执行该消息的内存发送了消息
程序并不会因为错误指针的传递或者赋值而崩溃,只会因为对其解引用而崩溃,被释放的对象会成为 zombie 对象。
在 Xcode 中开启 enable zombie Objects 可以在调试时使 zombie 保留在程序中,调试器可以观察到向一个 zombie 对象发送消息时的详细信息。
在一般的 Unix 平台上可以使用 Valgrind 内存检测工具检查内存的违法操作行为。