moj_labs/lab3.ipynb
2023-03-28 21:14:03 +02:00

22 KiB

import requests
import numpy as np
import string
import random
import gzip
from dahuffman import HuffmanCodec
import re
from collections import Counter
NR_INDEKSU = 449288
random.seed(NR_INDEKSU)
np.random.seed(NR_INDEKSU)

Zadanie 1

# All characters from [a-zA-Z0-9 ]
chars = list(string.ascii_lowercase) + list(string.ascii_uppercase) + [str(i) for i in range(10)] + [' ']
print(chars)
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ']
# 1. Natural language text
url = 'https://wolnelektury.pl/media/book/txt/rozprawa-o-metodzie.txt'
rozprawa = requests.get(url).content.decode('utf-8')
rozprawa = rozprawa[:100000]
rozprawa[50000:50100]
'iebie wmówić w młodości, nie zbadawszy nigdy, czy są prawdziwe. Mimo bowiem że spostrzegłem w tym ro'
# 2. Text generated randomly from a discrete uniform distribution, characters = [a-zA-Z0-9 ]
uniform = ''.join(random.choices(chars, k = 100000))
uniform[50000:50100]
'Yc9 h7SWSelg8Pkd1Nq0IudZdaGujrPwWFXpuWuj3bAxKxe1G8IEMGqedyXnPW2CUsDCsos3ljJNuKeEGDzgs4NQM7sG6FfLqSVT'
# 3. Text generated randomly from a geometric distribution, p = 0.5, characters = [a-zA-Z0-9 ]
samples = np.random.geometric(p=0.5, size=100000)
geometric = ''.join([chars[sample % len(chars)] for sample in samples])
geometric[50000:50100]
'cbbdebbebddbbbccbcbbbbbbbcdhcbbcbbbcdbedbcbbbbbecddbcbfbccbcbccdbcbebdbbcdcbdcbbbbbebbcbbebbdbcbbbdd'
# 4. Text generated randomly from a binomial distribution, p = 0.5, characters = [01]
samples = np.random.binomial(1, 0.5, 100000)
binomial_05 = ''.join([str(sample) for sample in samples])
binomial_05[50000:50100]
'0101000110000010100110101000100000011101000010011001010101010110011101111100101011110110101001100011'
# 5. Text generated randomly from a binomial distribution, p = 0.9, characters = [01]
samples = np.random.binomial(1, 0.9, 100000)
binomial_09 = ''.join([str(sample) for sample in samples])
binomial_09[50000:50100]
'1111111111111111111101101111111111111111111111111111111111111111110111101111111111101111111111111111'
# Creating a helper dict for function input
text_names = ['rozprawa_chars', 'uniform_chars', 'geometric_chars', 'binomial_05_chars', 'binomial_09_chars']
text_variables = [rozprawa, uniform, geometric, binomial_05, binomial_09]
texts = {text_name: text_variable for text_name, text_variable in zip(text_names, text_variables)}
# Calculating entropy for texts
def calculate_entropy(texts):
    for name, text in texts.items():
        with gzip.open(name + '_compressed.gz', 'wb') as f:
            compressed = gzip.compress(text.encode('utf-8'))
            print(f"{name}{' ' * (20 - len(name))}{8 * len(compressed) / len(text)}")
            f.write(compressed)

calculate_entropy(texts)
rozprawa_chars      3.2612
uniform_chars       6.03328
geometric_chars     2.4852
binomial_05_chars   1.24432
binomial_09_chars   0.66288
# Training codecs and encoding whole texts
def train_codecs(texts):
    codecs = {name: HuffmanCodec.from_data(text) for name, text in texts.items()}
    encoded_texts = {name: codecs[name].encode(text) for name, text in texts.items()}
    return codecs, encoded_texts

codecs, encoded_texts = train_codecs(texts)
# Decoding 3 initial characters for all texts
def three_initials(texts, codecs):
    for name, text in texts.items():
        decoded = ' '.join(["{:08b}".format(int(codecs[name].encode(char).hex(), 16)) for char in list(text[:3])])
        print(f"{name}{' ' * (20 - len(name))}{decoded}")

three_initials(texts, codecs)
rozprawa_chars      1111011110001111 10011111 00001111
uniform_chars       01110000 10101000 10000000
geometric_chars     00100000 01000000 10000000
binomial_05_chars   01000000 10000000 01000000
binomial_09_chars   10000000 10000000 10000000
# Saving raw texts, encoded texts and code tables
def to_files(texts, codecs, encoded_texts):
    for name, text in texts.items():
        with open(name + '_text.txt', 'w', encoding='utf-8') as f_text, open(name + '_encoded.bin', 'wb') as f_encoded, open(name + '_encoded_table.txt', 'w', encoding='utf-8') as f_table:
            f_text.write(text)
            f_encoded.write(encoded_texts[name])
            f_table.write(str(codecs[name].get_code_table()))

to_files(texts, codecs, encoded_texts)
# Comparing file sizes of all text format files
!ls -l rozprawa_chars*
!echo '----------'
!ls -l uniform_chars*
!echo '----------'
!ls -l geometric_chars*
!echo '----------'
!ls -l binomial_05_chars*
!echo '----------'
!ls -l binomial_09_chars*
-rw-rw-r-- 1 ked ked  40824 Mar 25 20:46 rozprawa_chars_compressed.gz
-rw-rw-r-- 1 ked ked  59007 Mar 25 20:46 rozprawa_chars_encoded.bin
-rw-rw-r-- 1 ked ked   1503 Mar 25 20:46 rozprawa_chars_encoded_table.txt
-rw-rw-r-- 1 ked ked 107454 Mar 25 20:46 rozprawa_chars_text.txt
----------
-rw-rw-r-- 1 ked ked  75484 Mar 25 20:46 uniform_chars_compressed.gz
-rw-rw-r-- 1 ked ked  74977 Mar 25 20:46 uniform_chars_encoded.bin
-rw-rw-r-- 1 ked ked    887 Mar 25 20:46 uniform_chars_encoded_table.txt
-rw-rw-r-- 1 ked ked 100000 Mar 25 20:46 uniform_chars_text.txt
----------
-rw-rw-r-- 1 ked ked  31120 Mar 25 20:46 geometric_chars_compressed.gz
-rw-rw-r-- 1 ked ked  24894 Mar 25 20:46 geometric_chars_encoded.bin
-rw-rw-r-- 1 ked ked    258 Mar 25 20:46 geometric_chars_encoded_table.txt
-rw-rw-r-- 1 ked ked 100000 Mar 25 20:46 geometric_chars_text.txt
----------
-rw-rw-r-- 1 ked ked  15606 Mar 25 20:46 binomial_05_chars_compressed.gz
-rw-rw-r-- 1 ked ked  18734 Mar 25 20:46 binomial_05_chars_encoded.bin
-rw-rw-r-- 1 ked ked     40 Mar 25 20:46 binomial_05_chars_encoded_table.txt
-rw-rw-r-- 1 ked ked 100000 Mar 25 20:46 binomial_05_chars_text.txt
----------
-rw-rw-r-- 1 ked ked   8338 Mar 25 20:46 binomial_09_chars_compressed.gz
-rw-rw-r-- 1 ked ked  13755 Mar 25 20:46 binomial_09_chars_encoded.bin
-rw-rw-r-- 1 ked ked     40 Mar 25 20:46 binomial_09_chars_encoded_table.txt
-rw-rw-r-- 1 ked ked 100000 Mar 25 20:46 binomial_09_chars_text.txt

Entropia

Entropia
tekst w jęz. naturalnym 3.2612
losowy tekst (jednostajny) 6.03328
losowy tekst (geometryczny) 2.4852
losowy tekst (dwupunktowy 0.5) 1.24432
losowy tekst (dwupunktowy 0.9) 0.66288

Wielkości w bitach:

Plik nieskompresowany Plik skompresowany (zip, tar,.. ) Plik skompresowany + tablica kodowa)
tekst w jęz. naturalnym 107454 40824 59007 + 1503
losowy tekst (jednostajny) 100000 75484 74977 + 887
losowy tekst (geometryczny) 100000 31120 24894 + 258
losowy tekst (dwupunktowy 0.5) 100000 15606 18734 + 40
losowy tekst (dwupunktowy 0.9) 100000 8338 13755 + 40

Wnioski:

  • Najwyższą entropię posiada losowy tekst z rozkładu jednostajnego. Jest to spodziewalne, ponieważ jest to właściwie czysty szum złożony z kilkudziesięciu możliwych symboli o tym samym prawdopodobieństwie wystąpienia
  • Losowe teksty złożone wyłącznie z 0 i 1 mają niższą entropię nawet od tekstu w języku naturalnym, ponieważ ich losowość jest porównywalnie niższa przez bardzo ograniczoną klasę możliwych symboli
  • Bardzo niska entropia losowego tekstu z rozkładu dwupunktowego 0.9 wynika z faktu, że cały tekst to w ~90% powtarzany jeden symbol
  • Wysoka entropia tekstu przyczynia się do braku możliwości efektywnego skompresowania go, a tym samym zaoszczędzenia więcej miejsca na dysku - ciężko jest stworzyć efektywne kodowanie dla symboli o bardzo podobnym rozkładzie
  • Klasa możliwych symboli znacząco wpływa na rozmiar tablicy kodującej
  • Kompresja gzip daje znacznie lepsze wyniki niż kodek Huffmana wyłącznie dla tekstu w języku naturalnym
  • Większy niż 100000 rozmiar w bajtach tekstu w języku naturalnym wynika z faktu, iż niektóre znaki w nim występujące (np. polskie litery) są kodowane więcej niż jednym bajtem

Zadanie 2

# Creating a helper dict for function input + deleting multiple spaces
text_names_words = [name.replace('char', 'word') for name in text_names[:3]]
text_variables_words = text_variables[:3]
texts_words = {text_name: re.sub(r'\s+', ' ', text_variable) for text_name, text_variable in zip(text_names_words, text_variables_words)}
# Defining a single space-preserving split function
word_split = lambda text: re.split(f"( )", text)
# Calculating entropy for texts
calculate_entropy(texts_words)
rozprawa_words      3.2606867202633665
uniform_words       6.033249974992498
geometric_words     2.4852
# Training codecs and encoding whole texts
def train_codecs_words(texts):
    codecs = {name: HuffmanCodec.from_data(word_split(text)) for name, text in texts.items()}
    encoded_texts = {name: codecs[name].encode(word_split(text)) for name, text in texts.items()}
    return codecs, encoded_texts

codecs_words, encoded_texts_words = train_codecs_words(texts_words)
# Decoding 3 initial words for all texts
def three_initials_words(texts, codecs):
    for name, text in texts.items():
        decoded = ' '.join(["{:08b}".format(int(codecs[name].encode([word]).hex(), 16)) for word in word_split(text)[:3]])
        print(f"{name}{' ' * (20 - len(name))}{decoded}")

three_initials_words(texts_words, codecs_words)
rozprawa_words      1000110111111101 01011101 1000011001010111
uniform_words       1000001101010111 01011101 1101011000111011
geometric_words     10000000
# Saving raw texts, encoded texts and code tables
to_files(texts_words, codecs_words, encoded_texts_words)
# Comparing file sizes of all text format files
!ls -l rozprawa_words*
!echo '----------'
!ls -l uniform_words*
!echo '----------'
!ls -l geometric_words*
-rw-rw-r-- 1 ked ked  40668 Mar 25 20:46 rozprawa_words_compressed.gz
-rw-rw-r-- 1 ked ked  23817 Mar 25 20:46 rozprawa_words_encoded.bin
-rw-rw-r-- 1 ked ked 151559 Mar 25 20:46 rozprawa_words_encoded_table.txt
-rw-rw-r-- 1 ked ked 107087 Mar 25 20:46 rozprawa_words_text.txt
----------
-rw-rw-r-- 1 ked ked  75461 Mar 25 20:46 uniform_words_compressed.gz
-rw-rw-r-- 1 ked ked   2500 Mar 25 20:46 uniform_words_encoded.bin
-rw-rw-r-- 1 ked ked 123504 Mar 25 20:46 uniform_words_encoded_table.txt
-rw-rw-r-- 1 ked ked  99970 Mar 25 20:46 uniform_words_text.txt
----------
-rw-rw-r-- 1 ked ked  31120 Mar 25 20:46 geometric_words_compressed.gz
-rw-rw-r-- 1 ked ked      1 Mar 25 20:46 geometric_words_encoded.bin
-rw-rw-r-- 1 ked ked 100026 Mar 25 20:46 geometric_words_encoded_table.txt
-rw-rw-r-- 1 ked ked 100000 Mar 25 20:46 geometric_words_text.txt

Entropia

Entropia
tekst w jęz. naturalnym 3.2606867202633665
losowy tekst (dyskretny) 6.033249974992498
losowy tekst (geometryczny) 2.4852

Wielkości w bitach:

Plik nieskompresowany Plik skompresowany (zip, tar,.. ) Plik skompresowany + tablica kodowa)
tekst w jęz. naturalnym 107087 40668 23817 + 151559
losowy tekst (jednostajny) 99970 75461 2500 + 123504
losowy tekst (geometryczny) 100000 31120 1 + 100026

Wnioski:

  • Plik skompresowany kodem Huffmana dla tekstu wygenerowanego losowo z rozkładu geometrycznego wychodzi dosyć osobliwie, spacja nie została wygenerowana ani razu, więc cały tekst reprezentowany jest jednym bajtem
  • Z tego samego powodu nie da się zdekodować 3 pierwszych słów tego tekstu na potrzebę jednego z zadań (jest tylko 1)
  • Generalnie podejście oparte na słowach przekłada się na znacznie większe tablice kodowe dla kodu Huffmana (bo jest więcej unikalnych słów niż symboli)
  • Analogicznie do powyższego same pliki zakodowane kodem Huffmana wychodzą znacznie mniejsze
  • re.split(f"( )", text) to naprawdę fajny trik, gdy chcemy podzielić tekst na słowa z zachowaniem spacji

Zadanie 3

# Generating text to encode
random.seed(NR_INDEKSU)
tekst = list('abcdefghijklmnoprst')
random.shuffle(tekst)
tekst = tekst[: 5 + random.randint(1,5)]
tekst = [a*random.randint(1,4) for a in tekst]
tekst = [item for sublist in tekst for item in sublist]
''.join(tekst)
random.shuffle(tekst)
tekst = ''.join(tekst)
print(tekst)
counts = sorted(Counter(tekst).items(), key=lambda x: (x[1], x[0]))
print([(letter, f'{count}/{len(tekst)}') for (letter, count) in counts])
biiibbikpdrkbk
[('d', '1/14'), ('p', '1/14'), ('r', '1/14'), ('k', '3/14'), ('b', '4/14'), ('i', '4/14')]

Zakodowany text 'biiibbikpdrkbk': 10 11 11 11 10 10 11 01 0001 0000 001 01 10 01