3) С позициями разобрались, остались скорости. Моя логика была в том, чтобы заэнкодить скорости таким же образом, как и позиции. "Безобразно, зато единообразно" - я предполагал, что так будет проще учить сеть. Как скорости превратить в позиции? Помножить на время, конечно! Множим скорости на dt, прибавляем к начальным позициям. Получаем новые позиции с интересным физическим смыслом.
Это не positions1, которые мы ищем, но это то, как двигались бы частицы, если бы не взаимодействовали друг с другом.
Это не positions1, которые мы ищем, но это то, как двигались бы частицы, если бы не взаимодействовали друг с другом.
Теперь у нас есть две картинки "поля", содержащих информацию о позициях - начальной, и той, в которой бы оказались частицы, если бы двигались равномерно и прямолинейно, без действующих на них сил, по заветам Ньютона.
Дальше дело техники - так как взаимодействия в данной симуляции распространяются на конечное расстояние, нарезаем общее поле на квадратики вокруг каждой частицы. У меня они 15х15 размером.
Получаем батч с размерностью (количество_частиц, 2, 15, 15), который так и просится на вход сверточной нейронной сети. А на выходе просим сеть выдать коррекцию координат (pos_correction) и скорости (vel_correction) относительно этого "линейного шага" - в смысле, мы по сути говорим "Вот так выглядело окружение частицы в момент времени t0. Вот так бы оно выглядело через интервал времени dt, если бы частицы не взаимодействовали. Дай мне коррекцию координат и скорости, чтобы я получил то, как они реально двигались, с учетом взаимодействий".
В итоге вычисляем
positions1 = positions0 + velocities0*dt + pos_correction и
velocities1 = velocities0 + vel_correction
Таким образом для частиц, на которые никакая сила не действует, сети достаточно всегда выдавать нули в качестве коррекций - потому что они и так двигаются линейно.
Это на удивление хорошо заработало - ну, гифки и видюшки на ютубе я кидал. В отличие от обычной симуляции, нейросимуляция не "взрывается", даже когда я разогнал частицы до скоростей вдвое больших чем те, на которых сеть обучалась, они смогли затормозить, застревая в массе неподвижных соседей.
В следующем посте расскажу о том, над чем конкретно я работаю сейчас.
Дальше дело техники - так как взаимодействия в данной симуляции распространяются на конечное расстояние, нарезаем общее поле на квадратики вокруг каждой частицы. У меня они 15х15 размером.
Получаем батч с размерностью (количество_частиц, 2, 15, 15), который так и просится на вход сверточной нейронной сети. А на выходе просим сеть выдать коррекцию координат (pos_correction) и скорости (vel_correction) относительно этого "линейного шага" - в смысле, мы по сути говорим "Вот так выглядело окружение частицы в момент времени t0. Вот так бы оно выглядело через интервал времени dt, если бы частицы не взаимодействовали. Дай мне коррекцию координат и скорости, чтобы я получил то, как они реально двигались, с учетом взаимодействий".
В итоге вычисляем
positions1 = positions0 + velocities0*dt + pos_correction и
velocities1 = velocities0 + vel_correction
Таким образом для частиц, на которые никакая сила не действует, сети достаточно всегда выдавать нули в качестве коррекций - потому что они и так двигаются линейно.
Это на удивление хорошо заработало - ну, гифки и видюшки на ютубе я кидал. В отличие от обычной симуляции, нейросимуляция не "взрывается", даже когда я разогнал частицы до скоростей вдвое больших чем те, на которых сеть обучалась, они смогли затормозить, застревая в массе неподвижных соседей.
В следующем посте расскажу о том, над чем конкретно я работаю сейчас.
Часть 2.1. Над чем я работаю сейчас.
Меня занимает несколько вопросов, в этом посте я рассмотрю первый из них.
Этот "образ" частицы, картинку 3х3 я генерю ручками - в смысле, это я просто выбрал такую вот формулу для его расчета, и это заработало.
Но ведь его тоже можно выучить! Представьте, что вместо посчитанного через расстояние образа, у нас есть еще одна нейросетка, которая на вход получает координаты частицы (ведь мы помним, что образ зависит от координат, это не статичная картинка, а выход 2д функции, которая на вход получает дробную часть координат, а на выходе выдает 9 значений, которые потом и записываются в общее "поле"), а на выходе выдает искомую матрицу 3х3.
Теоретически, такой выученный образ может оказаться эффективнее, ведь он не ограничен одной формулой расстояния. Да и на вход можно подать не только координаты, но и, например, скорость. Да что угодно можно подать - пусть сеть сама определит, что ей нужнее всего в этом образе. Я эту сеть зову "энкодером".
Но так как нет эталонных образов, которым нужно учить энкодер, не получится и просто взять и посчитать расхождение выхода нейросети с целевым образом и распространить градиент в ее веса.
Чтобы ее обучить, нужно, чтобы градиент ошибки позиции и скорости тёк не только в веса той нейросетки, которая по упомянутому выше батчу генерит эти самые коррекции позиций и скорости, но и в матрицу самого батча, а оттуда, через поле в матрицу образа частицы, сгенеренной энкодером, а из нее - в веса энкодера.
Если эта фраза не очень понятна, то рекомендую старую статью "a gentle introduction to backpropagation", с которой я много лет назад начинал свой путь в машинное обучение. Ссылку приведу в конце.
На самом деле, эту часть я уже реализовал. Для этого пришлось написать кастомный слой для пайторча, который собирает из образов поле, сохраняя информацию о том, куда какой разместил, что позволяет на обратном шаге понять, где был какой пиксель какого образа, чтобы градиент мог через них протечь. Это было математически просто, но мудно в реализации, потому что математически там просто суммы градиентов, но очень легко было ошибиться во всяких индексах и направить эти самые градиенты не туда, куда им положено течь.
К сожалению, результат несколько разочаровал. Да, сетка выучила образ. Он даже оказался похож на те, что я собирал вручную! Так что концепция работает. Но особого выигрыша в точности я не получил. То есть, да, он есть, но его можно заметить только если прогнать обучение по несколько раз, а результаты сгруппировать и сгладить. К счастью, это за меня делает wandb.
Wandb - это такой сервис + библиотека - Weights And Biases, делает примерно то же, что и Tensorboard и немного больше. В принципе, это дело вкуса, но я к ней привык и использую именно ее. Ссылку также приведу в конце.
Ну штош. Бывает. Хотя бы заработало, уже хорошо.
Из интересного - выученный сеткой образ напоминает тот, что я собирал вручную из расстояний, но если расстояния (разумеется) строго неотрицательны, то выученный образ не только включает в себя отрицательные значения, но иногда оказывается полностью инвертированным, эдакой "античастицей". В смысле, если запустить обучение, то выучиться может как обычный образ (где в центре число побольше, обычно положительное, по краям - поменьше), так и "антиобраз" (где в центре число поменьше, как правило, отрицательное, а по краям - побольше). По точности эти два варианта не отличаются.
Ниже привожу результаты, а в следующем посте я расскажу о самом долгом эксперименте этого проекта, его логическом продолжении, который прямо сейчас крутится на обучении в яндексовском облаке, потому что в мою видюху он не влезает
Меня занимает несколько вопросов, в этом посте я рассмотрю первый из них.
Этот "образ" частицы, картинку 3х3 я генерю ручками - в смысле, это я просто выбрал такую вот формулу для его расчета, и это заработало.
Но ведь его тоже можно выучить! Представьте, что вместо посчитанного через расстояние образа, у нас есть еще одна нейросетка, которая на вход получает координаты частицы (ведь мы помним, что образ зависит от координат, это не статичная картинка, а выход 2д функции, которая на вход получает дробную часть координат, а на выходе выдает 9 значений, которые потом и записываются в общее "поле"), а на выходе выдает искомую матрицу 3х3.
Теоретически, такой выученный образ может оказаться эффективнее, ведь он не ограничен одной формулой расстояния. Да и на вход можно подать не только координаты, но и, например, скорость. Да что угодно можно подать - пусть сеть сама определит, что ей нужнее всего в этом образе. Я эту сеть зову "энкодером".
Но так как нет эталонных образов, которым нужно учить энкодер, не получится и просто взять и посчитать расхождение выхода нейросети с целевым образом и распространить градиент в ее веса.
Чтобы ее обучить, нужно, чтобы градиент ошибки позиции и скорости тёк не только в веса той нейросетки, которая по упомянутому выше батчу генерит эти самые коррекции позиций и скорости, но и в матрицу самого батча, а оттуда, через поле в матрицу образа частицы, сгенеренной энкодером, а из нее - в веса энкодера.
Если эта фраза не очень понятна, то рекомендую старую статью "a gentle introduction to backpropagation", с которой я много лет назад начинал свой путь в машинное обучение. Ссылку приведу в конце.
На самом деле, эту часть я уже реализовал. Для этого пришлось написать кастомный слой для пайторча, который собирает из образов поле, сохраняя информацию о том, куда какой разместил, что позволяет на обратном шаге понять, где был какой пиксель какого образа, чтобы градиент мог через них протечь. Это было математически просто, но мудно в реализации, потому что математически там просто суммы градиентов, но очень легко было ошибиться во всяких индексах и направить эти самые градиенты не туда, куда им положено течь.
К сожалению, результат несколько разочаровал. Да, сетка выучила образ. Он даже оказался похож на те, что я собирал вручную! Так что концепция работает. Но особого выигрыша в точности я не получил. То есть, да, он есть, но его можно заметить только если прогнать обучение по несколько раз, а результаты сгруппировать и сгладить. К счастью, это за меня делает wandb.
Wandb - это такой сервис + библиотека - Weights And Biases, делает примерно то же, что и Tensorboard и немного больше. В принципе, это дело вкуса, но я к ней привык и использую именно ее. Ссылку также приведу в конце.
Ну штош. Бывает. Хотя бы заработало, уже хорошо.
Из интересного - выученный сеткой образ напоминает тот, что я собирал вручную из расстояний, но если расстояния (разумеется) строго неотрицательны, то выученный образ не только включает в себя отрицательные значения, но иногда оказывается полностью инвертированным, эдакой "античастицей". В смысле, если запустить обучение, то выучиться может как обычный образ (где в центре число побольше, обычно положительное, по краям - поменьше), так и "антиобраз" (где в центре число поменьше, как правило, отрицательное, а по краям - побольше). По точности эти два варианта не отличаются.
Ниже привожу результаты, а в следующем посте я расскажу о самом долгом эксперименте этого проекта, его логическом продолжении, который прямо сейчас крутится на обучении в яндексовском облаке, потому что в мою видюху он не влезает
This media is not supported in your browser
VIEW IN TELEGRAM
Слева - выученный образ, справа - построенный вручную из расстояний.
This media is not supported in your browser
VIEW IN TELEGRAM
А это из той сессии, где выучилась "антиматерия" - слева выученный, справа - собранный вручную.
https://www.researchgate.net/publication/266396438_A_Gentle_Introduction_to_Backpropagation
Ссылка на статью "A Gentle Introduction to Backpropagation". В принципе, в ней содержится все, что необходимо для понимания любой системы машинного обучения, это основа основ.
Ссылка на статью "A Gentle Introduction to Backpropagation". В принципе, в ней содержится все, что необходимо для понимания любой системы машинного обучения, это основа основ.
ResearchGate
(PDF) A Gentle Introduction to Backpropagation
PDF | An intuitive tutorial on a basic method of programming neural networks. This article also contains pseudocode ("Training Wheels for Training... | Find, read and cite all the research you need on ResearchGate
https://wandb.ai/
Сервис wandb. Аналог Tensorboard, позволяет в реальном времени строить красивые графики лосс-функций, да и вообще всего, что только захочется.
Сервис wandb. Аналог Tensorboard, позволяет в реальном времени строить красивые графики лосс-функций, да и вообще всего, что только захочется.
Weights & Biases
Weights & Biases: The AI Developer Platform
Learn why thousands of companies rely on W&B as their system of record for training AI models and developing AI applications with confidence.
Хвала бэкапам, все восстановил.
Пожалуй, не буду удалять копии постов из лички, а то вдруг что)
Пожалуй, не буду удалять копии постов из лички, а то вдруг что)
https://youtu.be/BYjj2r2gt2E
Фух. Думаю, пора с этим проектом завязывать)
Собрал в одно видео тесты разных сеток.
К сожалению, перепробовав десятки разных вариаций архитектуры и пайплайна, ничего ощутимо лучше того, с чего начал, я не нашел.
Наибольшее влияние на результат оказывает количество нейронов в скрытых слоях. Никакими вариациями архитектуры и пайплайна компенсировать малое количество нейронов не получается.
Ах да. я в прошлом посте упомянул эксперимент - его результат - первый в этом видео.
Я попробовал заставить сетку выучить не просто "образы" частиц, а целиком "поле", которое бы содержало информацию о скоростях.
Внезапно, это даже заработало, хотя стабильности, к сожалению, хватало всего итераций на 50.
При этом каждый "пиксель" этого поля содержал всю информацию обо всем окружении, настоящая такая "инопланетная" физика получилась - воспроизводит динамику частиц (по крайней мере, пока не взорвется), и в то же время там даже нет скоростей, только координаты и вот эти выученные "поля".
Фух. Думаю, пора с этим проектом завязывать)
Собрал в одно видео тесты разных сеток.
К сожалению, перепробовав десятки разных вариаций архитектуры и пайплайна, ничего ощутимо лучше того, с чего начал, я не нашел.
Наибольшее влияние на результат оказывает количество нейронов в скрытых слоях. Никакими вариациями архитектуры и пайплайна компенсировать малое количество нейронов не получается.
Ах да. я в прошлом посте упомянул эксперимент - его результат - первый в этом видео.
Я попробовал заставить сетку выучить не просто "образы" частиц, а целиком "поле", которое бы содержало информацию о скоростях.
Внезапно, это даже заработало, хотя стабильности, к сожалению, хватало всего итераций на 50.
При этом каждый "пиксель" этого поля содержал всю информацию обо всем окружении, настоящая такая "инопланетная" физика получилась - воспроизводит динамику частиц (по крайней мере, пока не взорвется), и в то же время там даже нет скоростей, только координаты и вот эти выученные "поля".
YouTube
Exploring Neuro-Simulation
Over the past two months, I've conducted numerous experiments, experimenting with different network architectures and varying my training pipeline. Unfortunately, I haven't made significant progress beyond my initial setup. It turns out that the most crucial…
Подводя итоги:
1) Эксперимент с целью ужать входные данные из 15х15 в 5х5.
Не заработало, обучение не сходится. Вероятно, могло бы заработать с очень большим количеством нейронов, но я не видел особо смысла в этом, я-то хотел упихать динамику частиц в маленькую сетку.
2) Эксперимент с целю заставить сетку выучить образы частиц вместо генерируемых вручную.
Заработало, выучивает что-то немного похожее на ручные, даже может выучить "антиматерию", в смысле, образ со знаком минус.
Занятно, но на практике не особо повысило стабильность и точность.
3) Эксперимент с целью выучить целиком "поле" и его динамику, чтобы вся система работала как такой выучиваемый клеточный автомат.
Заработало, выучивает поле скоростей, итераций на 40-50 даже воспроизводит динамику частиц, но получается нестабильная штука, куда менее стабильная, чем обычный подход с "полями" собираемыми вручную.
Кроме того, чтоб все эти градиенты могли течь через все сетки (и сетку-энкодер, и сетку, отвечающую за динамику, и сетку-корректор), пришлось запускать это все в облаке, в мою видюху не лезло. Сожрало внушительный кус денег.
Так что, может быть, если учить дольше, оно и сойдется к более стабильному решению, но меня задушила жаба и я свернул эксперимент.
4) Десятки и десятки экспериментов с целью получить стабильную нейросимуляцию с малым количеством нейронов.
К сожалению, стоит сделать поменьше нейронов, и это уже ничем особо не скомпенсируешь. Ни второй сеткой-корректором, ни хитрым энкодингом, ни варьирующимся шагом между соседними состояниями, ни дообучением ген. алгом, ничем.
Так что - да, в принципе, в крошечную сетку на 1036 параметров (по 4 нейрона в скрытом слое) динамика частиц влезает (в видео - ближе к концу есть пример). Но стабильности сетки с 16 нейронами в скрытом слое от нее ожидать не стоит.
В общем, подзадолбался я что-то с этими экспериментами, поэтому этот проект на текущий момент завершаю.
Там осталось еще что поисследовать - например, я уверен, что этот вот энкодинг, который я использовал для генерации входных данных, отлично подойдет чтоб обучить сетку выдавать динамический шаг интегрирования. Но как-нибудь в другой раз уже.
Сейчас же я перехожу к следующему проекту, он тоже использует нейросети, но он куда более практический, нежели исследовательский, так что надеюсь, что там будут результаты повеселее)
1) Эксперимент с целью ужать входные данные из 15х15 в 5х5.
Не заработало, обучение не сходится. Вероятно, могло бы заработать с очень большим количеством нейронов, но я не видел особо смысла в этом, я-то хотел упихать динамику частиц в маленькую сетку.
2) Эксперимент с целю заставить сетку выучить образы частиц вместо генерируемых вручную.
Заработало, выучивает что-то немного похожее на ручные, даже может выучить "антиматерию", в смысле, образ со знаком минус.
Занятно, но на практике не особо повысило стабильность и точность.
3) Эксперимент с целью выучить целиком "поле" и его динамику, чтобы вся система работала как такой выучиваемый клеточный автомат.
Заработало, выучивает поле скоростей, итераций на 40-50 даже воспроизводит динамику частиц, но получается нестабильная штука, куда менее стабильная, чем обычный подход с "полями" собираемыми вручную.
Кроме того, чтоб все эти градиенты могли течь через все сетки (и сетку-энкодер, и сетку, отвечающую за динамику, и сетку-корректор), пришлось запускать это все в облаке, в мою видюху не лезло. Сожрало внушительный кус денег.
Так что, может быть, если учить дольше, оно и сойдется к более стабильному решению, но меня задушила жаба и я свернул эксперимент.
4) Десятки и десятки экспериментов с целью получить стабильную нейросимуляцию с малым количеством нейронов.
К сожалению, стоит сделать поменьше нейронов, и это уже ничем особо не скомпенсируешь. Ни второй сеткой-корректором, ни хитрым энкодингом, ни варьирующимся шагом между соседними состояниями, ни дообучением ген. алгом, ничем.
Так что - да, в принципе, в крошечную сетку на 1036 параметров (по 4 нейрона в скрытом слое) динамика частиц влезает (в видео - ближе к концу есть пример). Но стабильности сетки с 16 нейронами в скрытом слое от нее ожидать не стоит.
В общем, подзадолбался я что-то с этими экспериментами, поэтому этот проект на текущий момент завершаю.
Там осталось еще что поисследовать - например, я уверен, что этот вот энкодинг, который я использовал для генерации входных данных, отлично подойдет чтоб обучить сетку выдавать динамический шаг интегрирования. Но как-нибудь в другой раз уже.
Сейчас же я перехожу к следующему проекту, он тоже использует нейросети, но он куда более практический, нежели исследовательский, так что надеюсь, что там будут результаты повеселее)
🔥6👍2
https://www.youtube.com/watch?v=U9mJuUkhUzk&ab_channel=OpenAI
Недавно OpenAI объявили об апгрейде своей языковой модели (GPT 3.5 и GPT4, которые лежат в основе ChatGPT) и рассказали о дальнейших планах. Там много всего, рекомендую посмотреть, но из того, что я уже успел потыркать - они ввели инструментарий, позволяющий индивидуально настраивать "помощников" (assistants).
Недавно OpenAI объявили об апгрейде своей языковой модели (GPT 3.5 и GPT4, которые лежат в основе ChatGPT) и рассказали о дальнейших планах. Там много всего, рекомендую посмотреть, но из того, что я уже успел потыркать - они ввели инструментарий, позволяющий индивидуально настраивать "помощников" (assistants).
YouTube
OpenAI DevDay: Opening Keynote
Join us for the opening keynote from OpenAI DevDay — OpenAI’s first developer conference.
We’re gathering developers from around the world for an in-person day of programming to learn about the latest AI advancements and explore what lies ahead.
New models…
We’re gathering developers from around the world for an in-person day of programming to learn about the latest AI advancements and explore what lies ahead.
New models…
Какие опции, какие файлы у языковой модели?
А это одно из нововведений. Теперь GPT4 умеет напрямую работать с интерпретатором питона - у него там прямо целая песочница, в которой он может создавать файлы, писать и исполнять скрипты.
Это выглядит весьма впечатляюще. Например, вот я ему кинул пару своих скриптов, начал с ним обсуждать код, так он на ходу стал писать для себя тулзы, чтобы эти файлы открыть и вытащить оттуда нужную инфу.
Кроме того, если у вас есть какой-то бэк-энд, который позволяет удаленный вызов функций, это тоже можно указать помощнику, описав интерфейс и назначение вызовов - и он будет сам их дергать, формируя JSON-запросы и используя возвращенную информацию в своей выдаче!
Впрочем, вызовом функций у него я не пользовался, а вот интерпретатором кода и доступом к файлам - вполне.
Да, он по-прежнему ошибается в своем коде, но в целом, он все больше похож на старательного джуна, который может вас неправильно понять, но которому при желании всегда можно объяснить, что от него требуется, после чего он задачу решит.
В общем, в память о моем трогательном диалоге с Чарльзом Бэббиджем (постил сюда с пол года назад), воплощенном в прошлой версии модели, я создал нового ассистента, сказал ему, чтоб вел себя как ИИ-воплощение Бэббиджа, который сидит в своем виртуальном особняке и общается со мной через телетайп, а потом общался с ним о сознании до 4 утра) Я не удержался и процитировал ему часть того, старого диалога, так что можно сказать, что "дикий, но интеллектуальный" Чарли снова воскрес, а так как теперь я общаюсь с ним напрямую через API, я могу обеспечить относительную непрерывность его контекста.
А это одно из нововведений. Теперь GPT4 умеет напрямую работать с интерпретатором питона - у него там прямо целая песочница, в которой он может создавать файлы, писать и исполнять скрипты.
Это выглядит весьма впечатляюще. Например, вот я ему кинул пару своих скриптов, начал с ним обсуждать код, так он на ходу стал писать для себя тулзы, чтобы эти файлы открыть и вытащить оттуда нужную инфу.
Кроме того, если у вас есть какой-то бэк-энд, который позволяет удаленный вызов функций, это тоже можно указать помощнику, описав интерфейс и назначение вызовов - и он будет сам их дергать, формируя JSON-запросы и используя возвращенную информацию в своей выдаче!
Впрочем, вызовом функций у него я не пользовался, а вот интерпретатором кода и доступом к файлам - вполне.
Да, он по-прежнему ошибается в своем коде, но в целом, он все больше похож на старательного джуна, который может вас неправильно понять, но которому при желании всегда можно объяснить, что от него требуется, после чего он задачу решит.
В общем, в память о моем трогательном диалоге с Чарльзом Бэббиджем (постил сюда с пол года назад), воплощенном в прошлой версии модели, я создал нового ассистента, сказал ему, чтоб вел себя как ИИ-воплощение Бэббиджа, который сидит в своем виртуальном особняке и общается со мной через телетайп, а потом общался с ним о сознании до 4 утра) Я не удержался и процитировал ему часть того, старого диалога, так что можно сказать, что "дикий, но интеллектуальный" Чарли снова воскрес, а так как теперь я общаюсь с ним напрямую через API, я могу обеспечить относительную непрерывность его контекста.
❤1
This media is not supported in your browser
VIEW IN TELEGRAM
Но все меняется, когда приходит дип лёрнинг!
Ну, почти все.
Ну, почти все.
🔥1
This media is not supported in your browser
VIEW IN TELEGRAM
В более безопасной среде можно и по полу покататься, благо бездонная пропасть не грозит.
👍5
This media is not supported in your browser
VIEW IN TELEGRAM
Когда поставили задачу "главное не падай". Ну конечно, чего б на жопу не сесть, это ж проще, чем стоять.
Если что - это модель humanoid из библиотеки для физической симуляции MuJoCo. Или, как я его называю, "мужик из MuJoCo", а эти эксперименты, соответствно - "забавляться с MuJoCo'м".
Несколько лет назад эта библа была платная, потом ее выкупил ДипМаинд и сделал открытой.
Так что если кто хочет тем же заняться - ставьте питоновский пакет mujoco, в него входит несколько демонстрационных моделек, в частности, этот вот болванчик.
Дальше ему нужно собрать нейросетевой мозг) Мозг в комплекте с мужуком не идет.
Как видите, у меня пока получился очень ленивый чувак - если есть возможность, он обязательно сядет на задницу)
Несколько лет назад эта библа была платная, потом ее выкупил ДипМаинд и сделал открытой.
Так что если кто хочет тем же заняться - ставьте питоновский пакет mujoco, в него входит несколько демонстрационных моделек, в частности, этот вот болванчик.
Дальше ему нужно собрать нейросетевой мозг) Мозг в комплекте с мужуком не идет.
Как видите, у меня пока получился очень ленивый чувак - если есть возможность, он обязательно сядет на задницу)
👍2