Skip to content

KL divergence #18

Description

@huangchieh
#%%
# The KL divergence score, quantifies how much one probability distribution differs from another probability distribution: 
# $$D_{KL}(p||q) = H(p, q) - H(p)$$
# where entropy $H(p) = -\sum_i p_i \log_2(p_i)$, the average information in bit, 
# and cross entropy $H(p, q) = - \sum_i p_i \log_2(q_i)$ is the average message length in bit.

#%%
import numpy as np 
import matplotlib.pyplot as plt
from PIL import Image 

#%%
def img2array(imgPath):
    '''Load image as numpy array'''
    image = Image.open(imgPath)
    image = image.convert("L")
    return np.array(image)

def pixelhistogram(gray_array):
    '''Get the pixel histogram for a graycale image'''
    histogram, bin_edges = np.histogram(gray_array, bins=256, range=(0, 256))
    return histogram, bin_edges

def relativeerror(img, ref_img):
    '''Calculate relative error'''
    N = img.shape[0] * img.shape[1]
    error = np.sum(np.abs(img - ref_img))/(np.max(ref_img) - np.min(ref_img))/ N
    return error

def entropy(p_x):
    '''Calculate the entropy of a distribution p(x)'''
    e_sum = 0
    for p in p_x: 
        e = p * np.log2(p) if p != 0 else 0
        e_sum += e
    return -e_sum

def crossentropy(p_x, q_x):
    '''Calculate the cross entropy between distributions p(x), q(x)'''
    e_sum = 0
    for p, q in zip(p_x, q_x):
        if p == 0:
            e = 0
        elif p != 0:
            if q == 0:
                q = 1E-12
            e = p*np.log2(q)
        e_sum += e
    return -e_sum

def relativeentropy(p_x, q_x):
    '''Calculate the relative entropy (or KL divergence) between between distributions p(x), q(x)'''
    return crossentropy(p_x, q_x) - entropy(p_x)


# %%
def main():
    # Load image 
    raw_img_path = './image0.png'
    gen_img_path = './image1.png'
    imgs = [img2array(path) for path in [raw_img_path, gen_img_path]]

    # Show images
    for i, img in enumerate(imgs):
        plt.subplot(1, len(imgs), i+1)
        plt.imshow(img, cmap='grey', vmin=0, vmax=255)
    plt.show()

    # Get the pixel distribution
    hists=[]
    p_xs=[]
    bins=[]
    fig, axs = plt.subplots(1, len(imgs), figsize=(15, 5))
    for i, ax in enumerate(axs):
        histogram, bin_edges = pixelhistogram(imgs[i])
        p_x = histogram / (imgs[i].shape[0] * imgs[i].shape[1])

        # Store the distribution
        hists.append(histogram)
        bins.append(bin_edges)
        p_xs.append(p_x)

        # Plot
        ax.bar(bin_edges[:-1], p_x, width=1, align='edge', color='gray')
        ax.set_title(f"Fig. {i+1} Distribution")
        ax.set_xlabel("Pixel Value x")
        ax.set_ylabel("Probability p(x)")
    plt.tight_layout()
    plt.show()

    # Compare the KL divergence 
    p_x = p_xs[0] # Real Image Distribution 
    q_x = p_xs[1] # Genergated Image Distribution 

    KL_00 = relativeentropy(p_x, p_x) # Use the same distribution the KL divergece should be 0
    KL_01 = relativeentropy(p_x, q_x) # 

    print('KL(img0, img0), KL(img0, img1)', KL_00, KL_01)
    # If the number is close to  0, means the two images are basicly the same.
    # KL divergence measures the inefficiency caused by the approximation (generated image). 
    # https://stats.stackexchange.com/questions/111445/analysis-of-kullback-leibler-divergence
    



# %%
main()
# %%

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions