26 KiB
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