الشبكة العصبية لمطوري C ++

مرحبا بالجميع.

كتب مكتبة لتدريب شبكة عصبية. من يهتم ، من فضلك.

لطالما أردت أن أجعل نفسي أداة بهذا المستوى. C الصيف حصل على الأعمال التجارية. إليك ما حدث:

  • تمت كتابة المكتبة من الصفر في C ++ (فقط STL + OpenBLAS للحساب) ، واجهة C ، win / linux ؛
  • يتم تحديد بنية الشبكة في JSON ؛
  • طبقات الأساس: متصلة بالكامل ، تلافيفية ، تجميع. الإضافي: تغيير الحجم ، المحاصيل .. ؛
  • الميزات الأساسية: batchNorm ، التسرب ، محسنات الوزن - adam ، adagrad .. ؛
  • يستخدم OpenBLAS لحساب وحدة المعالجة المركزية ، CUDA / cuDNN لبطاقة الفيديو. كما وضع التطبيق على OpenCL للمستقبل.
  • لكل طبقة هناك فرصة لتعيين بشكل منفصل على ما يجب مراعاته - CPU أو GPU (وأيهما) ؛
  • لم يتم تعيين حجم بيانات الإدخال بشكل صارم ؛ يمكن أن تتغير أثناء العمل / التدريب ؛
  • جعل واجهات C ++ و Python. سيأتي # # لاحقًا أيضًا.

كانت المكتبة تسمى SkyNet. (كل شيء معقد مع الأسماء ، والبعض الآخر كان خيارات ، ولكن هناك شيء غير صحيح ..)


مقارنة مع PyTorch باستخدام مثال MNIST:

PyTorch: الدقة: 98٪ ، الوقت: 140 ثانية
سكاي نت: الدقة: 95٪ ، الوقت: 150 ثانية

الآلة: i5-2300 ، GF1060. كود الاختبار.



هندسة البرمجيات




وهو يعتمد على رسم بياني للعمليات يتم إنشاؤه ديناميكيًا مرة واحدة بعد تحليل بنية الشبكة.
لكل فرع ، خيط جديد. كل عقدة في الشبكة (Node) هي طبقة من الحساب.

هناك ميزات للعمل:

  • وظيفة التنشيط ، التطبيع بالدُفعة ، التسرب - يتم تنفيذها جميعًا كمعلمات لطبقات محددة ، وبعبارة أخرى ، لا توجد هذه الوظائف كطبقات منفصلة. ربما يجب اختيار batchNorm في طبقة منفصلة في المستقبل ؛
  • softMax ليست أيضًا طبقة منفصلة ؛ إنها تنتمي إلى طبقة LossFunction الخاصة. حيث يتم استخدامه عند اختيار نوع معين من حساب الخطأ ؛
  • يتم استخدام طبقة "LossFunction" لحساب الخطأ تلقائيًا ، ومن الواضح أنه لا يمكنك استخدام خطوات التقديم / الخلف (أدناه مثال للعمل مع هذه الطبقة) ؛
  • لا توجد طبقة "Flatten" ، لا حاجة إليها لأن طبقة "FullyConnect" نفسها ترسم صفيف الإدخال ؛
  • يجب ضبط مُحسِّن الوزن لكل طبقة وزن ؛ افتراضيًا ، يستخدم الجميع "آدم".

أمثلة


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") ............. 


سيفار 10




هنا كان علي بالفعل تمكين batchNorm. تتعلم هذه الشبكة ما يصل إلى 50٪ من الدقة عبر 1000 تكرار ، الدفعة 100.

تحول هذا الرمز
 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-net tyni


المثال الأخير. شبكة 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 كمعيار لتقييم الأخطاء.


ما هي الخطوة التالية

لن تنمو المكتبة في العرض ، أي عدم وجود مقابس ، وما إلى ذلك ، حتى لا تضخم.
لن تتغير / تتسع واجهة المكتبة ، ولن أقول ذلك على الإطلاق ولن يحدث ذلك أبدًا ، ولكن أخيرًا وليس آخرًا.

فقط بالتفصيل: سأفعل الحساب على OpenCL ، واجهة C # ، يمكن أن تكون شبكة RNN ...
أعتقد أن MKL ليس من المنطقي الإضافة ، لأن الشبكة أعمق قليلاً - إنها أسرع على أي حال على بطاقة الفيديو ، وبطاقة الأداء المتوسط ​​ليست نقصًا على الإطلاق.

استيراد / تصدير الأوزان مع أطر أخرى - من خلال Python (لم يتم تنفيذها بعد). ستكون خارطة الطريق إذا نشأ اهتمام بالناس.

من يمكنه دعم الكود من فضلك. ولكن هناك قيود بحيث لا تنكسر البنية الحالية.

يمكنك توسيع واجهة python إلى استحالة ، تمامًا كما هو مطلوب الأرصفة والأمثلة.

للتثبيت من Python:

* تثبيت نقطة libskynet - وحدة المعالجة المركزية
* تثبيت نقطة libskynet-cu - CUDA9.2 + cuDNN7.3.1

دليل مستخدم Wiki.

يتم توزيع البرنامج بحرية ، رخصة MIT.

شكرا لك

Source: https://habr.com/ru/post/ar425717/


All Articles