В последнее время машинное обучение было в моде, но некоторые из нас познакомились с ним несколько десятилетий назад. Впервые я столкнулся с ним на уроке искусственного интеллекта (в свое время 6.034) в Массачусетском технологическом институте. Когда я проходил курс, его вел Патрик Уинстон, написавший книгу, которая в то время считалась исчерпывающим руководством по искусственному интеллекту. Эта книга до сих пор используется во многих местах, но я уверен, что она была заменена другими книгами, которые расширили идеи и включают больше обсуждений современных фреймворков, которые сделали эту тему более осязаемой.
Основы Схемы/Lisp
Класс преподавался на языке под названием Scheme. Для тех, кто не знаком со схемой, это диалект LISP (также известный как Многомного раздражающих и Глупые Pкруглые скобки). LISP и его диалекты используют круглые скобки для заключения выражений. Синтаксис иногда называют Польская нотация, но в LISP группировка операторов более явна, так что программистам не нужно так много думать о стеке вычислений. Например, это выражение, записанное в польской нотации:
Эти выражения обычно оцениваются справа налево, и интерпретатор, который интерпретирует этот оператор, использует стек. Таким образом, первое, что он делает, это помещает 1 в стек, а затем еще одну 1. Затем он сталкивается с плюсом, поэтому он извлекает обе единицы из стека и добавляет их, помещая 2 в стек. Это означает следующее (числа в квадратных скобках представляют собой стек после каждого шага):
Разработчики постоянно писали подобные выражения, и это было медленно и громоздко, поэтому LISP добавил круглые скобки, чтобы было понятнее, что происходит. Вместо этого приведенное выше выражение можно было бы записать так:
На первый взгляд это может показаться не намного лучше, но вы можете легко увидеть, как все упростилось:
Из-за этого было намного проще писать и понимать, что делает программа на LISP, по сравнению со старым выражением на основе стека.
Приведенный выше пример демонстрирует числовые вычисления в LISP, но также имеет функции. Структуры данных в Scheme состоят из списков. Например, я могу определить переменную x как список первых пяти целых чисел:
автомобиль и cdr
Для доступа к данным внутри этой структуры используются два основных ключевых слова: car и cdr. Эти функции возвращают первый элемент списка и остальную часть списка соответственно. Это означает:
Вы, наверное, заметили, что доступ к определенным элементам списка может быть довольно сложным. Например, чтобы получить 4-й элемент списка (4), нужно сделать так:
Выражение работает в порядке, обратном тому, как оно читается слева направо — вам нужно удалить 1, 2 и 3 с помощью cdr, а затем использовать car, чтобы получить первый элемент списка (4 5). Достаточно сказать, что этот язык появился во времена, когда языки программирования были гораздо более примитивными.
Лямбда-выражения
Однако в языке Scheme были некоторые концепции, которые только сейчас вновь появляются в языках программирования. Один из таких примеров называется лямбда-выражение. Эти выражения были введены в Java в версии 1.8 JDK, но на самом деле они существуют как программная конструкция уже более 30 лет.
Лямбда-выражение — это, по сути, выражение, которое не оценивается немедленно, а вместо этого требует, чтобы для оценки были предоставлены значения для заполнителей. Например, я могу определить лямбда-выражение в схеме и установить его в переменную:
По сути, это определяет выражение, которое суммирует входные данные. Это похоже на определение функции, за исключением того, что она фактически сохраняется в переменной и может передаваться (указатель на функцию является хорошей аналогией, но лямбда-выражения больше похожи на данные, которые могут храниться в стеке как в отличие от простого указания на некоторую инструкцию в пространстве данных программы). Затем вы можете оценить лямбда-выражение следующим образом:
Так зачем кому-то это делать? Хорошим примером является фабричный шаблон. Скажем, я хочу создать пользовательское выражение, которое умножает один аргумент на множитель, являющийся параметром фабрики. Например:
Сама фабрика представляет собой лямбда-выражение, которое возвращает другое лямбда-выражение. Я мог бы сделать тройной множитель и использовать его так:
Или представьте еще более сложный пример, где фабрика создает выражение, выполняющее любую операцию с параметром:
Это простой пример, но вы можете представить себе построение выражений, которые эффективно вычисляют выражения на основе параметров конфигурации. В мире машинного обучения мы часто выполняем сложные вычисления с параметрами, требующими настройки, поэтому нам нужен был способ определить вычисления с заполнителями, которые можно было бы изменить по мере обучения сети, чтобы она была более точной.
Python, конечно, позволяет нам создавать фабричные методы, но принципы, по сути, те же:
Python также имеет лямбда-выражения (это простой пример, но тело лямбда-выражения может быть таким же сложным, как и любой метод):
Вывод
Эта статья была для меня прогулкой по переулку воспоминаний. Когда я впервые начал программировать в Массачусетском технологическом институте, это было странно и необычно, но теперь, оглядываясь назад, я понимаю, что принципы, которые я усвоил тогда, по-прежнему лежат в основе того, что я делаю сейчас. Мне очень повезло, что я смог побывать в месте, где меня так рано научили этим основам, и теперь их завоевывает весь остальной мир. Схема на самом деле уже не так широко используется, но если вы покопаетесь, она все еще существует. Схема 9.2 была выпущена еще в 2014 году.
Первоначально опубликовано 18 декабря 2017 г.