KWT-2024/lab/lab_08.ipynb
Adam Stelmaszyk 7fd3eb01b3 updated
2024-05-04 14:59:05 +02:00

24 KiB

Logo 1

Komputerowe wspomaganie tłumaczenia

8. Wykorzystanie tłumaczenia automatycznego we wspomaganiu tłumaczenia [laboratoria]

Rafał Jaworski (2021)

Logo 2

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