challenging-america-word-ga.../src/04_statystyczny_model_język...

26 KiB

Modelowanie języka

4. Statystyczny model językowy [ćwiczenia]

NR_INDEKSU = 452629
from collections import Counter, defaultdict
from tqdm import tqdm
import re
import nltk
import math
import random
class Model():
    
    def __init__(self, vocab_size = 30_000, UNK_token = '<UNK>', n = 2):
        self.n = n
        self.vocab_size = vocab_size
        self.UNK_token = UNK_token
        self.ngrams = defaultdict(lambda: defaultdict(int))
        self.contexts = defaultdict(int)
        self.vocab = set()
    
    def train(self, corpus: list) -> None:
        self.vocab = set()
        self.vocab.add(self.UNK_token)

        counts = Counter(corpus)
        most_common = counts.most_common(self.vocab_size - 1)
        for word, _ in most_common:
            self.vocab.add(word)

        corpus = [word if word in self.vocab else self.UNK_token for word in corpus]

        n_grams = list(nltk.ngrams(corpus, self.n))
        for gram in tqdm(n_grams):
            context = gram[:-1]
            word = gram[-1]

            if word == self.UNK_token:
                continue

            self.ngrams[context][word] += 1
            self.contexts[context] += 1
    
    def get_conditional_prob_for_word(self, text: list, word: str) -> float:
        if len(text) < self.n - 1:
            raise ValueError("Text is too short for the given n-gram order.")
        
        context = tuple(text[-self.n + 1:])
        if context not in self.ngrams:
            return 0.0
        
        total_count = sum(self.ngrams[context].values())
        word_count = self.ngrams[context][word]
        
        if total_count == 0:
            return 0.0
        else:
            return word_count / total_count
    
    def get_prob_for_text(self, text: list) -> float:
        if len(text) < self.n - 1:
            raise ValueError("Text is too short for the given n-gram order.")
        
        prob = 1.0
        n_grams = list(nltk.ngrams(text, self.n))
        for gram in n_grams:
            context = gram[:-1]
            word = gram[-1]
            prob *= self.get_conditional_prob_for_word(context, word)
        
        return prob
    
    def most_probable_next_word(self, text: list) -> str:
        '''nie powinien zwracań nigdy <UNK>'''
        if len(text) < self.n - 1:
            raise ValueError("Text is too short for the given n-gram order.")
        
        context = tuple(text[-self.n+1:])
        if context not in self.ngrams:
            return ""
        
        most_probable_word = max(self.ngrams[context], key=self.ngrams[context].get)
        return most_probable_word
    
    def generate_text(self, text_beginning: list, length: int, greedy: bool) -> list:
        '''nie powinien zwracań nigdy <UNK>'''
        if len(text_beginning) < self.n - 1:
            raise ValueError("Text beginning is too short for the given n-gram order.")
        
        text_beginning = [word if word in self.vocab else self.UNK_token for word in text_beginning]
        
        generated_text = text_beginning[:]
        while len(generated_text) < length:
            if self.n == 1:
                context = ()
            else:
                context = tuple(generated_text[-self.n+1:])
            if greedy:
                next_word = self.most_probable_next_word(context)
            else:
                candidate_words = list(self.ngrams[context].keys())
                probabilities = [self.get_prob_for_text(generated_text + [word]) for word in candidate_words]
                next_word = random.choices(candidate_words, weights=probabilities)[0]
                
            if next_word == self.UNK_token:
                break
            generated_text.append(next_word)
        
        return generated_text

    def get_perplexity(self, text: list) -> float:
        if len(text) < self.n - 1:
            raise ValueError("Text is too short for the given n-gram order.")
        
        log_prob = 0.0
        N = 0
        for i in range(len(text) - self.n + 1):
            context = text[i:i + self.n - 1]
            word = text[i + self.n - 1]
            prob = self.get_prob_for_text(context + [word])
            if prob == 0.0:
                return float('inf')
            else:
                log_prob += math.log2(self.get_prob_for_text(context + [word]))
            N += 1
        
        if N == 0:
            return float('inf')

        avg_log_prob = log_prob / N
        perplexity = 2 ** (-avg_log_prob)
        return perplexity

Zadanie (60 punktów)

  • Wybierz tekst w dowolnym języku (10 000 000 słów).
  • Podziel zbiór na train/test w proporcji 9:1.
  • Stwórz unigramowy model językowy.
  • Stwórz bigramowy model językowy.
  • Stwórz trigramowy model językowy.
  • Wymyśl 5 krótkich zdań. Dla każdego oblicz jego prawdopodobieństwo.
  • Napisz włąsnoręcznie funkcję, która liczy perplexity na korpusie i policz perplexity na każdym z modeli dla podzbiorów train i test.
  • Wygeneruj tekst, zaczynając od wymyślonych 5 początków. Postaraj się, żeby dla obu funkcji, a przynajmniej dla high_probable_next_word, teksty były orginalne.
  • Stwórz model dla korpusu z ZADANIE 1 i policz perplexity dla każdego z tekstów (zrób split 9:1) dla train i test.

Dodatkowo:

  • Dokonaj klasyfikacji za pomocą modelu językowego.
    • Znajdź duży zbiór danych dla klasyfikacji binarnej, wytrenuj osobne modele dla każdej z klas i użyj dla klasyfikacji.
  • Zastosuj wygładzanie metodą Laplace'a.

START ZADANIA

Podział korpusu na train/test

corpus = re.split(r'\s+', open("04_materialy/pan-tadeusz.txt", encoding="UTF-8").read())

split_index = int(len(corpus) * 0.9)

while corpus[split_index].endswith(('.', '?', '!')) == False:
    split_index += 1
split_index += 1

train = corpus[:split_index]
test = corpus[split_index:]

print(f"Train size: {len(train)/len(corpus)*100:.3f}%")
print(f"Test size: {len(test)/len(corpus)*100:.3f}%")
Train size: 90.004%
Test size: 9.996%

Unigramowy model języka

print("Training unigram model...")
unigram_model = Model(vocab_size = 300_000, n = 1)
unigram_model.train(train)
Training unigram model...
100%|██████████| 62189/62189 [00:00<00:00, 1066841.60it/s]
print("Generating text with unigram model... (greedy)")
text = unigram_model.generate_text(re.split(r'\s+', 'Śród takich pól przed laty,'), 20, greedy = True)
print(' '.join(text))

print("Generating text with unigram model... (non-greedy)")
text = unigram_model.generate_text(re.split(r'\s+', 'Śród takich pól przed laty,'), 7, greedy = False)
print(' '.join(text))
Generating text with unigram model... (greedy)
Śród takich pól przed laty, w w w w w w w w w w w w w w w
Generating text with unigram model... (non-greedy)
Śród takich pól przed laty, dworskiej od

Bigramowy model języka

print("Training bigram model...")
bigram_model = Model(vocab_size = 300_000, n = 2)
bigram_model.train(train)
Training bigram model...
  0%|          | 0/62188 [00:00<?, ?it/s]100%|██████████| 62188/62188 [00:00<00:00, 714486.32it/s]
print("Generating text with bigram model... (greedy)")
text = bigram_model.generate_text(re.split(r'\s+', 'Śród takich pól przed laty,'), 20, greedy = True)
print(' '.join(text))

print("Generating text with bigram model... (non-greedy)")
text = bigram_model.generate_text(re.split(r'\s+', 'Śród takich pól przed laty,'), 20, greedy = False)
print(' '.join(text))
Generating text with bigram model... (greedy)
Śród takich pól przed laty, nad nim się w tym łacniej w tym łacniej w tym łacniej w tym łacniej
Generating text with bigram model... (non-greedy)
Śród takich pól przed laty, nad Woźnego lepiej niedźwiedź kości; Pójdź, księże, w sądy podkomorskie. Dotąd mej Birbante-rokka: Oby ten

Trigramowy model języka

print("Training trigram model...")
trigram_model = Model(vocab_size = 300_000, n = 3)
trigram_model.train(train)
Training trigram model...
100%|██████████| 62187/62187 [00:00<00:00, 295370.31it/s]
print("Generating text with trigram model... (greedy)")
text = trigram_model.generate_text(re.split(r'\s+', 'Śród takich pól przed laty,'), 40, greedy = True)
print(' '.join(text))

print("Generating text with trigram model... (non-greedy)")
text = trigram_model.generate_text(re.split(r'\s+', 'Śród takich pól przed laty,'), 40, greedy = False)
print(' '.join(text))
Generating text with trigram model... (greedy)
Śród takich pól przed laty, nad brzegiem ruczaju, Na pagórku niewielkim, we brzozowym gaju, Stał dwór szlachecki, z drzewa, lecz podmurowany; Świeciły się z nim na miejscu pustym oczy swe osadzał. Dziwna rzecz! miejsca wkoło są siedzeniem dziewic, Na które
Generating text with trigram model... (non-greedy)
Śród takich pól przed laty, nad brzegiem ruczaju, Na pagórku niewielkim, we brzozowym gaju, Stał dwór szlachecki, z drzewa, gotyckiej naśladowstwo sztuki. Z wierzchu ozdoby sztuczne, nie rylcem, nie dłutem, Ale zręcznie ciesielskim wyrzezane sklutem, Krzywe jak szabasowych ramiona świeczników;

Wymyśl 5 krótkich zdań. Dla każdego oblicz jego prawdopodobieństwo.

sentences = [
    "Nikt go na polowanie",
    "Podróżny długo w oknie stał",
    "Rzekł z uśmiechem,",
    "Pod płotem wąskie, długie, wypukłe pagórki,",
    "Hrabia oczy roztworzył."
]

for sentence in sentences:
    sentence = re.split(r'\s+', sentence)
    print(f"Sentence: {' '.join(sentence)}")
    print(f"Unigram model: {unigram_model.get_prob_for_text(sentence):.10f}")
    print(f"Bigram model: {bigram_model.get_prob_for_text(sentence):.10f}")
    print(f"Trigram model: {trigram_model.get_prob_for_text(sentence):.10f}")
    print()
Sentence: Nikt go na polowanie
Unigram model: 0.0000000000
Bigram model: 0.0000027142
Trigram model: 0.2000000000

Sentence: Podróżny długo w oknie stał
Unigram model: 0.0000000000
Bigram model: 0.0000124784
Trigram model: 0.3333333333

Sentence: Rzekł z uśmiechem,
Unigram model: 0.0000000001
Bigram model: 0.0000521023
Trigram model: 1.0000000000

Sentence: Pod płotem wąskie, długie, wypukłe pagórki,
Unigram model: 0.0000000000
Bigram model: 0.0192307692
Trigram model: 1.0000000000

Sentence: Hrabia oczy roztworzył.
Unigram model: 0.0000000000
Bigram model: 0.0004479283
Trigram model: 0.5000000000

Napisz włąsnoręcznie funkcję, która liczy perplexity na korpusie i policz perplexity na każdym z modeli dla podzbiorów train i test.

print("Calculating perplexity for unigram model...")
train_perplexity = unigram_model.get_perplexity(train)
test_perplexity = unigram_model.get_perplexity(test)
print(f"Train perplexity: {train_perplexity:.10f}")
print(f"Test perplexity: {test_perplexity:.10f}")

print("\n")

print("Calculating perplexity for bigram model...")
train_perplexity = bigram_model.get_perplexity(train)
test_perplexity = bigram_model.get_perplexity(test)
print(f"Train perplexity: {train_perplexity:.10f}")
print(f"Test perplexity: {test_perplexity:.10f}")

print("\n")

print("Calculating perplexity for trigram model...")
train_perplexity = trigram_model.get_perplexity(train)
test_perplexity = trigram_model.get_perplexity(test)
print(f"Train perplexity: {train_perplexity:.10f}")
print(f"Test perplexity: {test_perplexity:.10f}")
Calculating perplexity for unigram model...
Train perplexity: 5666.4901484896
Test perplexity: inf


Calculating perplexity for bigram model...
Train perplexity: 9.1369500910
Test perplexity: inf


Calculating perplexity for trigram model...
Train perplexity: 1.1857475475
Test perplexity: inf

Wygeneruj tekst, zaczynając od wymyślonych 5 początków. Postaraj się, żeby dla obu funkcji, a przynajmniej dla high_probable_next_word, teksty były orginalne.

sentences = [
    "Nikt go na polowanie",
    "Podróżny długo w oknie stał",
    "Rzekł z uśmiechem,",
    "Pod płotem wąskie, długie, wypukłe pagórki,",
    "Hrabia oczy roztworzył."
]

for sentence in sentences:
    sentence = re.split(r'\s+', sentence)
    print(f"Sentence: {' '.join(sentence)}")
    print(f"Unigram model: {' '.join(unigram_model.generate_text(sentence, 20, greedy = True))}")
    print(f"Bigram model: {' '.join(bigram_model.generate_text(sentence, 20, greedy = True))}")
    print(f"Trigram model: {' '.join(trigram_model.generate_text(sentence, 20, greedy = True))}")
    print()
Sentence: Nikt go na polowanie
Unigram model: Nikt go na polowanie w w w w w w w w w w w w w w w w
Bigram model: Nikt go na polowanie uprosić nie jest w tym łacniej w tym łacniej w tym łacniej w tym łacniej w
Trigram model: Nikt go na polowanie uprosić nie może, Białopiotrowiczowi samemu odmówił! Bo cóż by on na waszych polowaniach łowił? Piękna byłaby

Sentence: Podróżny długo w oknie stał
Unigram model: Podróżny długo w oknie stał w w w w w w w w w w w w w w w
Bigram model: Podróżny długo w oknie stał w tym łacniej w tym łacniej w tym łacniej w tym łacniej w tym łacniej
Trigram model: Podróżny długo w oknie stał patrząc, dumając, Wonnymi powiewami kwiatów oddychając. Oblicze aż na krzaki fijołkowe skłonił, Oczyma ciekawymi po

Sentence: Rzekł z uśmiechem,
Unigram model: Rzekł z uśmiechem, w w w w w w w w w w w w w w w w w
Bigram model: Rzekł z uśmiechem, a na kształt ogromnego gmachu, Słońce ostatnich kresów nieba dochodziło, Mniej silnie, ale nie jest w tym
Trigram model: Rzekł z uśmiechem, a był to pan kapitan Ryków, Stary żołnierz, stał w bliskiej wiosce na kwaterze, Pan Sędzia nagłym

Sentence: Pod płotem wąskie, długie, wypukłe pagórki,
Unigram model: Pod płotem wąskie, długie, wypukłe pagórki, w w w w w w w w w w w w w w
Bigram model: Pod płotem wąskie, długie, wypukłe pagórki, Bez Suwarowa to nie jest w tym łacniej w tym łacniej w tym łacniej
Trigram model: Pod płotem wąskie, długie, wypukłe pagórki, Bez drzew, krzewów i kwiatów: ogród na ogórki. Pięknie wyrosły; liściem wielkim, rozłożystym, Okryły

Sentence: Hrabia oczy roztworzył.
Unigram model: Hrabia oczy roztworzył. w w w w w w w w w w w w w w w w w
Bigram model: Hrabia oczy roztworzył. Zmieszany, zdziwiony, Milczał; bo w tym łacniej w tym łacniej w tym łacniej w tym łacniej w
Trigram model: Hrabia oczy roztworzył. Zmieszany, zdziwiony, Milczał; wreszcie, zniżając swej rozmowy tony: «Przepraszam — rzekł — mój Rejencie, prawda bez wątpienia,

Dokonaj klasyfikacji za pomocą modelu językowego.

  • Znajdź duży zbiór danych dla klasyfikacji binarnej, wytrenuj osobne modele dla każdej z klas i użyj dla klasyfikacji.
from datasets import load_dataset

# Load dataset as sentiment140
dataset = load_dataset("sentiment140")
train = zip(dataset["train"]["text"], dataset["train"]["sentiment"])
test = zip(dataset["test"]["text"], dataset["test"]["sentiment"])

train = list(train)
random.shuffle(train)
train = list(train)[:20_000]

test = list(test)
random.shuffle(test)
test = list(test)[:1_000]

pos = [text.split() for text, label in train if label == 0]
neg = [text.split() for text, label in train if label > 0]

pos_model = Model(vocab_size = 6_000_000, n = 3)
neg_model = Model(vocab_size = 6_000_000, n = 3)

pos_model.train(sum(pos, []))
neg_model.train(sum(neg, []))

correct = 0
for text, label in tqdm(test):
    text = text.split()
    pos_perplexity = pos_model.get_perplexity(text)
    neg_perplexity = neg_model.get_perplexity(text)
    result = "pos" if pos_perplexity < neg_perplexity else "neg"
    if result == "pos" and label == 0:
        correct += 1
    elif result == "neg" and label > 0:
        correct += 1

accuracy = correct / len(test)
print(f"Accuracy: {accuracy:.3f}")
100%|██████████| 136592/136592 [00:00<00:00, 357332.74it/s]
100%|██████████| 126878/126878 [00:00<00:00, 299366.46it/s]
100%|██████████| 498/498 [00:00<00:00, 71213.51it/s]
Accuracy: 0.645

Zastosuj wygładzanie metodą Laplace'a.

from nltk.lm import Laplace
from nltk.lm.preprocessing import padded_everygram_pipeline
from nltk.tokenize import sent_tokenize, word_tokenize

n = 5
tokenized_text = [list(map(str.lower, word_tokenize(sent))) for sent in sent_tokenize(open("04_materialy/pan-tadeusz.txt", encoding="UTF-8").read())]
train, vocab = padded_everygram_pipeline(n, tokenized_text)

model = Laplace(n)
model.fit(train, vocab)

print(' '.join(model.generate(20, random_seed=42)))
on , gdy tańczyłem , krzyknął : » precz za drzwi złodzieja ! « że wtenczas za pułkowej okradzenie kasy

KONIEC ZADANIA

WYKONANIE ZADAŃ

Zgodnie z instrukcją 01_Kodowanie_tekstu.ipynb

Teoria informacji

Wygładzanie modeli językowych