Имя: Пароль:
1C
 
Почему у нейронки в крестики-нолики лучший ход всегда в 0?
0 program345
 
01.08.25
17:32
привет!

Код модуля формы:

&НаКлиенте
Перем КоличествоПолей; // 3x3
//Перем КоличествоПолей = 9; // 3x3
&НаКлиенте
Перем Веса; // Массив весов нейронной сети
&НаКлиенте
Перем Ввод; // Массив весов нейронной сети

// Инициализация нейронной сети
&НаКлиенте
Процедура ИнициализироватьСеть()
	Веса = Новый Массив(КоличествоПолей);
	Для i = 0 По КоличествоПолей-1 Цикл
		Веса[i] = СлучайноеЧисло(0, 1); // Инициализация случайными весами
	КонецЦикла;
КонецПроцедуры 

&НаКлиенте
Функция СлучайноеЧисло(Число1,Число2)
	
	ГСЧ = Новый ГенераторСлучайныхЧисел;
	Возврат ГСЧ.СлучайноеЧисло(Число1,Число2);	

КонецФункции // ()

// Функция активации (ReLU)
&НаКлиенте
Функция ReLU(х)
	Возврат Макс(0, х);
КонецФункции

// Получить лучший ход на основе текущего состояния 
&НаКлиенте
Функция ПолучитьЛучшийХод(СостояниеДоски)
	
	ЛучшийХод = -1;
	МаксОценка = -9999;
	
	Для i = 0 По КоличествоПолей-1 Цикл
		Если СостояниеДоски[i] = 0 Тогда // Если клетка свободна
			Оценка = 0;
			Для j = 0 По КоличествоПолей-1 Цикл
				Оценка = Оценка + Веса[j] * СостояниеДоски[j];
			КонецЦикла;
			Оценка = Оценка + Веса[i] * 1; // Предполагаем наш ход в эту клетку
			Оценка = ReLU(Оценка);
			
			Если Оценка > МаксОценка Тогда
				МаксОценка = Оценка;
				ЛучшийХод = i;
			КонецЕсли;
		КонецЕсли;
	КонецЦикла;
	
	Возврат ЛучшийХод;
КонецФункции

// Обновление весов на основе результата игры
&НаКлиенте
Процедура ОбновитьВеса(СостояниеДоски, ВыбранныйХод, Результат, СкоростьОбучения = 0.1)
	
	// Вычисляем целевую оценку
	ЦелеваяОценка = Результат; // 1 - победа, 0 - ничья, -1 - поражение
	
	// Предсказанная оценка до обучения
	Предсказание = 0;
	Для i = 0 По КоличествоПолей-1 Цикл
		Предсказание = Предсказание + Веса[i] * СостояниеДоски[i];
	КонецЦикла;
	Предсказание = ReLU(Предсказание);
	
	// Ошибка
	Ошибка = ЦелеваяОценка - Предсказание;
	
	// Обновляем веса
	Для i = 0 По КоличествоПолей-1 Цикл
		Веса[i] = Веса[i] + СкоростьОбучения * Ошибка * СостояниеДоски[i];
	КонецЦикла;
	Веса[ВыбранныйХод] = Веса[ВыбранныйХод] + СкоростьОбучения * Ошибка * 1;
	
КонецПроцедуры

// Проверка победы 
&НаКлиенте
Функция ПроверитьПобеду(Доска, Игрок)
	// Горизонтальные линии
	Если (Доска[0] = Игрок И Доска[1] = Игрок И Доска[2] = Игрок) Или
		(Доска[3] = Игрок И Доска[4] = Игрок И Доска[5] = Игрок) Или
		(Доска[6] = Игрок И Доска[7] = Игрок И Доска[8] = Игрок) Тогда
		Возврат Истина;
	КонецЕсли;
	
	// Вертикальные линии
	Если (Доска[0] = Игрок И Доска[3] = Игрок И Доска[6] = Игрок) Или
		(Доска[1] = Игрок И Доска[4] = Игрок И Доска[7] = Игрок) Или
		(Доска[2] = Игрок И Доска[5] = Игрок И Доска[8] = Игрок) Тогда
		Возврат Истина;
	КонецЕсли;
	
	// Диагонали
	Если (Доска[0] = Игрок И Доска[4] = Игрок И Доска[8] = Игрок) Или
		(Доска[2] = Игрок И Доска[4] = Игрок И Доска[6] = Игрок) Тогда
		Возврат Истина;
	КонецЕсли;
	
	Возврат Ложь;
КонецФункции

// Проверка ничьи
&НаКлиенте
Функция ПроверитьНичью(Доска)
	Для i = 0 По КоличествоПолей-1 Цикл
		Если Доска[i] = 0 Тогда
			Возврат Ложь;
		КонецЕсли;
	КонецЦикла;
	Возврат Истина;
КонецФункции

// Процедура обучения 
&НаКлиенте
Процедура ОбучитьНейронку(КоличествоЭпох = 10000)
	
	ИнициализироватьСеть();
	
	Для эпоха = 1 По КоличествоЭпох Цикл
		
		Доска = Новый Массив(КоличествоПолей);
		Для i = 0 По КоличествоПолей-1 Цикл
			Доска[i] = 0; // 0 - пусто, 1 - X, -1 - O
		КонецЦикла;
		
		Ход = 1; // 1 - X (нейронка), -1 - O (оппонент)
		
		Пока Истина Цикл
			Если Ход = 1 Тогда
				// Ход нейронки
				ЛучшийХод = ПолучитьЛучшийХод(Доска);
				Если ЛучшийХод = -1 Тогда
					Прервать; // Нет доступных ходов
				КонецЕсли;
				Доска[ЛучшийХод] = 1;
				
				Если ПроверитьПобеду(Доска, 1) Тогда
					ОбновитьВеса(Доска, ЛучшийХод, 1); // Победа
					Прервать;
				ИначеЕсли ПроверитьНичью(Доска) Тогда
					ОбновитьВеса(Доска, ЛучшийХод, 0); // Ничья
					Прервать;
				КонецЕсли;
				
			Иначе
				// Ход оппонента (случайный)
				СвободныеКлетки = Новый Массив;
				Для i = 0 По КоличествоПолей-1 Цикл
					Если Доска[i] = 0 Тогда
						СвободныеКлетки.Добавить(i);
					КонецЕсли;
				КонецЦикла;
				
				Если СвободныеКлетки.Количество() = 0 Тогда
					Прервать;
				КонецЕсли;
				
				СлучайныйХод = СвободныеКлетки[СлучайноеЧисло(0, СвободныеКлетки.Количество()-1)];
				Доска[СлучайныйХод] = -1;
				
				Если ПроверитьПобеду(Доска, -1) Тогда
					ОбновитьВеса(Доска, ЛучшийХод, -1); // Поражение
					Прервать;
				ИначеЕсли ПроверитьНичью(Доска) Тогда
					ОбновитьВеса(Доска, ЛучшийХод, 0); // Ничья
					Прервать;
				КонецЕсли;
			КонецЕсли;
			
			Ход = -Ход; // Смена хода
		КонецЦикла;
	КонецЦикла;
	
	Сообщить("Обучение завершено!");
КонецПроцедуры

// Пример игры против обученной нейронки
&НаКлиенте
Процедура ИгратьПротивНейронки()
	
	Доска = Новый Массив(КоличествоПолей);
	Для i = 0 По КоличествоПолей-1 Цикл
		Доска[i] = 0;
	КонецЦикла;
	
	Ход = 1; // 1 - нейронка (X), -1 - игрок (O)
	
	Пока Истина Цикл
		// Отображаем доску
		СтрокаДоски = "";
		Для i = 0 По 8 Цикл
			Если Доска[i] = 1 Тогда
				СтрокаДоски = СтрокаДоски + "X";
			ИначеЕсли Доска[i] = -1 Тогда
				СтрокаДоски = СтрокаДоски + "O";
			Иначе
				СтрокаДоски = СтрокаДоски + i;
			КонецЕсли;
			
			Если (i+1) % 3 = 0 Тогда
				СтрокаДоски = СтрокаДоски + Символы.ПС;
			Иначе
				СтрокаДоски = СтрокаДоски + "|";
			КонецЕсли;
		КонецЦикла;
		
		Сообщить(СтрокаДоски);
		
		Если Ход = 1 Тогда
			// Ход нейронки
			ЛучшийХод = ПолучитьЛучшийХод(Доска);
			Если ЛучшийХод = -1 Тогда
				Сообщить("Ничья!");
				Возврат;
			КонецЕсли;
			Доска[ЛучшийХод] = 1;
			Сообщить("Нейронка походила в клетку " + ЛучшийХод);
			
			Если ПроверитьПобеду(Доска, 1) Тогда
				Сообщить("Нейронка победила!");
				Возврат;
			ИначеЕсли ПроверитьНичью(Доска) Тогда
				Сообщить("Ничья!");
				Возврат;
			КонецЕсли;
			
		Иначе
			// Ход игрока
			Сообщить("Ваш ход (введите номер клетки 0-8):");
			ВвестиЧислоМодально = ВвестиЧисло(Ввод);
			
			Если Не ВвестиЧислоМодально Тогда
			
				Пока Ввод < 0 Или Ввод > 8 Или Доска[Ввод] <> 0 Цикл
					Сообщить("Некорректный ход. Попробуйте еще раз:");
					ВвестиЧислоМодально = ВвестиЧисло(Ввод);
				КонецЦикла;
				
			КонецЕсли;
			
			Доска[Ввод] = -1;
			
			Если ПроверитьПобеду(Доска, -1) Тогда
				Сообщить("Вы победили!");
				Возврат;
			ИначеЕсли ПроверитьНичью(Доска) Тогда
				Сообщить("Ничья!");
				Возврат;
			КонецЕсли;
		КонецЕсли;
		
		Ход = -Ход; // Смена хода
	КонецЦикла;
	
КонецПроцедуры

// Запуск обучения и игры 
&НаКлиенте
Процедура Главная()
	ОбучитьНейронку(10000); // Обучаем на 10,000 играх
	ИгратьПротивНейронки(); // Играем против обученной нейронки
КонецПроцедуры

&НаКлиенте
Процедура ОбучитьНейронку_1(Команда)
	ОбучитьНейронку();
КонецПроцедуры

&НаКлиенте
Процедура ИгратьПротивНейронки_2(Команда)
	ИгратьПротивНейронки();
КонецПроцедуры

КоличествоПолей =9;



Что мне кажется тут где-то ошибка в лучшем коде или все верно?
1 OldCondom
 
02.08.25
14:12
Ошибка где-то в лучшем коде или вам кажется. Возможно, все верно.
2 Greeen
 
02.08.25
21:45
Лучше уберите этот код из открытого доступа, иначе этот ИИ сможет развиться и уничтожить человечество
3 Волшебник
 
02.08.25
21:46
Если противник первым ходом поставил в центр, то надо ставить в угол, иначе проиграешь
4 b_ru
 
03.08.25
01:00
(3) Если противник первым ходом поставил в центре, то он уже не выиграл.
5 Волшебник
 
03.08.25
15:49
(4) Если после первого хода противника в центр вы поставите в боковушечку, то вы уже проиграли
6 Chai Nic
 
03.08.25
10:26
(2) Раньше это называли "метод Монте-Карло", а теперь "нейросеть")
7 Волшебник
 
03.08.25
20:19
(4) Вы не понимаете, что крестики-нолики - это ничья? Вы совсем дурак и в школе не учились? Вы вообще хоть раз проигрывали в крестики-нолики? Если да, то я вам так скажу: когда вы перестали проигрывать в крестики-нолики без поддавков, тогда вы стали программистом. А потом вы начали поддаваться и ребёнок начал иногда выигрывать. Так вы стали родителем, то есть начали программировать новых программистов.
Чтобы обнаруживать ошибки, программист должен иметь ум, которому доставляет удовольствие находить изъяны там, где, казалось, царят красота и совершенство. Фредерик Брукс-младший