Русский
Русский
English
Статистика
Реклама

Вопрос залу

Совершенный цикл

26.08.2020 18:04:13 | Автор: admin

Сегодня необычный для меня формат статьи: я скорее задаю вопрос залу, нежели делюсь готовым рецептом. Впрочем, для инициирования дискуссии рецепт тоже предлагаю. Итак, сегодня мы поговорим о чувстве прекрасного :)


Я довольно давно пишу код, и так вышло, что практически всегда на C++. Даже и не могу прикинуть, сколько раз я написал подобную конструкцию:


for (int i=0; i<size; i++) {    [...]}

Хотя почему не могу, очень даже могу:


find . \( -name \*.h -o -name \*.cpp \) -exec grep -H "for (" {} \; | wc -l43641

Наш текущий проект содержит 43 тысячи циклов. Проект пилю не я один, но команда маленькая и проект у меня не первый (и, надеюсь, не последний), так что в качестве грубой оценки пойдёт. А насколько такая запись цикла for хороша? Ведь на самом деле, важно даже не то количество раз, когда я цикл написал, а то количество раз, когда я цикл прочитал (см. отладка и code review). А тут речь очевидно идёт уже о миллионах.


На КПДВ узел под названием совершенная петля (perfection loop).



Так каков он, совершенный цикл?


А в чём проблема?


Мы пишем много кода для математического моделирования; код довольно плотный, с огромным количеством целых чисел, которые являются индексами ячеек, функций и прочей лабуды. Чтобы был понятен масштаб проблемы, давайте я просто приведу крохотный кусочек кода из нашего проекта:


for (int iter=0; iter<nb_iter; iter++) {          // some iterative computation    for (int c=0; c<mesh.cells.nb(); c++)         // loop through all tetrahedra        for (int lv0=0; lv0<4; lv0++)             // for every vertex of the tet            for (int lv1 = lv0+1; lv1<4; lv1++)   // do stuff for subsequent vertices                for (int d=0; d<3; d++) {         // for each of 3 dimensions                    nlRowScaling(weight);                    nlBegin(NL_ROW);                    nlCoefficient(mesh.cells.vertex(c, lv0)*3 + d,  1);                    nlCoefficient(mesh.cells.vertex(c, lv1)*3 + d, -1);                    nlEnd(NL_ROW);                }    [...]}

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


Мы обязаны иметь дело с кучей вложенных циклов; вышеприведённые пять вложенных далеко не предел. Мы уже довольно давно (лет пятнадцать как) пришли к выводу, что стандартный for (int i=0; i<size; i++) это очень громоздкая конструкция: те самые пять вложенных заголовков for превращаются в совершенно нечитаемую кашу, и даже подсветка синтаксиса не спасает.


Когда мы читаем стандартный for(;;), мы должны на каждой строчке обратить внимание на три вещи: на инициализацию, на условие выхода и собственно на инкремент. Но ведь это совершеннейший оверкилл для тех случаев, когда нам нужно пройтись от 0 до size-1, а это подавляющее большинство всех циклов. Скажите, как часто вам приходится писать обратный цикл или итерацию с другими границами? Как мне кажется, один раз из десяти это ещё щедрая оценка.


До появления c++11 мы в итоге пришли к страшной вещи, а именно ввели в самый верхний заголовок вот такой дефайн:


#define FOR(I,UPPERBND) for(int I = 0; I<int(UPPERBND); ++I)

И тогда вышеприведённый кусок кода превращается из тыквы в кабачок:


FOR(iter, nb_iter) {    FOR(c, mesh.cells.nb())        FOR(lv0, 4)            for (int lv1 = lv0+1; lv1<4; lv1++)                FOR(d, 3) {                    nlRowScaling(weight);                    nlBegin(NL_ROW);                    nlCoefficient(mesh.cells.vertex(c, lv0)*3 + d,  1);                    nlCoefficient(mesh.cells.vertex(c, lv1)*3 + d, -1);                    nlEnd(NL_ROW);                }    [...]}

Польза такой трансформации в том, что когда я встречаю for (;;), я знаю, что мне нужно насторожиться и внимательно смотреть на все три места (инициализацию, условие, инкремент). В то время как если я вижу FOR(,) то это совершенно стандартный пробег от 0 до n-1 без каких-либо тонкостей. Я совершенно не предлагаю пользоваться вышеприведённым дефайном, но точно знаю, что для нашей команды он сберёг много ресурсов мозга, поскольку мы кода гораздо больше читаем (см. отладка), нежели пишем (как, наверное, и все программисты).


То есть, вопрос, которым я задаюсь, звучит так: "Как выглядит цикл, имеющий минимальную когнитивную нагрузку при чтении кода?"


Жизнь после 11го года, или range for


А как дела обстоят у соседей? Вы знаете, местами довольно недурно. Например, в лагере питонистов стандартный цикл выглядит следующим образом:


for i in range(n):    print(i)

Что любопытно, до третьего питона range() создавал в памяти массив индексов, и проходился по нему. И со времён c++11 мы вполне можем делать точно так же!


#include <iostream>int main() {    int range[] = {0,1,2,3,4,5};    for (int i : range) {        std::cerr << i;    }}

Разумеется, явно создавать в памяти массив индексов это несерьёзно, и с третьей версии в питоне это тоже поняли. Но и в C++ мы можем сделать не хуже!


Давайте посмотрим на следующую функцию range(int n):


#include <iostream>constexpr auto range(int n) {    struct iterator {        int i;        void operator++() { ++i; }        bool operator!=(const iterator& rhs) const { return i != rhs.i; }        const int &operator*() const { return i; }    };    struct wrapper {        int n;        auto begin() { return iterator{0}; }        auto end()   { return iterator{n}; }    };    return wrapper{n};}int main() {    for (int i: range(13)) {        std::cerr << i;    }    return 0;}

Пожалуйста, не начинайте int vs size_t, разговор не об этом. Если скомпилировать этот код при помощи gcc 10.2 с флагами компиляции -std=c++17 -Wall -Wextra -pedantic -O1, то мы получим следующий ассемблерный код (проверьте тут):


[...].L2:        mov     esi, ebx        mov     edi, OFFSET FLAT:_ZSt4cerr        call    std::basic_ostream<char, std::char_traits<char> >::operator<<(int)        add     ebx, 1        cmp     ebx, 13        jne     .L2[...]

То есть, компилятор начисто убрал все эти обёртки и оставил голый инкремент, ровно как если бы мы написали обычный for (int i=0; i<13; i++).


Лично мне кажется, что for (int i: range(n)) справляется с подчёркиванием обычности цикла чуть хуже, нежели FOR(,), но тоже вполне достойно, и за это не нужно платить дополнительными тактами процессора.


Продолжаем подглядывать в замочную скважину: enumerate


Range for в c++11 нанёс большую пользу. Давайте скажем, что у меня есть массив трёхмерных точек, и мне нужно распечатать икс координаты каждой точки, это можно сделать следующим образом:


#include <vector>#include <iostream>struct vec3 {  double x,y,z;  };int main() {    std::vector<vec3> points = {{6,5,8},{1,2,3},{7,3,7}};    for (vec3 &p: points) {        std::cerr << p.x;    }    return 0;}

for (vec3 &p: points) это прекрасная конструкция, никаких костылей, сразу из стандарта языка. Но что если у меня каждая точка из массива имеет цвет, вес или вкус? Это можно представить ещё одним массивом того же размера, что и массив точек. И тогда для доступа к атрибуту мне всё же понадобится индекс, который мы можем сгенерировать, например, вот таким образом:


    std::vector<vec3> points = {{6,5,8},{1,2,3},{7,3,7}};    std::vector<double> weights = {4,6,9};    int i = 0;    for (vec3 &p: points) {        std::cerr << p.x << weights[i++];    }

Для этого кода компилятор генерирует следующий ассемблер:


asm
.L2:        movsd   xmm0, QWORD PTR [r13+0]        mov     edi, OFFSET FLAT:_ZSt4cerr        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)        movsd   xmm0, QWORD PTR [rbp+0]        mov     rdi, rax        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)        add     rbp, 8        add     r13, 24        cmp     r14, rbp        jne     .L2

В принципе, имеет право на жизнь, но гулять так гулять, давайте снимем с программиста заботу о создании параллельного индекса, ровно как сделали в питоне, благо стандарт c++17 имеет structural binding!


Итак, можно сделать следующим образом:


#include <vector>#include <iostream>#include "range.h"struct vec3 {    double x,y,z;};int main() {    std::vector<vec3> points = {{6,5,8},{1,2,3},{7,3,7}};    std::vector<double> weights = {4,6,9};    for (auto [i, p]: enumerate(points)) {        std::cerr << p.x << weights[i];    }    return 0;}

Функция enumerate() определена в следующем заголовочном файле:


range.h
#ifndef __RANGE_H__#define __RANGE_H__#include <tuple>#include <utility>#include <iterator>constexpr auto range(int n) {    struct iterator {        int i;        void operator++() { ++i; }        bool operator!=(const iterator& rhs) const { return i != rhs.i; }        const int &operator*() const { return i; }    };    struct wrapper {        int n;        auto begin() { return iterator{0}; }        auto end()   { return iterator{n}; }    };    return wrapper{n};}template <typename T> constexpr auto enumerate(T && iterable) {    struct iterator {        int i;        typedef decltype(std::begin(std::declval<T>())) iterator_type;        iterator_type iter;        bool operator!=(const iterator& rhs) const { return iter != rhs.iter; }        void operator++() { ++i; ++iter; }        auto operator*() const { return std::tie(i, *iter); }    };    struct wrapper {        T iterable;        auto begin() { return iterator{0, std::begin(iterable)}; }        auto end()   { return iterator{0, std::end  (iterable)}; }    };    return wrapper{std::forward<T>(iterable)};}#endif // __RANGE_H__

При компиляции с флагами -std=c++17 -Wall -Wextra -pedantic -O2 мы получим следующий ассемблерный код (проверьте тут):


ASM
.L14:        movsd   xmm0, QWORD PTR [rbx]        mov     edi, OFFSET FLAT:_ZSt4cerr        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)        mov     rdi, rax        mov     rax, QWORD PTR [rsp+32]        movsd   xmm0, QWORD PTR [rax+rbp]        call    std::basic_ostream<char, std::char_traits<char> >& std::basic_ostream<char, std::char_traits<char> >::_M_insert<double>(double)        add     rbx, 24        add     rbp, 8        cmp     r12, rbx        jne     .L14

И снова компилятор начисто убрал обёртку (правда, для этого пришлось поднять уровень оптимизации с -O1 на -O2).
Кстати, в c++20 появился std::ranges, что ещё больше упрощает написание такой функции, но я пока не готов переходить на этот стандарт.


Вопрос залу


На ваш взгляд, каким должен быть совершенный цикл в 2020м году? Научите меня!


Если вы ещё не задавались этим вопросом, то скопируйте к себе в пет-проект заголовочный файл range.h и попробуйте его поиспользовать хотя бы несколько дней.

Подробнее..

Нейросети где взять данные для тонкого обучения алгоритмов?

06.10.2020 20:18:31 | Автор: admin

Привет, Хабр! Меня зовут Алиса Невейкина, и я работаю в стартапе из Беларуси SmartCoders. Мы занимаемся разработкой нейросетей и решений на базе ИИ для бизнеса. Этот пост стал результатом глубоких раздумий об особенностях развития проектов с использованием машинного обучения, а также о моделях монетизации данных технологий. Если вы уже работали с ИИ или планируете это сделать, приглашаю присоединиться к обсуждению.


Разработка ИИ определяется алгоритмами, которые заложены в нейросеть. Однако проверить их можно только опираясь на значительные объемы данных. Если речь идет об уже обкатанной системе, которая прошла боевое крещение хотя бы в одной из компаний, то тут все понятно. Но что делать в случае, когда нейросети нужны данные как воздух, чтобы доказать свою жизнеспособность?


Мы начинали работать как подрядчики на разных проектах, выполняя задачи заказчика. Так появилось решение для проекта Salary2.me, который помогает определить реальную зарплату ИТ-работника в Москве, Киеве, Минске и во многих городах Европы.


Но чтобы претендовать на универсальность этим технологиям не хватает машинного обучения по существующим дата-сетам.


Где взять данные для машинного обучения?


На этапе разработки самих алгоритмов можно использовать какие-то синтетические наборы данных. Однако для дальнейшего совершенствования функций ИИ этого недостаточно. Нужны живые дата-сеты, чтобы найти corner cases, проверить, как ведут себя алгоритмы на разных выборках и так далее. Но получить такой набор оказывается не так-то просто, потому что:


Все боятся за конфиденциальность данных


Несмотря на то, что для тестирования ИИ достаточно обезличенных данных, которые не содержат никакой персональной информации, компании боятся санкций со стороны регуляторов за передачу своих клиентских баз, да и мало кто может выдать обезличенный дата-сет. Его нужно готовить, а заниматься этим некому.


Существует проблеме недоверия


Еще одна проблема это отсутствие доверия. А вдруг мы со своим ИИ пришли от лица конкурентов? Вот нас пустят, дадут работать с данными, а база окажется в чужих руках.


Денег нет


Наконец, на тестирование ИИ нужны средства. Но если эффект не очевиден, в бюджете не появляется денег на внедрение и развитие ИИ. Компания просто продолжает работать также как работала.


Что делать?


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


Возможно, эту проблему можно решить одним из трех способов:


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


Заниматься развитием ИИ, вкладывая собственные средства, как партнер для магазина, игровой платформы, клиентского сервиса. При этом договориться об оплате в виде какой-то части добавочной прибыли. Мне кажется, что такой подход вообще будет более честным и справедливым. Остается только вопрос, как зафиксировать компенсацию в договоре?


Проводить бесплатные пилотные проекты, тестируя эффективность решения на ограниченном количестве пользователей. Но как найти компании, готовые к диалогу в России и СНГ, если у большинства пока нет практики сбора аналитики, а опасения за свои данные накладывают вето на участие даже в бесплатных тестах.


Развитие проектов


Дополнительным аргументом в пользу длительного сотрудничества является желание развивать дальше проекты, в которые уже было вложено много сил и креатива. Тем более, что отдача от ИИ может вырасти, если продолжить fine tuning алгоритмов.


Я понимаю, что этот пост порождает больше вопросов, чем ответов. И если у вас есть свое мнение или уже состоявшийся опыт развития нейросетей и поиска данных для их обучения, поделитесь им, пожалуйста, в комментариях. Буду признательна за живую дискуссию на эту тему.

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru