我正在开发一个基于.Net的遗传机器学习项目(与我常用的Matlab不同)。我不是专业的.Net程序员,所以请原谅我的任何新手实现方式。
这个项目本身非常庞大,我不会用全部细节来烦你,但基本上,一个由人工神经网络(类似于决策树)组成的种群会在一个问题领域中被评估,这个领域使用一系列的感官输入。在种群中表现最佳的个体被允许繁殖并产生后代(后代继承了双亲的倾向),而表现不佳的个体则被淘汰或从种群中移除。进化过程会持续进行,直到找到一个可接受的解决方案。一旦找到,最终进化出的“网络”将从实验室中提取出来,并放置在一个轻量级的现实世界应用中。这种技术可以用来开发非常复杂的控制解决方案,这些解决方案通常几乎不可能或耗时过长以至于无法通过常规编程实现,例如自动驾驶汽车、机械稳定性控制、数据中心负载平衡等,如此种种。
无论如何,到目前为止,这个项目已经取得了巨大的成功,并且产生了惊人的结果,但唯一的问题是一旦我转向更大的数据集时,性能非常慢。我希望这只是我的代码问题,所以我非常希望能得到一些专家的帮助。
在这个项目中,收敛到接近理想的解决方案通常需要大约7天的处理时间!仅仅是调整一个参数并等待结果就太痛苦了。
基本上,多个并行线程需要顺序读取一个非常大的数据集(数据一旦加载后不会改变)。数据集每行包含大约300到1000个双精度浮点数,并且行数超过500k。由于数据集可能超过.Net对象的2GB限制,它不能存储在普通的二维数组中——解决这个问题的最简单方法是使用单数组的通用列表。
并行可扩展性似乎是一个很大的限制因素,因为在一台配备32个Xeon核心的强大服务器上运行代码(通常可以轻松处理大型数据集)并没有比在Core i3桌面电脑上获得更多的性能提升!
随着核心数量的增加,性能提升很快就消失了。
通过对代码进行分析(以我有限的知识),我感觉在多个线程读取数据集时存在大量的争用情况。
我尝试过使用锯齿数组和各种并发集合来实验不同的数据集实现方式,但都没有效果。
我编写了一段快速而粗糙的基准测试代码,与原始代码的核心实现类似,仍然表现出类似的读取性能问题和并行可扩展性问题。
任何想法或建议都将不胜感激,或者确认这是我能得到的最佳结果。
非常感谢
using System;using System.Collections.Generic;using System.Diagnostics;using System.Threading.Tasks;//Benchmark script to time how long it takes to read dataset per iterationnamespace Benchmark_Simple{class Program{ public static TrainingDataSet _DataSet; public static int Features = 100; //Real test will require 300+ public static int Rows = 200000; //Real test will require 500K+ public static int _PopulationSize = 500; //Real test will require 1000+ public static int _Iterations = 10; public static List<NeuralNetwork> _NeuralNetworkPopulation = new List<NeuralNetwork>(); static void Main() { Stopwatch _Stopwatch = new Stopwatch(); //Create Dataset Console.WriteLine("Creating Training DataSet"); _DataSet = new TrainingDataSet(Features, Rows); Console.WriteLine("Finished Creating Training DataSet"); //Create Neural Network Population for (int i = 0; i <= _PopulationSize - 1; i++) { _NeuralNetworkPopulation.Add(new NeuralNetwork()); } //Main Loop for (int i = 0; i <= _Iterations - 1; i++) { _Stopwatch.Restart(); Parallel.ForEach(_NeuralNetworkPopulation, _Network => { EvaluateNetwork(_Network); }); //######## Removed for simplicity ########## //Run Evolutionary Genetic Algorithm on population - I.E. Breed the strong, kill of the weak //########################################## //Repeat until acceptable solution is found Console.WriteLine("Iteration time: {0}", _Stopwatch.ElapsedMilliseconds / 1000); _Stopwatch.Stop(); } Console.ReadLine(); } private static void EvaluateNetwork(NeuralNetwork Network) { //Evaluate network on 10% of the Training Data at a random starting point double Score = 0; Random Rand = new Random(); int Count = (Rows / 100) * 10; int RandonStart = Rand.Next(0, Rows - Count); //The data must be read sequentially for (int i = RandonStart; i <= RandonStart + Count; i++) { double[] NetworkInputArray = _DataSet.GetDataRow(i); //####### Dummy Evaluation - just give it somthing to do for the sake of it double[] Temp = new double[NetworkInputArray.Length + 1]; for (int j = 0; j <= NetworkInputArray.Length - 1; j++) { Temp[j] = Math.Log(NetworkInputArray[j] * Rand.NextDouble()); } Score += Rand.NextDouble(); //################## } Network.Score = Score; } public class TrainingDataSet { //Simple demo class of fake data for benchmarking private List<double[]> DataList = new List<double[]>(); public TrainingDataSet(int Features, int Rows) { Random Rand = new Random(); for (int i = 1; i <= Rows; i++) { double[] NewRow = new double[Features]; for (int j = 0; j <= Features - 1; j++) { NewRow[j] = Rand.NextDouble(); } DataList.Add(NewRow); } } public double[] GetDataRow(int Index) { return DataList[Index]; } } public class NeuralNetwork { //Simple Class to represent a dummy Neural Network - private double _Score; public NeuralNetwork() { } public double Score { get { return _Score; } set { _Score = value; } } }}}
回答: