Прошло несколько месяцев, как я начал изучать Deep Learning с одной из основных платформ глубокого обучения, PyTorch. Итак, я подумывал написать статью о том, что я узнал, в то же время понимая интригующую концепцию контролируемого, неконтролируемого и обучения с подкреплением. .
Я собираюсь рассказать, как создать простую нейронную сеть с помощью PyTorch для решения простой задачи классификации с набором данных Iris. Причина выбора набора данных Iris заключается в том, что он настолько прост, что не требует особой разработки функций, и мы можем просто взять данные для обучения и тестирования.
Почему PyTorch?
Хотя PyTorch пока не пользуется популярностью в отличие от других фреймворков глубокого обучения, таких как Keras и Tensorflow, благодаря своим возможностям он стал популярным. Однако он более подробный, чем его конкурирующие фреймворки. Следовательно, это лучший фреймворк для понимания внутреннего устройства нейронной сети, как я ее вижу. С другой стороны, Tesla использует PyTorch для автоматического вождения автомобилей, - не сомневайтесь в возможностях фреймворка.
Набор данных
Мы собираемся использовать популярный набор данных Iris для обучения и тестирования нейронной сети. Для получения дополнительной информации обратитесь к этому документу из Репозитория машинного обучения UCI.
В двух словах, цветок ирис насчитывает около 280 видов. В этом наборе данных мы рассматриваем только около 3 из них. Это Ирис сетоса, Ирис вирджинский и Ирис разноцветный. Каждый из этих видов мы можем отличить по ширине и длине чашелистиков и лепестков.
Я использую Панды для загрузки и управления набором данных. Вот как выглядит образец набора данных после выполнения нижеприведенного оператора;
df = pd.read_csv('data/iris.csv')
Чтобы продемонстрировать, как различаются виды, только по длине и ширине чашелистиков, мы можем просто использовать разбросанный график.
plt.figure(figsize=(7, 5)) plt.scatter(df['sepal_length'].values, df['sepal_width'].values, c=df['species_category'].values) plt.colorbar(ticks=[0, 1, 2], format=plt.FuncFormatter(lambda i, *args: labels[i])) plt.xlabel('sepal length(cm)') plt.ylabel('sepal width(cm)') plt.tight_layout() plt.show()
Как вы, возможно, уже знаете, нейронные сети не могут обрабатывать сложные типы данных, кроме чисел. В этом наборе данных размеры чашелистика и лепестка указаны в сантиметрах (см) и, очевидно, в числах. Но species
, который мы собираемся спрогнозировать (а также вычислить потери), не в числах, а в строковом названии вида. Следовательно, мы можем сделать этот небольшой дополнительный шаг для map
строкового имени вида в числа (существует лишь небольшое количество различных типов, поэтому мы можем просто сопоставить их с числами).
df['species_category'] = df['species'].map( {'setosa': 0, 'versicolor': 1, 'virginica': 2} )
Тогда набор данных будет выглядеть так, и теперь мы можем просто отбросить столбец species
.
# List index represent the reference species. labels = ['Iris setosa', 'Iris versicolor', 'Iris virginica']
Хотя сейчас у нас есть хороший набор данных со всеми необходимыми данными с числами, нам нужно понимать, какие данные нам нужны, чтобы сделать прогноз, и какое значение мы должны предсказать. Поскольку мы используем набор данных Iris, мы собираемся предсказать вид, посмотрев на длину чашелистиков и лепестков, как я упоминал ранее.
Итак, входными данными будут sepal_length
, sepal_width
, petal_length
и petal_width
.
Результатом будет просто species
(точнее species_category
).
X = df.iloc[:, 0:4].values # Input values. y = df.iloc[:, 5].values # Output values(species categories)
Теперь у нас есть массив NumPy входных и выходных значений, который имеет форму (150, 4) и (150,) соответственно.
Всегда полезно протестировать нейронную сеть после тренировки. Кроме того, нельзя использовать тот же набор данных для тестирования / проверки, который использовался для обучения. Это не помогло бы лучше понять, как обучение проходило с набором данных. Таким образом, всегда лучше иметь отдельный набор данных для тестирования. Поскольку в этом случае у нас есть только один набор данных, мы можем использовать scikit-learn, чтобы разделить набор данных на обучающий и тестовый.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
X_train
и y_train
можно использовать для обучения нейронной сети, а X_test
и y_test
можно использовать для проверки сети. X_test
и y_test
- это размер 20% исходного набора данных, как определено в test_size=0.2
, а random_state
устанавливает случайное начальное число для перемешивания набора данных перед разделением.
Если вы проверите тип данных всех этих наборов данных, они будут массивами NumPy, а PyTorch ожидает, что типом ввода будут объекты Tensor. Поэтому преобразуйте все эти массивы NumPy в объекты torch.Tensor.
X_train = torch.FloatTensor(X_train) X_test = torch.FloatTensor(X_test) y_train = torch.LongTensor(y_train) y_test = torch.LongTensor(y_test)
Нейронная сеть
Поскольку у нас есть необходимый набор данных, готовый к использованию, давайте продолжим нейронную сеть. Ниже представлена простая нейронная сеть с одним скрытым слоем, которую мы собираемся использовать для решения этой задачи классификации.
class NeuralNet(nn.Module): def __init__(self, in_features=4, out_features=3): super().__init__() self.fc1 = nn.Linear(in_features=in_features, out_features=120) self.fc2 = nn.Linear(in_features=120, out_features=84) self.fc3 = nn.Linear(in_features=84, out_features=out_features) def forward(self, X): X = F.relu(self.fc1(X)) X = F.relu(self.fc2(X)) return self.fc3(X)
Создайте экземпляр этой модели для обучения.
model = NeuralNet()
Обучение
Когда набор данных готов для обучения, продолжим обучение модели. Поскольку для обучения не так много точек данных, я собираюсь передать весь набор обучающих данных в сеть за одну эпоху на протяжении 50 эпох (эпоха = одна итерация обучения). Я также отслеживаю потери по эпохам, и давайте посмотрим, как потери сводятся к минимуму на протяжении всей тренировки.
Функция потерь / затрат, которую я собираюсь использовать, - это потеря кросс-энтропии, поэтому это проблема классификации, и оптимизатором будет Адам.
criteriotn = nn.CrossEntropyLoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
Давай, продолжим обучение;
epochs = 50 losses = [] for i in range(epochs): i += 1 y_pred = model(X_train) loss = criterion(y_pred, y_train) losses.append(loss) if i % 10 == 0: print(f'epoch: {i} -> loss: {loss}') optimizer.zero_grad() loss.backward() optimizer.step()
После 50 эпох график потерь по эпохам выглядит так;
plt.figure(figsize=(7, 5)) plt.plot(losses) plt.xlabel('Epochs') plt.ylabel('Loss') plt.show()
Глядя на сюжет, можно было понять, что этого достаточно для достижения минимальной потери 50 эпох. Однако к 30-й эпохе потери достигают минимума. Достигнув минимума, дальнейшее обучение рискует переобучиться.
Проверка
Хотя для проверки у нас есть только 20% исходного набора данных, это совершенно новые данные, которые нейронная сеть никогда не видела. Следовательно, используя этот набор данных, мы сможем понять, есть ли переоснащение или недостаточное.
Валидация должна выполняться в блоке torch.no_grad()
, поэтому мы не хотим нарушать обученные веса и смещения сети.
with torch.no_grad(): y_pred = model(X_test) preds = torch.max(y_pred, dim=1)[1] correct = (preds == y_test).sum() print(f'{correct} out of {y_test.shape[0]} is correct : {correct.item() / y_test.shape[0] * 100}%')
Возьмите max
значений тензора y_pred
, следовательно, значения по измерению 1 говорят нам о вероятности того, что в этом случае один может быть конкретным видом. Максимальное значение имеет максимальную вероятность быть правильным значением.
Результат - 30 out of 30 is correct : 100.0%
из приведенного выше фрагмента. Это означает, что все элементы правильно предсказываются сетью.
Вместо того, чтобы проходить весь тестовый пакет, мы можем также проверять правильность по очереди.
with torch.no_grad(): correct = 0 for i, X in enumerate(X_test): y_pred = model(X) if y_pred.argmax().item() == y_test[i]: correct += 1 print(f'{correct} out of {y_test.shape[0]} is correct : {correct / y_test.shape[0] * 100}%')
Применяйте неизвестные данные и получайте результаты
Используя совершенно неизвестные новые или выдуманные данные, мы можем теперь предсказать, к какому виду принадлежит данная точка данных. Эта функция позволяет нам делать это динамически.
@torch.no_grad() def predict_unknown(X_unknown): y_pred = model(X_unknown) return labels[y_pred.argmax()]
Теперь, если у нас есть следующие данные, мы можем попытаться их предсказать.
unknown_iris = torch.tensor([5.6, 3.7, 2.2, 0.5])
Вызов predict_unknown(mystery_iris)
даст нам результат Iris setosa. Мы не можем проверить, верен ли этот прогноз или нет, потому что у нас нет записи для подтверждения для этой точки данных. Но у нас могла бы быть идея, глядя на рассредоточенный график, который мы использовали ранее. Итак, мы могли бы попробовать сопоставить эту точку данных с этим рассредоточенным графиком.
Глядя на рассеянный граф, мы можем подтвердить, что прогноз нейронной сети верен.
Заключение
Эту нейронную сеть и набор обучающих данных очень просто и легко обучить и получить правильные результаты. Данные в реальном мире не могут применяться к нейронной сети без надлежащей модификации данных. Поиграйте с конфигурациями нейронной сети, такими как количество нейронов скрытого слоя, количество скрытых слоев, функции активации, которые очень необходимы в сети.
Поскольку я начал двигаться к все более и более сложным сценариям в этой области, надеюсь опубликовать больше интересных статей. Ценю мнения и комментарии экспертов.