Вопрос или проблема
У меня есть довольно простая задача классификации, которую я пытаюсь решить. Каждый экземпляр в моей задаче — это список из 1024 байтов (каждый байт представлен числом от 0 до 255). Есть 2 класса (скажем, класс ‘A’ и ‘B’). В классе ‘A’ все экземпляры имеют общую характеристику. Существует определенный двухбайтовый шаблон, например: “200 180”, который присутствует с высокой частотой среди всех экземпляров в классе ‘A’. Однако в классе ‘B’ это не так. В классе ‘B’ наличие последовательности байтов ‘200 180’ является больше артефактом случайности, чем чем-либо другим.
Я хотел бы, чтобы моя CNN смогла различать классы A и B без явной передачи ей шаблона из байтов. Я использую следующий код, чтобы попытаться сделать это:
model = models.Sequential()
model.add(layers.Conv1D(filters=1, kernel_size=2, activation='relu', input_shape=(1024, 1)))
model.add(layers.MaxPooling1D(pool_size=64))
model.add(layers.Flatten())
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer="adam",
loss="binary_crossentropy",
metrics=['accuracy'])
Когда я использую простые эвристики для оценки “различимости” между двумя классами, я вижу, что можно легко достичь более 90% точности, просто глядя на частоту шаблона байтов. Однако моя вышеуказанная CNN ужасно справляется с этой задачей. Например, вот результат попытки обучения моей CNN за 5 эпох:
Epoch 1/5
1563/1563 [==============================] - 65s 41ms/step - loss: 0.5297 - accuracy: 0.8468
Epoch 2/5
1563/1563 [==============================] - 61s 39ms/step - loss: 0.7534 - accuracy: 0.5000
Epoch 3/5
1563/1563 [==============================] - 61s 39ms/step - loss: 0.7100 - accuracy: 0.5029
Epoch 4/5
1563/1563 [==============================] - 61s 39ms/step - loss: 0.6977 - accuracy: 0.5216
Epoch 5/5
1563/1563 [==============================] - 62s 40ms/step - loss: 0.6934 - accuracy: 0.5303
Как вы видите, результат больше похож на случайное угадывание, чем на что-либо другое. Я хотел бы узнать, есть ли у вас какие-либо советы по улучшению этого процесса.
Вы можете добавить dropout в модель. Это изменит точность вашей модели.
from keras.layers import Dropout
model = models.Sequential()
model.add(layers.Conv1D(filters=1, kernel_size=2, activation='relu', input_shape=(1024, 1)))
model.add(layers.MaxPooling1D(pool_size=64))
model.add(layers.Flatten())
model.add(Dropout(0.5))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer="adam",
loss="binary_crossentropy",
metrics=['accuracy'])
Ответ или решение
Теория
Конволюционные нейронные сети (CNN) представляют собой мощный инструмент для автоматической идентификации паттернов в данных, и они широко используются в задачах классификации. Тем не менее, правильное применение архитектуры CNN требует учета многих факторов, таких как выбор подходящей архитектуры сети, корректная предобработка данных и правильный подбор гиперпараметров. В вашем случае задача состоит в бинарной классификации с использованием одномерной CNN для определения паттерна из двух байтов. Хотя это звучит просто, неправаясмая настройка модели может привести к ее неспособности выявить существенные особенности, что и происходит в вашем случае.
Пример
Рассмотрим вашу текущую реализацию модели:
model = models.Sequential()
model.add(layers.Conv1D(filters=1, kernel_size=2, activation='relu', input_shape=(1024, 1)))
model.add(layers.MaxPooling1D(pool_size=64))
model.add(layers.Flatten())
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer="adam",
loss="binary_crossentropy",
metrics=['accuracy'])
Основная проблема этой архитектуры – её излишняя простота. Усложняя модель, мы увеличиваем её способность извлекать более сложные паттерны.
Применение
-
Предобработка данных: Убедитесь, что ваши данные масштабированы. Нормализуйте значения байтов до диапазона от 0 до 1, деля их на 255.
-
Усложнение архитектуры:
- Увеличьте число фильтров в слое Conv1D, например, до 32. Это позволит модели извлекать более сложные особенности.
- Добавьте не один, а несколько сверточных слоев, чтобы постепенно усложнять извлекаемые паттерны.
- Между сверточными слоями можно добавить слои MaxPooling или Dropout для снижения переобучения и уменьшения размерности.
- Используйте более сложные подходы к агрегированию признаков, возможно, с применением GlobalAveragePooling.
-
Изменение гиперпараметров:
- Измените размерности ядра, добавив несколько разных размеров ядра (например, 3 и 5), таким образом сеть сможет захватывать различные паттерны.
- Подумайте о применении других функций активации, таких как LeakyReLU.
- Измените параметры обучения, такие как размер батча и скорость обучения оптимизатора Adam.
-
Анализ и визуализация обучения:
- Ведите логи потерь и точности по эпохам. Используйте средства визуализации, такие как TensorBoard, чтобы отслеживать, как изменяются затраты и точность модели.
- В случае, если модель переобучает, попробуйте снизить сложность архитектуры или добавить регуляризацию.
-
Проверка подбора случайных начальных весов:
- Повторите обучение несколько раз с разными началами, чтобы удостовериться, что проблема не в неудачных начальных весах.
После изменений ваша новая архитектура может выглядеть следующим образом:
model = models.Sequential()
model.add(layers.Conv1D(filters=32, kernel_size=3, activation='relu', input_shape=(1024, 1)))
model.add(layers.Conv1D(filters=64, kernel_size=3, activation='relu'))
model.add(layers.MaxPooling1D(pool_size=2))
model.add(layers.Dropout(0.5))
model.add(layers.Conv1D(filters=128, kernel_size=3, activation='relu'))
model.add(layers.GlobalAveragePooling1D())
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(optimizer="adam",
loss="binary_crossentropy",
metrics=['accuracy'])
Подобный подход должен улучшить способность нейронной сети в улавливании паттернов различимой частоты двухбайтовых последовательностей в ваших данных.
Таким образом, комбинация изменения архитектуры, улучшенной предобработки данных и выбора более подходящих гиперпараметров должна существенно повысить производительность вашей модели на этой задаче.