Введение в основные принципы логического программирования [часть 1]
Команда форума подготовила для вас перевод одной очень интересной статьи -> Источник. Приятного чтения!
Логическое программирование — парадигма программирования, основанная на автоматическом доказательстве теорем, а также раздел дискретной математики, изучающий принципы логического вывода информации на основе заданных фактов и правил вывода.(c)Wikipedia
Прежде чем мы поговорим о том, что это такое, и как это относится к искусственному интеллекту, давайте немного поговорим о парадигмах программирования.
Отступление от 5h3ll:
Парадигма программирования, определяет то, как вы будете подходить к решению проблемы. Тут не существует плохой, правильной или не правильной парадигмы, проще всего подобные абстракции, объясняются на примерах из реальной жизни.
Например есть Коля и Петя. Коля строит небольшие сараи на участках, и ход его работ прост, прямолинеен и понятен. Он просто выполняет определенное действие в нужном ему порядке. По сути коля строит сарай используя процедуральную парадигму программирования, причина тому проста — он делал это 1000 раз и тратить время на бесполезное планирование(время деньги) смысла нет.
С другой стороны его друг Петя строит небоскребы в Moscow-City и руководит большим штатом рабочих: архитекторов, юристов, строителей и тд. Понятное дело, что такая большая схема будет крайне неповоротлива, если исполнять все по пунктам. Проблема в том, что пока одно звено не закончит свою функцию — последующее не начнет работу, а это простои работы, финансовые потери ну, и как результат — недовольные инвесторы. Потому лучший выбор для Пети — это: ООП, где все объекты(классы) будут структурированы, заниматься каждый своим делом и в то же время взаимодействовать друг с другом по мере необходимости.
Так что, как мы видим, для каждой проблемы лучше использовать подходящую парадигму.
Вот некоторые из наиболее популярных парадигм программирования:
- Imperative: в этой парадигме используются операторы для изменения состояния программы, что позволяет учитывать побочные эффекты.
- Functional: эта парадигма рассматривает вычисления как оценку математических функций и не позволяет изменять состояния или изменяемые данные.
- Declarative: это способ программирования, при котором вы пишете свои программы путем описывания, что именно вы хотите сделать, а не как вы хотите это сделать. Вы выражаете логику базовых вычислений без явного описания потока управления.
- Object Oriented: в этой парадигме код группируется в программе таким образом, что каждый объект отвечает за себя. Объекты содержат данные и методы, которые определяют, каким именно образом происходят изменения.
- Procedural: в этой парадигме код группируется по функциям, и каждая функция отвечает за определенную последовательность шагов.
- Symbolic: в этой парадигме используется особый стиль синтаксиса и грамматики, с помощью которого программа может изменять свои собственные компоненты, рассматривая их как простые данные.
- Logic: в этой парадигме вычисления рассматриваются как автоматическое обоснование базы данных знаний, состоящей из фактов и правил.
Чтобы понять логическое программирование, давайте разберемся с понятиями вычислений и дедукций. Чтобы что-то вычислить, мы начнем с выражения и набора правил. Этот набор правил в основном программа. Мы используем эти выражения и правила для генерации вывода.
Например, мы хотим вычислить сумму 23, 12 и 49:
Процедура будет выглядеть следующим образом:
23 + 12 + 49 => (2 + 1 + 4 + 1)4 => 84
С другой стороны, если мы хотим что-то сделать, нам нужно начать с гипотезы. Затем нам нужно построить доказательство в соответствии с набором правил. По сути, процесс вычисления является
механическим, тогда как процесс дедукции более творческий. Когда мы пишем программу в логической парадигме программирования, мы указываем набор утверждений,
основанных на фактах и правилах о проблемной области, и решающая программа решает ее, используя эту информацию.
Понимание составных элементов логического программирования
При программировании объектно-ориентированных или императивных парадигм мы всегда должны указывать, как определяется переменная. В логическом программировании все работает немного иначе.
Мы можем передать неопределенный аргумент функции, и интерпретатор припишет значение этих переменных для нас, взглянув на факты, определенные пользователем. Это мощный способ решения проблемы соответствия переменных. Процесс сопоставления переменных с различными элементами называется унификацией. Это одно из мест, где логическое программирование действительно находится в стороне.
Нам нужно указать то, что называется связи/отношения в логическом программировании. Эти связи определяются с помощью положений, называемых фактами и правилами.
Факты — это просто утверждения, которые являются правдой о нашей программе и данных, с которыми она работает. Синтаксис довольно прост. Например, Дональд — сын Аллана, может быть фактом, тогда как, Кто сын Аллана? не может быть фактом.
Каждая логическая программа нуждается в фактах для работы, чтобы она могла достичь поставленной цели на их основе.
Правила — это та информация, которую мы узнали о том каким образом выражать различные факты и как их запрашивать. Это те ограничения, с которыми нам приходится работать, и они позволяют нам делать выводы о проблемной области.
Например, допустим, вы работаете над созданием шахматного движка. Вам нужно указать все правила о том, как каждая фигура может двигаться на шахматной доске. По сути, окончательный вывод действителен только в том случае, если все отношения/связи верны.
Решение проблем с помощью логического программирования
Логическое программирование ищет решения, используя факты и правила. Нам нужно указать цель для каждой программы. В случае, когда логическая программа и цель не содержат каких-либо переменных,
решающая программа создает дерево, которое представляет собой пространство поиска для решения проблемы и достижения цели.
Одна из самых важных вещей в логическом программировании — это то, как мы относимся к правилам. Правила можно рассматривать как логические утверждения. Давайте рассмотрим следующее:
Кэти любит шоколад => Александр любит Кэти
Это можно прочитать как подтекст, который говорит: если Кэти любит шоколад, то Александр любит Кэти. Это также может быть истолковано как то, что Кэти любит шоколад подразумевает, что Александр любит Кэти.
Аналогично, давайте рассмотрим следующее правило:
Криминальные фильмы, английский язык => Martin Scorsese
Это может быть прочитано следующим образом: Если вам нравятся криминальные фильмы на английском языке, то вам понравятся фильмы, снятые Мартином Скорсезе.
Эта конструкция используется в различных формах в логическом программировании для решения различных типов задач. Давайте продолжим и посмотрим, как решить эти проблемы в Python.
Установка пакетов Python
Прежде чем мы начнем логическое программирование на Python, нам нужно установить пару пакетов. Пакет logpy — это пакет Python, который делает доступным логическое программирование на Python.
Мы также будем использовать sympy для решения некоторых проблем. Итак, давайте установим logpy и sympy с помощью pip :
$ pip3 install logpy $ pip3 install sympy
Согласование математических выражений
Мы постоянно сталкиваемся с математическими операциями. Логическое программирование — это очень эффективный способ сравнения выражений и обнаружения неизвестных значений. Давайте посмотрим, как это сделать.
Создайте новый файл Python и импортируйте следующие пакеты:
from logpy import run, var, fact import logpy.assoccomm as la
Определите пару математических операций:
add = 'addition' mul = 'multiplication'
И сложение, и умножение являются коммутативными операциями. Давайте определим их, а именно:
fact(la.commutative, mul) fact(la.commutative, add) fact(la.associative, mul) fact(la.associative, add)
Давайте определим некоторые переменные:
Рассмотрим следующее выражение:
expression_orig = 3 x (-2) + (1 + 2 x 3) x (-1)
Сгенерируем это выражение с замаскированными переменными.
expression1 = (1 + 2 x a) x b + 3 x c
expression2 = c x 3 + b x (2 x a + 1)
expression3 = (((2 x a) x b) + b) + 3 x
Если вы достаточно внимательны, вы заметите, что все три выражения представляют одно и то же основное выражение.
Наша цель — сопоставить эти выражения с исходным выражением для извлечения неизвестных значений:
expression_orig = (add, (mul, 3, -2), (mul, (add, 1, (mul, 2, 3)), -1)) expression1 = (add, (mul, (add, 1, (mul, 2, a)), b), (mul, 3, c)) expression2 = (add, (mul, c, 3), (mul, b, (add, (mul, 2, a), 1))) expression3 = (add, (add, (mul, (mul, 2, a), b), b), (mul, 3, c))
Сравните выражения с исходным выражением. Данный метод обычно используется в logpy . Этот метод принимает входные аргументы и выполняет выражение.
Первый аргумент — это число значений, второй аргумент — это переменная, а третий аргумент — это функция:
print(run(0, (a, b, c), la.eq_assoccomm(expression1, expression_orig))) print(run(0, (a, b, c), la.eq_assoccomm(expression2, expression_orig))) print(run(0, (a, b, c), la.eq_assoccomm(expression3, expression_orig)))
Полный код предоставлен в expression_matcher.py . Если вы запустите код, вы увидите следующий вывод в своем терминале:
Три значения в первых двух строках представляют собой значения для a, b и c. Первые два выражения совпадают с исходным выражением, а третье ничего не возвращает.
Причина в том, что хоть третье выражение математически одинаково, оно структурно отличается. Сравнение шаблонов работает путем сравнения структуры выражений.