
Вступление
Одно из моих профессиональных устремлений в течение некоторого времени на самом деле было грязным и грязным, чтобы написать немного кода ML и, наконец, получить достойное представление о DNN (глубоких нейронных сетях) и Tensorflow.. несколько курсов по Coursera и Udemy, но ничто не сравнится с работой над реальным проектом. Попутно я работал над проектом, который назвал Octopus (должно быть, это были все шарики Tako-pachi или японского осьминога, которые я ел). Octopus - это проект по классификации заявок об ошибках Jira для инженерных групп, использующих традиционный ML, и я хотел посмотреть, можно ли применить DNN к аналогичному проекту: присвоение приоритета заявкам об ошибках Jira на основе описания.
У меня появился шанс сделать это, когда я записался на онлайн-курс AICamp Deep Learning for Developers - интенсивный 4-недельный курс, который познакомит вас с основами DNN, но в очень практичном виде. манера. В рамках курса я приступил к работе над проектом Capstone, и вуаля, у меня появился шанс по-настоящему покопаться!
Было всего 2 проблемы:
- Поскольку в этом проекте будет контролируемое обучение, мне понадобятся данные для обучения с пометкой. Я не мог использовать данные Octopus, поскольку они внутренние. Также набор данных был слишком мал (сотни против десятков тысяч, необходимых для обучения DNN).
- Все примеры в курсе AICamp были сосредоточены в основном на классификации изображений, например. MNIST, CIFAR. Ничего о классификации текста.
Я также хотел получить практический опыт по следующим вопросам:
- Обучите модель с помощью графических процессоров, чтобы почувствовать ускорение обучения.
- Упакуйте модель в какую-нибудь развертываемую форму (возможно, используя pickle).
- Упакуйте предсказатель как HTTP POST API с помощью Apache Flask.
- Создайте Docker-образ пакета предиктора.
- Создайте конвейер CI для сборки пакета и развертывания в реестре Docker.
Я смог выполнить это, и вы можете найти Jupyter Notebooks, data, Dockerfile, Gitlab YAML здесь:
Поскольку одна из целей, которую я поставил перед собой, заключалась в обучении модели с использованием графических процессоров, использование Google Colab стало естественным (Colab - это инструмент Google для ноутбуков, эквивалентный Jupyter). Кроме того, в Google Colab встроен Tensorflow 2 и есть несколько отличных улучшений для Jupyter Notebook, которые упрощают работу с Python и наукой о данных. Большинство записных книжек в репо предназначены для работы в Google Colab.
Данные
Оказывается, кто-то ранее открывал исходные данные заявок Jira из нескольких проектов с открытым исходным кодом и импортировал их в базу данных PostgresSQL. Вы можете найти его здесь: [1]. Команда, которая работала над извлечением этого набора данных, также написала об этом статью [2].
Следующим шагом была установка PostgresSQL и выполнение импорта данных.
После этого мне нужен был набор данных приличного размера, чтобы увидеть, как будут работать традиционные алгоритмы машинного обучения, поскольку, в отличие от Octopus, мы будем классифицировать билеты по приоритету ошибок, а не по команде, поскольку нет командных данных, являющихся проектами с открытым исходным кодом.
Я выбрал для этого сообщения об ошибках проекта Zookeeper:
select * from jira_issue_report
where status = ‘Closed’
and type = ‘Bug’
and project = ‘ZOOKEEPER’
not (description is null or description = ‘’)
and priority is not null
Вы можете найти CSV-файл в репозитории с именем: JIRA_OPEN_DATA_ZOOKEEPER.csv. У этого было ~ 400 строк.
Я также извлек ВСЕ билеты в CSV: JIRA_OPEN_DATA_ALL.csv. У этого было около 200 тысяч строк.
Однако вскоре я понял, что этот набор данных слишком велик для обработки в Google Colab (что было жаль), и поэтому я создал подмножество примерно из 40 тысяч строк: JIRA_OPEN_DATA_LARGESET.csv с помощью SQL-запроса:
select * from jira_issue_report
where status = ‘Closed’
and type = ‘Bug’
and (project = ‘FLEX’
or project = ‘JBIDE’
or project = ‘RF’
or project = ‘SPR’
or project = ‘HBASE’)
and not (description is null or description = ‘’)
and priority is not null
Классификация текста с использованием традиционного машинного обучения
Примечание. Я лишь кратко рассмотрю этот раздел, так как он не является «сутью» данной статьи. Хорошее введение в тему см. В [4].
В ExploratoryDataAnalysis.ipynb был проведен очень простой исследовательский анализ данных. Также для определения основы классификации использовался очень простой классификатор наивный байесовский объект. Это следовало примеру, найденному в [3].
Несколько замечаний:
- Точность была ~ 49% на наивном байесовском классификаторе текстовых блобов.
- Цели классификации очень предвзяты. Вместо того, чтобы балансировать, мы хотели посмотреть, насколько это будет плохо / хорошо.
- Чтобы получить TF-IDF для использования в качестве функций для традиционной классификации машинного обучения, я провел анализ слов, чтобы извлечь избыточные данные, например. Идентификаторы, адреса электронной почты, URL-адреса. Также была проведена токенизация в слова и биграммы.
Все это было сделано с использованием библиотек Numpy, Pandas, Sci-kit Learn и Matplotlib.
Руководство о том, как это сделать, можно найти в [4] и [5].
Затем я использовал обработанный набор данных и запустил его: Наивный байесовский, логистический регресс, SVM и классификаторы случайного леса, а также выполнил некоторую настройку гиперпараметров для случайного леса, классифицированного в соответствии с руководство из [6].
Все это можно найти в ModelAnalysis.ipynb репозитория. В результате точность составила всего ~ 33%. (В то время я не пытался использовать XGBoost. Возможно, позже!)
Классификация текста с использованием DNN и Tensorflow
Если вы посмотрите на литературу, применение DNN к классификации текста обычно можно разделить на следующие подходы:
- Использование рекуррентной нейронной сети (RNN), в частности LSTM (долгосрочная, краткосрочная память).
- Использование сверточной нейронной сети (CNN).
- Использование встраивания слов, например word2vec, GloVe.
- Использование предварительно обученной модели в качестве базовой модели и замораживания / размораживания слоев, например. БЕРТ.
Я решил пойти с CNN, так как я немного познакомился с ним после посещения курса AICamp. Ключевые различия между стандартной CNN для классификации изображений и классификации текста заключаются в следующем:
- Использование слоя внедрения для сопоставления каждого слова с векторным пространством
- Использование 1D слоев свертки вместо 2D
Остальная часть сети представляет собой довольно стандартный CNN для классификации изображений.
Чтобы получить хорошее представление о том, как CNN могут применяться к классификации текста, я рекомендую либо посмотреть видео Лукаса Бевальда на YouTube «Классификация текста с использованием сверточных нейронных сетей (2019)» - см. [7], либо пройти курс ленивого программиста по Udemy » Tensorflow 2.0: глубокое обучение и искусственный интеллект »[8]. Есть раздел о применении LSTM и CNN для двоичной классификации текста.
Ключевая идея, лежащая в основе слоя «Встраивание», заключается в следующем: если мы сопоставим каждое слово с N-мерным векторным пространством, то «расстояние» между словами (то есть, как каждое слово соотносится друг с другом) можно «узнать», настроив расположение каждого слова в векторном пространстве.
Что касается слоев 1D Convolution - идея та же, что и слоев 2D Convolution, но вместо работы с n x n ядром мы работаем с 1 x n ядрами, которые являются числовыми представлениями слов. Ключевая интуиция, лежащая в основе этого, заключается в том, что слова имеют отношения со словами рядом с ними (та же идея, что и использование н-граммов в традиционном НЛП).
Подготовка данных
Подобно традиционному ML, где мы конвертируем необработанный текст в матрицу TF-IDF, здесь мы должны сделать нечто подобное. В основном создайте серию последовательностей слов и дополните ее нулями в конце.
# Convert sentences to sequences MAX_VOCAB_SIZE = 20000 tokenizer = Tokenizer(num_words=MAX_VOCAB_SIZE) tokenizer.fit_on_texts(df_train) sequences_train = tokenizer.texts_to_sequences(df_train) sequences_test = tokenizer.texts_to_sequences(df_test) # get word -> integer mapping word2idx = tokenizer.word_index V = len(word2idx) # pad sequences so that we get a N x T matrix data_train = pad_sequences(sequences_train) # get sequence length T = data_train.shape[1] data_test = pad_sequences(sequences_test, maxlen=T)
Исследование / настройка модели
В DNNModelAnalysis.ipynb вы найдете исследование модели:
- Я начал с базовой модели CNN, предложенной для бинарной классификации, и модифицировал ее для обработки классификации по нескольким категориям.
- Постепенно добавляйте больше сверточных и плотных слоев, чтобы модель лучше училась.
- Добавление пакетной нормализации и исключения для уменьшения чрезмерной подгонки.
- Наконец добавляем раннюю остановку.
В финальной модели было следующее:
- Встраиваемый слой
- 6 одномерных сверточных слоев с размером ядра 3
- Слой MaxPooling
- 4 одномерных сверточных слоя с размером ядра 3
- Слой MaxPooling
- 2 сверточных слоя ID с размером ядра 3
- Глобальный максимальный пул
- 4 плотных слоя с выпадением
- Все слои с пакетной нормализацией
- Ранняя остановка
Код:
D = 20 early_stopper = EarlyStopping(monitor=’val_loss’, patience=5, restore_best_weights=True) model = Sequential() model.add(Input(shape=(T,))) model.add(Embedding(V + 1, D)) model.add(Conv1D(32, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(Conv1D(32, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(Conv1D(32, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(Conv1D(32, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(Conv1D(32, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(Conv1D(32, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(MaxPooling1D(3)) model.add(Conv1D(64, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(Conv1D(64, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(Conv1D(64, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(Conv1D(64, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(MaxPooling1D(3)) model.add(Conv1D(128, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(Conv1D(128, 3, activation=’relu’)) model.add(BatchNormalization()) model.add(GlobalMaxPooling1D()) model.add(Dense(units=800, activation=’relu’)) model.add(BatchNormalization()) model.add(Dropout(0.2)) model.add(Dense(units=400, activation=’relu’)) model.add(BatchNormalization()) model.add(Dropout(0.2)) model.add(Dense(units=200, activation=’relu’)) model.add(BatchNormalization()) model.add(Dropout(0.2)) model.add(Dense(units=100, activation=’relu’)) model.add(BatchNormalization()) model.add(Dropout(0.2)) model.add(Dense(units=num_classes, activation=’softmax’)) model.compile(optimizer=’adam’, loss=’categorical_crossentropy’, metrics=[‘accuracy’]) history = model.fit(data_train1, training_labels1, epochs=40, batch_size=8, verbose=True, validation_split=.2, callbacks=[early_stopper]) plot_training_history(history, model, data_test1, test_labels1)
Достигнутая точность составляет ~ 54%, хотя это в некотором роде сравнение яблок с апельсинами, если сравнивать его с традиционными моделями машинного обучения, но набор данных LARGESET был бы слишком большим для моделей машинного обучения, а набор данных ZOOKEEPER был бы слишком мал для DNN.
Построение развертываемой модели
Для этого есть 3 компонента:
- Обучение и сохранение модели
- Создание простой оболочки HTTP POST API
- Создание простого конвейера CI
Примечание. Существует несколько способов создания развертываемой модели для развертывания облака: у Google Cloud (GCP) есть несколько способов сделать это, которые хорошо документированы, как и Amazon (AWS), например. Sagemaker и Microsoft Azure.
Обучение и сохранение модели
DNNProductionTraining.ipynb содержит шаги, которые я предпринял для обучения модели и сохранения модели в .h5 файл. Он разработан для работы в Google Colab с использованием графического процессора и среды выполнения с высокой оперативной памятью. Обучение на традиционном процессоре займет значительно больше времени.
Модель такая же, как окончательная модель, описанная в последнем разделе. Единственная разница в том, что на этот раз он обучается со всем набором данных LARGESET (без разделения на поезд / тест).
После обучения он сохраняется:
model.save(“/content/gdrive/My Drive/Colab Notebooks/Octopus2/jira_open_data_classifier.h5”, save_format=’tf’)
Затем мы тестируем модель, чтобы увидеть, будет ли она предсказывать.
test_sentance = [‘The hard coded host of the client can only let it run on the same host as the thrift server.’] test_seq = tokenizer.texts_to_sequences(test_sentance) test_padded = pad_sequences(test_seq, maxlen=T) p = model.predict_classes(test_padded)
Создание простой оболочки HTTP POST API
По сути, это app.py в репо.
Это просто приложение Apache Flask, которое:
- Загружает модель из
jira_open_data_classifier.h5 - Извлекает «заголовок» и «описание» из полезной нагрузки JSON POST.
- Выполняет прогноз и выводит прогноз. Этот код похож на тестовый код в предыдущем разделе.
Для запуска app.py, просто «python3 app.py»
test.sh содержит команду curl для выполнения POST:
curl -X POST -H “Content-Type: application/json” \
-d ‘{“title”: “TestSizeBasedThrottler fails occasionally”, “description”: “Every now and then TestSizeBasedThrottler fails reaching the test internal timeouts.I think the timeouts (200ms) are too short for the Jenkins machines.On my (reasonably fast) machine I get timeout reliably when I set the timeout to 50ms, and occasionally at 100ms.”}’ \
“http://localhost:5000/api_predict"
Создание простого конвейера CI
Одна приятная вещь, которую я обнаружил в Gitlab, заключается в том, что они предоставляют шаблоны для .gitlab-ci.yml (для CI) и Dockerfile. Чтобы создать конвейер CI, вам просто нужно на главной странице проекта нажать кнопку «Настроить CI / CD», и будет создан базовый бланк .gitlab-ci.yml и появится раскрывающийся список с надписью «Применить шаблон», который позволит вам выберите из множества типов построения конвейеров, например. Android, C ++ и т. Д. Для этого я выбрал «Docker», так как хотел создать контейнер Docker.


Примечание: если вы никогда не создавали контейнер Docker и не публиковали контейнер Hello World в реестре Docker, я предлагаю перейти на docker.com и следовать руководству Quickstart, чтобы создать простой контейнер Hello World и опубликовать его. в реестре Docker.
Вернувшись к .gitlab-ci.yml, вы заметите следующие переменные среды:
$CI_REGISTRY_USER- это идентификатор пользователя, который вы используете для входа в реестр (в данном случае docker.com)$CI_REGISTRY_PASSWORD- это пароль, который вы используете для входа в реестр (в данном случае docker.com)$CI_REGISTRY- это можно удалить, поскольку мы используем реестр Docker по умолчанию. Он понадобится, если у вас есть отдельный реестр Docker, например. AWS
Эти переменные среды необходимо настроить в разделе «Настройки -› CI / CD - ›Переменные». Оказавшись там, вы можете добавить эти переменные как пары ключ-значение.

Если вы все настроили правильно (фактически, когда в репозитории есть .gitlab-ci.yml), Gitlab запустит сборку. Перейдите в «CI / CD -› Pipelines », вы должны увидеть, что некоторые из них работают.

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

К сожалению, конвейер, скорее всего, выйдет из строя, поскольку файл Dockerfile еще не создан. Для этого перейдите в «Обзор проекта» и нажмите кнопку «Новый файл» (если вы не видите кнопку «Новый файл», вы также можете нажать кнопку «Журнал изменений»).

Затем вам будет разрешено выбрать шаблон. В качестве основы я выбрал Python.
Созданный мной Dockerfile был:
FROM python:3.6 WORKDIR /usr/src/app COPY requirements.txt /usr/src/app/ RUN pip install --no-cache-dir -r requirements.txt COPY . /usr/src/app # Inform Docker that the container is listening on the specified port at runtime. EXPOSE 5000 # Run command CMD ["python", "app.py"]
Как только это будет сделано, сборка должна завершиться успешно, т. Е. образ докера создается и публикуется в реестре.
использованная литература
[1] Социальный репозиторий Jira, Марко Орту и др. Https://github.com/marcoortu/jira-social-repository
[2] Набор данных репозитория JIRA, Марко Орту и др., Сентябрь 2015 г. https://www.researchgate.net/publication/301370380_The_JIRA_Repository_Dataset
[3] Учебное пособие: создание системы классификации текста, TextBlob https://textblob.readthedocs.io/en/dev/classifiers.html
[4] Текстовая аналитика для начинающих с использованием NLTK, Авинаш Навлани, декабрь 2019 г. https://www.datacamp.com/community/tutorials/text-analytics-beginners-nltk
[5] Понимание классификаторов случайных лесов в Python, Авинаш Навлани, май 2019 г. https://www.datacamp.com/community/tutorials/random-forests-classifier-python
[6] Настройка гиперпараметров случайного леса в Python, Уилл Кёрсен, январь 2018 г. https://towardsdatascience.com/hyperparameter-tuning-the-random-forest-in-python-using-scikit-learn-28d2aa77dd74
[7] Классификация текстов с использованием сверточных нейронных сетей (2019), Лукас Бевальд https://youtu.be/8YsZXTpFRO0
[8] Tensorflow 2.0: глубокое обучение и искусственный интеллект, ленивый программист https://www.udemy.com/course/deep-learning-tensorflow-2/