KWT-2024/lab/lab_08.ipynb
Marek Susniak e580651f9f Lab vol.2
2024-04-23 23:54:26 +02:00

18 KiB
Raw Permalink Blame History

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.

from google.colab import drive
drive.mount('/content/drive')
Mounted at /content/drive
import zipfile
import nltk
from nltk.translate.bleu_score import corpus_bleu
from nltk import word_tokenize
import io
nltk.download('punkt')

def calculate_bleu(zip_filename, hypothesis_filename, reference_filenames):
    with zipfile.ZipFile(zip_filename, 'r') as zip_file:
        with zip_file.open(hypothesis_filename) as file:
            hypothesis = file.read().decode('utf-8')
        hypotheses = [word_tokenize(hypothesis)]

        references = []
        for ref_filename in reference_filenames:
            with zip_file.open(ref_filename) as file:
                reference = file.read().decode('utf-8')
            references.append([word_tokenize(reference)])

        return corpus_bleu(references, hypotheses)
[nltk_data] Downloading package punkt to /root/nltk_data...
[nltk_data]   Package punkt is already up-to-date!
calculate_bleu("./drive/MyDrive/data/en-pl.txt.zip", "ELRC-873-Development.en-pl.pl", ["ELRC-873-Development.en-pl.pl"])
1.0

Ć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?

from nltk.translate.bleu_score import sentence_bleu
from nltk import word_tokenize

def load_sentences(zip_filename, filepath):
    with zipfile.ZipFile(zip_filename, 'r') as zip_file:
        with zip_file.open(filepath) as file:
          sentences = [word_tokenize(file.read().decode('utf-8'))]
    return sentences

def calculate_segment_bleu(hypotheses, references, start, end):
    segment_hypotheses = hypotheses[start:end]
    segment_references = references[start:end]
    bleu_scores = [sentence_bleu([ref], hyp) for hyp, ref in zip(segment_hypotheses, segment_references)]
    return sum(bleu_scores) / len(bleu_scores) if bleu_scores else 0


def analyze_bleu(zip, hypothesis_file, reference_file, segments):
    hypotheses = load_sentences(zip, hypothesis_file)
    references = load_sentences(zip, reference_file)

    segment_results = {}
    for start, end in segments:
        segment_bleu = calculate_segment_bleu(hypotheses, references, start, end)
        segment_results[f"{start}-{end}"] = segment_bleu

    overall_bleu = calculate_segment_bleu(hypotheses, references, 0, len(hypotheses))

    return segment_results, overall_bleu
analyze_bleu("./drive/MyDrive/data/en-pl.txt.zip", "ELRC-873-Development.en-pl.pl", "ELRC-873-Development.en-pl.pl", [(0, 100), (500, 600)])
({'0-100': 1.0, '500-600': 0}, 1.0)

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.

!pip install jiwer
from jiwer import wer

def calculate_wer(reference, hypothesis):
    return wer(reference, hypothesis)
Collecting jiwer
  Downloading jiwer-3.0.3-py3-none-any.whl (21 kB)
Requirement already satisfied: click<9.0.0,>=8.1.3 in /usr/local/lib/python3.10/dist-packages (from jiwer) (8.1.7)
Collecting rapidfuzz<4,>=3 (from jiwer)
  Downloading rapidfuzz-3.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.4 MB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.4/3.4 MB 10.2 MB/s eta 0:00:00
[?25hInstalling collected packages: rapidfuzz, jiwer
Successfully installed jiwer-3.0.3 rapidfuzz-3.8.1
text_1 = load_sentences("./drive/MyDrive/data/en-pl.txt.zip", "ELRC-873-Development.en-pl.pl")[0]
text_2 = load_sentences("./drive/MyDrive/data/en-pl.txt.zip", "ELRC-873-Development.en-pl.pl")[0]

calculate_wer(text_1, text_2)
0.0

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.:

!pip install python-Levenshtein

import Levenshtein

def calculate_levenshtein(hypotheses, references):
    total_distance = 0
    count = 0
    for hyp, ref in zip(hypotheses, references):
        hyp_words = hyp.split()
        ref_words = ref.split()
        total_distance += Levenshtein.distance(hyp_words, ref_words)
        count += 1
    return total_distance / count if count != 0 else 0
Requirement already satisfied: python-Levenshtein in /usr/local/lib/python3.10/dist-packages (0.25.1)
Requirement already satisfied: Levenshtein==0.25.1 in /usr/local/lib/python3.10/dist-packages (from python-Levenshtein) (0.25.1)
Requirement already satisfied: rapidfuzz<4.0.0,>=3.8.0 in /usr/local/lib/python3.10/dist-packages (from Levenshtein==0.25.1->python-Levenshtein) (3.8.1)
calculate_levenshtein("To jest zdanie prawie dobrze przetlumaczone", "To jest zdanie bardzo dobrze przetlumaczone")
0.13953488372093023

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?
!pip install PyDictionary

from PyDictionary import PyDictionary
import re

def load_sentences(filepath):
    with open(filepath, 'r', encoding='utf-8') as file:
        sentences = [line.strip() for line in file if line.strip()]
    return sentences

def translate_words(words, dictionary, target_language='German'):
    translations = {}
    for word in words:
        try:
            translation = dictionary.translate(word, target_language)
            if translation:
                translations[word] = translation
        except Exception as e:
            print(f"Error translating {word}: {e}")
    return translations

def count_matches(translations, sentence):
    words = set(re.findall(r'\w+', sentence.lower()))
    matches = sum(1 for word in words if translations.get(word.lower()))
    return matches

def analyze_translations(source_file, mt_file, human_file):
    dictionary = PyDictionary()
    source_sentences = load_sentences(source_file)
    mt_sentences = load_sentences(mt_file)
    human_sentences = load_sentences(human_file)

    mt_matches_total = 0
    human_matches_total = 0

    for source, mt, human in zip(source_sentences, mt_sentences, human_sentences):
        source_words = re.findall(r'\w+', source.lower())
        translations = translate_words(source_words, dictionary)

        mt_matches = count_matches(translations, mt)
        human_matches = count_matches(translations, human)

        mt_matches_total += mt_matches
        human_matches_total += human_matches

        print(f"MT matches: {mt_matches}, Human matches: {human_matches}")

    print(f"Total MT matches: {mt_matches_total}")
    print(f"Total Human matches: {human_matches_total}")
Requirement already satisfied: PyDictionary in /usr/local/lib/python3.10/dist-packages (2.0.1)
Requirement already satisfied: bs4 in /usr/local/lib/python3.10/dist-packages (from PyDictionary) (0.0.2)
Requirement already satisfied: click in /usr/local/lib/python3.10/dist-packages (from PyDictionary) (8.1.7)
Requirement already satisfied: goslate in /usr/local/lib/python3.10/dist-packages (from PyDictionary) (1.5.4)
Requirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (from PyDictionary) (2.31.0)
Requirement already satisfied: beautifulsoup4 in /usr/local/lib/python3.10/dist-packages (from bs4->PyDictionary) (4.12.3)
Requirement already satisfied: futures in /usr/local/lib/python3.10/dist-packages (from goslate->PyDictionary) (3.0.5)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests->PyDictionary) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests->PyDictionary) (3.7)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests->PyDictionary) (2.0.7)
Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests->PyDictionary) (2024.2.2)
Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.10/dist-packages (from beautifulsoup4->bs4->PyDictionary) (2.5)