Здравствуйте, коллеги!

Из последних известий по нашим планируемым новинкам из области ML/DL:

Нишант Шакла, "Машинное обучение с Tensorflow" — книга в верстке, ожидается в магазинах в январе

Делип Рао, Брайан Макмахан, "Обработка естественного языка на PyTorch" — контракт подписан, планируем приступать к переводу в январе.

В данном контексте мы хотели в очередной раз вернуться к болезненной теме — слабой проработке темы ML/DL в языке Java. Из-за явной незрелости этих решений и алгоритмов на языке Java мы когда-то приняли решение отказаться от книги Гибсона и Паттерсона по DL4J, и публикуемая сегодня статья Хамфри Шейла (Humphrey Sheil) подсказывает, что мы, вероятно, были правы. Предлагаем познакомиться с мыслями автора о том, каким образом язык Java мог бы наконец составить конкуренцию Python в машинном обучении

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

Язык Java практически отсутствует в сегменте машинного обучения. Почти не существует фреймворков ML, которые были бы написаны на Java (есть DL4J, но лично я не знаю никого, кто бы ей пользовался, в MXNet есть API на Scala, но не на Java, причем, сам этот фреймворк написан не на Java). У Tensorflow есть неполный API на Java, однако, Java занимает огромную долю в enterprise-разработке, за последние 20 лет в этот язык были инвестированы триллионы долларов практически во всех мыслимых предметных областях: финансовые услуги, электронные сделки, интернет-магазины, телекоммуникации – список можно продолжать бесконечно. В машинном обучении «первый среди равных» — это Python, а не Java. Лично мне очень нравится программировать как на Python, так и на Java, но Фрэнк Греко сформулировал интересный вопрос, натолкнувший меня на размышления:
Зачем Java конкурировать с Python в ML? Почему бы Java не взяться за то, чтобы довести до ума серьезную поддержку ML?

Важно ли это?

Давайте обоснуем эту тему. С 1998 года язык Java – в высшей лиге, без него не обошлось никаких эволюционных и революционных событий в энтерпрайзе. Речь и о веб-технологиях, и о мобильных, о сравнении браузерных и нативных решений, о системах обмена сообщениями, поддержке глобализации i18n и l10n, горизонтальном масштабировании и поддержке хранилищ для любой enterprise-информации, какую только можно вообразить – от реляционных баз данных до Elasticsearch.

Такой уровень безусловной поддержки обеспечивает весьма здоровую культуру, сложившуюся в Java-командах: «мы сможем», «закатай рукава и пиши код». Нет такого волшебного компонента или API, который нельзя было бы дополнить или заменить хорошей командой Java-разработчиков.

Но этот принцип не работает в машинном обучении. Здесь у Java-команд остается два выхода:

  1. Переучиться / доучиться на Python.
  2. Использовать сторонний API, чтобы добавить возможности машинного обучения в имеющуюся enterprise-систему.

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

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

Поэтому важно и желательно, чтобы в языке и на платформе Java появилась первоклассная поддержка машинного обучения. В противном случае есть риск, что в ближайшие 5-10 лет Java будет вытеснен другими языками, где ML поддерживается лучше.

Почему Python настолько доминирует в ML?

Для начала давайте обсудим, почему Python занял лидирующие позиции в сфере машинного и глубокого обучения.

Подозреваю, все началось с совершенно невинного свойства? — ?поддержки срезов списков (list slicing). Такая поддержка расширяема: любой класс Python, реализующий методы __getitem__ и __setitem__, можно разрезать при помощи такого синтаксиса. Следующий листинг демонстрирует, насколько проста и естественна данная возможность Python.

a = [1, 2, 3, 4, 5, 6, 7, 8]
print(a[1:4])
#возвращает [2, 3, 4] – выбираем срез со средними элементами

print(a[1:-1])
# возвращает [2, 3, 4, 5, 6, 7] - пропускаем 0-й и последние элементы

print(a[4:])
#возвращает [5, 6, 7, 8] – конечная точка задается по умолчанию

print(a[:4])
#возвращает [1, 2, 3, 4] – начальная точка задается по умолчанию

print(a[:4:2])
#возвращает [1, 3] (обратите внимание на приращение среза)

Разумеется, это еще не все. Код на Python гораздо более компактный и лаконичный по сравнению со «старым» кодом Java. Исключения поддерживаются, но не проверяются, а разработчики могут легко писать скрипты Python, пригодные в качестве расходного материала – попробовать, «как все работает», не утопая в Java-мировоззрении «все есть класс». С Python легко втянуться в работу.

Однако, сейчас важнейший фактор перевеса на мой взгляд (что не мешает мне признавать, какую каторжную работу сообщество Python проделывает, чтобы поддерживать связь между Python 2.7 и Python 3) – в том, что им удалось создать гораздо более качественно спроектированную и быструю библиотеку для операций с числами ?—?NumPy. Numpy построена вокруг ndarray?—?объекта, представляющего собой N-мерный массив. Цитирую документацию: “Главный объект в NumPy – это однородный многомерный массив. Это таблица элементов (обычно чисел), всех одного типа, индексируемых при помощи кортежа положительных целых чисел”. Вся работа NumPy основана на записи ваших данных в ndarray и последующих операциях над ними. NumPy поддерживает разнообразные варианты индексирования, широковещания, векторизации для скорости и вообще позволяет разработчикам с легкостью создавать крупные числовые массивы и манипулировать ими.

В следующем листинге на практике показано индексирование и широковещание в ndarray — это ключевые операции в ML / DL.

import numpy as np

# Простой пример широковещания
a = np.array([1.0, 2.0, 3.0])
b = 2.0
c = a * b
print(c)
# возвращает [ 2.  4.  6.] - скаляр b автоматически продвигается / широковещается и применяется к вектору для создания c

#2-d (матрица с рангом 2) индексирование в NumPy – распространяется и на тензоры - т.e. ранг > 2
y = np.arange(35).reshape(5,7)
print(y)
# array([[ 0,  1,  2,  3,  4,  5,  6],
#        [ 7,  8,  9, 10, 11, 12, 13],
#        [14, 15, 16, 17, 18, 19, 20],
#        [21, 22, 23, 24, 25, 26, 27],
#        [28, 29, 30, 31, 32, 33, 34]])
print(y[0,0])
# доступ к отдельной ячейке – нотация в построчном порядке, возвращает 0
print(y[4,])
# возвращает всю строку 4: array([28, 29, 30, 31, 32, 33, 34])
print(y[:,2])
# возвращает весь столбец 2: array([ 2,  9, 16, 23, 30])

Работая с крупными многомерными числовыми массивами, мы метим в самое сердце программирования для машинного обучения, и в особенности – глубокого обучения. Глубокие нейронные сети – это решетки из узлов и ребер, смоделированные на уровне чисел. Операции во время выполнения при обучении сети или выполнении вывода на ее основе требуют быстрого перемножения матриц.

Благодаря NumPy удалось сделать гораздо больше ?—?scipy, pandas и множество других библиотек, основанных на NumPy. Ведущие библиотеки глубокого обучения (Tensorflow от Google, PyTorch от Facebook) серьезно развивают Python. У Tensorflow есть другие API для Go, Java и JavaScript, но они неполны и считаются нестабильными. PyTorch исходно была написана на Lua, и испытала настоящий всплеск популярности, когда в 2017 году перешла с этого откровенно нишевого языка в основную экосистему ML Python в 2017 году.

Недостатки Python

Python – не идеальный язык, не идеальна и наиболее популярная среда его исполнения, CPython. Ей присуща?глобальная блокировка интерпретатора (GIL), так что масштабирование — дело не простое. Более того, фреймворки Python для глубокого обучения, например, PyTorch и Tensorflow, по-прежнему передают ключевые методы непрозрачным реализациям. Например, библиотека cuDNN от NVidia сильнейшим образом повлияла на область применения реализации RNN / LSTM в PyTorch. RNN и LSTM (рекуррентные нейронные сети и долгая краткосрочная память) – очень важный инструментарий DL для бизнес-приложений, в частности, потому, что они специализируются на классификации и прогнозировании последовательных рядов переменной длины ?—?напр. отслеживание навигации (clickstream) в вебе, анализ текстовых фрагментов, пользовательских событий и пр.

Ради непредвзятости к Python следует отметить, что такая непрозрачность/ограниченность касается практически любого фреймворка для ML/DL кроме написанных на C или C++. Почему? Потому что для достижения максимальной производительности для базовых, высоконагруженных операций, таких как перемножение матриц, разработчики опускаются «поближе к металлу» насколько это возможно.

Что нужно Java, чтобы конкурировать на этом поле?

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

  1. Добавить в ядро языка нативную поддержку индексирования/срезов, чтобы можно было конкурировать с Python при всей простоте его использования и выразительности. Возможно, выстраивать такие возможности в Java следует вокруг уже существующей упорядоченной коллекции, интерфейса List<E>. Для такой поддержки также потребуется признать необходимость перегрузки – она нужна для выполнения пункта #2.
  2. Создать реализацию тензора — вероятно, в пакете java.math, но также с выходом на Collections API. Этот набор классов и интерфейсов мог бы работать эквивалентно ndarray и обеспечил дополнительную поддержку индексирования, в частности, те три типа индексирования, что доступны в NumPy: доступ к полям, простейшие срезы и продвинутое индексирование, необходимое для программирования.
  3. Обеспечить широковещание —? скаляры и тензоры произвольных (но совместимых) размерностей.

Если бы три эти задачи удалось выполнить в ядре языка Java и среде исполнения, перед нами открылся бы путь к созданию “NumJava”, эквивалентной NumPy. Проект Panama также мог бы пригодиться для обеспечения векторизованного низкоуровневого доступа к быстрым тензорным операциям, выполняемым на CPU, GPU, TPU и не только, чтобы Java ML могло стать быстрейшим из себе подобных.

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

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

// Как мог бы выглядеть тензор Java с возможностью широковещания
// Использование var-синтаксиса в Java 10 для краткости
// В Java не поддерживается перегрузка операторов, поэтому мы не можем сделать "a * b"
// Следует ли добавить это в список требований? 
var a = new Tensor([1.0, 2.0, 3.0]);
var b = 2.0;
var c = a.mult(b);

/**
 * А вот фрагмент кода, демонстрирующий, как мог бы выглядеть класс Tensor в  Java.
 */

import static java.math.Numeric.arange;

//arange возвращает экземпляр тензора, а reshape определяется на тензоре
var y = arange(35).reshape(5,7);
System.out.println(y);
// tensor([[ 0,  1,  2,  3,  4,  5,  6],
//        [ 7,  8,  9, 10, 11, 12, 13],
//        [14, 15, 16, 17, 18, 19, 20],
//        [21, 22, 23, 24, 25, 26, 27],
//        [28, 29, 30, 31, 32, 33, 34]])
System.out.println(y[0,0]);
// доступ к отдельной ячейке – нотация в построчном порядке, возвращает 0
System.out.println(y[4,]);
// возвращает все из 4-й строки (5-я строка начинается с индекса 0): tensor([28, 29, 30, 31, 32, 33, 34])
System.out.println(y[:,2]);
// возвращает все из 2-го столбца (3-й столбец начинется с индекса 0): tensor([ 2,  9, 16, 23, 30])

Перспектива и призыв к действию

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

Исходя с прагматичных позиций, изложенных в этой статье, на Java можно написать не меньше фреймворков для машинного и глубокого обучения (работающих на JRE), чем уже имеющихся фреймворков для веба, долговременного хранения или синтаксического разбора XML ?—? вы только представьте! Можно вообразить фреймворки Java с поддержкой сверточных нейронных сетей (CNN) для ультрасовременных реализаций компьютерного зрения, таких реализаций рекуррентных нейроонных сетей LSTM для последовательных датасетов (имеющих ключевое значение для бизнеса), с самыми современными возможностями ML, такими как автоматическая дифференциация и не только. Затем эти фреймворки помогли бы воплотить и подпитывать следующее поколение энтерпрайз-систем, которые можно было бы гладко интегрировать с уж имеющимися Java-системами, пользуясь все тем же инструментарием ?—?IDE, фреймворками тестирования, непрерывной интеграцией. Что наиболее важно – они будут написаны и будут поддерживаться нашими людьми. Если вы фанат Java – разве вам не нравится такая перспектива?

Комментарии (4)


  1. Sap_ru
    12.11.2018 19:18

    А причём тут Java? По-моему все «претензии» можно легко написать в виде отдельной независимой библиотеки. Низкий уровень можно даже в нативный код через JNI запихать.


    1. sshikov
      12.11.2018 19:38

      Автор просто не специалист в Java. Он разбирается в своей области, но зачем-то обобщает на все. Даже тот факт, что вообще не упомянут Spark ML, говорит о многом.

      Ну и вот такие фразы:
      >есть API на Scala, но не на Java,

      Они как-бы намекают. Я вот пишу на Spark, и тот факт, что он, внезапно, написан на Scala, он лишь слегка напрягает. Не более. А так в целом Scala и Java вполне способны взаимодействовать, так что любой ML проект на скале будет использован в Java без проблем.


  1. mikhailian
    12.11.2018 19:29

    Все современные гуру машинного обучения сами обучались на OpenNLP и Weka. И не было лучше библиотеки для натягивания принципов машинного обучения на студентов, чем OpenNLP.


    И всё это было исключительно на Java.


    А писание алгоритмов на C++ и популяризация обвязок на Питоне — это новое веяние. Ему и 10 лет нет.


  1. MasteR_GeliOS
    12.11.2018 20:29

    Сама по себе данная перспектива мне чрезвычайно приятна. Но я скорее вижу большую вероятность развития ML на основе JVM с использованием Kotlin и его большей выразительностью.