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

Машинное обучение

Пора избавляться от мышки или Hand Pose Estimation на базе LiDAR за 30 минут

12.01.2021 14:15:46 | Автор: admin
image

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

Предлагаю начать с посмотра коротенького видео, на котором видно, как можно за пару вечеров накидать простейшее управления курсором мышки на основе Object Detection, Hand Pose Estimation и камеры Intel Realsense L515. Конечно, оно далеко от идеала, но кажется, что осталось совсем немного подтянуть технологии и появятся принципиально новые способы управлять устройствами.



Справа на экране в реальном времени происходит отображение руки в RGB и Depth формате, то есть на карте глубины, получаемой с лидара, что даёт нам возможность получить координаты ключевых точек фаланги пальца это красные и чёрные точки соотвественно. Как видно на видео, курсор двигается довольно плавно, и мне не потребовалось долго привыкать к такому управлению.

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

Основная идея это двигать мышь, передвигая не всю руку, а только указательный палец, что позволит не отрывая рук от клавиатуры, бегать по меню, нажимать кнопки и в совокупности с горячими клавишами превратиться в настоящего клавиатурного ninja! А что будет если добавить жесты пролистывания или скоролла? Думаю будет бомба! Но до этого момента нам еще придётся подождать пару-тройку лет)

Начнём собирать наш протитип манипулятора будущего



Что понадобится:
1. Камера с LiDAR Intel Realsense L515.
2. Умение программировать на python
3. Совсем чуть-чуть вспомнить школьную математику
4. Крепление для камеры на монитор ака штатив

Крепим камеру на шатив с алиэкспресс, он оказался очень удобный, лёгкий и дешевый =)
image
image

Разбираемся, как и на чём делать прототип



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

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

Во-первых, там все уже есть из коробки установка и запуск потребует минут 30, с учётом всех пререквизитов.

Во-вторых, благодаря мощной команде разработчиков, они не только бирут State Of Art в Hand Pose Estimation, но и дают лёгкое в понимание API.

В-третьих, сеть готова работать на CPU, так что порог входа минимален.

Наверное, вы спросите почему я не зашёл вот сюда и не воспользовался репозиториями победителей этого соревнования. На самом деле я довольно подробно изучил их решение, они вполне prod-ready, никаких стаков миллионов сеток и т.д. Но самая большая проблема, как мне кажется это то, что они работают с изображением глубины. Так как это академики, они не гнушались все данные конвертировать через матлаб, кроме того, разрешение, в котором были отсняты глубины, мне показались маленьким. Это могло сильно сказаться на результате. Поэтому, кажется, что проще всего получить ключевые точки на RGB картинке и по XY координатам взять значение по оси Z в Depth Frame. Сейчас не стоит задача сильно что-то оптимизировать, так что сделаем так, как это быстрее с точки зрения разработки.

Вспоминаем школьную математику



Как я уже писал что бы получить координату точки, где должен оказаться курсор мышки, нам необходимо построить линию, проходящую через две ключевые точки фаланги пальца, и найти точку пересечения линии и плоскости монитора.
image

На картинке схематично изображена плоскость монитора и линия, ее пересекающая. Посмотреть на математику можно тут

По двум точкам получаем параметрическое представление прямой в пространстве.

image

Не буду сильно заострять внимание на школьной программе математики.

Установка бибилотеки для работы с камерой


Пожалуй, это самая сложная часть этой работы. Как оказалось, софт для камеры под Ubuntu очень сырой, librealsense просто завален все возможными багами, глюками и танцами с бубном.
До сих пор мне не удалось победить странное поведение камеры, иногда она не подгружает параметры при запуске.
Камера работает только один раз после рестарта компьютера!!! Но есть решение: перед каждым запуском делать програмно hard reset камеры, резет usb, и, может быть, всё будет хорошо. Кстати для Windows 10 там все нормально. Странно разработчики себе представляют роботов на базе винды =)

Чтобы под Ubuntu 20 у вас завелся realsense, сделайте так:
$ sudo apt-get install libusb-1.0-0-devThen rerun cmake and make install. Here is a complete recipe that worked for me:$ sudo apt-get install libusb-1.0-0-dev$ git clone https://github.com/IntelRealSense/librealsense.git$ cd librealsense/$ mkdir build && cd build$ cmake ../ -DFORCE_RSUSB_BACKEND=true -DBUILD_PYTHON_BINDINGS=true -DCMAKE_BUILD_TYPE=release -DBUILD_EXAMPLES=true -DBUILD_GRAPHICAL_EXAMPLES=true$ sudo make uninstall && make clean && make && sudo make install


Собрав из сорцов, оно будет более или менее стабильно.
Месяц общения с техподдержкой выявил, что надо ставить Ubuntu 16 или страдать. Я выбрал сами понимаете что.

Продолжаем разбираться в тонкостях работы нейросети



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


Это, как вы поняли, не тряска моих рук, на праздниках я выпил всего одну кружку New England DIPA =) Все дело в постоянных флуктациях ключевых точек и Z-координаты на основе значений, получаемых от лидара.
Посмотим вблизи:


В нашей SOTA от mediapipe флуктаций конечно меньше, но они тоже есть. Как выяснилось, они борются с этим путём прокидывания из прошлого кадра heatmap в текущий кадр и обучают сеть это даёт больше стабильности, но не 100%.

Еще, как мне кажется, играет роль специфика разметки. Врядли можно сделать на таком колличестве кадров одинаковую разметку, не говоря уже о том, что разрешение кадра везде разное и не очень большое. Также мы не видим мерцание света, которое, вероятнеё всего, не постоянно из-за разного периода работы и величины экспозиции камеры. И еще сеть возращает бутерброд из heatmap, равный количеству ключевых точек на экране, размер этого тензора BxNx96x96, где N это кол-во ключевых точек, и, конечно же, после threshold и resize к оригинальному размеру кадра, мы получаем то что получаем =(

Прмер визуализации heatmap:
image

Обзор кода


Весь код находится в этом репозитории и он очень короткий. Давайте разберём основной файл, а остальное вы посмотрите сами.
import cv2import mediapipe as mpimport numpy as npimport pyautoguiimport pyrealsense2.pyrealsense2 as rsfrom google.protobuf.json_format import MessageToDictfrom mediapipe.python.solutions.drawing_utils import _normalized_to_pixel_coordinatesfrom pynput import keyboardfrom utils.common import get_filtered_values, draw_cam_out, get_right_indexfrom utils.hard_reset import hardware_resetfrom utils.set_options import set_short_rangepyautogui.FAILSAFE = Falsemp_drawing = mp.solutions.drawing_utilsmp_hands = mp.solutions.hands# инициализируем mediapipe для Hand Pose Estimationhands = mp_hands.Hands(max_num_hands=2, min_detection_confidence=0.9) def on_press(key):    if key == keyboard.Key.ctrl:        pyautogui.leftClick()    if key == keyboard.Key.alt:        pyautogui.rightClick()def get_color_depth(pipeline, align, colorizer):    frames = pipeline.wait_for_frames(timeout_ms=15000) # ождидаем фрейм от камеры    aligned_frames = align.process(frames)     depth_frame = aligned_frames.get_depth_frame()    color_frame = aligned_frames.get_color_frame()    if not depth_frame or not color_frame:        return None, None, None    depth_image = np.asanyarray(depth_frame.get_data())    depth_color_image = np.asanyarray(colorizer.colorize(depth_frame).get_data())    color_image = np.asanyarray(color_frame.get_data())    depth_color_image = cv2.cvtColor(cv2.flip(cv2.flip(depth_color_image, 1), 0), cv2.COLOR_BGR2RGB)    color_image = cv2.cvtColor(cv2.flip(cv2.flip(color_image, 1), 0), cv2.COLOR_BGR2RGB)    depth_image = np.flipud(np.fliplr(depth_image))    depth_color_image = cv2.resize(depth_color_image, (1280 * 2, 720 * 2))    color_image = cv2.resize(color_image, (1280 * 2, 720 * 2))    depth_image = cv2.resize(depth_image, (1280 * 2, 720 * 2))    return color_image, depth_color_image, depth_imagedef get_right_hand_coords(color_image, depth_color_image):    color_image.flags.writeable = False    results = hands.process(color_image)    color_image.flags.writeable = True    color_image = cv2.cvtColor(color_image, cv2.COLOR_RGB2BGR)    handedness_dict = []    idx_to_coordinates = {}    xy0, xy1 = None, None    if results.multi_hand_landmarks:        for idx, hand_handedness in enumerate(results.multi_handedness):            handedness_dict.append(MessageToDict(hand_handedness))        right_hand_index = get_right_index(handedness_dict)        if right_hand_index != -1:            for i, landmark_list in enumerate(results.multi_hand_landmarks):                if i == right_hand_index:                    image_rows, image_cols, _ = color_image.shape                    for idx, landmark in enumerate(landmark_list.landmark):                        landmark_px = _normalized_to_pixel_coordinates(landmark.x, landmark.y,                                                                       image_cols, image_rows)                        if landmark_px:                            idx_to_coordinates[idx] = landmark_px            for i, landmark_px in enumerate(idx_to_coordinates.values()):                if i == 5:                    xy0 = landmark_px                if i == 7:                    xy1 = landmark_px                    break    return color_image, depth_color_image, xy0, xy1, idx_to_coordinatesdef start():    pipeline = rs.pipeline() # инициализируем librealsense    config = rs.config()     print("Start load conf")    config.enable_stream(rs.stream.depth, 1024, 768, rs.format.z16, 30)    config.enable_stream(rs.stream.color, 1280, 720, rs.format.bgr8, 30)    profile = pipeline.start(config)     depth_sensor = profile.get_device().first_depth_sensor()    set_short_range(depth_sensor) # загружаем параметры для работы на маленьком расстоянии    colorizer = rs.colorizer()    print("Conf loaded")    align_to = rs.stream.color    align = rs.align(align_to) # совокупляем карту глубины и цветную картинку    try:        while True:            color_image, depth_color_image, depth_image = get_color_depth(pipeline, align, colorizer)            if color_image is None and color_image is None and color_image is None:                continue            color_image, depth_color_image, xy0, xy1, idx_to_coordinates = get_right_hand_coords(color_image,                                                                                                 depth_color_image)            if xy0 is not None or xy1 is not None:                z_val_f, z_val_s, m_xy, c_xy, xy0_f, xy1_f, x, y, z = get_filtered_values(depth_image, xy0, xy1)                pyautogui.moveTo(int(x), int(3500 - z))  # 3500 хард код специфичый для моего монитора                if draw_cam_out(color_image, depth_color_image, xy0_f, xy1_f, c_xy, m_xy):                    break    finally:        hands.close()        pipeline.stop()hardware_reset() # делаем ребут камеры и ожидаем её появленияlistener = keyboard.Listener(on_press=on_press) # устанавливаем слушатель нажатия кнопок клавиатурыlistener.start()start() # запуск программы


Я не стал использовать классы или потоки, потому что для такого простого случая достаточно всё выполнять в основном потоке в бесконечном цикле while.

В самом начале происходит инициализация mediapipe, камеры, загрузка настроек камеры для работы short range и вспомогательных переменных. Следом идёт магия под названием alight depth to color эта функция ставит в соответсвие каждой точки из RGB картинки, точку на Depth Frame, то есть даёт нам возможность получать по координатам XY, значение Z.

Понято что необходимо произвести калибровку на вашем мониторе. Я специально не стал вытаскивать отдельно эти параметры, что бы читатель решивший запустить код сделал это сам, за одно резберётся в коде =)

Далее мы берём из всего предсказания только точки под номером 5 и 7 правой руки.
image

Осталось дело за малым полученные координаты фильтруем с помощью скользящего среднего. Можно было конечно применить более серьзные алгоритмы фильтрации, но взглянув на их визуализацию и подёргав разные рычажки, стало понятно, что для демо вполне хватит и скользящего среднего с глубиной 5 фреймов, хочу заметить что для XY вполне хватало и 2-3-х, но вот с Z дела обстоят хуже.
deque_l = 5x0_d = collections.deque(deque_l * [0.], deque_l)y0_d = collections.deque(deque_l * [0.], deque_l)x1_d = collections.deque(deque_l * [0.], deque_l)y1_d = collections.deque(deque_l * [0.], deque_l)z_val_f_d = collections.deque(deque_l * [0.], deque_l)z_val_s_d = collections.deque(deque_l * [0.], deque_l)m_xy_d = collections.deque(deque_l * [0.], deque_l)c_xy_d = collections.deque(deque_l * [0.], deque_l)x_d = collections.deque(deque_l * [0.], deque_l)y_d = collections.deque(deque_l * [0.], deque_l)z_d = collections.deque(deque_l * [0.], deque_l)def get_filtered_values(depth_image, xy0, xy1):    global x0_d, y0_d, x1_d, y1_d, m_xy_d, c_xy_d, z_val_f_d, z_val_s_d, x_d, y_d, z_d    x0_d.append(float(xy0[1]))    x0_f = round(mean(x0_d))    y0_d.append(float(xy0[0]))    y0_f = round(mean(y0_d))    x1_d.append(float(xy1[1]))    x1_f = round(mean(x1_d))    y1_d.append(float(xy1[0]))    y1_f = round(mean(y1_d))    z_val_f = get_area_mean_z_val(depth_image, x0_f, y0_f)    z_val_f_d.append(float(z_val_f))    z_val_f = mean(z_val_f_d)    z_val_s = get_area_mean_z_val(depth_image, x1_f, y1_f)    z_val_s_d.append(float(z_val_s))    z_val_s = mean(z_val_s_d)    points = [(y0_f, x0_f), (y1_f, x1_f)]    x_coords, y_coords = zip(*points)    A = np.vstack([x_coords, np.ones(len(x_coords))]).T    m, c = lstsq(A, y_coords)[0]    m_xy_d.append(float(m))    m_xy = mean(m_xy_d)    c_xy_d.append(float(c))    c_xy = mean(c_xy_d)    a0, a1, a2, a3 = equation_plane()    x, y, z = line_plane_intersection(y0_f, x0_f, z_val_s, y1_f, x1_f, z_val_f, a0, a1, a2, a3)    x_d.append(float(x))    x = round(mean(x_d))    y_d.append(float(y))    y = round(mean(y_d))    z_d.append(float(z))    z = round(mean(z_d))    return z_val_f, z_val_s, m_xy, c_xy, (y0_f, x0_f), (y1_f, x1_f), x, y, z


Создаем deque c длинной 5 фреймов и усредняем все подряд =) Дополнительно расчитываем y = mx+c, Ax+By+Cz+d=0, уравнение для прямой луч на RGB картинке и уравнение плоскости монитора, оно у нас получается y=0.

Итоги



Ну вот и всё, мы запилили простейший манипулятор, который даже при своем драматически простом исполнении уже сейчас может быть, хоть и с трудом, но использован в реальной жизни!

Благодарности



Спасибо сообществу ods.ai, без него невозможно развиваться!
Подробнее..

Перевод Новый высокопроизводительный одноплатный компьютер BeagleV построенный на открытой архитектуре RISC-V за 119

16.01.2021 02:07:06 | Автор: admin
BeagleV SBC

До недавнего времени платы построенные на процессоре RISC-V такие как Kendryte K210, невозможно было использовать для высоко ресурсоемких задач, т.к. производительность конечных была очень низкой. В частности одноплатный компьютер XuanTie C906 на базе Allwinner RISC-V можно было использовать только для нишевых небольших задач, например в качестве камеры из-за отсутствия аппаратного графического ускорителя и наличия только 256 МБ оперативной памяти. Можно использовать одноплатные компьютеры такие как SiFive HiFive Unmatched или PolarBerry, но стоимость конечной системы составит сотни, а то и более тысячи долларов, что является весьма дороговато.

Таким образом, очевидна необходимость в недорогом одноплатном компьютере на Linux, построенным на высокопроизводительном процессоре RISC-V. И есть отличная новость, фонд BeagleBoard.org, Seeed Studio и китайский фабричный производитель микросхем Starfive объединились для разработки и производства одноплатного компьютера BeagleV (Beagle Five) на базе двухъядерного процессора StarFive JH7100 (ядро SiFive U74 RISC-V) с цифровым сигнальным процессором(DSP) для компьютерного зрения, ускорителем машинного обучения (Deep learning) NVDLA и нейронной сети для искусственного интеллекта (AI).

Технические характеристики BeagleV:
  • Процессор: SoC StarFive JH7100 Vision SoC с:
    • двухядерным ядром RISC-V U74 с 2MB L2 cache @ 1.5 GHz
    • Vision DSP Tensilica-VP6 для компьютерного зрения
    • NVDLAEngine 1 core (configuration 2048 MACs @ 800MHz 3.5 TOPS)
    • Ускоритель нейронной сети (1024MACs @ 500MHz 1 TOPS)
    • VPU H.264/H.265 decoder up to 4Kp60, dual-stream decoding up to 2Kp30
    • JPEG encoder/decoder
    • Цифровым сигнальным процессором(DSP) для обработка аудио потока

  • Оперативная память: 4GB или 8GB LPDDR4
  • Пользовательская память: слот MicroSD
  • Видео:
    • 1x HDMI порт с поддержкой разрешения до 1080p30
    • 1x MIPI DSI интерфейс с поддержкой разрешения до 4Kp30
    • MIPI-CSI TX для вывода видео после обработки модуля искусственного интеллекта (AI)

  • Камера:Двухканнальный процессор обработки изображений (ISP) с поддержкой разрешения до 4K @ 30FPS,2 x MIPI-CSI Rx
  • Звук: аналоговый вывод (3.5 mm TRRS jack)
  • Связь: 1x Gigabit Ethernet, 2.4 GHz 802.11b/g/n WiFi 4, и Bluetooth 4.2
  • USB: 4 порта USB 3.0
  • Expansions Header: 40-контактный разъем совместимый с Raspberry Pi 3, включает GPIO с 28 x GPIO, I2C, I2S, SPI, UART
  • Безопасность: поддержка TRNG и OTP
  • Разное: кнопка питания и сброса
  • Питание: 5V/3A через порт USB Type-C
  • Размер: 9070 (приблизительно) мм.


BeagleV block diagram

Основываясь на сведениях о ядре SiFive U74, производительность должна быть эквивалентна ядру Cortex-A55. Cortex-A55 является приемником Cortex-A53 на котором построен процессор Allwinner A64. По заявлению компании ARM, ядро Cortex-A55 имеет на 18% большую производительность и на 15% большую энергоэффективность. Плата Banana Pi BPI-M64 содержит 4-ядра Cortex-A53 с частотой 1.2 GHz, а BeagleV будет содержать два ядра RISC-V U74 с частотой 1.5 GHz, что говорит о практически идентичной производительности.

Поэтому производительность одноплатного компьютера BeagleV будет не такой высокой по сравнению с платами на процессоре Arm, но за счет наличия модуля ускорителя нейронной сети и цифрового зрения, BeagleV будет конкурентоспособным по сравнению с другими платами с искусственными интеллектом, такими как Coral Dev Board mini.

Первая партия поставок плат запланированная на март будет без графического процессора, но следующая которая должна будет выпущена в сентябре будет с графическим процессором от Imagination Technologies GPU.

Поддержка BeagleV будет в основной ветки mainline Linux, образ ОС будет построен на версии Debian. Так будет поддержка Fedora и FreeRTOS. Одноплатные компьютеры на RISC-V являются открытым аппаратным обеспечением(open-source hardware) как и другие платы организации BeagleBoard.org, это означает что файлы дизайна оборудования(hardware design files), прошивки(firmware) и программное обеспечение будут общедоступными.

Конечная цена BeagleV составит $119 с 4GB RAM и $149 с 8GB RAM, но первая партия будет выпущена только с 8GB RAM. Энтузиасты и гики могут заполнить заявку на получение платы на сайте. Так же можно оформить предзаказ платы в Seeed Studio. Дополнительную информацию можно найти на странице продукта.

Ссылки:


Подробнее..

Перевод 12 платформ соревнований по Data Science и искусственному интеллекту для развития ваших навыков в 2021 году

12.01.2021 14:15:46 | Автор: admin
Data Science требует использования статистических методов и алгоритмов машинного обучения для работы с большим объёмом данных, и для того чтобы делать это эффективно, вам потребуется много практики. Отличная возможность попрактиковаться соревнования по Data Science. Они служат платформой для изучения лучших практик, получения отзывов и повышения квалификации. Это также отличный способ расширить возможности творить и границы в области науки о данных. Я делал подобный список и в прошлом году, поэтому подумал, что это будет хорошее время, чтобы обновить его на 2021 год.




Большинство конкурсов данных предполагают, что участники решают реальную бизнес-задачу, используя методы геймификации, чтобы найти лучшее решение в короткие сроки. Быть хорошим специалистом по Data Science означает обладать знаниями в предметной области, поэтому соревнования отличная возможность погрузиться в предметную область и понять, как использовать свои знания для решения бизнес-задач. Вот 10 соревнований по Data Science и искусственному интеллекту или платформ соревнований, на которых вы сможете вывести свои навыки на новый уровень в 2021 году. Если у вас есть предложения, что добавить в список, оставьте их в комментариях.

1. Numer.ai


Что это такое?
Numer.ai, согласно их веб-сайту, самый сложный турнир по науке о данных на планете, его поддерживает Union Square Ventures, соучредитель Renaissance и Coinbase. Основная идея турнира создать мировой открытый хедж-фонд путём моделирования фондового рынка. На веб-сайте подробно описаны эти шаги:

  1. Начните с качественных данных о хедж-фондах.
  2. Примените машинное обучение для прогнозирования поведения фондового рынка.
  3. Предоставьте свои прогнозы, чтобы контролировать капитал хедж-фонда Numer.ai.

Специалисты по Data Science, которые примут во всём этом участие, получат выплаты (в криптовалюте), исходя из производительности моделей. На данный момент исследователям данных выплачено в общей сложности 37 160 659 долларов США.

Как это работает: ставка делается, чтобы заработать. Вы либо зарабатываете, либо сжигаете процент от своих акций в зависимости от производительности вашей модели. Прочитать об этом больше можно на веб-сайте.

Когда
Турнир питает хедж-фонд в режиме реального времени, поэтому должен продолжаться до тех пор, пока работает хедж-фонд. Чтобы узнать больше о Numer.ai, читайте их блог здесь.

2. IASC Data Analysis Competition 2021


Что это такое?
IASC это ассоциация Международного статистического института (ISI), цель которой продвижение теории, методов и практики статистических вычислений, а также развитие интереса и знаний в области эффективных и действенных статистических вычислений (с веб-сайта). Тема: анализ данных, связанных с качеством жизни.

Ваша презентация должна чётко описывать значимость ваших выводов как для отдельных людей, так и для общества. Первичный набор данных может поступать из одной или нескольких баз данных, но объединение информации из разных баз данных может помочь получить интересные и оригинальные выводы. Соревнование открыто для всех желающих представить свой плакат на выставке WSC 2021. Над вашим PDF можно работать лично или в небольшой группе (до 5 участников).

Когда окончательные материалы (в формате PDF) должны быть представлены 30 апреля 2021 года.

3. TopCoder Open 2021


Что это такое?
Ежегодный TopCoder Open (ТСО), организованный TopCoder, это запредельный турнир по программированию и дизайну.

В общей сложности 6 конкурсных треков, где первые соревнования проходят онлайн, победители накапливают баллы TCO на протяжении всего соревнования, а те, кто наберёт наибольшее количество баллов, выигрывают поездку в финал TCO. TCO также проводит небольшие региональные мероприятия, поощряя участников со всего мира.
Всего есть шесть конкурсных треков:

  1. Алгоритмы.
  2. Разработка.
  3. Первый на финише.
  4. Марафон.
  5. Дизайн пользовательского интерфейса.
  6. Обеспечение качества.

Чтобы узнать больше, прочитайте обзор.

Когда старт дан 1 января 2021 года!

4. Challenge Data 2021


Что это такое?
Хозяева Challenge Data ENS и Collge de France. Организован сбор бизнес-задач Data Science с данными, предоставляемыми государственными службами, компаниями и лабораториями. Нужно решить следующие проблемы:

  • Данные.
  • Скоринг.
  • Тестовый набор.
  • Оценка.

А у участников есть публичные (представления рангов в открытом тестовом наборе) и закрытые таблицы лидеров (ранговые представления в закрытом тестовом наборе). Подробнее читайте в основной документации и часто задаваемых вопросах.

Когда соревнование началось 4 января 2021 года.

5. Kaggle Competitions


Что это такое?
Kaggle не нуждается в представлении благодаря распространённости в сообществе Data Science. Это самая популярная платформа для проведения соревнований по Data Science с очень хорошими призами. Соревнования Kaggle созданы, чтобы бросать вызов конкурентам на всех этапах их карьеры в области машинного обучения. Существует обширный ряд типов соревнований (из документации):

  1. Оборочные соревнования.
  2. Соревнования исследований.
  3. Песочница.
  4. Рекрутинговые соревнования.
  5. Годовые соревнования.
  6. Соревнования с ограниченным участием.

Последние соревнования

6. CodaLab


Что это такое?
CodaLab это платформа с открытым исходным кодом, которая даёт экосистему для проведения вычислительных исследований более эффективным, воспроизводимым и командным образом. В CodaLab есть два аспекта: рабочие листы и соревнования.

Рабочие листы позволяют воспроизводить сложные исследовательские программы и создавать исполняемые статьи. Используйте любой формат данных или язык программирования платформа отлично подходит для опытного пользователя! У рабочих листов Codalab есть открытые экземпляры, размещённые в Стэнфордском университете.

Сегодня соревнования объединяют всё сообщество в решении самых сложных проблем с данными и вычислениями. Вы можете выиграть призы, а также создать своё собственное соревнование.

Последние соревнования 2020 Seu Video Caption Competition.

7. Driven Data


Что это такое?
DrivenData привносит передовые методы в области Data Science и краудсорсинга в некоторые из самых больших социальных проблем, а также организации, берущие эти проблемы на себя. Существует множество онлайн-задач, обычно длящихся 2-3 месяца, где глобальное сообщество специалистов Data Science соревнуется за лучшую статистическую модель для решения сложных, значимых прогностических задач.
Согласно веб-сайту:

  1. Всё начинается с данных конкурентам даеётся набор данных как с зависимыми, так и с независимыми переменными.
  2. Второй набор данных данных только с независимыми переменными, а зависимые переменные, соответствующие этому набору, держатся в секрете.
  3. Вы строите модель.
  4. Представляете прогнозы для второго набора, сравниваете их с фактическими.
  5. Команда с лучшей производительностью объявляется победителем.

Последние соревнования MagNet: Model the Geomagnetic Field.

8. DataHack


Что это такое?
Последнее поле битвы Data Science, коллекция соревнований, чтобы сражаться, побеждать, практиковаться, учиться и создать своё портфолио Data Science! Хакатоны в области Data Science на DataHack позволяют конкурировать с ведущими исследователями данных и экспертами в области машинного обучения во всем мире. Это ваш шанс поработать над задачами науки на реальных данных, повысить мастерство, учиться у профессионалов в области информатики и машинного обучения, а также прорубить свой путь к вершине хакатона! У вас также есть шанс выиграть призы и устроиться на работу в компанию Data Science вашей мечты. Начните хакатон Data Science уже сегодня!

Последние соревнования Loan Prediction.

9. Machine Hack


Что это такое?
Онлайновая платформа для проведения соревнований по машинному обучению. Здесь найдутся самые сложные бизнес-проблемы, которые теперь можно решить с помощью Data Science и ML. Соревнуйтесь с сотнями специалистов на хакатоне, созданном индустрией.

Последние соревнования Buyers Time Prediction Challenge.

10. Zindi


Что это такое?
Zindi первая платформа для проведения соревнований в области Data Science в Африке. На Zindi расположена целая научная экосистема данных, в которую входят учёные, инженеры, академики, компании, НПО, правительства и институты, сосредоточенные на решении самых насущных проблем Африки.

Для специалистов, от новичков до рок-звёзд, Zindi, это место, где можно получить доступ к африканским наборам данных и решить проблемы Африки. Специалисты по работе с данными найдут на Zindi все необходимые инструменты, чтобы соревноваться, делиться идеями, оттачивать свои навыки, создавать свой профессиональный профиль, находить возможности карьерного роста и весело проводить время! Заметьте, что некоторые проблемы стоят только перед жителями Африки.

Последние соревнования Uber Nairobi Ambulance Perambulation Challenge.

11. AIcrowd


Что это такое?
AIcrowd это платформа для оптимизации рабочего процесса, связанного с искусственным интеллектом, как внутри компании, так и за её пределами путём выполнения задач, связанных с искусственным интеллектом, машинным обучением и другими задачами в области Data Science. Она помогает организациям будь то предприятия, университеты, правительственные учреждения или НПО разрабатывать, управлять и продвигать свои задачи. Она оптимизирует рабочий процесс машинного обучения и связывает ваши задачи со специалистами и энтузиастами в области машинного обучения и Data Science, которые совместно пытаются найти наиболее точные, эффективные и действенные решения.

Последние соревнования Motor insurance market simulation.

12. Iron Viz 2021


Что это такое?
IronViz крупнейший в мире конкурс по визуализации виртуальных данных зажигает и демонстрирует мощь сообщества Tableau, позволяя вам взаимодействовать с рок-звёздами данных по всему миру. Три финалиста (определяемые по результатам отборочного турнира IronViz в начале этого года) выйдут на Чемпионат IronViz в октябре 2021 года. Победитель зарабатывает не только право на хвастовство в течение года и место среди невероятного списка прошлых чемпионов, в 2020 году на кону было $ 10,000, но и пожертвование в размере $ 5,000 в пользу некоммерческой организации по выбору чемпионов.

Когда будет объявлено позже.

Для учащихся


Если вы студент или если вы преподаватель, поощряющий студентов изучать Data Science и AI, посмотрите на эти задачи.

  • Nittany AI Challenge предлагает командам студентов возможность побороться за награду в области Data Science, разрабатывая и представляя решения на основе искусственного интеллекта, чтобы сделать мир вокруг нас лучше с помощью четырёх столпов: образование, окружающая среда, здоровье и гуманизм.
  • Data Mining Cup вдохновляет отечественных и иностранных студентов на интеллектуальный анализ данных и предлагает им найти лучшее решение проблемы интеллектуального анализа данных, конкурируя с другими.
  • Imagine Cup Junior 2021 предоставляет учащимся в возрасте от 13 до 18 лет возможность узнать о технологиях и о том, как их можно использовать, чтобы изменить мир к лучшему. Глобальная задача сосредоточена на искусственном интеллекте (AI), знакомстве студентов с ИИ и инициативах [Microsoft AI for Good] (http://personeltest.ru/aways/www.microsoft.com/en-us/ai/ai-for-good), чтобы они могли придумать идеи для решения социальных, культурных и экологических проблем.

Идите в ногу с последними конкурсами с помощью этих ресурсов



Спасибо, что прочитали! Пожалуйста, оставьте в комментариях любые другие крутые соревнования, которых нет в списке. Берегите себя и счастливых вам хаков!

image



Подробнее..

Введение Соревнование от финансовой группы HOME CREDIT по определеню риска дефолта заемщика

13.01.2021 10:12:44 | Автор: admin

В статье рассматривается соревнование по машинному обучениюHomeCreditDefaultRisk, цель которого использовать исторические данные о заявках на получение кредита, чтобы предсказать, сможет ли заявитель погасить ссуду (определить риск дефолта заемщика). Прогнозирование того, вернет ли клиент ссуду или столкнется с трудностями, является критически важной бизнес-задачей, и HomeCredit проводит конкурс на платформе Kaggle, чтобы увидеть, какие модели машинного обучения, способные помочь им в решении этой задачи, может разработать сообщество.

Это стандартная задача классификации с учителем:

  • Обучение с учителем: верные ответы включаются в состав обучающих данных, и цель состоит в том, чтобы обучить модель предсказывать эти ответы на основе имеющихся признаков.

  • Задача классификации: для каждого набора признаков необходимо предсказать ответ, который представляет собой двоичную переменную со значениями 0 (заемщик выплатит кредит вовремя) или 1 (возникнут трудности с погашением кредита).

ДАННЕ

Данные предоставлены финансовой группой HomeCredit, предлагающей кредитные линии (ссуды) населению, не охваченному банковскими услугами. Всего имеется 7 различных источников данных:

  • applicationtrain / applicationtest: основные данные для обучения и тестирования с информацией о каждой кредитной заявке в HomeCredit. Каждая ссуда представлена отдельной строкой, в которой признакSKIDCURRявляется уникальным идентификатором. Данные обучающей выборки имеют меткуTARGETсо значениями:

    • 0, если ссуда была возвращена;

    • 1, если ссуда не была погашена.

  • bureau: данные о предыдущих кредитах клиента в других финансовых учреждениях. Каждый предыдущий кредит в этом файле представлен отдельной строкой, при этом для каждой записи в обучающей выборке может иметься несколько записей о предыдущих кредитах.

  • bureaubalance: ежемесячные данные о предыдущих кредитах. Каждая строка представляет собой данные за один месяц срока действия предыдущего кредита. При этом каждый предыдущий кредит может иметь несколько строк, по одной на каждый месяц продолжительности кредита.

  • previousapplication: предыдущие заявки на получение ссуд в HomeCredit для клиентов, данные которых имеются в обучающей выборке. Каждая текущая ссуда в обучающей выборке может иметь несколько предыдущих ссуд, каждая из которых представлена в файле одной строкой и идентифицируется признакомSKIDPREV.

  • POSCASHBALANCE: исторические ежемесячные данные о покупках и выдаче наличных денег для клиентов, обслуживающихся в HomeCredit. Каждая строка представляет собой данные за один месяц, при этом каждая предыдущая ссуда может иметь несколько строк в данном файле.

  • creditcardbalance: ежемесячные данные о предыдущих кредитных картах, которые клиенты получили в HomeCredit. Каждая строка представляет собой информацию о балансе кредитной карты за один месяц. При этом одна кредитная карта может быть представлена несколькими строками.

  • installments_payment: история платежей по предыдущим кредитам в HomeCredit, содержит по одной строке для каждого совершенного и пропущенного платежа.

Эта диаграмма показывает, как данные связаны между собой:

Кроме того, в рамках соревнования предоставлены описания всех признаков (в файлеHomeCredit_columns_description.csv) и пример результирующего файла с предсказанными ответами.

В рамках данной статьи я буду использовать только основные данные для обучения и тестирования (application_train / application_test), который должны быть более понятными. Это позволит установить базовый уровень, который можно постепенно улучшать. В подобных соревнованиях лучше постепенно осмысливать проблему, чем сразу полностью погрузиться в нее и запутаться! Однако если вы хотите иметь хоть какую-то надежду на серьезный результат, в дальнейшем нужно будет использовать все данные.

МЕТРИКА: ROC AUC

Как только вы разберетесь с данными (в этом очень помогает прочтение описания признаков), следующим шагом должно стать понимание метрике, по которой оценивается работа. В данном случае это общепринятая метрика классификации,известная как площадь под кривой ошибок (ROC AUC, также иногда называемая AUROC).

Метрика ROC AUC может показаться пугающей, но она относительно проста, если разобраться с двумя отдельными концепциями.

Кривая ошибок (ROC)отображает соотношение между долей объектов от общего количества носителей признака, верно классифицированных как несущие признак, и долей объектов от общего количества объектов, не несущих признака, ошибочно классифицированных как несущие признак:

Отдельные линии на графике обозначают кривую для одной модели, а движение вдоль линии указывает на изменение порога, используемого для классификации положительных экземпляров. Порог начинается с 0 в правом верхнем углу и переходит в 1 в левом нижнем углу. Кривая, которая находится левее и выше других, указывает на лучшую модель. Например, модель, представленная синей линией, лучше красной, которая, в свою очередь, лучше черной (эта диагональная линия указывает на наивную модель случайного угадывания).

Площадь под кривой (AUC)уже содержит объяснение в своем названии. Это просто область под кривой ROC (интеграл кривой). Этот показатель находится в диапазоне от 0 до 1, при этом лучшая модель получает более высокий балл. Модель, которая просто угадывает результат случайным образом, будет иметь ROC AUC = 0,5.

Когда мы оцениваем классификатор в соответствии с метрикой ROCAUC, мы генерируем не точные прогнозы 0 или 1, а скорее вероятность от 0 до 1. Это может сбивать с толку, потому что обычно мы предпочитаем думать с точки зрения точности, однако, когда мы сталкиваемся с проблемой несбалансированных классов (далее мы увидим, что это так) точность не лучшая метрика. Например, если бы я хотел построить модель, которая могла бы обнаруживать террористов с точностью 99,9999%, я бы просто сделал модель, предсказывающую, что каждый человек не является террористом. Ясно, что это будет неэффективно (полнота будет равна нулю), поэтому мы будем использовать более сложные показатели, такие как ROCAUC или оценка F1, чтобы более точно отразить эффективность классификатора. Модель с высоким значением ROCAUC также будет иметь высокую точность, но кроме этого ROCAUC лучше отражает и другие характеристики модели.

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

ИМПОРТ

Я буду использовать типичный стек библиотек для работы с данными:numpyиpandasдля манипулирования данными,sklearnpreprocessingдля работы с категориальными переменными,matplotlibиC11CC12CC13Cдля построения графиков и диаграмм. Также импортирую дополнительные модули для упрощения работы.

import osimport numpy as npimport pandas as pdpd.set_option('display.max_columns', None)from sklearn.preprocessing import LabelEncoderimport matplotlib.pyplot as pltimport seaborn as sns# Подавление предупрежденийimport warningswarnings.filterwarnings('ignore')

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

# Список доступных файловprint(os.listdir("../input/"))

POSCASHbalance.csv, bureaubalance.csv, applicationtrain.csv, previousapplication.csv, installmentspayments.csv, creditcardbalance.csv, samplesubmission.csv, applicationtest.csv, bureau.csv]

# Тренировочные данныеapp_train = pd.read_csv('../input/application_train.csv')print('Training data shape: ', app_train.shape)app_train.head()

Training data shape: (307511, 122)

Файл с тренировочными данными содержит 307511 строк, для каждой из которых имеется 120 признаков, а также столбец с идентификатором ссуды и столбец с целевой меткой, которую мы хотим предсказать.

# Тестовые данныеapp_test = pd.read_csv('../input/application_test.csv')print('Testing data shape: ', app_test.shape)app_test.head()

Testing data shape: (48744, 121)

Тестовых данных значительно меньше, и в них отсутствует столбец TARGET.

ИССЛЕДОВАТЕЛЬСКИЙ АНАЛИЗ ДАННХ (EXPLORATORY DATA ANALYSIS EDA)

Исследовательский анализ данных (EDA) это открытый процесс, в ходе которого необходимо рассчитывать статистики и строить графики, чтобы найти тенденции, аномалии, закономерности или взаимосвязи в данных. Цель EDA узнать, что могут нам сказать наши данные. Обычно он начинается с общего обзора, а затем сужается к конкретным областям, когда мы находим интересные области данных. Результаты могут быть интересны сами по себе, или их можно использовать для принятия решения о выборе модели, например, помогая нам решить, какие признаки использовать.

ИЗУЧИМ РАСПРЕДЕЛЕНИЕ ЦЕЛЕВОЙ МЕТКИ

Цель это то, что нас просят предсказать: либо 0, если ссуда была выплачена вовремя, либо 1, если у клиента возникли трудности с оплатой. Сначала мы можем изучить количество кредитов, попадающих в каждую категорию.

app_train['TARGET'].value_counts()
app_train['TARGET'].astype(int).plot.hist();

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

ПРОВЕРИМ ПРОПУЩЕННЕ ЗНАЧЕНИЯ

Теперь мы можем посмотреть количество и процент пропущенных значений в каждом столбце.

# Функция для расчета пропущенных значений в столбцах датафреймаdef missing_values_table(df):        # Общее количество пропущенных значений        mis_val = df.isnull().sum()        # Доля пропущенных значений        mis_val_percent = 100 * df.isnull().sum() / len(df)        # Таблица с результатом расчета        mis_val_table = pd.concat([mis_val, mis_val_percent], axis=1)        # Переименовываем столбцы        mis_val_table_ren_columns = mis_val_table.rename(        columns = {0 : 'Missing Values', 1 : '% of Total Values'})        # Сортируем по столбцу с долей пропущенных значений в порядке убывания        mis_val_table_ren_columns = mis_val_table_ren_columns[            mis_val_table_ren_columns.iloc[:,1] != 0].sort_values(        '% of Total Values', ascending=False).round(1)        # Вывод сводной информации        print("Your selected dataframe has " + str(df.shape[1]) + " columns.\n"                  "There are " + str(mis_val_table_ren_columns.shape[0]) +              " columns that have missing values.")        return mis_val_table_ren_columns# Статистика пропущенных значенийmissing_values = missing_values_table(app_train)missing_values.head(10)

Your selected dataframe has 122 columns.

There are 67 columns that have missing values.

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

ТИП ДАННХ ПРИЗНАКОВ

Настало время посмотреть на количество признаков каждого типа данных.int64иfloat64 числовые переменные (которые могут быть дискретными или непрерывными). Признаки с типом данныхobjectсодержат строки и являютсякатегориальными признаками.

# Количество признаков каждого типа данныхapp_train.dtypes.value_counts()

Теперь узнаем количество уникальных записей в каждом из столбцов типаobject(категориальных).

# Количество уникальных значений в каждом из категориальных признаковapp_train.select_dtypes('object').apply(pd.Series.nunique, axis = 0)

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

КОДИРОВАНИЕ КАТЕГОРИАЛЬНХ ПЕРЕМЕННХ

Прежде чем продолжить, необходимо разобраться с категориальными переменными. К сожалению, модели машинного обучения не могут работать с категориальными переменными (за исключением некоторых моделей, таких какLightGBM). Следовательно, мы должны найти способ закодировать (представить) эти переменные в виде чисел, прежде чем передавать их модели. Есть два основных способа осуществить этот процесс:

Кодирование метки (Label encoding): назначаем каждой уникальной категории в категориальной переменной целое число. Новые столбцы не создаются. Пример показан ниже:

Однопроходное кодирование (One-hot encoding): создаем новый столбец для каждой уникальной категории в категориальной переменной. Каждое наблюдение получает 1 в столбце для соответствующей категории и 0 во всех других новых столбцах.

Проблема с кодированием меток состоит в том, что оно дает категориям произвольный порядок. Значение, присвоенное каждой из категорий, является случайным и не отражает каких-либо неотъемлемых аспектов категории. В приведенном выше примере программист получает метку 4, а специалист по данным 1, но если бы мы повторили тот же процесс снова, метки могли бы быть перевернутыми или совершенно другими. Фактическое присвоение целых чисел произвольно. Следовательно, когда мы выполняем кодирование меток, модель может использовать относительное значение функции (например, программист = 4 и специалист по данным = 1) для присвоения весов, а это совсем не то, чего мы хотим. Если у нас есть только два уникальных значения для категориальной переменной (например, мужчина / женщина), тогда кодирование метки подойдет, но для более чем двух уникальных категорий безопасным вариантом является однопроходное кодирование.

Об относительных достоинствах этих подходов ведутся споры, и некоторые модели могут без проблем работать с категориальными переменными, закодированными метками. Один из участников рассматриваемого мной соревнования Kaggle-masterWill Koehrsen, считает, что для категориальных переменных с множеством классов наиболее безопасным подходом является однопроходное кодирование, поскольку оно не навязывает произвольные значения категориям. И в этом с ним согласны многие специалисты в области машинного обучения и анализа данных. Единственным недостатком этого метода является то, что количество признаков (измерений данных) может увеличиваться из-за категориальных переменных со многими категориями. Чтобы справиться с этим, можно выполнить однопроходное кодирование с последующим применением методаPCAили другимиметодами уменьшения размерности, чтобы уменьшить количество измерений (при этом, все еще пытаясь сохранить полезную информацию).

В своем примере я буду использовать LabelEncoding для любых категориальных переменных только с 2 категориями и One-HotEncoding для любых категориальных переменных с более чем 2 категориями. Возможно, потребуется изменить этот подход по мере того, как мы углубимся в проект, но пока можно посмотреть, к чему это приведет. Я также не буду использовать какие-либо методы уменьшение размерности.

LABEL ENCODING И ONE-HOT ENCODING

Реализуем описанные выше методы: для любой категориальной переменной (dtype == object) с двумя уникальными категориями я буду использовать кодирование меток, а для любой категориальной переменной с более чем двумя уникальными категориями однопроходное кодирование.

Для кодирования меток воспользуюсь методом LabelEncoder из библиотеки Scikit-Learn, а для однопроходного кодирования функцией pandas get_dummies(df).

# Создаем объект label encoderle = LabelEncoder()le_count = 0# Проходим по всем столбцамfor col in app_train:    if app_train[col].dtype == 'object':        # Если признак имеет 2 или менее уникальных значения        if len(list(app_train[col].unique())) <= 2:            # Обучаем LabelEncoder на тренировочных данных            le.fit(app_train[col])            # Трансформируем обучающий и тестовый датафреймы            app_train[col] = le.transform(app_train[col])            app_test[col] = le.transform(app_test[col])                        # Подсчитываем, сколько признаков обработано методом LabelEncoder            le_count += 1print('%d columns were label encoded.' % le_count)

3 columns were label encoded.

# Применяем one-hot encoding к категориальным признакамapp_train = pd.get_dummies(app_train)app_test = pd.get_dummies(app_test)print('Training Features shape: ', app_train.shape)print('Testing Features shape: ', app_test.shape)

raining Features shape: (307511, 243)

Testing Features shape: (48744, 239).

ВРАВНИВАНИЕ ДАННХ ОБУЧЕНИЯ И ТЕСТИРОВАНИЯ

В данных для обучения и тестирования должны быть одни и те же признаки (столбцы). Однопроходное кодирование создало больше столбцов в обучающих данных, потому что в них имелись категориальные переменные с категориями, не представленными в данных тестирования. В связи с этим необходимо выровнять фреймы данных. Сначала извлекаем целевой столбец из данных обучения (потому что его нет в данных тестирования, но нужно сохранить эту информацию). При выполнении выравнивание необходимо убедиться, что установлен параметр axis = 1, чтобы выровнять фреймы данных на основе столбцов, а не строк!

train_labels = app_train['TARGET']# Выравниваем тренировочный и тестовый датафреймы, сохраняем только признаки, имеющиеся в обеих таблицахapp_train, app_test = app_train.align(app_test, join = 'inner', axis = 1)# Возвращаем метку с ответами обратноapp_train['TARGET'] = train_labelsprint('Training Features shape: ', app_train.shape)print('Testing Features shape: ', app_test.shape)

Training Features shape: (307511, 240)

Testing Features shape: (48744, 239)

Теперь наборы данных для обучения и тестирования имеют одинаковые признаки, которые требуются для машинного обучения. Количество признаков значительно выросло за счет однопроходного кодирования. В какой-то момент вы, вероятно, захотите попробоватьуменьшить размерность (удалить признаки, которые не имеют отношения к делу), чтобы уменьшить размер наборов данных.

ВОЗВРАЩАЕМСЯ К ИССЛЕДОВАТЕЛЬСКОМУ АНАЛИЗУ ДАННХ АНОМАЛИИ

Одна из проблем, на которую вы всегда должны обращать внимание при выполнении EDA, это аномалии в данных. Они могут возникнуть из-за неправильно набранных чисел, ошибок в измерительном оборудовании или они могут являться достоверными, но экстремальными измерениями. Одним из способов поиска аномалий является просмотр статистики столбца с помощью методаdescribe. Числа в столбце DAYS_BIRTH отрицательны, поскольку они записаны относительно текущей заявки на получение кредита. Чтобы увидеть эту статистику в годах, мы можем умножить столбец на -1 и разделить на количество дней в году:

(app_train['DAYS_BIRTH'] / -365).describe()

Данные о возрасте выглядят разумно нет никаких выбросов значений. Теперь рассмотрим рабочие дни.

app_train['DAYS_EMPLOYED'].describe()

Это выглядит неправильно максимальное значение (кроме того, что оно положительное) около 1000 лет!

app_train['DAYS_EMPLOYED'].plot.hist(title = 'Days Employment Histogram');plt.xlabel('Days Employment');

Давайте рассмотрим аномальных клиентов и посмотрим, имеют ли они отличия в показателях дефолта от остальных клиентов.

anom = app_train[app_train['DAYS_EMPLOYED'] == 365243]non_anom = app_train[app_train['DAYS_EMPLOYED'] != 365243]print('The non-anomalies default on %0.2f%% of loans' % (100 * non_anom['TARGET'].mean()))print('The anomalies default on %0.2f%% of loans' % (100 * anom['TARGET'].mean()))print('There are %d anomalous days of employment' % len(anom))

The non-anomalies default on 8.66% of loans

The anomalies default on 5.40% of loans

There are 55374 anomalous days of employment

Это выглядит очень интересно оказывается, аномалии имеют меньшую частоту дефолта.

Обработка аномалий зависит от конкретной ситуации и не имеет установленных правил. Один из самых безопасных подходов просто установить для аномальных значений пропуски, а затем заполнить их перед машинным обучением. В этом случае, поскольку все аномалии имеют одинаковое значение, можно заполнить их одинаковым значением на случай, если все эти ссуды имеют что-то общее. Возможно, аномальные значения имеют некоторую важность, поэтому желательно сообщить модели машинного обучения, действительно ли мы сами заполнили эти значения. В качестве решения заполним аномальные значения не числом (np.nan), а затем создадим новый логический столбец, указывающий, было ли значение аномальным.

# Создаем признак, показывающий наличие аномального значенияapp_train['DAYS_EMPLOYED_ANOM'] = app_train["DAYS_EMPLOYED"] == 365243# Заполняем аномальные значения значением nanapp_train['DAYS_EMPLOYED'].replace({365243: np.nan}, inplace = True)app_train['DAYS_EMPLOYED'].plot.hist(title = 'Days Employment Histogram');plt.xlabel('Days Employment');

Теперь распределение выглядит гораздо более соответствующим тому, что можно было ожидать. Также создан новый столбец, чтобы сообщить модели, что эти значения изначально были аномальными (поскольку нужно будет заполнитьnansнекоторым значением, возможно, медианным значением столбца). Другие столбцы с DAYS во фрейме данных выглядят примерно так, как и ожидается, без явных выбросов.

Чрезвычайно важное замечание: все, что мы делаем с данными обучения, необходимо делать и с данными тестирования. Обязательно создайте новый столбец и заполните аномальные значенияnp.nanв данных тестирования.

app_test['DAYS_EMPLOYED_ANOM'] = app_test["DAYS_EMPLOYED"] == 365243app_test["DAYS_EMPLOYED"].replace({365243: np.nan}, inplace = True)print('There are %d anomalies in the test data out of %d entries' % (app_test["DAYS_EMPLOYED_ANOM"].sum(), len(app_test)))

There are 9274 anomalies in the test data out of 48744 entries

КОРРЕЛЯЦИИ

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

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

  • .000.19 очень слабый

  • .20-.39 слабый

  • .400.59 умеренный

  • 0,600,79 сильный

  • 0,801,0 очень сильный

# Найдем корреляцию признаков с целевой меткой и отсортируем результатcorrelations = app_train.corr()['TARGET'].sort_values()# Отобразим результат вычисленийprint('Most Positive Correlations:\n', correlations.tail(15))print('\nMost Negative Correlations:\n', correlations.head(15))

Посмотрим на некоторые из наиболее значимых корреляций: у признака DAYSBIRTH самая положительная корреляция (кроме TARGET, потому что корреляция переменной с самой собой всегда равна 1). Если посмотреть в описании, DAYSBIRTH это возраст клиента в отрицательных днях на момент выдачи кредита. Корреляция положительная, но значение этого признака на самом деле отрицательное, что означает, что по мере того, как клиент становится старше, он с меньшей вероятностью не выплатит свой кредит (т.е. цель == 0). Это немного сбивает с толку, поэтому стоит взять абсолютное значение признака, чтобы корреляция стала отрицательной.

app_train['DAYS_BIRTH'] = abs(app_train['DAYS_BIRTH'])app_train['DAYS_BIRTH'].corr(app_train['TARGET'])

-0.07823930830982694

По мере того, как клиент становится старше, возникает отрицательная линейная связь с целью, что означает, что по мере взросления клиенты склонны чаще возвращать свои ссуды вовремя.

Стоит подробнее проанализировать эту переменную. Во-первых, можно построить гистограмму возраста. Чтобы сделать график более понятным, проведем осьxчерез годы.

plt.style.use('fivethirtyeight')# Построим распределение по возрасту в годахplt.hist(app_train['DAYS_BIRTH'] / 365, edgecolor = 'k', bins = 25)plt.title('Age of Client'); plt.xlabel('Age (years)'); plt.ylabel('Count');

Само по себе возрастное распределение не говорит нам ничего, кроме того, что в нем отсутствуют явные выбросы, поскольку все возрасты выглядят разумно. Чтобы визуализировать влияние возраста на цель, мы построим графикоценки плотности ядра(KDE), раскрашенный значением цели.График оценки плотности ядра показывает распределение одной переменнойи может рассматриваться как сглаженная гистограмма (она создается путем вычисления ядра, обычно гауссовского, в каждой точке данных, а затем усреднения всех отдельных ядер для получения единой сглаженной кривой). Для этого графика мы будем использовать seaborn kdeplot.

plt.figure(figsize = (10, 8))sns.kdeplot(app_train.loc[app_train['TARGET'] == 0, 'DAYS_BIRTH'] / 365, label = 'target == 0')sns.kdeplot(app_train.loc[app_train['TARGET'] == 1, 'DAYS_BIRTH'] / 365, label = 'target == 1')plt.xlabel('Age (years)'); plt.ylabel('Density'); plt.title('Distribution of Ages');

Кривая target == 1 смещена в сторону младшего конца диапазона. Хотя это несущественная корреляция (коэффициент корреляции -0,07), эта переменная, вероятно, будет полезна в модели машинного обучения, поскольку она действительно влияет на цель. Давайте посмотрим на эту взаимосвязь с другой стороны: средняя непогашенная задолженность по возрастным группам.

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

age_data = app_train[['TARGET', 'DAYS_BIRTH']]age_data['YEARS_BIRTH'] = age_data['DAYS_BIRTH'] / 365age_data['YEARS_BINNED'] = pd.cut(age_data['YEARS_BIRTH'], bins = np.linspace(20, 70, num = 11))age_data.head(10)
# Группируем по ячейкам и считаем среднее значениеage_groups  = age_data.groupby('YEARS_BINNED').mean()age_groups
plt.figure(figsize = (8, 8))plt.bar(age_groups.index.astype(str), 100 * age_groups['TARGET'])plt.xticks(rotation = 75); plt.xlabel('Age Group (years)'); plt.ylabel('Failure to Repay (%)')plt.title('Failure to Repay by Age Group');

Прослеживается четкая тенденция: молодые заемщики чаще не возвращают кредит. Уровень невозврата составляет более 10% для трех младших возрастных групп и ниже 5% для самой старшей возрастной группы.

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

ВНЕШНИЕ ИСТОЧНИКИ

Три переменных с наиболее сильной отрицательной корреляцией с целью:EXTSOURCE1,EXTSOURCE2иEXTSOURCE3. Согласно документации, эти признаки представляют собой нормализованную оценку из внешнего источника данных. У меня нет полной уверенности, что именно это означает, но, возможно, это совокупный кредитный рейтинг, рассчитанный с использованием многочисленных источников данных.

Давайте посмотрим на эти переменные.

Во-первых, мы можем показать корреляции функций EXT_SOURCE с целью и друг с другом.

ext_data = app_train[['TARGET', 'EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3', 'DAYS_BIRTH']]ext_data_corrs = ext_data.corr()ext_data_corrs
plt.figure(figsize = (8, 6))# Тепловая карта корреляцийsns.heatmap(ext_data_corrs, cmap = plt.cm.RdYlBu_r, vmin = -0.25, annot = True, vmax = 0.6)plt.title('Correlation Heatmap');

Все три признака EXT_SOURCE имеют отрицательную корреляцию с целью, что указывает на то, что по мере увеличения значения EXT_SOURCE клиент с большей вероятностью погасит ссуду. Мы также можем видеть, что DAYS_BIRTH положительно коррелирует с EXT_SOURCE_1, указывая на то, что, возможно, одним из факторов в этой оценке является возраст клиента.

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

plt.figure(figsize = (10, 12))for i, source in enumerate(['EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3']):    plt.subplot(3, 1, i + 1)    sns.kdeplot(app_train.loc[app_train['TARGET'] == 0, source], label = 'target == 0')    sns.kdeplot(app_train.loc[app_train['TARGET'] == 1, source], label = 'target == 1')    plt.title('Distribution of %s by Target Value' % source)    plt.xlabel('%s' % source); plt.ylabel('Density');plt.tight_layout(h_pad = 2.5)

EXT_SOURCE_3 отображает наибольшую разницу между значениями цели. Можно ясно видеть, что этот признак имеет некоторое отношение к вероятности возврата кредита заемщиком. Связь не очень сильная (на самом деле все они считаются очень слабыми), но эти признаки все равно будут полезны для модели машинного обучения, чтобы предсказать, вернет ли кандидат ссуду вовремя.

ПАРНЙ ГРАФИК

В качестве дополнительного исследовательского сюжета мы можем построить парный график признаков EXTSOURCE и признака DAYSBIRTH. График пар отличный инструмент для исследования, потому что он позволяет видеть отношения между несколькими парами признаков, а также распределения отдельных признаков. Здесь мы используем библиотеку визуализации seaborn и функцию PairGrid, чтобы создать график пар с диаграммами рассеяния в верхнем треугольнике, гистограммами на диагонали и графиками плотности ядра 2D и коэффициентами корреляции в нижнем треугольнике.

plot_data = ext_data.drop(columns = ['DAYS_BIRTH']).copy()plot_data['YEARS_BIRTH'] = age_data['YEARS_BIRTH']plot_data = plot_data.dropna().loc[:100000, :]# Функция для расчета коэффициента корреляции между двумя признакамиdef corr_func(x, y, **kwargs):    r = np.corrcoef(x, y)[0][1]    ax = plt.gca()    ax.annotate("r = {:.2f}".format(r),                xy=(.2, .8), xycoords=ax.transAxes,                size = 20)# Создаем объект парного графикаgrid = sns.PairGrid(data = plot_data, size = 3, diag_sharey=False,                    hue = 'TARGET',                     vars = [x for x in list(plot_data.columns) if x != 'TARGET'])grid.map_upper(plt.scatter, alpha = 0.2)grid.map_diag(sns.kdeplot)grid.map_lower(sns.kdeplot, cmap = plt.cm.OrRd_r);plt.suptitle('Ext Source and Age Features Pairs Plot', size = 32, y = 1.05);

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

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

При подготовке статьи были использованы материалы из открытых источников:источник_1,источник_2.

Подробнее..

Соревнование KAGGLE по определению риска дефолта заемщика. Разработка признаков

14.01.2021 08:18:31 | Автор: admin

Введение: Соревнование от финансовой группы HOME CREDIT по определению риска дефолта заемщика

Соревнования Kaggle с использованием структурированных данных очень часто выигрывают специалисты по разработке признаков: побеждают те, кто может создавать наиболее полезные признаки из данных. Это представляет собой одну из закономерностей в машинном обучении: разработка признаков дает больший возврат инвестиций, чем построение модели и настройка гиперпараметров. Как говорит один из ведущих ученых в области машинного обучения Эндрю н: Прикладное машинное обучение это в основном разработка признаков.

Хотя выбор правильной модели и ее оптимальная настройка, несомненно, очень важны, модель может учиться только на основе предоставленных данных. Обеспечение максимального соответствия этих данных задаче задача специалиста по данным. Конструирование признаков может включать, как создание признаков: добавление новых признаков из существующих данных, так и выбор признаков: выбор только наиболее важных признаков или другие методы уменьшения размерности. Есть много методов, которые мы можем использовать как для создания, так и для выбора признаков.

В рамках этой статьи я рассмотрю два простых метода построения признаков:

  • Полиномиальные признаки;

  • Признаки, основанные на понимании предметной области.

ПОЛИНОМИАЛЬНЕ ПРИЗНАКИ

Одним из простых методов построения признаков является созданиеполиномиальных признаков. В этом методе создаются признаки, которые являются степенями существующих признаков, а также определенными взаимодействиями между существующими признаками. Например, мы можем создать переменные EXTSOURCE1 ^ 2 и EXTSOURCE2 ^ 2, а также такие переменные, как EXTSOURCE1 * EXTSOURCE2, EXTSOURCE1 * EXTSOURCE2 ^ 2, EXTSOURCE1 ^ 2 x EXTSOURCE2 ^ 2 и так далее. Эти признаки, представляющие собой комбинацию нескольких отдельных переменных, называются условиями взаимодействия, поскольку они фиксируют взаимодействия между переменными. Другими словами, хотя две переменные сами по себе могут не иметь сильного влияния на цель, объединение их вместе в одну взаимодействующую переменную может показать связь с целью. Термины взаимодействия обычно используются в статистических моделях, чтобы уловить влияние нескольких переменных, но в машинном обучении используются не очень часто. Тем не менее, мы можем попробовать несколько вариантов, чтобы увидеть, могут ли они помочь нашей модели предсказать, вернет ли клиент ссуду или нет.

Следующим программным кодом я создам полиномиальные признаки, используя переменные EXTSOURCE и переменную DAYSBIRTH.В Scikit-Learn есть полезный классPolynomialFeatures, который создает полиномы и условия взаимодействия до определенной степени. Достаточно использовать степень 3, чтобы увидеть результат (при создании полиномиальных признаков, нежелательно использовать слишком высокую степень, потому что количество признаков экспоненциально масштабируется со степенью, а также можно столкнуться спроблемами переобучения).

poly_features = app_train[['EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3', 'DAYS_BIRTH', 'TARGET']]poly_features_test = app_test[['EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3', 'DAYS_BIRTH']]from sklearn.preprocessing import Imputerimputer = Imputer(strategy = 'median')poly_target = poly_features['TARGET']poly_features = poly_features.drop(columns = ['TARGET'])poly_features = imputer.fit_transform(poly_features)poly_features_test = imputer.transform(poly_features_test)from sklearn.preprocessing import PolynomialFeatures# Создаем объект PolynomialFeatures, указав степень взаимодействия, равную 3poly_transformer = PolynomialFeatures(degree = 3)poly_transformer.fit(poly_features)poly_features = poly_transformer.transform(poly_features)poly_features_test = poly_transformer.transform(poly_features_test)print('Polynomial Features shape: ', poly_features.shape)

Polynomial Features shape: (307511, 35)

Чтобы получить названия новых признаков, нужно использовать методget_feature_names.

poly_transformer.get_feature_names(input_features = ['EXT_SOURCE_1', 'EXT_SOURCE_2', 'EXT_SOURCE_3', 'DAYS_BIRTH'])[:15]

Теперь у нас имеется 35 признаков новых: индивидуальные признаки; признаки, повышенные до степени 3; взаимодействующие признаки. Можно посмотреть, коррелируют ли какие-либо из этих новых признаков с целевой меткой.

poly_features = pd.DataFrame(poly_features,                              columns = poly_transformer.get_feature_names(['EXT_SOURCE_1', 'EXT_SOURCE_2',                                                                            'EXT_SOURCE_3', 'DAYS_BIRTH']))poly_features['TARGET'] = poly_target# Найдем корреляцию с целевой меткойpoly_corrs = poly_features.corr()['TARGET'].sort_values()print(poly_corrs.head(10))print(poly_corrs.tail(5))

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

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

poly_features_test = pd.DataFrame(poly_features_test,                                   columns = poly_transformer.get_feature_names(['EXT_SOURCE_1', 'EXT_SOURCE_2',                                                                                 'EXT_SOURCE_3', 'DAYS_BIRTH']))poly_features['SK_ID_CURR'] = app_train['SK_ID_CURR']app_train_poly = app_train.merge(poly_features, on = 'SK_ID_CURR', how = 'left')poly_features_test['SK_ID_CURR'] = app_test['SK_ID_CURR']app_test_poly = app_test.merge(poly_features_test, on = 'SK_ID_CURR', how = 'left')app_train_poly, app_test_poly = app_train_poly.align(app_test_poly, join = 'inner', axis = 1)print('Training data with polynomial features shape: ', app_train_poly.shape)print('Testing data with polynomial features shape:  ', app_test_poly.shape)

Training data with polynomial features shape: (307511, 275)

Testing data with polynomial features shape: (48744, 275)

ПРИЗНАКИ, ОСНОВАННЕ НА ПОНИМАНИИ ПРЕДМЕТНОЙ ОБЛАСТИ

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

  • CREDITINCOMEPERCENT: процент суммы кредита относительно дохода клиента;

  • ANNUITYINCOMEPERCENT: процент аннуитетного платежа кредита относительно дохода клиента;

  • CREDITTERM: продолжительность платежа в месяцах;

  • DAYSEMPLOYED_PERCENT: процент отработанных дней по отношению к возрасту клиента.

app_train_domain = app_train.copy()app_test_domain = app_test.copy()app_train_domain['CREDIT_INCOME_PERCENT'] = app_train_domain['AMT_CREDIT'] / app_train_domain['AMT_INCOME_TOTAL']app_train_domain['ANNUITY_INCOME_PERCENT'] = app_train_domain['AMT_ANNUITY'] / app_train_domain['AMT_INCOME_TOTAL']app_train_domain['CREDIT_TERM'] = app_train_domain['AMT_ANNUITY'] / app_train_domain['AMT_CREDIT']app_train_domain['DAYS_EMPLOYED_PERCENT'] = app_train_domain['DAYS_EMPLOYED'] / app_train_domain['DAYS_BIRTH']app_test_domain['CREDIT_INCOME_PERCENT'] = app_test_domain['AMT_CREDIT'] / app_test_domain['AMT_INCOME_TOTAL']app_test_domain['ANNUITY_INCOME_PERCENT'] = app_test_domain['AMT_ANNUITY'] / app_test_domain['AMT_INCOME_TOTAL']app_test_domain['CREDIT_TERM'] = app_test_domain['AMT_ANNUITY'] / app_test_domain['AMT_CREDIT']app_test_domain['DAYS_EMPLOYED_PERCENT'] = app_test_domain['DAYS_EMPLOYED'] / app_test_domain['DAYS_BIRTH']

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

plt.figure(figsize = (12, 20))for i, feature in enumerate(['CREDIT_INCOME_PERCENT', 'ANNUITY_INCOME_PERCENT', 'CREDIT_TERM', 'DAYS_EMPLOYED_PERCENT']):    plt.subplot(4, 1, i + 1)    sns.kdeplot(app_train_domain.loc[app_train_domain['TARGET'] == 0, feature], label = 'target == 0')    sns.kdeplot(app_train_domain.loc[app_train_domain['TARGET'] == 1, feature], label = 'target == 1')    plt.title('Distribution of %s by Target Value' % feature)    plt.xlabel('%s' % feature); plt.ylabel('Density');plt.tight_layout(h_pad = 2.5)

Сложно сказать заранее, будут ли полезны эти новые признаки. Единственный способ узнать наверняка попробовать их.

BASELINE

Для наивного решения можно было бы указать одно и то же значение для всех примеров из тестового набора. Нас просят предсказать вероятность невыплаты ссуды, поэтому можно предположить вероятность 0,5 для всех наблюдений на тестовой выборке. Это даст нам площадь под кривой ошибок (AUC ROC), равную 0,5. Но лучше воспользоваться для нашего бейзлайна чуть более сложной моделью: логистической регрессией.

РЕАЛИЗАЦИЯ ЛОГИСТИЧЕСКОЙ РЕГРЕССИИ

Чтобы получить базовый уровень, я использую рассмотренные ранее признаки, за исключением созданных вручную. Предварительно обработаю данные, заполнив недостающие значения и нормализуя диапазон признаков (масштабирование признаков). Следующий код выполняет оба этих шага предварительной обработки.

from sklearn.preprocessing import MinMaxScaler, Imputerif 'TARGET' in app_train:    train = app_train.drop(columns = ['TARGET'])else:    train = app_train.copy()features = list(train.columns)test = app_test.copy()# Заполняем пропущенные значения медианным значением признакаimputer = Imputer(strategy = 'median')# Масштабируем признаки в диапазон 0-1scaler = MinMaxScaler(feature_range = (0, 1))imputer.fit(train)train = imputer.transform(train)test = imputer.transform(app_test)scaler.fit(train)train = scaler.transform(train)test = scaler.transform(test)print('Training data shape: ', train.shape)print('Testing data shape: ', test.shape)

Training data shape: (307511, 240)

Testing data shape: (48744, 240)

Для первой модели я использую LogisticRegressionиз библиотеки Scikit-Learn. Единственное изменение, которое потребуется в настройки модели по умолчанию, уменьшупараметр регуляризацииC, контролирующий степень переобучения (более низкое значение должно уменьшить переобучение). Это даст чуть лучшие результаты, чем LogisticRegression с параметрами по умолчанию, но все равно установит низкую планку для любых будущих моделей.

Сначала создаем модель, затем обучаем ее с помощью метода.fit, а затем делаем прогнозы на основе тестовых данных с помощью метода.predict_proba(нам нужны вероятности, а не целые числа 0 или 1).

from sklearn.linear_model import LogisticRegression# Создаем модель и тренируем ееlog_reg = LogisticRegression(C = 0.0001)log_reg.fit(train, train_labels)

Теперь, когда модель обучена, можно использовать ее для прогнозирования. В задаче требуется спрогнозировать вероятность невыплаты ссуды, поэтому использую метод моделиpredict.proba. Он возвращает массив m*2, где m количество наблюдений. Первый столбец это вероятность того, что цель будет равна 0, а второй вероятность того, что цель будет равна 1 (поэтому для каждой строки два столбца в сумме должны быть равными 1). Нам нужна вероятность того, что ссуда не будет погашена, поэтому выберу второй столбец.

Следующая строка кода формирует прогнозы и выбирает правильный столбец.

log_reg_pred = log_reg.predict_proba(test)[:, 1]

Прогнозы должны быть в формате, показанном в файле samplesubmission.csv, где есть только два столбца: SKID_CURR и TARGET. Создадим фрейм данных в этом формате из набора тестов и прогнозов.

submit = app_test[['SK_ID_CURR']]submit['TARGET'] = log_reg_predsubmit.head()

Прогнозы представляют собой вероятность от 0 до 1 того, что ссуда не будет возвращена. Если бы требовалось использовать их для классификации заемщиков, можно было бы установить порог вероятности для определения рискованности ссуды.

submit.to_csv('log_reg_baseline.csv', index = False)

Файл с результатом теперь сохранен в виртуальной среде, в которой работает наш код. Чтобы получить доступ к нему, в конце программы необходимо нажать синюю кнопку Зафиксировать и запустить в правом верхнем углу экрана. Это действие запускает весь код, а затем позволяет загружать любые файлы, созданные во время запуска.

После запуска программы созданные файлы будут доступны на вкладке Версии раздела Вывод. Отсюда файлы с результатами можно отправить на оценку или скачать. Поскольку в этой программе несколько моделей, файлов результата будет несколько.

При отправке результата на оценку базовый показатель данной модели должен быть приблизительно равен 0,671.

УЛУЧШЕННАЯ МОДЕЛЬ: СЛУЧАЙНЙ ЛЕС

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

from sklearn.ensemble import RandomForestClassifierrandom_forest = RandomForestClassifier(n_estimators = 100, random_state = 50, verbose = 1, n_jobs = -1)random_forest.fit(train, train_labels)# Определяем важность признаковfeature_importance_values = random_forest.feature_importances_feature_importances = pd.DataFrame({'feature': features, 'importance': feature_importance_values})# Формируем предсказания для тестовых данныхpredictions = random_forest.predict_proba(test)[:, 1]

Оценка этой модели чуть выше базового уровня и составляет примерно 0,678 балла.

ДЕЛАЕМ ПРОГНОЗ, ИСПОЛЬЗУЯ СПЕЦИАЛЬНЕ ПРИЗНАКИ

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

poly_features_names = list(app_train_poly.columns)imputer = Imputer(strategy = 'median')poly_features = imputer.fit_transform(app_train_poly)poly_features_test = imputer.transform(app_test_poly)scaler = MinMaxScaler(feature_range = (0, 1))poly_features = scaler.fit_transform(poly_features)poly_features_test = scaler.transform(poly_features_test)random_forest_poly = RandomForestClassifier(n_estimators = 100, random_state = 50, verbose = 1, n_jobs = -1)random_forest_poly.fit(poly_features, train_labels)# Формируем предсказания на тестовых данныхpredictions = random_forest_poly.predict_proba(poly_features_test)[:, 1]
app_train_domain = app_train_domain.drop(columns = 'TARGET')domain_features_names = list(app_train_domain.columns)imputer = Imputer(strategy = 'median')domain_features = imputer.fit_transform(app_train_domain)domain_features_test = imputer.transform(app_test_domain)scaler = MinMaxScaler(feature_range = (0, 1))domain_features = scaler.fit_transform(domain_features)domain_features_test = scaler.transform(domain_features_test)random_forest_domain = RandomForestClassifier(n_estimators = 100, random_state = 50, verbose = 1, n_jobs = -1)random_forest_domain.fit(domain_features, train_labels)feature_importance_values_domain = random_forest_domain.feature_importances_feature_importances_domain = pd.DataFrame({'feature': domain_features_names, 'importance': feature_importance_values_domain})predictions = random_forest_domain.predict_proba(domain_features_test)[:, 1]

Оценка для этой модели составляет 0,679 балла, что указывает на низкую значимость разработанных признаков в этом случае.

ИНТЕРПРЕТАЦИЯ МОДЕЛИ: ВАЖНОСТЬ ПРИЗНАКОВ

В качестве простого метода определения наиболее релевантных признаков можно взглянуть на важность признаков для модели случайного леса. Учитывая корреляции, которые мы видели в исследовательском анализе данных, следует ожидать, что наиболее важными признаками являются EXTSOURCE и DAYSBIRTH.

def plot_feature_importances(df):    df = df.sort_values('importance', ascending = False).reset_index()    df['importance_normalized'] = df['importance'] / df['importance'].sum()    plt.figure(figsize = (10, 6))    ax = plt.subplot()    ax.barh(list(reversed(list(df.index[:15]))),             df['importance_normalized'].head(15),             align = 'center', edgecolor = 'k')    ax.set_yticks(list(reversed(list(df.index[:15]))))    ax.set_yticklabels(df['feature'].head(15))    plt.xlabel('Normalized Importance'); plt.title('Feature Importances')    plt.show()    return df# Отображаем важность признаков без учета специальных признаковfeature_importances_sorted = plot_feature_importances(feature_importances)

Как и ожидалось, наиболее важные признаки связаны с EXTSOURCE и DAYSBIRTH. Можно заметить, что существует лишь несколько признаков, имеющих существенное значение для модели. Это говорит о том, что можно отказаться от многих признаков без снижения качества (и даже увидеть его увеличение). Это не самый изощренный метод интерпретации модели или уменьшения размерности, но он позволяет начать понимать, какие факторы модель принимает во внимание при прогнозировании.

feature_importances_domain_sorted = plot_feature_importances(feature_importances_domain)

Все четыре созданные вручную признака вошли в топ-15 самых важных. Это должно вселить в нас уверенность в том, что наши знания в предметной области хотя бы частично соответствуют правильному пути.

ВВОД

В этой и предыдущей статьях я постарался показать, как начать соревнования по машинному обучению Kaggle. Сначала постарайтесь понять данные, задачу и метрику, по которой будут оцениваться результаты. Затем проведите простой EDA, чтобы попытаться определить взаимосвязи, тенденции или аномалии, которые могут помочь в моделировании. Попутно можно выполнить необходимые шаги предварительной обработки, такие как кодирование категориальных переменных, заполнение пропущенных значений и масштабирование признаков. Затем создайте новые признаки на основе существующих данных, чтобы посмотреть, может ли это помочь модели.

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

В целом, необходимо следовать общей схемепроекта машинного обучения:

  1. Разберитесь в проблеме и данных

  2. Очистите и отформатируйте данные (в основном это сделали для нас организаторы конкурса)

  3. Проведите исследовательский анализ данных

  4. Создайте базовую модель

  5. Улучшить модель

  6. Интерпретируйте модель

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

Я надеюсь, что моя статья помогла вам принять участие в этом (или каком-либо другом) соревновании по машинному обучению, и что теперь вы готовы самостоятельно и с помощью сообщества начать дальнейшую работу над серьезными проблемами!

При подготовке статьи были использованы материалы из открытых источников:источник1,источник2.

Подробнее..

Под капотом OpenVINO (Intel Neural Stick)

14.01.2021 20:16:39 | Автор: admin

Введение

Привет, Habr! Сегодня я хочу рассказать немного об аппаратном ускорителе Neural Compute Stick. Расскажу с точки зрения hardware-разработчика.

Что внутри OpenVINO

Известно, что OpenVINO взаимодействует с Intel Neural Compute Stick, а сердцем самого стика является чип Movidius Myriad X. Некоторые характеристики данного чипа можно найти в блоге компании Intel. В частности из статьи мы узнаем следующие характеристики Myriad X:

  • Векторные процессоры: 16

  • Основные интерфейсы: 16x MIPI lanes, USB 3.1, PCIe 3.0

  • 20+ ускорителей обработки медиа

Я попытался найти более подробную информацию о чипе в открытых источниках.

В частности, ресурс wikichip.org повествует о том, что у старых версий чипа есть еще и такие интерфейсы: SDIO, SPI, I2C, I2S, UART, JTAG и датчик температуры. В репозитории OpenVINO, в файле ie_plugin_config.hpp находим крохотное подтверждение информации с wikichip.org, что датчик температуры действительно есть: Metric to get a float of device thermal. String value is "DEVICE_THERMAL". Кроме того, SHAVE (Streaming Hybrid Architecture Vector Engine) имеют возможность отключать тактирование, что в целом интересно для LowPower устройств. Это говорит о том, что возможности чипа куда шире, чем просто ускоритель Deep Neural Network (DNN).

В качестве протокола коммуникации выступает фирменный XLink, который кроме всего прочего имеет интересную функцию XLinkBoot(deviceDesc_t deviceDesc, const char* binaryPath)*: Boots specified firmware binary to the remote device, binaryPath - path to the *.mvcmd file.

*.mvcmd - по сути является модифицированным elf-файлом, содержащим дополнительные инструкции для внутреннего загрузчика Myriad.

Пожалуй, на этом информация, которая имеет хоть какое-то отношение к hardware, заканчивается.

Интерфейсы

MIPI-CSI Как указано выше, Myriad X действительно имеет 16 MIPI-CSI lanes, если быть точнее, то 8 MIPI-CSI интерфейсов, которые работают в 2-lane режиме. Так же они могут работать в паре, в 4-lane режиме. Работа с несколькими сенсорами позволяет реализовать стерео зрение. Для его реализации на чипе присутствуют аппаратные ускорители.

MIPI-DSI Кроме того, чип имеет MIPI-DSI интерфейсы, что позволяет использовать Myriad X в проектировании автономных интерактивных устройств с дисплеем. Как пример, первое что приходит в голову: ATM, фотобудка (с поиском лица на фото и т.п.).

I2C Было бы странно, если бы в чипе была реализация MIPI, но не было бы I2C. С этим всё понятно.

I2S Действительно, Myriad X имеет возможность подключения аудио-устройств, что в симбиозе с DNN ускорителем делает его весьма интересным для применения в умных колонках, как например "Яндекс.Станция"

Пример применения (капля пиара)

Мы использовали аппаратные возможности Intel Myriad X в проекте камеры аналитики для ритейла под кодовым названием Jachin.

Камера представляет из себя аппаратную реализацию на базе чипа MA2085 с двумя цветными сенсорами и Ethernet. По своей задумке, камера вешается над входом в магазин или какой-либо другой интересующий нас объект. Один из сенсоров смотрит вниз и один в торговый зал.

С помощью сенсора, направленного на вход, и алгоритма обработки данных на выходе нейронной сети производится вычисление следующих метрик:

  1. Сколько человек вошло/вышло из магазина

  2. Сколько времени провел человек в магазине

По косвенным данным производится вывод о посещаемости магазина в зависимости от

  1. Времени суток

  2. Погоды

  3. Праздничный или будний день

  4. и т.д

Второй сенсор смотрит в сторону торгового зала. Через него, не без помощи нейросетей, производится построение "тепловой карты" - мест, наиболее популярных у посетителей магазина. Оценивается взаимодействие покупателей с товаром и проводится детальная аналитика розничного магазина подобно аналитике сайтов и их посетителей.

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

Мы не стали использовать H.264/H.265 только по той причине, что видеонаблюдение не является основным функционалом камеры.

Это краткое описание функционала, которое я могу рассказать как firmware разработчик. Бизнес, бэкэнд, фронтэнд и прочие составные части проекта в данной статье рассматриваться не будут. Если вам интересно взглянуть на описанную камеру, то вы можете увидеть её в разборе на анимации главной страницы https://smass.tech

Вернемся к процессу разработки на Myriad X.

Firmware

Очевидно, что поддержка работы периферии такого кристалла требует ОС. Когда OpenVINO загружает mvcmd файл внутрь Neural Computer Stick, на этом стике запускается RTEMS. Эта информация подтверждается и в открытых источниках от пользователя walts.

Философия разработки автономных решений на базе Myriad X практически наследует уже знакомую пользователям OpenVINO идею Pipeline'ов. Каждый элемент Pipeline работает в отдельном потоке, обрабатывает информацию, преобразует ее и передает следующему звену через очередь сообщений операционной системы.

Для реализации своих идей в распоряжении разработчика есть стандартные библиотеки C:

  • limits.h

  • math.h

  • stdarg.h

  • stdbool.h

  • stddef.h

  • stdint.h

  • stdio.h

  • stdlib.h

  • string.h

  • types.h

  • assert.h

  • ctype.h

И стандартные библиотеки С++:
  • algorithm

  • array

  • bitset

  • cassert

  • cctype

  • cerrno

  • cfenv

  • cfloat

  • ciso646

  • climits

  • cmath

  • cstdarg

  • cstdbool

  • cstddef

  • cstdint

  • cstdio

  • cstdlib

  • cstring

  • ctime

  • cwchar

  • cwctype

  • deque

  • exception

  • forward_list

  • functional

  • initializer_list

  • iterator

  • limits

  • list

  • map

  • numeric

  • queue

  • ratio

  • stack

  • stdexcept

  • string

  • tuple

  • type_traits

  • typeindex

  • typeinfo

  • unordered_map

  • unordered_set

  • utility

  • valarray

  • vector

  • ext/hash_map

  • ext/hash_set

Конечно, ресурсы чипа ограничены, поэтому разработчикам крайне необходимо знать азы написания эффективного кода: как ведут себя контейнеры в C++, как работать с указателями и т.п.

Из информации на официальном сайте Intel, SDK также включает специализированную инфраструктуру FLIC в разработке конвейеров обработки изображений. FLIC (Frame Level Isp Cv) представляет из себя плагин с конкретным функциональным назначением, который работает в отдельном потоке операционной системы.

Плагины включают в себя порты коммуникации между собой.

Передатчик (Sender) - шаблонный компонент, передающий указатель на объект. Приемник (Receiver) - очевидно, шаблонный компонент, принимающий указатель на тип данных, указанный в качестве параметра шаблона. По сути, внутри себя оба компонента реализуют функционал через mqsend, mqreceive.

Оба компонента имеют механизмы связывания между собой.

Камера на Myriad X

Рассмотрим упрощенный пример того, как сделать USB камеру на Myriad X.

  1. У нас есть сенсор, подключенный по MIPI к Myriad X. Для того, чтобы сохранить кадр с сенсора нам нужно сначала выделить для него память. Для этого используется FLIC плагин, который выделяет область памяти фиксированного размера из статического массива.

  2. Подключаем плагин, реализующий работу с MIPI. Упрощенно, у него используется два основных порта. Первый, receiver, связывается с sender плагина из п.1, чтобы получить пустой фрейм. Полученные по MIPI данные заполняют пустой фрейм. Когда будет получен сигнал End Of Frame, во второй порт, sender, будет передан указатель на область памяти, где уже содержатся полезные данные в формате RGRB.

  3. Подключаем третий плагин, в котором производится дебаеризация данных. Receiver плагина подключается к sender из п.2. Полученный фрейм из п.2 переводится в формат YUV и передается в sender.

На практике, нам необходим еще один плагин, который выделит память под дебаеризованный фрейм (тоже самое, что в п.1). По большому счету, всегда, когда происходит преобразование данных, нам необходимо выделять новые пустые фреймы: вычитываем из области памяти данные в формате RGRB, преобразуем, записываем в другую область памяти результат в YUV формате. Но для упрощения статьи такие детали далее будут опускаться.

  1. Подключаем плагин H.264 энкодера. Он принимает данные в формате YUV от sender в плагине п.3, производит кодирование и отправляет указатель на закодированные данные в sender.

  2. Подключаем плагин вывода данных по USB. Как уже говорилось в начале статьи, в SDK реализован протокол коммуникации XLink, что используется в OpenVINO. Хорошо, берем плагин XLink. Он не имеет порта sender, но имеет receiver. Его мы подключаем к sender плагина H.264 энкодера. Как только от энкодера придут данные в плагин XLink, они будут переданы по USB хосту.

Когда на указатель перестает кто-либо ссылаться, он "возвращается" обратно в пул памяти и тогда плагин п.1 может вновь использовать эту область для новых данных.

MIPI генерирует фреймы с фиксированным FPS, пусть для примера он будет равен 20, то есть период следования фреймов 50мс. За 50мс программа должна успеть принять данные, передать их в энкодер и далее по USB. Предположим, возникли проблемы с USB, и весь цикл обработки одного фрейма занял 62мс. В такой ситуации фрейм, выделенный в п.1, не успеет вовремя вернуться обратно в пул, и тогда новый кадр с сенсора будет пропущен. Как правило, плагин из п.1 использует статический массив, в котором умещается сразу несколько пустых фреймов.

DEBUG

Стоит отметить, что отладка кода представляет из себя не менее увлекательный процесс, чем его написание. К счастью или нет, но мне очень нравится среда разработки Visual Studio, ну вот нравится и всё, что бы мне не говорили про VIM, Sublime, gedit и прочее. Соответственно, после Visual Studio, со всеми ее брейкпоинтами и пошаговой отладкой было довольно непривычно отлаживать код через CLI интерфейс:

  • Запуск отладки через консольную команду для утилиты, работающей через JTAG (тут ничего удивительного)

  • Установка breakpoint через консольную команду для утилиты, работающей через JTAG. В качестве аргумента указывается номер строки конкретного файла проекта.

  • Пошаговая отладка тоже через консоль

В целом, здесь всё делается через консоль. Хочешь посмотреть состояние аппаратного блока - вводи команду. Проверить стек вызовов - вводим команду. Наверное, это единственное, что может слегка расстроить с непривычки: вместо пары кликов в IDE нужно сделать 15-30 кликов по клавиатуре.

Заключение

Я не планировал в рамках данной статьи давать исчерпывающие ответы на аппаратные и программные возможности Myriad X, а лишь хотел рассказать разработчикам, что Intel Myriad X может найти более широкое применение, нежели просто USB ускоритель DNN.

Подводя промежуточные итоги, хотел бы сказать, что Myriad X довольно интересный чип при своем энергопотреблении. Огромное количество документации поражает воображение. Но, если изучить эту документацию, то с Myriad X перед разработчиками открываются довольно мощные возможности в проектировании AI устройств: от интеллектуальных беспилотников на аккумуляторном питании, до банкоматов с распознаванием лиц.

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

Подробнее..

Мы опубликовали современный Voice Activity Detector и не только

14.01.2021 10:07:09 | Автор: admin

image


Всегда при работе с речью встает несколько очень "простых" вопросов, для решения которых нет большого количества удобных, открытых и простых инструментов: детекция наличия голоса (или музыки), детекция наличия цифр и классификация языков.


Для решения задачи детекции голоса (Voice Activity Detector, VAD) существует довольно популярный инструмент от Google webRTC VAD. Он нетребовательный по ресурсам и компактный, но его основной минус состоит в неустойчивости к шуму, большом числе ложноположительных срабатываний и невозможности тонкой настройки. Понятно, что если переформулировать задачу не в детекцию голоса, а в детекцию тишины (тишина это отсутствие и голоса и шума), то она решается весьма тривиальными способами (порогом по энергии, например), но с теми же минусами и ограничениями. Что самое неприятное зачастую такие решения являются хрупкими и какие-то хардкодные пороги не переносятся на другие домены.


Изначально мы хотели сделать простой и быстрый внутренний инструмент для себя и наших партнеров для детекции произнесенных чисел без привлечения полноценного STT (фишка изначально была именно в портативности засчет использования современных фреймворков типа PyTorch и ONNX), но в итоге оказалось, что можно сделать не только детектор чисел, но и качественный, быстрый и портативный VAD и классификатор языков, который и опубликовали бесплатно для всех желающих тут под лицензией MIT. За подробностями прошу под кат.


Основные фишки нашего решения


Что же умеет делать наш "VAD"?


  • Именно сам VAD находит в аудио участки, где люди говорят;
  • Number detector находит в аудио участки, где люди произносят цифры;
  • Language classifier классифицирует языки;
  • Это все сейчас работает на 4 языках (Русский, Английский, Немецкий, Испанский), но с высокой степенью вероятности именно сам VAD будет работать и на других родственных им языках (небольшой квест для Хабра если вы говорите на каком-то экзотическом языке, запишите свой голос, прогоните VAD и поделитесь результатом!);

Основные "фишки" на данный момент:


  • Поддержка 4 языков;
  • Именно VAD сильно выигрывает у WebRTC по качеству;
  • Натренирован на огромных речевых и шумовых корпусах;
  • Ест мало ресурсов и памяти, работает на 1 потоке процессора;
  • Его скорости достаточно для edge и мобильных применений;
  • Построен на базе современных и портативных технологий (PyTorch, ONNX);
  • В отличие от WebRTC скорее является детектором голоса, а не детектором тишины;
  • Мы выложили чекпойнты как для PyTorch (JIT), так и для ONNX;

Возможные применения


  • Детекция конца фразы;
  • Подготовка и очистка голосовых корпусов;
  • Часть пайплайна для анонимизации речевых корпусов (по-хорошему еще надо уметь искать имена, но это совсем другая проблема, и она довольно специфична для решаемой задачи и требует наличия и тонкой настройки STT);
  • Детекция наличия голоса для применения на мобильных и edge устройствах;
  • Компактность и наличие ONNX позволяет запускать его с большим количеством доступных бекендов;
  • VAD кушает данные с частотой дискретизации 16 kHz, но он научен не бояться и данных с 8 kHz;

Примеры


Вообще мы постарались привести основные примеры использования в интерактивном ноутбучке в colab и в самом репозитории. Все их выписывать в статью смысла нет, давайте просто перечислим самые важные и приведем самый простой пример:


  • Все примеры есть как для PyTorch так и для ONNX;
  • Для самого важного алгоритма VAD мы привели примеры как для работы с целыми отдельными файлами, так и для однопоточного / многопоточного стриминга;
  • Для остальных приведены только примеры по работе с отдельными файлами. Но имея VAD уже несложно длинные файлы разделить на короткие;
  • Примеры специально приводятся в виде простейшего тулкита, который легко будет адаптировать на свой язык с минимальными усилиями (обработка целых файлов тривиальна, стриминг в 1 поток несложный, несколько потоков немного сложноват из-за механизма окон);

Самый просто пример, где мы натравливаем VAD на файл:


import torchtorch.set_num_threads(1)model, utils = torch.hub.load(repo_or_dir='snakers4/silero-vad',                              model='silero_vad',                              force_reload=True)(get_speech_ts, _, read_audio, _, _, _) = utilsfiles_dir = torch.hub.get_dir() + '/snakers4_silero-vad_master/files'wav = read_audio(f'{files_dir}/en.wav')speech_timestamps = get_speech_ts(wav, model,                                  num_steps=4)print(speech_timestamps)

Как работает VAD


Тут кратко опишем, как данные кормятся в VAD. Для остальных алгоритмов заинтересовавшиеся просто найдут информацию в коде.


  • Аудио разделяется на кусочки длиной например 250 мс. Можно конечно порезать и короче, но по нашему опыту все паузы менее 100 мс являются малозначимыми и получается очень много шума, если пытаться поделить по 30-50мс. По просьбам интересующихся мы также привели график зависимости качества от длины кусочка тут (мы сравнили 100 мс и 250 мс);
  • VAD держит в памяти прошлый кусочек (или нули в начале стрима);
  • Эти кусочки по 500 мс (или по 200 мс) делятся на 4 или 8 окон внахлест и модель применяется к каждому такому окну;
  • Вероятности выдаваемые моделью усредняются по всем таким окнам;
  • Дальше эта вероятность используется, чтобы или "войти" в речь или из нее "выйти". Базовые оптимальные гипер-параметры приведены в коде примеров;

Скорость и задержка


Все замеры скорости мы делали на 1 потоке процессора AMD Ryzen Threadripper 3960X. Для этого мы использовали такие настройки:


torch.set_num_threads(1) # pytorchort_session.intra_op_num_threads = 1 # onnxort_session.inter_op_num_threads = 1 # onnx

Подробнее вы можете просто посмотреть в коде, но задежка зависит от следующих параметров:


  • num_steps число таких окон "внахлест";
  • number of audio streams число одновременно обрабатываемых потоков аудио;
  • По сути получается, что модель всегда видит батч длины равной num_steps * number of audio streams;

Получаются такие задержки:


Batch size Pytorch latency, ms Onnx latency, ms
2 9 2
4 11 4
8 14 7
16 19 12
40 36 29
80 64 55
120 96 85
200 157 137

Попробуем теперь измерить пропускную способность в секундах аудио, обрабатываемых за одну секунду на 1 потоке процессора:


Batch size num_steps Pytorch model RTS Onnx model RTS
40 4 68 86
40 8 34 43
80 4 78 91
80 8 39 45
120 4 78 88
120 8 39 44
200 4 80 91
200 8 40 46

Качество


По логике процесса, описанного выше, мы измеряли качество нашего VAD по сути просто присваивая некую усредненную вероятность каждому кусочку аудио и сравнивая ее с истинными метками. Но как добавить к сравнению WebRTС, он же выдает просто 0 или 1?


WebRTC принимает на вход фреймы аудио и отдает 0 или 1. По-умолчанию используется длина фрейма в 30 мс, то есть каждый кусочек аудио в 250 мс мы делится примерно на 8 таких фреймов. Это неидеально, но мы просто интерпретируем среднее из таких 0 и 1 как вероятность.


В итоге получается вот такой результат:


image


Тонкая настройка и остальные алгоритмы


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

Подробнее..

Перевод Как преобразовать аудиоданные в изображения

14.01.2021 14:20:31 | Автор: admin

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


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

Сегодня, специально к старту нового потока курса по машинному обучению делюсь с вами статьей, в которой авторы, в качестве примера определяют вид птиц по их пению. Они находят в записях, сделанных в естественных условиях, фрагменты с пением птиц, и классифицируют виды. Преобразовав аудиоданные в данные изображений и применив модели компьютерного зрения, авторы этой статьи получили серебряную медаль (как лучшие 2 %) на соревновании Kaggle Cornell Birdcall Identification.




Обработка аудио как изображений


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

Те же рассуждения применимы к задачам обнаружения звука. Есть спектрограммы четырёх видов птиц. Прослушать оригинальные отрезки звука можно здесь. Глазами человек тем более мгновенно увидит различия видов по цвету и форме.

Перемещение звуковых волн во времени требует больше вычислительных ресурсов, и мы можем получить больше информации из двумерных данных изображений, чем из одномерных волн. Кроме того, недавнее быстрое развитие компьютерного зрения, особенно с помощью свёрточных нейронной сетей, может значительно улучшить результаты, если рассматривать аудио как изображения, так мы (и почти все остальные участники) и поступили на соревнованиях.

Понимание спектрограммы


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


Объяснение параметров звуковых волн

На спектрограмме аудиоклипа горизонтальное направление представляет время, а вертикальное направление представляет частоты. Наконец, амплитуда звуков определённой частоты, существующих в определённый момент, представлена цветом точки, который обозначается соответствующими координатами x-y.


Объяснение спектрограммы

Чтобы понять, как частоты отражаются в спектрограммах, посмотрите на трёхмерную визуализацию, которая демонстрирует амплитуду с помощью дополнительного измерения. По оси X отложено время, а по оси Y значения частот. Ось z это амплитуда звуков частоты координаты y в момент координаты x. По мере увеличения значения z цвет меняется с синего на красный, получается цвет, который мы видели в предыдущем примере 2D-спектрограммы.


Визуализация трёхмерной спектрограммы

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

Шкала мел и её спектрограмма


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

Однако человеческий слух не воспринимает различия во всех частотных диапазонах одинаково. По мере увеличения частот нам становится всё труднее различать их. Чтобы лучше имитировать поведение человеческого уха с помощью моделей глубокого обучения, мы измеряем частоты по шкале мел. В шкале мел любое равное расстояние между частотами звучит для человеческого уха одинаково. Единица мел (m) связана с герцами (f) таким уравнением:

$m = 2595 * log(1+f/700).$


Спектрограмма на мел-шкале это просто спектрограмма с частотами, измеренными в мел.

Как мы используем спектрограмму?


Для создания мел-спектрограммы из звуковых волн мы воспользуемся библиотекой librosa.

import librosay, sr = librosa.load('img-tony/amered.wav', sr=32000, mono=True)melspec = librosa.feature.melspectrogram(y, sr=sr, n_mels = 128)melspec = librosa.power_to_db(melspec).astype(np.float32)

Где y обозначает необработанные данные волны, sr обозначает частоту дискретизации аудио-сэмпла, а n_mels определяет количество полос мел в сгенерированной спектрограмме. При использовании метода melspectrogram вы также можете установить параметры метода f_min и f_max. Можно установить Then и преобразовать спектрограмму в спектрограмму мел, выражающую амплитуду на прямоугольной шкале, к децибелам с помощью метода power_to_db.

Чтобы визуализировать сгенерированную спектрограмму, запустите код:

import librosa.displaylibrosa.display.specshow(melspec, x_axis='time',  y_axis='mel', sr=sr, fmax=16000)

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

from torchlibrosa.stft import Spectrogram, LogmelFilterBankspectrogram_extractor = Spectrogram()logmel_extractor = LogmelFilterBank()y = spectrogram_extractor(y)y = self.logmel_extractor(y)

Резюме


В заключение скажу, что мы можем воспользоваться преимуществами последних достижений компьютерного зрения в задачах, связанных со звуком, путём преобразования данных аудиоклипов в данные изображения. Мы достигаем этого с помощью спектрограмм, показывающих сведения о частоте, амплитуде и времени аудиоданных в изображении. Использование шкалы мел и спектрограммы шкалы мел помогает компьютерам имитировать человеческий слух, чтобы различать звуки разных частот. Для генерации спектрограмм мы могли бы воспользоваться библиотекой librosa или torchlibrosa для ускорения GPU на Python. Рассматривая таким образом задачи, связанные со звуком, мы можем создавать эффективные модели глубокого обучения для выявления и классификации звуков, так же, как, например, врачи диагностируют сердечные заболевания с помощью ЭКГ.




Подробнее..

Нейросети в большом городе. Разбираемся, как они помогают идентифицировать людей, и запускаем собственную нейросеть

08.01.2021 16:23:02 | Автор: admin
Алгоритмы по детекции лиц плотно вошли в нашу жизнь, хотя и не все это замечают. Началось всё в 2015 году со сферы развлечений. Стартап Looksery, занимающийся разработкой AR-фильтров, был куплен Snapchat. Приложение распознавало лицо человека на фотографии и накладывало на него весёлые рожицы. Чуть позже, в начале 2016 года, Facebook купил белорусский стартап MSQRD и запустил маски в Facebook Stories. Но это можно считать только обкаткой таких технологий.

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




Прежде чем продолжить, чтобы не запутаться, дадим определения некоторым терминам.

Детекция обнаружение какого-либо класса объектов, например лица.

Идентификация обнаружение определённого объекта, например лица Васи Пупкина.

Дальнейшее развитие нейронных сетей подняло точность идентификации, достаточную для решения таких задач, как защита и безопасность. В 2017 году Apple представила FaceID сканер, позволяющий разблокировать телефон по лицу владельца. Появился новый способ оплаты покупок по биометрии. Летом 2020 года сеть московских кафе Prime начала тестировать систему оплаты по лицу клиента. Вместо использования пропусков для турникетов начинают идентифицировать человека через видеокамеру.

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

Распознавание с помощью алгоритмов


В 2001 году Пол Виола и Майкл Джонс предложили метод детекции объектов, который широко использовался для определения лиц. В 2005 году Навнит Далал и Билл Триггс показали свои гистограммы направленных градиентов (Histogram of Oriented Gradients, HOG), с помощью которых также можно было детектировать лица.

К сожалению, качество данных подходов оставляло желать лучшего. Был высок уровень срабатывания ошибок первого и второго рода.

Ошибка первого рода отказ от правильной гипотезы, когда лицо в кадре не детектируется.
Ошибка второго рода принятие неправильной гипотезы, когда детектируется то, что к лицу не относится.

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

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

  • Плохой уровень освещённости (света от монитора в тёмной комнате достаточно для распознавания);
  • Голова наклонена или слегка повёрнута в сторону;
  • Лицо не полностью попадает в кадр или частично прикрыто ладонью;
  • Борода, очки не проблема.

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

Распознавание лиц в городах


По состоянию на 2019 год, количество камер с системой распознавания лиц, установленных в крупных городах:

  • Пекин 470 тыс.;
  • Великобритания 420 000 тыс.;
  • Вашингтон 30 000 тыс.;

В Москве на данный момент установлено около 193 тыс. HD-камер. Расположение камер можно посмотреть на сайте data.mos.ru.

Также система распознавания лиц запущена в московском метро. Камеры установлены в вагонах и у турникетов. В 2021 году будет добавлена функция оплаты проезда через идентификацию лица. При прохождении человека через турникет оплата будет списываться автоматически. Для этого всего лишь нужно связать банковскую карту с биометрическими данными.

Цели развёртывания систем распознавания


Теоретически эти меры должны будут сократить преступность, распознавая нарушителей за доли секунды. Система слежения позволит выявить самые опасные места в городе и вычислить нелегальных мигрантов. По данным МВД, за два года тестовой работы системы распознавания лиц нашли около 100 человек, находящихся в федеральном розыске, после чего в конце 2019 года к системе решили подключить все городские камеры.

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

К сожалению, ни одна система не идеальна иногда происходит утечка данных. В сети за скромную плату такая информация продаётся всем желающим. Чаще всего такие данные покупают частные детективы или коллекторы.

Как обрабатываются такие данные


Ряд компаний на протяжении более 40 лет активно разрабатывает системы распознавания человеческих лиц. В их числе даже знаменитый производитель оружия Smith & Wesson со своей системой ASID Automated Suspect Identification System. А полиция Лондона сейчас тестирует похожую систему в сотрудничестве с японской компанией NEC. Но лидером в данной области можно смело назвать российскую компанию NtechLab. В 2015 году алгоритм распознавания лиц от NtechLab был признан лучшим на организованном Вашингтонским университетом международном конкурсе The MegaFace Benchmark. В мае 2016 NtechLab в числе трёх российских компаний была допущена к официальному тестированию технологий биометрии, проводимому NIST. Сам факт допуска к испытаниям дал компании право участвовать в гостендерах США и ряда других стран.


Широкой аудитории алгоритм был представлен в виде сервиса FindFace, который искал людей во Вконтакте по фотографии. После своего запуска сервис наделал много шума в соцсетях, а также спровоцировал несколько скандалов с деаноном. По всей видимости, сервис был маркетинговым приёмом, призванным показать возможности платформы потенциальным приобретателям технологии. Вскоре после этого разработчики закрыли сервис, и компания начала оказывать услуги государству и различным отраслям бизнеса. В частности стало известно, что мэрия Москвы заплатила NtechLab не менее $3,2 млн за использование её технологии распознавания лиц в городской системе видеонаблюдения.

Собственная система слежения в домашних условиях на основе нейронных сетей


MTCNN нейронная сеть для детекции лиц


MTCNN это каскад свёрточных нейронных сетей. В модели используются 3 сети: P-Net, R-Net и O-net. Каждая последующая нейронная сеть увеличивает точность предсказания.



Первая P-Net на выходе выдаёт координаты ограничивающих прямоугольников предполагаемых лиц. Далее R-net отсекает менее вероятные области лиц и добавляет уровень достоверности к тем, которые остались. В третьей сети мы снова избавляемся от прямоугольников с более низким уровнем достоверности и добавляем координаты 5 лицевых ориентиров.


Результат работы mtcnn

Для тех, кто хочет поэкспериментировать, нейронная сеть упакована в Python-библиотеку с одноимённым названием MTCNN. Для запуска достаточно создать объект MTCNN и вызвать метод detect_face.

Детектор возвращает словарь с тремя ключами: координаты прямоугольника, ключевые точки и уровень достоверности. Пример кода с гитхаба проекта ниже:

import cv2from mtcnn import MTCNN detector = MTCNN()image = cv2.cvtColor(cv2.imread("ivan.jpg"), cv2.COLOR_BGR2RGB)result = detector.detect_faces(image)bounding_box = result[0]['box']keypoints = result[0]['keypoints']cv2.rectangle(image,          (bounding_box[0], bounding_box[1]),          (bounding_box[0]+bounding_box[2], bounding_box[1] + bounding_box[3]),          (0,155,255), 2)cv2.circle(image,(keypoints['left_eye']), 2, (0,155,255), 2)cv2.circle(image,(keypoints['right_eye']), 2, (0,155,255), 2)cv2.circle(image,(keypoints['nose']), 2, (0,155,255), 2)cv2.circle(image,(keypoints['mouth_left']), 2, (0,155,255), 2)cv2.circle(image,(keypoints['mouth_right']), 2, (0,155,255), 2)cv2.imwrite("ivan_drawn.jpg", cv2.cvtColor(image, cv2.COLOR_RGB2BGR))print(result)

У меня были проблемы с зависимостями пакетов. Если у вас появляются ошибки, скопируйте в файл requirements.txt следующие пакеты:

opencv-python==4.2tensorflow==1.12.3keras==2.2.4numpy == 1.16protobuf==3.6.0mtcnn

Если tensorflow не устанавливается тогда обновите setuptools командой:

pip install setuptools --upgrade --ignore-installed

FaceNet


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

После обучения нейронная сеть способна производить идентификацию, сверяя текущее лицо с лицами, которые хранятся в базе данных.

Для того чтобы провести идентификацию, сначала нужно провести детекцию, воспользовавшись любым из методов по определению лиц. После получения изображения лица, в котором глаза и губы находятся примерно на одном и том же месте изображения, мы передаём картинку в FaceNet, предварительно преобразовав к разрешению 96x96 пикселей.

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


Принцип работы faceNet

Тот, кто хочет протестировать модель, может заглянуть на данный репозиторий. Веса для предобученной нейронной сети можно скачать с google-диска.

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

Слабые места подобных систем


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


На точность распознавания в условиях городской среды влияют такие факторы, как погодные условия, место размещения камеры, направление объектива, уровень освещённости, частичное закрывание лица шарфом или медицинской маской.


Известны случаи, когда алгоритм ошибается и принимает добропорядочного человека за нарушителя, который находится в розыске. Полиция задерживает человека до тех пор, пока не будет уверена в том, что алгоритм ошибся.

Перспективы развития систем видеонаблюдения


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

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

image



Подробнее..

Алгоритм обучения нейронных сетей с помощью нового вида чисел

13.01.2021 14:06:06 | Автор: admin

Приветствую читатель. Когда я был ребёнком и учился в школе, моим любимым предметом была математика, любимым предметом она была из-за того, что я очень люблю решать задачи, в какой-то момент своей жизни я начал составлять сам для себя заведомо нерешаемые задачи и пытался их решить, по полной напрягая свой разум в продумывании подхода для решения нерешаемой задачи, иногда оказывалось, что нерешаемая задача только казалась таковой из-за упущения некоторых неочевидных моментов. Любовь к решению задач сильно повлияла на меня, из-за чего я у себя в голове постоянно решаю какие-либо задачи, не только математические, но и из других сфер. За жизнь у меня накопилось множество идей (решений), от 3d принтера печатающего сталью до способа решения проблемы утилизации радиоактивных отходов атомных электростанций. Наверняка многие идеи на самом деле не реализуемы, по тем или иным причинам, а некоторые наверняка были придуманы до меня, а я просто о них не знал (так уже бывало). В прошлой моей статье я упомянул (сам не знаю зачем) о том, что я придумал новый вид чисел с помощью которых можно обучать нейронные сети. Я хотел открыть сервис по обучению нейронных сетей с помощью этих чисел, но с учётом пандемии и моего плохого состояния здоровья, я подумал, что вдруг я реально первый кто додумался до этих чисел и будет крайне плохо если я умру и знания о этих числах уйдут со мной. Поэтому я и решил написать эту статью, в которой расскажу подробно о этих числах и как их использовать для обучения нейронных сетей. Сразу скажу, что я не прорабатывал все необходимые формулы для работы с такими числами, так как был занят своим языком программирования, это лишь идея, а не готовая реализация.

Для того, что бы полностью понять о чём пойдёт речь в статье, нужно иметь знание о строении простых feedforward нейронных сетей.

Предположим нужно обучить feedforward нейронную сеть имея некую обучающую выборку, в которой есть примеры того, что подаётся на вход нейронной сети и что ожидается получить на выходе. Для такого случая можно написать функцию, назовём её fitness (как в генетическом алгоритме), на вход такой функции даётся нейронная сеть и обучающая выборка, а функция возвращает число от 0 до 1, число соответствует тому, на сколько данная нейронная сеть обучена данной выборкой, где 0 - максимально не обучена, 1 - идеально обучена. Используя такую fitness функцию, нейронную сеть можно представить как математическую функцию у которой аргументы - это веса нейронной сети, а результатом является результат fitness функции применяемой к нейронной сети с данными весами и обучающей выборкой. Я начал размышлять "как найти максимум такой функции?". В свой голове я представил 3х мерный график функции с 2 аргументами и подумал, что если добавить условие что каждый вес будет ограничен каким либо конечным диапазоном возможных значений, то можно разделить этот график на две части, в одной части графика первый аргумент имеет одни значения из своего возможного диапазона, а вторая часть графика имеет все оставшиеся значения аргумента, затем проанализировать в какой части максимум больше, взять эту часть и делить её таким же образом, но уже опираясь на другой аргумент, после чего полученную в результате второго деления часть, снова нужно разделить на две части опираясь на первый аргумент. Такое деление на части нужно производить до тех пор, пока значения результата функции на полученном от делении участке, будут иметь слишком большие колебания. Любые аргументы из полученной части графика и являются подходящими весами. Для лучшего понимания, поясню вышесказанное на примере.

График функции y = sin xГрафик функции y = sin x

Предположим есть функция y(x) = sin x, x принадлежит множеству [-4, 4], нужно найти максимум, или значение очень близкое к максимуму, данной функции на данном участке. Разделим график на 2 части, в одной части x принадлежит множеству [-4, 0], во второй части x принадлежит множеству [0, 4], как видно на графике максимум находится во второй части, затем делим вторую часть на две части [0, 2] и [2, 4]. В какой-то момент в результате деления получится часть, в которых минимальное и максимальное значение функции очень близко к 1, например [pi * 999999 / 2000000, pi / 2], из этой части любой x будет приемлемым решением. Теперь о том как вышесказанное реализовать на практике. Однажды я сидел и смотрел в интернете научно-популярное видео, касалось оно космоса и в нём вскользь мелькнула информация о суперпозиции, в этот момент я подумал о числах которые не занимают определённую позицию, а находятся во многих местах одновременно. Например представить множество [0, 1], не как множество, а как число одновременно имеющее значение всех чисел из множества. Назовём такие числа "суперпозиционными". Результатом применения любой унарной операции к суперпозиционному числу, это суперпозиционное число, которое охватывает множество чисел, которые могут быть получены в результате применения унарной операции к любому числу из множества, которое охватывает исходное суперпозиционное число. Например: sin([-pi, pi]) = [-1, 1]. Результатом применения любой бинарной операции над суперпозиционными числами, это суперпозиционное число, которое охватывает множество чисел, которые могут быть получены в результате применения бинарной операции к двум любым числам из множеств, которые охватывают исходные суперпозиционные числа. Например: [-3, 6] - [-12, 7] = [-10, 18]. Вещественные числа так же можно представить в виде суперпозиционных как множество с одним числом, например [3, 3]. Что дают такие числа? Если есть некая функция с одним или несколькими неизвестными аргументами, но при этом значения этих аргументов ограничены каким либо конечным множество возможных значений, то подставив в качестве таких аргументов суперпозиционные числа, охватывающие собой множества возможных аргументов, можно узнать диапазон возможных значений функции. Вот как я придумал обучать простые feedforward нейронные сети:

  • создать нейронную сеть в которой все веса - суперпозиционные числа с множеством, которое ограниченно минимальным и максимальным значением веса

  • создать fintess функцию которая принимает указную нейронную сеть и обучающую выборку

  • уменьшить размер множества из суперпозиционного числа в одном весе в 2 раза, охватив либо половину множества с меньшими числами, либо с большими числами. Выбирается та половина, заменив на которую fitness функция даст большее значение. Если для обоих половин fitness функция выдаёт одинаковое значение, то выбирается случайная половина

  • выше указанные действия повторяются для каждого веса

  • выше указанные действия повторяются до тех пор, пока границы всех множеств суперпозиционных чисел в весах отличаются на определённое значение

  • заменить каждый вес на любое вещественное число из множества охватываемого суперпозиционным числом из соответствующего веса

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

Хотя как идея, суперпозиционные числа олицетворяют собой числа которые находятся одновременно в нескольких местах, по факту вышеуказанные алгоритм использует такие числа как олицетворение вещественного числа находящегося в неком промежутке, а в результате получаем промежуток в котором может быть результат действий производимого над числами. Одна из проблем такого подхода в том, что совершенно не учитываются вероятность получения каких либо значений в диапазоне чисел. Например: если есть некое число принадлежащее множеству [1, 2] и есть второе число принадлежащее множеству [4, 5], то если сложить два этих числа, получится число принадлежащее множеству [5, 7], но число 5 можно получить только если сложить 1 и 4, а для числа 6 существует бесконечное количество пар чисел, поэтому вероятность получения числа 6 выше, чем вероятность получения числа 5. Поэтому мною были придуман новый вид чисел, с помощью которых можно учитывать вероятности. Разумеется если взять некое бесконечное множество и некое неизвестное число гарантированно принадлежащее данному множеству, то вероятность того что это число будет равным некому заранее известному числу из этого множества, стремится к 0, поскольку множество бесконечно. Но вот что я придумал сделать, предположим есть множество [a, b] и есть некое случайное число x принадлежащее множеству, предположим что если разделить множество на n равных частей, то x имеет некую вероятность принадлежать одной из частей, для каждой части вероятность может быть различной. Предположим что есть функция f получающая номер части и возвращающая вероятность (от 0 до 1) того, принадлежит ли этой части число x. На основе функции f можно создать функцию f1, f1(x) = f(x) * n. При стремлении n к бесконечности, функция f1 возвращает значение, с помощью которого можно оценить вероятность того, что x находится в окрестностях некого числа. Если f1 возвращает значение 0, то считается, что вероятность существует, но она минимально возможная, а чем выше число, тем выше вероятность. Для создания нового вида чисел, я взял суперпозиционные числа и добавил к ним функцию f1. Характеристику числа, которую описывает функция f1 я изначально назвал концентрацией, но уже после того как я всё придумал, мне в подписки на youtube пришло два видео (первое, второе), в которых рассказывается о вероятности вероятностей и там такую характеристику называли плотностью, поэтому и я тоже буду называть её плотностью, а сами числа - плотностными. Для удобства в дальнейшем областью определения (какому множеству принадлежит x) функции в плотностных числах, будет множество [0, 1], а для получения плотности в конкретной точке, значение получаемое из функции, будет масштабироваться.

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

  • создать нейронную сеть в которой все веса - плотностные числа с множеством, которое ограниченно минимальным и максимальным значением веса и с функцией y(x) = 1

  • создать fintess функцию которая принимает указную нейронную сеть и обучающую выборку

  • уменьшить размер множества из плотностного числа в одном весе в 2 раза, охватив либо половину множества с меньшими числами, либо с большими числами. Выбирается та половина, заменив на которую fitness функция даст большее значение. Если для обоих половин fitness функция выдаёт одинаковое значение, то выбирается случайная половина

  • выше указанные действия повторяются для каждого веса

  • выше указанные действия повторяются до тех пор, пока границы всех множеств плотностных чисел в весах отличаются на определённое значение

  • заменить каждый вес на любое вещественное число из множества охватываемого плотностным числом из соответствующего веса

  • заменить один вес на плотностное число, охватывающее множество ограниченное минимальным и максимальным значением веса

  • уменьшить размер множества из плотностного числа в одном весе в 2 раза, охватив либо половину множества с меньшими числами, либо с большими числами. Выбирается та половина, заменив на которую fitness функция даст большее значение. Если для обоих половин fitness функция выдаёт одинаковое значение, то выбирается случайная половина

  • выше указанное действие повторять до тех пор, пока границы множества плотностного числа в весе отличаются на определённое значение

  • заменить вес на любое вещественное число из множества охватываемого плотностным числом из веса

  • повторить 4 последних действия над каждым весом

  • если в результате последних 5-ти действий нейронная сеть стала более обученной, то повторить эти 5 действий ещё раз

Хотя и в суперпозиционных числах, и в плотностных числах может быть сколь угодно много отрезков возможных чисел (например: суперпозиционное число состоящее из 3 отрезков ([0, 1], [20, 40], [100, 101]) ), поскольку я эти числа использую только в контексте обучения нейронных сетей, то всё что я говорю ниже, относиться к плотностным числам которые состоят из 1 отрезка ограниченного 2-мя вещественными числами. В начале этой статьи я писал о том, что я не проработал все необходимые формулы и вот о каких формулах идёт речь. Если взять два плотностных числа [1, 2, y(x) = 1], [4, 5, y(x) = 1], то при сложении этих чисел получается число [5, 7, y(x) = 1 - |0.5 - x| * 2]. Откуда взялась функция y(x) = 1 - |0.5 - x| * 2? Я её вывел вручную и возможно она не верна, но даже если я не прав с формулой, то в любом случае при сложении двух плотностных чисел, в результирующем числе будет функция которая использует функции из слагаемых. Если в качестве функции активации нейронов использовать функцию y(x) = x, то даже в таком случае для обучения моим алгоритмом, нужно знать как складывать и перемножать 2 плотностных числа. С учётом того, что в результате сложения или перемножения двух плотностных чисел, формулы из 2-х исходных чисел будут храниться и в результирующем числе, то при увеличении количества нейронов и слоёв в сети, количество времени и памяти для обучения растёт экспоненциально. Однако есть одно но, для того чтобы узнать какая функция будет в результате сложения, необходимо знать только функции из слагаемых, т.е. в выражении [a1, b1, y1(x) = f1(x)] + [a2, b2, y2(x) = f2(x)] = [a3, b3, y3(x) = f3(x)], f3 не зависит от a1, b1, a2, b2, а зависит только от f1 и f2, если этим свойством обладает и произведение, то есть несколько оптимизаций с помощью которых можно значительно сократить скорость роста потребления памяти и времени во время обучения. Поскольку статья получилась и так не маленькой, а так же поскольку формулы для сложения и произведения пока не известны, рассказ об оптимизациях отложу на потом.

P. S. Достаточно быстро я остановил разработку компилятора для моего языка, о котором была моя прошлая статья. Кратко о том по чему я это сделал - https://cine-lang.blogspot.com/2020/02/blog-post.html. После прекращения разработки, я начал разработку нового языка, у которого отсутствуют недостатки предыдущего. На момент написания статьи я реализовал новый язык, компилятор и стандартную библиотеку на 80%. Мне очень хочется закончить новый вариант своего языка программирования, а так же реализовать хотя бы, несколько из своих идей (когда закончу ЯП, хочу попробовать реализовать свой алгоритм сжатия изображений, аудио и видео). Но в последнее время жизнь складывается не очень (как в прочем и у многих), из-за совокупности нескольких факторов (включая пандемию), я потерял работу и если осенью я хотя бы грибами питался, то сейчас иногда приходиться и ролтоны есть. Из за того, что в данный момент я много времени трачу на поиск работы (подработок) и иногда еды, а так же из-за психологического давления того факта, что у меня нет средств к существованию, разработка языка продвигается медленно. Если я бы смог фокусироваться на разработке, то компилятор и стандартную библиотеку я закончил бы, скорее всего к концу февраля. Поскольку моя обычная зарплата не превышает 200 долларов США в месяц, даже незначительная финансовая помощь поможет мне значительно. По этому, прошу помощи у всех не равнодушных (на хабре есть кнопка "Задонатить"). Благодарю всех откликнувшихся.

Подробнее..

Перевод Как экономить память и удваивать размеры моделей PyTorch с новым методом Sharded

07.01.2021 18:21:18 | Автор: admin
Модели глубокого обучения улучшаются с увеличением количества данных и параметров. Даже с последней моделью GPT-3 от Open AI, которая использует 175 миллиардов параметров, нам ещё предстоит увидеть плато роста количества параметров.

Для некоторых областей, таких как NLP, рабочей лошадкой был Transformer, который требует огромных объёмов памяти графического процессора. Реалистичные модели просто не помещаются в памяти. Последний метод под названием Sharded [букв. сегментированный] был представлен в Zero paper Microsoft, в котором они разработали метод, приближающий человечество к 1 триллиону параметров.

Специально к старту нового потока курса по Machine Learning, делюсь с вами статьей о Sharded в которой показывается, как использовать его с PyTorch сегодня для обучения моделей со вдвое большей памятью и всего за несколько минут. Эта возможность в PyTorch теперь доступна благодаря сотрудничеству между командами FairScale Facebook AI Research и PyTorch Lightning.





Для кого эта статья?


Эта статья предназначена для всех, кто использует PyTorch для обучения моделей. Sharded работает на любой модели, независимо от того, какую модель обучать: NLP (трансформатор), зрительную (SIMCL, swav, Resnet) или даже речевые модели. Вот моментальный снимок прироста производительности, который вы можете увидеть с помощью Sharded во всех типах моделей.



SwAV это современный метод контролируемого данными обучения в области компьютерного зрения.
DeepSpeech2 это современный метод для речевых моделей.
Image GPT передовой метод для визуальных моделей.
Трансформер передовой метод обработки естественного языка.

Как использовать Sharded вместе с PyTorch


Для тех, у кого не так много времени, чтобы прочитать интуитивно понятное объяснение о том, как работает Sharded, я сразу объясню, как использовать Sharded с вашим кодом PyTorch. Но призываю прочитать конец статьи, чтобы понять, как работает Sharded.

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

Самый простой способ зарядить ваш код с помощью Sharded это преобразовать вашу модель в PyTorch Lightning (это всего лишь рефакторинг). Вот 4-минутное видео, которое показывает, как преобразовать ваш код PyTorch в Lightning.



Как только вы это сделаете, включить Sharded на 8 графических процессорах будет так же просто, как изменить один флаг: не требуется никаких изменений в вашем коде.



Если ваша модель взята из другой библиотеки глубокого обучения, она всё равно будет работать с Lightning (NVIDIA Nemo, fast.ai, Hugging Face). Всё, что вам нужно сделать, это импортировать модель в LightningModule и начать обучение.

from argparse import ArgumentParserimport torchimport torch.nn as nnimport pytorch_lightning as plfrom pytorch_lightning.metrics.functional import accuracyfrom transformers import BertModelclass LitBertClassifier(pl.LightningModule):    def __init__(self, n_classes, pretrained_model_name='bert-base-uncased'):        super().__init__()        self.save_hyperparameters()        self.bert = BertModel.from_pretrained(pretrained_model_name)        self.drop = nn.Dropout(p=0.3)        self.out = nn.Linear(self.bert.config.hidden_size, n_classes)        self.loss_fn = nn.CrossEntropyLoss()    def forward(self, input_ids, attention_mask):        outputs = self.bert(            input_ids=input_ids,            attention_mask=attention_mask,            return_dict=False        )        pooled_output = outputs[1]        output = self.drop(pooled_output)        return self.out(output)    def training_step(self, batch, batch_idx):        loss, acc = self._shared_step(batch, batch_idx)        self.log("acc", acc)        return loss    def validation_step(self, batch, batch_idx):        _, acc = self._shared_step(batch, batch_idx)        self.log("val_acc", acc)    def _shared_step(self, batch, batch_idx):        input_ids = batch["input_ids"]        attention_mask = batch["attention_mask"]        targets = batch["targets"]        outputs = self.forward(            input_ids=input_ids,            attention_mask=attention_mask        )        _, preds = torch.max(outputs, dim=1)        loss = self.loss_fn(outputs, targets)        acc = accuracy(preds, targets)        return loss, acc    def configure_optimizers(self):        return torch.optim.AdamW(self.parameters(), lr=2e-5)if __name__ == '__main__':    # TODO: add your own dataset    train_dataloader = ...    val_dataloader = ...    bert = LitBertClassifier()    trainer = pl.Trainer(gpus=8, plugins='ddp_sharded')    trainer.fit(bert, train_dataloader)

Интуитивно понятное объяснение работы Sharded


Для эффективного обучения на большом количестве графических процессорах применяются несколько подходов. В одном подходе (DP) каждый пакет делится между графическими процессорами. Вот иллюстрация DP, где каждая часть пакета отправляется на другой графический процессор, а модель многократно копируется на каждый из них.


Обучение DP

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

В другом методе (параллельное распределение данных, DDP) каждый графический процессор обучается на подмножестве данных и градиенты синхронизируются между графическими процессорами. Этот метод также работает на многих машинах (узлах). На этом рисунке каждый графический процессор получает подмножество данных и инициализирует одинаковые веса модели для всех графических процессоров. Затем, после обратного прохода, все градиенты синхронизируются и обновляются.


Параллельное распределение данных

Однако в этом методе всё ещё есть проблема, которая заключается в том, что каждый графический процессор должен поддерживать копию всех состояний оптимизатора (примерно в 23 раза больше параметров модели), а также всех прямых и обратных активаций.

Sharded устраняет эту избыточность. Он работает так же, как DDP, за исключением того, что все накладные расходы (градиенты, состояние оптимизатора и т. д.) вычисляются только для части полных параметров, и, таким образом, мы устраняем избыточность хранения одного и того же градиента и состояний оптимизатора на всех графических процессорах. Иначе говоря, каждый графический процессор хранит только подмножество активаций, параметров оптимизатора и вычислений градиента.

Использование какого-либо распределённого режима




В PyTorch Lightning переключение режимов распределения тривиально.

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

Хорошая новость в том, что все эти режимы доступны в PyTorch Lightning без необходимости изменять код. Вы можете попробовать любой из них и при необходимости отрегулировать его для вашей конкретной модели.

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

Преимущество использования Lightning в том, что вы никогда не отстанете от последних достижений в области исследований искусственного интеллекта! Команда и сообщество ПО с открытым исходным кодом с помощью Lightning стремятся поделиться с вами последними достижениями.

image



Подробнее..

Самообучение в Data science, с нуля до Senior за два года

08.01.2021 18:05:33 | Автор: admin

Хочу поделиться методами освоения Data science с нуля человеком из другой ИТ специальности. Цель: дать понять, подходит ли Вам эта специальность в принципе, и рассказать про эффективные подходы к самообучению, которые мне помогли (отдельно планирую потом детальные статьи по отдельным темам).

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

И важно сказать сразу: я верю, что любой человек с аналитическими способностями и структурным мышлением может стать специалистом по машинному обучению/data science. Еще 4 года назад я сомневался, потеряв веру в свои математические способности из-за преподавателей университета. Теперь верю: основы машинного обучения и минимально необходимую математику сможет выучить любой сильно замотивированный человек.

Мой опыт:

  • Когда я понял, что скоро мне стукнет 30 лет, решил уйти в другую сферу и переехать из РФ. В своей сфере (1С) я был карьерно успешен, но стало ясно, что дальнейший рост очень затруднителен и требует выполнять работу, которая мне неинтересна и почти противна.

  • Через полгода перебора вариантов решил, что Data science мне интереснее всего.

  • Ещё через год имел достаточную квалификацию и прошёл собеседование на работу в Чехии (оговорка: у меня еще до этого было неплохое знание английского).

  • Ещё через год стал Senior Data scientist в Vodafone (мой LinkedIn).

Цель - учиться эффективнее и бесплатно

Мне помогло то, что до этого я сформировал привычки к самообразованию, а экономность не позволила мне пойти по самому простому пути: найти онлайн курс с именитыми преподами, заплатить им много денег и довериться, что они всему научат лучше всего. В итоге я перебирал много бесплатно доступных книг и курсов (книги часто были найдены на b-ok.org). Из всех курсов и книг отбирал самые лучшие, забрасывая то, что казалось слишком теоретизированными или плохо структурированным.

На основе этих десятков книг и курсов я и сформировал то мнение, которым хочу поделить. Вероятно, существует еще более эффективный и быстрый способ научится этому всему. То, как учился я, было всего-лишь быстреее большинства платных программ, которые я видел, и заодно бесплатным (на многие лучшие англоязычные курсы всегда можно записаться бесплатно; покупал я только книги русских авторов и пару книг, которые иначе не смог найти).

Сначала надо понять, что такое Data science/машинное обучение и подойдет ли оно вам

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

Какие альтернативы:

Возможно, вы технарь-интроверт, желающий делать что-то своими руками и не желающий много общаться с другими людьми или вникать в бизнес (потому что DS очень прикладная штука, требующая погруженная в предметную область). Тогда есть варианты: или "просто программирование" вам будет интереснее (Не хочется разрабатывать сайты? - Нужны разработчики бэкенд приложений и дата-инженеры, в больших количествах), или если всё-таки хочется заниматься машинным обучением, то изучать все методы data science и знать их лучше всех, чтобы пойти сразу в более крупную компанию, где достаточно чисто-технических задач.

Если вы человек творческий, возможно, разработка интерфейсов (фронтенд, мобильные приложения) вам подойдёт больше.

Если вы от природы аналитик и любите разбираться в данных, но программирование вас не заинтересует, а на изучение всей математики вам не хватает времени, стоит выбрать тот же самый учебный путь! Просто сделать акцент на мнее математических задачах, и не лезть в программироване сложных систем. Аналитики, знающие основы data science, тоже нужны в компаниях.

Важно, чтобы работа зажигала. Без искреннего интереса "грызть" Data science будет тяжело, потому что надо разобраться в куче нюансов, особенно если у вас нет за плечами хороших знаний в статистике, линейной алгебре и мат.анализе.

Как понять, будет ли вам интересно заниматься именно data science?

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

Мне кажется, что идеально эту роль выполняет книга Datasmart (выше писал сайт, на котором я нашёл её бесплатно). На русский она тоже переведена: "Много цифр. Анализ больших данных при помощи Excel, Джон Форман". Хотя, если вы хотите работать в data science, знание английского необходимо (технический английский выучить намного легче разговорного, и это будет очень полезно для любой работы в ИТ).

Эта книга показывет многие из технических методов Data science на уровне интуиции и даёт сразу достаточно детальное представление о решаемых задачах и где в бизнесе можно применить данные модели.

Если эта книга не вызовет интерес разобраться во всех указанных алгоритмах детальнее, вероятно, работа в data science не для вас.

Если книга интересн вызовет, но вам также хочется больше программировать, скорее всего, вам интересно будет стать machine learning engineer. Разница между data scientist и machine learning engineer в том, что первый должен общаться с людьми и понимать, какую задачу имеет смысл решать, а второй должен уметь состыковать программы с "искусственным интеллектом" с другими ИТ системами, мобильными телефонами или требованиями обрабатывать огромные объемы данных.

Кстати, подобная книга для тех, кто хочет понять стоит ли ему заниматься визуализацией данных (PowerBI, Tableau и т.п.) - "Storytelling with data". Если эта книга тоже вдохновила, вместе с предыдущей, вероятно вы data scientist, способный выполнять и роль аналитика. Если же заниматься объяснением данных вам неинтересно, вам стоит нацелеваться на позицию machine learning engineer или подумать, не легче ли быть "обычным" программистом.

Что учить

Если вы решили, что готовы "грызть гранит науки", то в образовании специалиста data science есть два кита:

  • Непосредственные методы Data science, которые стоят на трёх математических черепахах: теории вероятностей и статистике, линейной алгебре и основах мат.анализа (только основах, там требуется минимум сверх школьного курса "алегбра и начало анализа"). Кстати, вся эта математика далеко не так сложна. Проблема в том, что её плохо и неинтересно объясняют во многих вузах. Позже поделюсь советами, как её можно легче освоить.

  • Программирование на Python (+SQL и подобные), которое позволит применить все изученные методы с помощью логичных и простых в своей сути библиотек готовых функций.

    Каждый data scientist немного программист. При этом именно python является стандартом де-факто для нашей сферы. Вероятно, этот язык занял своё положение благодаря тому, что он очень простой и логичный. Если вы программировали на чём угодно, и слова "цикл" или "if-then-else" вас не пугают, то вам не будет очень сложно освоить Пайтон. Если вы никогда не программировали, но считаете, что структурное и математическое мышление - ваш конёк, с программированием у вас не будет проблем. Даже если вы "конченный гуманитарий", освоить Python значительно легче, чем выучить многие иностранные языки (но, внимание! для людей без предыдущего опыта программирования обучаться ему эффективнее по-другому, не так, как для тех, кто уже имеет опыт программирования)

Даже примерный учебный пландля изучения методов Data science требует отдельного поста. Ниже напишу чуть подробнее про Python и SQL

Английский необходим!

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

Принципы эффективного обучения

  1. Эффективный учебный план. Хороший план позволяет вам учить вещи в таком порядке, чтобы каждая новая вещь базировалась на уже полученных знаниях. И, в идеале, он идёт по спирали, постепенно углубляя знания во всех аспектах. Потому что учить теоретически математику, без интересных примеров применения - неэффективно. Именно это является одной из проблем плохого усваивания материалов в школе и институте.

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

  2. Следует концентрироваться на понимании главных принципов - это легче, чем запоминать отдельные детали (они часто оказываются не нужны). Особенно важно это становится, когда вы учите язык программирования, тем более свой первый: не стоит зубрить правильное написание команд ("синтаксис") или заучивать API библиотек.

    Это вторая вещь, с которой я хочу помочь - разобраться, что важно, а на что не следует тратить много времени.

Программирование: что и как учить?

Что такое SQL и зачем его учить?

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

SQL простой, потому что он "декларативный": нужно точно описать "запрос" как должен выглядеть финальный результат, и всё! - база данных сама покажет вам данные в нужной форме. В обычных "императивных" языках программирования нужно описывать шаги, как вы хотите чтобы компьютер выполнил вашу инструкцию. C SQL намного легче, потому что достаточно только точно понять что вы хотите получить на выходе.

Сам язык программирования - это ограниченный набор команд.

Когда вы будете работать с данными - даже аналитиком, даже необязательно со знанием data science, - самой первой задачей всегда будет получить данные из базы данных. Поэтому SQL надо знать всем. Даже веб-аналитики и маркетологи зачастую его используют.

Как учить SQL:

Наберите в Гугле "sql tutorial" и начните учиться по первой же ссылке. Если она вдруг окажется платной, выберете другую. По SQL полно качественных бесплатных курсов.

На русском языке тоже полно курсов. Выбирайте бесплатные.

Главное - выбирайте курсы, в которых вы можете сразу начать прямо в браузере пробовать писать простейшие запросы к данным. Только так, тренируясь на разных примерах, действительно можно выучить SQL.

На изучение достаточно всего лишь от 10 часов (общее понимание), до 20 часов (уверенное владение большей частью всего необходимого).

Почему именно Python?

В первую очередь, зачем учить Python. Возможно, вы слышали что R (другой популярный язык программирования) тоже умеет очень многое, и это действительно так. Но Python намного универсальнее. Мало сфер и мест работы, где Python вам не сможет заменить R, но в большинстве компаний, где Data Science можно делать с помощью Python, у вас возникнут проблемы при попытке использования R. Поэтому - точно учите Python. Если вы где-то услышите другое мнение, скорее всего, оно устарело на несколько лет (в 2015г было совершенно неясно какой язык перспективнее, но сейчас это уже очевидно).

У всех других языков программирования какие-либо специализированные библиотеки для машинного обучения есть только в зачаточном состоянии.

Как учить Python

Основы:

http://pythontutor.ru/

Прочитать основы и пройти все упражнения с этого сайта можно за 5-40 часов, в зависимости от вашего предыдущего опыта.

После этого варианты (все эти книги есть и на русском):

  1. Learning Python, by Mark Lutz (5 издание). Существует и на русском.

    Есть много книг, которые сразу обучают использованию языка в практических задачах, но не дают полного представления о детальных возможностях языка.

    Эта книга, наоборот, разбирает Python досконально. Поэтому по началу её чтение будет идти медленнее, чем аналоги. Но зато, прочтя её, вы будете способны разобраться во всём.

    Я прочёл её почти целиком в поездах в метро за месяц. А потом сразу был готов писать целые программы, потому что самые основы были заложены в pythontutor.ru, а эта книга детально разжевывает всё.

    В качестве практики берите, что угодно, когда дочитаете эту книгу до 32 главы, и решайте реальные примеры (кстати, главы 21-31 не надо стараться с первого раза запоминать детально. Просто пробежите глазами, чтобы вы понимали что вообще Python умеет).

    Не надо эту книгу (и никакую другую) стараться вызубрить и запомнить все детали сразу. Просто позже держите её под рукой и обращайтесь к ней при необходимости.

    Прочитав эту книгу, и придя на первую работу с кучей опытных коллег, я обнаружил, что некоторые вещи знаю лучше них.

  2. Python Crash Course, byEric Matthes

    Эта книга проще написана и отсеивает те вещи, которые всё-таки реже используются. Если вы не претендуете быстрее стать высоко-классным знатоком Python - её будет достаточно.

  3. Automate the Boring Stuff with Python

    Книга хороша примерами того, что можно делать с помощью Python. Рекомендую просмотреть их все, т.к. они уже похожи на реальные задачи, с которыми приходится сталкиваться на практике, в том числе специалисту по анализу данных.

Какие трудозатраты?

Путь с нуля до уровня владения Python, на котором я что-то уже мог, занял порядка 100ч. Через 200ч я уже чувствовал себя уверенно и мог работать над проектом вместе с коллегами.

(есть бесплатные программы - трекеры времени, некоторым это помогает для самоконтроля)

Пожалуйста, дайте обратную связь

Если это было кому-то полезно (а не просто помогло убить время), поставьте лайк. Можете написать на адрес ниже пожелания и рекомендации. Буду стараться быть полезным большинству.

Для желающих могу выступить в роли ментора

Посчитав, что мой опыт самообучения эффективен и быстрый рост (с нуля до синьора за 2 года) это доказывает, год назад я вписался в программу менторства родного университета (Высшая школа экономики). За год помог нескольким студентам не-ИТ факультетов начать изучать DS и ИТ вообще или выбрать более эффективные пути самообучения.

Помогать другим учиться мне нравится с детства, когда хотел, как папа, стать учителем информатики. Поэтому решил сделать это своим хобби: если вам нужен ментор, чтобы освоить DS эффективнее - обращайтесь (кстати, могу научить как стать 1С экспертом и руководителем проектов, если вдруг это окажется интереснее кому-то читающему эту статью).

Буду раду помочь, пишите на адрес почты ниже.

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

self.development.mentor в домене gmail.com, Олег

Подробнее..

Перевод Анимации градиентного спуска и ландшафта потерь нейронных сетей на Python

10.01.2021 14:07:00 | Автор: admin
Во время изучения различных алгоритмов машинного обучения я наткнулся на ландшафт потерь нейронных сетей с их горными территориями, хребтами и долинами. Эти ландшафты потерь сильно отличались от выпуклых и гладких ландшафтов потерь, с которыми я столкнулся при использовании линейной и логистической регрессий. Здесь мы создадим ландшафты потерь нейронных сетей и анимированного градиентного спуска с помощью датасета MNIST.


Рисунок 1 Ландшафт потерь свёрточной нейронной сети с 56 слоями (VGG-56, источник)



На приведённом выше изображении показан ландшафт нейронной сети с высокой степенью поверхностных потерь. Ландшафт потерь это визуальное представление значений, которые функция стоимости берёт на себя для заданного диапазона значений параметров с учётом наших тренировочных данных. Поскольку нашей целью является визуализация затрат в трёх измерениях, необходимо выбрать два конкретных параметра, которые будут варьироваться в наших графиках, тогда как все остальные параметры модели остаются неизменными. Стоит, однако, отметить, что существуют более продвинутые методы (например, уменьшение размерности, нормализация фильтра), которые могут использоваться для приближения ландшафтов потерь нейронной сети в подпространстве с низкой размерностью. Трёхмерное представление ландшафта потерь нейронной сети VGG с 56 слоями показано на рисунке 1. Однако это выходит за рамки данной статьи.

Искусственная нейронная сеть, с которой мы будем работать, состоит из одного входного слоя (с 784 узлами), двух скрытых слоёв (с 50 и 500 узлами соответственно) и одного выходного слоя (с 10 узлами). Мы будем повсеместно использовать сигмовидную функцию в качестве функции активации. Нейронная сеть не будет подвержена предвзятости. Обучающие данные состоят из изображений 28x28 пикселей, рукописных цифр в диапазоне от 0 до 9 из набора данных MNIST. Технически мы могли бы выбрать любой из 784*50+50*500+500*10=69,200 весов, которые мы используем в нашей нейронной сети. Я произвольно решил использовать веса w250, 5 (2) и w251,5(2), которые соединяют 250-й и 251-й узлы второго скрытого слоя с 6-м выходным нейроном соответственно. В нашей модели 6-й выходной нейрон возвращает активацию для модели, прогнозируя наличие цифры 5 на изображении. На рисунке 2 схематично показана архитектура нейронной сети, с которой мы будем работать. Из соображений ясности некоторые связи между нейронами и большая часть весовых аннотаций были намеренно опущены.


Рисунок 2 Архитектура нейронной сети

Мы импортируем MNIST в скрипт на Python. Рукописные цифры набора данных MNIST представлены в виде изображений в оттенках серого, поэтому мы можем нормализовать входные данные путём масштабирования значений пикселей из диапазона 0-255 в диапазон 0-1,2 в нашем коде, следовательно, мы делим x-значения на 255.

# Import librariesimport numpy as npimport gzipfrom sklearn.preprocessing import OneHotEncoderimport matplotlib.pyplot as pltfrom mpl_toolkits.mplot3d import Axes3Dfrom scipy.special import expitimport celluloidfrom celluloid import Camerafrom matplotlib import animation # Open MNIST-files: def open_images(filename):    with gzip.open(filename, "rb") as file:             data=file.read()        return np.frombuffer(data,dtype=np.uint8, offset=16).reshape(-1,28,28).astype(np.float32) def open_labels(filename):    with gzip.open(filename,"rb") as file:        data = file.read()        return np.frombuffer(data,dtype=np.uint8, offset=8).astype(np.float32)     X_train=open_images("C:\\Users\\tobia\\train-images-idx3-ubyte.gz").reshape(-1,784).astype(np.float32) X_train=X_train/255 # rescale pixel values to 0-1y_train=open_labels("C:\\Users\\tobia\\train-labels-idx1-ubyte.gz")oh=OneHotEncoder(categories='auto') y_train_oh=oh.fit_transform(y_train.reshape(-1,1)).toarray() # one-hot-encoding of y-values

Чтобы создать ландшафты потерь, создадим график поверхности стоимости по отношению к вышеупомянутым весам w_250, 5(2) и w_251,5(2). Для этого мы определим среднеквадратичную функцию стоимости ошибки по отношению к весам w_a и w_b. Стоимости нашей модели J эквивалентны усреднённой сумме квадратичных ошибок между прогнозом модели и фактическим значением каждого из 10 выходных нейронов нашего обучающего набора данных с размером N:



С y и pred, представляющим матрицы фактических и прогнозируемых значений y соответственно. Прогнозируемые значения вычисляются путём прямого распространения входных данных через нейронную сеть на конечный слой. Вывод каждого слоя служит входными данными для следующего слоя. Входная матрица умножается на весовую матрицу соответствующего слоя. После этого сигмоидная функция применяется для получения выходных данных этого конкретного слоя. Весовые матрицы инициализируются малыми случайными числами с помощью генератора псевдослучайных чисел numpy. С помощью seed мы гарантируем воспроизводимость результатов. После этого подставляем два веса, которые могут изменяться в зависимости от аргументов функции w_a и w_b. Мы разработали функцию затрат на Python следующим образом:

hidden_0=50 # number of nodes of first hidden layerhidden_1=500 # number of nodes of second hidden layer# Set up cost function:def costs(x,y,w_a,w_b,seed_):          np.random.seed(seed_) # insert random seed         w0=np.random.randn(hidden_0,784)  # weight matrix of 1st hidden layer        w1=np.random.randn(hidden_1,hidden_0) # weight matrix of 2nd hidden layer        w2=np.random.randn(10,hidden_1) # weight matrix of output layer        w2[5][250] = w_a # set value for weight w_250,5(2)        w2[5][251] = w_b # set value for weight w_251,5(2)        a0 = expit(w0 @ x.T)  # output of 1st hidden layer        a1=expit(w1 @ a0)  # output of 2nd hidden layer        pred= expit(w2 @ a1) # output of final layer        return np.mean(np.sum((y-pred)**2,axis=0)) # costs w.r.t. w_a and w_b

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

# Set range of values for meshgrid: m1s = np.linspace(-15, 17, 40)   m2s = np.linspace(-15, 18, 40)  M1, M2 = np.meshgrid(m1s, m2s) # create meshgrid # Determine costs for each coordinate in meshgrid: zs_100 = np.array([costs(X_train[0:100],y_train_oh[0:100].T                                 ,np.array([[mp1]]), np.array([[mp2]]),135)                         for mp1, mp2 in zip(np.ravel(M1), np.ravel(M2))])Z_100 = zs_100.reshape(M1.shape) # z-values for N=100zs_10000 = np.array([costs(X_train[0:10000],y_train_oh[0:10000].T                                 ,np.array([[mp1]]), np.array([[mp2]]),135)                         for mp1, mp2 in zip(np.ravel(M1), np.ravel(M2))])Z_10000 = zs_10000.reshape(M1.shape) # z-values for N=10,000# Plot loss landscapes: fig = plt.figure(figsize=(10,7.5)) # create figureax0 = fig.add_subplot(121, projection='3d' )ax1 = fig.add_subplot(122, projection='3d' )fontsize_=20 # set axis label fontsizelabelsize_=12 # set tick label size# Customize subplots: ax0.view_init(elev=30, azim=-20)ax0.set_xlabel(r'$w_a$', fontsize=fontsize_, labelpad=9)ax0.set_ylabel(r'$w_b$', fontsize=fontsize_, labelpad=-5)ax0.set_zlabel("costs", fontsize=fontsize_, labelpad=-30)ax0.tick_params(axis='x', pad=5, which='major', labelsize=labelsize_)ax0.tick_params(axis='y', pad=-5, which='major', labelsize=labelsize_)ax0.tick_params(axis='z', pad=5, which='major', labelsize=labelsize_)ax0.set_title('N:100',y=0.85,fontsize=15) # set title of subplot ax1.view_init(elev=30, azim=-30)ax1.set_xlabel(r'$w_a$', fontsize=fontsize_, labelpad=9)ax1.set_ylabel(r'$w_b$', fontsize=fontsize_, labelpad=-5)ax1.set_zlabel("costs", fontsize=fontsize_, labelpad=-30)ax1.tick_params(axis='y', pad=-5, which='major', labelsize=labelsize_)ax1.tick_params(axis='x', pad=5, which='major', labelsize=labelsize_)ax1.tick_params(axis='z', pad=5, which='major', labelsize=labelsize_)ax1.set_title('N:10,000',y=0.85,fontsize=15)# Surface plots of costs (= loss landscapes):  ax0.plot_surface(M1, M2, Z_100, cmap='terrain', #surface plot                             antialiased=True,cstride=1,rstride=1, alpha=0.75)ax1.plot_surface(M1, M2, Z_10000, cmap='terrain', #surface plot                             antialiased=True,cstride=1,rstride=1, alpha=0.75)plt.tight_layout()plt.show()


Рисунок 3 Ландшафты с различными размерами образцов

На рисунке 3 показаны два примерных ландшафта потерь с одинаковыми весами (w_250, 5 (2) и w_251,5(2)) и одинаковыми случайными начальными весами. Левый участок поверхности создавался с помощью первых 100 изображений набора данных MNIST, в то время как участок справа был создан с помощью первых 10 000 изображений. Если мы присмотримся к левому графику, то увидим некоторые типичные черты невыпуклых ландшафтов потерь: локальные минимумы, плато, хребты (иногда также называемые седловыми точками) и глобальный минимум. Однако термин минимум следует использовать с осторожностью, поскольку мы видим только заданный диапазон значений, вместе с тем не проводился тест первой производной.


Рисунок 4

Градиентный спуск


Эти географические барьеры резко контрастируют с гладкими и выпуклыми ландшафтами потерь, которые можно увидеть в линейной и логистической регрессиях. Считается, что эти барьеры замедляют достижение глобального минимума и даже препятствуют этому, а следовательно, негативно влияют на производительность модели [3]. Для исследования явления я решил анимировать градиентный спуск с этим конкретным ландшафтом потерь и тремя характерными начальными точками. Градиентный спуск в основном компрометирует обновление параметров модели (например, весов) в соответствии со следующим уравнением:



где J градиент нашей функции стоимости, w вес всей модели, e соответствующая эпоха и скорость обучения.

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



где w определяется как вес между j-м узлом слоя до и i-м узлом текущего слоя, который является выходным слоем в нашем случае. Вход i-го нейрона в выходном слое просто обозначается как in () и эквивалентен сумме активаций слоя до умножения на их соответствующие веса соединения, ведущие к этому узлу. Выход * i*-го нейрона в выходном слое обозначается как out () и соответствует (in ()). Решая уравнение выше, мы получаем:



с * out (), соответствующим активации j-го узла в слое, перед которым в выходном слое через w. соединен с n-м узлом. Переменная target обозначает целевой вывод для каждого из 10 выходных нейронов. Ссылаясь на рисунок 2, out () будет соответствовать активации h или h, в зависимости от того веса, от которого мы намереваемся вычислить частную производную. Отличное объяснение, включая подробный математический вывод, можно найти здесь [4].

Поскольку вывод нейронов выходного слоя эквивалентен прогнозу нейронной сети, в следующем коде воспользуемся более удобной аббревиатурой 'PRE'. Поскольку строгая фокусировка на конкретном узле может привести к путанице в коде, мы стремимся придерживаться установленного принципа использования весовых матриц и умножения матриц для обновления всех весов в выходном слое сразу. Наконец, мы обновим только два конкретных веса выходного слоя, которые собираемся обновить на самом деле. На Python мы реализуем алгоритм градиентного спуска только для двух весов вот так:

# Store values of costs and weights in lists: weights_2_5_250=[] weights_2_5_251=[] costs=[] seed_= 135 # random seedN=100 # sample size # Set up neural network: class NeuralNetwork(object):    def __init__(self, lr=0.01):        self.lr=lr        np.random.seed(seed_) # set random seed        # Intialize weight matrices:         self.w0=np.random.randn(hidden_0,784)          self.w1=np.random.randn(hidden_1,hidden_0)        self.w2=np.random.randn(10,hidden_1)        self.w2[5][250] = start_a # set starting value for w_a        self.w2[5][251] = start_b # set starting value for w_b        def train(self, X,y):        a0 = expit(self.w0 @ X.T)          a1=expit(self.w1 @ a0)          pred= expit(self.w2 @ a1)        # Partial derivatives of costs w.r.t. the weights of the output layer:         dw2= (pred - y.T)*pred*(1-pred)  @ a1.T / len(X)   # ... averaged over the sample size        # Update weights:         self.w2[5][250]=self.w2[5][250] - self.lr * dw2[5][250]         self.w2[5][251]=self.w2[5][251] - self.lr * dw2[5][251]         costs.append(self.cost(pred,y)) # append cost values to list        def cost(self, pred, y):        return np.mean(np.sum((y.T-pred)**2,axis=0))    # Initial values of w_a/w_b: starting_points = [  (-9,15),(-10.1,15),(-11,15)] for j in starting_points:    start_a,start_b=j    model=NeuralNetwork(10) # set learning rate to 10    for i in range(10000):  # 10,000 epochs                    model.train(X_train[0:N], y_train_oh[0:N])         weights_2_5_250.append(model.w2[5][250]) # append weight values to list        weights_2_5_251.append(model.w2[5][251]) # append weight values to list# Create sublists of costs and weight values for each starting point: costs = np.split(np.array(costs),3) weights_2_5_250 = np.split(np.array(weights_2_5_250),3)weights_2_5_251 = np.split(np.array(weights_2_5_251),3)

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

fig = plt.figure(figsize=(10,10)) # create figureax = fig.add_subplot(111,projection='3d' ) line_style=["dashed", "dashdot", "dotted"] #linestylesfontsize_=27 # set axis label fontsizelabelsize_=17 # set tick label fontsizeax.view_init(elev=30, azim=-10)ax.set_xlabel(r'$w_a$', fontsize=fontsize_, labelpad=17)ax.set_ylabel(r'$w_b$', fontsize=fontsize_, labelpad=5)ax.set_zlabel("costs", fontsize=fontsize_, labelpad=-35)ax.tick_params(axis='x', pad=12, which='major', labelsize=labelsize_)ax.tick_params(axis='y', pad=0, which='major', labelsize=labelsize_)ax.tick_params(axis='z', pad=8, which='major', labelsize=labelsize_)ax.set_zlim(4.75,4.802) # set range for z-values in the plot# Define which epochs to plot:p1=list(np.arange(0,200,20))p2=list(np.arange(200,9000,100))points_=p1+p2camera=Camera(fig) # create Camera objectfor i in points_:    # Plot the three trajectories of gradient descent...    #... each starting from its respective starting point    #... and each with a unique linestyle:    for j in range(3):         ax.plot(weights_2_5_250[j][0:i],weights_2_5_251[j][0:i],costs[j][0:i],                linestyle=line_style[j],linewidth=2,                color="black", label=str(i))        ax.scatter(weights_2_5_250[j][i],weights_2_5_251[j][i],costs[j][i],                   marker='o', s=15**2,               color="black", alpha=1.0)    # Surface plot (= loss landscape):    ax.plot_surface(M1, M2, Z_100, cmap='terrain',                              antialiased=True,cstride=1,rstride=1, alpha=0.75)    ax.legend([f'epochs: {i}'], loc=(0.25, 0.8),fontsize=17) # set position of legend    plt.tight_layout()     camera.snap() # take snapshot after each iteration    animation = camera.animate(interval = 5, # set delay between frames in milliseconds                          repeat = False,                          repeat_delay = 0)animation.save('gd_1.gif', writer = 'imagemagick', dpi=100)  # save animation   


Рисунок 5 Траектории градиентного спуска

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

fig = plt.figure(figsize=(10,10)) # create figureax0=fig.add_subplot(2, 1, 1) ax1=fig.add_subplot(2, 1, 2) # Customize subplots: ax0.set_xlabel(r'$w_a$', fontsize=25, labelpad=0)ax0.set_ylabel(r'$w_b$', fontsize=25, labelpad=-20)ax0.tick_params(axis='both', which='major', labelsize=17)ax1.set_xlabel("epochs", fontsize=22, labelpad=5)ax1.set_ylabel("costs", fontsize=25, labelpad=7)ax1.tick_params(axis='both', which='major', labelsize=17)contours_=21 # set the number of contour linespoints_=np.arange(0,9000,100) # define which epochs to plotcamera = Camera(fig) # create Camera objectfor i in points_:    cf=ax0.contour(M1, M2, Z_100,contours_, colors='black', # contour plot                     linestyles='dashed', linewidths=1)    ax0.contourf(M1, M2, Z_100, alpha=0.85,cmap='terrain') # filled contour plots         for j in range(3):        ax0.scatter(weights_2_5_250[j][i],weights_2_5_251[j][i],marker='o', s=13**2,               color="black", alpha=1.0)        ax0.plot(weights_2_5_250[j][0:i],weights_2_5_251[j][0:i],                linestyle=line_style[j],linewidth=2,                color="black", label=str(i))                ax1.plot(costs[j][0:i], color="black", linestyle=line_style[j])    plt.tight_layout()    camera.snap()    animation = camera.animate(interval = 5,                          repeat = True, repeat_delay = 0)  # create animation animation.save('gd_2.gif', writer = 'imagemagick')  # save animation as gif


Рисунок 6 Траектории градиентного спуска в 2D

Обе анимации показывают, что градиентный спуск может застревать в локальных минимумах, седловых точках или плато с невыпуклыми ландшафтами потерь. Для преодоления некоторых из этих препятствий были реализованы многочисленные варианты градиентного спуска (ADAGRAD, Adam и др.). Однако я хотел бы прояснить, что не все ландшафты потерь настолько невыпуклые в определённом диапазоне значений для w_a и w_b. Выпуклость ландшафта потерь зависит, среди прочего, от количества скрытых слоев, при этом глубокие нейронные сети приводят к сильно невыпуклым ландшафтам потерь.

Я решил создать несколько произвольных ландшафтов потерь, перебирая случайные начальные значения для генерации. На этот раз веса, которые могут изменяться, находятся на втором скрытом уровне (код). Репрезентативный образец ландшафтов потерь, с которыми я столкнулся при таком подходе, можно увидеть ниже. Размер выборки и индексы весов, представленных w_a и w_b, указаны соответственно в заголовках.


Рисунок 7 N=500, w20030(1), w20031(1) (создано автором, код)


Рисунок 8 N=1000, w55(1), w56(1) (создано автором, код)

Визуализация ландшафта потерь может быть полезна для лучшего понимания лежащей в основе теории и потенциальных недостатков различных алгоритмов оптимизации. Однако на практике актуальность локальных минимумов, плато или хребтов всё ещё обсуждается. Некоторые авторы утверждают, что локальные минимумы очень редки в многомерных пространствах и что седловые точки могут быть даже более проблематичными, чем локальные минимумы в отношении оптимизации параметров. Другие авторы даже предполагают, что минимизация затрат, связанная с локальными минимумами, является достаточной и может предотвратить переобучение [7].

Я надеюсь, что вам понравилось! Полный код Jupyter Notebook можно найти на моём GitHub.

Приложение



Представленное изображение

Ссылки
База данных MNIST

  1. Li, Hao, et al. Visualizing the loss landscape of neural nets. Advances in neural information processing systems. 2018.
  2. Как нормализовать, центрировать и стандартизировать пиксели изображения в Keras
  3. Почему сложно обучать нейронную сеть
  4. Пример пошагового обратного распространения ошибки
  5. Staib, Matthew & J. Reddi, Sashank & Kale, Satyen & Kumar, Sanjiv & Sra, Suvrit. (2019). Escaping Saddle Points with Adaptive Gradient Methods.
  6. Dauphin, Yann et al. Identifying and attacking the saddle point problem in high-dimensional non-convex optimization. NIPS (2014).
  7. Choromanska, A., Henaff, M., Mathieu, M., Arous, G. B., & LeCun, Y. (2015). The loss surfaces of multilayer networks. Journal of Machine Learning Research, 38, 192204.


image



Подробнее..

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

11.01.2021 18:14:50 | Автор: admin


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

MuZero


DeepMind неожиданно опубликовали статью о MuZero, алгоритме, который способен играть как в популярные логические настольные игры вроде шахмат, Сёги и Го, так и в видеоигры Atari вроде Pac-Man.

MuZero пытается смоделировать не всю среду, а только отдельные аспекты, которые важны для процесса принятия стратегических решений агентом. Алгоритм постоянно собирает информацию о текущем и предыдущем состоянии игры изучая таким образом запреты и награды. Так, например, модель понимает, что в шахматах цель игры поставить мат, а в пакмане съесть желтую точку.

Есть еще одно важное преимущество: MuZero многократно использует изученную модель для улучшения планирования, а не для сбора новых данных о среде. Например, в играх Atari со сложной изменяющейся средой алгоритм использовал изученную модель в 90% случаев чтобы перепланировать то, что должно было быть сделано в прошлых игровых сессиях.

Почему это важно. По сути, MuZero модель общего назначения, которую можно будет применять для решения сложных проблем реального мира, которые трудно свести к простым правилам. DeepMind предлагают такую аналогию новый подход похож на то, как человек в пасмурную погоду решает взять зонтик, чтобы остаться сухим, в то время как прежние подходы пытались бы смоделировать, в каком порядке будут падать капли дождя.



Infinite Nature


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

Задача очень непростая, так как нужно генерировать новые изображения, которые могут сильно отличаться от вводных данных на фото часто присутствуют деревья и скалы, которые заслоняют располагающиеся за ними фрагменты ландшафта.

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



Time Travel Rephotography


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



pi-GAN


Еще одна GAN-модель, которая генерирует 3D представление объекта из нескольких неразмеченных двухмерных изображений. В демо показано, как модель можно использовать для вращения головы, подобно тому как ранее демонстрировали Nvidia в Maxine.



Neural Scene Flow Fields


Новый NeRf-метод, который строит представление динамической сцены из видео, снятого на обычную камеру. Это позволяет, например, остановить кадр и сдвинуть камеру или наоборот зафиксировать камеру, но будто перемотать время. Алгоритм дорисовывает окружение со сложной структурой, например, с тонкими объектами вроде решеток, и движущимися объектами вроде мыльных пузырей.



YolactEdge


Первый метод мгновенной сегментации изображений, который в режиме реального времени работает на слабых устройствах. Исходный код уже доступен.

ModNet


Технология, которая позволяет не только качественно удалять фон с портретных снимков, но также заменять фон на видео. По факту это может стать неплохой заменой хромакея. В отличие от платного remove.bg, здесь есть и исходный код, коллаб и даже веб-приложение с простеньким интерфейсом, в котором пока можно протестировать только работу с фото.

Svoice


Facebook наконец опубликовала исходники алгоритма, который определяет на аудиозаписи голоса нескольких говорящих людей.


Hypersim


Apple опубликовали датасет с сегментационными масками для искусственно созданных сцен. Почти два терабайта рендеров помещений в сверхвысоком разрешении. Маркировка данных здесь на уровне отдельных пикселей.

ArtLine


Открытая модель, которая превращает фотопортрет в карандашный набросок. Пока что плохо справляется с текстурами одежды и с тенями, но в целом выдает достойные результаты. За основу взята архитектура DeOldify, которая позволяет хорошо распознавать черты лица.

На этом все, вот таким на удивление насыщенным оказался декабрь. Начало года тоже обещает быть интересным. Нам уже не терпится посмотреть, что в январе появится на основе Dall-E от OpenAI. Как говорится, stay tuned!
Подробнее..

Распознавание Ворониных на фотографиях от концепции к делу

17.01.2021 16:22:22 | Автор: admin

Всем привет. В этом небольшом материале я расскажу как довольно быстро и малыми усилиями можно реализовать распознавание лиц на изображениях. Приподниму завесу тайны - получится довольно легко и безболезненно. Добро пожаловать под кат!

Откуда родилась задача

На данный момент я учусь в магистратуре. Этот семестр, помимо дистанционного образования, порадовал нас предметом, на котором мы учились проектировать интеллектуальные системы. В качестве практики было предложено выбрать какую-то группу объектов и научиться их распознавать на фотографиях при помощи нейронных сетей. Условие было лишь одно - нельзя выбирать тривиальные темы для распознавания, которые все делали уже сотню раз (кошечки, собачки, машины, автомобильные номера и т.п.). Мой выбор пал не героев сериала "Воронины".

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

Что будем использовать

Первым шагом к решению проблемы стал выбор инструментария. После изучения нескольких популярных решений и подходов, выбор пал на библиотеку face-recognition. О других реализациях распознавания нестандартных объектов постараюсь рассказать в следующих статьях (дайте знать, если тема распознавания грибов на фото трогает вашу душу).

Выбранная библиотека имеет довольно широкий функционал по распознаванию лиц на фотографиях, а также довольно приятную документацию и ряд интересных примеров с фрагментами кода. Помимо фото, она позволяет распознавать лица и на видео (не без помощи openCV).

Устанавливается face-recognition довольно легко, единственная важная оговорка - пользователям Mac и Linux (в моем случае была Ubuntu 20.04) необходимо вручную установить dlib. Процесс тоже довольно подробно описан в документации.

Немного о том, как все работает

face-regontion использует внутри себя обозначенный выше dlib и OpenFace. Сначала на фотографии выделяется лицо, а после на лице выделяется 68 значимых точек.

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

Подробно о том, как все устроено, можно узнать в оригинальном посте автора библиотеки.

От слов к реализации

После того, как инструментарий выбран, осталось перейти к реализации системы, которая сейчас представляет из себя до боли простое веб-приложение (форма и кнопка), которое позволяет загрузить изображение с героем и увидеть результат в алерте.

На серверной стороне python с fast api. Эта давно зарекомендовавшая себя пара и тут продемонстрировала все свои красоту и удобство.

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

Далее мы загружаем в систему все наши тестовые фотографии, чтобы выделить кодировки лиц:

kostik = face_recognition.load_image_file("samples/kostik.jpg")kostik_encoding = face_recognition.face_encodings(kostik)[0]vera = face_recognition.load_image_file("samples/vera.jpg")vera_encoding = face_recognition.face_encodings(vera)[0]

После я реализовал набор функций, которые принимают на вход кодировку присланного изображения, сравнивают его с определенном персонажем, а после возвращают либо имя, либо вызов следующей функции. Выглядит все следующим образом:

def start_comparing(encoding):compare_result = face_recognition.compare_faces([kostik_encoding], encoding)if compare_result[0]:return "Костик"else:return compare_vera(encoding)def compare_vera(encoding):compare_result = face_recognition.compare_faces([vera_encoding], encoding)if compare_result[0]:return "Вера"else:return compare_nikolay(encoding)

Тут есть один интересный момент: я расположил вызовы функции в порядке популярности персонажей. То есть, чем популярнее персонаж, тем быстрее мы будем получать результат (просто за счет того, что выполняем меньше проверок). Популярность, разумеется, определялась моим мнением, но если бы речь шла о создании не учебной системы, то подобный ход мог бы существенно повысить производительность.

Внимательный читатель наверняка уже заметил, что функция compare_faces возвращает массив. Связанно это с тем, что мы можем на одном фото определять сразу несколько лиц, а также сравнивать сразу несколько лиц. Покопавшись в api библиотеки можно найти еще ряд полезных функций, которые сильно увеличат потенциал системы.

Ну и в конце нужно реализовать обычный API-поинт, который умел бы принимать фото и возвращать результат:

@app.post("/recognize/")def create_file(file: UploadFile = File(...)):with open("samples/test.jpeg", "wb") as buffer:    shutil.copyfileobj(file.file, buffer)unknown_image = face_recognition.load_image_file("samples/test.jpeg")try:unknown_encoding = face_recognition.face_encodings(unknown_image)[0]result = start_comparing(unknown_encoding)except Exception as e:result = "Тут нет лица"else:passfinally:passreturn {"filename": file.filename, "result": result}

Получаем файл изображения, сохраняем его, кодируем и запускаем алгоритм сравнения. Если вы все сделали правильно, не забыли про CORS и написали веб-приложение (или использовали мое, с 2 формами), то теперь у вас готовая система, которая умеет отличать Галю от Лени. Итоговое решение можно найти тут. Если вдруг кто-то заинтересуется и будет скучать томными вечерами, то присылайте пулл-реквесты - буду рад!

Выводы

Теперь немного выводов:

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

  • Библиотека замечательно показала себя в учебном проекте. Использование ее будет вполне оправдано в хакатонах или же стартапах, где нужно как можно быстрее реализовать MVP.

  • Не стоит бездумно использовать библиотеку в больших проектах, все же лучше изучить ограничения производительности.

  • Навык импорта библиотеки в python и последующее использование ее методов не делает из программиста Senior Data Sciene Engineer. Область искусственного интеллекта куда как сложнее и глубже.

  • Математику все же забывать не стоит :)

Подробнее..

Перевод Как обучать огромные модели машинного обучения на случайных GPU

11.01.2021 14:11:20 | Автор: admin
Вы можете спросить: почему эти полумагические модели машинного обучения работают так хорошо? Короткий ответ: эти модели чрезвычайно сложны и обучаются на огромном количестве данных. На самом деле, Lambda Labs недавно подсчитала, что для обучения GPT-3 на одном GPU потребовалось бы 4,6 миллиона долларов если бы такое было возможно.

Такие платформы, как PyTorch и Tensorflow, могут обучать эти огромные модели, потому что распределяют рабочую нагрузку по сотням (или тысячам) GPU одновременно. К сожалению, этим платформам требуется идентичность графических процессоров (они должны иметь одинаковую память и вычислительную производительность). Но многие организации не имеют тысячи одинаковых GPU. Малые и средние организации покупают разные компьютерные системы, что приводит к неоднородной инфраструктуре, которую нелегко адаптировать для вычисления больших моделей. В этих условиях обучение моделей даже среднего размера может занимать недели или даже месяцы. Если не принять меры, университеты и другие небольшие организации рискуют потерять конкурентоспособность в погоне за разработкой новых, лучших моделей машинного обучения. Но это можно исправить.

В этом посте представлена предыстория и практические шаги по обучению BERT с нуля в университете с использованием пакета HetSeq. Это адаптация популярного пакета PyTorch, которая предоставляет возможность обучать большие модели нейронных сетей на гетерогенной инфраструктуре.





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

Эксперименты показывают, что систему на базе BERT можно за день обучить с помощью более чем 8 GPU, большинство из которых нам пришлось позаимствовать в неработающих лабораториях. Прежде чем мы представим HetSeq, нужна небольшая предыстория.

Типовое обучение нейронной сети


def train(args):    # main components    dataset = Dataset()    dataloader = DataLoader(dataset)    model = Model()    loss_ = Loss()    optimizer = Optimizer(model.parameters())    # specify the GPU and transfer model to the GPU     device = Device()     model.to(device)    model.train()        # actual training loops    for epoch in range(1, Max_Epoch):        for (data, target) in dataloader:            data, target = data.to(device), target.to(device)   # **load input data and target to GPU**            optimizer.zero_grad()            output = model(data)    # **forward compute the output of model given input data**            loss = loss_(output, target)   # **forward process to compute the real loss function**            loss.backward()    # **backward process to obtain the**            optimizer.step()    # **update parameters** 

Обучение на одном GPU

Этот код показывает этап обучения базовой модели контролируемого обучения на фреймворке нейронных сетей. Учитывая некоторую архитектуру, эта задача оптимизирует параметры модели через SGD на функции потерь между предсказанными экземплярами и наблюдаемой истиной.

Фактически процесс обучения состоит из четырёх отдельных этапов: (1) загрузка данных, (2) прямой проход, (3) обратный проход, (4) обновление.

1. Загрузка данных


При единственном GPU первые существующие параметры модели (которые изначально случайны) и данные передаются на графический процессор, и это первое, что происходит. Как правило, набор данных содержит большое количество обучающих экземпляров, которые не умещаются на одном графическом процессоре. В этом общем случае мы разделяем набор данных на несколько пакетов и загружаем их по одному.


Прямой проход с одним GPU

2. Прямой проход


Следующий шаг вычисление функции потерь. Для этого пакет данных передаётся через модель (отсюда и название прямой проход) и сравнивается с метками обучения наблюдаемой истины. В блоке прямой проход состоит из двух этапов: генерации спрогнозированной метки (вывод) и измерения разницы (потери) между выводом и целью.

3. Обратный проход


Вычисленная на предыдущем шаге потеря определяет, насколько нужно изменить параметры модели; это называется градиентом, который применяется к архитектуре нейронной сети в обратном направлении (отсюда название обратный проход, или обратное распространение).


Параметры обновления с единственным GPU

4. Обновление


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

Краткое описание этапов обучения


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

Что делать, если у нас несколько GPU?


Поскольку пакеты данных независимы друг от друга, довольно просто распараллелить этот процесс, отправив разные пакеты данных на разные GPU. Затем, если мы сможем каким-то образом объединить вычисленные потери и синхронизировать обновлённые параметры модели, тогда получится сделать обучение намного быстрее.

def torch.nn.parallel.DistributedDataParallel(    module,  # pre-defined model    device_ids=None, # input device_ids    output_device=None,  # output device_ids, in our case, input device = output device = single GPU    dim=0,     broadcast_buffers=True, # set to False in our implementation    process_group=None, # Core part    bucket_cap_mb=25,     find_unused_parameters=False,     check_reduction=False)view raw

Класс параллельного распределения данных

Это не новая идея. В PyTorch мы используем для модели модуль torch.nn.parallel.DistributedDataParallel (DDP) вместо модуля torch.nn.Module. Каждый GPU это отдельный процесс, и связь между ними осуществляется с помощью стандартного IPC. Но это ещё не всё. Четыре шага требуют некоторой настройки.

1. Загрузка данных с помощью DDP


С помощью DDP мы разделяем каждый пакет данных на множество различных GPU столько, сколько у нас есть. В этом случае очень важно, чтобы у каждого графического процессора были одинаковые параметры модели.

Это основная идея параллельного распределения данных (DDP): каждый GPU имеет идентичные параметры модели, но одновременно обрабатывает разные пакеты данных.


Прямой проход с несколькими GPU

2. Прямой проход с DDP


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

3. Обратный проход с DDP


Мы используем средние потери для получения градиентов параметров модели при обратном проходе. Прежде чем начать, необходимо сообщить о средних потерях на разные GPU, чтобы параметры модели могли оставаться синхронизированными. Как только одни и те же параметры на разных графических процессорах получают свои градиенты, выполняется синхронизация последних, чтобы они были одинаковы.


Синхронизация градиента

4. Обновление с DDP


После синхронизации градиентов мы можем обновлять параметры модели параллельно, используя отдельные оптимизаторы на каждом процессоре.

Следующий шаг обучения обычно можно начинать сразу. Однако, поскольку почти все параметры плавают и в некоторых GPU может возникать ошибка вычислений, особенно когда выполняется много шагов обучения, мы иногда синхронизируем параметры в начале или в конце шага.

Эти изменения отражены в следующем псевдокоде. Примечательно, что функцию принимает идентификатор устройства (т. е. идентификатор GPU), модель должна выполнять синхронизацию параметров перед каждым прямым проходом, функция потерь должна усредняться перед обратным проходом, и, наконец, градиенты должны быть усреднены перед обновлением параметров модели.

def train_multiple_GPUs(args, device_id):    # main components    dataset = Dataset()    dataloader = DataLoader(dataset)    model = DDP(Model())    loss_ = Loss()    optimizer = Optimizer(model.parameters())    device = Device(device_id)     model.to(device)    model.train()        # actual training loops    for epoch in range(1, Max_Epoch):        for (data, target) in dataloader:            data, target = data.to(device), target.to(device)              optimizer.zero_grad()            model.synchronization()    #  parameter synchronization            output = model(data)                loss = loss_(output, target)            loss_average = average(loss)            loss.backward()            model.parameter.grad.average()            optimizer.step()

Обучение на нескольких GPU

Масштабирование несколько узлов с несколькими GPU


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

def torch.distributed.init_process_group(            backend=args.distributed_backend,    # 'nccl' is the best available backend for GPU            init_method=args.distributed_init_method,    # 'tcp' or shared file system            world_size=args.distributed_world_size,  # number of nodes in total            rank=args.distributed_rank, # index of current node        )

К счастью, тот же механизм, который используется для адресации процессоров на одном узле, можно распространить на несколько узлов. Вы можете просто установить индекс узла, то есть параметр rank в функции init_process_group глобально, чтобы каждый GPU имел уникальный идентификатор относительно всех узлов.

Коммуникация вот где возникают сложности



Внутриузловая и межузловая коммуникация

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

Конечно, межузловая связь намного медленнее, чем внутриузловая. А совместное использование градиентов и обновлений параметров превращается в полный беспорядок, когда узлы и GPU не идентичны как в случае, когда у вас нет миллиарда долларов, которые можно потратить на центр обработки данных с настраиваемым вычислительным оборудованием.

Когда родители заставляют вас делиться игрушками


В большинстве университетских вычислительных центров различные исследовательские лаборатории совместно используют свои вычислительные ресурсы. Существуют разные модели того, как это делается, но обычно ИТ-администраторы берут на себя значительный контроль над системами и не позволяют учёным устанавливать (обновлять или удалять обновления) необходимого программного обеспечения.
Это означает, что если необходимо обучить большую модель, то некоторым бедным аспирантам необходимо привести алгоритм обучения в соответствие с инфраструктурой. А это сложно по нескольким причинам:

  1. Некоторые игрушки имеют сложные инструкции. Распределённая параллельная обработка данных пакета (DDP) это боль, трудно понять её и заставить работать. Особенно верно это для большинства исследователей машинного обучения, которые не очень хорошо разбираются в особенностях распределённых вычислений. В дополнение к базовой настройке DDP мирный тренировочный запуск различных архитектур GPU на многих узлах требует тщательного разделения данных и изнурительного налаживания связи между GPU и узлами.
  2. С какими-то игрушками лучше играть лучше, чем с другими. В гетерогенной системе некоторые GPU работают быстрее других, а некоторые имеют больше памяти, чем у других. Это означает, что какие-то процессоры получают больше данных для обработки, чем другие, что прекрасно; но это также означает, что средние значения градиентов и обновления параметров должны тщательно взвешиваться.
  3. Родители не разрешают нам играть с какими-то игрушками. Большинство существующих распределённых обучающих платформ GPU требуют дополнительных пакетов, таких как Docker, OpenMPI и т. д. К сожалению, большинство компетентных администраторов кластеров не позволяют пользователям иметь административные привилегии, необходимые для настройки каждого узла, чтобы обучить модель.
  4. Какие-то игрушки плохо работают с другими. Пакеты глубокого обучения, такие как BERT и GPT2/3, разработанные крупными компаниями, как правило, имеют определённые форматы дизайна модели с несколькими логическими слоями, что затрудняет их использование и адаптацию к приложению.

Из-за этих проблем мы создали общую систему, которая охватывает все сложные части DDP: разделение данных, совместимость и настраиваемость, и развернули эту систему в Нотр-Даме.

Мы называем эту систему HetSeq. Она была адаптирована из популярного пакета PyTorch и обеспечивает возможность обучения больших моделей нейронных сетей в гетерогенной инфраструктуре. Её можно легко настроить через общую файловую систему без дополнительных пакетов и административных привилегий. Вот как обучать BERT с помощью HetSeq.

BERT в университете с HetSeq


Начнём с Anaconda. Создадим виртуальную среду и установим Python.

$ conda create --name hetseq$ conda activate hetseq$ conda install python=3.7.4


Затем мы установим пакеты и привязки HetSeq: загрузим HetSeq с GitHub, установим пакеты из requirements.txt, а также HetSeq и биндинги из setup.py.
$ git clone https://github.com/yifding/hetseq.git $ cd /path/to/hetseq $ pip install -r requirements.txt $ pip install --editable .

Последний шаг перед обучением это загрузка файлов данных BERT, включая корпус обучения, конфигурацию модели и словарь BPE отсюда. Загрузите DATA.zip, распакуйте его и поместите в каталог preprocessing/.

Обучение BERT с помощью HetSeq


Крутая вещь в HetSeq: она абстрагирует все детали о распределённой обработке. Таким образом, код обучения для 100 GPU почти такой же, как для одного! Давайте попробуем!

$DIST=/path/to/hetseq $python3 ${DIST}/train.py  \ $       --task bert   --data ${DIST}/preprocessing/test_128/ \ $       --dict ${DIST}/preprocessing/uncased_L-12_H-768_A-12/vocab.txt  \ $       --config_file ${DIST}/preprocessing/uncased_L-12_H-768_A-12/bert_config.json  \ $       --max-sentences 32  --fast-stat-sync --max-update 900000 --update-freq 4  \ $       --valid-subset test --num-workers 4 \ $       --warmup-updates 10000  --total-num-update 1000000 --lr 0.0001  \ $       --weight-decay 0.01 --distributed-world-size 1  \ $       --device-id 0 --save-dir bert_single_gpu

В этом случае предположим, что у нас есть два вычислительных узла.

На первом узле:

$DIST=/path/to/hetseq $python3 ${DIST}/train.py  \ $       --task bert   --data ${DIST}/preprocessing/test_128/ \ $       --dict ${DIST}/preprocessing/uncased_L-12_H-768_A-12/vocab.txt  \ $       --config_file ${DIST}/preprocessing/uncased_L-12_H-768_A-12/bert_config.json  \ $       --max-sentences 32  --fast-stat-sync --max-update 900000 --update-freq 4  \ $       --valid-subset test --num-workers 4 \ $       --warmup-updates 10000  --total-num-update 1000000 --lr 0.0001  \ $       --weight-decay 0.01 --save-dir bert_node2gpu4  \ $       --distributed-init-method tcp://10.00.123.456:11111 \ $       --distributed-world-size 8 --distributed-gpus 4 --distributed-rank 0

На втором узле:

$DIST=/path/to/hetseq $python3 ${DIST}/train.py  \ $       --task bert   --data ${DIST}/preprocessing/test_128/ \ $       --dict ${DIST}/preprocessing/uncased_L-12_H-768_A-12/vocab.txt  \ $       --config_file ${DIST}/preprocessing/uncased_L-12_H-768_A-12/bert_config.json  \ $       --max-sentences 32  --fast-stat-sync --max-update 900000 --update-freq 4  \ $       --valid-subset test --num-workers 4 \ $       --warmup-updates 10000  --total-num-update 1000000 --lr 0.0001  \ $       --weight-decay 0.01 --save-dir bert_node2gpu4  \ $       --distributed-init-method tcp://10.00.123.456:11111 \ $       --distributed-world-size 8 --distributed-gpus 4 --distributed-rank 4

Два блока кода работают на двух разных узлах. Адрес TCP/IP должен быть установлен как один из IP-адресов узла. Как только они будут запущены, вы сможете наблюдать за выполнением кода на 8 процессорах и 2 разных узлах!

Так насколько хорошо это работает? Мы провели несколько экспериментов (подробности тут) над различными однородными (гомогенными, hom) и неоднородными (гетерогенными, het) установками.

nodes GPUs training_time speed_up
1 4 7.19day 1.00
2(het) 8 4.19day 1.72
2(hom) 8 4.26day 1.69
4(het) 16 2.23day 3.22
4(hom) 16 2.19day 3.28
8(het) 32 1.21day 5.94

В общей сложности мы смогли управлять 32 GPU в 8 неоднородных узлах, сокращая время обучения языковой модели BERT с семи дней до примерно одного дня.

Под капотом HetSeq



Структура пакета HetSeq

Пакет HetSeq содержит три основных модуля, показанных на рисунке слева: train.py, task.py и controller.py для координации основных компонентов, показанных справа. Модуль train.py инициализирует распределённую систему и её различные компоненты.

Модуль task.py определяет функции модели, набора данных, загрузчика данных и оптимизатора; он также выполняет функции прямого и обратного распространения. Модуль controller.py действует как главный контроллер обучения. Он работает как модель, оптимизатор и планировщик скорости обучения; загружает и сохраняет чекпоинты, сообщает о потере и обновляет параметры.
Но я хочу обучить не BERT!

Но я хочу обучить не BERT!


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

image



Подробнее..

Нейронные сетевые технологии

12.01.2021 16:20:24 | Автор: admin

Статья нашего сотрудника.

Тема посвящена нейронным сетевым технологиям.

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

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

В нейронных сетевых технологиях есть метод решения проблем исключающего или (XOR).

Таблица исключающего или выглядит следующим образом:

для бинарного сложения по модулю 2 (применяется в двоичных полусумматорах):

A

B

A+B

0

0

0

0

1

1

1

0

1

1

1

0

Правило: результат равен 0, если оба операнда равны; в остальных случаях результат равен 1.

для тернарного сложения по модулю 2 (применяется в двоичных полных сумматорах):

A

B

C

A+B+C

0

0

0

0

0

0

1

1

0

1

0

1

0

1

1

0

1

0

0

1

1

0

1

0

1

1

0

0

1

1

1

1

Правило: результат равен 0, если нет операндов, равных 1, либо их чётное количество.

Следовательно, нейронная сеть берет на вход два числа (параметра) и должна на выходе дать другое эквивалентное число ответ.

Нейрон это единица (входные данные (input data)), которая получает информацию и дальше передаёт информацию на скрытые слои n, где происходят арифметико-логические вычисления, после следует выходной слой, который выводит результат (выходные данные (output data)).

Синапс это связь между двумя нейронами, у синапсов есть 1 параметр вес.
Благодаря ему входная информация изменяется, когда передаётся от одного нейрона к другому.

Аннотированная ссылка

Нейроны оперируют числами в диапазоне [0,1] или [-1,1]. Если числа выходят из данного диапазона, то необходимо 1 разделить на полученное число.

Аннотированная ссылка

В поле input попадает суммарная информация всех нейронов с предыдущего слоя, после чего она нормализуется с помощью функции активации.

Функция активация служит для нормализации входных данных (оптимизация), то есть, если на вход поступает большое число, функция активации позволяет получить это число в нужном для нас диапазоне.

Самые основные функции активации: Линейная, Сигмоид и Гиперболический тангенс. Главное их отличие, это диапазон значений:

Линейная функция

Чаще всего используется для тестирования нейронной сети.

Сигмоид

Распространённая нормализуемая сигмоидальная функция активации, её диапазон значений [0,1].

Гиперболический тангенс

Используется тогда, когда могут быть числа отрицательными, и положительными, так как диапазон функции [-1,1].

Аннотированная ссылка

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

Аннотированная ссылка

где x min минимальное выборочные значения признака.
x max максимальное выборочное значение признака.
[a, b] выборка интервала.
x i значение признака.

Либо, если метод обработки нейронной сети обучения не имеет жёстких ограничений, применяется формула по масштабированию, дающая ненулевое среднее и единичную дисперсию пред обработанной величине:

Аннотированная ссылка

где M(x) исходное выборочное среднее.
(x) среднее квадратичное отклонение.
x i значение признака.

На этапах предобработки данных, отбора подмножеств примеров, поиска нетипичных наблюдений, при разведочном анализе данных повышение скорости работы алгоритмов (через их распараллеливание (Аннотированная ссылка) позволит проводить более быстрый, глубокий и всесторонний анализ свойств данных).

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

Аннотированная ссылка

Примером может быть популярная система распознавания объектов с помощью изображения от разработчиков компании Google.
Вот пример обработки изображения с целью получения результата, что это за действие/объект:

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

Заключение

Нейронные сети позволят обновить все нынешние системы принятия и поддержки решения, автоматизировать процесс, и в дальнейшем создать самостоятельную систему после прохождения запуска итераций обучения нейронной сети. В перспективе полная глобализация автоматизированных процессов, вплоть до поддержки пользователей в решении бытовых и иных задач на персональных компьютерах: начиная с захвата на экране области ошибки на станции АРМ/ПК и поиска по сходству точек/пикселей аналогичной записи в базе данных и выдачи решения.

Автор: Кирилл Артамонов

Подробнее..

Доббль практичный подход с OpenCV и NumPy

13.01.2021 18:13:38 | Автор: admin

О чём мы вспоминаем в первую очередь, когда слышим про распознавание образов? Сложные нейронные сети, мощные видеокарты, объёмные наборы данных. Всего этого не будет в моей истории - я расскажу, как с помощью OpenCV и NumPy можно за 1 вечер решить задачу классификации 57 символов из игры Доббль, используя менее 500 их изображений без дополнительной аугментации. Разный масштаб, произвольный угол поворота - всё это не имеет значения, когда для описания символа достаточно четырёх чисел.

Эта история произошла весной 2020 года, во время вынужденной самоизоляции. Я смотрел ролики на youtube и наткнулся на интересную игру - Доббль, или по-другому SpotIt. В местных магазинах я вряд ли смог бы её найти, а в условиях самоизоляции вариант с заказом тоже выглядел довольно призрачно. В результате нашёл в сети файл с изображениями карточек, распечатал на плотной фотобумаге и вырезал - получился довольно аккуратный набор. Сыну игра понравилась, стали играть.

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

И тут на Хабре обнаружилась статья "Как я научила свой компьютер играть в Доббль с помощью OpenCV и Deep Learning". Казалось бы, вот оно - решение, но Проанализировав код, я понял, что у подобного решения есть два фатальных недостатка - нарезка и разметка такого количества картинок займет слишком много времени, а тренировка модели на машине с неподдерживаемой видеокартой продлится еще дольше. Стал думать.

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

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

Чтобы облегчить дальнейшую разметку, названия папок соответствуют соотношению длин сторон прямоугольника, а названия файлов содержат все описывающие символ параметры и номер карточки. Потом запустил IrfanView в режиме миниатюр и перетащил в нужные папки неточно распределённые файлы. Это самый трудоёмкий и длительный этап работ, но благодаря тому что файлы были уже сгруппированы он длился меньше часа. В завершение переименовал папки в соответствии с номером и названием содержащегося в них символа.

Один из неудачных вариантов автоматической разметкиОдин из неудачных вариантов автоматической разметки

Потом я обучил нейросеть - многослойный перцептрон (MLP). Сеть сделал на основе учебного примера из книги "Python Machine Learning" Себастьяна Рашки, для её реализации достаточно пакета NumPy.

В качестве входных данных использовал список файлов с символами - содержащейся в нём информации достаточно для обучения сети. Название папки с символами начинается с двузначного числа - номер символа, используем его как метку. Имя файла содержит 4 числа, соответствующие параметрам символа. Значение всех параметров оказалось в пределах 45..255, поэтому для полного использования диапазона 0..1 вычитаем из них 45 и делим на 210. Так как данных мало, в качестве тестового набора используем часть тренировочного. В списке 440 файлов, время обучения составило около 1 минуты.

И всё равно в результате проверки идеи выявились коллизии, не дающие правильно опознать символ. Пришлось добавить еще один параметр - отношение площади символа к площади описывающего его прямоугольника (я назвал его "плотность"). В результате этот параметр оказался очень полезным, от одного из цветовых каналов удалось избавиться. Единичные символы теперь распознавались устойчиво по четырём признакам.

Казалось бы, можно брать файл с карточкой, распознавать первые 8 самых крупных символов и получится описание карточки. Но тут выявилась другая проблема - некоторые символы состоят из нескольких отдельных частей, а с учётом разного масштаба мелкая часть одного может оказаться крупнее другого. Пришлось добавить в набор данных символы точек от восклицательного и вопросительного знаков и пропускать их при подсчете найденных символов.

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

Проверка работы нейросетиПроверка работы нейросети

Ради интереса проверил производительность - полный проход по колоде из 55 карточек (1485 комбинаций) занял 170 секунд, ошибок 0. Была идея сделать распознавание карточек по изображению с веб-камеры, но оказалось, что они сильно бликуют. Кроме того, у сети выявился недостаток - она сильно чувствительна к понижению разрешения картинки: мелкие детали (например, лучи снежинки) сливаются и это приводит к изменению параметров. Но сделать на ней игру с поиском совпадений по двум карточкам можно. Или использовать уже размеченные изображения символов для создания полноценного датасета и тренировки свёрточной нейронной сети.

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

Подробнее..

Как мы управляли поездами на соревновании NeurIPS 2020 Flatland

15.01.2021 12:16:15 | Автор: admin
Всем привет! Мы команда из Питерской Вышки, и в этом году мы заняли первое место в RL треке соревнования NeurIPS 2020: Flatland. Цель Flatland разработать алгоритм, способный как можно лучше управлять трафиком движения поездов по сети железных дорог, при этом система должна принимать решения за ограниченное время.

О том, что это за соревнование и как нам удалось в нем победить (а на контест прислали большее 2000 решений из 51 страны) читайте под катом.




Наша команда


В команде JBR_HSE было пять участников: Константин Махнев (учится на 4-м курсе бакалаврской программы Прикладная математика и информатика) Олег Свидченко и Владимир Егоров (оба учатся в магистратуре Программирование и анализ данных), аспирант Дмитрий Иванов и заведующий Центром анализа данных и машинного обучения НИУ ВШЭ Санкт-Петербург Алексей Шпильман. Все, кроме Константина, также работают в лаборатории Агентных систем и обучения с подкреплением JetBrains Research отсюда и название команды. Во время участия в соревновании Костя стажировался в этой же лаборатории.

NeurIPS 2020: Flatland что это?


Flatland это соревнование, которое проходило с 10 июля по 15 ноября на основе платформы AIcrowd и при поддержке известной международной конференции NeurIPS. Цель соревнования разработать алгоритм, способный как можно лучше управлять железнодорожным трафиком. Важным ограничением было то, что решения нужно принимать за ограниченное время (5 секунд на один шаг симулятора), что не позволяло просто подбирать оптимальные действия.



В NeurIPS 2020: Flatland было два направления: общее и Reinforcement Learning (RL). Первое направление было открыто для любых решений, а второе только для тех, которые используют обучение с подкреплением.

Организаторы предоставили участникам симулятор Flatland, который представляет собой двумерную сетку, где каждая клетка имеет свой тип: дорога, поворот, развилка или непроходимая местность. Каждый поезд занимает ровно одну клетку на сетке и имеет направление, по которому он сейчас перемещается. Симуляция происходит пошагово, на каждом шаге для каждого поезда нужно определить, какое действие ему совершить: остановиться, ехать по самому левому пути или ехать по самому правому пути. Поскольку в текущей реализации полного перекрестка не предусмотрено, т.е. не бывает так, что можно поехать одновременно вперед, направо и налево, то и действие ехать вперед тоже не нужно. В случае, когда никаких поворотов нет, второе и третье действие эквивалентны. Поезда могут сталкиваться между собой получается deadlock. Задача соревнования заставить все поезда как можно быстрее доехать до их пунктов назначения. Решения оцениваются по сумме времени, которое потратили поезда на достижение цели. В случае, если поезд не доехал, его время равно максимальному времени симуляции.

Решение: как агент будет взаимодействовать с симулятором


На Хабре уже было достаточно много постов про обучение с подкреплением, поэтому не будем подробно останавливаться на основных понятиях. Скажем только, что в случае Flatland в качестве среды выступает симуляция железной дороги, а в качестве агентов поезда. Важно отметить, что поскольку в симуляции участвует множество поездов, то данное окружение является мультиагентным.

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

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

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

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



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

Осталось определить только функцию награды для агента. После некоторого подбора, мы остановились на довольно простой: $0.01 \cdot \Delta d 5 \cdot \text{is\_deadlocked} + 10 \cdot \text{is\_succeed}$, т.е. награда отражает то, насколько изменилось расстояние $d$ до пункта назначения после принятия решения, с дополнительными наградой в случае успешного завершения эпизода и наказанием в случае попадания в дедлок.

Алгоритм PPO


Существует множество алгоритмов обучения с подкреплением, которые разработаны для решения мультиагентных задач. Однако в качестве baseline алгоритма мы решили использовать алгоритм PPO Proximal Policy Optimization, поскольку его код можно было бы переиспользовать для реализации алгоритмов multiagent RL (например, COMA). Позже, правда, оказалось, что PPO (с некоторыми модификациями) сам по себе решает задачу неплохо, а вот мультиагентные методы обучаются значительно хуже, поэтому PPO остался основной частью нашего финального решения.

Классический алгоритм PPO состоит из двух частей: актора и критика. Критик приближает value-function, а актор учит рандомизированную политику $\pi_\theta(a | s)$, которая максимизирует математическое ожидание суммарной награды.



Одна из важных модификаций, которые мы сделали добавление в архитектуру актора модуля, который позволяет агенту коммуницировать с ближайшими агентами. Процесс коммуникации построен довольно просто: агенты одновременно генерируют сообщения и рассылают их всем поездам, рядом с которыми находятся, а затем, получив сообщения от соседей, объединяют их в одно сообщение путем взвешенного усреднения. Для определения весов, с которыми будут усредняться сообщения, использовался простой механизм self-attention. При таком построении процесса коммуникации нет необходимости как-то модифицировать процесс обучения, т.к. достаточно просто разрешить градиентам проходить через сообщения во время применения обратного распространения ошибки.

Обучать PPO решили с картой небольшого размера и маленьким количеством агентов, предположив, что поскольку агент наблюдает только то, что находится рядом с ним, после обучения он будет хорошо работать и в окружениях большего размера.

Как решить, каким поездам когда стартовать?


Попробовав просто применить алгоритм PPO, мы довольно быстро пришли к выводу, что большая часть дедлоков происходит именно из-за того, что поезда начинают двигаться одновременно и мешают друг другу. Эту проблему мы решили так: стали одновременно запускать только несколько агентов. Когда один из агентов заканчивал свой эпизод, другому агенту позволялось начать движение. В таком подходе важно понять, в какой последовательности нужно запускать поезда.

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

Результаты соревнования


В результате наше решение заняло первое место в треке RL и восьмое место в общем зачете. Это значит, что на данный момент не-RL подход пока решает лучше, однако показывает, что у обучения с подкреплением есть потенциал. В нашем подходе довольно много слабых мест и нерешенных вопросов (например, серьезные проблемы с масштабируемостью на окружения большого размера), поэтому нам есть, над чем еще поработать. Сейчас мы вместе с организаторами соревнования готовим статью для отправки ее в competition book NeurIPS 2020.
Подробнее..

Перевод Разбираем XLNet

15.01.2021 18:06:16 | Автор: admin

Введение


XLNet новейшая и самая крупная модель, появившаяся в активно развивающейся сфере обработки естественного языка (Natural Language Processing, NLP). Статья о XLNet объединяет современные достижения в NLP и инновационный подход к решению задачи языкового моделирования. Обученная на огромном корпусе, модель достигает выдающихся результатов в NLP-задачах бенчмарка GLUE.


XLNet представляет собой авторегрессионную языковую модель, которая выдает на выходе вероятность совместной встречаемости последовательности токенов на основе архитектуры рекуррентного Трансформера. Задачей обучения модели является подсчет вероятности для заданного слова (токена), при условии наличия всех других слов в предложении (а не только слов слева или справа от заданного).


Если вам все понятно в описании выше, то этот пост не для вас. Если же нет, то продолжайте читать о том, как работает XLNet и почему он стал стандартом для многих NLP задач.


Языковое моделирование


Языковое моделирование заключается в подсчете распределения совместной вероятности для последовательности токенов (слов), и зачастую достигается путем факторизации совместного распределения на распределение условной вероятности одного токена с учетом других токенов в последовательности. Например, дана последовательность: "New", "York", "is", "a", "city". Вероятность слова New модель подсчитает как $inline$Pr(\text{"New"} | \text{"is"}, \text{"a"}, \text{"city"})$inline$, т.е. вероятность того, что токен "New" находится в той же последовательности, что и токены "is", "a" и "city" (см. рис. 1).


Заметим, что обычно языковая модель принимает текстовую последовательность в $T$ токенов, $\mathbf{x} = [x_1, x_2,\ldots, x_T]$, и подсчитывает вероятность появления некоторых токенов $\mathbf{x}^{\prime}$ в последовательности при известных $\mathbf{x}^{\prime\prime}$: $Pr(\mathbf{x}^{\prime} | \mathbf{x}^{\prime\prime})$, где $\mathbf{x}^{\prime}$ and $\mathbf{x}^{\prime\prime}$ непересекающиеся подмножества $\mathbf{x}$.


xlnet_figure1


Рис. 1. Языковая модель. Модель это функция, которая принимает на вход несколько контекстных токенов и выдает на выходе вероятность каждого токена в словаре. Толстыми линиями обозначены более информативные контекстные слова и более вероятные слова из словаря.


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


Стоя на плечах моделей-гигантов


Перейдем к основам архитектуры XLNet. Первый компонент языковой модели это матрица словарных эмбеддингов: каждому токену в словаре присваивается вектор фиксированной длины и, таким образом, последовательность переводится в набор векторов.


Далее нам нужно построить отношения между векторизованными токенами в последовательности. Долгое время признанным фаворитом для решения этой задачи была архитектура LSTM, которая строит отношения между смежными токенами (напр., модель ELMo), однако сейчас наилучшие результаты показывают модели Трансформера (напр., модель BERT). Архитектура Трансформера позволяет объединять несмежные токены, и, таким образом, генерировать более высокоуровневую информацию с помощью механизма внимания. Это помогает модели легче выстраивать более отдаленные отношения в тексте, чем подходы на основе LSTM.


У Трансформеров есть один недостаток: они работают с последовательностями фиксированной длины. Но что, если знание того, что "New" должно появиться в предложении "____ York is a city", также требует, чтобы модель прочитала что-то об Эмпайр-стейт-билдинг в предыдущем предложении? Transformer-XL решает эту проблему, позволяя текущей последовательности видеть информацию из предыдущих последовательностей. Именно на этой архитектуре строится XLNet.


Цель обучения XL


Основной вклад XLNet это не архитектура, а модифицированная цель обучения языковой модели, которая должна выучить условные распределения для всех перестановок токенов в последовательности. Прежде чем углубиться в детали этой цели, давайте вернемся к модели BERT и объясним такой выбор XLNet.


В бывшей SOTA-модели (BERT) целью обучения было восстановление маскированных слов в предложении: так, для каждого предложения в корпусе некоторые токены заменяются универсальным токеном [mask]. Задача модели восстановить изначальные токены.


xlnet_figure2


Рис. 2. Изображение модели BERT. На входе модели контекстные токены, некоторые из которых маскированы. Обращая внимание на правильные токены контекста, модель узнает, что наиболее вероятным словом в маскированной позиции будет "boat".


В статье XLNet утверждается, что это не лучший способ обучать модель. Оставим детали аргументации самой статье и вместо этого приведем менее точный, но охватывающий некоторые важные для нас концепции аргумент.


Языковая модель должна кодировать как можно больше информации и нюансов из текста. Модель BERT пытается восстановить маскированные слова в предложении "The [mask] was beached on the riverside" (рисунок 2) ("[mask] была выброшена на берег реки"). Здесь могут встречаться такие слова, как "лодка" или "каноэ". BERT может знать это, потому что лодка может быть выброшена на берег, и ее часто можно найти на берегу реки. Но для BERT'а вовсе необязательно знать это про лодку, достаточно костыля вроде упоминания "берег реки", чтобы сделать вывод, что "лодка" является маскированным токеном.


Более того, BERT предсказывает маскированные токены независимо, поэтому не узнает, как они влияют друг на друга. Если бы пример был "The [mask] was [mask] on the riverside" ("[mask] была [mask] на берег реки"), то BERT мог выдать высокие вероятности не только для таких корректных пар, как ("лодка", "выброшена") и ("парад", "виднелся"), но и для пары ("парад", "выброшен").


Такие подходы, как BERT и ELMo, стали в свое время SOTA за счет включения в предсказание левого и правого контекстов. XLNet пошла еще дальше: модель предназначена для предсказания каждого слова в последовательности, используя любую комбинацию других слов в этой последовательности. XLNet могут попросить предсказать, какое слово может следовать за "The" в нашем предложении. Вероятно, много слов, но "лодка" более вероятна, чем "они", потому что модель уже кое-что узнала о лодке (в основном, что это не местоимение). Затем модель могут попросить предсказать второе слово, учитывая, что последующие слова "была" и "выброшена". И затем ее могут попросить предсказать четвертое слово, учитывая, что третье "был", пятое "на" и седьмое "берег реки.


xlnet_figure3


Рис. 3. Изображение модели XLNet. Задача рассчитать, что лодка является вероятным токеном для многих различных контекстов, взятых из последовательности.


Таким образом, XLNet действительно не на что опереться. Ей подают сложный и зачастую неоднозначный контекст, из которого необходимо определить, входит ли слово в предложение. И это то, что позволяет модели выжать больше информации из обучающего корпуса (см. рис. 3).


На практике модель XLNet делает выборку из всех возможных перестановок, так что ей не удается просмотреть каждое отдельное отношение. Также, она не использует очень маленькие контексты, поскольку они мешают обучению. После применения этих практических эвристик XLNet стала больше походить на BERT.


В следующих нескольких разделах будут рассмотрены более сложные аспекты статьи.


Перестановки


Для последовательности $\mathbf{x}$ авторегрессионная (auto-regressive, AR) модель
подсчитывает вероятность $Pr(x_i | x_{<i})$. В языковом моделировании, это вероятность токена $x_{i}$ в предложении при условии, что токены $x_{<i}$ предшествуют ему. Эти обуславливающие слова называют контекстом. Подобная модель ассиметрична и не получает информацию о всех отношениях между токенами в корпусе.


Авторегрессионные модели, такие как ELMo, позволяют также учиться отношениям между токеном и последующими токенами. Цель AR в этом случае может рассматриваться как $Pr(x_i) = Pr(x_i | x_{>i})$. Это авторегрессия для обратной последовательности. Но зачем останавливаться? Ведь могут существовать отношения, интересные для задачи обучения, как между двумя ближайшими токенами $Pr(x_i) = Pr(x_i | x_{i-1}, x_{i+1})$, так и вообще для любой комбинации токенов $Pr(x_i) = Pr(x_i | x_{i-1}, x_{i+2}, x_{i-3})$.


XLNet предлагает в качестве цели обучения получить представление над всеми такими перестановками. Возьмем, например, последовательность $inline$\mathbf{x} = ["This", "is", "a", "sentence"]$inline$, где $T=4$. Получится набор из всех 4! перестановок $\mathcal{Z} = \{[1, 2, 3, 4], [1, 2, 4, 3],. . ., [4, 3, 2, 1]\}$. Модель XLNet авторегрессионна для всех таких перестановок: она может посчитать вероятность токена $x_i$, учитывая предыдущие токены $x_{<i}$ для любого порядка $\mathbf{z}$ из $\mathcal{Z}$.


Например, можно вычислить вероятность третьего элемента с учетом двух предыдущих из любой перестановки. Три перестановки $[1, 2, 3, 4]$, $[1, 2, 4, 3]$ и $[4, 3, 2, 1]$ будут соответствовать $inline$Pr("a", | "This", "is")$inline$, $inline$Pr("sentence" | "This", "is")$inline$ и $inline$Pr("is" | "sentence", "a")$inline$. Схожим образом вероятность второго элемента с учетом первого можно выразить как $inline$Pr("is" | "This")$inline$, $inline$Pr("is" | "This")$inline$ и $inline$Pr("a" | "sentence")$inline$. Если же рассматривать все 4 позиции и все 4! перестановок, то модель будет учитывать все возможные зависимости.


Эти идеи включены в следующую формулу из статьи:


$\hat{\boldsymbol\theta} = \mathop{\rm argmax}_{\boldsymbol\theta}\left[\mathbb{E}_{\mathbf{z}\sim\mathcal{Z}}\left[\sum_{t=1}^{T} \log \left[Pr(x_{z[t]}|x_{z[<t]}) \right] \right]\right]$


Описанный критерий ищет параметры модели $\boldsymbol\theta$, обеспечивающие максимальную вероятность токенов $x_{z[t]}$ в последовательности длиной $T$ с учетом предыдущих токенов $x_{z[<t]}$, где $z[t]$ это $t^{ый}$ элемент перестановки $z$ индексов токенов и $z[<t]$ предыдущие элементы перестановки. Сумма логарифмов вероятностей означает, что для любой одной перестановки модель авторегрессионна, поскольку она является произведением вероятности для каждого элемента в последовательности. Ожидается, что по всем перестановкам в $\mathcal{Z}$ модель будет обучена одинаково вычислять вероятности для любого токена в любом контексте.


Маска внимания


Но в текущем представлении модели чего-то не хватает: откуда она знает о порядке слов? Модель может вычислить $inline$Pr("This" | "is")$inline$, а также $inline$Pr("This" | "a")$inline$. В идеале она должна кое-что знать об относительном положении "This" и "is", а также "a". В противном случае модель просто бы решила, что все токены в последовательности с равной вероятностью находятся рядом друг с другом. Нам нужна модель, которая предсказывает
$inline$Pr("This" | "is", 2)$inline$ и $inline$Pr("This" | "a", 3)$inline$, а для этого она должна знать индексы токенов контекста.


Архитектура Трансформера решает эту проблему, добавляя позиционную информацию в эмбеддинги токенов. Цель обучения можно представить как $inline$Pr("This" | "is+2")$inline$. Но в случае, если токены предложений действительно будут перемешаны, подобный механизм сломается. И здесь на помощь приходят маски внимания. Когда модель вычисляет контекст, который является входом для вычисления вероятности, она всегда использует один и тот же порядок токенов и просто маскирует те токены, которые не находятся в рассматриваемом контексте (т.е. те, которые поступают впоследствии в перемешанном порядке).


В качестве конкретного примера рассмотрим перестановку $[3, 2, 4, 1]$. При вычислении вероятности первого элемента в этом порядке (т.е. токена 3) модель не имеет контекста, поскольку другие токены еще не были поданы. Таким образом, маска будет $[0, 0, 0, 0]$. Для второго элемента (токен 2) маска равна $[0, 0, 1, 0]$, поскольку его единственный контекст это токен 3. Следуя этой логике, третий и четвертый элементы (токены 4 и 1) имеют маски $[0, 1, 1, 0]$ и $[0, 1, 1, 1]$. Сложив все элементы в порядке токенов, получится матрица (как показано на рис. 2 статьи):


$\begin{bmatrix} 0& 1& 1& 1 \\ 0& 0& 1& 0\\ 0& 0& 0& 0 \\ 0& 1& 1& 0 \end{bmatrix}$


Можно также посмотреть на это с другой стороны: цель обучения будет содержать следующие условия, где подчеркивания представляют маскированные токены:


$inline$Pr("This"|\_\_\_,"is+2","a+3","sentence+4")$inline$


$inline$Pr("is"|\_\_\_,\_\_\_,"a+3",\_\_\_)$inline$


$inline$Pr("a"|\_\_\_,\_\_\_,\_\_\_,\_\_\_)$inline$


$inline$Pr("sentence"|\_\_\_,"is+2","a+3",\_\_\_)$inline$


Двухпотоковый механизм внутреннего внимания


Остается исправить одну ошибку: мы хотим, чтобы вероятность не только зависела от индексов токенов контекста, но и от индекса токена, вероятность которого вычисляется. Другими словами мы хотим вычислить $inline$Pr("This" | "1", "is+2")$inline$: вероятность "This" при условии, что это первый токен, а "is" второй. Но архитектура Трансформера кодирует позиционную информацию 1 и 2 внутри эмбеддинга для "This" и "is". Значит это должно выглядеть как $inline$Pr("This" | "This+1", "is+2")$inline$. К сожалению, модель теперь просто напросто знает, что токен "This" должен быть вероятен как часть предложения.


Решение этой проблемы двухпотоковый механизм внутреннего внимания. Каждая позиция токена $i$ имеет два связанных вектора на каждом слое внутреннего внимания $m$: $\mathbf{h}_i^m$ and $\mathbf{g}_i^m$. Векторы $\mathbf{h}$ принадлежат потоку содержания (content stream), а векторы $\mathbf{g}$ потоку запроса (query stream). Векторы потока содержания инициализируются эмбеддингами токенов, добавленными к позиционным эмбеддингам. Векторы потока запросов инициализируются общим вектором эмбеддинга $\mathbf{w}$, добавленного к позиционным эмбеддингам. Стоит отметить, что вектор $\mathbf{w}$ будет одним и тем же независимо от токена, и поэтому не может использоваться для различения токенов.


На каждом слое каждый вектор содержания $\mathbf{h}_i$ обновляется с использованием тех векторов $\mathbf{h}$, которые остаются немаскированными, и самим собой (эквивалентно демаскированию диагонали из матрицы, показанной в предыдущем разделе). Таким образом, $\mathbf{h}_3$ обновляется маской $[0,0,1,0]$, а вектор $\mathbf{h}_2$ обновляется маской $[0,1,1,0]$. Обновление использует векторы содержания в качестве запроса, ключа и значения.


Каждый вектор запроса $\mathbf{g}_i$, напротив, на каждом уровне обновляется с использованием немаскированных векторов содержания и самого себя. Обновление использует $\mathbf{g}_i$ в качестве запроса, а вектор $\mathbf{h}_j$ в качестве ключей и значений, где $j$ индекс немаскированного токена в контексте $i$.


На рисунке 4 показано, как подчитывается запрос $\mathbf{g}_4^m$ для 4-го токена в $m$-ом слое внутреннего внимания. Это показывает, что $\mathbf{g}_4^m$ представляет собой совокупность "is + 2", "a + 3" и позиции 4, что в точности соответствует контексту, необходимому для вычисления вероятности токена "sentence".


xlnet_figure4


Рис. 4. Двойной механизм внимания для подсчета $\mathbf{g}_4^m$ четвертого токена в $m$-ом слое внутреннего внимания. Стрелки указывают на передачу информацию от векторов. Пересечения линий и кругов указывают на подсчет и агрегацию операций запроса/ключа/значения механизма внутреннего внимания. Желтые линии обозначают обновления потока содержания для третьего символа (зависит только от себя самого) и второго символа (зависит от себя и от третьего символа). Голубые линии обозначают обновление потока запроса (зависит от себя, второго и третьего символа из потока содержания).


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


$inline$Pr("This"|*,"is+2","a+3","sentence+4")$inline$


$inline$Pr("is"|\_\_\_,*,"a+3",\_\_\_)$inline$


$inline$Pr("a"|\_\_\_,\_\_\_,*,\_\_\_)$inline$


$inline$Pr("sentence"|\_\_\_,"is+2","a+3",*)$inline$


Результаты


Работает ли все это? Краткий ответ да. Длинный ответ тоже да. Возможно, это не так уж и удивительно: XLNet опирается на предшествующие современные методы. Она была обучена на корпусе из 30 миллиардов слов (на порядок больше, чем тот, который использовался для обучения BERT'а, и был взят из более разнообразных источников), и это обучение потребовало значительно больше часов вычислительного времени, чем предыдущие модели:


Модель Объем вычислений
ULMFit 1 GPU-день
ELMo 40 GPU-дней
BERT 450 GPU-дней
XLNet 2000 GPU-дней

Таблица 1. Приблизительное время вычислений для обучения актуальных моделей NLP.


Вероятно, более интересно, что исследование абляции XLNet показывает: XLNet работает лучше, чем BERT при честном сравнении (рис. 5). То есть когда модель обучается на том же корпусе, что и BERT, с использованием тех же гиперпараметров и того же количества слоев, она неизменно превосходит BERT. Что еще более интересно, XLNet также превосходит Transformer-XL в честном сравнении. Transformer-XL можно рассматривать как отказ от цели перестановки AR. Постоянное улучшение данного показателя свидетельствует об эффективности этого метода.


Результаты абляции


xlnet_figure5


Рис. 5. Исследование абляции XLNet на четырех бенчмарках: RACE, SQuAD2.0 F1, MNLI mm и SST-2. Результаты сходны с результатами BERT на одном обучающем датасете. Различные столбцы представляют различные настройки обучения, описанные в части 3.7 статьи.


Что не может оценить исследование абляции, так это вклад двухпотокового механизма внутреннего внимания в улучшение производительности XLNet. Последний позволяет механизму внимания явно учитывать позицию целевого токена и вводить дополнительную скрытую емкость в виде векторов потока запросов. Хотя это довольно сложная часть архитектуры XLNet, вполне возможно, что такие модели, как BERT, также могут извлечь выгоду из этого механизма без использования той же цели обучения, что и XLNet.


Авторы


Подробнее..
Категории: Машинное обучение , Nlp , Bert , Xlnet

Категории

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

© 2006-2021, personeltest.ru