Форум программистов, компьютерный форум, киберфорум
C++: ИИ, нейросети, ML, агенты
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
0 / 0 / 0
Регистрация: 16.02.2025
Сообщений: 14

Простая нейронка

11.01.2026, 10:56. Показов 900. Ответов 8

Студворк — интернет-сервис помощи студентам
Здравствуйте. Несколько лет назад я заинтересовался нейронными сетями. В те времена Chat GPT небыли такими «умными» и писать код еще не умели поэтому пришлось разыскивать подходящие материалы самому. В конце- концов мне удалось найти на сайте Back propagation - алгоритм обучения по методу обратного распространения | #3 нейросети на Python
видео (доступно на RuTube ) в котором достаточно просто объяснялось создание и работа простой нейросети размерностью 2-3-2-1. Используя этот урок и на основании того что мне удалось понять, я написал на с++ свою простую нейросеть 4-10-12-4 которая умеет (после обучения с «учителем») к четырехбитному двоичному числу в диапазоне от 0000 до 1111 прибавлять 1. Нейросеть при обучении использует мини-пакетный градиентный спуск с полным проходом по всем данным в каждой "эпохе". Происходит Online Learning - веса обновляются сразу после каждого примера,
Так как материалов по самописным нейросетям мало даже сейчас, я решил выложить свое творение на всеобщее обозрение. Надеюсь, моя программа будет полезна ученикам средних школ и студентам непрофильных ВУЗов, интересующимися нейросетями. Конечно, в программе могут быть ошибки, если кто-то сможет подсказать что-то полезное, буду благодарен за помощь. Для наглядности при создании нейросети я использовал статически выделенные массивы в си стиле.
У моей нейросети имеются следующие недоработки:
1. Она переобучена — при обучении на вход в 1 батче подается весь набор данных, причем один за другим. Для правильного обучения обучающие данные рекомендуется перемешивать в случайном порядке.
2. Нейросеть должна научиться находить паттерны. Для этого обычно при обучении на вход подается примерно 80% примеров. А оставшиеся 20% подают при тестировании. У меня же нейросеть «вызубривает» все 100% подаваемых примеров.
3. Обычно начальная инициализация весов сети происходит по определенным алгоритмам. У меня в программе начальные значения задаются конкретными литералами.
С другой стороны, это дает новичкам возможность для творчества. Я специально снабдил программу многочисленными комментариями, чтобы ученику было проще понять, что и зачем происходит в данном коде.
Когда результаты нейросети совпадают с ожидаемыми, говорят, что нейросеть сходится. Но не всегда результаты совпадают с нашими ожиданиями. Согласитесь, что нет необходимости добиваться на выходе именно 0 или 1. Достаточно, что бы результаты явно попадали в заданный диапазон. Например, вполне можно считать величину больше 0.9 единицей, а величину меньше 0.1 нулем. Определить это поможет специальная функция CompareBinaryResult( N_out, n_out, max0, min1) предназначенная для сравнения результатов. Если полученное число окажется вне нужного диапазона, вместо цифры будет выведен «?». Естественно, данная нейросеть вполне может научиться имитировать работу двух битного сумматора (микросхема К155ИМ2 (7482)).
С уважением PavelT.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/// Обучение с учителем. Обучаем нейросеть прибавлять к числу 1
/// Нейросеть получает на вход от 0000 до 1111 
/// На выход подаем "правильные" то есть нужные нам данные
/// В результате после обучения нейросеть выдает на выходе значения от 0000+1 до 1111+1
/// Нейросеть создана на основании примера
/// Back propagation - алгоритм обучения по методу обратного распространения | #3 нейросети на Python
/// 10.01.2026
 
#include <iostream>
#include <vector>
#include <random>
#include <cmath>
 
//========  размеры  слоев
const int n_input = 4;
const int n_vnut0 = 10;
const int n_vnut1 = 12;
const int n_out   = 4;
 
const int var_combInp = pow(2, n_input);
///  нейроны
float N_inp[n_input] = {0};
float N_vn0[n_vnut0] = {0};
float N_vn1[n_vnut1] = {0};
float N_out[n_out]   = {0};
 
/// веса
float W_vn0[n_vnut0][n_input] = {0};
float W_vn1[n_vnut1][n_vnut0] = {0};
float W_out[n_out][n_vnut1]   = {0};
 
///bias
float bias_vn0[n_vnut0] = {0};
float bias_vn1[n_vnut1] = {0};
float bias_out[n_out]   = {0};
 
/// обучение
float target[n_out]         = {0}; // цель задаваемая нейросети
float ErrorN_Out[n_out]     = {0};
 
/// Производные сигмоиды
float SigmDxN_vn0[n_vnut0]  = {0}; // Производные сигмоиды
float SigmDxN_vn1[n_vnut1]  = {0};
float SigmDxN_out[n_out]  = {0};
/// Изменения весов
float weight_deltaOut[n_out]     = {0}; //
float weight_deltaNvn1[n_vnut1]  = {0};
float weight_deltaNvn0[n_vnut0]  = {0};
float learning_rate = 0.5f;
 
//---------------  rnd
std::mt19937 gen(325);
float rnd(float a, float b){
    std::uniform_real_distribution<float> d(a,b);
    return d(gen);
}
//void CompareBinaryResult(const vector1D& N_out, float max0, float min1) {
void CompareBinaryResult(const float* N_out, int n_out, float max0, float min1) {
    for(int i = 0; i < n_out; ++i) {
        if(N_out[i] <= max0) {  // max0 = 0.1
            std::cout << '0';
        }
        else if(N_out[i] >= min1) {  // min1 = 0.9
            std::cout << '1';
        }
        else {
            std::cout << '?';  // Результат вне ожидаемого диапазона
        }
    }
}
 
///======  Нейросеть 
/// вычисление сигмоиды
void SigmoidActivation(float* N_layer, int Nlayer_size){
    for(int i=0;i<Nlayer_size;++i)
     N_layer[i] = 1.0f/(1.0f+std::exp(-N_layer[i]));
    ///N_layer[i] = 1.0f/(1.0f+expf(-N_layer[i]));
}
/// Вычисление производной сигмоиды
void ComputeSigmDxN(float* SigmDxN, float* N_layer, int Nlayer_size){
    for(int i=0;i<Nlayer_size;++i)
    SigmDxN[i] = N_layer[i]*(1.0f-N_layer[i]);
}
 
/// вычисление содержимого следующего слоя нейронов
void ComputeLayerNeyron(float* inp_Nlayer, float* out_Nlayer, float* Weights, int inp_size, int out_size, float* bias = nullptr)
{
    for (int i = 0; i < out_size; i++) {
        out_Nlayer[i] = 0.0f;  // Обнуляем перед суммированием
        for (int j = 0; j < inp_size; j++) {
            out_Nlayer[i] += Weights[i * inp_size + j] * inp_Nlayer[j];
        }
        // Добавляем bias, если передан
        //if (bias != nullptr) {
            out_Nlayer[i] += bias[i];
       // }
    }
}
 
///  Универсальная функция для обновления весов любого слоя
void UpdateWeights(float* Weights,const int output_size,const int input_size, float* deltasW, float* inputsN, const float learning_rate) {
    for (int j = 0; j < output_size; j++) {
        for (int i = 0; i < input_size; i++) {
            int index = j * input_size + i;  // Эмуляция weights[j][i] для одномерного массива
            Weights[index] -= deltasW[j] * inputsN[i] * learning_rate;
        }
    }
}
 
/// Вычисление локальных градиентов
void ComputeLocalGradients(const float* Weights, int prev_size, int current_size,
                           float* prev_gradientsW, const float* current_gradientsW, const float* SigmDxN) {
    // Сначала вычисляем сумму без производной
    for (int i = 0; i < prev_size; i++) {
        prev_gradientsW[i] = 0.0f;  // Обнуляем
        for (int j = 0; j < current_size; j++) {
            int index = j * prev_size + i;  // Эмуляция weights[j][i]
            prev_gradientsW[i] += current_gradientsW[j] * Weights[index];
        }
        // Потом умножаем на производную активации (SigmDxN[i])
        prev_gradientsW[i] *= SigmDxN[i];
    }
}
 
///  forward прямое прохождение
/// от начала к концу inp -> 0-> 1 -> out
void forwardPass(){
/// 2. Вычисляем содержимое нейронов нулевого слоя N_vn0[i]
    ComputeLayerNeyron(N_inp, N_vn0, &W_vn0[0][0], n_input, n_vnut0, bias_vn0);
///3. применим к внутренним нейронам нулевого слоя сигмоиду:
    SigmoidActivation(N_vn0, n_vnut0);
///4. Вычисляем содержимое N_vn1[i]
    ComputeLayerNeyron(N_vn0, N_vn1, &W_vn1[0][0], n_vnut0, n_vnut1, bias_vn1);
///5. применим к внутренним нейронам первого слоя сигмоиду:
    SigmoidActivation(N_vn1, n_vnut1);
/// 6. Вычисляем содержимое нейронов выходного слоя N_out[i]
    ComputeLayerNeyron(N_vn1, N_out, &W_out[0][0], n_vnut1, n_out, bias_out);
///7. применим к нейронам выходного слоя сигмоиду:
    SigmoidActivation(N_out, n_out);
}
///   backward обратное прохождение (Backpropagation) 
/// коррекция от конца к началу! out -> 1-> 0
 
void backwardPass(){
///8.  Вычисление ошибки для каждого выходного нейрона и локального градиента weiht_deltaOut
    for(int i=0;i<n_out;++i)
     {   ErrorN_Out[i] = N_out[i]-target[i];
        ComputeSigmDxN(SigmDxN_out, N_out, n_out);
        weight_deltaOut[i]=ErrorN_Out[i]* SigmDxN_out[i];
      /// weight_deltaOut[i] = ErrorN_Out[i] * (N_out[i] * (1 - N_out[i]));
     }
/// 9. Коррекция весов выходного слоя
    UpdateWeights(&W_out[0][0], n_out, n_vnut1, weight_deltaOut, N_vn1, learning_rate);
    // Обновление bias выходного слоя
    for(int i=0;i<n_out;++i)
    bias_out[i] -= learning_rate*weight_deltaOut[i];
    ///  N_out[ii] * (1 - N_out[ii]) // производная выходного слоя
 
/// 10. Вычисление производной сигмоиды 1-ого слоя
    ComputeSigmDxN(SigmDxN_vn1,N_vn1, n_vnut1);
/// 11. вычисление локальных градиентов weiht_deltaNvn1[i]
    ComputeLocalGradients(&W_out[0][0], n_vnut1, n_out, weight_deltaNvn1, weight_deltaOut, SigmDxN_vn1);
/// 12. вычисление новых значений весов первого слоя W_vn1
    UpdateWeights(&W_vn1[0][0], n_vnut1, n_vnut0, weight_deltaNvn1, N_vn0, learning_rate);
    // Обновление bias первого скрытого слоя
    for(int i=0;i<n_vnut1;++i)
    bias_vn1[i] -= learning_rate*weight_deltaNvn1[i];
 
/// 13. вычисление значений производной нейронов 0-ого слоя N_vn0
    ComputeSigmDxN(SigmDxN_vn0, N_vn0, n_vnut0);
/// 14. вычисление значений локального градиента weiht_deltaNvn0
    ComputeLocalGradients(&W_vn1[0][0], n_vnut0, n_vnut1, weight_deltaNvn0, weight_deltaNvn1, SigmDxN_vn0);
/// 15. вычисление новых значений  W_vn0
    UpdateWeights(&W_vn0[0][0], n_vnut0, n_input, weight_deltaNvn0, N_inp, learning_rate);
    // Обновление bias нулевого скрытого слоя
    for(int i=0;i<n_vnut0;++i)
    bias_vn0[i] -= learning_rate*weight_deltaNvn0[i];
 
}
///  инициализация весов 
void initNet(){
    for(int i=0;i<n_vnut0;++i) for(int j=0;j<n_input;++j)
    W_vn0[i][j]=rnd(-0.5f,0.5f);
    for(int i=0;i<n_vnut1;++i) for(int j=0;j<n_vnut0;++j)
    W_vn1[i][j]=rnd(-0.5f,0.5f);
    for(int i=0;i<n_out;  ++i) for(int j=0;j<n_vnut1;++j)
    W_out[i][j]=rnd(-0.5f,0.5f);
 
    for(int i=0;i<n_vnut0;++i) bias_vn0[i]=rnd(-0.1f,0.1f);
    for(int i=0;i<n_vnut1;++i) bias_vn1[i]=rnd(-0.1f,0.1f);
    for(int i=0;i<n_out;  ++i) bias_out[i]=rnd(-0.1f,0.1f);
}
///  обучение на 16 примерах  (батчах)
    // входы  0000..1111
    float inp_patterns[var_combInp][n_input] = {
        {0,0,0,0},{0,0,0,1},{0,0,1,0},{0,0,1,1},
        {0,1,0,0},{0,1,0,1},{0,1,1,0},{0,1,1,1},
        {1,0,0,0},{1,0,0,1},{1,0,1,0},{1,0,1,1},
        {1,1,0,0},{1,1,0,1},{1,1,1,0},{1,1,1,1}
    };
    // выходы от 0000+1 ...1111+1
    float out_patterns[var_combInp][n_input] = {
        {0,0,0,1},{0,0,1,0},{0,0,1,1},{0,1,0,0},
        {0,1,0,1},{0,1,1,0},{0,1,1,1},{1,0,0,0},
        {1,0,0,1},{1,0,1,0},{1,0,1,1},{1,1,0,0},
        {1,1,0,1},{1,1,1,0},{1,1,1,1},{0,0,0,0}};
 
void teach16(){
 
    for(int epoch=0;epoch<1600;++epoch){
        float lossSum=0;
        for(int p=0;p<var_combInp;++p){ // батч=16
 
            for(int i=0;i<n_input;++i)
                N_inp[i]=inp_patterns[p][i];
 
            for(int i=0;i<n_out;++i)
                target[i]=out_patterns[p][i];
 
            forwardPass();// прямое прохождение
 
            // вычисление ошибки. Должна постоянно уменьшаться
            float e = 0;
            lossSum = 0; // начальное значение
 
            for(int i = 0; i < 4; ++i) {
            //e += (target[i] - N_out[i])²;  // Накапливаем e
            e += std::pow(target[i] - N_out[i], 2);
            }
            lossSum += e;
            // потеря (MSE приблизительно)
 
            backwardPass(); // обратное прохождение
 
        } // батч
        if(epoch%300==0) std::cout<<"epoch "<<epoch<<"  loss="<<lossSum/n_input<<"\n";
    } /// epoch
 
}/// Тренировкa сети окончена !!!
///===========  main 
int main(){
    initNet(); /// инициализация весов и биасов
    teach16(); /// обучение
 
    float min1=0.9;
    float max0=0.1;
 
    // проверка
    std::cout << "\n----  test  ----\n"
              << "inp   target   out      real_result \n";
 
    for(int p = 0; p < var_combInp; ++p){
        // Подаем на вход 4 числа типа float (набор 0 и 1)
        for(int i = 0; i < n_input; ++i)
            N_inp[i] = inp_patterns[p][i];
 
        // Прогоняем через сеть
        forwardPass(); // сеть натренирована backward больше не нужен
 
        // Выводим вход
        for(int i = 0; i < n_input; ++i)
            std::cout << (int)inp_patterns[p][i];
        std::cout << "   ";
 
        // Выводим цель
        for(int i = 0; i < n_input; ++i)
            std::cout << (int)out_patterns[p][i];
        std::cout << "   ";
 
        CompareBinaryResult( N_out, n_out, max0, min1); /// сравниваем результат приводим к 0 или 1 и печатаем
        std::cout << "    ";
        // Выводим реальный результат сети
        for(int i = 0; i < n_input; ++i)
            std::cout << N_out[i]<<"  ";
        std::cout << '\n';
    }
    ///Вывод данных после обучения по заданному вектору
    std::cout << std::endl;
    float numberN;
    while (true)
     {
        for (int i = 0; i < n_input; i++)
        {
            std::cout <<"Введите число от 0 до 15 в двоичном виде."<< std::endl;
            std::cout <<"Я покажу это число в двоичном виде +1."<< std::endl;
            std::cin >> numberN;
            N_inp[i] = numberN;
            std::cout << "N_inp[" << i << "]= " << N_inp[i] << std::endl;
        }
                std::cout << std::endl;
        // Прогоняем через сеть
        forwardPass(); // сеть натренирована backward больше не нужен
        /// двоичное число выданное нейросетью:
              for (int i = 0; i < n_out; i++)
                {
                    std::cout << "N_out["<<i<<"]=" <<N_out[i] << '\t'<< std::endl;
                }
        std::cout << "Повторить ввод? (y - да, любой другой символ - выход): ";
        char replyYN;
        std::cin >> replyYN;
        if (replyYN != 'Y' && replyYN != 'y') { break;}
        }
    return 0;
}
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
11.01.2026, 10:56
Ответы с готовыми решениями:

Нейронка, складывающая два числа
Здравствуйте! Начал изучать нейронные сети, и возник вопрос: как сложить два числа, допустим 5 и...

простая арифметика
Такой вот вопрос, 2 переменные типа int , содержать значения. Поменять их значения, не...

Простая задача на C++: поменять местами bool-ы.
Всех приветствую. В процессе ознакомления с С++ появилась необходимость выполнить нижеописанную...

8
Эксперт .NET
 Аватар для Usaga
14299 / 9384 / 1353
Регистрация: 21.01.2016
Сообщений: 35,381
11.01.2026, 11:01
Цитата Сообщение от vasya_sidorov_2 Посмотреть сообщение
Так как материалов по самописным нейросетям мало даже сейчас, я решил выложить свое творение на всеобщее обозрение.
Самописность с полного нуля сейчас вещь спорная в полезности. Мне по работе пришлось начать с этим всем знакомиться. И я, как будто, не вижу недостатка ни в литературе, ни в статьях в сети...
0
2649 / 1660 / 267
Регистрация: 19.02.2010
Сообщений: 4,392
11.01.2026, 15:34
Цитата Сообщение от vasya_sidorov_2 Посмотреть сообщение
если кто-то сможет подсказать что-то полезное, буду благодарен за помощь.
Говно. Переделывай.
Привычно наблюдаю в коде одно лишнее умножение при каждой модификации веса/смещения.
А ведь всем должны были в средних классах на алгебре вдолбить в башку экономящий вычисления приём вынесения общего множителя за скобки (в данном же случае будет не "за скобки" - а вообще за пределы сетки).
После этого вряд ли можно утверждать, что писателями/читателями были/будут правильно освоены-поняты более поздние темы. Такие, как цепное правило дифференцирования сложных функций (а бэкпроп - это оно и есть) и - это уже будут ВУЗовские темы - градиентная оптимизация / стохастическая аппроксимация (тот или иной способ коррекции весов на основе вектора производных, вычисленного бэкпропом - это имено град.оптимизация либо стох.аппроксимация).

Цитата Сообщение от vasya_sidorov_2 Посмотреть сообщение
к четырехбитному двоичному числу в диапазоне от 0000 до 1111 прибавлять 1

Ну да, для того, чтобы запомнить "разметку" вершин 4мерного гиперкуба - надо обучать нейронку, да ещё и рисковать, что она не обучится (из-за неверной структуры, неверных настроек, ошибок в коде,...).
В общем, нейронки на основе бэкпропа - они совсем для другого. Они для решения тех задач обучения с учителем, которые решает статистика (для аппроксимации поведения оптимального байесовского решателя в задаче классификации с учителем и для аппроксимации условного матожидания в задаче нелинейной регрессии). Также эти нейронки, при необходимости, позволяют догружать задачу любыми гладкими (точнее, хотя-бы однократно или, очень редко, двукратно непрерывно дифференцируемыми) добавочными условиями/ограничениями (штрафными слагаемыми в функции потерь, налагаемыми на нежелательные свойства решения="на нежелательное поведение нейронки" и/или на внутренние свойства модели).
А для полностью детерминированных задач отображения "ключей" (входов) на "значения" (выходы) - у минимально умеющих в STL плюсовиков есть std::map и/или std::unordered_map. Имеются в виду те задачи, для которых сложно создать идеальный хэш. В данной же учебной задаче - идеальный хэш очевиден (4битовое значение и является этим самым оптимальным/идеальным хэшем, позволяющим получать соответствующее ключу значение вообще без проверок, прямым доступом к элементу массива по индексу). Ну а тот, кто зачем-то представляет 4битовое целое число набором из 4рёх флоатов - ССЗБ и, наверное, действительно без костыля из внешней нейронки не справится.

Цитата Сообщение от vasya_sidorov_2 Посмотреть сообщение
Надеюсь, моя программа будет полезна ученикам средних школ и студентам непрофильных ВУЗов, интересующимися нейросетями.
Такие с начала нулевых использовали/используют FANN.
0
wound up as Aussie
463 / 115 / 20
Регистрация: 15.05.2019
Сообщений: 455
11.01.2026, 15:47
Цитата Сообщение от vasya_sidorov_2 Посмотреть сообщение
Обычно начальная инициализация весов сети происходит по определенным алгоритмам. У меня в программе начальные значения задаются конкретными литералами.
1) в этом случае главное чтобы первый набор входов соответствовал первому ожидаемому выходу, и т.д.
3) та просто random можно делать в этом случае.
Это у Вас MLP и его лучше сделать бутылочного типа (у Вас сейчас "мяч для регби" скорее ))) - поднять число нейронов на внутренних слоях и выходов сделать 16. А если выходы переписать, то тогда получится "дешифратор" типа К155ИД3. )))
C++
1
2
3
4
5
6
7
8
9
10
11
//========  размеры  слоев
const int n_input = 4;
const int n_vnut0 = 128;
const int n_vnut1 = 64;
const int n_out   = 16;
// ***********************
// выходы по числу "ножек", при входах от 0000 до 1111
    float out_patterns[var_combInp][n_input] = {
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}, {0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0},
        ....., // ещё 13 наборов, как ножки у микросхемы (
        {1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}};
ну как-то так, примерно.
0
0 / 0 / 0
Регистрация: 16.02.2025
Сообщений: 14
11.01.2026, 17:12  [ТС]
Привычно наблюдаю в коде одно лишнее умножение при каждой модификации веса/смещения.
Ваше величество, укажите на это одно лишнее умножение, буду очень благодарен
0
2649 / 1660 / 267
Регистрация: 19.02.2010
Сообщений: 4,392
11.01.2026, 20:04
Цитата Сообщение от vasya_sidorov_2 Посмотреть сообщение
укажите на это одно лишнее умножение
* learning_rate или learning_rate*, в зависимости от места в коде.
Это можно вынести вне сетки, после вычисления производных функции потерь по выходам сети. Т.е. вставить в строку 149.
Можно и в строку 147 - но таки надо отделять момент вычисления производной функции потерь (которая может быть не только в виде MSE) от "границ" самОй сетки и "границ" метода обучения (в данном случае - метод обучения устанавливает для всех весов/смещений одну и ту же скорость обучения - и, поэтому, внутрь сетки либо к вектору производных ему лезть совсем не нужно).

Цитата Сообщение от vasya_sidorov_2 Посмотреть сообщение
Ваше величество
В данном случае - великий фараон Амнепох.
Т.е. уровень владения предметом я увидел - и дальше мне не интересно.
0
wound up as Aussie
463 / 115 / 20
Регистрация: 15.05.2019
Сообщений: 455
12.01.2026, 03:01
Лучший ответ Сообщение было отмечено vasya_sidorov_2 как решение

Решение

Цитата Сообщение от vasya_sidorov_2 Посмотреть сообщение
в программе могут быть ошибки
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
#include <iostream>
#include <vector>
#include <random>
#include <cmath>
#include <limits>
#include <string>
 
//========  размеры  слоев
const int n_input = 4;
const int n_vnut0 = 10;
const int n_vnut1 = 12;
const int n_out   = 4;
 
// Лучше без pow() (он возвращает double)
const int var_combInp = 1 << n_input; // 16
 
///  нейроны
float N_inp[n_input] = {0};
float N_vn0[n_vnut0] = {0};
float N_vn1[n_vnut1] = {0};
float N_out[n_out]   = {0};
 
/// веса
float W_vn0[n_vnut0][n_input] = {0};
float W_vn1[n_vnut1][n_vnut0] = {0};
float W_out[n_out][n_vnut1]   = {0};
 
///bias
float bias_vn0[n_vnut0] = {0};
float bias_vn1[n_vnut1] = {0};
float bias_out[n_out]   = {0};
 
/// обучение
float target[n_out]         = {0}; // цель задаваемая нейросети
float ErrorN_Out[n_out]     = {0};
 
/// Производные сигмоиды
float SigmDxN_vn0[n_vnut0]  = {0};
float SigmDxN_vn1[n_vnut1]  = {0};
float SigmDxN_out[n_out]    = {0};
 
/// Дельты (локальные градиенты) для нейронов слоев
float weight_deltaOut[n_out]     = {0};
float weight_deltaNvn1[n_vnut1]  = {0};
float weight_deltaNvn0[n_vnut0]  = {0};
 
float learning_rate = 0.5f;
 
//---------------  rnd
std::mt19937 gen(325);
float rnd(float a, float b){
    std::uniform_real_distribution<float> d(a,b);
    return d(gen);
}
 
// Печать бинарного результата по порогам
void CompareBinaryResult(const float* out, int out_size, float max0, float min1) {
    for(int i = 0; i < out_size; ++i) {
        if(out[i] <= max0) {
            std::cout << '0';
        }
        else if(out[i] >= min1) {
            std::cout << '1';
        }
        else {
            std::cout << '?';
        }
    }
}
 
///======  Нейросеть
 
// Сигмоида
inline float sigmoid(float x){
    return 1.0f/(1.0f + std::exp(-x));
}
 
/// вычисление сигмоиды для слоя
void SigmoidActivation(float* N_layer, int Nlayer_size){
    for(int i=0;i<Nlayer_size;++i)
        N_layer[i] = sigmoid(N_layer[i]);
}
 
/// Производная сигмоиды (ожидаем, что N_layer уже после сигмоиды)
void ComputeSigmDxN(float* SigmDxN, const float* N_layer, int Nlayer_size){
    for(int i=0;i<Nlayer_size;++i)
        SigmDxN[i] = N_layer[i]*(1.0f - N_layer[i]);
}
 
/// вычисление следующего слоя: out = W*inp + bias
void ComputeLayerNeyron(const float* inp_Nlayer, float* out_Nlayer,
                        const float* Weights, int inp_size, int out_size,
                        const float* bias)
{
    for (int i = 0; i < out_size; i++) {
        float s = 0.0f;
        for (int j = 0; j < inp_size; j++) {
            s += Weights[i * inp_size + j] * inp_Nlayer[j];
        }
        // bias обязателен (мы всегда его передаем)
        s += bias[i];
        out_Nlayer[i] = s;
    }
}
 
/// Обновление весов: W -= lr * delta[j] * input[i]
void UpdateWeights(float* Weights, int output_size, int input_size,
                   const float* deltas, const float* inputs, float lr)
{
    for (int j = 0; j < output_size; j++) {
        float d = deltas[j];
        for (int i = 0; i < input_size; i++) {
            int index = j * input_size + i;
            Weights[index] -= d * inputs[i] * lr;
        }
    }
}
 
/// ComputeLocalGradients: delta_prev[i] = (sum_j delta_cur[j] * W_cur[j][i]) * SigmDx_prev[i]
void ComputeLocalGradients(const float* Weights, int prev_size, int current_size,
                           float* prev_deltas, const float* current_deltas,
                           const float* SigmDx_prev)
{
    for (int i = 0; i < prev_size; i++) {
        float s = 0.0f;
        for (int j = 0; j < current_size; j++) {
            int index = j * prev_size + i; // W[j][i]
            s += current_deltas[j] * Weights[index];
        }
        prev_deltas[i] = s * SigmDx_prev[i];
    }
}
 
/// forward
void forwardPass(){
    ComputeLayerNeyron(N_inp, N_vn0, &W_vn0[0][0], n_input, n_vnut0, bias_vn0);
    SigmoidActivation(N_vn0, n_vnut0);
 
    ComputeLayerNeyron(N_vn0, N_vn1, &W_vn1[0][0], n_vnut0, n_vnut1, bias_vn1);
    SigmoidActivation(N_vn1, n_vnut1);
 
    ComputeLayerNeyron(N_vn1, N_out, &W_out[0][0], n_vnut1, n_out, bias_out);
    SigmoidActivation(N_out, n_out);
}
 
/// backward
void backwardPass(){
    // 1) delta для выхода
    ComputeSigmDxN(SigmDxN_out, N_out, n_out);
    for(int i=0;i<n_out;++i){
        ErrorN_Out[i] = N_out[i] - target[i];
        weight_deltaOut[i] = ErrorN_Out[i] * SigmDxN_out[i];
    }
 
    // 2) обновляем W_out и bias_out
    UpdateWeights(&W_out[0][0], n_out, n_vnut1, weight_deltaOut, N_vn1, learning_rate);
    for(int i=0;i<n_out;++i)
        bias_out[i] -= learning_rate * weight_deltaOut[i];
 
    // 3) delta для vn1
    ComputeSigmDxN(SigmDxN_vn1, N_vn1, n_vnut1);
    ComputeLocalGradients(&W_out[0][0], n_vnut1, n_out, weight_deltaNvn1, weight_deltaOut, SigmDxN_vn1);
 
    // 4) обновляем W_vn1 и bias_vn1
    UpdateWeights(&W_vn1[0][0], n_vnut1, n_vnut0, weight_deltaNvn1, N_vn0, learning_rate);
    for(int i=0;i<n_vnut1;++i)
        bias_vn1[i] -= learning_rate * weight_deltaNvn1[i];
 
    // 5) delta для vn0
    ComputeSigmDxN(SigmDxN_vn0, N_vn0, n_vnut0);
    ComputeLocalGradients(&W_vn1[0][0], n_vnut0, n_vnut1, weight_deltaNvn0, weight_deltaNvn1, SigmDxN_vn0);
 
    // 6) обновляем W_vn0 и bias_vn0
    UpdateWeights(&W_vn0[0][0], n_vnut0, n_input, weight_deltaNvn0, N_inp, learning_rate);
    for(int i=0;i<n_vnut0;++i)
        bias_vn0[i] -= learning_rate * weight_deltaNvn0[i];
}
 
/// инициализация весов
void initNet(){
    for(int i=0;i<n_vnut0;++i) for(int j=0;j<n_input;++j)
        W_vn0[i][j]=rnd(-0.5f,0.5f);
    for(int i=0;i<n_vnut1;++i) for(int j=0;j<n_vnut0;++j)
        W_vn1[i][j]=rnd(-0.5f,0.5f);
    for(int i=0;i<n_out;  ++i) for(int j=0;j<n_vnut1;++j)
        W_out[i][j]=rnd(-0.5f,0.5f);
 
    for(int i=0;i<n_vnut0;++i) bias_vn0[i]=rnd(-0.1f,0.1f);
    for(int i=0;i<n_vnut1;++i) bias_vn1[i]=rnd(-0.1f,0.1f);
    for(int i=0;i<n_out;  ++i) bias_out[i]=rnd(-0.1f,0.1f);
}
 
///  обучение на 16 примерах
float inp_patterns[var_combInp][n_input] = {
    {0,0,0,0},{0,0,0,1},{0,0,1,0},{0,0,1,1},
    {0,1,0,0},{0,1,0,1},{0,1,1,0},{0,1,1,1},
    {1,0,0,0},{1,0,0,1},{1,0,1,0},{1,0,1,1},
    {1,1,0,0},{1,1,0,1},{1,1,1,0},{1,1,1,1}
};
 
// выходы от 0000+1 ...1111+1 (мод 16)
float out_patterns[var_combInp][n_out] = {
    {0,0,0,1},{0,0,1,0},{0,0,1,1},{0,1,0,0},
    {0,1,0,1},{0,1,1,0},{0,1,1,1},{1,0,0,0},
    {1,0,0,1},{1,0,1,0},{1,0,1,1},{1,1,0,0},
    {1,1,0,1},{1,1,1,0},{1,1,1,1},{0,0,0,0}
};
 
void teach16(){
    for(int epoch=0; epoch<1600; ++epoch){
        float lossSum = 0.0f;
 
        for(int p=0; p<var_combInp; ++p){
            for(int i=0;i<n_input;++i)
                N_inp[i]=inp_patterns[p][i];
 
            for(int i=0;i<n_out;++i)
                target[i]=out_patterns[p][i];
 
            forwardPass();
 
            // MSE по 4 выходам
            float e = 0.0f;
            for(int i = 0; i < n_out; ++i)
                e += (target[i] - N_out[i]) * (target[i] - N_out[i]);
            lossSum += e / n_out;
 
            backwardPass();
        }
 
        if(epoch % 300 == 0)
            std::cout << "epoch " << epoch << "  loss=" << lossSum / var_combInp << "\n";
    }
}
 
// Утилита: прочитать 4 бита как 0/1
bool read4bits(float* bits){
    std::cout << "Введите 4 бита (например 0 1 0 1) или строкой 0101:\n> ";
 
    std::string s;
    if(!(std::cin >> s)) return false;
 
    if(s.size() == 4 && (s.find_first_not_of("01") == std::string::npos)){
        for(int i=0;i<4;++i) bits[i] = (s[i] == '1') ? 1.0f : 0.0f;
        return true;
    }
 
    auto parseBit = [](const std::string& t, float &v)->bool{
        if(t == "0" || t == "0.0" ) { v = 0.0f; return true; }
        if(t == "1" || t == "1.0" ) { v = 1.0f; return true; }
        return false;
    };
 
    if(!parseBit(s, bits[0])) return false;
    for(int i=1;i<4;++i){
        std::string t;
        if(!(std::cin >> t)) return false;
        if(!parseBit(t, bits[i])) return false;
    }
    return true;
}
 
int main(){
    initNet();
    teach16();
 
    float min1=0.9f;
    float max0=0.1f;
 
    std::cout << "\n----  test  ----\n"
              << "inp   target   out      real_result\n";
 
    for(int p = 0; p < var_combInp; ++p){
        for(int i = 0; i < n_input; ++i)
            N_inp[i] = inp_patterns[p][i];
 
        forwardPass();
 
        for(int i = 0; i < n_input; ++i)
            std::cout << (int)inp_patterns[p][i];
        std::cout << "   ";
 
        for(int i = 0; i < n_out; ++i)
            std::cout << (int)out_patterns[p][i];
        std::cout << "   ";
 
        CompareBinaryResult(N_out, n_out, max0, min1);
        std::cout << "    ";
 
        for(int i = 0; i < n_out; ++i)
            std::cout << N_out[i] << "  ";
        std::cout << '\n';
    }
 
    std::cout << "\n--- interactive ---\n";
    while(true){
        float bits[4];
        if(!read4bits(bits)){
            std::cout << "Некорректный ввод. Выход.\n";
            break;
        }
        for(int i=0;i<4;++i) N_inp[i] = bits[i];
 
        forwardPass();
 
        std::cout << "Binary out (thresholded): ";
        CompareBinaryResult(N_out, n_out, max0, min1);
        std::cout << "\nRaw out: ";
        for(int i=0;i<n_out;++i) std::cout << N_out[i] << (i==n_out-1?"\n":" ");
 
        std::cout << "Повторить ввод? (y - да, другой символ - выход): ";
        char replyYN;
        std::cin >> replyYN;
        if (replyYN != 'Y' && replyYN != 'y') break;
    }
 
    return 0;
}
1
0 / 0 / 0
Регистрация: 16.02.2025
Сообщений: 14
13.01.2026, 20:51  [ТС]
Т.е. уровень владения предметом я увидел - и дальше мне не интересно.
Ну если бы мой уровень соответствовал вашим высоким критериям, я бы на форум и заходить не стал бы. Если можете что-то конкретно посоветовать, подскажите.

Добавлено через 14 минут
"А если выходы переписать, то тогда получится "дешифратор" типа К155ИД3. " Да, можно, только var_combInp будет для микросхемы равен 19 (возможным вариантам состояний), n_input=6, по количеству входных ножек, а n_out=16 вариантов по количеству выходных ног. Как -то так, если упрощенно
0
2 / 2 / 0
Регистрация: 04.07.2021
Сообщений: 101
04.02.2026, 10:53
свой код это хорошо, значит вы разобрались в теме. но есть много библиотек сейчас, я например использую opennn, пока правда похвастаться нечем.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
04.02.2026, 10:53
Помогаю со студенческими работами здесь

Простая игра
Скиньте мне в файле ворда какую то простую игру в С++. Пожалуйста:'(

Помогите! нужна простая программа
Всем привет, нуждаюсь в вашей помощи, мне нужна простая программа написанная на С++, не важно что...

Простая задачка
Здравствуйте. Есть вот такая задачка: Написать программу на С пораждающая в цикле следующюю...

Работа с файлами,простая БД
Помогите пожалуйста, по ПЯВУ получил курсовую: &quot;Система учета автомобили - владельцы -...

Простая задача.
Пожалуйста помогите кто может, очень нужно Даны натуральное число n, действительные числа...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
9
Ответ Создать тему
Новые блоги и статьи
Как дизайн сайта влияет на конверсию: 7 решений, которые реально повышают заявки
Neotwalker 08.03.2026
Многие до сих пор воспринимают дизайн сайта как “красивую оболочку”. На практике всё иначе: дизайн напрямую влияет на то, оставит человек заявку или уйдёт через несколько секунд. Даже если у вас. . .
Модульная разработка через nuget packages
DevAlt 07.03.2026
Сложившийся в . Net-среде способ разработки чаще всего предполагает монорепозиторий в котором находятся все исходники. При создании нового решения, мы просто добавляем нужные проекты и имеем. . .
Модульный подход на примере F#
DevAlt 06.03.2026
В блоге дяди Боба наткнулся на такое определение: В этой книге («Подход, основанный на вариантах использования») Ивар утверждает, что архитектура программного обеспечения — это структуры,. . .
Управление камерой с помощью скрипта OrbitControls.js на Three.js: Вращение, зум и панорамирование
8Observer8 05.03.2026
Содержание блога Финальная демка в браузере работает на Desktop и мобильных браузерах. Итоговый код: orbit-controls-threejs-js. zip. Сканируйте QR-код на мобильном. Вращайте камеру одним пальцем,. . .
SDL3 для Web (WebAssembly): Синхронизация спрайтов SDL3 и тел Box2D
8Observer8 04.03.2026
Содержание блога Финальная демка в браузере. Итоговый код: finish-sync-physics-sprites-sdl3-c. zip На первой гифке отладочные линии отключены, а на второй включены:. . .
SDL3 для Web (WebAssembly): Идентификация объектов на Box2D v3 - использование userData и событий коллизий
8Observer8 02.03.2026
Содержание блога Финальная демка в браузере. Итоговый код: finish-collision-events-sdl3-c. zip Сканируйте QR-код на мобильном и вы увидите, что появится джойстик для управления главным героем. . . .
Реалии
Hrethgir 01.03.2026
Нет, я не закончил до сих пор симулятор. Эта задача сложнее. Не получилось уйти в плавсостав, но оно и к лучшему, возможно. Точнее получалось - но сварщиком в палубную команду, а это значит, в моём. . .
Ритм жизни
kumehtar 27.02.2026
Иногда приходится жить в ритме, где дел становится всё больше, а вовлечения в происходящее — всё меньше. Плотный график не даёт вниманию закрепиться ни на одном событии. Утро начинается с быстрых,. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru