В эпоху быстрого развития AI и LLM я все чаще вижу, что многие люди думают AI заменят всех программистов и они будут не нужны. Такую мысль высказывал даже CEO Nvidia - Jensen Huang.

Но несмотря на впечатляющие достижения ИИ в области генерации кода, существует фундаментальное ограничение, которое не позволяет им полностью заменить человека в программировании — неспособность к настоящему рассуждению (reasoning).

Иллюзия интеллекта

Когда мы отправляем запрос к LLM, кажется, что он знает ответы на абсолютно все вопросы. Это выглядит магическим, но причина достаточно проста — большие LLM реально обучены на всем интернете. Это трудно вообразить, но в тренировочном наборе LLM действительно есть абсолютно весь интернет. Из-за этого уровня масштаба создается ощущение, что LLM обладает интеллектом. Но важно понимать, что это всего лишь извлечение (retrieval) уже существующих решений или их комбинаций, но никак не решение новых задач.

Source: Andrej Karpathy, Intro to LLM
Source: Andrej Karpathy, Intro to LLM

Тест на способность к рассуждению - Strawberry

Очень простой пример: Сколько букв r в слове strawberry? Любой человек скажет что их 3, но LLM отвечает - 2. Секрет, почему ChatGPT неверно счиатает количество букв R кроется в токенизации. Токены можно представлять как строительный блоки, которые модель использует для понимания и генерации текста. Наличие токенов позволяет быстро и эффективно обрабатывать текст, но при этом не подразумевает у модели наличия способностей к рассуждению.

В токенайзере GPT-4 можно подробно увидеть почему это происходит

С точки зрения AI, "strawberry" - это не последовательность отдельных букв, это также не серия строк [str, aw, berry], а скорее последовательность токен IDs [496, 675, 15717]. LLM несмотря на их кажущуюся магию, следует воспринимать как систему статистического моделирования, выполняющую задачу предсказания следующего токена на основе тренировочных данных. Именно поэтому она и не может посчитать количество r в слове strawberry/

Тест на способность к рассуждению - Алгоритмическая задача

Возьмем простую и достаточно старую задачу для подготовки к алгоритмической сессии почти в любую IT компанию - Invert Binary Tree. Эту задачу легко решить стажер в почти любой компании, и в ее классическом виде ее также решат все LLM.

  • Добавим всего несколько нюансов, чтобы сделать задачу новой, которой не было в обучающей выборке LLM.

    • Нужно инвертировать ключи (перестановка с обращением битов)

    • Решение должно быть чистой рекурсивной функцией без зависимостей

    • Функция должна иметь тип Bit -> Tree -> Tree

Задачу с дополнением этих нюансов решит любой программист, умеющий кодить. При этом ни одна LLM решить ее не может, даже последние версии chatGPT, Claude или Gemini. Решение очень простое и включает всего 7 строчек, но требует способности к рассуждению.

Эти небольшие изменения достаточны, чтобы вывести задачу из "зоны запомненных решений". Ее нет в интернете, и, что самое важное, все современные ИИ не справляются с ней, независимо от того, как их запрашивать.

Ссылка на источник задачи: https://gist.github.com/VictorTaelin/45440a737e47b872d7505c6cda27b6aa

Почему это важно?

  1. Неспособность к оригинальному мышлению: LLM могут комбинировать существующие решения, но не способны создавать действительно новые.

  2. Ограничения в исследованиях: Эта неспособность решать новые задачи означает, что LLM не смогут вносить оригинальный вклад в научные исследования.

  3. Иллюзия понимания: LLM создают впечатление понимания, но на самом деле они лишь воспроизводят комбинации из своей обучающей выборки.

  4. Предел автоматизации: Хотя LLM могут автоматизировать многие задачи, они не способны заменить человека в создании действительно инновационных решений.

Стоит отметить, что LLM в любом случае дают сильный рост продуктивности текущих программистов:

  • Они значительно повысят продуктивность программистов.

  • Вероятно, автоматизируют 99% существующих задач.

  • Окажут влияние, сравнимое с появлением компьютеров и интернета.

Сегодня ключевое ограничение остается: LLM не способны к настоящему рассуждению и созданию оригинальных решений.

Заключение

Понимание принципов работы современного искусственного интеллекта актуально как никогда. Фундаментальное понимание того, как работает AI, поможет вам критически оценивать заявления продавцов AI-продуктов и фильтровать их утверждения о том, что AI полностью заменит программистов.

Среди рекомендуемых материалов для изучения могу выделить

  1. Канал Андрея Карпатого

  2. Курсы Стэнфордского университета по AI и машинному обучению

  3. Курсы Школы Высшей Математики

  4. Курсы на факультете компьютерных наук Высшей школы экономики (ФКН ВШЭ)

Глубокое понимание принципов работы AI поможет вам лучше фильтровать новости и заявления, особенно те, которые исходят от людей, не имеющих глубокого понимания технологии.

P.S. Как early adopter AI - я занимался машинным обучением и NLP задолго до появления ChatGPT. Моя вера в потенциал этих технологий остается непоколебимой. Цель этой статьи - объективно осветить текущие ограничения LLM, признавая при этом их значительный прогресс и потенциал для дальнейшего развития. Это не критика технологии, а скорее анализ ее нынешнего состояния, направленный на стимулирование дискуссии о путях ее совершенствования.

Комментарии (21)


  1. mclander
    14.10.2024 08:09

    С - самоуспокоение?


    1. FurySeer
      14.10.2024 08:09

      Реалистичный взгляд


    1. rk-helper Автор
      14.10.2024 08:09

      Вряд ли самоуспокоение. LLM пишет крутой код для сниппетов, простых задач. Но написать гораздо более сложные вещи, требующие много думать она не может. Мне приходится ускорять инференс ML на работе например - она не понимает, и вряд ли когда-то сможет понять как это работает.


      1. TerraV
        14.10.2024 08:09

        Что такое "вещи, требующие много думать"?


  1. Octabun
    14.10.2024 08:09

    Как вариант. AI точно заменит программистов. Возможно, некоторые программисты переучатся на профессию которая будет называться иначе и продолжат участвовать в создании приложений. Потому, что люди уже обращаются к AI так, как будто он думает, пусть пока и в попытках доказать что AI не думает.


    1. Zenitchik
      14.10.2024 08:09

      Нет. Программисты переучатся на профессию, которая будет по сути другой, но называться будет так же. Такое уже случалось.


    1. rk-helper Автор
      14.10.2024 08:09

      Да, в этом и смысл статьи - может быть писать код и не надо будет. Но иметь reasoning и уметь решать новые проблемы все еще кто-то должен. К сожалению, LLM может это делать только в пределах трэйн сета, а ее трэйн сет это только доступный в интернете текст.


      1. Zenitchik
        14.10.2024 08:09

        Всё равно надо будет. Возможно, на сильно формализованном подъязыке естественного языка и называя его не кодом, а спецификацией.

        Предельно подробное описание того, что должна сделать машина, называется "программа".


        1. rk-helper Автор
          14.10.2024 08:09

          Да, то что вы описали я и имел ввиду. Написание псевдокода для решения задачи, а LLM например сама переведет это в нужный синтаксис и тд.


  1. TerraV
    14.10.2024 08:09

    Как же вы задолбали со своим Strawberry. Мне это напоминает анекдот про японскую бензопилу и лом. У вас блин окно на миллион токенов, вы используете от силы 1000 на вход и потом обвиняете что LLM косячит.

    Скрытый текст

    Да, это действительно связано с тем, как я обрабатываю текст, включая токенизацию. Токенизация — это процесс разбиения текста на отдельные компоненты (токены), такие как слова или символы, которые я затем анализирую.

    Когда ты задал вопрос про количество букв "r" в слове "strawberry", моя первоначальная реакция не была связана с подробной токенизацией каждого символа. Вместо этого я полагался на "быстрое" знание, которое связано с типичными примерами слов. Это сработало, но я мог недооценить ситуацию и не заметил, что буква "r" встречается три раза, а не два.

    Когда ты запросил таблицу частоты букв, я использовал более точный метод, который включает работу с каждым символом слова. Здесь важна символная токенизация, где каждый символ анализируется по отдельности, что исключает вероятность ошибки.

    Таким образом, можно сказать, что первоначальный ответ больше зависел от общей текстовой обработки и моего быстрого "знания", тогда как более точный подсчет частоты символов уже требует детализированной токенизации. Это позволяет мне более точно работать с текстом, когда важно учитывать каждый символ или слово по отдельности.

    Надеюсь, это объяснение помогает лучше понять, как я обрабатываю текст!

    Вместо того чтобы разбираться какие границы применимости есть у технологии, мы огульно скажем - LLM шлак и пойдем рубить дальше.

    Я скажу больше, у меня сейчас ChatGPT 4o пишет код на уровне мидла, o1 preview на уровне сеньора. Просто нужно помнить что узкое место как правило между клавиатурой и сиденьем.


    1. rk-helper Автор
      14.10.2024 08:09

      Мой поинт не в том, что LLM шлак - это супер полезная технология, увеличивающая продуктивность и позволяющая упрощать многие моменты в программирование. Но написать что-то за пределами трэйн сета она не может.


    1. talraaash
      14.10.2024 08:09

      Я скажу больше, у меня сейчас ChatGPT 4o пишет код на уровне мидла

      Я уже постил тут задачу для стажера... добьетесь от LLM её решения? У меня ушло больше трех часов и кучи итераций промтов. В итоге получился почти работающий код. https://habr.com/ru/news/847802/comments/#comment_27370784


      1. TerraV
        14.10.2024 08:09

        В такой постановке не удивительно что вы получаете шлак на выходе. У вас не постановка задачи а какой-то бессвязный поток мыслей. Ответтье мне на следующие вопросы и я дам вам промпт:

        • Что такое header

        • Что такое content

        • Что такое hashtag


        1. talraaash
          14.10.2024 08:09

          Если вы не уловили, то по ссылке - не промт для LLM, а текст для человека. Для LLM писались другие промты, в том числе с примерами того что должно быть в output. А по тексту, вы как человек не смогли вычленить ответы на заданные вопросы?

          • Что такое header

          один с headers[список, контекст вполне читаем], т.е. каждый элемент начиная с # до \n,

          • Что такое content

           второй с headers + content т.е. все что до следующего #

          Тут отчасти согласен, можно было точнее выразится, что conten - все что следует за header, до следующего валидного header т.е. патерна "# Любой символ".

          • Что такое hashtag

          при этом хештеги #tags должны попадать в контент


          1. TerraV
            14.10.2024 08:09

            Как отличить хедер от хэштега, стоящего первым в строке? Будет ли корректно сказать что синтаксис похож на markdown?

            P.S. ща с обеда вернусь и скину промпт


            1. talraaash
              14.10.2024 08:09

              Как отличить хедер от хэштега, стоящего первым в строке?

              Вы серьезно? Различий в "# Header" и "#tags" не видите(если что, правильный паттерн для каждого элемента дополнительно уточнялся в промте для LLM) Там по ссылке даже есть пример петли, в которую попадала LLM и переубедить её, что она не права уже не получалось, приходилось сбрасывать контекст:

              A valid header must have at least one hashtag followed by whitespace and then one or more word characters. "# Hello" does not meet this requirement.


              1. TerraV
                14.10.2024 08:09

                Промпт

                Привет дружище, как насчет написать небольшую программку на javascript? Условия немного мутные, так что надо будет покрошить на человекочитаемые и человекомисправляемые методы чтоб если чё можно было допилить напильником. Вот входные условия: На входе строка, которая на самом деле не строка а множество строк с разделителем. Разделитель \n, но это не точно. Каждая индивидуальная строка может быть либо header (начинается с одного или нескольких #, после чего следует минимум один пробел и дальше какая-то шляпа) либо content - любая строка не попадающая под критерии header. Нужно написать такую функцию, которая на выходе даст два списка. Первый список должен содержать только header'ы, второй должен содержать конкатенации header + весь content ему принадлежащий. Конкатенация должна быть через тот же разедлитель что и оригинальная строка. Когда будешь писать код, постарайся учесть что может поменяться во входном условии и изолировать эти изменения в самостоятельных функциях

                ChatGPT 4o

                Привет! Вот вариант программы на JavaScript, которая отвечает твоим условиям, с учетом гибкости для возможных изменений:

                function parseInput(input, separator = '\n') {
                    // Функция для разделения строки на массив подстрок
                    function splitString(str, sep) {
                        return str.split(sep);
                    }
                
                    // Функция для проверки, является ли строка заголовком
                    function isHeader(line) {
                        return /^#+\s/.test(line);  // Проверка на наличие одного или нескольких # и хотя бы одного пробела
                    }
                
                    // Основная функция для получения двух списков
                    function processInput(input, sep) {
                        const lines = splitString(input, sep);
                        const headers = [];
                        const contentBlocks = [];
                        
                        let currentHeader = null;
                        let currentContent = [];
                
                        lines.forEach(line => {
                            if (isHeader(line)) {
                                // Если был предыдущий заголовок, сохраняем его с контентом
                                if (currentHeader) {
                                    contentBlocks.push(currentHeader + sep + currentContent.join(sep));
                                }
                                // Начинаем новый блок
                                headers.push(line);
                                currentHeader = line;
                                currentContent = [];
                            } else {
                                // Если строка не заголовок, добавляем её к текущему контенту
                                currentContent.push(line);
                            }
                        });
                
                        // Добавляем последний блок контента, если он есть
                        if (currentHeader) {
                            contentBlocks.push(currentHeader + sep + currentContent.join(sep));
                        }
                
                        return { headers, contentBlocks };
                    }
                
                    // Вызов основной функции
                    return processInput(input, separator);
                }
                
                // Пример использования
                const inputString = `
                # Header 1
                Some content under header 1
                Another line of content
                ## Subheader 1.1
                Content for subheader 1.1
                # Header 2
                Content under header 2
                More content under header 2
                `;
                
                const result = parseInput(inputString.trim());
                console.log("Headers:", result.headers);
                console.log("Content blocks:", result.contentBlocks);
                

                Пояснение:

                1. splitString: Функция, которая разделяет входную строку по заданному разделителю (по умолчанию — \n). Это позволяет легко изменить способ разделения строк, если изменится разделитель.

                2. isHeader: Проверяет, является ли строка заголовком. Использует регулярное выражение, которое ищет строки, начинающиеся с одного или нескольких символов #, за которыми следует хотя бы один пробел. Эту проверку можно изменить, если изменятся условия распознавания заголовков.

                3. processInput: Основная функция, которая проходит по каждой строке, разделенной на массив. Сохраняет заголовки в один список, а блоки "заголовок + контент" в другой.

                4. Результат: На выходе функция возвращает два массива — список заголовков и список конкатенированных блоков "заголовок + контент".

                Пример вывода:

                Headers: [ '# Header 1', '## Subheader 1.1', '# Header 2' ]
                Content blocks: [
                  '# Header 1\nSome content under header 1\nAnother line of content',
                  '## Subheader 1.1\nContent for subheader 1.1',
                  '# Header 2\nContent under header 2\nMore content under header 2'
                ]
                

                Такой код легко модифицируется: можно менять условия распознавания заголовков или способ разделения строк, сохраняя общую логику.

                o1 preview

                Привет! Вот вариант программы на JavaScript, которая отвечает твоим условиям, с учетом гибкости для возможных изменений:

                function parseInput(input, separator = '\n') {
                    // Функция для разделения строки на массив подстрок
                    function splitString(str, sep) {
                        return str.split(sep);
                    }
                
                    // Функция для проверки, является ли строка заголовком
                    function isHeader(line) {
                        return /^#+\s/.test(line);  // Проверка на наличие одного или нескольких # и хотя бы одного пробела
                    }
                
                    // Основная функция для получения двух списков
                    function processInput(input, sep) {
                        const lines = splitString(input, sep);
                        const headers = [];
                        const contentBlocks = [];
                        
                        let currentHeader = null;
                        let currentContent = [];
                
                        lines.forEach(line => {
                            if (isHeader(line)) {
                                // Если был предыдущий заголовок, сохраняем его с контентом
                                if (currentHeader) {
                                    contentBlocks.push(currentHeader + sep + currentContent.join(sep));
                                }
                                // Начинаем новый блок
                                headers.push(line);
                                currentHeader = line;
                                currentContent = [];
                            } else {
                                // Если строка не заголовок, добавляем её к текущему контенту
                                currentContent.push(line);
                            }
                        });
                
                        // Добавляем последний блок контента, если он есть
                        if (currentHeader) {
                            contentBlocks.push(currentHeader + sep + currentContent.join(sep));
                        }
                
                        return { headers, contentBlocks };
                    }
                
                    // Вызов основной функции
                    return processInput(input, separator);
                }
                
                // Пример использования
                const inputString = `
                # Header 1
                Some content under header 1
                Another line of content
                ## Subheader 1.1
                Content for subheader 1.1
                # Header 2
                Content under header 2
                More content under header 2
                `;
                
                const result = parseInput(inputString.trim());
                console.log("Headers:", result.headers);
                console.log("Content blocks:", result.contentBlocks);
                

                Пояснение:

                1. splitString: Функция, которая разделяет входную строку по заданному разделителю (по умолчанию — \n). Это позволяет легко изменить способ разделения строк, если изменится разделитель.

                2. isHeader: Проверяет, является ли строка заголовком. Использует регулярное выражение, которое ищет строки, начинающиеся с одного или нескольких символов #, за которыми следует хотя бы один пробел. Эту проверку можно изменить, если изменятся условия распознавания заголовков.

                3. processInput: Основная функция, которая проходит по каждой строке, разделенной на массив. Сохраняет заголовки в один список, а блоки "заголовок + контент" в другой.

                4. Результат: На выходе функция возвращает два массива — список заголовков и список конкатенированных блоков "заголовок + контент".

                Пример вывода:

                Headers: [ '# Header 1', '## Subheader 1.1', '# Header 2' ]
                Content blocks: [
                  '# Header 1\nSome content under header 1\nAnother line of content',
                  '## Subheader 1.1\nContent for subheader 1.1',
                  '# Header 2\nContent under header 2\nMore content under header 2'
                ]
                

                Такой код легко модифицируется: можно менять условия распознавания заголовков или способ разделения строк, сохраняя общую логику.


                1. talraaash
                  14.10.2024 08:09

                  Почти... 4.0 написала слишком строгий regex для header "# Header! 1" >> "# Header 1", о1 тоже не все пограничные случаи отлавливает "#Header ## Subheader 1.1" в output даст не то что ожидается.

                  Не стоит тратить время, на допиливание, если что. Задачу я решил сам, разобравшись в незнакомом мне до этого js за ~час. А цель добиться решения от LLM была, в понимании сможет ли no code мимокрокодил решать прикладные задачи. На данный момент развития технологий ответ для себя я получил.


                  1. TerraV
                    14.10.2024 08:09

                    Примеры батенька, примеры. Выкладывайте тестовый кейс, прогоним и разберемся. Я уверен что код 100% рабочий и покрывает все граничные случаи которые вы явно упомянули. Если у вас в голове сидит кусок неописанной логики, я не экстрасенс чтоб угадывать.


  1. wataru
    14.10.2024 08:09

    Более того, LLM не справляются не только с задачами, которых в интернете нет, но и с достаточно редкими.

    Мой пример: надо с помощью WinAPI MediaFoundations проверить, что камера не работает, потому что она занята другим приложением. Все сетки, что я пробовал выдают разыне вариации из перемешанного DirectShow, выдуманных методов MediaFondations или просто интерпретируют любую ошибку, как "камера занята". В 75% случаев код даже не компилировался. Даже после подсказок использовать IMFActivityMonitor - ни одна сетка не справилась.

    А ответ в интернете даже есть, правда в считанных по пальцам местах. Документация Microsoft и пара примеров на гитхабе.

    А вот задачки с литкода, решение которых растиражированы стонями в интернете, да какие-то сайтики на сверх-популярных фреймворках оно может, да. Оно как трамвай - по построенным путям ездит отлично, но никуда с них свернуть не может. Личный автомобиль/такси оно не заменяет.


  1. gun_dose
    14.10.2024 08:09

    Прежде чем рассуждать не тему, может ли ИИ заменить программиста, нужно понять, что значит "заменить". Давайте представим, например, очень маленькую веб-студию, где есть дизайнер, менеджер и программист. Берём выкидываем прогера, ставим вместо него ИИ. Смогут ли теперь дизайнер и менеджер справиться со всей работой вдвоём без увеличения нагрузки? Думаю, это вопрос риторический