24 KiB
Komputerowe wspomaganie tłumaczenia
8. Wykorzystanie tłumaczenia automatycznego we wspomaganiu tłumaczenia [laboratoria]
Rafał Jaworski (2021)
W dzisiejszych czasach, niezwykle ważną techniką wspomagania tłumaczenia jest użycie tłumaczenia maszynowego. Tekst źródłowy do tłumaczenia jest najpierw tłumaczony całkowicie autommatycznie, a następnie tłumacz ludzki dokonuje korekty wyniku. Technologia tłumaczenia maszynowego jest już na tyle dojrzała, że oferuje bardzo wysoką jakość wyników. Coraz częstsze stają się scenariusze, w których ludzka korekta to niemal całkowicie machinalne (sic!) zatwierdzanie wyników tłumaczenia maszynowego. Na dzisiejszych zajęciach poznamy techniki ewaluacji tłumaczenia maszynowego oraz sprawdzania jego przydatności w procesie wspomagania tłumaczenia ludzkiego.
Jakość tłumaczenia maszynowego możemy oceniać na dwóch niezależnych płaszczyznach: dokładność i płynność. Płynność jest subiektywnie odbieranym odczuciem, że czytany tekst jest napisany językiem naturalnym i zrozumiałym. Systemy tłumaczenia maszynowego oparte na uczeniu głębokim z wykorzystaniem sieci neuronowych osiągają duży stopień płynności tłumaczenia. Niestety jednak ich dokładność nie zawsze jest równie wysoka.
Dokładność tłumaczenia maszynowego jest parametrem, który łatwiej zmierzyć. Wartość takich pomiarów daje obraz tego, jaka jest faktyczna jakość tłumaczenia maszynowego i jaka jest jego potencjalna przydatność we wspomaganiu tłumaczenia.
Najczęściej stosowaną techniką oceny tłumaczenia maszynowego jest ocena BLEU. Do obliczenia tej oceny potrzebny jest wynik tłumaczenia maszynowego oraz referencyjne tłumaczenie ludzkie wysokiej jakości.
Ćwiczenie 1: Zaimplementuj program do obliczania oceny BLEU dla korpusu w folderze data. Użyj implementacji BLEU z pakietu nltk. Dodatkowe wymaganie techniczne - napisz program tak, aby nie musiał rozpakwowywać pliku zip z korpusem na dysku.
import zipfile
import nltk.translate.bleu_score as bleu
import string
def remove_punctuation(text):
text_without_punctuations = text.translate(str.maketrans('', '', string.punctuation))
sentences = text_without_punctuations.split('\n')
return [[word.lower() for word in sentence.split()] for sentence in sentences if sentence != '']
def calculate_bleu():
zip = zipfile.ZipFile('data/corpus_corrected.zip')
files = {name: remove_punctuation(zip.read(name).decode('utf-8'))
for name in zip.namelist()}
corpus_de_human, corpus_de_nmt = files['corpus_de_human.txt'], files['corpus_de_nmt.txt']
return bleu.corpus_bleu(corpus_de_human, corpus_de_nmt)
calculate_bleu()
/opt/anaconda3/lib/python3.11/site-packages/nltk/translate/bleu_score.py:552: UserWarning: The hypothesis contains 0 counts of 3-gram overlaps. Therefore the BLEU score evaluates to 0, independently of how many N-gram overlaps of lower order it contains. Consider using lower n-gram order or use SmoothingFunction() warnings.warn(_msg) /opt/anaconda3/lib/python3.11/site-packages/nltk/translate/bleu_score.py:552: UserWarning: The hypothesis contains 0 counts of 4-gram overlaps. Therefore the BLEU score evaluates to 0, independently of how many N-gram overlaps of lower order it contains. Consider using lower n-gram order or use SmoothingFunction() warnings.warn(_msg)
3.984587822441638e-156
Ćwiczenie 2: Oblicz wartość bleu na różnych fragmentach przykładowego korpusu (np. na pierwszych 100 zdaniach, zdaniach 500-600). Czy w jakimś fragmencie korpusu jakość tłumaczenia znacząco odbiega od średniej?
def analyze_bleu(start_sentence_index, finish_sentence_index):
zip = zipfile.ZipFile('data/corpus_corrected.zip')
files = {name: remove_punctuation(zip.read(name).decode('utf-8'))
for name in zip.namelist()}
corpus_de_human, corpus_de_nmt = files['corpus_de_human.txt'][start_sentence_index:finish_sentence_index], files['corpus_de_nmt.txt'][start_sentence_index:finish_sentence_index]
return bleu.corpus_bleu(corpus_de_human, corpus_de_nmt)
print("0 to 100 - "+str(analyze_bleu(0, 100)))
print("500 to 600 - "+str(analyze_bleu(500, 600)))
print("800 to 900 - "+str(analyze_bleu(800, 900)))
print("200 to 300 - "+str(analyze_bleu(200, 300)))
0 to 100 - 4.97555004481153e-232 500 to 600 - 5.956707985683837e-232 800 to 900 - 4.774461089627919e-232 200 to 300 - 5.56331772444502e-232
/opt/anaconda3/lib/python3.11/site-packages/nltk/translate/bleu_score.py:552: UserWarning: The hypothesis contains 0 counts of 2-gram overlaps. Therefore the BLEU score evaluates to 0, independently of how many N-gram overlaps of lower order it contains. Consider using lower n-gram order or use SmoothingFunction() warnings.warn(_msg)
Inną metodą oceny jakości tłumaczenia maszynowego jest parametr WER - Word Error Rate. Definiuje się on w następujący sposób:
$WER = \frac{S+D+I}{N}=\frac{S+D+I}{S+D+C}$
gdzie:
- S - liczba substytucji (słów)
- D - liczba usunięć
- I - liczba wstawień
- C - liczba poprawnych śłów
- N - liczba słów w tłumaczeniu referencyjnym (N=S+D+C)
Miara ta jest zwykle używana w do oceny systemów automatycznego rozpoznawania mowy, jednak w kontekście wspomagania tłumaczenia może być rozumiana jako wielkość nakładu pracy tłumacza nad poprawieniem tłumaczenia maszynowego.
Ćwiczenie 3: Oblicz wartość WER dla przykładowego korpusu. Skorzystaj z gotowej implementacji WER.
from jiwer import wer
import zipfile
def calculate_wer():
ourZip = zipfile.ZipFile('data/corpus_corrected.zip')
files = {name: remove_punctuation(ourZip.read(name).decode('utf-8'))
for name in ourZip.namelist()}
corpus_de_human, corpus_de_nmt = files['corpus_de_human.txt'], files['corpus_de_nmt.txt']
sum_wer = 0
for human_sent, nmt_sent in zip(corpus_de_human, corpus_de_nmt):
sum_wer+= wer(" ".join(human_sent), " ".join(nmt_sent))
return sum_wer/(len(corpus_de_human))
calculate_wer()
0.17355216569308377
Poza wymienionymi powyżej, stosować można jeszcze inne miary oparte na porównywaniu tłumaczenia maszynowego z ludzkim. Przypomnijmy sobie jedną, którą stosowaliśmy wcześniej.
Ćwiczenie 4: Oblicz średnią wartość dystansu Levenshteina pomiędzy zdaniami przetłumaczonymi automatycznie oraz przez człowieka. Użyj implementacji z ćwiczeń nr 2.
import Levenshtein
def calculate_levenshtein():
ourZip = zipfile.ZipFile('data/corpus_corrected.zip')
files = {name: remove_punctuation(ourZip.read(name).decode('utf-8'))
for name in ourZip.namelist()}
corpus_de_human, corpus_de_nmt = files['corpus_de_human.txt'], files['corpus_de_nmt.txt']
sum_disatnce = 0
for human_element, nmt_element in zip(corpus_de_human, corpus_de_nmt):
sum_disatnce+= Levenshtein.distance(human_element, nmt_element)
return sum_disatnce/(len(corpus_de_human))
calculate_levenshtein()
2.653
A teraz sprawdźmy coś jeszcze. W danych przykładowego korpusu znajduje się także angielski tekst źródłowy. Teoretycznie, dobre tłumaczenie niemieckie powinno zawierać jak najwięcej słów z angielskiego źródła. Wykonajmy najstępujący eksperyment:
Ćwiczenie 5: Dla każdej trójki zdań z korpusu przykładowego wykonaj następujące kroki:
- Przetłumacz każde angielskie słowo na niemiecki przy użyciu modułu PyDictionary.
- Sprawdź, które z niemieckich tłumaczeń zawiera więcej spośród tych przetłumaczonych słów - automatyczne, czy ludzkie. Następnie wypisz statystyki zbiorcze. Które tłumaczenie zawiera więcej słownikowych tłumaczeń słów ze źródła?
from PyDictionary import PyDictionary
import zipfile
def transalate(word_list):
transalted_words = {}
for word in word_list:
translation = PyDictionary().translate(word, 'German')
if translation:
transalted_words[word] = translation
return transalted_words
def analyze_translations():
ourZip = zipfile.ZipFile('data/corpus_corrected.zip')
files = {name: remove_punctuation(ourZip.read(name).decode('utf-8'))
for name in ourZip.namelist()}
corpus_de_human, corpus_de_nmt, corpus_en = files['corpus_de_human.txt'], files['corpus_de_nmt.txt'], files['corpus_en.txt']
for human_element, nmt_element, element in zip(corpus_de_human, corpus_de_nmt, corpus_en):
transalted_words = transalate(element)
words = set(re.findall(r'\w+', sentence.lower()))
matches = sum(1 for word in words if translations.get(word.lower()))
# tranlsations = [PyDictionary().translate(word, 'de') for word in element]
analyze_translations()
press Invalid Word None h Invalid Word None while Invalid Word None in Invalid Word None review Invalid Word None mode Invalid Word None to Invalid Word None display Invalid Word None keyboard Invalid Word None shortcuts Invalid Word None for Invalid Word None working Invalid Word None in Invalid Word None review Invalid Word None mode Invalid Word None click Invalid Word None remove Invalid Word None shortcut Invalid Word None selects Invalid Word None the Invalid Word None nonselected Invalid Word None areas Invalid Word None for Invalid Word None more Invalid Word None information Invalid Word None see Invalid Word None screen Invalid Word None class Invalid Word None form Invalid Word None class Invalid Word None and Invalid Word None slide Invalid Word None class Invalid Word None in Invalid Word None the Invalid Word None actionscript Invalid Word None 20 Invalid Word None components Invalid Word None language Invalid Word None reference Invalid Word None clicking Invalid Word None the Invalid Word None mouse Invalid Word None retracts Invalid Word None the Invalid Word None bezier Invalid Word None handles Invalid Word None and Invalid Word None causes Invalid Word None the Invalid Word None curved Invalid Word None path Invalid Word None across Invalid Word None the Invalid Word None anchor Invalid Word None point Invalid Word None to Invalid Word None revert Invalid Word None to Invalid Word None straight Invalid Word None segments Invalid Word None an Invalid Word None array Invalid Word None of Invalid Word None keyvalue Invalid Word None pairs Invalid Word None that Invalid Word None represent Invalid Word None the Invalid Word None information Invalid Word None in Invalid Word None the Invalid Word None ilst Invalid Word None atom Invalid Word None which Invalid Word None is Invalid Word None the Invalid Word None equivalent Invalid Word None of Invalid Word None id3 Invalid Word None tags Invalid Word None for Invalid Word None mp4 Invalid Word None files Invalid Word None when Invalid Word None you Invalid Word None add Invalid Word None 3d Invalid Word None comments Invalid Word None to Invalid Word None the Invalid Word None default Invalid Word None view Invalid Word None of Invalid Word None a Invalid Word None model Invalid Word None a Invalid Word None new Invalid Word None view Invalid Word None called Invalid Word None 3dcommentview Invalid Word None is Invalid Word None created Invalid Word None when Invalid Word None you Invalid Word None re Invalid Word None finished Invalid Word None setting Invalid Word None options Invalid Word None click Invalid Word None set Invalid Word None default Invalid Word None set Invalid Word None the Invalid Word None location Invalid Word None for Invalid Word None the Invalid Word None stroke Invalid Word None in Invalid Word None relationship Invalid Word None to Invalid Word None the Invalid Word None marquee Invalid Word None by Invalid Word None choosing Invalid Word None inside Invalid Word None center Invalid Word None or Invalid Word None outside