Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > .NET Frameworks (точка нет фреймворки) > C# (си шарп)
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 15.06.2012, 08:38   #1
yaapelsinko
Пользователь
 
Регистрация: 15.01.2012
Сообщений: 67
По умолчанию Parallel.For ничерта не ускоряет

Значит, играюсь я тут это с нейронными сетями. Два года игрался в один поток, но решил - пора, пора уже перевести их на параллельные рельсы, зря что ли четыре ядра в компьютере?

Поскольку в нейронных сетях при вычислениях для каждого конкретного нейрона в соседние нейроны заглядывать не приходится, то вот я и распараллелил циклы проходов по нейронам, используя System.Threading.Tasks.Parallel.For .

Загрузка процессора стала хоть куда, если раньше один поток шпыняло по ядрам туда сюда и процессор загружался на 25-28% (т.е. поток вычисоений плюс система), то теперь больше 95%, все четыре ядра пашут, аж дым столбом.

Только вот количество итераций обучения за единицу времени при этом никак не изменилось. Создаю сеть со скрытыми 100х100 нейронами, там параллель - не хочу, для каждого нейрона во второй сотне нужно провести цикл по сотне его входных связей. Но что параллельно - 5 итераций в секунду, что последовательно - 5 в секунду.

Ладно бы только это, так в параллельном режиме сеть даёт отклик с ошибками, и чем больше сеть, тем больше ошибок. Выглядит так, будто бы в принципе считает правильно, но в какой-то момент формула получает какое-то неверное значение. Поток пытается обратиться к элементу другого слоя сети одновременно с другим потоком? Ну я делаю lock на нейрон, к которому теоретически может быть одновременное обращение других потоков - ничё не меняется.

Вот, скажем, код прожёвывания сетью одного входного вектора:

Код:
 public float[] Processing(float[] Input)
        {
            int LL = Layers.Length - 1; //Last Layer
            int LLNC = Layers[LL].Neurons.Length; //Last Layer Neuron count

            if (Input.Length != this.Layers[0].Neurons.Length - 1)
            {
                return new float[] {};
            }

            for (int i = 0; i < this.Layers[0].Neurons.Length - 1; i++)
            {
                this.Layers[0].Neurons[i].Output = Perceptron.Normalize(Input[i], NormFactor);
            }

            //Распространение сигнала дальше по слоям
            float InputWeightedSum;
            int Length;

            for (int i = 1; i < this.Layers.Length; i++) //i-й слой
            {
                if (i == this.Layers.Length - 1)
                {
                    Length = this.Layers[i].Neurons.Length;
                }
                else
                {
                    Length = this.Layers[i].Neurons.Length - 1;
                }

                Parallel.For(0, Length, (j) =>
                //for (int j = 0; j < Length; j++) //j-й нейрон
                {
                    InputWeightedSum = 0;

                    for (int k = 0; k < this.Layers[i].Neurons[j].InputWeights.Length; k++) //k-й вес
                    {
                        lock (this.Layers[i - 1].Neurons[k])
                        {
                            InputWeightedSum += this.Layers[i].Neurons[j].InputWeights[k] * this.Layers[i - 1].Neurons[k].Output;
                        }
                    }

                    this.Layers[i].Neurons[j].Output = ExpSigmoid(InputWeightedSum, this.SigmaFactor);
                });
            }
            
            float[] Out = new float[LLNC];
            for (int i = 0; i < LLNC; i++)
            {
                Out[i] = Perceptron.Denormalize(this.Layers[LL].Neurons[i].Output, NormFactor);
            }
            return Out;
        }

Последний раз редактировалось yaapelsinko; 15.06.2012 в 11:18.
yaapelsinko вне форума Ответить с цитированием
Старый 15.06.2012, 10:59   #2
yaapelsinko
Пользователь
 
Регистрация: 15.01.2012
Сообщений: 67
По умолчанию

Ха, блин, да это просто издевательство, а не многопоточность.

Значит, с ошибочным пересчётом сети разобрался - оказывается, надо было лочить глобальную в рамках сети переменную this.SigmaFactor - сделал объявление новой переменной внутри цикла, лочу зыс, присваиваю значение - всё, работает нормально, даже если не лочить при этом нейрон, с которого в цикле берётся выходное значение. То ли конфликтов не возникает, то ли они настолько редкие, что незаметно.

Однако ж по скорости это всё - даже медленнее немного, чем в один поток. Не смотри, что процессор за 95% загружен. Ну ЧЯДНТ?

Даже выставляя ограничение на степень параллельность равное 4 (не более 4 потоков одновременно) - улучшения производительности нет (а то я бы понял, если процессор перегружается 40-100 потоками и не может это всё провернуть).

При этом, поскольку процессор всё же загружен на 95%, эти потоки реально работают, а не простаивают в ожидании разлочивания общего ресурса (если бы простаивали - процессор бы не нагружали).

Другая распараллеленая процидурка (шаг обучения):

Код:
        public static void PerceptronOnline(Perceptron Network, float[] input, float[] output, float LearningSpeed = 1, float LearningMomentum = 0.1f) //Обучение
        {
            //Perceptron Network = Net;

            int LL = Network.Layers.Length - 1; //индекс последнего слоя
            int LLNC = Network.Layers[LL].Neurons.Length; //количество нейронов последнего слоя
                        
            Network.Processing(input); //Прожевали пример

            for (int n = LL; n > 0; n--) //n-й слой, обратный порядок
            {
                //коэффициенты корекции весов
                ParallelOptions po = new ParallelOptions();
                po.MaxDegreeOfParallelism = 4;

                Parallel.For(0, Network.Layers[n].Neurons.Length, po, (i) =>
                //for (int i = 0; i < Network.Layers[n].Neurons.Length; i++) //i-й нейрон n-го слоя, 
                {                    
                    if (n == LL)
                    {
                        float O = Network.Layers[LL].Neurons[i].Output;
                        Network.Layers[LL].Neurons[i].Delta = (O) * (1 - O) * (Perceptron.Normalize(output[i], Network.NormFactor) - O);
                    }
                    else
                    {
                        float dwsum = 0; //Сумма d(k) w(ik) с n+1 слоя

                        for (int k = 0; k < Network.Layers[n + 1].Neurons.Length; k++) //Пробежка по i-ым весам k-го нейрона на (n+1)-ом слою
                        {
                            dwsum += Network.Layers[n + 1].Neurons[k].Delta * Network.Layers[n + 1].Neurons[k].InputWeights[i];
                        }

                        float O = Network.Layers[n].Neurons[i].Output;
                        Network.Layers[n].Neurons[i].Delta = (O) * (1 - O) * dwsum;
                    }
                });

                //коррекция весов
                Parallel.For(0, Network.Layers[n].Neurons.Length, po, (i) =>
                //for (int i = 0; i < Network.Layers[n].Neurons.Length; i++)
                {
                    //Коррекция j-х весов i-го нейрона n-го слоя
                    for (int j = 0; j < Network.Layers[n].Neurons[i].InputWeights.Length; j++) //Пробежка по j-м весам i-го нейрона
                    {
                        Network.Layers[n].Neurons[i].Update[j] = LearningSpeed * Network.Layers[n].Neurons[i].Delta * Network.Layers[n - 1].Neurons[j].Output +
                                                              LearningMomentum * Network.Layers[n].Neurons[i].Update[j];

                        Network.Layers[n].Neurons[i].InputWeights[j] += Network.Layers[n].Neurons[i].Update[j];
                        //Скорректировали веса нейрона
                    }
                });
            } //Завершили работу с очередным слоем

        }

Последний раз редактировалось yaapelsinko; 15.06.2012 в 11:18.
yaapelsinko вне форума Ответить с цитированием
Старый 15.06.2012, 11:32   #3
Гаврилов
Пользователь
 
Регистрация: 11.05.2012
Сообщений: 85
По умолчанию

Недавно я присутствовал на крещении племянника в одном монастыре. Батюшку ждали 3 часа, а потом он ещё сподобился выдать нам проповедь. Главной темой этой проповеди было: "Весь мир и человек были созданы одновременно 8 тысяч лет назад. А наукам не верьте - все они от Лукавого". Причем, без очков было видно, что все сидящие перед ним (кроме замученных детишек, конечно) имеют как минимум по одному диплому, т.е. суть представители этих самых наук, которые от Лукавого,-бесы, другим словом.
К Вашему вопросу это имеет вот какое отношение.
И я тоже ожидал от многопроцессорности гораздо большего. Почему-то в машине AS-400 с осью OS-400 это получалось неплохо, и можно было практически до бесконечности наращивать количество основных процессоров (счета), и процессоров ввода-вывода, а главное, с пользой. А вот с нашими настольными и переносными компами, оснащенными разной пробы Виндами, этого явно не произошло. Видно, дело в архитектуре, которая, как и женщина, может дать не более того, что она может дать. И никакие программные ухищрения здесь не помогут.
Вот и поверишь, что все от Лукавого, по крайней мере Windows.
Гаврилов вне форума Ответить с цитированием
Старый 15.06.2012, 13:49   #4
Скарам
Дружите с Linq ;)
Форумчанин
 
Аватар для Скарам
 
Регистрация: 15.10.2008
Сообщений: 822
По умолчанию

По сути Вы работаете с один и тем же объектом в разных потоках, т.е. он лочиться, передается из потока в поток, чтобы можно было обратиться к какому-то слою.. И как это получиться быстрее, чем в одном потоке?.. нужно попробовать "нарезать" слои, после это только работать в параллели..
Не давай организму поблажки, каждый день тренируй его в шашки..
Скарам вне форума Ответить с цитированием
Старый 15.06.2012, 14:15   #5
yaapelsinko
Пользователь
 
Регистрация: 15.01.2012
Сообщений: 67
По умолчанию

Ну я же с разными полями этого объекта работаю.
Я не вижу принципиальной разницы с примером от MS - http://msdn.microsoft.com/ru-ru/library/dd460713.aspx

Опять же, если бы дело было в том, что объект лочится не даёт работать другим потокам - у меня не могло бы быть полной загрузки процессора, как я понимаю.

Всё, что я реально лочу в процессе пересчёта сети - это до инициализации длинного цикла (который вызывается внутри параллельного потока) лочу на время считывания одну переменную из объекта. Считываю и разлочиваю. То есть, тут больших задержек возникать не должно.
В процессе расчёта взвешенной суммы лок убрал - он почему-то ни на что не виляет, ну и хорошо. То есть, циклы свободно выполняются в параллель и друг другу не мешают (теоретически, практически, я, если честно, не понимаю, почему сигму считывать без лока нельзя, а нейроны и их поля можно).
В процедуре обучения там вообще всё как у микрософта в туториале. Ну только там у них массив всё ж, а у меня объект.
А производительность одна и та же.
yaapelsinko вне форума Ответить с цитированием
Старый 15.06.2012, 16:25   #6
Lime
Форумчанин
 
Аватар для Lime
 
Регистрация: 10.02.2009
Сообщений: 815
По умолчанию

Соглашусь с Скарам. А процессор нагружается в следствии того что все потоки работают практически одновременно, но благодаря lock'у выходит 1 "полезное" действие (4 потока попеременно работают над одним обьектом).
И ещё, каким способом вы обучатете сеть?
Насколько я понимаю (сейчас этим 1 раз занимаюсь) процесс обучения нельзя разделить на несколько отдельных потоков, ведь каждая операция затрагивает несколько обьектов, которые во время просчета могут быть изменены другим потоком.. и получится каша
Lime вне форума Ответить с цитированием
Старый 15.06.2012, 21:52   #7
yaapelsinko
Пользователь
 
Регистрация: 15.01.2012
Сообщений: 67
По умолчанию

Говорят - http://www.albahari.com/threading/ - что A thread, while blocked, doesn't consume CPU resources.
К тому же блокировки у меня сейчас минимум, не должна она настолько влиять.
Это раз.

Второе, всё, что происходит внутри одного слоя нейронной сети, прекрасно распараллеливается, так как нейроны не имеют связей между собой в пределах этого слоя. Соответственно, можно заниматься просчётом хоть всех нейронов слоя одновременно, беря соответствующие необходимые значения из предыдущего или последующего слоя.
yaapelsinko вне форума Ответить с цитированием
Старый 15.06.2012, 23:49   #8
Lime
Форумчанин
 
Аватар для Lime
 
Регистрация: 10.02.2009
Сообщений: 815
Смущение

Цитата:
Сообщение от yaapelsinko Посмотреть сообщение
Второе, всё, что происходит внутри одного слоя нейронной сети, прекрасно распараллеливается, так как нейроны не имеют связей между собой в пределах этого слоя. Соответственно, можно заниматься просчётом хоть всех нейронов слоя одновременно, беря соответствующие необходимые значения из предыдущего или последующего слоя.
Так и есть, я невнимательно прочитал 2рой кусок кода (предположил что это изменённый варинт 1го и пропустил).
Lime вне форума Ответить с цитированием
Старый 16.06.2012, 01:34   #9
yaapelsinko
Пользователь
 
Регистрация: 15.01.2012
Сообщений: 67
По умолчанию

...и, кстати, что касается процедуры обучения - там вообще всё в шоколаде.

Цикл распараллеливается так, что в каждом потоке свой собственный индекс i. Внутри же я запускаю цикл, который обращается в нейронам более высокого слоя, который на данным момент уже был обработан.
В каждом нейроне каждый поток обращается только к своим весам (то есть, поток с i=1 - к первому элементу массива весов, с i=2 - ко второму элементу, и так далее, практически как в туториале от МС).

То есть, там всё должно нормально работать. Однако вот - работать работает, да не быстрее, а даже пожалуй медленнее.
yaapelsinko вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Микрофон ускоряет голос Yarigk Компьютерное железо 2 05.09.2011 12:29
Ускоряет или нет. _PROGRAMM_ Софт 5 24.06.2010 12:43