AIProjekt/Podprojekt_s444426/raport.md
2020-05-26 19:55:26 +00:00

5.1 KiB

Agata Lenz - drzewa decyzyjne, algorytm ID3

Wymagania

Importowane biblioteki:

  • numpy
  • pandas

Opis podprojektu

Podprojekt implementuje algorytm ID3 pozwalający wyznaczyć drzewo decyzyjne, przy użyciu którego agent (traktor) podejmie decyzję co posadzić w danym miejscu, na podstawie:

  • previous - gatunku rośliny, która poprzednio rosła w danym miejscu
  • pH - pH gleby
  • dry_level - suchości gleby

Uczenie modelu

Dane

Dane uczące jak i testowe znajdują się w pliku data.py. Dane uczące są zapisane w postaci listy, której elementy to przykładowe dane w formacie ['previous', 'pH', 'dry_level', 'label'], gdzie label oznacza posadzoną w wymienionych warunkach roślinę. Łącznie zestaw uczący zawiera 47 elementów, a zestaw testowy - 15.

Przykładowe elementy zestawu uczącego:

 ['pumpkin', 'neutral', 'dry', 'beetroot'],
 ['none', 'neutral', 'wet', 'pumpkin'],
 ['cabbage', 'alkaline', 'soaking wet', 'none']

Implementacja algorytmu ID3

Za budowę drzewa decyzyjnego odpowiada rekurencyjna funkcja ID3(data, original_data, features, target_attribute_name="label", parent_node_class=None):

def ID3(data, original_data, features, target_attribute_name="label", parent_node_class=None):
    """
    Algorytm ID3

    parametry:
        data                    zbiór danych, dla którego poszukujemy drzewa decyzyjnego
        original_data           oryginalny zbiór danych (zwracany gdy data == None)
        features                lista atrybutów wejściowego zbioru
        target_attribute_name   docelowy atrybut, który chcemy przewidzieć
        parent_node_class       nadrzędna wartość
    """

    # Jeżeli wszystkie atrybuty są takie same, zwracamy liść z pierwszą napotkaną wartością

    if len(np.unique(data[target_attribute_name])) <= 1:
        return np.unique(data[target_attribute_name])[0]

    elif len(data) == 0:
        return np.unique(original_data[target_attribute_name])[
            np.argmax(np.unique(original_data[target_attribute_name], return_counts=True)[1])]

    elif len(features) == 0:
        return parent_node_class

    else:

        # Aktualizacja nadrzędnej wartości
        parent_node_class = np.unique(data[target_attribute_name])[
            np.argmax(np.unique(data[target_attribute_name], return_counts=True)[1])]

        # Obliczenie przyrostu informacji dla każdego potencjalnego atrybutu,
        # według którego nastąpi podział zbioru
        item_values = [info_gain(data, feature, target_attribute_name) for feature in
                       features]

        # Najlepszym atrybutem jest ten o największym przyroście informacji
        best_feature_index = np.argmax(item_values)
        best_feature = features[best_feature_index]

        # Struktura drzewa
        tree = {best_feature: {}}

        # Aktualizacja zbioru atrybutów
        features = [i for i in features if i != best_feature]

        # Dla każdej wartości wybranego atrybutu budujemy kolejne poddrzewo
        for value in np.unique(data[best_feature]):

            sub_data = data.where(data[best_feature] == value).dropna()
            subtree = ID3(sub_data, data, features, target_attribute_name, parent_node_class)

            tree[best_feature][value] = subtree

        return (tree)

Do obliczenia przyrostu informacji służy funkcja info_gain(data, split_attribute_name, target_name="label"), która dla wejściowego zestawu danych (data), oblicza jego entropię oraz średnią ważoną entropii każdego podzestawu (wyznaczanego przez unikalne wartości atrybutu split_attribute_name):

def info_gain(data, split_attribute_name, target_name="label"):

    total_entropy = entropy(data[target_name])
    vals, counts = np.unique(data[split_attribute_name], return_counts=True)

    weighted_entropy = np.sum(
        [(counts[i] / np.sum(counts)) * entropy(data.where(data[split_attribute_name] == vals[i]).dropna()[target_name])
         for i in range(len(vals))])

    information_gain = total_entropy - weighted_entropy

    return information_gain

Implementacja w projekcie głównym

Funkcja, z której będzie korzystał traktor, aby podjąć decyzję o posadzeniu rośliny, to def decide_to_plant(soil)

def decide_to_plant(soil):

    if soil.have_plant():
        plant = soil.get_plant()
        if plant.collect() == 'True':
            info = get_info(soil)
            plant.leave_soil()
        else:
            return [['none']]
    else:
        info = get_info(soil)

    data = []
    data.append(info)

    predicted = predict_data(data)
    grow_a_plant(soil,predicted[0][0])

    return predicted

Pierwszym krokiem jest sprawdzenie, czy w danej ziemi już coś rośnie - jeżeli jest to roślina dojrzała, zostaje ona zebrana, a jej nazwa staje się wartością atrybutu previous. Jeżeli nie, nie przewiduje się sadzenia w tym miejscu żadnej rośliny w danym momencie. Funkcja get_info(soil) zwraca listę parametrów obiektu Soil, potrzebną do poszukiwań w drzewie decyzyjnym. W pliku dataset.py znajduje się funkcja create_data_soil() pozwalająca przetestować działanie algorytmu na obiektach typu Soil