DSZI_Survival/DecisionTree.md
2020-05-25 15:06:53 +02:00

208 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# DSZI_Survival - Drzewa Decyzyjne
### Autor: Michał Czekański
## Cel zastosowania w projekcie
W projekcie DSZI_Survival drzewo decyzyjne użyte jest do podejmowania decyzji przez agenta, rozbitka na bezludnej wyspie,
jaką czynność wykonać w danej chwili.
Czy:
* zdobyć pożywienie
* udać się do źródła wody
* odpocząć przy ognisku
## Opis drzewa decyzyjnego
* **Drzewo decyzyjne** to drzewo reprezentujące jakąś funkcję, Boolowską w najprostszym przypadku.
* Drzewo decyzyjne jako **argument** przyjmuje obiekt - sytuację opisaną za pomocą zestawu **atrybutów**
* **Wierzchołek** drzewa decyzyjnego odpowiada testowi jednego z atrybutów (np. IsMonday)
* Każda **gałąź** wychodząca z wierzchołka jest oznaczona możliwą wartością testu z wierzchołka (np. True)
* **Liść** zawiera wartość do zwrócenia (**decyzję, wybór**), gdy liść ten zostanie osiągnięty (np. ShopType.Grocery)
## Metoda uczenia - Algorytm ID3
Metoda użyta do uczenia drzewa decyzyjnego to metoda **indukcyjnego uczenia drzewa decyzyjnego**.
### Działanie ID3
* Definiujemy atrybuty, które będą posiadały przykłady służące do uczenia drzewa (**atrybuty**)
```python
class AttributeDefinition:
def __init__(self, id, name: str, values: List):
self.id = id
self.name = name
self.values = values
class Attribute:
def __init__(self, attributeDefinition: AttributeDefinition, value):
self.attributeDefinition = attributeDefinition
self.value = value
```
* Tworzymy przykłady z wykorzystaniem atrybutów (**przykłady**)
```python
class DecisionTreeExample:
def __init__(self, classification, attributes: List[Attribute]):
self.attributes = attributes
self.classification = classification
```
* Ustalamy domyślną wartość do zwrócenia przez drzewo - **klasa domyślna**
* Następnie postępujemy indukcyjnie:
* Jeżeli liczba przykładów == 0: zwracamy wierzchołek oznaczony klasą domyślną
* Jeżeli wszystkie przykłady są tak samo sklasyfikowane: zwracamy wierzchołek oznacz. tą klasą
* Jeżeli liczba atrybutów == 0: zwracamy wierzchołek oznacz. klasą, którą posiada większość przykładów
* W przeciwnym wypadku **wybieramy atrybut** A (o wyborze atrybutu poniżej) i czynimy go korzeniem drzewa T
* **nowa_klasa_domyślna** = wierzchołek oznaczony klasą, która jest przypisana największej liczbie przykładów
* Dla każdej wartości W atrybutu A:
* nowe_przykłady = przykłady, dla których atrybut A przyjmuje wartość W
* Dodajemy do T krawędź oznaczoną przez wartość W, która prowadzi do wierzchołka zwróconego przez wywołanie indukcyjne:
*treelearn(nowe_przykłady, atrybutyA, nowa_klasa_domyślna)*
* Zwróć drzewo T
```python
class DecisionTree(object):
def __init__(self, root):
self.root = root
self.branches = []
self.branchesNum = 0
```
### Wybór atrybutu
W trakcie uczenia drzewa decyzyjnego chcemy wybrać jak najlepszy atrybut, dzięki któremu możliwie jak najszybciej będziemy mogli sklasyfikować podane przykłady.
Miarą porównawczą atrybutów będzie **zysk informacji** dla danego atrybutu (**information gain**).
Atrybut o największym zysku zostanie wybrany.
**Implementacja**
```python
def chooseAttribute(attributes: List[AttributeDefinition], examples: List[DecisionTreeExample], classifications):
bestAttribute = None
bestAttributeGain = -1
for attribute in attributes:
attrInformationGain = calculateInformationGain(attribute, classifications, examples)
if attrInformationGain > bestAttributeGain:
bestAttribute = attribute
bestAttributeGain = attrInformationGain
return bestAttribute
```
#### Obliczanie zysku informacji
(Wszelkie obliczenia wedle wzorów podanych na zajęciach)
* I(C) - Obliczamy zawartość informacji dla zbioru możliwych klasyfikacji
* E(A) - Obliczamy ilość informacji potrzebną do zakończenia klasyfikacji po sprawdzeniu atrybutu
* **G(A)** - **Przyrost informacji dla atrybutu A** = I(C) - E(A)
**Implementacja**
```python
def calculateInformationGain(attribute: AttributeDefinition, classifications, examples: List[DecisionTreeExample]):
return calculateEntropy(classifications, examples) - calculateRemainder(attribute, examples, classifications)
```
## Opis implementacji
### Definicje atrybutów:
* Głód: **[0, 1/4); [1/4, 1/2); [1/2, 3/4); [3/4, 1]**
* Pragnienie: **[0, 1/4); [1/4, 1/2); [1/2, 3/4); [3/4, 1]**
* Energia: **[0, 1/4); [1/4, 1/2); [1/2, 3/4); [3/4, 1]**
* Odległość od jedzenia: **[0, 3); [3, 8); [8, 15); [15, max)**
* Odległość od źródła wody: **[0, 3); [3, 8); [8, 15); [15, max)**
* Odległość od miejsca spoczynku: **[0, 3); [3, 8); [8, 15); [15, max)**
* Odległość pomiędzy wodą a jedzeniem: **[0, 3); [3, 8); [8, 15); [15, max)**
```python
class PlayerStatsValue(Enum):
ZERO_TO_QUARTER = 0
QUARTER_TO_HALF = 1
HALF_TO_THREE_QUARTERS = 2
THREE_QUARTERS_TO_FULL = 3
class DistFromObject(Enum):
LT_3 = 0
GE_3_LT_8 = 1
GE_8_LT_15 = 2
GE_15 = 3
```
### Uczenie drzewa
```python
def inductiveDecisionTreeLearning(examples: List[DecisionTreeExample], attributes: List[AttributeDefinition], default,
classifications)
```
### Zwracanie decyzji przez drzewo
```python
def giveAnswer(self, example: DecisionTreeExample):
if self.branchesNum == 0:
return self.root
for attr in example.attributes:
if attr.attributeDefinition.id == self.root.id:
for branch in self.branches:
if branch.label == attr.value:
return branch.subtree.giveAnswer(example)
```
### Wybór celu dla agenta
```python
def pickEntity(self, player, map, pickForGa=False):
foods = map.getInteractablesByClassifier(Classifiers.FOOD)
waters = map.getInteractablesByClassifier(Classifiers.WATER)
rests = map.getInteractablesByClassifier(Classifiers.REST)
playerStats = DTPlayerStats.dtStatsFromPlayerStats(player.statistics)
# Get waters sorted by distance from player
dtWaters: List[DTSurvivalInteractable] = []
for water in waters:
dtWater = DTSurvivalInteractable.dtInteractableFromInteractable(water, player.x, player.y)
dtWaters.append(dtWater)
dtWaters.sort(key=lambda x: x.accurateDistanceFromPlayer)
nearestDtWater = dtWaters[0]
# Get foods sorted by distance from player
dtFoods: List[DTSurvivalInteractable] = []
for food in foods:
dtFood = DTSurvivalInteractable.dtInteractableFromInteractable(food, player.x, player.y)
dtFoods.append(dtFood)
dtFoods.sort(key=lambda x: x.accurateDistanceFromPlayer)
# If there is no food on map return nearest water.
try:
nearestDtFood = dtFoods[0]
except IndexError:
return nearestDtWater.interactable
# Get rest places sorted by distance from player
dtRestPlaces: List[DTSurvivalInteractable] = []
for rest in rests:
dtRest = DTSurvivalInteractable.dtInteractableFromInteractable(rest, player.x, player.y)
dtRestPlaces.append(dtRest)
dtRestPlaces.sort(key=lambda x: x.accurateDistanceFromPlayer)
nearestDtRest = dtRestPlaces[0]
currentSituation = SurvivalDTExample(None, playerStats.hungerAmount, playerStats.thirstAmount,
playerStats.staminaAmount,
nearestDtFood.dtDistanceFromPlayer, nearestDtWater.dtDistanceFromPlayer,
nearestDtRest.dtDistanceFromPlayer,
nearestDtFood.getDtDistanceFromOtherInteractable(nearestDtWater.interactable))
treeDecision, choice = self.__pickEntityAfterTreeDecision__(currentSituation,
dtFoods,
dtRestPlaces,
dtWaters)
return choice.interactable
```
## Zestaw uczący, zestaw testowy
### Zestaw uczący
Zestaw uczący był generowany poprzez tworzenie losowych przykładów i zapytanie użytkownika o klasyfikację, a następnie zapisywany do pliku.
### Zestaw testowy
Przy testowaniu drzewa podajemy ile procent wszystkich, wcześniej wygenerowanych przykładów mają być przykłady testowe.