适用于C ++开发人员的神经网络

大家好

他写了一个用于训练神经网络的库。 请谁在乎。

我一直想使自己成为这种水平的乐器。 C夏天,他开始做生意。 这是发生了什么:

  • 该库是从头开始用C ++(仅用于STL + OpenBLAS进行计算),C接口,win / linux编写的;
  • 网络结构在JSON中指定;
  • 基本层:完全连接,卷积,池化。 附加:调整大小,裁剪..;
  • 基本功能:batchNorm,辍学,体重优化器-Adam,adagrad ..;
  • OpenBLAS用于计算视频卡的CPU,CUDA / cuDNN。 他还为将来部署了OpenCL。
  • 对于每一层,都有机会分别设置要考虑的内容-CPU或GPU(以及哪一个);
  • 输入数据的大小不是严格设置的;它可能在工作/培训期间发生变化;
  • 制作了用于C ++和Python的接口。 C#也将稍后发布。

该图书馆被称为天网。 (一切都与名称复杂,其他都是选项,但有些不对..)


使用MNIST示例与PyTorch进行比较:

PyTorch:准确度:98%,时间:140秒
天网:准确性:95%,时间:150秒

机器:i5-2300,GF1060。 测试代码。



软件架构




它基于操作图,该图在解析网络结构后动态创建一次。
对于每个分支,都有一个新线程。 网络的每个节点(节点)都是一个计算层。

有工作特征:

  • 激活功能,按批归一化,退出-它们全都作为特定层的参数来实现,换句话说,这些功能不作为单独的层存在。 也许将来应在单独的层中选择batchNorm;
  • softMax也不是单独的层;它属于特殊的LossFunction层。 在选择特定类型的误差计算时使用它;
  • “ LossFunction”层用于自动计算错误,您显然不能使用前进/后退步骤(以下是使用此层的示例);
  • 没有“ Flatten”层,因此不需要,因为“ FullyConnect”层本身会绘制输入数组;
  • 必须为每个权重层设置权重优化器;默认情况下,每个人都使用“ adam”。

例子


Mnist




C ++代码如下所示:
//   sn::Net snet; snet.addNode("Input", sn::Input(), "C1") .addNode("C1", sn::Convolution(15, 0, sn::calcMode::CUDA), "C2") .addNode("C2", sn::Convolution(15, 0, sn::calcMode::CUDA), "P1") .addNode("P1", sn::Pooling(sn::calcMode::CUDA), "FC1") .addNode("FC1", sn::FullyConnected(128, sn::calcMode::CUDA), "FC2") .addNode("FC2", sn::FullyConnected(10, sn::calcMode::CUDA), "LS") .addNode("LS", sn::LossFunction(sn::lossType::softMaxToCrossEntropy), "Output"); ............. // -  //   for (int k = 0; k < 1000; ++k){ targetLayer.clear(); srand(clock()); //   for (int i = 0; i < batchSz; ++i){ ............. } //     float accurat = 0; snet.training(lr, inLayer, outLayer, targetLayer, accurat); } 


完整的代码在这里 。 在示例旁边的存储库中添加了一些图片。 我使用opencv读取图像,但未将其包含在套件中。

另一个计划相同的网络,更加复杂。



创建这样的网络的代码:
  //   sn::Net snet; snet.addNode("Input", sn::Input(), "C1 C2 C3") .addNode("C1", sn::Convolution(15, 0, sn::calcMode::CUDA), "P1") .addNode("P1", sn::Pooling(sn::calcMode::CUDA), "FC1") .addNode("C2", sn::Convolution(12, 0, sn::calcMode::CUDA), "P2") .addNode("P2", sn::Pooling(sn::calcMode::CUDA), "FC3") .addNode("C3", sn::Convolution(12, 0, sn::calcMode::CUDA), "P3") .addNode("P3", sn::Pooling(sn::calcMode::CUDA), "FC5") .addNode("FC1", sn::FullyConnected(128, sn::calcMode::CUDA), "FC2") .addNode("FC2", sn::FullyConnected(10, sn::calcMode::CUDA), "LS1") .addNode("LS1", sn::LossFunction(sn::lossType::softMaxToCrossEntropy), "Summ") .addNode("FC3", sn::FullyConnected(128, sn::calcMode::CUDA), "FC4") .addNode("FC4", sn::FullyConnected(10, sn::calcMode::CUDA), "LS2") .addNode("LS2", sn::LossFunction(sn::lossType::softMaxToCrossEntropy), "Summ") .addNode("FC5", sn::FullyConnected(128, sn::calcMode::CUDA), "FC6") .addNode("FC6", sn::FullyConnected(10, sn::calcMode::CUDA), "LS3") .addNode("LS3", sn::LossFunction(sn::lossType::softMaxToCrossEntropy), "Summ") .addNode("Summ", sn::Summator(), "Output"); ............. 


在示例中不是,您可以从此处复制。

在Python中,代码也看起来
  //   snet = snNet.Net() snet.addNode("Input", Input(), "C1 C2 C3") \ .addNode("C1", Convolution(15, 0, calcMode.CUDA), "P1") \ .addNode("P1", Pooling(calcMode.CUDA), "FC1") \ .addNode("C2", Convolution(12, 0, calcMode.CUDA), "P2") \ .addNode("P2", Pooling(calcMode.CUDA), "FC3") \ .addNode("C3", Convolution(12, 0, calcMode.CUDA), "P3") \ .addNode("P3", Pooling(calcMode.CUDA), "FC5") \ \ .addNode("FC1", FullyConnected(128, calcMode.CUDA), "FC2") \ .addNode("FC2", FullyConnected(10, calcMode.CUDA), "LS1") \ .addNode("LS1", LossFunction(lossType.softMaxToCrossEntropy), "Summ") \ \ .addNode("FC3", FullyConnected(128, calcMode.CUDA), "FC4") \ .addNode("FC4", FullyConnected(10, calcMode.CUDA), "LS2") \ .addNode("LS2", LossFunction(lossType.softMaxToCrossEntropy), "Summ") \ \ .addNode("FC5", FullyConnected(128, calcMode.CUDA), "FC6") \ .addNode("FC6", FullyConnected(10, calcMode.CUDA), "LS3") \ .addNode("LS3", LossFunction(lossType.softMaxToCrossEntropy), "Summ") \ \ .addNode("Summ", LossFunction(lossType.softMaxToCrossEntropy), "Output") ............. 


CIFAR-10




在这里,我已经必须启用batchNorm。 在1000次迭代(第100次迭代)中,此网格的学习精度高达50%。

这段代码原来
 sn::Net snet; snet.addNode("Input", sn::Input(), "C1") .addNode("C1", sn::Convolution(15, -1, sn::calcMode::CUDA, sn::batchNormType::beforeActive), "C2") .addNode("C2", sn::Convolution(15, 0, sn::calcMode::CUDA, sn::batchNormType::beforeActive), "P1") .addNode("P1", sn::Pooling(sn::calcMode::CUDA), "C3") .addNode("C3", sn::Convolution(25, -1, sn::calcMode::CUDA, sn::batchNormType::beforeActive), "C4") .addNode("C4", sn::Convolution(25, 0, sn::calcMode::CUDA, sn::batchNormType::beforeActive), "P2") .addNode("P2", sn::Pooling(sn::calcMode::CUDA), "C5") .addNode("C5", sn::Convolution(40, -1, sn::calcMode::CUDA, sn::batchNormType::beforeActive), "C6") .addNode("C6", sn::Convolution(40, 0, sn::calcMode::CUDA, sn::batchNormType::beforeActive), "P3") .addNode("P3", sn::Pooling(sn::calcMode::CUDA), "FC1") .addNode("FC1", sn::FullyConnected(2048, sn::calcMode::CUDA, sn::batchNormType::beforeActive), "FC2") .addNode("FC2", sn::FullyConnected(128, sn::calcMode::CUDA, sn::batchNormType::beforeActive), "FC3") .addNode("FC3", sn::FullyConnected(10, sn::calcMode::CUDA), "LS") .addNode("LS", sn::LossFunction(sn::lossType::softMaxToCrossEntropy), "Output"); 


我认为很明显,可以替换任何图片类。



U网蒂尼


最后一个例子。 简化的本地U-Net进行演示。



让我解释一下:DC1层-反卷积,Concat1层-通道的附加层,
Rsz1 ...-用于在相反的步骤中商定通道数,因为来自通道总和的误差从Concat层返回。

C ++代码。
  sn::Net snet; snet.addNode("In", sn::Input(), "C1") .addNode("C1", sn::Convolution(10, -1, sn::calcMode::CUDA), "C2") .addNode("C2", sn::Convolution(10, 0, sn::calcMode::CUDA), "P1 Crop1") .addNode("Crop1", sn::Crop(sn::rect(0, 0, 487, 487)), "Rsz1") .addNode("Rsz1", sn::Resize(sn::diap(0, 10), sn::diap(0, 10)), "Conc1") .addNode("P1", sn::Pooling(sn::calcMode::CUDA), "C3") .addNode("C3", sn::Convolution(10, -1, sn::calcMode::CUDA), "C4") .addNode("C4", sn::Convolution(10, 0, sn::calcMode::CUDA), "P2 Crop2") .addNode("Crop2", sn::Crop(sn::rect(0, 0, 247, 247)), "Rsz2") .addNode("Rsz2", sn::Resize(sn::diap(0, 10), sn::diap(0, 10)), "Conc2") .addNode("P2", sn::Pooling(sn::calcMode::CUDA), "C5") .addNode("C5", sn::Convolution(10, 0, sn::calcMode::CUDA), "C6") .addNode("C6", sn::Convolution(10, 0, sn::calcMode::CUDA), "DC1") .addNode("DC1", sn::Deconvolution(10, sn::calcMode::CUDA), "Rsz3") .addNode("Rsz3", sn::Resize(sn::diap(0, 10), sn::diap(10, 20)), "Conc2") .addNode("Conc2", sn::Concat("Rsz2 Rsz3"), "C7") .addNode("C7", sn::Convolution(10, 0, sn::calcMode::CUDA), "C8") .addNode("C8", sn::Convolution(10, 0, sn::calcMode::CUDA), "DC2") .addNode("DC2", sn::Deconvolution(10, sn::calcMode::CUDA), "Rsz4") .addNode("Rsz4", sn::Resize(sn::diap(0, 10), sn::diap(10, 20)), "Conc1") .addNode("Conc1", sn::Concat("Rsz1 Rsz4"), "C9") .addNode("C9", sn::Convolution(10, 0, sn::calcMode::CUDA), "C10"); sn::Convolution convOut(1, 0, sn::calcMode::CUDA); convOut.act = sn::active::sigmoid; snet.addNode("C10", convOut, "Output"); 


完整的代码和图像在这里


这样的开源数学。
我在MNIST上测试了所有层; TF作为评估错误的标准。


接下来是什么

库的宽度不会增加,也就是说,不会增加opencv,套接字等,以免膨胀。
库界面不会更改/扩展,我不会再说了,永远不会说,但最后但并非最不重要。

仅深入探讨:我将在OpenCL,C#接口,RNN网络上进行计算...
我认为添加MKL是没有道理的,因为网络更深一些-无论如何在视频卡上速度都更快,而且平均性能卡也不乏。

使用其他框架导入/导出权重-通过Python(尚未实现)。 路线图将是人们的兴趣所在。

请谁支持该代码。 但是存在一些限制,因此当前架构不会中断。

您可以将python的界面扩展到不可能,就像需要停靠和示例一样。

要从Python安装:

* pip安装libskynet-CPU
* pip安装libskynet-cu-CUDA9.2 + cuDNN7.3.1

Wiki用户指南

该软件是MIT许可免费分发的。

谢谢啦

Source: https://habr.com/ru/post/zh-CN425717/


All Articles