Как стать автором
Обновить

Выразить иерархически: вопрос как увидеть хамелеона

Время на прочтение9 мин
Количество просмотров939

Проблема нейросетей - невозможность обучаться на единичных примерах. Справиться может табличное RL, но обучаться на данных большой размерности - иная неразрешимая сторона этой парадигмы https://habr.com/ru/post/437020/. Решение только в одном: видеть мир иерархически, где каждая его подчасть также может быть выражена иерархически.

Как здесь https://habr.com/ru/post/420219/ - сложность игры которой сокращается за счет введения небоевого состояния, подсостояниями которого являются ожидание и патрулирование.

Хабр. Обзор техник реализации игрового ИИ
Хабр. Обзор техник реализации игрового ИИ

Теперь маштабируем этот подход. На этот счет интересны рассуждения В.Турчина

Турчин // Феномен науки. Кибернетический подход к эволюции
Турчин // Феномен науки. Кибернетический подход к эволюции

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

Вот что пишет об этом А. Карпатый https://habr.com/ru/post/439674/. Как только вы поймете «хитрость», с помощью которой работают эти алгоритмы, вы сможете понять их сильные и слабые стороны. В частности, эти алгоритмы далеко отстают от людей в построении абстрактных представлений об играх.

Так каков критерий видеть эти отдельности? Попытки представить предметы и явления как результат алгоритмов, например, свертки - лишь частный случай более фундаментального подхода.

Р. Эшби .//Введение в кибернетику [стр 63]
Р. Эшби .//Введение в кибернетику [стр 63]

Пусть имеется мячик (синий квадрат) как в игре пинг-понг, летящий по некой траектории.

Как мы понимаем, что это отдельная сущность, а не множество синих мячей появляющихся и исчезающих в разных точках пространства? Контраст предмета синий и белого фона? А если этот предмет будет менять цвет, например на случайно выбранные синий, серый (код ниже, BALL_CHANGE_BG = True)? Вроде на фоне белого же. Но что если фоном будет случайно выбранные цвета?

Из трассировок видно, например, что из состояния (1,2) следует как состояние (1,3), так и состояния (2,3), (0,3). По этой причине невозможно (если переходы равновероятны) поймать синий мячик из этого состояния. Выигрыш не более 1/3, если основываться только на политике без учета предшествующих состояний.

Можно попытаться найти такое действие, которое бы приводило к желаемой однозначности. Им является предшествующее состояние. Тогда: (1,2) перейдет однозначно в (1,3) при действии (1,1); (1,2) перейдет только в (2,3) при действии (0,1); (1,2) перейдет только в (0,3) при (2,1).

Всегда ли можно найти однозначные преобразования, подобрав необходимые действия? Нет конечно. Но к этому нужно стремится. Ниже 4 фрагмента.

  1. Действие неизвестно. По умолчанию оно одно (условно f). Коэффициент однозначности 0.6

  2. Вносим неустраняемую неопределенность отскока от черного квадрата. Коэффициент будет ниже.

  3. Выбраны верные действия. Коэффициент единица (однозначность сто процентов),. Теперь, зная граф переходов, безошибочно вычисляется куда прилетит синий мяч.

  4. Добавляем неопределенность к верным действиям. Из состояния (1.2) она максимальна из-за черного квадрата (неопределенный отскок)

Так как же можно разглядеть явления, которые могут как влиять друг на друга, так и быть иерархически выстроены?

Ниже лабиринт (Л) - это одно из сменяемых состояний {11, 12, 14, 15} в результате действий {31, 32}. В свою очередь, состояние лабиринта ключ (11) тоже выражено сменой своих подсостояний {45, 44, 41} в результате действий {35, 36, 34}. И состояние лабиринта дверь (12) управляется ключом (11). Управляется по той причине, что состояния ключа являются действиями для двери. Когда подсостояние двери принимает значение открыто (54), агент может из лабиринта (Л) перейти в комнату (км) и найти клад (63). В отдельно взятой таблице поиск оптимального пути может осуществляться средствами RL.

Но! В начале игры агент ничего не ведает, что есть клад. Не ведает что есть эти отдельности (лабиринт, ключ, двер, комната). Он лишь наблюдает поток данных. Сначала [32, 52, .., 52, ..], затем [.., .., 44, .. 36] и т.д. Рассматривает разные гипотезы, стремясь максимизировать коэффициент однозначности. Так, рассмотрев черную гипотезу, приходит к выводу, что есть некая сущность (11), состояниями которой являются {45, 44, 41}. Обнаруживает также однозначность переходов сущности (12). И далее уже о сущности (Л) тоже мыслит как о какой-то отдельности, переходы состояний которой однозначны. По той простой причине, что для красной гипотезы (попытка понять что есть лабиринт) состояния {44, 41, 45} заменятся на (11), а состояния {52, 53, 54} заменятся на (12).

По Турчину, (11) и (12) являются классификаторами нижнего уровня для классификатора (Л) более верхнего уровня, образуя иерархию понятий.

Ниже представлен код для игры в понг, но без части максимизации однозначности и без оптимизации маршрута по графу (состояние и действие мяча, состояние и действие ловящего мяч) . Без комментариев.

# Выразить иерархически: вопрос как увидеть хамелена
from tkinter import *
import random


class Hd():
	def __init__(self, graph):
		self.graph = graph
		
	def rnd_get(self, v):
		return random.choice(v.split("|"))
		
	def show(self, graph):
		for x in graph:
			print(x, ":", graph[x])
			
	def get_ku(self, graph):
		'''return (коэффициент однозначности, размер)'''
		n = 0; u = 0
		for x in graph:
			for y in graph[x]:
				if y[0]:
					u += y[0].count("|")
					n += 1
		if n != 0:
			return (round(1- u/(n+u), 3), n+u) 
		else:
			return (None, None)
			
	def get_states(self, x, f_list = [], n=11):
		x_list = []
		x_list.append(x)
		
		if f_list != []:
			for f in f_list:
				xf = [xf for xf in g[x] if xf[1] == f]
				if not xf:
					x_list.append(''); f_list[len(x_list)-2] = ''
					return (x_list, f_list[:len(x_list)-1])
				x = self.rnd_get(xf[0][0])
				x_list.append(x)
		else:
			for i in range(n):
				if not self.graph[x]:
					x_list.append(''); f_list.append('')
					break
				t = random.choice(self.graph[x])
				f = t[1]
				x = random.choice(t[0].split('|'))
				x_list.append(x); f_list.append(f)
			
		if len(f_list) == len(x_list) -1:
			return (x_list, f_list)
		else:
			return ([], [])
			
	def get_yfx(self, f_list, x_list, t = True):
		if len(x_list) == len(f_list):
			x_list.append('')
			
		path = []
		for i in range(len(f_list)):
			path.append((x_list[i], f_list[i], x_list[i+1]))
		
		if t:	
			return path #(x, f, next_x)
		else:
			p = []
			for xfy in path:
				if xfy[2] != '':
					p.append(xfy[2]+'='+xfy[1]+'('+xfy[0]+')')
			return p
			
	def flow(self, path, rnd=False):
		if not path:  return []
		fl = []
		for p in path:
			if not rnd:
				fl.append(p[:-1])
			else:
				pr = list(p[:-1])
				#random.shuffle(pr)
				fl.append(tuple(pr))
				fl.append(pr)
		return fl
		
	def fORx(self, flow, f=0):
		'''index: f=0, x=1 or x=0, f=1'''
		def add_empty():
			empty = []
			for k in graph:
				for x in graph[k]:
					z = list(set(x[0].split('|')) - set(list(graph.keys())))
					if z:
						empty.append(z[0])
			return empty
			
		if not flow: return []
		
		graph = {}
		fli = flow[0]
		
		for t in flow[1:]:
			if f == 0:
				if fli[1] not in graph:
					graph[fli[1]] = []
				r = [(i, xf) for i,xf in enumerate(graph[fli[1]]) if xf[1] == fli[0]]
				if r:
					if t[1] not in r[0][1][0]:
						x_new = r[0][1][0] + "|" + t[1]
						if x_new != '':
							graph[fli[1]][r[0][0]] = (x_new, r[0][1][1])
				if not r:
					if t[1] != '':
						graph[fli[1]].append((t[1], fli[0]))
					
			if f == 1:
				if fli[0] not in graph:
					graph[fli[0]] = []
				r = [(i, xf) for i,xf in enumerate(graph[fli[0]]) if xf[1] == fli[1]]
				if r:
					if t[0] not in r[0][1][0]:
						x_new = r[0][1][0] + "|" + t[0]
						if x_new != '':
							graph[fli[0]][r[0][0]] = (x_new, r[0][1][1])
				if not r:
					if t[0] != '':
						graph[fli[0]].append((t[0], fli[1]))
			fli = t
				
		em = add_empty()
		if em:
			graph[em[0]] = []
		return graph
		
	def merge(self, g1, g2):
		def find_index():
			for i in range(len(graph[k])):
				if graph[k][i][1] == x[1]:
					return i
			return -1
					
		all_keys = set(list(g1.keys()) + list(g2.keys()))
		graph = {}
		
		for k in all_keys:
			if k not in graph: 
				if k in g1:
					graph[k] = g1[k]
				else:
					graph[k] = g2[k]
					
			if k in g2:
				for x in g2[k]:
					ind = find_index()
					if ind != -1:
						if x[0] != []:
							t1 = x[0].split('|')
						if graph[k][ind] != -1:
							t2 = graph[k][ind][0].split('|')
						z = "|".join(set(t1+t2))
						#xf = [z, graph[k][ind][1]]
						xf = (z, graph[k][ind][1])
						graph[k][ind] = xf
					else:
						graph[k].append(x)
				
		return graph


class Wednesday():
    def __init__(self, root, maps, ban):
        self.root = root
        self.maps = maps
        self.ban = ban
        Wednesday.time = {}
        Wednesday.hierarchy = {}
        Wednesday.neighbor = {}
          
        self.init_3_dict()
        
    def init_3_dict(self):
        def is_en(yx):
            if yx[0] < 0 or yx[0] > len(self.maps)-1: return
            if yx[1] < 0 or yx[1] > len(self.maps[0])-1: return
            if yx in self.ban: return
            return True
            
        for y in range(len(self.maps)):
            for x in range(len(self.maps[0])):
                Wednesday.hierarchy[(y,x)] = self.maps[y][x]
                
                n = []
                if is_en((y-1,x)):
                    n.append((y-1,x))
                if is_en((y+1,x)):
                    n.append((y+1,x))
                if is_en((y,x-1)):
                    n.append((y,x-1))
                if is_en((y,x+1)):
                    n.append((y,x+1))

                if is_en((y-1,x-1)):
                    n.append((y-1,x-1))
                if is_en((y-1,x+1)):
                    n.append((y-1,x+1))
                if is_en((y+1,x+1)):
                    n.append((y+1,x+1))
                if is_en((y+1,x-1)):
                    n.append((y+1,x-1))
                    
                Wednesday.neighbor[(y,x)] = n
                
                Wednesday.time[(y,x)] = 0
                if (y, x) in self.ban:
                    lb = Label(text=" ", background = 'black')
                    lb.configure(text=self.maps[y][x])
                else:
                    bg = "white"
                    if CHANGE_BG:
                        bg = random.choice(["yellow", "red", "white", "brown", "pink"])
                    lb = Label(text=" ", background = bg)
                    lb.configure(text=self.maps[y][x])
                lb.grid(row=y, column=x, ipadx=15, ipady=10, padx=1, pady=1)
               
                    
    def update(self):
        for y in range(len(self.maps)):
            for x in range(len(self.maps[0])):
                if Wednesday.time[(y,x)]:
                    Wednesday.time[(y,x)] -=1
                else:
                   Wednesday.hierarchy[(y,x)] = self.maps[y][x] 
                   
                if not (y, x) in self.ban:
                    bg = "white"
                    if CHANGE_BG:
                        bg = random.choice(["yellow", "red", "white", "brown", "pink"])
                    lb = Label(text=" ", background = bg)
                    lb.configure(text=Wednesday.hierarchy[(y,x)])
                    lb.grid(row=y, column=x, ipadx=15, ipady=10, padx=1, pady=1)
                       
        self.root.after(TIME, self.update)

class Base():
    def __init__(self, root, color, node):
        self.root = root
        self.color = color
        self.y = node[0]
        self.x = node[1]
        self.visit = {}
        self.neighbor = {}
        self.count = 0
     
    def show(self, y, x, color):
        lb = Label(text=" ", background = color)
        try:
            lb.configure(text=Wednesday.hierarchy[(y,x)] )
            lb.grid(row=self.y, column=self.x, ipadx=15, ipady=10, padx=1, pady=1)
        except:
            pass
        
    def add_neighbor(self):
        try:
            self.neighbor[(self.y, self.x)] = Wednesday.neighbor[(self.y, self.x)]
        except:
            pass
             
    def choice(self, yx):
        self.add_neighbor()
        return random.choice(self.neighbor[yx])
            
    def move(self):     
        yx = self.choice((self.y, self.x))
        self.redraw(yx)
        self.count +=1
  
    def redraw(self, yx):
        self.show(self.y, self.x, 'white')  
        self.y = yx[0]
        self.x = yx[1]
        self.show(yx[0], yx[1], self.color)
        
    def update(self):
        self.move()   
        self.root.after(TIME, self.update)
        
    def env(self):
        return (self.color, (self.y, self.x))


class WhoBeats(Base):
    def __init__(self, root, color, node, lenYX):
        super().__init__(root, color, node)
        self.lenYX = lenYX
        self.balls = []

    def choice(self, yx):
        self.add_neighbor()
        
        if self.count % (self.lenYX[1] +PAUSE)  == 0:
            y = yx[0] + random.choice([-1, 0, 1])
            if y < 0:
                y = 0
            if y > self.lenYX[0] -1:
                y = self.lenYX[0] -1
            dy = y-yx[0]
            ball = Ball(root, "blue", (y, yx[1]), self.lenYX, dy)
            self.balls.append(ball)
            ball.update()

        yx = list(random.choice(self.neighbor[yx]))
        yx[1] = 0
    
        return tuple(yx)
        
    def get_balls(self):
        for ball in self.balls:
            pass
        return self.balls


class Ball(Base):
    def __init__(self, root, color, node, lenYX, dy):
        super().__init__(root, color, node)
        self.lenYX = lenYX
        self.dy = dy

    def choice(self, yx):
        if BALL_CHANGE_BG:
            self.color = random.choice(["grey", "blue"])
        self.add_neighbor()
        
        if yx[0] <= 0:
            self.dy = 1
        if yx[0] >= self.lenYX[0]-1:
            self.dy = -1
         
        try:   
            if (yx[0] +self.dy, yx[1] + 1) in self.neighbor[yx]:
                yx = (yx[0] +self.dy, yx[1] + 1)

            else:
                if yx[1]+1 >= self.lenYX[1]: 
                    yx = (yx[0], self.lenYX[1])
                else: 
                    v = []
                    for i in self.neighbor[yx]: 
                        if not i in self.visit:
                            self.visit[i] = 0
                        v.append((i, self.visit[i])) 
                    
                    v.sort(key = lambda x: x[1], reverse = False)
                    v = [i for i in v if v[0][1] == i[1]]
                    v = random.choice(v)
                    self.visit[v[0]] += 1
                    yx = v[0]
                    self.dy = random.choice([-1,1])
        except:
            pass
         
            
        return yx
    
    
class WhoСatches(Base):
    def __init__(self, root, color, node, lenYX, whoBeats):
        super().__init__(root, color, node)
        self.lenYX = lenYX
        self.whoBeats = whoBeats
        self.win = 0
        
        self.routes = []
        self.route = []
        self.gf = [{}, {}]
        
        self.hdWC = Hd({})
        

    def choice(self, yx):
        self.add_neighbor()
        self.neighbor[yx] = [x for x in self.neighbor[yx] if x[1] == self.lenYX[1]-1]
        
        dy = self.action()
        if (yx[0] +dy, yx[1]) in self.neighbor[(yx[0], self.lenYX[1]-1)]:

            hd = Hd({})
            hdF = self.tuple_str((dy,0))
            hdX = self.tuple_str(yx)
            hdY = self.tuple_str((yx[0] +dy, yx[1]))
            path = hd.get_yfx([hdX, hdY], [hdF, ""])
            gn = hd.fORx(hd.flow(path), f=0)
            self.hdWC.graph = hd.merge(self.hdWC.graph, gn)
            print("\nwhoСatches ku = ", self.hdWC.get_ku(self.hdWC.graph))
            self.hdWC.show(self.hdWC.graph)

            yx = (yx[0] +dy, self.lenYX[1]-1) 
        
        self.winner()

        return yx
        
        
    def action(self, choice = None):
        if choice == None:
            dy = random.choice([-1,0,1])
        return dy
        
        
    def hypothesis(self, x_list, f = None):
        if f ==  None:          
            f_list = ["f"]*len(x_list)
            return (f_list, x_list)
        else:
            f_list = x_list[:-1] #дейсвие = предыдущее состояние
            x_list = x_list[1:]  
            return (f_list, x_list)
            
    def tuple_str(self, v, convert = True):
        if convert:
            delimiter = ','
            r = delimiter.join([str(x) for x in v])
        else:
            r = [ x for x in v.split(',')]
        return r
        
    
    def winner(self):
        balls = self.whoBeats.get_balls()

        for i, ball in enumerate(balls):  
            if ball.env()[1][1] == 1:
                self.route = []
                self.route.append(ball.env()[1])
            elif ball.env()[1][1] < self.lenYX[1]-1:
                self.route.append(ball.env()[1])
            else:
                self.route.append(ball.env()[1])
                if self.route not in self.routes:
                    self.routes.append(self.route)
            if self.x == ball.env()[1][1]:
                if self.y == ball.env()[1][0]:
                    self.win +=1
                else:
                    self.win -=1
                root.title("win = " + str(self.win))
                self.whoBeats.balls.pop(i)
        
        if  self.routes: 
            hd = Hd({}) 
            for x_list in self.routes:
                x_list = [self.tuple_str(x) for x in x_list]
                if HYPOTHESIS:
                    f_list, x_list = self.hypothesis(x_list, f=True)
                else:
                    f_list, x_list = self.hypothesis(x_list)
                path = hd.get_yfx(x_list, f_list)
                gn = hd.fORx(hd.flow(path), f=0)
                self.gf[0] = hd.merge(self.gf[0], gn)
            ku = hd.get_ku(self.gf[0])
            print("\n\nball  ku =", ku)
            hd.show(self.gf[0])
            print("\nroutes")
            t = [print(x) for x in self.routes]

        
        
#--------- main ---------#    
if __name__ == "__main__":

    TIME = 500 #частота обновления
    PAUSE = 5
    HYPOTHESIS = True
    
    CHANGE_BG = False 
    BALL_CHANGE_BG = False 
    #CHANGE_BG = True
    #BALL_CHANGE_BG = True

    
    root = Tk()
    
    #ban=[(1,3), (1,5), (1,6), (1,7), (0,7), (1,4), (2,4), (3,4), (4,3), (4,4), (5,6), (5,7), (4,7), (3,7), (2,7)] 
    #M=8; N=12
    
    ban=[(1,3)] 
    #ban = []
    M=3; N=6
    
    maps = [["" for i in range(N)] for j in range(M)]
    
    def update():
        #w1.update()
        b1.update()
        c1.update()
        
    w1 = Wednesday(root, maps, ban)
    b1 = WhoBeats(root, "red", (1,0), (M, N))
    c1 = WhoСatches(root, "green", (1,N-1), (M, N), b1)
    
    update()
    root.mainloop()
    


Теги:
Хабы:
Всего голосов 4: ↑1 и ↓3-2
Комментарии19

Публикации

Истории

Ближайшие события

19 сентября
CDI Conf 2024
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн