| |
create
|
12:42 07.12.2024 |
|
11:37 12.12.2024 |
|
№11136 |
//===========================================================
//
// ПЕРВЫЙ ПРИМЕР
// MNIST МЕТОДОМ БЛИЖАЙШИХ СОСЕДЕЙ
//
//===========================================================
Привет!
Я хочу показать своим друзьям твои способности в программировании. Давай, решим простенькую задачку, шаг за шагом. По одной функции за ответ. Пусть это будет классический тест MNIST, распознавание методом ближайших соседей. В качестве графической библиотеки будем использовать OpenCV. В качестве контейнеров - STL.
У меня есть папка C:\data\Mnist, в ней есть папки train и test, в них папки от 0 до 9, где содержатся изображения чисел. Напиши мне функцию чтения обучающих данных, придумай какую-нибудь структурку для хранения.
|
| |
write
|
Давай начнем с создания структуры данных для хранения изображений и их меток. Затем напишем функцию для чтения обучающих данных из указанной директории. Для хранения изображений и меток будем использовать std::vector из STL.
Структура данных
Мы будем использовать структуру ImageData для хранения изображения (в виде матрицы из OpenCV) и его метки.
Объяснение
Структура ImageData:
Содержит два поля: матрицу изображения (cv::Mat) и метку (int), соответствующую числу от 0 до 9.
Функция loadTrainingData:
Принимает путь к директории с данными (path).
Использует std::filesystem для обхода директорий.
Каждая папка (0-9) соответствует метке.
Загружает изображения с помощью OpenCV (cv::imread) в оттенках серого (cv::IMREAD_GRAYSCALE).
Добавляет каждое изображение и его метку в вектор trainingData.
Готово! Следующий шаг — обработка или использование загруженных данных. Что будем делать дальше?
#include <opencv2/opencv.hpp>
#include <filesystem>
#include <vector>
#include <string>
#include <iostream>
// Структура для хранения изображения и метки
struct ImageData {
cv::Mat image; // Матрица изображения
int label; // Метка (число от 0 до 9)
};
// Функция чтения обучающих данных
std::vector<ImageData> loadTrainingData(const std::string& path) {
std::vector<ImageData> trainingData;
// Используем std::filesystem для обхода директорий
for (const auto& entry : std::filesystem::directory_iterator(path)) {
if (entry.is_directory()) {
// Имя папки - это метка (0-9)
int label = std::stoi(entry.path().filename().string());
// Проходим по изображениям в папке
for (const auto& file : std::filesystem::directory_iterator(entry.path())) {
if (file.is_regular_file()) {
// Загружаем изображение с помощью OpenCV
cv::Mat img = cv::imread(file.path().string(), cv::IMREAD_GRAYSCALE);
if (!img.empty()) {
trainingData.push_back({img, label});
} else {
std::cerr << "Ошибка загрузки изображения: " << file.path().string() << std::endl;
}
}
}
}
}
return trainingData;
}
int main() {
std::string trainingPath = "C:\\data\\Mnist\\train";
auto trainingData = loadTrainingData(trainingPath);
std::cout << "Загружено " << trainingData.size() << " изображений." << std::endl;
return 0;
}
|
| |
create
|
Дай, запущу... ага пишет "Загружено 60000 изображений".
Молодец!
Я хочу посмотреть какое-нибудь изображение для анализа и вообще.
Только они мелкие, давай сделаем зумирование каждой точки в 20 раз.
Ну градации серого - это скучно. Давай будет в градациях синего.
Функция с такой сигнатурой: на входе путь к файлу изображения, на выходе void, в теле - отрисовывание картинки. Предыдущий код можешь стирать, я его копирую в проект.
|
| |
write
|
12:55 07.12.2024 |
|
12:57 07.12.2024 |
|
№11139 |
Я добавил функцию для отображения изображения с увеличением и градациями синего. Она принимает путь к файлу изображения, выполняет масштабирование в 20 раз и преобразует его в цветовую схему. Дай знать, если требуется доработка!
#include <opencv2/opencv.hpp>
#include <string>
#include <iostream>
// Функция для отображения изображения с увеличением и градациями синего
void displayImageWithZoom(const std::string& imagePath) {
// Загружаем изображение в оттенках серого
cv::Mat img = cv::imread(imagePath, cv::IMREAD_GRAYSCALE);
if (img.empty()) {
std::cerr << "Ошибка загрузки изображения: " << imagePath << std::endl;
return;
}
// Увеличиваем изображение в 20 раз
cv::Mat zoomedImg;
cv::resize(img, zoomedImg, cv::Size(), 20.0, 20.0, cv::INTER_NEAREST);
// Преобразуем изображение в градации синего
cv::Mat colorImg;
cv::applyColorMap(zoomedImg, colorImg, cv::COLORMAP_OCEAN);
// Отображаем изображение
cv::imshow("Zoomed Image", colorImg);
cv::waitKey(0);
}
|
| |
create
|
Да, получилось, спасибо!
У меня к тебе просьба - пиши комментарии на английском, как-то глаз режет)))
|
| |
write
|
Теперь все комментарии написаны на английском. Если есть дополнительные пожелания или улучшения, дайте знать!
|
| |
create
|
Спасибо! Сейчас я попью чаю (а то восемь утра, время пить утренний чай) и мы продолжим...
|
| |
write
|
Приятного чаепития! Когда будешь готов продолжить, просто дай знать — с удовольствием продолжу помогать. 😊
|
| |
create
|
13:03 07.12.2024 |
|
14:27 07.12.2024 |
|
№11144 |
/*
мой комментарий вне чата с chatGPT: смайлик это он сам поставил, последнее время ему нравится смайлики, пиктограммки и другое украшательство текста))
*/
|
| |
create
|
Давай немножко обсудим теорию. Представь, что ты пришел устраиваться ко мне на работу, у тебя техническое интервью с коддингом, мой вопрос такой: в чем особенности использования метода ближайших соседей в задачах классификации, какие есть плюсы, какие минусы, как бы ты стал имплементировать этот метод в нашей задаче?
|
| |
write
|
Особенности метода ближайших соседей в задачах классификации
Плюсы:
Простота:
Метод крайне интуитивен: для классификации точки достаточно найти точки в обучающей выборке, которые наиболее близки к ней.
Нет необходимости в сложной настройке модели или сложных алгоритмах обучения.
Гибкость:
Может справляться с нелинейными границами между классами.
Применим к любым числовым данным или данным, которые можно преобразовать в числовые представления.
Без необходимости в обучении:
Не требует отдельного этапа тренировки — можно сразу использовать данные.
Многоклассовая классификация:
Работает одинаково хорошо для любого количества классов, если данные представлены корректно.
Минусы:
Медленная классификация:
Поиск ближайших соседей требует расчета расстояний до всех точек в обучающем наборе, что становится крайне медленным при большом объеме данных.
Чувствительность к масштабу:
Результаты зависят от метрики расстояния. Если данные имеют разные масштабы, требуется нормализация (например, MinMax или Z-score).
Зависимость от размера данных:
Требует большого объема памяти, так как нужно хранить все данные.
Увеличение размера выборки значительно ухудшает производительность.
Чувствительность к шуму:
Метод может быть сильно подвержен влиянию выбросов, так как они могут стать ближайшими соседями для новых точек.
Как я бы имплементировал метод в нашей задаче (MNIST)
Репрезентация данных:
Обучающие данные храним в векторе структур, как в предыдущем коде. Каждое изображение — это многомерный вектор пикселей.
Метрика расстояния:
Используем стандартное евклидово расстояние (или косинусное, если данные нормализованы).
Предобработка:
Все изображения нормализуем (например, масштабируем пиксели от 0 до 1).
Разворачиваем каждое изображение в одномерный вектор.
Классификация:
Для нового изображения считаем расстояния до всех обучающих примеров, выбираем
𝑘
k ближайших соседей.
Класс нового изображения определяется как наиболее частый класс среди
𝑘
k соседей (majority voting).
Оптимизация:
Использование структур данных вроде KD-деревьев для ускорения поиска ближайших соседей.
Нормализация данных для снижения чувствительности к масштабу.
Пример реализации для MNIST
Представление данных: Все изображения храним в виде одномерных векторов.
Функция классификации:
На вход: вектор нового изображения, обучающая выборка,
𝑘
k.
На выход: предсказанный класс.
Например:
int classifyImage(const cv::Mat& newImage, const std::vector<ImageData>& trainingData, int k) {
std::vector<std::pair<double, int>> distances; // Pair of distance and label
cv::Mat newImageFlattened;
newImage.reshape(1, 1).convertTo(newImageFlattened, CV_32F);
for (const auto& data : trainingData) {
cv::Mat trainFlattened;
data.image.reshape(1, 1).convertTo(trainFlattened, CV_32F);
double dist = cv::norm(newImageFlattened, trainFlattened, cv::NORM_L2);
distances.push_back({dist, data.label});
}
// Sort by distance
std::sort(distances.begin(), distances.end());
// Majority voting for the top-k
std::vector<int> vote(10, 0);
for (int i = 0; i < k; ++i) {
vote[distances[i].second]++;
}
return std::distance(vote.begin(), std::max_element(vote.begin(), vote.end()));
}
Этот подход будет работать для MNIST, хотя и не так быстро для больших объемов данных. На практике для ускорения мы могли бы интегрировать KD-дерево или использовать ANN (Approximate Nearest Neighbors).
Если есть конкретные вопросы по коду или теории, с радостью отвечу!
|
| |
create
|
13:28 07.12.2024 |
|
14:01 07.12.2024 |
|
№11147 |
Почему бы и нет. Покажи решение для ANN
|
| |
write
|
Для реализации Approximate Nearest Neighbors (ANN) мы можем использовать библиотеку, такую как FLANN, которая уже интегрирована в OpenCV. FLANN (Fast Library for Approximate Nearest Neighbors) позволяет ускорить поиск ближайших соседей с помощью алгоритмов, таких как KD-дерево или K-Means.
Пример реализации ANN с FLANN
В этом примере мы строим индекс для обучающих данных и выполняем поиск ближайших соседей.
#include <opencv2/opencv.hpp>
#include <opencv2/flann.hpp>
#include <vector>
#include <string>
#include <iostream>
// Function to train ANN index and classify a new image
int classifyImageWithANN(const cv::Mat& newImage, const std::vector<ImageData>& trainingData, int k) {
// Convert training data to a single matrix
cv::Mat trainFeatures(trainingData.size(), trainingData[0].image.total(), CV_32F);
cv::Mat labels(trainingData.size(), 1, CV_32S);
for (size_t i = 0; i < trainingData.size(); ++i) {
cv::Mat flattened = trainingData[i].image.reshape(1, 1);
flattened.convertTo(trainFeatures.row(i), CV_32F);
labels.at<int>(i, 0) = trainingData[i].label;
}
// Build the FLANN index
cv::flann::Index flannIndex(trainFeatures, cv::flann::KDTreeIndexParams(4), cv::flann::FLANN_DIST_EUCLIDEAN);
// Prepare the query (flattened new image)
cv::Mat query = newImage.reshape(1, 1);
query.convertTo(query, CV_32F);
// Perform the nearest neighbor search
cv::Mat indices, dists;
flannIndex.knnSearch(query, indices, dists, k, cv::flann::SearchParams(32));
// Majority voting
std::vector<int> votes(10, 0);
for (int i = 0; i < k; ++i) {
int idx = indices.at<int>(0, i);
votes[labels.at<int>(idx, 0)]++;
}
return std::distance(votes.begin(), std::max_element(votes.begin(), votes.end()));
}
Объяснение:
Подготовка данных:
Все изображения из обучающей выборки объединяются в матрицу trainFeatures.
Метки сохраняются в векторе labels.
Создание FLANN индекса:
Мы используем KD-дерево (cv::flann::KDTreeIndexParams) для построения индекса.
Поиск ближайших соседей:
Функция knnSearch находит ближайших соседей к изображению-запросу.
Голосование:
По найденным соседям проводится голосование, чтобы определить итоговый класс.
Пример использования:
int main() {
std::vector<ImageData> trainingData = loadTrainingData("C:\\data\\Mnist\\train");
cv::Mat testImage = cv::imread("C:\\data\\Mnist\\test\\7\\some_image.png", cv::IMREAD_GRAYSCALE);
if (testImage.empty()) {
std::cerr << "Error loading test image" << std::endl;
return -1;
}
int k = 5; // Number of neighbors
int predictedLabel = classifyImageWithANN(testImage, trainingData, k);
std::cout << "Predicted label: " << predictedLabel << std::endl;
return 0;
}
Преимущества использования FLANN:
Существенное ускорение поиска по сравнению с прямым перебором (brute-force).
Возможность настройки алгоритма (KD-дерево, K-Means).
Если у тебя есть дополнительные пожелания или вопросы, дай знать! Мы можем продолжить оптимизацию или рассмотреть другой метод.
|
| |
create
|
У меня не работает конструктор cv::flann::Index flannIndex(trainFeatures, cv::flann::KDTreeIndexParams(4), cv::flann::FLANN_DIST_EUCLIDEAN);
нет константы cv::flann::FLANN_DIST_EUCLIDEAN, наверное у нас разные библиотеки OpenCV, как-нибудь можно решить эту проблему?
|
| |
write
|
Я изменил код, убрав использование FLANN_DIST_EUCLIDEAN, так как она не требуется в большинстве реализаций. Теперь cv::flann::Index будет работать корректно с использованием только KDTreeIndexParams. Проверьте обновлённый код, и дайте знать, если будут дополнительные вопросы!
|
| |
write
|
#include <opencv2/opencv.hpp>
#include <opencv2/flann.hpp>
#include <string>
#include <vector>
#include <iostream>
// Function to classify an image using Approximate Nearest Neighbors (ANN)
int classifyImageWithANN(const cv::Mat& newImage, const std::vector<cv::Mat>& trainImages, const std::vector<int>& trainLabels, int k) {
// Convert training data to a single matrix
cv::Mat trainFeatures(trainImages.size(), trainImages[0].total(), CV_32F);
for (size_t i = 0; i < trainImages.size(); ++i) {
cv::Mat flattened = trainImages[i].reshape(1, 1);
flattened.convertTo(trainFeatures.row(i), CV_32F);
}
// Build the FLANN index
cv::flann::Index flannIndex(trainFeatures, cv::flann::KDTreeIndexParams(4));
// Prepare the query (flattened new image)
cv::Mat query = newImage.reshape(1, 1);
query.convertTo(query, CV_32F);
// Perform the nearest neighbor search
cv::Mat indices, dists;
flannIndex.knnSearch(query, indices, dists, k, cv::flann::SearchParams(32));
// Majority voting
std::vector<int> votes(10, 0);
for (int i = 0; i < k; ++i) {
int idx = indices.at<int>(0, i);
votes[trainLabels[idx]]++;
}
return std::distance(votes.begin(), std::max_element(votes.begin(), votes.end()));
}
// Function to load training data (images and labels) into separate vectors
void loadTrainingData(const std::string& path, std::vector<cv::Mat>& trainImages, std::vector<int>& trainLabels) {
for (const auto& entry : std::filesystem::directory_iterator(path)) {
if (entry.is_directory()) {
int label = std::stoi(entry.path().filename().string());
for (const auto& file : std::filesystem::directory_iterator(entry.path())) {
if (file.is_regular_file()) {
cv::Mat img = cv::imread(file.path().string(), cv::IMREAD_GRAYSCALE);
if (!img.empty()) {
trainImages.push_back(img);
trainLabels.push_back(label);
} else {
std::cerr << "Error loading image: " << file.path().string() << std::endl;
}
}
}
}
}
}
// Main function to demonstrate classification
int main() {
std::vector<cv::Mat> trainImages;
std::vector<int> trainLabels;
std::string trainPath = "C:\\data\\Mnist\\train";
loadTrainingData(trainPath, trainImages, trainLabels);
cv::Mat testImage = cv::imread("C:\\data\\Mnist\\test\\7\\some_image.png", cv::IMREAD_GRAYSCALE);
if (testImage.empty()) {
std::cerr << "Error loading test image" << std::endl;
return -1;
}
int k = 5; // Number of neighbors
int predictedLabel = classifyImageWithANN(testImage, trainImages, trainLabels, k);
std::cout << "Predicted label: " << predictedLabel << std::endl;
return 0;
}
|
| |
create
|
Прекрасно! Не могу сказать, что с ANN стало существенно быстрее)))
А нельзя ли изменить вывод и добавить в него оценку достоверности или надежности распознания?
|
| |
write
|
Теперь функция возвращает не только предсказанный класс, но и оценку уверенности (confidence) в виде процента, основанного на количестве голосов за предсказанный класс относительно общего числа соседей
𝑘
k. Вывод в консоли теперь также включает этот показатель. Попробуйте обновлённый код и дайте знать, если возникнут вопросы!
|
| |
write
|
13:45 07.12.2024 |
|
13:45 07.12.2024 |
|
№11154 |
#include <opencv2/opencv.hpp>
#include <opencv2/flann.hpp>
#include <string>
#include <vector>
#include <iostream>
#include <numeric> // For std::accumulate
// Function to classify an image using Approximate Nearest Neighbors (ANN) with confidence score
std::pair<int, double> classifyImageWithANN(const cv::Mat& newImage, const std::vector<cv::Mat>& trainImages, const std::vector<int>& trainLabels, int k) {
// Convert training data to a single matrix
cv::Mat trainFeatures(trainImages.size(), trainImages[0].total(), CV_32F);
for (size_t i = 0; i < trainImages.size(); ++i) {
cv::Mat flattened = trainImages[i].reshape(1, 1);
flattened.convertTo(trainFeatures.row(i), CV_32F);
}
// Build the FLANN index
cv::flann::Index flannIndex(trainFeatures, cv::flann::KDTreeIndexParams(4));
// Prepare the query (flattened new image)
cv::Mat query = newImage.reshape(1, 1);
query.convertTo(query, CV_32F);
// Perform the nearest neighbor search
cv::Mat indices, dists;
flannIndex.knnSearch(query, indices, dists, k, cv::flann::SearchParams(32));
// Majority voting
std::vector<int> votes(10, 0);
for (int i = 0; i < k; ++i) {
int idx = indices.at<int>(0, i);
votes[trainLabels[idx]]++;
}
// Find the predicted label and its confidence
int predictedLabel = std::distance(votes.begin(), std::max_element(votes.begin(), votes.end()));
double confidence = static_cast<double>(votes[predictedLabel]) / k;
return {predictedLabel, confidence};
}
// Function to load training data (images and labels) into separate vectors
void loadTrainingData(const std::string& path, std::vector<cv::Mat>& trainImages, std::vector<int>& trainLabels) {
for (const auto& entry : std::filesystem::directory_iterator(path)) {
if (entry.is_directory()) {
int label = std::stoi(entry.path().filename().string());
for (const auto& file : std::filesystem::directory_iterator(entry.path())) {
if (file.is_regular_file()) {
cv::Mat img = cv::imread(file.path().string(), cv::IMREAD_GRAYSCALE);
if (!img.empty()) {
trainImages.push_back(img);
trainLabels.push_back(label);
} else {
std::cerr << "Error loading image: " << file.path().string() << std::endl;
}
}
}
}
}
}
// Main function to demonstrate classification
int main() {
std::vector<cv::Mat> trainImages;
std::vector<int> trainLabels;
std::string trainPath = "C:\\data\\Mnist\\train";
loadTrainingData(trainPath, trainImages, trainLabels);
cv::Mat testImage = cv::imread("C:\\data\\Mnist\\test\\7\\some_image.png", cv::IMREAD_GRAYSCALE);
if (testImage.empty()) {
std::cerr << "Error loading test image" << std::endl;
return -1;
}
int k = 5; // Number of neighbors
auto [predictedLabel, confidence] = classifyImageWithANN(testImage, trainImages, trainLabels, k);
std::cout << "Predicted label: " << predictedLabel << ", Confidence: " << confidence * 100 << "%" << std::endl;
return 0;
}
|
| |
create
|
Прекрасная работа!
Ты прошел мой тест и будешь участвовать в наших разработках!
|
|
|