В тази публикация обобщавам моето разбиране за това какво означава ентропия в машинното обучение, като използвам някои примери за код. Тази публикация е документация за моето разбиране за страхотното видео от Statquest.

Защо обсъждаме Ентропия?

Ентропията се използва за много цели в Data Science.

  • Може да се използва за изграждане на класификационни дървета.
  • Това е основата за взаимна информация, която количествено определя връзката между две неща.
  • Това е основата на относителната ентропия (известна още като разстоянието Kullback-Leibler) и кръстосаната ентропия.

Всички по-горе използват ентропия или нещо, получено от нея, за да определят количествено приликите и разликите. Нека научим как ентропията определя количествено приликите и разликите

Внос

# Import Packages
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
from collections import Counter
%matplotlib inline
%config InlineBackend.figure_format='retina'

# Code to plot a function. Borrowed from fastai library.
def plot_func(f, tx=None, ty=None, title=None, min=-2, max=2, figsize=(6,4)):
    x = np.linspace(min,max)
    fig,ax = plt.subplots(figsize=figsize)
    ax.plot(x,f(x))
    if tx is not None: ax.set_xlabel(tx)
    if ty is not None: ax.set_ylabel(ty)
    if title is not None: ax.set_title(title)

Въведение в изненадата: чанти, ябълки и портокали

Да кажем, че имаме три чанти 💼, които са червени, зелени и бели на цвят. Всички тези торбички съдържат ябълки 🍎 и портокали 🍊 в различни количества. Предстоящата задача е да се определи количествено колко подобни или различни са тези торбички една от друга по отношение на техния състав, като се използва „Ентропия“ като показател.

Тъй като броят на ябълките в червената торба е повече, вероятността да вземете ябълка от червената торба е по-висока. Затова не би било много изненадващо, ако вземем ябълки от червената торба. За разлика от това, ако вземем портокал от червената чанта, ще бъдем относително по-изненадани.

red_bag = np.array(['Apple']*4 + ['Orange'])
print(f'Red Bag conatins: {Counter(red_bag)}') 

green_bag = np.array(['Orange']*9 + ['Apple'])
print(f'Green Bag conatins: {Counter(green_bag)}')

white_bag = np.array(['Orange']*5 + ['Apple']*5)
print(f'White Bag conatins: {Counter(white_bag)}')

# Define the probability of observing Appless & Oranges in red bag.
red_ctr = Counter(red_bag)

P_apple_red = red_ctr['Apple'] / len(red_bag)
print(f'P(Apple from Red Bag) : {round(P_apple_red,2)}')

P_orange_red = red_ctr['Orange'] / len(red_bag)
print(f'P(Orange from Red Bag): {round(P_orange_red,2)}')

В зелената чанта има много повече портокали, отколкото ябълки. Затова не бихме се изненадали много, ако вземем портокал от тази чанта. От друга страна, ако вземем ябълка, това би било сравнително изненадващо.

# Define the probability of observing Appless & Oranges in green bag.
green_ctr = Counter(green_bag)

P_apple_green = green_ctr['Apple'] / len(green_bag)
print(f'P(Apple from Green Bag) : {round(P_apple_green,2)}')

P_orange_green = green_ctr['Orange'] / len(green_bag)
print(f'P(Orange from Green Bag): {round(P_orange_green,2)}')

# Define the probability of observing Appless & Oranges in white bag.
white_ctr = Counter(white_bag)

P_apple_white = white_ctr['Apple'] / len(white_bag)
print(f'P(Apple from White Bag) : {round(P_apple_white,2)}')

P_orange_white = white_ctr['Orange'] / len(white_bag)
print(f'P(Orange from White Bag): {round(P_orange_white,2)}')

Разглеждайки горните примери, можем да наблюдаваме връзката между изненадата и вероятността.

  • Когато вероятността да вземете ябълка от червената торба е висока, изненадата е ниска.
  • Когато вероятността да вземете ябълка от зелената торба е ниска, изненадата е голяма.

Следователно можем да заключим, че изненадата е обратно пропорционална на вероятността за събитие.

Дефиниране на изненадата математически 🧮

Поради обратната връзка между изненадата и вероятността, ние определяме изненадата като логаритъм на обратната стойност на вероятността. Въз основа на тази дефиниция изненадата е нула, когато вероятността е 1, а изненадата е недефинирана, когато вероятността е нула, което има смисъл. Можем да видим на графиката, че вероятността нараства изненадата намалява и става нула, когато вероятността е равна на единица. От друга страна, когато вероятността се доближава до нула, изненадата клони към големи стойности.

# Function to calculate surprise
def surprise(probability):
    return np.log2(1/probability)

# Ploting the
plot_func(surprise, tx='Probabity', ty='Surprise', title='Surprise vs Entropy', min=0, max=1)

Да приемем, че имаме монета с отклонение с 𝑃(𝐻) = 0,9 и 𝑃(𝑇) = 0,1. Можем да изчислим изненадата на главите и опашките, както следва

# Defining the probability and getting the surprise values
P_h = 0.9
P_t = 0.1

S_h = surprise(P_h); 
print(f'Surprise of heads is: {round(S_h,2)}')

S_t = surprise(P_t); 
print(f'Surprise of tails is: {round(S_t,2)}')

Изненадата за опашки е много по-голяма от изненадата за глави поради обратната връзка.

Изчисляване на изненада за поредица от събития

Нека изчислим изненадата от получаването на 2 глави и 1 опашка. Оказва се, че е същото, добавяйки изненадата от 2 глави и 1 опашка, както се вижда на фрагментите по-долу.

print(f'Surprise by using the definition: {round(surprise(P_h * P_h * P_t),2)}')
print(f'Surprise by adding up the individual values: { round((S_h + S_h + S_t),2)} ')

Ентропията като очаквана стойност на изненадата

table = pd.DataFrame({'Heads': [.15, .9], 'Tails': [3.32, .1]}, index=['S(x)','P(x)']); table

# Entropy is the average surprise per iteration of the process
Entropy =  table.loc['P(x)','Heads'] * table.loc['S(x)', 'Heads'] +  table.loc['P(x)', 'Tails'] * table.loc['S(x)', 'Heads']
Entropy

От горното уравнение може да се види, че ентропията може също да бъде представена като точков продукт на вероятността за наблюдаване на определени събития и свързаната с тях изненада.

# Entropy as a dot product of surprise and its associated probability.
P_e = np.array([.9, .1])
S_e = np.array([.15, 3.32])

Entropy = np.dot(P_e, S_e)
Entropy

Ентропия в действие 🎬

def entropy(arr):
    ent = 0
    probs = dict()
    ctr = Counter(arr)
    for e in ctr:
        probs[f'P_{e}'] = ctr[e] / len(arr)
    for p in probs:
        ent += -1 * probs[p] * np.log2(probs[p])
        return round(ent, 2)  
def show_entropy(bag_type=None, bag_type_str=None):
    ctr = Counter(bag_type)
    print(f"{bag_type_str} conatins: {Counter(bag_type)}")
    print(f"P(Apple from {bag_type_str}) : { ctr['Apple'] / len(bag_type)}")
    print(f"P(Orange from {bag_type_str}): {ctr['Orange'] / len(bag_type)}")
    print(f"Entropy of the {bag_type_str} is: {entropy(bag_type)}")

Ентропията за червената торба се оказва 0,26. По-близо е до вероятността да вземете ябълка от червената торба, защото в червената торба има повече ябълки в сравнение с портокалите.

show_entropy(bag_type = red_bag, bag_type_str='Red Bag')

Ентропията за зелената торба се оказва 0,14. Това има смисъл, защото зелената торба има по-голяма вероятност да откъснете плод с по-малко изненада.

show_entropy(bag_type = green_bag, bag_type_str='Green Bag')

Ентропията за бяла торба е 0,5. В този случай вероятността и изненадата имат еднакви стойности и за двата плода.

show_entropy(bag_type = white_bag, bag_type_str='White Bag')

Резюме

Използваме ентропията, за да определим количествено сходството или разликата между две групи, съставени от определени типове обекти. Ентропията на група е най-висока, когато имаме еднакъв брой обекти, принадлежащи към различни класове в тази група, и намалява с разликата между обектите на различни класове се увеличава.