Содержание
- Вступление
- Понимание данных
- Исследовательский анализ данных
- Функциональная инженерия
- Связь между географическими местоположениями с LSTM
- Описание методологии
- Окончательные предварительно обработанные данные
- Обучение и оценка
- Заключение
- использованная литература
Вступление
По мере того, как мы глубоко погружаемся в сферу искусственного интеллекта, мы сталкиваемся с путями, с помощью которых ИИ может использовать человеческую жизнь и потенциально спасти ее от бедствий в ближайшем будущем. Одним из таких небольших шагов к этому далекому, но многообещающему будущему является использование данных из прошлого и поиск некоторых закономерностей, которые являются универсальными по своей природе и которые могут обещать некоторые точные прогнозы, тем самым помогая людям минимизировать последствия стихийных бедствий. В этой статье я собираюсь объяснить свой подход к прогнозированию ущерба, нанесенного зданию во время землетрясения в Непале в 2015 году. Конкурс проводится компанией Driven Data.
Полный код этого проекта можно найти здесь.
Понимание данных
Мы пытаемся предсказать порядковую переменную damage_grade
, которая представляет уровень повреждения здания, пострадавшего от землетрясения. Различают 3 степени повреждений:
1
означает низкий урон2
представляет собой средний урон3
представляет собой почти полное разрушение
В этом наборе данных 39 столбцов, где столбец building_id
является уникальным и случайным идентификатором.
Описание
geo_level_1_id
,geo_level_2_id
,geo_level_3_id
(тип: int): географический регион, в котором находится здание, от самого большого (уровень 1) до наиболее специфичного субрегиона (уровень 3). Возможные значения: уровень 1: 0-30, уровень 2: 0-1427, уровень 3: 0-12567.count_floors_pre_eq
(тип: int): количество этажей в здании до землетрясения.age
(тип: int): возраст здания в годах.area_percentage
(тип: int): нормализованная площадь контура здания.height_percentage
(тип: int): нормализованная высота контура здания.land_surface_condition
(тип: категориальный): состояние поверхности земли, на которой построено здание. Возможные значения: n, o, t.foundation_type
(тип: категориальный): тип фундамента при строительстве. Возможные значения: h, i, r, u, w.
Подробную информацию обо всех 38 функциях можно найти здесь.
Загрузка набора данных
X = pd.read_csv('/content/drive/My Drive/data_ml/Richters Predictor Earthquake Damage/train_values_RP.csv', index_col='building_id') Y= pd.read_csv('/content/drive/My Drive/data_ml/Richters Predictor Earthquake Damage/train_labels_RP.csv',index_col='building_id')
Исследовательский анализ данных
- Посчитать участок застройки
Большинству зданий нанесен ущерб 2-го уровня.
Особенности проектирования идентификатора географического уровня
Для этого проекта я использовал классификатор случайных лесов. Классификатор случайного леса - это метод пакетирования, который представляет собой совокупность глубоких деревьев решений (модели с высокой дисперсией и низким смещением или переобученные модели). Классификатор случайного леса, таким образом, фокусируется на уменьшении дисперсии при принятии любого решения.
Деревья решений неэффективны, если у какой-либо функции есть много категорий. Например, почтовые индексы городов имеют множество значений, что не идеально для деревьев решений.
Точно так же в этом наборе данных идентификаторы географического уровня имеют большой диапазон значений.
geo_level_3_id
имеет 11595 различных типов значений, geo_level_2_id
имеет 1414 различных типов значений. geo_level_1_id
имеет 64 различных типа значений (хотя у него не так много значений, но мы его разработали).
Чтобы преодолеть это, я применил технику разработки функций , заменив значения их условными вероятностями по отношению к каждому результату. Таким образом, каждый столбец geo_lev заменен на 3 столбцы для каждого результата (y).
Например, P (y = 1 | x = 6) для geo_level_1 вместо того, где X[geo_level_1_id]=6
и damage_grade = 1 .
. Мы делаем это для всех geo_уровней со всеми результатами.
Фрагмент кода разработки функций
fea = pd.merge(X, Y, on='building_id') damage1 = {} damage2 = {} damage3 = {} for i,j in (X['geo_leveldamage_grade
id'].value_counts()).iteritems(): n1 = len(fea[fea['damage_grade']==1][fea['geo_leveldamage_grade
id']==i]) n2 = len(fea[fea['damage_grade']==2][fea['geo_leveldamage_grade
id']==i]) n3 = len(fea[fea['damage_grade']==3][fea['geo_leveldamage_grade
id']==i]) damage1[i] = n1/j damage2[i] = n2/j damage3[i] = n3/j list1 = [] list2 = [] list3 = [] for i in X['geo_leveldamage_grade
id']: list1.append(damage1.get(i)) list2.append(damage2.get(i)) list3.append(damage3.get(i)) X['prob1_geo1'] = list1 X['prob2_geo1'] = list2 X['prob3_geo1'] = list3
Связь между географическими уровнями с LSTM
Эта часть немного сложна. Изучая данные, я обнаружил некоторую взаимосвязь между этими тремя уровнями локаций. Гео-уровень-3 - это самое мелкомасштабное местоположение под гео-уровнем 2, которое снова находится ниже-1, являясь крупномасштабным местоположением.
Для конкретного значения местоположения-1 и местоположения-2 существует большое количество одинаковых значений местоположения-3. Пример-
LSTM
Долгосрочная краткосрочная память - это тип рекуррентной нейронной сети. LSTM отличаются от обычных RNN, поскольку имеют сеть обратной связи. LSTM очень хороши для использования в последовательном прогнозировании данных, особенно в области машинного перевода.
Я применил аналогичный метод для создания этих геоуровней. Кажется, существует последовательная связь между географическими уровнями, поскольку уровень-1 является наивысшим и так далее. Итак, я создал специальный ввод для LSTM, который представляет собой последовательность гео-уровня 1 и гео-уровня 2, а вывод - гео-уровень-3
- Интуитивно мы даем последовательность геоуровня 1 и уровня 2 в качестве входных данных для нашей модели, которая предсказывает географический уровень 3.
- После обучения данных мы берем выходные данные слоя LSTM (промежуточный слой) и используем их в качестве функций для нашего прогнозирования землетрясений.
- Идея состоит в том, чтобы найти взаимосвязь между этими геоуровнями в математическом формате.
Описание методологии
Для начала нам нужно понять, какой тип ввода требует RNN.
Тип входных данных, требуемых LSTM:
( # Datapts, # Timestamps, dimension of vector )
Timpstamps означает «количество слов или количество последовательностей» в точке данных. Это ось времени.
Чтобы получить правильный формат, мы имитируем работу слоя встраивания и функции токенизатора keras.
- Сначала я объединяю geo_level
damage_grade
id и geo_level1
id вместе и передаю их через one_hot_encoder, чтобы получить общий словарь или измерение каждой последовательности. Это похоже на то, как мы делаем это в НЛП, у нас есть слова как последовательность, и мы передаем ее через Tokenizer, чтобы получить vocab_size.
# l1 is series df of lev1 # l2 is series df of lev2 inp = pd.concat([l1,l2]).to_numpy().reshape(-1,1) #get shared vocab of l1 and l2 onehot = OneHotEncoder(sparse=False) onehot.fit(inp)
- Теперь я трансформирую l1 и l2 по отдельности, чтобы получить их горячие векторы. Это приводит к тому же размеру вектора для последовательности-1, которая является geo_level_1, и последовательности-2, которая является geo_level_2, в соответствии с требованиями RNN.
l1_hot = onehot.transform(l1) l1_hot.shape >>> (347469, 1419) l2_hot = onehot.transform(l2) l2_hot.shape >>> (347469, 1419) #both have same # datapts and dimension
- Теперь последняя часть - получение правильной формы ввода для LSTM. Ему нужна последовательность данных с каждой последовательностью one_hot рядом. Мы даем его с помощью стека numpy.
fin_inp = np.stack((l1_hot,l2_hot), axis=1) fin_inp.shape >>> (347469, 2, 1419)
Это именно то, что LSTM хочет в качестве входных данных. Это легко описать на рисунке ниже.
Примечание. Мы не можем использовать word_embedding , потому что здесь нет встраивания слов, есть только один_hot_vector каждой последовательности, которые складываются вместе для создания правильной формы ввода для РНН. В основном имитирует алгоритм встраивания
Модель RNN
inpx = Input( shape=fin_inp.shape[1:] ) lstm = LSTM(20, )(inpx) lvl3 = Dense(l3_hot.shape[1], activation='sigmoid')(lstm) modelf = Model(inputs= inpx, outputs=lvl3) modelf.compile(loss='binary_crossentropy', optimizer='adam') modelf.summary() >>> Output Model: "model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) [(None, 2, 1419)] 0 _________________________________________________________________ lstm (LSTM) (None, 20) 115200 _________________________________________________________________ dense (Dense) (None, 11861) 249081 ================================================================= Total params: 364,281 Trainable params: 364,281 Non-trainable params: 0
Подгонка модели
l3_hot = np.array(pd.get_dummies(l3)) #l3 is geo_lev_3 df modelf.fit(fin_inp, l3_hot, epochs=10, batch_size=64) Epoch 1/10 5430/5430 [=================] - 33s 6ms/step - loss: 0.0185 Epoch 2/10 5430/5430 [=================] - 33s 6ms/step - loss: 8.3740e-04 Epoch 3/10 5430/5430 [=================] - 34s 6ms/step - loss: 8.3151e-04 . . . Epoch 9/10 5430/5430 [=================] - 33s 6ms/step - loss: 7.0521e-04 Epoch 10/10 5430/5430 [=================] - 32s 6ms/step - loss: 6.7878e-04
Последний и самый важный шаг → получение контекстного вывода промежуточного слоя, который представляет собой отношение между этими geo_levels, выступающими в качестве наших функций.
Layers [1] - это результат слоя LSTM, который мы хотим
inter = Model(inputs=modelf.input, outputs=modelf.layers[1].output)
Получение fin_inp тем же методом и прогнозирование получения необходимых функций.
fin_inp_train = np.stack((l1_hot,l2_hot), axis=1) fin_inp_train.shape >>> (260601, 2, 1419) fin_inp_test = np.stack((l1_hot,l2_hot), axis=1) fin_inp_test.shape >>> (86868, 2, 1419) geo_fea_train = inter.predict(fin_inp_train) geo_fea_test = inter.predict(fin_inp_train)
Окончательные предварительно обработанные данные
Для наших окончательных данных мы отбрасываем geo_levels и создаем one_hot всех оставшихся функций. Сложите их вместе с нашими недавно созданными функциями geo_fea_train.
Примечание: рассчитанные вероятности уже добавлены к данным.
fx_tr = np.hstack((np.array(pd.get_dummies(X.copy().drop(['geo_leveldamage_grade
id', 'geo_level1
id', 'geo_level2
id'], axis=1))), geo_fea_train)) fx_tr.shape >>> (260601, 94) fx_te = np.hstack((np.array(pd.get_dummies(test.copy().drop(['geo_leveldamage_grade
id', 'geo_level1
id', 'geo_level2
id'], axis=1))), geo_fea_test)) fx_te.shape >>> (86868, 94) fy_tr = Y.to_numpy().ravel() fy_tr.shape >>> (260601,)
Обучение и оценка
Здесь мы используем поиск по сетке CV и случайный лес.
param = {'n_estimators': [ 500, 1000], 'min_samples_split':[20, 50, 500]} clf = RandomForestClassifier() gd_sr = GridSearchCV(estimator=clf,param_grid=param,scoring='f1_micro',cv=5,n_jobs=-1) gd_sr.fit(fx_tr, fy_tr)
Выход
gd_sr.best_score_ >>> 0.75467870076 gd_sr.best_params_ >>> {'min_samples_split': 20, 'n_estimators': 1000}
Заключение
Используя лучшие параметры, я обучил модель и предсказал тестовые значения. Занял позицию среди 3% лучших в конкурсе.