diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a27230..75cf940 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -256,3 +256,63 @@ set_tests_properties(test_simd_optimization PROPERTIES TIMEOUT 120 LABELS "simd;performance" ) + +# Test 10: Training functions - triplet_parallel +# Tests the default triplet_parallel function +add_test( + NAME test_func_triplet_parallel + COMMAND NNets -c ${CMAKE_SOURCE_DIR}/configs/test_funcs_triplet.json -t + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +set_tests_properties(test_func_triplet_parallel PROPERTIES + TIMEOUT 120 + LABELS "training_funcs;triplet" +) + +# Test 11: Training functions - exhaustive_full_parallel +# Tests the exhaustive search function +add_test( + NAME test_func_exhaustive_parallel + COMMAND NNets -c ${CMAKE_SOURCE_DIR}/configs/test_funcs_exhaustive.json -t + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +set_tests_properties(test_func_exhaustive_parallel PROPERTIES + TIMEOUT 180 + LABELS "training_funcs;exhaustive" +) + +# Test 12: Training functions - random_pair_ext_parallel +# Tests the random pair generation function +add_test( + NAME test_func_random_pair_parallel + COMMAND NNets -c ${CMAKE_SOURCE_DIR}/configs/test_funcs_random_pair.json -t + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +set_tests_properties(test_func_random_pair_parallel PROPERTIES + TIMEOUT 120 + LABELS "training_funcs;random_pair" +) + +# Test 13: Training functions - sequence of multiple functions +# Tests using multiple functions in sequence with error checking after each +add_test( + NAME test_func_sequence + COMMAND NNets -c ${CMAKE_SOURCE_DIR}/configs/test_funcs_sequence.json -t + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +set_tests_properties(test_func_sequence PROPERTIES + TIMEOUT 180 + LABELS "training_funcs;sequence" +) + +# Test 14: List available training functions +# Tests the --list-funcs command +add_test( + NAME test_list_funcs + COMMAND NNets --list-funcs + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} +) +set_tests_properties(test_list_funcs PROPERTIES + TIMEOUT 10 + LABELS "training_funcs;help" +) diff --git a/configs/test_funcs_exhaustive.json b/configs/test_funcs_exhaustive.json new file mode 100644 index 0000000..941db66 --- /dev/null +++ b/configs/test_funcs_exhaustive.json @@ -0,0 +1,10 @@ +{ + "receptors": 10, + "classes": [ + { "id": 0, "word": "" }, + { "id": 1, "word": "x" } + ], + "generate_shifts": false, + "funcs": ["exhaustive_last_parallel", "triplet_parallel"], + "description": "Test config for exhaustive search - uses exhaustive_last_parallel followed by triplet_parallel for convergence" +} diff --git a/configs/test_funcs_random_pair.json b/configs/test_funcs_random_pair.json new file mode 100644 index 0000000..9b527e6 --- /dev/null +++ b/configs/test_funcs_random_pair.json @@ -0,0 +1,10 @@ +{ + "receptors": 10, + "classes": [ + { "id": 0, "word": "" }, + { "id": 1, "word": "x" } + ], + "generate_shifts": false, + "funcs": ["random_pair_ext_parallel", "random_pair_ext_parallel", "random_pair_ext_parallel", "triplet_parallel"], + "description": "Test config for random_pair_ext_parallel function, followed by triplet_parallel to ensure convergence" +} diff --git a/configs/test_funcs_sequence.json b/configs/test_funcs_sequence.json new file mode 100644 index 0000000..30b88de --- /dev/null +++ b/configs/test_funcs_sequence.json @@ -0,0 +1,10 @@ +{ + "receptors": 10, + "classes": [ + { "id": 0, "word": "" }, + { "id": 1, "word": "x" } + ], + "generate_shifts": false, + "funcs": ["random_from_inputs", "random_pair_opt_parallel", "triplet_parallel"], + "description": "Test config for multiple functions in sequence - error checked after each" +} diff --git a/configs/test_funcs_triplet.json b/configs/test_funcs_triplet.json new file mode 100644 index 0000000..04a365b --- /dev/null +++ b/configs/test_funcs_triplet.json @@ -0,0 +1,10 @@ +{ + "receptors": 20, + "classes": [ + { "id": 0, "word": "" }, + { "id": 1, "word": "test" } + ], + "generate_shifts": false, + "funcs": ["triplet_parallel"], + "description": "Test config for triplet_parallel function" +} diff --git a/include/json_io.h b/include/json_io.h index 94d1239..56916c5 100644 --- a/include/json_io.h +++ b/include/json_io.h @@ -13,6 +13,13 @@ #ifndef JSON_IO_H #define JSON_IO_H +// ============================================================================ +// Глобальные переменные для конфигурации функций обучения +// ============================================================================ + +// Список имён функций обучения из конфига (пустой = использовать функцию по умолчанию) +std::vector g_trainingFuncs; + // ============================================================================ // Функции загрузки конфигурации // ============================================================================ @@ -144,6 +151,15 @@ bool loadConfig(const string& configPath, int& receptors) { } } + // Загружаем последовательность функций обучения (если задана) + g_trainingFuncs.clear(); + if (config.contains("funcs")) { + for (const auto& func : config["funcs"]) { + string funcName = func.get(); + g_trainingFuncs.push_back(funcName); + } + } + cout << "Loaded config: " << configPath << endl; cout << " Receptors: " << receptors << endl; cout << " Classes: " << Classes << endl; @@ -151,6 +167,14 @@ bool loadConfig(const string& configPath, int& receptors) { if (config.contains("description")) { cout << " Description: " << config["description"].get() << endl; } + if (!g_trainingFuncs.empty()) { + cout << " Training funcs: "; + for (size_t i = 0; i < g_trainingFuncs.size(); i++) { + if (i > 0) cout << ", "; + cout << g_trainingFuncs[i]; + } + cout << endl; + } return true; } diff --git a/include/learning_funcs/exhaustive_search.h b/include/learning_funcs/exhaustive_search.h new file mode 100644 index 0000000..58648c9 --- /dev/null +++ b/include/learning_funcs/exhaustive_search.h @@ -0,0 +1,553 @@ +/* + * exhaustive_search.h - Функции полного перебора для генерации нейронов + * + * Этот модуль содержит функции, использующие полный перебор комбинаций: + * - exhaustive_full_search (бывш. rod) - полный перебор всех пар нейронов + * - exhaustive_last_combine (бывш. rod2) - комбинирование с последним нейроном + * - combine_old_new (бывш. rod3) - комбинирование старых нейронов с новыми + * + * Эти функции гарантируют нахождение оптимального решения в пределах + * заданного пространства поиска, но работают медленнее случайных методов. + */ + +#ifndef EXHAUSTIVE_SEARCH_H +#define EXHAUSTIVE_SEARCH_H + +#include "learning_func_base.h" + +// ============================================================================ +// Последовательные версии функций +// ============================================================================ + +/** + * Полный перебор комбинаций нейронов (exhaustive_full_search) + * + * Перебирает все комбинации пар нейронов и операций, + * выбирая оптимальную комбинацию с минимальной ошибкой. + * Медленный, но гарантирует нахождение лучшего нейрона. + * + * Создаёт: 1 нейрон + * Сложность: O(N^2 * O) где N - количество нейронов, O - количество операций + * + * @return минимальная достигнутая ошибка + */ +float exhaustive_full_search() { + int i; + float min = big; + int optimal_i = 0; + int optimal_j = 0; + oper optimal_op = op[0]; + float square, sum; + Neiron& cur = nei[Neirons]; + float* curval; + + for (cur.i = 1; cur.i < Neirons; cur.i++) // Выбор 1-го нейрона + { + for (cur.j = 0; cur.j < cur.i; cur.j++) // Выбор 2-го нейрона + { + for (i = 0; i < op_count; i++) // Выбор операции + { + cur.cached = false; + cur.op = op[i]; + sum = 0.0; + curval = GetNeironVector(Neirons); + + // Вычисляем сумму квадратов ошибок + for (int index = 0; index < Images && sum < min; index++) + { + square = vz[index] - curval[index]; + sum += square * square; + } + + if (min > sum) + { + min = sum; + optimal_op = cur.op; + optimal_i = cur.i; + optimal_j = cur.j; + } + } + } + } + + // Сохраняем оптимальные параметры + cur.cached = false; + cur.i = optimal_i; + cur.j = optimal_j; + cur.op = optimal_op; + std::cout << "min = " << min << ", (" << Neirons << ") = (" << optimal_i << ")op(" << optimal_j << ")\n"; + Neirons++; + return min; +} + +/** + * Комбинирование с последним нейроном (exhaustive_last_combine) + * + * Фиксирует первый вход как последний созданный нейрон + * и перебирает только второй вход и операцию. + * Быстрее, чем exhaustive_full_search(), но менее гибкий. + * + * Создаёт: 1 нейрон + * Сложность: O(N * O) где N - количество нейронов, O - количество операций + * + * @return минимальная достигнутая ошибка + */ +float exhaustive_last_combine() { + int i; + float min = big; + int optimal_i = 0; + int optimal_j = 0; + oper optimal_op = op[0]; + float square, sum; + Neiron& cur = nei[Neirons]; + float* curval; + + cur.i = Neirons - 1; // Фиксируем последний нейрон + + for (cur.j = 0; cur.j < cur.i; cur.j++) // Выбор 2-го нейрона + { + for (i = 0; i < op_count; i++) // Выбор операции + { + cur.op = op[i]; + sum = 0.0; + cur.cached = false; + curval = GetNeironVector(Neirons); + + for (int index = 0; index < Images && sum < min; index++) + { + square = vz[index] - curval[index]; + sum += square * square; + } + + if (min > sum) + { + min = sum; + optimal_op = cur.op; + optimal_i = cur.i; + optimal_j = cur.j; + } + } + } + + cur.cached = false; + cur.i = optimal_i; + cur.j = optimal_j; + cur.op = optimal_op; + std::cout << "min = " << min << ", (" << Neirons << ") = (" << optimal_i << ")op(" << optimal_j << ")\n"; + Neirons++; + return min; +} + +/** + * Комбинирование старых и новых нейронов (combine_old_new) + * + * Комбинирует старые нейроны (до Classes*3) с новыми. + * Специализированная стратегия для определённых этапов обучения. + * + * Создаёт: 1 нейрон + * Сложность: O(N_old * N_new * O) + * + * @return минимальная достигнутая ошибка + */ +float combine_old_new() { + int i; + float min = big; + int optimal_i = 0; + int optimal_j = 0; + oper optimal_op = op[0]; + float square, sum; + Neiron& cur = nei[Neirons]; + float* curval; + + for (cur.i = 0; cur.i < Neirons - Classes * 3; cur.i++) // Старые нейроны + { + for (cur.j = Neirons - Classes * 3; cur.j < Neirons; cur.j++) // Новые нейроны + { + for (i = 0; i < op_count; i++) + { + cur.cached = false; + cur.op = op[i]; + sum = 0.0; + curval = GetNeironVector(Neirons); + + for (int index = 0; index < Images && sum < min; index++) + { + square = vz[index] - curval[index]; + sum += square * square; + } + + if (min > sum) + { + min = sum; + optimal_op = cur.op; + optimal_i = cur.i; + optimal_j = cur.j; + } + } + } + } + + cur.cached = false; + cur.i = optimal_i; + cur.j = optimal_j; + cur.op = optimal_op; + std::cout << "min = " << min << ", (" << Neirons << ") = (" << optimal_i << ")op(" << optimal_j << ")\n"; + Neirons++; + return min; +} + +// ============================================================================ +// Многопоточные версии функций +// ============================================================================ + +/** + * Структура результата поиска для потока (exhaustive search) + */ +struct ExhaustiveSearchResult { + float min_error; + int optimal_i; + int optimal_j; + int optimal_op_index; + bool found; + + ExhaustiveSearchResult() : min_error(big), optimal_i(0), optimal_j(0), optimal_op_index(0), found(false) {} +}; + +/** + * Функция потока для параллельного полного перебора + */ +void ExhaustiveSearchThreadFunc( + int start_i, + int end_i, + int /* current_neirons */, + ExhaustiveSearchResult& result, + std::atomic* global_min) +{ + Neiron local_cur; + std::vector local_cache(Images); + + for (local_cur.i = start_i; local_cur.i < end_i; local_cur.i++) + { + float* i_cache = GetNeironVector(local_cur.i); + + for (local_cur.j = 0; local_cur.j < local_cur.i; local_cur.j++) + { + float* j_cache = GetNeironVector(local_cur.j); + + for (int op_idx = 0; op_idx < op_count; op_idx++) + { + local_cur.op = op[op_idx]; + (*local_cur.op)(local_cache.data(), i_cache, j_cache, Images); + + float current_global_min = global_min->load(std::memory_order_relaxed); + float sum = 0.0f; + + for (int index = 0; index < Images && sum < current_global_min; index++) + { + float square = vz[index] - local_cache[index]; + sum += square * square; + } + + if (result.min_error > sum) + { + result.found = true; + result.min_error = sum; + result.optimal_i = local_cur.i; + result.optimal_j = local_cur.j; + result.optimal_op_index = op_idx; + + // Обновляем глобальный минимум + float expected = global_min->load(std::memory_order_relaxed); + while (sum < expected) { + if (global_min->compare_exchange_weak(expected, sum, std::memory_order_relaxed)) { + break; + } + } + } + } + } + } +} + +/** + * Параллельный полный перебор комбинаций нейронов + * + * Многопоточная версия exhaustive_full_search(). + * Распределяет поиск по первому входу нейрона между потоками. + * + * @return минимальная достигнутая ошибка + */ +float exhaustive_full_search_parallel() { + if (!UseMultithreading || NumThreads <= 1 || Neirons < 10) { + return exhaustive_full_search(); + } + + // Прогреваем кэши всех существующих нейронов + for (int n = 0; n < Neirons; n++) { + GetNeironVector(n); + } + + std::vector results(NumThreads); + std::atomic global_min(big); + std::vector threads; + threads.reserve(NumThreads); + + // Распределяем работу по потокам (по первому индексу) + int chunk = (Neirons + NumThreads - 1) / NumThreads; + + for (int t = 0; t < NumThreads; t++) { + int start_i = std::max(1, t * chunk); + int end_i = std::min(Neirons, (t + 1) * chunk); + + if (start_i < end_i) { + threads.emplace_back(ExhaustiveSearchThreadFunc, + start_i, end_i, Neirons, + std::ref(results[t]), &global_min); + } + } + + for (auto& t : threads) { + t.join(); + } + + // Находим лучший результат + float best_min = big; + int best_thread = -1; + for (int t = 0; t < NumThreads; t++) { + if (results[t].found && results[t].min_error < best_min) { + best_min = results[t].min_error; + best_thread = t; + } + } + + if (best_thread >= 0) { + Neiron& cur = nei[Neirons]; + cur.cached = false; + cur.i = results[best_thread].optimal_i; + cur.j = results[best_thread].optimal_j; + cur.op = op[results[best_thread].optimal_op_index]; + std::cout << "min = " << best_min << ", (" << Neirons << ") = (" + << cur.i << ")op(" << cur.j << ") [parallel]\n"; + Neirons++; + return best_min; + } + + return big; +} + +/** + * Параллельное комбинирование с последним нейроном + * + * Многопоточная версия exhaustive_last_combine(). + * + * @return минимальная достигнутая ошибка + */ +float exhaustive_last_combine_parallel() { + if (!UseMultithreading || NumThreads <= 1 || Neirons < 10) { + return exhaustive_last_combine(); + } + + // Прогреваем кэши + for (int n = 0; n < Neirons; n++) { + GetNeironVector(n); + } + + int last_neuron = Neirons - 1; + float* last_cache = GetNeironVector(last_neuron); + + std::vector results(NumThreads); + std::atomic global_min(big); + std::vector threads; + threads.reserve(NumThreads); + + // Лямбда для потока + auto thread_func = [&](int start_j, int end_j, int thread_id) { + std::vector local_cache(Images); + + for (int j = start_j; j < end_j; j++) { + float* j_cache = GetNeironVector(j); + + for (int op_idx = 0; op_idx < op_count; op_idx++) { + (*op[op_idx])(local_cache.data(), last_cache, j_cache, Images); + + float current_global_min = global_min.load(std::memory_order_relaxed); + float sum = 0.0f; + + for (int index = 0; index < Images && sum < current_global_min; index++) { + float square = vz[index] - local_cache[index]; + sum += square * square; + } + + if (results[thread_id].min_error > sum) { + results[thread_id].found = true; + results[thread_id].min_error = sum; + results[thread_id].optimal_i = last_neuron; + results[thread_id].optimal_j = j; + results[thread_id].optimal_op_index = op_idx; + + float expected = global_min.load(std::memory_order_relaxed); + while (sum < expected) { + if (global_min.compare_exchange_weak(expected, sum, std::memory_order_relaxed)) { + break; + } + } + } + } + } + }; + + int chunk = (last_neuron + NumThreads - 1) / NumThreads; + + for (int t = 0; t < NumThreads; t++) { + int start_j = t * chunk; + int end_j = std::min(last_neuron, (t + 1) * chunk); + + if (start_j < end_j) { + threads.emplace_back(thread_func, start_j, end_j, t); + } + } + + for (auto& t : threads) { + t.join(); + } + + // Находим лучший результат + float best_min = big; + int best_thread = -1; + for (int t = 0; t < NumThreads; t++) { + if (results[t].found && results[t].min_error < best_min) { + best_min = results[t].min_error; + best_thread = t; + } + } + + if (best_thread >= 0) { + Neiron& cur = nei[Neirons]; + cur.cached = false; + cur.i = results[best_thread].optimal_i; + cur.j = results[best_thread].optimal_j; + cur.op = op[results[best_thread].optimal_op_index]; + std::cout << "min = " << best_min << ", (" << Neirons << ") = (" + << cur.i << ")op(" << cur.j << ") [parallel]\n"; + Neirons++; + return best_min; + } + + return big; +} + +/** + * Параллельное комбинирование старых и новых нейронов + * + * Многопоточная версия combine_old_new(). + * + * @return минимальная достигнутая ошибка + */ +float combine_old_new_parallel() { + if (!UseMultithreading || NumThreads <= 1) { + return combine_old_new(); + } + + int boundary = Neirons - Classes * 3; + if (boundary <= 0) { + return combine_old_new(); + } + + // Прогреваем кэши + for (int n = 0; n < Neirons; n++) { + GetNeironVector(n); + } + + std::vector results(NumThreads); + std::atomic global_min(big); + std::vector threads; + threads.reserve(NumThreads); + + auto thread_func = [&](int start_i, int end_i, int thread_id) { + std::vector local_cache(Images); + + for (int i = start_i; i < end_i; i++) { + float* i_cache = GetNeironVector(i); + + for (int j = boundary; j < Neirons; j++) { + float* j_cache = GetNeironVector(j); + + for (int op_idx = 0; op_idx < op_count; op_idx++) { + (*op[op_idx])(local_cache.data(), i_cache, j_cache, Images); + + float current_global_min = global_min.load(std::memory_order_relaxed); + float sum = 0.0f; + + for (int index = 0; index < Images && sum < current_global_min; index++) { + float square = vz[index] - local_cache[index]; + sum += square * square; + } + + if (results[thread_id].min_error > sum) { + results[thread_id].found = true; + results[thread_id].min_error = sum; + results[thread_id].optimal_i = i; + results[thread_id].optimal_j = j; + results[thread_id].optimal_op_index = op_idx; + + float expected = global_min.load(std::memory_order_relaxed); + while (sum < expected) { + if (global_min.compare_exchange_weak(expected, sum, std::memory_order_relaxed)) { + break; + } + } + } + } + } + } + }; + + int chunk = (boundary + NumThreads - 1) / NumThreads; + + for (int t = 0; t < NumThreads; t++) { + int start_i = t * chunk; + int end_i = std::min(boundary, (t + 1) * chunk); + + if (start_i < end_i) { + threads.emplace_back(thread_func, start_i, end_i, t); + } + } + + for (auto& t : threads) { + t.join(); + } + + // Находим лучший результат + float best_min = big; + int best_thread = -1; + for (int t = 0; t < NumThreads; t++) { + if (results[t].found && results[t].min_error < best_min) { + best_min = results[t].min_error; + best_thread = t; + } + } + + if (best_thread >= 0) { + Neiron& cur = nei[Neirons]; + cur.cached = false; + cur.i = results[best_thread].optimal_i; + cur.j = results[best_thread].optimal_j; + cur.op = op[results[best_thread].optimal_op_index]; + std::cout << "min = " << best_min << ", (" << Neirons << ") = (" + << cur.i << ")op(" << cur.j << ") [parallel]\n"; + Neirons++; + return best_min; + } + + return big; +} + +// Сохраняем обратную совместимость со старыми именами +inline float rod() { return exhaustive_full_search(); } +inline float rod2() { return exhaustive_last_combine(); } +inline float rod3() { return combine_old_new(); } +inline float rod_parallel() { return exhaustive_full_search_parallel(); } +inline float rod2_parallel() { return exhaustive_last_combine_parallel(); } +inline float rod3_parallel() { return combine_old_new_parallel(); } + +#endif // EXHAUSTIVE_SEARCH_H diff --git a/include/learning_funcs/learning_func_base.h b/include/learning_funcs/learning_func_base.h new file mode 100644 index 0000000..0a2600c --- /dev/null +++ b/include/learning_funcs/learning_func_base.h @@ -0,0 +1,90 @@ +/* + * learning_func_base.h - Базовые определения для функций обучения + * + * Этот модуль содержит: + * - Общие типы и структуры для функций генерации нейронов + * - Объявление глобальных переменных, используемых функциями обучения + * - Вспомогательные функции для работы с нейронами + * + * Примечание: Этот файл должен включаться первым перед другими + * файлами функций обучения. + */ + +#ifndef LEARNING_FUNC_BASE_H +#define LEARNING_FUNC_BASE_H + +#include +#include +#include +#include +#include +#include + +// Максимальное значение ошибки (используется для инициализации) +extern const float big; + +// Количество операций +extern const int op_count; + +// Массив операций +extern oper op[]; + +// Количество потоков и флаг многопоточности +extern int NumThreads; +extern bool UseMultithreading; + +// Глобальные переменные сети +extern int Neirons; +extern int Images; +extern int Inputs; +extern int Receptors; +extern int Classes; +extern std::vector nei; +extern std::vector vz; +extern std::vector> vx; +extern std::vector NetInput; + +// Константы итераций +extern const int rod2_iter; +extern const int rndrod_iter; +extern const int rndrod2_iter; +extern const int MAX_NEURONS; + +// ============================================================================ +// Типы для регистрации функций обучения +// ============================================================================ + +/** + * Тип функции обучения + * + * @return ошибка обучения (чем меньше - тем лучше) + */ +typedef float (*LearningFunc)(); + +/** + * Структура описания функции обучения + */ +struct LearningFuncInfo { + std::string name; // Имя функции для использования в конфиге + std::string description; // Описание функции + LearningFunc func; // Указатель на функцию + bool is_parallel; // Является ли функция параллельной +}; + +// ============================================================================ +// Прототипы функций из neuron_generation.h +// ============================================================================ + +// Функция получения вектора значений нейрона (для всех образов) +float* __fastcall GetNeironVector(const int i); + +// Функция получения одиночного значения нейрона +float __fastcall GetNeironVal(const int i); + +// Функция очистки кэша значений +void clear_val_cache(std::vector& n, const int size); + +// Функция инициализации нейронов +void initNeurons(); + +#endif // LEARNING_FUNC_BASE_H diff --git a/include/learning_funcs/learning_funcs.h b/include/learning_funcs/learning_funcs.h new file mode 100644 index 0000000..1f9c0bf --- /dev/null +++ b/include/learning_funcs/learning_funcs.h @@ -0,0 +1,292 @@ +/* + * learning_funcs.h - Единый интерфейс для всех функций обучения + * + * Этот модуль предоставляет: + * - Единую точку включения для всех функций обучения + * - Реестр функций обучения для динамического выбора + * - Функции для работы с последовательностями обучения + * + * Использование: + * 1. Подключите только этот файл: #include "learning_funcs/learning_funcs.h" + * 2. Используйте getLearningFunc() для получения функции по имени + * 3. Используйте getAvailableLearningFuncs() для получения списка функций + */ + +#ifndef LEARNING_FUNCS_H +#define LEARNING_FUNCS_H + +#include +#include +#include +#include +#include + +// Подключаем все модули функций обучения +// Примечание: эти заголовки зависят от определений в neuron_generation.h, +// поэтому должны включаться после него +#include "exhaustive_search.h" +#include "random_search.h" +#include "triplet_search.h" + +// ============================================================================ +// Реестр функций обучения +// ============================================================================ + +/** + * Информация о функции обучения + */ +struct LearningFunctionInfo { + std::string name; // Имя функции для использования в конфиге + std::string description; // Описание функции на русском + std::string old_name; // Старое имя для обратной совместимости + LearningFunc func; // Указатель на функцию + bool is_parallel; // Является ли функция параллельной + int neurons_created; // Сколько нейронов создаёт функция (0 = переменное) +}; + +/** + * Получение списка всех доступных функций обучения + * + * @return вектор информации о функциях + */ +inline std::vector getAvailableLearningFuncs() { + return { + // Полный перебор (последовательные) + { + "exhaustive_full", + "Полный перебор всех пар нейронов и операций", + "rod", + exhaustive_full_search, + false, + 1 + }, + { + "exhaustive_last", + "Комбинирование с последним созданным нейроном", + "rod2", + exhaustive_last_combine, + false, + 1 + }, + { + "combine_old_new", + "Комбинирование старых нейронов с новыми", + "rod3", + combine_old_new, + false, + 1 + }, + + // Полный перебор (параллельные) + { + "exhaustive_full_parallel", + "Параллельный полный перебор всех пар нейронов", + "rod_parallel", + exhaustive_full_search_parallel, + true, + 1 + }, + { + "exhaustive_last_parallel", + "Параллельное комбинирование с последним нейроном", + "rod2_parallel", + exhaustive_last_combine_parallel, + true, + 1 + }, + { + "combine_old_new_parallel", + "Параллельное комбинирование старых с новыми", + "rod3_parallel", + combine_old_new_parallel, + true, + 1 + }, + + // Случайный поиск (последовательные) + { + "random_single", + "Генерация одного случайного нейрона", + "rndrod", + random_neurons, + false, + 1 + }, + { + "random_from_inputs", + "Случайная генерация на основе входов", + "rndrod0", + random_from_inputs, + false, + 1 + }, + { + "random_pair_opt", + "Оптимизированная генерация пары нейронов", + "rndrod2", + random_pair_optimized, + false, + 2 + }, + { + "random_pair_ext", + "Расширенная генерация пары нейронов", + "rndrod3", + random_pair_extended, + false, + 2 + }, + + // Случайный поиск (параллельные) + { + "random_pair_opt_parallel", + "Параллельная оптимизированная генерация пары", + "rndrod2_parallel", + random_pair_optimized_parallel, + true, + 2 + }, + { + "random_pair_ext_parallel", + "Параллельная расширенная генерация пары", + "rndrod3_parallel", + random_pair_extended_parallel, + true, + 2 + }, + + // Генерация тройки нейронов (последовательная) + { + "triplet", + "Генерация тройки связанных нейронов (основной метод)", + "rndrod4", + triplet_random, + false, + 3 + }, + + // Генерация тройки нейронов (параллельная) - метод по умолчанию + { + "triplet_parallel", + "Параллельная генерация тройки нейронов (метод по умолчанию)", + "rndrod4_parallel", + triplet_random_parallel, + true, + 3 + } + }; +} + +/** + * Получение функции обучения по имени + * + * Поддерживает как новые, так и старые имена для обратной совместимости. + * + * @param name - имя функции (новое или старое) + * @return указатель на функцию или nullptr если не найдена + */ +inline LearningFunc getLearningFunc(const std::string& name) { + auto funcs = getAvailableLearningFuncs(); + for (const auto& info : funcs) { + if (info.name == name || info.old_name == name) { + return info.func; + } + } + return nullptr; +} + +/** + * Получение информации о функции обучения по имени + * + * @param name - имя функции + * @param info - выходной параметр с информацией + * @return true если функция найдена + */ +inline bool getLearningFuncInfo(const std::string& name, LearningFunctionInfo& info) { + auto funcs = getAvailableLearningFuncs(); + for (const auto& f : funcs) { + if (f.name == name || f.old_name == name) { + info = f; + return true; + } + } + return false; +} + +/** + * Проверка существования функции обучения + * + * @param name - имя функции + * @return true если функция существует + */ +inline bool learningFuncExists(const std::string& name) { + auto funcs = getAvailableLearningFuncs(); + for (const auto& info : funcs) { + if (info.name == name || info.old_name == name) { + return true; + } + } + return false; +} + +/** + * Вывод списка доступных функций обучения + */ +inline void printAvailableLearningFuncs() { + std::cout << "\nДоступные функции обучения:" << std::endl; + std::cout << "==========================" << std::endl; + + auto funcs = getAvailableLearningFuncs(); + + std::cout << "\nПолный перебор (детерминированные):" << std::endl; + for (const auto& f : funcs) { + if (f.name.find("exhaustive") != std::string::npos || f.name.find("combine") != std::string::npos) { + std::cout << " " << f.name; + if (f.is_parallel) std::cout << " [parallel]"; + std::cout << " - " << f.description; + std::cout << " (создаёт " << f.neurons_created << " нейрон(ов))" << std::endl; + } + } + + std::cout << "\nСлучайный поиск:" << std::endl; + for (const auto& f : funcs) { + if (f.name.find("random") != std::string::npos) { + std::cout << " " << f.name; + if (f.is_parallel) std::cout << " [parallel]"; + std::cout << " - " << f.description; + std::cout << " (создаёт " << f.neurons_created << " нейрон(ов))" << std::endl; + } + } + + std::cout << "\nГенерация тройки (рекомендуемые):" << std::endl; + for (const auto& f : funcs) { + if (f.name.find("triplet") != std::string::npos) { + std::cout << " " << f.name; + if (f.is_parallel) std::cout << " [parallel]"; + std::cout << " - " << f.description; + std::cout << " (создаёт " << f.neurons_created << " нейрон(ов))" << std::endl; + } + } + + std::cout << "\nПо умолчанию: triplet_parallel" << std::endl; +} + +/** + * Получение функции обучения по умолчанию + * + * @return указатель на функцию triplet_random_parallel + */ +inline LearningFunc getDefaultLearningFunc() { + return triplet_random_parallel; +} + +/** + * Получение имени функции по умолчанию + * + * @return "triplet_parallel" + */ +inline std::string getDefaultLearningFuncName() { + return "triplet_parallel"; +} + +#endif // LEARNING_FUNCS_H diff --git a/include/learning_funcs/random_search.h b/include/learning_funcs/random_search.h new file mode 100644 index 0000000..3a9aef4 --- /dev/null +++ b/include/learning_funcs/random_search.h @@ -0,0 +1,500 @@ +/* + * random_search.h - Функции случайного поиска для генерации нейронов + * + * Этот модуль содержит функции, использующие случайный поиск: + * - random_neurons (бывш. rndrod) - случайная генерация заданного количества нейронов + * - random_from_inputs (бывш. rndrod0) - случайная генерация на основе входов + * - random_pair_optimized (бывш. rndrod2) - оптимизированная генерация пары нейронов + * - random_pair_extended (бывш. rndrod3) - расширенная генерация пары нейронов + * + * Эти функции работают быстрее детерминированных методов, но не гарантируют + * нахождение оптимального решения. + */ + +#ifndef RANDOM_SEARCH_H +#define RANDOM_SEARCH_H + +#include "learning_func_base.h" +#include + +// ============================================================================ +// Последовательные версии функций +// ============================================================================ + +/** + * Случайная генерация нейронов (random_neurons) + * + * Создаёт заданное количество случайных нейронов. + * Используется для расширения пространства поиска. + * + * Создаёт: count нейронов + * Сложность: O(count) + * + * @param count - количество создаваемых нейронов + */ +void random_neurons_n(unsigned count) { + do + { + nei[Neirons].cached = false; + nei[Neirons].i = rand() % (Neirons); + nei[Neirons].j = rand() % (Neirons); + nei[Neirons].op = op[rand() % op_count]; + std::cout << "(" << Neirons << ") = (" << nei[Neirons].i << ")op(" << nei[Neirons].j << ")\n"; + Neirons++; + } while (--count > 0); +} + +/** + * Случайная генерация одного нейрона + * + * Создаёт один случайный нейрон с вычислением ошибки. + * Обёртка для использования в системе функций обучения. + * + * @return ошибка нейрона (для совместимости с интерфейсом) + */ +float random_neurons() { + nei[Neirons].cached = false; + nei[Neirons].i = rand() % (Neirons); + nei[Neirons].j = rand() % (Neirons); + nei[Neirons].op = op[rand() % op_count]; + + // Вычисляем ошибку созданного нейрона + float* curval = GetNeironVector(Neirons); + float sum = 0.0f; + for (int index = 0; index < Images; index++) { + float square = vz[index] - curval[index]; + sum += square * square; + } + + std::cout << "(" << Neirons << ") = (" << nei[Neirons].i << ")op(" << nei[Neirons].j << "), error = " << sum << "\n"; + Neirons++; + return sum; +} + +/** + * Случайная генерация на основе входов (random_from_inputs) + * + * Создаёт случайные нейроны, комбинируя только входы сети. + * Полезно на начальных этапах обучения для создания базовых комбинаций. + * + * Создаёт: count нейронов + * Сложность: O(count) + * + * @param count - количество создаваемых нейронов + */ +void random_from_inputs_n(unsigned count) { + do + { + nei[Neirons].cached = false; + nei[Neirons].i = rand() % (Inputs); + nei[Neirons].j = rand() % (Receptors); + nei[Neirons].op = op[rand() % op_count]; + std::cout << "(" << Neirons << ") = (" << nei[Neirons].i << ")op(" << nei[Neirons].j << ")\n"; + Neirons++; + } while (--count > 0); +} + +/** + * Случайная генерация одного нейрона на основе входов + * + * @return ошибка нейрона + */ +float random_from_inputs() { + nei[Neirons].cached = false; + nei[Neirons].i = rand() % (Inputs); + nei[Neirons].j = rand() % (Receptors); + nei[Neirons].op = op[rand() % op_count]; + + // Вычисляем ошибку + float* curval = GetNeironVector(Neirons); + float sum = 0.0f; + for (int index = 0; index < Images; index++) { + float square = vz[index] - curval[index]; + sum += square * square; + } + + std::cout << "(" << Neirons << ") = (" << nei[Neirons].i << ")op(" << nei[Neirons].j << "), error = " << sum << "\n"; + Neirons++; + return sum; +} + +/** + * Оптимизированная случайная генерация пары нейронов (random_pair_optimized) + * + * Создаёт пару нейронов с оптимизированными параметрами. + * Ищет лучшую комбинацию среди случайных вариантов. + * Первый нейрон комбинирует недавно созданные нейроны с остальными. + * + * Создаёт: 2 нейрона + * Сложность: O(Inputs * Neirons * rndrod_iter) + * + * @return минимальная достигнутая ошибка + */ +float random_pair_optimized() { + int count, count_max = Inputs * Neirons * rndrod_iter; + float min = big; + float square, sum; + int r[5] = { 0,0,0,0,0 }; + oper ro[5] = { 0,0,0,0,0 }; + int Neirons_p_1 = Neirons + 1; + Neiron& Neiron_A = nei[Neirons]; + Neiron& Neiron_B = nei[Neirons_p_1]; + + Neiron_B.i = Neirons; + + for (count = 0; count < count_max; count++) + { + Neiron_A.cached = false; + Neiron_A.i = rand() % rndrod_iter + Neirons - rndrod_iter; // Последние случайные + if (Neiron_A.i < 0) Neiron_A.i = 0; + Neiron_A.j = rand() % std::max(1, Neirons - rndrod_iter); + Neiron_A.op = op[rand() % op_count]; + + Neiron_B.cached = false; + Neiron_B.j = rand() % Inputs; + Neiron_B.op = op[rand() % op_count]; + + float* NBVal = GetNeironVector(Neirons_p_1); + + sum = 0.0; + + for (int index = 0; index < Images && sum < min; index++) + { + square = vz[index] - NBVal[index]; + sum += square * square; + } + + if (min > sum) + { + min = sum; + ro[0] = Neiron_A.op; + r[1] = Neiron_A.i; + r[2] = Neiron_A.j; + ro[3] = Neiron_B.op; + r[4] = Neiron_B.j; + } + } + + Neiron_A.cached = false; + Neiron_A.i = r[1]; + Neiron_A.j = r[2]; + Neiron_A.op = ro[0]; + Neiron_B.cached = false; + Neiron_B.j = r[4]; + Neiron_B.op = ro[3]; + std::cout << "min = " << min << ", (" << Neirons + 1 << ") = ((" << r[1] << ")op(" << r[2] << "))op(" << r[4] << ")\n"; + Neirons += 2; + return min; +} + +/** + * Расширенная случайная генерация пары нейронов (random_pair_extended) + * + * Аналогична random_pair_optimized(), но с большим пространством поиска. + * Оба входа нейрона A выбираются из всех существующих нейронов. + * + * Создаёт: 2 нейрона + * Сложность: O(Neirons^2 * 6) + * + * @return минимальная достигнутая ошибка + */ +float random_pair_extended() { + int count, count_max = Neirons * Neirons * 6; + float min = big; + float square, sum; + int r[5] = { 0,0,0,0,0 }; + oper ro[5] = { 0,0,0,0,0 }; + int Neirons_p_1 = Neirons + 1; + Neiron& Neiron_A = nei[Neirons]; + Neiron& Neiron_B = nei[Neirons_p_1]; + + Neiron_B.i = Neirons; + + for (count = 0; count < count_max; count++) + { + Neiron_A.cached = false; + Neiron_A.i = rand() % Neirons; + Neiron_A.j = rand() % Neirons; + Neiron_A.op = op[rand() % op_count]; + + Neiron_B.cached = false; + Neiron_B.j = rand() % Neirons; + Neiron_B.op = op[rand() % op_count]; + + float* NBVal = GetNeironVector(Neirons_p_1); + + sum = 0.0; + + for (int index = 0; index < Images && sum < min; index++) + { + square = vz[index] - NBVal[index]; + sum += square * square; + } + + if (min > sum) + { + min = sum; + ro[0] = Neiron_A.op; + r[1] = Neiron_A.i; + r[2] = Neiron_A.j; + ro[3] = Neiron_B.op; + r[4] = Neiron_B.j; + } + } + + Neiron_A.cached = false; + Neiron_A.i = r[1]; + Neiron_A.j = r[2]; + Neiron_A.op = ro[0]; + Neiron_B.cached = false; + Neiron_B.j = r[4]; + Neiron_B.op = ro[3]; + std::cout << "min = " << min << ", (" << Neirons + 1 << ") = ((" << r[1] << ")op(" << r[2] << "))op(" << r[4] << ")\n"; + Neirons += 2; + return min; +} + +// ============================================================================ +// Многопоточные версии функций +// ============================================================================ + +/** + * Структура результата для пары нейронов + */ +struct PairSearchResult { + float min_error; + int A_i, A_j, B_j; + int A_op_index, B_op_index; + bool found; + + PairSearchResult() : min_error(big), A_i(0), A_j(0), B_j(0), A_op_index(0), B_op_index(0), found(false) {} +}; + +/** + * Функция потока для параллельного поиска пары нейронов + */ +void PairSearchThreadFunc( + int thread_id, + int iterations_per_thread, + int current_neirons, + unsigned int seed, + bool optimized_mode, + PairSearchResult& result, + std::atomic* global_min) +{ + // Локальный генератор случайных чисел + unsigned int local_seed = seed + thread_id * 1099087573u; + auto local_rand = [&local_seed]() -> int { + local_seed = local_seed * 1103515245u + 12345u; + return (int)((local_seed >> 16) & 0x7FFF); + }; + + std::vector A_Vector(Images), B_Vector(Images); + + for (int count = 0; count < iterations_per_thread; count++) + { + int A_i, A_j, B_j; + + if (optimized_mode) { + // Режим random_pair_optimized + A_i = local_rand() % rndrod_iter + current_neirons - rndrod_iter; + if (A_i < 0) A_i = 0; + A_j = local_rand() % std::max(1, current_neirons - rndrod_iter); + B_j = local_rand() % Inputs; + } else { + // Режим random_pair_extended + A_i = local_rand() % current_neirons; + A_j = local_rand() % current_neirons; + B_j = local_rand() % current_neirons; + } + + float* A_i_cache = GetNeironVector(A_i); + float* A_j_cache = GetNeironVector(A_j); + float* B_j_cache = GetNeironVector(B_j); + + for (int A_op = 0; A_op < op_count; A_op++) + { + (*op[A_op])(A_Vector.data(), A_i_cache, A_j_cache, Images); + + for (int B_op = 0; B_op < op_count; B_op++) + { + (*op[B_op])(B_Vector.data(), A_Vector.data(), B_j_cache, Images); + + float current_global_min = global_min->load(std::memory_order_relaxed); + float sum = 0.0f; + + for (int index = 0; index < Images && sum < current_global_min; index++) + { + float square = vz[index] - B_Vector[index]; + sum += square * square; + } + + if (result.min_error > sum) + { + result.found = true; + result.min_error = sum; + result.A_i = A_i; + result.A_j = A_j; + result.B_j = B_j; + result.A_op_index = A_op; + result.B_op_index = B_op; + + float expected = global_min->load(std::memory_order_relaxed); + while (sum < expected) { + if (global_min->compare_exchange_weak(expected, sum, std::memory_order_relaxed)) { + break; + } + } + } + } + } + } +} + +/** + * Параллельная оптимизированная генерация пары нейронов + * + * @return минимальная достигнутая ошибка + */ +float random_pair_optimized_parallel() { + if (!UseMultithreading || NumThreads <= 1) { + return random_pair_optimized(); + } + + int count_max = Inputs * Neirons * rndrod_iter; + int iterations_per_thread = std::max(100, (count_max + NumThreads - 1) / NumThreads); + + // Прогреваем кэши + for (int n = 0; n < Neirons; n++) { + GetNeironVector(n); + } + + std::vector results(NumThreads); + std::atomic global_min(big); + std::vector threads; + threads.reserve(NumThreads); + + unsigned int base_seed = (unsigned int)(Neirons * 1099087573u + 12345u); + + for (int t = 0; t < NumThreads; t++) { + threads.emplace_back(PairSearchThreadFunc, + t, iterations_per_thread, Neirons, + base_seed, true, + std::ref(results[t]), &global_min); + } + + for (auto& t : threads) { + t.join(); + } + + // Находим лучший результат + float best_min = big; + int best_thread = -1; + for (int t = 0; t < NumThreads; t++) { + if (results[t].found && results[t].min_error < best_min) { + best_min = results[t].min_error; + best_thread = t; + } + } + + if (best_thread >= 0) { + Neiron& Neiron_A = nei[Neirons]; + Neiron& Neiron_B = nei[Neirons + 1]; + + Neiron_A.cached = false; + Neiron_A.i = results[best_thread].A_i; + Neiron_A.j = results[best_thread].A_j; + Neiron_A.op = op[results[best_thread].A_op_index]; + + Neiron_B.cached = false; + Neiron_B.i = Neirons; + Neiron_B.j = results[best_thread].B_j; + Neiron_B.op = op[results[best_thread].B_op_index]; + + std::cout << "min = " << best_min << ", (" << Neirons + 1 << ") = ((" + << Neiron_A.i << ")op(" << Neiron_A.j << "))op(" << Neiron_B.j << ") [parallel]\n"; + Neirons += 2; + return best_min; + } + + return big; +} + +/** + * Параллельная расширенная генерация пары нейронов + * + * @return минимальная достигнутая ошибка + */ +float random_pair_extended_parallel() { + if (!UseMultithreading || NumThreads <= 1) { + return random_pair_extended(); + } + + int count_max = Neirons * Neirons * 6; + int iterations_per_thread = std::max(100, (count_max + NumThreads - 1) / NumThreads); + + // Прогреваем кэши + for (int n = 0; n < Neirons; n++) { + GetNeironVector(n); + } + + std::vector results(NumThreads); + std::atomic global_min(big); + std::vector threads; + threads.reserve(NumThreads); + + unsigned int base_seed = (unsigned int)(Neirons * 1099087573u + 12345u); + + for (int t = 0; t < NumThreads; t++) { + threads.emplace_back(PairSearchThreadFunc, + t, iterations_per_thread, Neirons, + base_seed, false, + std::ref(results[t]), &global_min); + } + + for (auto& t : threads) { + t.join(); + } + + // Находим лучший результат + float best_min = big; + int best_thread = -1; + for (int t = 0; t < NumThreads; t++) { + if (results[t].found && results[t].min_error < best_min) { + best_min = results[t].min_error; + best_thread = t; + } + } + + if (best_thread >= 0) { + Neiron& Neiron_A = nei[Neirons]; + Neiron& Neiron_B = nei[Neirons + 1]; + + Neiron_A.cached = false; + Neiron_A.i = results[best_thread].A_i; + Neiron_A.j = results[best_thread].A_j; + Neiron_A.op = op[results[best_thread].A_op_index]; + + Neiron_B.cached = false; + Neiron_B.i = Neirons; + Neiron_B.j = results[best_thread].B_j; + Neiron_B.op = op[results[best_thread].B_op_index]; + + std::cout << "min = " << best_min << ", (" << Neirons + 1 << ") = ((" + << Neiron_A.i << ")op(" << Neiron_A.j << "))op(" << Neiron_B.j << ") [parallel]\n"; + Neirons += 2; + return best_min; + } + + return big; +} + +// Сохраняем обратную совместимость со старыми именами +inline void rndrod(unsigned count) { random_neurons_n(count); } +inline void rndrod0(unsigned count) { random_from_inputs_n(count); } +inline float rndrod2() { return random_pair_optimized(); } +inline float rndrod3() { return random_pair_extended(); } +inline float rndrod2_parallel() { return random_pair_optimized_parallel(); } +inline float rndrod3_parallel() { return random_pair_extended_parallel(); } + +#endif // RANDOM_SEARCH_H diff --git a/include/learning_funcs/triplet_search.h b/include/learning_funcs/triplet_search.h new file mode 100644 index 0000000..390149c --- /dev/null +++ b/include/learning_funcs/triplet_search.h @@ -0,0 +1,328 @@ +/* + * triplet_search.h - Функции генерации тройки нейронов + * + * Этот модуль содержит функции генерации трёх связанных нейронов: + * - triplet_random (бывш. rndrod4) - генерация тройки со случайным поиском + * - triplet_random_parallel (бывш. rndrod4_parallel) - многопоточная версия + * + * Тройка нейронов состоит из: + * - Нейрон A: комбинирует существующие нейроны + * - Нейрон B: комбинирует существующие нейроны + * - Нейрон C: объединяет A и B + * + * Это основной метод обучения в текущей версии, обеспечивающий + * создание более сложных функций за счёт иерархической структуры. + */ + +#ifndef TRIPLET_SEARCH_H +#define TRIPLET_SEARCH_H + +#include "learning_func_base.h" + +// ============================================================================ +// Последовательная версия функции +// ============================================================================ + +/** + * Генерация тройки нейронов (triplet_random) + * + * Создаёт три связанных нейрона: A, B и C. + * C объединяет A и B, обеспечивая более сложные функции. + * Это основной метод обучения в текущей версии. + * + * Создаёт: 3 нейрона (A, B, C) + * Сложность: O(Neirons * Receptors * 4 * op_count^2) + * + * @return минимальная достигнутая ошибка, или big если не найдено + */ +float triplet_random() { + int count, count_max = Neirons * Receptors * 4; + float min = big; + float square, sum; + int A_id = Neirons; + int B_id = Neirons + 1; + int C_id = Neirons + 2; + Neiron& Neiron_A = nei[A_id]; + Neiron& Neiron_B = nei[B_id]; + Neiron& Neiron_C = nei[C_id]; + Neiron optimal_A, optimal_B, optimal_C; + bool finded = false; + + // C объединяет A и B + Neiron_C.i = A_id; + Neiron_C.j = B_id; + + // Инициализируем A случайными значениями + Neiron_A.i = rand() % Neirons; + Neiron_A.j = rand() % Neirons; + Neiron_A.op = op[rand() % op_count]; + Neiron_A.cached = false; + + for (count = 0; count < count_max; count++) + { + // Генерируем случайные параметры для B + Neiron_B.i = rand() % Neirons; + Neiron_B.j = rand() % Neirons; + + // Перебираем операции для B и C + for (int B_op = 0; B_op < op_count; B_op++) + { + Neiron_B.op = op[B_op]; + Neiron_B.cached = false; + + for (int C_op = 0; C_op < op_count; C_op++) + { + Neiron_C.op = op[C_op]; + Neiron_C.cached = false; + + float* C_Vector = GetNeironVector(C_id); + sum = 0.0; + + // Вычисляем ошибку по всем образам + for (int index = 0; index < Images && sum < min; index++) + { + square = vz[index] - C_Vector[index]; + sum += square * square; + } + + if (min > sum) + { + finded = true; + min = sum; + optimal_A = Neiron_A; + optimal_B = Neiron_B; + optimal_C = Neiron_C; + + // Используем оптимальный нейрон B как новый A + Neiron_A = Neiron_B; + } + } + } + } + + if (finded) + { + Neiron_A = optimal_A; + Neiron_B = optimal_B; + Neiron_C = optimal_C; + Neirons += 3; + return min; + } + else + return big; +} + +// ============================================================================ +// Многопоточные версии функций +// ============================================================================ + +/** + * Структура для хранения результата поиска в потоке + */ +struct TripletSearchResult { + float min_error; + Neiron optimal_A; + Neiron optimal_B; + Neiron optimal_C; + bool found; + + TripletSearchResult() : min_error(big), found(false) {} +}; + +/** + * Функция потока для параллельного поиска оптимальных нейронов + */ +void TripletSearchThreadFunc( + int thread_id, + int iterations_per_thread, + int current_neirons, + unsigned int seed, + TripletSearchResult& result, + std::atomic* global_min) +{ + // Локальный генератор случайных чисел для потока + unsigned int local_seed = seed + thread_id * 1099087573u; + auto local_rand = [&local_seed]() -> int { + local_seed = local_seed * 1103515245u + 12345u; + return (int)((local_seed >> 16) & 0x7FFF); + }; + + Neiron local_A, local_B, local_C; + std::vector A_Vector(Images), B_Vector(Images), C_Vector(Images); + + // Инициализируем A случайными значениями + local_A.i = local_rand() % current_neirons; + local_A.j = local_rand() % current_neirons; + local_A.op = op[local_rand() % op_count]; + + float* A_i_cache = GetNeironVector(local_A.i); + float* A_j_cache = GetNeironVector(local_A.j); + (*local_A.op)(A_Vector.data(), A_i_cache, A_j_cache, Images); + + for (int count = 0; count < iterations_per_thread; count++) + { + // Генерируем случайные параметры для B + local_B.i = local_rand() % current_neirons; + local_B.j = local_rand() % current_neirons; + + float* B_i_cache = GetNeironVector(local_B.i); + float* B_j_cache = GetNeironVector(local_B.j); + + // Перебираем операции для B и C + for (int B_op = 0; B_op < op_count; B_op++) + { + local_B.op = op[B_op]; + (*local_B.op)(B_Vector.data(), B_i_cache, B_j_cache, Images); + + for (int C_op = 0; C_op < op_count; C_op++) + { + local_C.op = op[C_op]; + (*local_C.op)(C_Vector.data(), A_Vector.data(), B_Vector.data(), Images); + + // Вычисляем ошибку по всем образам + float current_global_min = global_min->load(std::memory_order_relaxed); + float sum = 0.0f; + for (int index = 0; index < Images && sum < current_global_min; index++) + { + float square = vz[index] - C_Vector[index]; + sum += square * square; + } + + if (result.min_error > sum) + { + result.found = true; + result.min_error = sum; + result.optimal_A = local_A; + result.optimal_B = local_B; + result.optimal_C = local_C; + + // Обновляем глобальный минимум для early termination в других потоках + float expected = global_min->load(std::memory_order_relaxed); + while (sum < expected) { + if (global_min->compare_exchange_weak(expected, sum, std::memory_order_relaxed)) { + break; + } + } + + // Используем оптимальный нейрон B как новый A + local_A = local_B; + for (int im = 0; im < Images; im++) { + A_Vector[im] = B_Vector[im]; + } + } + } + } + } +} + +/** + * Многопоточная генерация тройки нейронов (triplet_random_parallel) + * + * Параллельная версия triplet_random(), распределяющая поиск + * между несколькими потоками. Каждый поток исследует + * случайные комбинации параметров нейронов независимо. + * + * Стратегия распределения работы: + * - Общее количество итераций делится между потоками + * - Это обеспечивает приблизительно такое же качество поиска + * как однопоточная версия, но за меньшее время + * - Каждый поток имеет минимальное гарантированное количество итераций + * + * Создаёт: 3 нейрона (A, B, C) + * + * @return минимальная достигнутая ошибка, или big если не найдено + */ +float triplet_random_parallel() { + // Если многопоточность отключена или только 1 поток, используем последовательную версию + if (!UseMultithreading || NumThreads <= 1) { + return triplet_random(); + } + + // Общее количество итераций для однопоточного поиска + int count_max = Neirons * Receptors * 4; + + // Минимальное количество итераций на поток для качественного поиска + const int MIN_ITERATIONS_PER_THREAD = 1000; + + // Вычисляем количество итераций на поток + int base_iterations_per_thread = (count_max + NumThreads - 1) / NumThreads; + int iterations_per_thread = std::max(base_iterations_per_thread, MIN_ITERATIONS_PER_THREAD); + + // Используем однопоточную версию если count_max слишком мал + if (count_max < 2000) { + return triplet_random(); + } + + // Создаём результаты для каждого потока + std::vector results(NumThreads); + + // Атомарная переменная для синхронизации глобального минимума + std::atomic global_min(big); + + // Получаем начальное значение для генераторов случайных чисел + unsigned int base_seed = (unsigned int)(Neirons * 1099087573u + 12345u); + + // Предварительно прогреваем кэши всех существующих нейронов + for (int n = 0; n < Neirons; n++) { + GetNeironVector(n); + } + + // Запускаем потоки + std::vector threads; + threads.reserve(NumThreads); + + for (int t = 0; t < NumThreads; t++) { + threads.emplace_back(TripletSearchThreadFunc, + t, + iterations_per_thread, + Neirons, + base_seed, + std::ref(results[t]), + &global_min); + } + + // Ждём завершения всех потоков + for (auto& t : threads) { + t.join(); + } + + // Находим лучший результат среди всех потоков + float best_min = big; + int best_thread = -1; + for (int t = 0; t < NumThreads; t++) { + if (results[t].found && results[t].min_error < best_min) { + best_min = results[t].min_error; + best_thread = t; + } + } + + if (best_thread >= 0) { + // Сохраняем оптимальные нейроны + int A_id = Neirons; + int B_id = Neirons + 1; + int C_id = Neirons + 2; + + nei[A_id] = results[best_thread].optimal_A; + nei[A_id].cached = false; + nei[B_id] = results[best_thread].optimal_B; + nei[B_id].cached = false; + nei[C_id] = results[best_thread].optimal_C; + nei[C_id].cached = false; + + // Устанавливаем правильные связи для C + nei[C_id].i = A_id; + nei[C_id].j = B_id; + + Neirons += 3; + return best_min; + } + else { + return big; + } +} + +// Сохраняем обратную совместимость со старыми именами +inline float rndrod4() { return triplet_random(); } +inline float rndrod4_parallel() { return triplet_random_parallel(); } + +#endif // TRIPLET_SEARCH_H diff --git a/include/neuron_generation.h b/include/neuron_generation.h index 09ce2ab..c462441 100644 --- a/include/neuron_generation.h +++ b/include/neuron_generation.h @@ -8,6 +8,13 @@ * * Примечание: Этот файл предназначен для включения в main.cpp после * определения всех необходимых типов и переменных. + * + * Начиная с версии 1.1, функции обучения вынесены в отдельные файлы + * в директории learning_funcs/ для лучшей модульности: + * - exhaustive_search.h - функции полного перебора + * - random_search.h - функции случайного поиска + * - triplet_search.h - функции генерации тройки нейронов + * - learning_funcs.h - единый интерфейс и реестр функций */ #ifndef NEURON_GENERATION_H @@ -121,691 +128,10 @@ float __fastcall GetNeironVal(const int i) { } // ============================================================================ -// Функции генерации (рождения) нейронов +// Подключение модульных функций обучения // ============================================================================ -/** - * Полный перебор комбинаций нейронов - * - * Перебирает все комбинации пар нейронов и операций, - * выбирая оптимальную комбинацию с минимальной ошибкой. - * Медленный, но гарантирует нахождение лучшего нейрона. - * - * @return минимальная достигнутая ошибка - */ -float rod() { - // Полный перебор всех комбинаций нейронов - int i; - float min = big; - int optimal_i = 0; - int optimal_j = 0; - oper optimal_op = op[0]; - float square, sum; - Neiron& cur = nei[Neirons]; - float* curval; - - for (cur.i = 1; cur.i < Neirons; cur.i++) // Выбор 1-го нейрона - { - for (cur.j = 0; cur.j < cur.i; cur.j++) // Выбор 2-го нейрона - { - for (i = 0; i < op_count; i++) // Выбор операции - { - cur.cached = false; - cur.op = op[i]; - sum = 0.0; - curval = GetNeironVector(Neirons); - - // Вычисляем сумму квадратов ошибок - for (int index = 0; index < Images && sum < min; index++) - { - square = vz[index] - curval[index]; - sum += square * square; - } - - if (min > sum) - { - min = sum; - optimal_op = cur.op; - optimal_i = cur.i; - optimal_j = cur.j; - } - } - } - } - - // Сохраняем оптимальные параметры - cur.cached = false; - cur.i = optimal_i; - cur.j = optimal_j; - cur.op = optimal_op; - cout << "min = " << min << ", (" << Neirons << ") = (" << optimal_i << ")op(" << optimal_j << ")\n"; - Neirons++; - return min; -} - -/** - * Комбинирование с последним нейроном - * - * Фиксирует первый вход как последний созданный нейрон - * и перебирает только второй вход и операцию. - * Быстрее, чем rod(), но менее гибкий. - * - * @return минимальная достигнутая ошибка - */ -float rod2() { - // Комбинирование с последним нейроном - int i; - float min = big; - int optimal_i = 0; - int optimal_j = 0; - oper optimal_op = op[0]; - float square, sum; - Neiron& cur = nei[Neirons]; - float* curval; - - cur.i = Neirons - 1; // Фиксируем последний нейрон - - for (cur.j = 0; cur.j < cur.i; cur.j++) // Выбор 2-го нейрона - { - for (i = 0; i < op_count; i++) // Выбор операции - { - cur.op = op[i]; - sum = 0.0; - cur.cached = false; - curval = GetNeironVector(Neirons); - - for (int index = 0; index < Images && sum < min; index++) - { - square = vz[index] - curval[index]; - sum += square * square; - } - - if (min > sum) - { - min = sum; - optimal_op = cur.op; - optimal_i = cur.i; - optimal_j = cur.j; - } - } - } - - cur.cached = false; - cur.i = optimal_i; - cur.j = optimal_j; - cur.op = optimal_op; - cout << "min = " << min << ", (" << Neirons << ") = (" << optimal_i << ")op(" << optimal_j << ")\n"; - Neirons++; - return min; -} - -/** - * Комбинирование старых и новых нейронов - * - * Комбинирует старые нейроны (до Classes*3) с новыми. - * Специализированная стратегия для определённых этапов обучения. - * - * @return минимальная достигнутая ошибка - */ -float rod3() { - // Комбинирование старых нейронов с новыми - int i; - float min = big; - int optimal_i = 0; - int optimal_j = 0; - oper optimal_op = op[0]; - float square, sum; - Neiron& cur = nei[Neirons]; - float* curval; - - for (cur.i = 0; cur.i < Neirons - Classes * 3; cur.i++) // Старые нейроны - { - for (cur.j = Neirons - Classes * 3; cur.j < Neirons; cur.j++) // Новые нейроны - { - for (i = 0; i < op_count; i++) - { - cur.cached = false; - cur.op = op[i]; - sum = 0.0; - curval = GetNeironVector(Neirons); - - for (int index = 0; index < Images && sum < min; index++) - { - square = vz[index] - curval[index]; - sum += square * square; - } - - if (min > sum) - { - min = sum; - optimal_op = cur.op; - optimal_i = cur.i; - optimal_j = cur.j; - } - } - } - } - - cur.cached = false; - cur.i = optimal_i; - cur.j = optimal_j; - cur.op = optimal_op; - cout << "min = " << min << ", (" << Neirons << ") = (" << optimal_i << ")op(" << optimal_j << ")\n"; - Neirons++; - return min; -} - -/** - * Случайная генерация нейронов - * - * Создаёт заданное количество случайных нейронов. - * Используется для расширения пространства поиска. - * - * @param count - количество создаваемых нейронов - */ -void rndrod(unsigned count) { - // Генерация случайных нейронов - do - { - nei[Neirons].cached = false; - nei[Neirons].i = rand() % (Neirons); - nei[Neirons].j = rand() % (Neirons); - nei[Neirons].op = op[rand() % op_count]; - cout << "(" << Neirons << ") = (" << nei[Neirons].i << ")op(" << nei[Neirons].j << ")\n"; - Neirons++; - } while (--count > 0); -} - -/** - * Случайная генерация на основе входов - * - * Создаёт случайные нейроны, комбинируя только входы сети. - * - * @param count - количество создаваемых нейронов - */ -void rndrod0(unsigned count) { - // Генерация случайных нейронов на основе входов - do - { - nei[Neirons].cached = false; - nei[Neirons].i = rand() % (Inputs); - nei[Neirons].j = rand() % (Receptors); - nei[Neirons].op = op[rand() % op_count]; - cout << "(" << Neirons << ") = (" << nei[Neirons].i << ")op(" << nei[Neirons].j << ")\n"; - Neirons++; - } while (--count > 0); -} - -/** - * Оптимизированная случайная генерация (2 нейрона) - * - * Создаёт пару нейронов с оптимизированными параметрами. - * Ищет лучшую комбинацию среди случайных вариантов. - * - * @return минимальная достигнутая ошибка - */ -float rndrod2() { - // Оптимизированная генерация пары нейронов - int count, count_max = Inputs * Neirons * rndrod_iter; - float min = big; - float square, sum; - int r[5] = { 0,0,0,0,0 }; - oper ro[5] = { 0,0,0,0,0 }; - int Neirons_p_1 = Neirons + 1; - Neiron& Neiron_A = nei[Neirons]; - Neiron& Neiron_B = nei[Neirons_p_1]; - - Neiron_B.i = Neirons; - - for (count = 0; count < count_max; count++) - { - Neiron_A.cached = false; - Neiron_A.i = rand() % rndrod_iter + Neirons - rndrod_iter; // Последние случайные - Neiron_A.j = rand() % (Neirons - rndrod_iter); - Neiron_A.op = op[rand() % op_count]; - - Neiron_B.cached = false; - Neiron_B.j = rand() % Inputs; - Neiron_B.op = op[rand() % op_count]; - - float* NBVal = GetNeironVector(Neirons_p_1); - - sum = 0.0; - - for (int index = 0; index < Images && sum < min; index++) - { - square = vz[index] - NBVal[index]; - sum += square * square; - } - - if (min > sum) - { - min = sum; - ro[0] = Neiron_A.op; - r[1] = Neiron_A.i; - r[2] = Neiron_A.j; - ro[3] = Neiron_B.op; - r[4] = Neiron_B.j; - } - } - - Neiron_A.cached = false; - Neiron_A.i = r[1]; - Neiron_A.j = r[2]; - Neiron_A.op = ro[0]; - Neiron_B.cached = false; - Neiron_B.j = r[4]; - Neiron_B.op = ro[3]; - cout << "min = " << min << ", (" << Neirons + 1 << ") = ((" << r[1] << ")op(" << r[2] << "))op(" << r[4] << ")\n"; - Neirons += 2; - return min; -} - -/** - * Расширенная случайная генерация (2 нейрона) - * - * Аналогична rndrod2(), но с большим пространством поиска. - * - * @return минимальная достигнутая ошибка - */ -float rndrod3() { - // Расширенная случайная генерация пары нейронов - int count, count_max = Neirons * Neirons * 6; - float min = big; - float square, sum; - int r[5] = { 0,0,0,0,0 }; - oper ro[5] = { 0,0,0,0,0 }; - int Neirons_p_1 = Neirons + 1; - Neiron& Neiron_A = nei[Neirons]; - Neiron& Neiron_B = nei[Neirons_p_1]; - - Neiron_B.i = Neirons; - - for (count = 0; count < count_max; count++) - { - Neiron_A.cached = false; - Neiron_A.i = rand() % Neirons; - Neiron_A.j = rand() % Neirons; - Neiron_A.op = op[rand() % op_count]; - - Neiron_B.cached = false; - Neiron_B.j = rand() % Neirons; - Neiron_B.op = op[rand() % op_count]; - - float* NBVal = GetNeironVector(Neirons_p_1); - - sum = 0.0; - - for (int index = 0; index < Images && sum < min; index++) - { - square = vz[index] - NBVal[index]; - sum += square * square; - } - - if (min > sum) - { - min = sum; - ro[0] = Neiron_A.op; - r[1] = Neiron_A.i; - r[2] = Neiron_A.j; - ro[3] = Neiron_B.op; - r[4] = Neiron_B.j; - } - } - - Neiron_A.cached = false; - Neiron_A.i = r[1]; - Neiron_A.j = r[2]; - Neiron_A.op = ro[0]; - Neiron_B.cached = false; - Neiron_B.j = r[4]; - Neiron_B.op = ro[3]; - cout << "min = " << min << ", (" << Neirons + 1 << ") = ((" << r[1] << ")op(" << r[2] << "))op(" << r[4] << ")\n"; - Neirons += 2; - return min; -} - -/** - * Генерация тройки нейронов (основной метод обучения) - * - * Создаёт три связанных нейрона: A, B и C. - * C объединяет A и B, обеспечивая более сложные функции. - * Это основной метод обучения в текущей версии. - * - * @return минимальная достигнутая ошибка, или big если не найдено - */ -float rndrod4() { - // Генерация тройки связанных нейронов - основной метод обучения - int count, count_max = Neirons * Receptors * 4; - float min = big; - float square, sum; - int A_id = Neirons; - int B_id = Neirons + 1; - int C_id = Neirons + 2; - Neiron& Neiron_A = nei[A_id]; - Neiron& Neiron_B = nei[B_id]; - Neiron& Neiron_C = nei[C_id]; - Neiron optimal_A, optimal_B, optimal_C; - bool finded = false; - - // C объединяет A и B - Neiron_C.i = A_id; - Neiron_C.j = B_id; - - // Инициализируем A случайными значениями - Neiron_A.i = rand() % Neirons; - Neiron_A.j = rand() % Neirons; - Neiron_A.op = op[rand() % op_count]; - Neiron_A.cached = false; - - for (count = 0; count < count_max; count++) - { - // Генерируем случайные параметры для B - Neiron_B.i = rand() % Neirons; - Neiron_B.j = rand() % Neirons; - - // Перебираем операции для B и C - for (int B_op = 0; B_op < op_count; B_op++) - { - Neiron_B.op = op[B_op]; - Neiron_B.cached = false; - - for (int C_op = 0; C_op < op_count; C_op++) - { - Neiron_C.op = op[C_op]; - Neiron_C.cached = false; - - float* C_Vector = GetNeironVector(C_id); - sum = 0.0; - - // Вычисляем ошибку по всем образам - // vz[index] содержит ожидаемое значение для образа index - // (1.0 если образ принадлежит текущему классу, 0.0 иначе) - for (int index = 0; index < Images && sum < min; index++) - { - square = vz[index] - C_Vector[index]; - sum += square * square; - } - - if (min > sum) - { - finded = true; - min = sum; - optimal_A = Neiron_A; - optimal_B = Neiron_B; - optimal_C = Neiron_C; - - // Используем оптимальный нейрон B как новый A - Neiron_A = Neiron_B; - } - } - } - } - - if (finded) - { - Neiron_A = optimal_A; - Neiron_B = optimal_B; - Neiron_C = optimal_C; - Neirons += 3; - return min; - } - else - return big; -} - -// ============================================================================ -// Многопоточные версии функций генерации нейронов -// ============================================================================ - -/** - * Структура для хранения результата поиска в потоке - */ -struct ThreadSearchResult { - float min_error; - Neiron optimal_A; - Neiron optimal_B; - Neiron optimal_C; - bool found; - - ThreadSearchResult() : min_error(big), found(false) {} -}; - -/** - * Вычисление вектора значений для заданного нейрона - * (с использованием кэша для уже существующих нейронов) - * - * @param neuron_i - первый вход нейрона - * @param neuron_j - второй вход нейрона - * @param neuron_op - операция нейрона - * @param result - буфер для результата - */ -void ComputeNeironVector_Direct( - int neuron_i, - int neuron_j, - oper neuron_op, - vector& result) -{ - result.resize(Images); - float* i_cache = GetNeironVector(neuron_i); - float* j_cache = GetNeironVector(neuron_j); - (*neuron_op)(result.data(), i_cache, j_cache, Images); -} - -/** - * Функция потока для параллельного поиска оптимальных нейронов - * - * Каждый поток получает своё подмножество случайных комбинаций - * параметров нейронов для поиска оптимального решения. - * - * @param thread_id - ID потока - * @param iterations_per_thread - количество итераций для этого потока - * @param current_neirons - текущее количество нейронов - * @param seed - начальное значение для генератора случайных чисел - * @param result - результат поиска (выходной параметр) - * @param global_min - указатель на атомарную переменную с глобальным минимумом - */ -void SearchThreadFunc( - int thread_id, - int iterations_per_thread, - int current_neirons, - unsigned int seed, - ThreadSearchResult& result, - std::atomic* global_min) -{ - // Локальный генератор случайных чисел для потока - // Используем линейный конгруэнтный генератор для независимости от глобального состояния - unsigned int local_seed = seed + thread_id * 1099087573u; - auto local_rand = [&local_seed]() -> int { - local_seed = local_seed * 1103515245u + 12345u; - return (int)((local_seed >> 16) & 0x7FFF); - }; - - Neiron local_A, local_B, local_C; - vector A_Vector(Images), B_Vector(Images), C_Vector(Images); - - // Инициализируем A случайными значениями - local_A.i = local_rand() % current_neirons; - local_A.j = local_rand() % current_neirons; - local_A.op = op[local_rand() % op_count]; - - float* A_i_cache = GetNeironVector(local_A.i); - float* A_j_cache = GetNeironVector(local_A.j); - (*local_A.op)(A_Vector.data(), A_i_cache, A_j_cache, Images); - - for (int count = 0; count < iterations_per_thread; count++) - { - // Генерируем случайные параметры для B - local_B.i = local_rand() % current_neirons; - local_B.j = local_rand() % current_neirons; - - float* B_i_cache = GetNeironVector(local_B.i); - float* B_j_cache = GetNeironVector(local_B.j); - - // Перебираем операции для B и C - for (int B_op = 0; B_op < op_count; B_op++) - { - local_B.op = op[B_op]; - (*local_B.op)(B_Vector.data(), B_i_cache, B_j_cache, Images); - - for (int C_op = 0; C_op < op_count; C_op++) - { - local_C.op = op[C_op]; - (*local_C.op)(C_Vector.data(), A_Vector.data(), B_Vector.data(), Images); - - // Вычисляем ошибку по всем образам - float current_global_min = global_min->load(std::memory_order_relaxed); - float sum = 0.0f; - for (int index = 0; index < Images && sum < current_global_min; index++) - { - float square = vz[index] - C_Vector[index]; - sum += square * square; - } - - if (result.min_error > sum) - { - result.found = true; - result.min_error = sum; - result.optimal_A = local_A; - result.optimal_B = local_B; - result.optimal_C = local_C; - - // Обновляем глобальный минимум для early termination в других потоках - float expected = global_min->load(std::memory_order_relaxed); - while (sum < expected) { - if (global_min->compare_exchange_weak(expected, sum, std::memory_order_relaxed)) { - break; - } - } - - // Используем оптимальный нейрон B как новый A - local_A = local_B; - for (int im = 0; im < Images; im++) { - A_Vector[im] = B_Vector[im]; - } - } - } - } - } -} - -/** - * Многопоточная генерация тройки нейронов - * - * Параллельная версия rndrod4(), распределяющая поиск - * между несколькими потоками. Каждый поток исследует - * случайные комбинации параметров нейронов независимо. - * - * Стратегия распределения работы: - * - Общее количество итераций делится между потоками - * - Это обеспечивает приблизительно такое же качество поиска - * как однопоточная версия, но за меньшее время - * - Каждый поток имеет минимальное гарантированное количество итераций - * - * @return минимальная достигнутая ошибка, или big если не найдено - */ -float rndrod4_parallel() { - // Если многопоточность отключена или только 1 поток, используем последовательную версию - if (!UseMultithreading || NumThreads <= 1) { - return rndrod4(); - } - - // Общее количество итераций для однопоточного поиска - int count_max = Neirons * Receptors * 4; - - // Минимальное количество итераций на поток для качественного поиска. - // При слишком малом количестве итераций случайный поиск может - // не найти хорошее решение из-за недостаточного исследования - // пространства параметров. Значение 1000 обеспечивает достаточное - // покрытие пространства поиска на каждом потоке. - const int MIN_ITERATIONS_PER_THREAD = 1000; - - // Вычисляем количество итераций на поток - // Общая работа делится между потоками, но каждый поток получает - // минимум MIN_ITERATIONS_PER_THREAD итераций - int base_iterations_per_thread = (count_max + NumThreads - 1) / NumThreads; - int iterations_per_thread = std::max(base_iterations_per_thread, MIN_ITERATIONS_PER_THREAD); - - // Используем однопоточную версию если count_max слишком мал, - // чтобы параллелизация имела смысл. Порог: нужно достаточно - // итераций чтобы каждый поток мог эффективно работать. - // При count_max < 2000 накладные расходы на потоки превышают выгоду. - if (count_max < 2000) { - return rndrod4(); - } - - // Создаём результаты для каждого потока - vector results(NumThreads); - - // Атомарная переменная для синхронизации глобального минимума - std::atomic global_min(big); - - // Получаем начальное значение для генераторов случайных чисел - // Используем текущее количество нейронов как основу для seed, - // что обеспечивает детерминированность при фиксированном srand() - // и разные стартовые точки для каждого вызова rndrod4_parallel - unsigned int base_seed = (unsigned int)(Neirons * 1099087573u + 12345u); - - // ВАЖНО: Предварительно прогреваем кэши всех существующих нейронов - // Это необходимо для избежания race condition, когда несколько потоков - // одновременно пытаются заполнить кэш одного и того же нейрона. - // GetNeironVector() не является потокобезопасным при заполнении кэша. - for (int n = 0; n < Neirons; n++) { - GetNeironVector(n); - } - - // Запускаем потоки - vector threads; - threads.reserve(NumThreads); - - for (int t = 0; t < NumThreads; t++) { - threads.emplace_back(SearchThreadFunc, - t, - iterations_per_thread, - Neirons, - base_seed, - std::ref(results[t]), - &global_min); - } - - // Ждём завершения всех потоков - for (auto& t : threads) { - t.join(); - } - - // Находим лучший результат среди всех потоков - float best_min = big; - int best_thread = -1; - for (int t = 0; t < NumThreads; t++) { - if (results[t].found && results[t].min_error < best_min) { - best_min = results[t].min_error; - best_thread = t; - } - } - - if (best_thread >= 0) { - // Сохраняем оптимальные нейроны - int A_id = Neirons; - int B_id = Neirons + 1; - int C_id = Neirons + 2; - - nei[A_id] = results[best_thread].optimal_A; - nei[A_id].cached = false; - nei[B_id] = results[best_thread].optimal_B; - nei[B_id].cached = false; - nei[C_id] = results[best_thread].optimal_C; - nei[C_id].cached = false; - - // Устанавливаем правильные связи для C - nei[C_id].i = A_id; - nei[C_id].j = B_id; - - Neirons += 3; - return best_min; - } - else { - return big; - } -} +// Подключаем все функции обучения через единый заголовок +#include "learning_funcs/learning_funcs.h" #endif // NEURON_GENERATION_H diff --git a/main.cpp b/main.cpp index e606189..0f58b67 100644 --- a/main.cpp +++ b/main.cpp @@ -266,6 +266,7 @@ void printUsage(const char* programName) { cout << endl; cout << "GENERAL OPTIONS:" << endl; cout << " -h, --help Show this help message" << endl; + cout << " --list-funcs List available training functions" << endl; cout << endl; cout << "INTERRUPTION:" << endl; cout << " Press Ctrl+C during training to interrupt gracefully." << endl; @@ -286,8 +287,11 @@ void printUsage(const char* programName) { cout << " { \"id\": 0, \"word\": \"\" }," << endl; cout << " { \"id\": 1, \"word\": \"time\" }" << endl; cout << " ]," << endl; - cout << " \"generate_shifts\": true" << endl; + cout << " \"generate_shifts\": true," << endl; + cout << " \"funcs\": [\"triplet_parallel\"] // Optional: specify training functions" << endl; cout << " }" << endl; + cout << endl; + cout << "Use --list-funcs to see all available training functions." << endl; } /** @@ -414,6 +418,9 @@ int main(int argc, char* argv[]) } else if (arg == "-h" || arg == "--help") { printUsage(argv[0]); return 0; + } else if (arg == "--list-funcs") { + printAvailableLearningFuncs(); + return 0; } } @@ -705,11 +712,28 @@ int main(int argc, char* argv[]) // Обучаем распознавание текущего класса if (class_er[classIndex] > er) { - // Используем метод rndrod4_parallel - многопоточная генерация тройки нейронов - class_er[classIndex] = rndrod4_parallel(); - - // Сохраняем выходной нейрон для класса - NetOutput[classIndex] = Neirons - 1; + // Если заданы функции обучения в конфиге - используем их последовательно + if (!g_trainingFuncs.empty()) { + // Вызываем все указанные функции в указанной последовательности + for (const auto& funcName : g_trainingFuncs) { + if (class_er[classIndex] <= er) break; // Уже достигли нужной ошибки + + LearningFunc func = getLearningFunc(funcName); + if (func != nullptr) { + float newError = func(); + if (newError < class_er[classIndex]) { + class_er[classIndex] = newError; + NetOutput[classIndex] = Neirons - 1; + } + } else { + cerr << "Warning: Unknown training function '" << funcName << "', skipping" << endl; + } + } + } else { + // По умолчанию: используем triplet_random_parallel (rndrod4_parallel) + class_er[classIndex] = triplet_random_parallel(); + NetOutput[classIndex] = Neirons - 1; + } } cout << ", n" << NetOutput[classIndex] << " = " << class_er[classIndex] << endl; @@ -717,6 +741,12 @@ int main(int argc, char* argv[]) if (++classIndex >= Classes) // Переходим к следующему классу по кругу classIndex = 0; + // Проверяем достижение лимита нейронов + if (Neirons >= MAX_NEURONS - 10) { // Оставляем запас в 10 нейронов + cout << "\n[WARNING] Maximum neuron limit (" << MAX_NEURONS << ") nearly reached. Stopping training." << endl; + break; + } + } while (sum(class_er.data(), Classes) > Classes * er); // Конец обучения