Визуализация данных, Машинное обучение

Прогнозирование временных рядов - построение и развертывание моделей

Прогнозирование состояния гидравлического испытательного стенда с течением времени с использованием ансамблевого обучения и нейронных сетей. Часть 1/2

TL / DR: я построил модели для прогнозирования состояния гидравлической установки с использованием различных инструментов, включая tsfresh, ансамблевое обучение и рекуррентные нейронные сети (RNN). Модели развертываются с помощью Flask с использованием HTML-интерфейсов.

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

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

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

Вторая и заключительная часть этой статьи посвящена инструментам прогнозирования AzureML и ARIMA.

Мой подход изложен в этом связанном оглавлении - ссылки открывают новое окно

  1. Определите бизнес-проблему
  2. Обзор данных
  3. Загрузка данных
  4. Разработка функций - взаимодействие с №5 и №6
  5. Выбор функции - взаимодействие с №4 и №6
  6. Разработка и оценка модели - взаимодействие с №4 и №5
  7. Развертывание моделей
  8. Выводы и выводы для бизнеса

Вот поддерживающее репо GitHub.

Определите бизнес-проблему

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

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

Обзор данных

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

Есть 6 датчиков давления, 4 датчика температуры, 1 датчик мощности двигателя, 2 датчика объемного расхода, 2 датчика для измерения эффективности охлаждения и мощности и 1 для измерения общей эффективности.

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

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

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

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

Без тщательно подобранного CSV-файла для применения метода read_csv библиотеки Pandas мне нужен другой подход. Я мог бы отдельно открывать каждый файл, а затем читать каждую строку в каждом файле, например.

import numpy as np
import pandas as pd
cooleff = [] 
ce = open('CE.txt','r') # Read in a new file
for line in ce:
    cooleff.append(line.split()) # Read in each line of opened file, while separating each line.
cooleff = np.reshape(cooleff,(2205,60)) # Reshape the array
cooleffDF = pd.DataFrame(cooleff)       # Convert array to dataframe

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

import numpy as np
import pandas as pd
import glob
locn = ".Downloads\\hyddata\\*.txt" # find all the text files in the path 
files = glob.glob(locn) # Compile list of aforementioned text files
features = {} # use a dictionary to save all the variables
for file in files:
    df = pd.read_csv(open(file),delim_whitespace=True,header=None)

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

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

label = pd.read_csv (".Downloads \\hyddata\\ profile.txt", delim_whitespace=True, header=None)
label.columns = ['cooler_condition', 'valve_condition', 'pump_leak', 'hydraulic_accumulator', 'stable_flag']

Теперь для оценки моего подхода. На рисунке 5 ниже представлена ​​блок-схема стратегий решения.

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

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

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

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

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

univar = series_to_supervised(label[['hydraulic_accumulator']],n_in=2,n_out=1)
# Creating a dataframe above for use in a supervised learning problem. 2 columns will have 2 preceding values in the sequence while the last column features value at that time i.e. the label
univar = univar.values
train,test = univar[:1201,:], univar[1202:,:]
# Creating training and testing sets, by simply splitting the data.
# Final step below splits training and testing sets into features and labels. Remember that only the last column features labels i.e. the y.
xtrain,ytrain = train[:,0:2],train[:,-1]
xtest,ytest = test[:,0:2],test[:,-1]

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

LSTM требуют, чтобы входные данные функций были трехмерными: поэтому приведенный ниже код изменяет форму xtrain и xtest, чтобы иметь одинаковое количество строк, то есть 1201 для xtrain, количество временных шагов (1) и количество функций (2).

xtrain = xtrain.reshape((xtrain.shape[0], 1, xtrain.shape[1]))
xtest = xtest.reshape((xtest.shape[0], 1, xtest.shape[1]))

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

%pip install keras
%pip install tensorflow
import tensorflow
import keras
from keras.models import Sequential
from keras.layers import Activation, Dense, LSTM
model = Sequential()
model.add(LSTM(60, input_shape=(xtrain.shape[1], xtrain.shape[2])))
model.add(Dense(1))
model.compile(loss='mae', optimizer='adam')
# I've used mean absolute error as a performance metric for the LSTM and the Adam optimizer, to minimize this error.
# Fit the model
history = model.fit(xtrain, ytrain, epochs=50, batch_size=20, validation_data=(xtest, ytest), verbose=0, shuffle=False)
# Above: fitting the model above with pre-prepared test and validation data. Below is a plot the results of my fitting and validation.
from matplotlib import pyplot
pyplot.plot(history.history['loss'], label='train')
pyplot.plot(history.history['val_loss'], label='test')
pyplot.legend()
pyplot.show()

В этой статье не будем углубляться в детали выбора значений гиперпараметров. Ниже я замечаю, что производительность модели улучшается за 50 эпох обучения с окончательной ошибкой ~ 10% (ошибка 10,95 / среднее значение ytest).

Вот тенденции для флага стабильности - категориальная метка - и утечки насоса, предположительно сложной метрики для прогнозирования согласно документации репозитория UCI. Последний имеет ошибку 0,04 / среднее значение утечки насоса или 6%.

Разработка функций

Использование функций с разработкой функций: может ли это улучшить производительность? Чтобы ответить на этот вопрос, давайте сначала поймем, что разработка функций для временных рядов фокусируется на извлечении информации о тенденциях. Пример готового пакета python для нашего использования - tsfresh, функция extract_features которого вычисляет исчерпывающий набор функций. Для этой функции требуется фрейм данных, который имеет четко определенный столбец с номерами идентификаторов, по одному номеру идентификатора для каждого временного ряда. Еще один столбец с номерами сортировки поможет нам организовать отметки времени в каждой серии.

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

df.index.name="cycle"
df_T = df.T # Transpose
df_T.index.name="time"
df_T.reset_index(inplace=True)
# Setting indices name for all rows and columns
string = ' cycle'.join(str(e) for e in list(temp_df_transposed.columns))
            temp_df_transposed.columns = string.split(" ")
            
# Adjusting the names of columns to add prefix of "cycle". This prefix acts as a stub to guide the reorientation of the dataframe when using the pandas method wide_to_long below.
temp_df_long = pd.wide_to_long(temp_df_transposed.iloc[1:,:],stubnames='cycle', i=['time'], j='c')
temp_df_long.reset_index(inplace=True)

Возможно, лучший способ понять влияние приложения wide_to_long - посмотреть на «до» и «после». Как показано на рисунке 9 ниже, матрица 60x2206 со временем и циклом на разных осях становится матрицей с обеими переменными на одной оси. Общее количество значений теперь составляет 60 * 2206— 60 (индекс) = 132 300 всего в 1 столбце.

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

features[name[9:-4]] = temp_df_long
for key in list(features.keys()):
    features[key].columns=['seconds','cycle',key]
dfs= [features['...\\features\\CP'],
      features['..features\\CE'],
 .....
     features['rangy\\Downloads\\Hydraulics-main\\features\\VS1']]
from functools import reduce
features_join = reduce(lambda left,right: pd.merge(left,right,on=['seconds','cycle']), dfs)
features_join.head()

Последний сегмент приведенного выше кода использует функцию сокращения Python для применения слияния созданных dfs, чтобы, наконец, получить столбец, который идентифицирует каждый временной ряд, и еще один столбец для сортировки каждого ряда. Первый - это «цикл», а второй - «секунды», как показано на Рисунке 10 ниже.

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

%pip install tsfresh
from tsfresh import extract_features
# Automatic feature extraction using the tsfresh package
extracted_features = extract_features(features_join, column_id = "cycle", column_sort="seconds")
impute(extracted_features)

Вот представление о типах статистических показателей, полученных в результате разработки функций с использованием tsfresh:

extracted_features.columns
Index(['.\features\CE__variance_larger_than_standard_deviation',
       '.\features\CE__has_duplicate_max',
       '.\features\CE__has_duplicate_min',
       ...
       '.\features\CE__mean_abs_change',
       '.\features\CE__mean_change',
       '.\features\CE__mean_second_derivative_central',
       '.\features\CE__median',
       ...
       '.\features\VS1__fourier_entropy__bins_2',
       '.\hyddata\features\VS1__fourier_entropy__bins_3',
       '.\features\VS1__fourier_entropy__bins_100',
       '.\features\VS1__permutation_entropy__dimension_3__tau_1',
       ...
       '.\features\VS1__permutation_entropy__dimension_7__tau_1'] 
      dtype='object', length=13243)

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

См. Характеристики обучения и тестирования модели на рисунке 13 ниже.

Разработка и оценка модели

quadvar_y = label['hydraulic_accumulator']
quadvar_x = extracted_features.values
xtrain_quad,ytrain_quad = quadvar_x[:1201,:], quadvar_y[:1201,]
xtest_quad,ytest_quad = quadvar_x[1202:,:],quadvar_y[1202:,]
xtrain_quad = xtrain_quad.reshape((xtrain_quad.shape[0], 1, xtrain_quad.shape[1]))
xtest_quad = xtest_quad.reshape((xtest_quad.shape[0], 1, xtest_quad.shape[1]))
quadmodel = Sequential()
quadmodel.add(LSTM(60, input_shape=(xtrain_quad.shape[1], xtrain_quad.shape[2])))
quadmodel.add(Dense(5))
quadmodel.add(Dense(1))
quadmodel.compile(loss='mae', optimizer='adam')
# Fit the model
quadhistory = quadmodel.fit(xtrain_quad, ytrain_quad, epochs=50, batch_size=20, validation_data=(xtest_quad, ytest_quad), verbose=0, shuffle=False)

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

Выбор функции

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

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

from tsfresh import select_features
global features_filtered_accum
features_filtered_accum = select_features(extracted_features, label['hydraulic_accumulator'])
global features_filtered_leak
features_filtered_leak = select_features(extracted_features, label['pump_leak'])

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

import xgboost
from xgboost import XGBClassifier, XGBRegressor
xgr = XGBRegressor()
xgc = XGBClassifier()
features_filtered_accum = features_filtered_accum.values
label['hydraulic_accumulator'] = label['hydraulic_accumulator'].values
features_filtered_leak = features_filtered_leak.values
label['pump_leak'] = label['pump_leak']
from sklearn.model_selection import KFold, cross_validate
cv = KFold(n_splits=7,shuffle=True)
cross_validate(xgr,features_filtered_accum,label['hydraulic_accumulator'],cv=cv,scoring='neg_mean_absolute_error')
cross_validate(xgr,features_filtered_leak,label['pump_leak'],cv=cv,scoring='neg_mean_absolute_error')

Разработка и оценка модели

Извлеченные функции значительно улучшаются - средняя абсолютная ошибка ниже (test_score) на несколько порядков ниже той, которую достигла LSTM без выбора функции!

Развертывание моделей

Обращение к ресурсам tsfresh по созданию конвейера scikit-learn с их функциями дало мне необходимое понимание для этого шага.

Приведенный ниже код создает конвейеры scikit-learn для двух разных меток - гидроаккумулятор и флаг стабильности - а затем выгружает конвейеры в сохраненную модель.

from tsfresh.transformers import RelevantFeatureAugmenter
from sklearn.pipeline import Pipeline
pipeline_flag = Pipeline([('augmenter', RelevantFeatureAugmenter (column_id="cycle", column_sort="seconds")), ('xgc' , XGBClassifier())])
pipeline_accum = Pipeline([('augmenter', RelevantFeatureAugmenter (column_id="cycle", column_sort="seconds")), ('xgr', XGBRegressor())])
y_stable_flag = label['stable_flag']
y_hydraulic_accumulator = label['hydraulic_accumulator']
X = pd.DataFrame(index = y_stable_flag.index)
pipeline_flag.set_params(augmenter__timeseries_container=features_join)
pipeline_accum.set_params(augmenter__timeseries_container=features_join)
pipeline_flag.fit(X,y_stable_flag)
pipeline_accum.fit(X,y_hydraulic_accumulator)
import pickle
pickle.dump(pipeline_accum,open('pipeline_accum.pkl','wb'))
pickle.dump(pipeline_flag,open('pipeline_flag.pkl','wb'))

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

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

Мои #comments ниже предназначены для читателей и не являются частью сценария HTML. На рисунке 15 после этого сценария приведен снимок того, что видит пользователь.

<html>
<body>
<h3>Prediction of Stability Flag and Hydraulic Accumulation </h3>
# Comment: this is the title of the webpage.
<div>
<form action="/predict" method="POST">
<label for="timstmp">Cycle number</label>
# Comment: this is the label and variable that users will be asked to enter
<input type="number" step="1" id="timstmp" name="timstmp">
# Comment: this is the actual box into which a user enters the above variable.
<br>
<input type="submit" value="Submit">
# Comment: creating a submit button so the user can run the query.
</form>
</div>
</body>
</html>

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

<!doctype html>
<html>
<body>
<h1> {{ prediction_text}}</h1>
</body>
</html>

Если я ввел определенный цикл на странице индекса, то вот результат, когда я нажимаю «Отправить» - опять же, я не стремлюсь к эстетике в этой статье, только к функциональности.

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

3. Скрипт, написанный для загрузки сохраненных моделей с помощью pickle и последующего ввода данных, указанных на странице hyindex. Загруженные модели делают прогнозы, используя входные данные, и эти прогнозы возвращаются на страницу гипотез. Опять же, мои #comments ниже предназначены для читателей, а не являются частью сценария HTML.

import pandas as pd, numpy as np
import pickle
import flask
from flask import Flask, request, jsonify, render_template
app = Flask(__name__) #Creating instance of Flask class for web app
@app.route('/')
def Hm():
    return render_template('hyindex.html')
#Code above routes any user from localhost:5000 to the hyindex.html webpage through the Hm() function.
model_accum = pickle.load(open('pipeline_accum.pkl','rb'))
model_flag = pickle.load(open('pipeline_flag.pkl','rb'))
# Loading models that were saved earlier.
# Code below reads the number specified in the request form, converts it into a dataframe for use in the saved model's predict method. The dataframe's index must be set as equal to the values for running the code.
@app.route('/predict', methods=['POST'])
def predict():
    inputt = [int(x) for x in request.form.values()]
    xtest = np.array(inputt)
    xtest_df = pd.DataFrame(xtest)
    xtest_df.set_index(xtest,inplace=True)
    Xtest_df = pd.DataFrame(index = xtest_df.index)
    prediction_accum = model_accum.predict(Xtest_df)
    prediction_flag = model_flag.predict(Xtest_df)
#Having made my predictions above, I'll now send these over to hypredict with the variable name prediction_text.
    
    return render_template('hypredict.html',prediction_text= 'Hydraulic accumulation prediction is ' + format(prediction_accum) + ' and stability flag prediction is ' + format(prediction_flag))
#Python assigns the name "_main_" to this script; hence below, we tell the app to run if this script is run.
if __name__ == "__main__": 
    app.run(debug=True)

Поэтому, чтобы на самом деле запустить эту развернутую модель, я просто запускаю приведенный выше сценарий на терминале, как показано ниже на рисунке 17. Я вызвал сценарий выше hyapp.py

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

Выводы и выводы для бизнеса

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

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

В следующей и последней статьях этой серии к этой проблеме будут применены ARIMA и AzureML. Я приветствую любые комментарии.