loss_output нужен только для отрисовки графика функции потерь. Согласен, что в ранее предложенной реализации есть неоднозначность со значением loss_output. Поправил код в статье.
Тогда как в статье, я упростил код и использовал константы вместо передачи аргументов в функцию init. Это никак не должно повлиять на работу класса DQN.
По ошибке, рекомендую посмотреть код DQN(). Возможно, у вас не вызывается функция init у родительского класса nn.Module.
Но не проверил, что в предустановленных окружениях его нет :-( Поэтому в статье, к сожалению, ввёл вас в заблуждение. Поправлю статью. Спасибо, что обратили внимание.
Действительно, есть такая хорошая практика "не смешивать технический и бизнес код". В своём примере я намеренно его нарушил, добавив конвертацию типов. Чуть ниже опишу причину.
Также вы упомянули про MVC шаблон, где пользователь взаимодействует с Controller и меняет модель (через слои Service + Repository, которого в примере у меня нет), а потом изменяет View (для бекенда - это просто вернуть ответ UI приложению)
Дополнительно, мне очень понравилось про инфраструктурную роль. Ранее мне не доводилось слышать о таком подходе применимо к классам. Это интересно. Спасибо, что поделились.
Но в итоге я понял, что мне нужно пояснить, что я имел ввиду под "образовательная цель примера".
Возможно, многие опытные специалисты, которые оставляли критические комментарии, к примеру, уже подзабыли, когда делали свои первые шаги в программировании и какие именно программы им давали писать учителя.
Статья в первую очередь направлена на начинающих специалистов и тех, кто только делает первые шаги в программировании. И если накрутить все-все хорошие практики, то фокус у обучаемого может сместиться или более того, структура приложения может оказаться слишком сложной для их восприятия и осознания.
Поэтому у меня были следующие задачи перед написанием примера:
Стартовый код примера должен умещаться в 1 класс и желательно в 1 метод
Бизнес-логика кода должна быть максимально простой, сходу понятной и не требующего погружения в предметную область
Количество операций в стартовом коде приложения не должно быть больше 3-5.
Исходя из этих задач, я выбрал максимально простой сервис: математические операции. Ведь математика - это предметная область, знакомая любому человеку, который ходил в школу. Но бизнес-код оказался настолько простой, что пришлось добавить преобразование типов, также это позволило абстрагироваться от логики UI и просто написать, что UI пришлёт текст, как его ввёл пользователь.
Подведу итог:
Образовательный пример - это не production-ready код. Он может нарушать некоторые хорошие практики, принятые в профессии, в угоду образовательной цели.
Постараюсь вам показать разницу в категориях мышления.
Процедурное мышление чем-то напоминает подход code first при проектировании API. Где сначала создаётся реализация, а затем разработчик смотрит на то, что у него по итогу получилось и описывает интерфейсы. И если на этом этапе приходит понимание, что получилось не очень удобная реализация, то начинается рефакторинг.
И при процедурном мышлении получается примерно также. Сначала код пишется в лоб, где фокус на реализацию алгоритма задачи. А затем разработчик смотрит на то, что получилось и как этим в дальнейшем пользоваться, то как правило это заканчивается рефакторигном (вынесение в классы, группировки в отдельные методы и т.п.), потому что фокус у него был другой (на реализацию алгоритма)
Мышление объектами напоминает подход contract first при проектировании API. Где сначала проектируются интерфейсы, а затем под них уже выполняется реализации.
Разработчик в этом случае выступает в роли архитектора, только не на уровне информационной системы (ИС) в целом, а на уровне приложения. Для такого разработчика программа - миниатюрная ИС со своими микросервисами и отношениями между ними. И микросервисы в данном случае выступают классы. Такой разработчик оперирует сущностями и контрактами между ними. Алгоритм реализации - это уже следующий этап, когда будут сформированы сущности и связи между ними. А количество строчек кода вообще перестаёт играть какое-либо значение.
Резюме:
И получается достаточно интересная ситуация. С точки зрения процедурного мышления - ваши претензии вполне оправданы, т.к. группировка кода (именно так рассматриваются методы в этой категории мышления) из одной строчки не имеет смысла. А с точки зрения мышления объектами, важна архитектура приложения (сущности и их отношения), а количество строчек кода вообще не имеет никакого значения.
Я не призываю вас принять не свойственную вам точку зрения. Просто хочу показать, что на один и тот же предмет можно смотреть под разными углами.
P.S. В заключении скажу, что я понимаю ваши вопросы, т.к. всех программистов обучают писать код императивным стилем. А такой стиль кодирования формирует процедурное мышление. Я пришёл к мышлению объектами (сущностями, если быть более точным), когда стал изучать функциональное программирование. По началу, мне оно показалось полным бредом, пока я не начал сквозь боль и неприятие учиться этим пользоваться. И после этого у меня началась формироваться новая категория мышления, т.к. я осознал, что в этом есть определённый смысл.
Класс, который следует концепции Rich объект (в терминах DDD) представляет собой класс-гибрид (я упоминал об этом в статье). Он сочетает в себе сразу несколько типов. В основе Rich объекта лежит Дата-класс, в который добавляется, как минимум Класс-исполнитель.
Уместность зависит от категории мышления программиста.
Программист, который думает процедурно, то он будет писать код примерно, как в этом примере:
public class MathService {
public String multiply(String strNumber1, String strNumber2) {
int num1 = Integer.parseInt(strNumber1);
int num2 = Integer.parseInt(strNumber2);
int result = num1 * num2;
String strResult = new Integer(result).toString();
return strResult;
}
}
Фокус в процедруном мышлении на строчки кода. Весь код приложения воспринимается, как длинная программа поделенная на классы по большей части для удобства ориентации в ней и использования. Другими словами, классы и методы нужны только для того, чтобы объединять код (в процедуры и функции) с возможностью его где-то переиспользовать.
Программист, который думает объектами, то он будет писать код примерно, как в этом примере:
public class MathWebService {
private final NumberConverter numberConverter = new NumberConverter(); // для простоты DI не буду использовать в примере.
private final MathOperations mathOperations = new MathOperations(); // для простоты DI не буду использовать в примере.
public String multiplyFeature(String strNumber1, String strNumber2) {
int num1 = numberConverter.toInt(strNumber1);
int num2 = numberConverter.toInt(strNumber2);
int result = mathOperations.multiply(num1, num2);
String strResult = numberConverter.toString(result);
return strResult;
}
}
Фокус на объектах и отношениях между ними. Для такого разработчика программа воспринимается как организация со своей иерархией, должностями и должностными инструкциями. Классы - это должности (роли) в организации, а методы - это выполняемые действия в соответствии с должностной инструкцией. Количество строк кода в должностной инструкции в этом случае играет второстепенную роль.
Если программист тяготеет к процедурному мышлению. То он вполне естественно испытывает диссонанс, увидев метод из одной строчки кода, т.к. в его категории мышления - группировка кода из одного метода лишена здравого смысла.
Такое может быть. Например, в интеграционных-сервисах, которые обогащают DTO данными, полученных из других источников, прежде чем прокинуть DTO дальше другому сервису.
Насчёт примера. Цель моего примера исключительно образовательная: показать работу принципов. Всё, что не касается этой цели было намерено убрано или упрощено.
Согласен, простота написания юнит-теста - это хороший сигнал к тому, что код перегружен в классах-исполнителях. Но если делить код по принципу "лишь бы юнит-тест было просто написать", то классы, как правило, получаются со множеством ролей, а в совсем запущенных случаях и со множеством доменов. Такой код в дальнейшем сложно поддерживать и расширять.
Также отмечу, что юнит-тест на метод класс-управленец априори будет состоять только из одних моков, т.к. класс-управленец ничего не делает сам, а только делегирует выполнение работы другим классам.
>Хотя некоторая трава, на вкус, слишком яркой стала
Цветовую палитру брал с имперской травы :-) Поэтому визуально цветовая гамма эльфийской травы в игре смотрится гармонично с окружением. А в целом, цвета могут казаться слишком яркими на фоне тёмных в видео. Рекомендую, посмотреть в самой игре, если у вас есть такая возможность.
loss_output нужен только для отрисовки графика функции потерь. Согласен, что в ранее предложенной реализации есть неоднозначность со значением loss_output. Поправил код в статье.
Всё верно: по одному (самому первому) шагу нельзя понять положительный он или нет, т.к. для сравнения требуется опыт двух шагов.
В финальном примере у меня этот случай обрабатывается, а когда упрощал код для примера в статье, то этот момент упустил.
Добавил условие в функции learn() у агента в примере в статье.
В коде репозитория используется параметризированный инициализатор
Тогда как в статье, я упростил код и использовал константы вместо передачи аргументов в функцию init. Это никак не должно повлиять на работу класса DQN.
По ошибке, рекомендую посмотреть код DQN(). Возможно, у вас не вызывается функция init у родительского класса nn.Module.
Поправил в статье
Я взял окружение gridworld из официального гайда библиотеки https://gymnasium.farama.org/tutorials/gymnasium_basics/environment_creation/
Но не проверил, что в предустановленных окружениях его нет :-( Поэтому в статье, к сожалению, ввёл вас в заблуждение. Поправлю статью. Спасибо, что обратили внимание.
Быстрый фикс:
Cкачать два файла из https://github.com/DarrMirr/reinforcement-learning-introduction/tree/main/src/environment
Положить два скаченных файла к себе в проект
Перед gym.make(...) добавить строчку class_GridWorldEnv.register() и соответствующий импорт class_GridWorldEnv.py
При написании проекта использовал VS Code.
Спасибо за ссылку. С интересом почитал статью.
Я хотел лишь донести до вас мысль, что всё зависит от контекста.
В контексте коммерческой разработки: представленный пример однозначно потребуется доработать и точно учесть те предложения, что вы описали выше.
В контексте обучения: перегруженный и накрученный различными практиками пример будет только отвлекать обучаемого от основной темы.
Вы упомянули несколько хороших практик.
Действительно, есть такая хорошая практика "не смешивать технический и бизнес код". В своём примере я намеренно его нарушил, добавив конвертацию типов. Чуть ниже опишу причину.
Также вы упомянули про MVC шаблон, где пользователь взаимодействует с Controller и меняет модель (через слои Service + Repository, которого в примере у меня нет), а потом изменяет View (для бекенда - это просто вернуть ответ UI приложению)
Дополнительно, мне очень понравилось про инфраструктурную роль. Ранее мне не доводилось слышать о таком подходе применимо к классам. Это интересно. Спасибо, что поделились.
Но в итоге я понял, что мне нужно пояснить, что я имел ввиду под "образовательная цель примера".
Возможно, многие опытные специалисты, которые оставляли критические комментарии, к примеру, уже подзабыли, когда делали свои первые шаги в программировании и какие именно программы им давали писать учителя.
Статья в первую очередь направлена на начинающих специалистов и тех, кто только делает первые шаги в программировании. И если накрутить все-все хорошие практики, то фокус у обучаемого может сместиться или более того, структура приложения может оказаться слишком сложной для их восприятия и осознания.
Поэтому у меня были следующие задачи перед написанием примера:
Стартовый код примера должен умещаться в 1 класс и желательно в 1 метод
Бизнес-логика кода должна быть максимально простой, сходу понятной и не требующего погружения в предметную область
Количество операций в стартовом коде приложения не должно быть больше 3-5.
Исходя из этих задач, я выбрал максимально простой сервис: математические операции. Ведь математика - это предметная область, знакомая любому человеку, который ходил в школу. Но бизнес-код оказался настолько простой, что пришлось добавить преобразование типов, также это позволило абстрагироваться от логики UI и просто написать, что UI пришлёт текст, как его ввёл пользователь.
Подведу итог:
Образовательный пример - это не production-ready код. Он может нарушать некоторые хорошие практики, принятые в профессии, в угоду образовательной цели.
Постараюсь вам показать разницу в категориях мышления.
Процедурное мышление чем-то напоминает подход code first при проектировании API. Где сначала создаётся реализация, а затем разработчик смотрит на то, что у него по итогу получилось и описывает интерфейсы. И если на этом этапе приходит понимание, что получилось не очень удобная реализация, то начинается рефакторинг.
И при процедурном мышлении получается примерно также. Сначала код пишется в лоб, где фокус на реализацию алгоритма задачи. А затем разработчик смотрит на то, что получилось и как этим в дальнейшем пользоваться, то как правило это заканчивается рефакторигном (вынесение в классы, группировки в отдельные методы и т.п.), потому что фокус у него был другой (на реализацию алгоритма)
Мышление объектами напоминает подход contract first при проектировании API. Где сначала проектируются интерфейсы, а затем под них уже выполняется реализации.
Разработчик в этом случае выступает в роли архитектора, только не на уровне информационной системы (ИС) в целом, а на уровне приложения. Для такого разработчика программа - миниатюрная ИС со своими микросервисами и отношениями между ними. И микросервисы в данном случае выступают классы. Такой разработчик оперирует сущностями и контрактами между ними. Алгоритм реализации - это уже следующий этап, когда будут сформированы сущности и связи между ними. А количество строчек кода вообще перестаёт играть какое-либо значение.
Резюме:
И получается достаточно интересная ситуация. С точки зрения процедурного мышления - ваши претензии вполне оправданы, т.к. группировка кода (именно так рассматриваются методы в этой категории мышления) из одной строчки не имеет смысла. А с точки зрения мышления объектами, важна архитектура приложения (сущности и их отношения), а количество строчек кода вообще не имеет никакого значения.
Я не призываю вас принять не свойственную вам точку зрения. Просто хочу показать, что на один и тот же предмет можно смотреть под разными углами.
P.S.
В заключении скажу, что я понимаю ваши вопросы, т.к. всех программистов обучают писать код императивным стилем. А такой стиль кодирования формирует процедурное мышление. Я пришёл к мышлению объектами (сущностями, если быть более точным), когда стал изучать функциональное программирование. По началу, мне оно показалось полным бредом, пока я не начал сквозь боль и неприятие учиться этим пользоваться. И после этого у меня началась формироваться новая категория мышления, т.к. я осознал, что в этом есть определённый смысл.
Класс, который следует концепции Rich объект (в терминах DDD) представляет собой класс-гибрид (я упоминал об этом в статье). Он сочетает в себе сразу несколько типов. В основе Rich объекта лежит Дата-класс, в который добавляется, как минимум Класс-исполнитель.
Уместность зависит от категории мышления программиста.
Программист, который думает процедурно, то он будет писать код примерно, как в этом примере:
Фокус в процедруном мышлении на строчки кода. Весь код приложения воспринимается, как длинная программа поделенная на классы по большей части для удобства ориентации в ней и использования. Другими словами, классы и методы нужны только для того, чтобы объединять код (в процедуры и функции) с возможностью его где-то переиспользовать.
Программист, который думает объектами, то он будет писать код примерно, как в этом примере:
Фокус на объектах и отношениях между ними. Для такого разработчика программа воспринимается как организация со своей иерархией, должностями и должностными инструкциями. Классы - это должности (роли) в организации, а методы - это выполняемые действия в соответствии с должностной инструкцией. Количество строк кода в должностной инструкции в этом случае играет второстепенную роль.
Если программист тяготеет к процедурному мышлению. То он вполне естественно испытывает диссонанс, увидев метод из одной строчки кода, т.к. в его категории мышления - группировка кода из одного метода лишена здравого смысла.
Обычно ментор = наставник.
Более подробно с описанием из разных толковых словарей можно посмотреть здесь: https://gufo.me/dict/kuznetsov/ментор
Насчёт примера.
Цель моего примера исключительно образовательная: показать работу принципов. Всё, что не касается этой цели было намерено убрано или упрощено.
Такое может быть. Например, в интеграционных-сервисах, которые обогащают DTO данными, полученных из других источников, прежде чем прокинуть DTO дальше другому сервису.
Насчёт примера.
Цель моего примера исключительно образовательная: показать работу принципов. Всё, что не касается этой цели было намерено убрано или упрощено.
Согласен, простота написания юнит-теста - это хороший сигнал к тому, что код перегружен в классах-исполнителях. Но если делить код по принципу "лишь бы юнит-тест было просто написать", то классы, как правило, получаются со множеством ролей, а в совсем запущенных случаях и со множеством доменов. Такой код в дальнейшем сложно поддерживать и расширять.
Также отмечу, что юнит-тест на метод класс-управленец априори будет состоять только из одних моков, т.к. класс-управленец ничего не делает сам, а только делегирует выполнение работы другим классам.
Rogue Lords тоже стоит в wishlist'е. А вот ArtStation, к сожалению, у меня не открылся. Видимо нужен впн...
Попробуйте, возможно, ваше мнение изменится.
Я в пятых героев тоже наиграл не мало часов. Но Герои и Disciples хоть и близкие по жанру игры, но по механикам игры довольно разные.
Кампанию можно проходить с модом. Я сейчас так и делаю :-)
Приятной вам игры. Если будет возможность, поделитесь своими впечатлениями после игры.
>Хотя некоторая трава, на вкус, слишком яркой стала
Цветовую палитру брал с имперской травы :-) Поэтому визуально цветовая гамма эльфийской травы в игре смотрится гармонично с окружением. А в целом, цвета могут казаться слишком яркими на фоне тёмных в видео. Рекомендую, посмотреть в самой игре, если у вас есть такая возможность.