Привет, дорогой друг, ты всегда хотел попробовать машинное обучение, но область выглядела загадочно и сложно? Я хотел бы поделиться с тобой моей историей как я сделал первые шаги в машинном обучении, при нулевом знании Python и высшей математики на небольшом примере.
Преамбула Я работаю веб разработчиком в консалтинговой компании, и иногда настает момент, когда один проект уже кончился, а на следующий еще не назначили. Каждый оказавшийся на скамейке запасных, чтоб не просто штаны просиживать должен внести вклад в интеллектуальную собственность компании. Как правило это либо создание обучающих материалов по теме, которой владеет автор, либо изучение новой технологии и последующая демонстрация или презентация в конце недели.
Решил, раз есть такая возможность, то попробовать коснуться темы Машинного обучения, поскольку это стильно, модно и молодежно. Из предыдущих познаний в данной теме у меня были только пара презентаций от ведущего разработчика, которые имели скорее популяризаторский нежели информационный оттенок.
Я определил конкретную проблему, чтобы решить ее с помощью машинного обучения и начал копать. Хочу заметить, что имея конечную цель было легче ориентироваться в потоке информации.
Втыкаем лопату
Первым делом я отправился на официальный сайт TensorFlow и прочитал ML for Beginners и TensorFlow for beginners. Материалы на английском.
TensorFlow это поделка команды Google и наиболее популярная библиотека для работы с машинным обучением, которая поддерживает Python, Java, C++, Go, а также возможность использования вычислительных мощностей графической видеокарты для расчетов сложных нейросетей.
В своих поисках я нашел еще одну библиотеку для машинного обучения Scikit-learn ориентированную на Python. Плюс этой библиотеки, в большом количестве алгоритмов для машинного обучения прямо из коробки, что было несомненным плюсом в моем случае, так как презентация в пятницу, и очень хотелось продемонстрировать рабочую модель.
В поисках готовых примеров я наткнулся на туториал по определению языка на котором написан текст с помощью Scikit-learn.
Итак, моей задачей было обучить модель определять наличие SQL инъекции в текстовой строке. (Конечно, можно решить эту задачу с помощью регулярных выражений, но в образовательных целях можно по воробьям стрелять из пушки)
Первым делом, первым делом датасеты...
Тип задачи который я пытаюсь решить это классификация, то есть алгоритм должен в ответ на вскормленные данные выдать мне к какой из категорий эти данные относятся.
Данные в которых алгоритм будет искать закономерности называются features.
Категория, к которой относится та или иная feature, называется label. Важно отметить, что входные данные могут иметь несколько features, но всего один label.
В классическом примере машинного обучения, определения разновидностей цветков ириса по длине пестиков и тычинок, каждый отдельный столбец с информацией о размере это feature, а последний столбец, который означает к какому из подвидов ириса относится цветок с такими значениями это label
Способ, которым я буду решать проблему классификации, называется supervised learning, или обучение под надзором. Это значит, что в процессе обучения алгоритм будет получать и features и labels.
Шаг номер один в решении любой задачи с помощью машинного обучения это сбор данных, на которых эта самая машина и будет учится. В идеальном мире это должны быть реальные данные, но, к сожалению, в интернете я не смог найти ничего что бы меня удовлетворило. Решено было сгенерировать данные самостоятельно.
Я написал скрипт, который генерировал случайные адреса электронной почты и SQL инъекции. В итоге в моем csv файле получалось три типа данных: случайные имейлы (20 тыс.), случайные имейлы с SQL инъекцией (20 тыс.) и чистые SQL инъекции (10 тыс.). Выглядело это примерно вот так:
Теперь исходные данные нужно считать. Функция возвращает лист X, в котором содержатся features, лист Y, в котором содержатся labels для каждой feature и лист label_names, который просто содержит текстовое определения для labels, нужен для удобства при выводе результатов.
import csv def get_dataset(): X = [] y = [] label_names = ["safe data","Injected email"] with open('trainingSet.csv') as csvfile: readCSV = csv.reader(csvfile, delimiter=' ') for row in readCSV: splitted = row[0].split(',') X.append(splitted[0]) y.append(splitted[1]) print(" Data set features {0}". format(len(X))) print("Data set labels {0} ". format(len(y))) print(X) return X, y, label_names
Далее эти данные нужно разбить на тренировочный сет и на тестовый. В этом нам поможет заботливо написанная для нас функция cross_validation.train_test_split(), которая перетасует записи и вернет нам четыре сета данных — два тренировочных и два тестовых для features и labels.
# Split the dataset on training and testing sets X_train, X_test, y_train, y_test = cross_validation.train_test_split(X,y,test_size=0.2,random_state=0)
Затем мы инициализируем объект vectorizer, который будет считывать переданные в него данные по одному символу, комбинировать их в
N-граммы и переводить в числовые векторы, который способен воспринимать алгоритм машинного обучения.
#Setting up vectorizer that will convert dataset into vectors using n-gram vectorizer = feature_extraction.text.TfidfVectorizer(ngram_range=(1, 4), analyzer='char')
Скармливаем данные
Следующий шаг мы инициализируем pipeline и передадим в него ранее созданный vectorizer и алгоритм, которым мы хотим анализировать наш дата сет. В данном мы будем использовать алгоритм логистической регрессии.
#Setting up pipeline to flow data though vectorizer to the liner model implementation pipe = pipeline.Pipeline([('vectorizer', vectorizer), ('clf', linear_model.LogisticRegression())])
Модель готова к перевариванию данных. Теперь просто передаем тренировочные сеты features и labels в наш pipeline и модель начинает обучение. Следующей строкой мы пропускаем тестовый сет features через pipeline, но теперь мы используем predict, чтобы получить число правильно угаданных данных.
#Pass training set of features and labels though pipe. pipe.fit(X_train, y_train) #Test model accuracy by running feature test set y_predicted = pipe.predict(X_test)
Если хочется узнать насколько модель точна в предсказаниях, можно сравнить угаданные данные и тестовый лист labels.
print(metrics.classification_report(y_test, y_predicted,target_names=label_names))
Точность модели определяется величиной от 0 до 1, и можно перевести в проценты. Эта модель дает правильный ответ в 100% случаев. Конечно, используя реальные данные, подобного результата будет добиться не так просто, да и задача достаточно простая.
Последний финальный штрих это сохранить модель в обученном виде, чтоб ее можно было без повторного обучения использовать в любой другой python программе. Мы сериализуем модель в pickle файл с помощью встроенной в Scikit-learn функции:
#Save model into pickle. Built in serializing tool joblib.dump(pipe, 'injection_model.pkl')
Небольшая демонстрация того, как использовать сериализованную модель в другой программе.
import numpy as np from sklearn.externals import joblib #Load classifier from the pickle file clf = joblib.load('injection_model.pkl') #Set of test data input_data = ["aselectndwdpyrey@gmail.com", "andrew@microsoft.com'", "a.johns@deloite.com", "'", "select@mail.jp", "update11@nebuzar.com", "' OR 1=1", "asdasd@sick.com'", "andrew@mail' OR 1=1", "an'drew@bark.1ov111.com", "andrew@gmail.com'"] predicted_attacks = clf.predict(input_data).astype(np.int) label_names = ["Safe Data", "SQL Injection"] for email, item in zip(input_data, predicted_attacks): print(u' {} ----> {}'.format(label_names[item], email))
На выходе мы получим вот такой результат:
Как видите, модель достаточно уверенно определяет SQL инъекции.
Заключение
В итоге мы имеем тренированную модель для определения SQL инъекций, в теории, мы можем воткнуть ее в серверную часть, и в случае определения инъекции перенаправлять все запросы за фальшивую базу данных, чтоб отвадить взгляд от других возможных уязвимостей. Для демонстрации в конце недели я написал небольшой REST API на Flask.
Это были мои первые шаги в области машинного обучения. Надеюсь, что я смогу вдохновить тех, кто так же как и я долгое время с интересом смотрел на машинное обучение, но боялся прикоснутся к нему.
Полный код
from sklearn import ensemble from sklearn import feature_extraction from sklearn import linear_model from sklearn import pipeline from sklearn import cross_validation from sklearn import metrics from sklearn.externals import joblib import load_data import pickle # Load the dataset from the csv file. Handled by load_data.py. Each email is split in characters and each one has label assigned X, y, label_names = load_data.get_dataset() # Split the dataset on training and testing sets X_train, X_test, y_train, y_test = cross_validation.train_test_split(X,y,test_size=0.2,random_state=0) #Setting up vectorizer that will convert dataset into vectors using n-gram vectorizer = feature_extraction.text.TfidfVectorizer(ngram_range=(1, 4), analyzer='char') #Setting up pipeline to flow data though vectorizer to the liner model implementation pipe = pipeline.Pipeline([('vectorizer', vectorizer), ('clf', linear_model.LogisticRegression())]) #Pass training set of features and labels though pipe. pipe.fit(X_train, y_train) #Test model accuracy by running feature test set y_predicted = pipe.predict(X_test) print(metrics.classification_report(y_test, y_predicted,target_names=label_names)) #Save model into pickle. Built in serializing tool joblib.dump(pipe, 'injection_model.pkl')
Справочные Материалы
Оставляю список полезных ресурсов, которые помогли мне с данным проектом (почти все они на английском)
Tensorflow for begginers
Scikit-Learn Tutorials
Building Language Detector via Scikit-Learn
Нашел несколько отличных статей на Medium включая серию из восьми статей, которые дают хорошее представление, о машинном обучении на простых примерах. (UPD: русский перевод этих же статей)