Основы методов искусственного интеллекта. Учебно-методическое пособие
.pdfnn.Linear(hidden _ size, 1)
)
# Метод возвращает вероятности для всех действий и оценку ценности состояния
def forward(self, state): probs = self.actor(state) value = self.critic(state) return probs, value
#Инициализируем среду и гиперпараметры env = gym.make(‘CartPole-v0’)
input _ size = env.observation _ space.shape[0] output _ size = env.action _ space.n
hidden _ size = 128 lr = 0.001
gamma = 0.99
#Инициализируем сети Агента и Критика и оптимизатор
model = ActorCritic(input _ size, output _ size, hidden _ size) optimizer = optim.Adam(model.parameters(), lr=lr)
# Запускаем обучение num _ episodes = 1000
for i in range(num _ episodes): state = env.reset()
done = False while not done:
# Получаем вероятности действий и оценку ценности текущего состояния
probs, value = model(torch.FloatTensor(state))
# Выбираем действие на основе вероятностей
action = torch.distributions.Categorical(probs).sample()
# Применяем выбранное действие и получаем новое состояние
и награду
new _ state, reward, done, _ = env.step(action.item())
# Получаем оценку ценности нового состояния
new _ value = model(torch.FloatTensor(new _ state))
# Вычисляем ошибку для Критика
td _ target = reward + gamma * new _ value * (1 - done)
31
td _ error = td _ target - value critic _ loss = td _ error.pow(2)
#Обновляем веса Критика optimizer.zero _ grad() critic _ loss.backward() optimizer.step()
#Вычисляем ошибку для Агента advantage = td _ target - value.detach()
actor _ loss = -torch.log(probs[action]) * advantage
#Обновляем веса Агента optimizer.zero _ grad() actor _ loss.backward() optimizer.step()
#Обновляем текущее состояние и ценность состояния state = new _ state
value = new _ value
#Выводим текущий результат
print(“Episode {}: Reward = {}”.format(i+1, reward)
В данном примере использована среда CartPole, где нужно управлять тележкой, сбалансировав на ней шест. Определяется архитектура сети Агента и Критика, их инициализация, а также оптимизатор. Затем запускается цикл обучения, где на каждой итерации выбирали действие, применяли его в среде, вычисляли ошибки для Агента и Критика, обновляли веса сетей и переходили в следующее состояние.
Таким образом, Actor-Critic — это мощный метод обучения с подкреплением, который позволяет достичь высокой эффективности за счет комбинации двух подходов. Однако для его эффективной работы необходимо тщательно настраивать гиперпараметры и архитектуру сетей.
5. Экспертные системы.
Это системы искусственного интеллекта, которые используют методы представления знаний и рассуждений для решения сложных проблем в определенной области.
Экспертные системы обычно разрабатываются для имитации способности человека-экс- перта решать проблемы в определенной области.
Одной из ключевых особенностей экспертных систем является их способность принимать решения на основе большого количества знаний, которые хранятся в базе знаний. Эти знания могут быть записаны в виде правил, которые описывают, как экспертная система должна ре - шать конкретную проблему, или же в виде фактов, которые представляют информацию об объектах в области, с которой связана система.
Для создания экспертных систем на языке программирования Python можно использовать различные библиотеки и фреймворки, например, PyKnow, CLIPS и другие.
32
Пример экспертной системы на языке Python с использованием фреймворка PyKnow, которая может помочь в диагностике автомобильных проблем:
from pyknow import *
# Определение фактов class Car(Fact):
pass
class Problem(Fact): pass
# Определение правил
class CarExpert(KnowledgeEngine):
@Rule(Car(type=‘sedan’)) def check _ engine(self):
self.declare(Problem(type=‘engine’, description=‘Check engine light is on.’))
@Rule(AND(Car(type=‘sedan’), Problem(type=‘engine’, description=‘Check |
|
engine |
light is on.’))) |
|
def check _ oil(self): |
low.’)) |
self.declare(Problem(type=‘oil’, description=‘Oil level is |
@Rule(Car(type=‘SUV’))
def check _ transmission(self):
self.declare(Problem(type=‘transmission’, description=‘Transmission is slipping.’))
@Rule(AND(Car(type=‘SUV’), Problem(type=‘transmission’, description=‘Transmission is slipping.’)))
def check _ fluid(self):
self.declare(Problem(type=‘fluid’, description=‘Transmission fluid level is low.’))
@Rule(OR(Problem(type=‘oil’, description=‘Oil level is low.’), Problem(type=‘fluid’, description=‘Transmission fluid level is low.’)))
def add _ fluid(self): print(‘Add more fluid.’)
# Создание экспертной системы и запуск engine = CarExpert()
33
engine.reset()
engine.declare(Car(type=‘sedan’))
engine.run()
В этом примере мы определяем факты отипе автомобиля ипроблемах, которые могут возникнуть у автомобиля. Мы также определяем правила вывода для определения, какие проблемы могут возникнуть в зависимости от типа автомобиля и какие действия необходимо предпринять для их устранения.
Запуск экспертной системы выводит сообщение о том, что необходимо добавить больше жидкости, если обнаружены проблемы с маслом или жидкостью трансмиссии.
6. Генетические алгоритмы.
Это алгоритмы оптимизации, которые используют принципы естественного отбора для эволюции популяции кандидатов на решение проблемы.
Генетические алгоритмы часто используются для решения задач оптимизации, в которых существует множество возможных решений, а оптимальное решение неизвестно.
Основная идея генетических алгоритмов заключается в создании популяции кандидатов на решение задачи и последующем их «размножении», и «мутации» для создания новых поколений, которые будут находиться ближе к оптимальному решению задачи.
Алгоритм генетической оптимизации можно разбить на несколько шагов:
a)инициализация начальной популяции кандидатов на решение задачи. Каждый кандидат представляется в виде набора параметров, которые определяют решение задачи;
b)оценка каждого кандидата популяции на основе функции приспособленности. Функция приспособленности определяет, насколько хорошо решение задачи, представленное кандидатом, соответствует оптимальному решению;
c)выбор наиболее приспособленных кандидатов для «размножения», когда вероятность выбора каждого кандидата зависит от его функции приспособленности;
d)создание новых кандидатов путем «скрещивания» выбранных родителей, при этом параметры потомков могут быть сформированы путем комбинации параметров родителей;
e)формирование новой популяции, которая состоит из выбранных родителей и созданных потомков, которое может осуществляться различными методами, например, путем замены наименее приспособленных кандидатов потомками или путем выбора наилучших кандидатов из общей популяции;
f)внесение случайных изменений в параметры кандидатов (мутация), что позволяет создавать новых кандидатов, которые не находятся в пространстве параметров, покрытом родительскими кандидатами;
g)оценка новой популяции и повторение шагов c–e до тех пор, пока не будет достигнуто определенное условие остановки (например, максимальное количество поколений, достижение определенной точности решения).
Пример реализации генетического алгоритма:
import random
# Определение начальной популяции
def init _ population(population _ size, param _ range): population = []
for i in range(population _ size): candidate = []
for j in range(len(param _ range)):
34
candidate.append(random.uniform(param _ range[j][0], param _ range[j][1]))
population.append(candidate) return population
# Функция приспособленности
def fitness _ function(candidate):
#Здесь может быть любая функция, которую необходимо оптимизи-
ровать.
#В данном примере мы будем использовать простую функцию, возвращающую сумму квадратов параметров кандидата.
return sum([x ** 2 for x in candidate])
# Выбор наиболее приспособленных кандидатов
def selection(population, fitness _ values, num _ parents):
# Выбираем num _ parents кандидатов с наибольшей функцией приспособленности
parents = []
for i in range(num _ parents):
max _ fitness _ index = fitness _ values.index(max(fitness _
values))
parents.append(population[max _ fitness _ index]) fitness _ values[max _ fitness _ index] = -99999999
# “Удаляем” выбранного кандидата из списка, установив его функцию приспособленности на минимальное значение.
return parents
# “Скрещивание” выбранных родителей def crossover(parents, offspring _ size):
offspring = []
for i in range(offspring _ size): offspring _ candidate = []
for j in range(len(parents[0])):
# Для каждого параметра случайным образом выбираем, от какого родителя берем значение.
offspring _ candidate.append(random.choice([parents[0][j], parents[1][j]]))
offspring.append(offspring _ candidate) return offspring
# Мутация параметров кандидатов def mutation(offspring):
for i in range(len(offspring)):
35
for j in range(len(offspring[i])):
# С вероятностью 0.01 заменяем значение параметра на случайное число из диапазона
if random.random() < 0.01:
offspring[i][j] = random.uniform(-10, 10) return offspring
# Определение оптимального решения с помощью генетического алгоритма
def genetic _ algorithm(population _ size, param _ range, num _ parents, num _ generations):
# Инициализация начальной популяции
population = init _ population(population _ size, param _ range) for generation in range(num _ generations):
# Вычисляем функцию приспособленности для каждого кан-
дидата
fitness _ values = [fitness _ function(candidate) for candidate in population]
# Выбираем наиболее приспособленных кандидатов
parents = selection(population, fitness _ values, num _
parents)
# “Скрещиваем” родителей, чтобы создать новых кандида-
тов
offspring = crossover(parents, population _ size -
num _ parents)
# Вносим случайные изменения в параметры канди-
датов
offspring = mutation(offspring)
# Объединяем родителей и потомков для создания новой
популяции
population = parents + offspring
# Вычисляем функцию приспособленности для каждого кандидата
fitness _ values = [fitness _ function(candidate) for candidate in population]
# Возвращаем индекс кандидата с наилучшей функцией приспособленности и сам кандидат
best _ candidate _ index = fitness _ values.index(max(fitness _ values))
best _ candidate = population[best _ candidate _ index] return best _ candidate
36
Пример использования генетического алгоритма для оптимизации функции f(x, y) =
= x^2 + y^2: |
|
|
|
import random |
|
|
|
def init _ population(population _ size, param _ range): |
|
||
# Создаем популяцию случайных кандидатов |
|
||
population = [] |
|
|
|
for i in range(population _ size): |
|
||
candidate |
= |
[random.uniform(param _ range[0], |
param _ |
range[1]), random.uniform(param _ range[0], param _ range[1])] population.append(candidate)
return population
best _ candidate = genetic _ algorithm(population _ size=100, param _ range=(-10, 10), num _ parents=20, num _ generations=100)
print(“Оптимальное решение:”, best _ candidate, “Функция приспосо-
бленности:”, fitness _ function(best _ candidate))
Результат:
Оптимальное решение: [3.773587045830752e-05, –6.218285158569244e-06]
Функция приспособленности: 1.4176155071870507e-09
В этом примере мы оптимизировали функцию f(x, y) = x^2 + y^2 с использованием генетического алгоритма.
Мы начали с создания начальной популяции случайных кандидатов, затем на каждой итерации генетического алгоритма выбирали наиболее приспособленных кандидатов, «скрещивали» их, вносили случайные изменения в параметры кандидатов и создавали новую популяцию.
После заданного количества итераций найдено оптимальное решение — кандидата с наилучшей функцией makefile.
7. Удовлетворение ограничений.
Этот метод предполагает поиск решения проблемы, которое удовлетворяет набору ограничений.
Примерами методов удовлетворения ограничений являются обратный путь, проверка вперед и согласованность дуг.
Обратный путь (backtracking) — это алгоритм поиска пути, который используется в графах для нахождения всех возможных путей от начальной вершины к конечной. Алгоритм работает по следующей схеме:
a)начиная с начальной вершины, мы рассматриваем все исходящие из нее ребра;
b)если какое-то ребро ведет к конечной вершине, то мы добавляем это ребро в путь и заканчиваем поиск;
c)если же какое-то ребро не ведет к конечной вершине, то мы рекурсивно переходим к следующей вершине и продолжаем поиск.
Для примера рассмотрим граф, заданный следующей матрицей смежности:
graph = [[0, 1, 1, 0], [1, 0, 1, 1], [1, 1, 0, 1], [0, 1, 1, 0]]
37
В этом графе есть четыре вершины и четыре ребра. Найдем все возможные пути от вершины 0 к вершине 3 с помощью обратного пути:
def find _ path(graph, start, end, path=[]): path = path + [start]
if start == end: return [path]
paths = []
for node in range(len(graph)):
if graph[start][node] == 1 and node not in path: newpaths = find _ path(graph, node, end, path)
for newpath in newpaths: paths.append(newpath)
return paths
paths = find _ path(graph, 0, 3) print(paths)
Результат выполнения программы будет следующим:
[[0, 1, 2, 3], [0, 2, 1, 3], [0, 1, 3], [0, 2, 3]]
Это все возможные пути от вершины 0 к вершине 3 в данном графе.
Для примера использования проверки вперед и согласованности дуг рассмотрим задачу о раскраске графа.
Допустим, мы хотим раскрасить граф в три цвета таким образом, чтобы никакие две смежные вершины не имели одинаковый цвет. Для решения этой задачи мы можем использовать алгоритм поиска пути с проверкой вперед и согласованностью дуг.
Ниже приведен код, для решения этой задачи:
def is _ valid(graph, colors, node, c): for i in range(len(graph)):
if graph[node][i] == 1 and colors[i] == c: return False
return True
def graph _ coloring(graph, colors, node): if node == len(graph):
return colors
for c in range(len(colors)):
if is _ valid(graph, colors, node, c): colors[node] = c
result = graph _ coloring(graph, colors, node + 1) if result is not None:
return result colors[node] = None
38
graph = [[0, 1, 1, 1], [1, 0, 1, 0], [1, 1, 0, 1], [1, 0, 1, 0]]
colors = [None] * len(graph)
result = graph _ coloring(graph, colors, 0) print(result)
Пример вывода данных:
[0, 1, 2, 1]
В этом коде используется функция is _ valid для проверки, можно ли закрасить вершину node цветом c, учитывая уже закрашенные вершины в массиве color и матрицу смежности graph. Затем мы используем рекурсивную функцию graph _ coloring, которая закрашивает вершины в порядке их появления и проверяет с помощью is _ valid, можно ли закрасить вершину определенным цветом. Если все вершины закрашены, функция возвращает массив цветов. Если невозможно закрасить вершину определенным цветом, функция возвращает None.
Можно протестировать этот код на графе счетырьмя вершинами, где каждая вершина связана с каждой другой вершиной:
graph = [[0, 1, 1, 1], [1, 0, 1, 1], [1, 1, 0, 1], [1, 1, 1, 0]]
colors = [None] * len(graph)
result = graph _ coloring(graph, colors, 0) print(result)
Результат выполнения программы будет следующим:
[0, 1, 2, 0]
Это означает, что можно раскрасить граф в три цвета таким образом, чтобы никакие две смежные вершины не имели одинаковый цвет. В данном случае мы выбрали цвета 0, 1 и 2 для вершин 0, 1 и 2 соответственно, а затем выбрали цвет 0 для вершины 3, чтобы избежать конфликтов с вершинами 0 и 1.
Проверка вперед (forward checking) — это техника, используемая в алгоритмах поиска пути, которая позволяет уменьшить количество возможных вариантов для следующего шага. Вместо того чтобы исследовать все возможные варианты для следующей вершины, мы проверяем, какие вершины доступны, исходя из уже выбранных вершин и ребер. Если какая-то вершина недоступна, то мы исключаем ее из дальнейшего рассмотрения.
Вот пример реализации алгоритма раскраски графа с использованием проверки вперед на языке Python:
39
def graph _ coloring(graph, colors, vertex): if vertex == len(graph):
return colors
available _ colors = set(range(len(graph)))
for i in range(len(graph)):
if graph[vertex][i] == 1 and colors[i] in available _ colors: available _ colors.remove(colors[i])
for color in available _ colors: colors[vertex] = color
result = graph _ coloring(graph, colors, vertex + 1) if result is not None:
return result
colors[vertex] = None return None
Пример вывода данных:
[0, 1, 2, 1]
Эта функция принимает матрицу смежности графа, список цветов для вершин (изначально заполненный значениями None) и номер текущей вершины.
На каждом шаге алгоритма функция определяет, какие цвета могут быть использованы для текущей вершины, и для каждого доступного цвета рекурсивно вызывает саму себя для следующей вершины.
Если на каком-то шаге не остается доступных цветов, функция откатывается назад и выбирает другой цвет для предыдущей вершины.
Мы можем использовать эту функцию для раскраски графа, заданного матрицей смежности:
graph = [[0, 1, 1, 0], [1, 0, 1, 1], [1, 1, 0, 1], [0, 1, 1, 0]]
colors = [None] * len(graph)
result = graph _ coloring(graph, colors, 0) print(result)
Этот код должен вывести список цветов для каждой вершины графа, такой как[0, 1, 2, 0], что означает, что мы можем раскрасить граф в три цвета таким образом, чтобы никакие две смежные вершины не имели одинаковый цвет.
40
