Laboratorium #12

This commit is contained in:
Marek Susniak 2024-06-15 16:02:09 +02:00
parent 9e978980fd
commit 1ff5f22caf

View File

@ -3,7 +3,9 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "virtual-accreditation", "id": "virtual-accreditation",
"metadata": {}, "metadata": {
"id": "virtual-accreditation"
},
"source": [ "source": [
"![Logo 1](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech1.jpg)\n", "![Logo 1](https://git.wmi.amu.edu.pl/AITech/Szablon/raw/branch/master/Logotyp_AITech1.jpg)\n",
"<div class=\"alert alert-block alert-info\">\n", "<div class=\"alert alert-block alert-info\">\n",
@ -18,7 +20,9 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "featured-afghanistan", "id": "featured-afghanistan",
"metadata": {}, "metadata": {
"id": "featured-afghanistan"
},
"source": [ "source": [
"Badania nad komputerowym wspomaganiem tłumaczenia często prowadzone są przy użyciu metodologii testowania interfejsów użytkownika - UI/UX testing. Program typu CAT traktuje się wówczas jak każdy inny program komputerowy i przeprowadza testy wydajności i użyteczności." "Badania nad komputerowym wspomaganiem tłumaczenia często prowadzone są przy użyciu metodologii testowania interfejsów użytkownika - UI/UX testing. Program typu CAT traktuje się wówczas jak każdy inny program komputerowy i przeprowadza testy wydajności i użyteczności."
] ]
@ -26,7 +30,9 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "severe-protein", "id": "severe-protein",
"metadata": {}, "metadata": {
"id": "severe-protein"
},
"source": [ "source": [
"Testy takie prowadzone są zawsze na użytkownikach końcowych, w tym przypadku - na tłumaczach. Podstawowym celem testów jest próba zaobserwowania faktycznego sposobu pracy tłumacza - które funkcje programu są przez niego wykorzystywane najczęściej, jakich innych narzędzi poza CAT-em używa on do swojej pracy, które funkcje programu działają zgodnie, a które niezgodnie z intuicją użytkownika oraz wiele innych czynników. Aby wszystkie te analizy były możliwe, konieczne jest zgromadzenie jak największej ilości danych dotyczących przebiegu testu." "Testy takie prowadzone są zawsze na użytkownikach końcowych, w tym przypadku - na tłumaczach. Podstawowym celem testów jest próba zaobserwowania faktycznego sposobu pracy tłumacza - które funkcje programu są przez niego wykorzystywane najczęściej, jakich innych narzędzi poza CAT-em używa on do swojej pracy, które funkcje programu działają zgodnie, a które niezgodnie z intuicją użytkownika oraz wiele innych czynników. Aby wszystkie te analizy były możliwe, konieczne jest zgromadzenie jak największej ilości danych dotyczących przebiegu testu."
] ]
@ -34,7 +40,9 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "constant-underground", "id": "constant-underground",
"metadata": {}, "metadata": {
"id": "constant-underground"
},
"source": [ "source": [
"Testy są przede wszystkim nagrywane. Nagrywany jest zarówno ekran komputera (screen capture), jak i sam użytkownik pracujący przy komputerze. To jednak nie wszystko - często stosuje się specjalne techniki eye-trackingu, które są w stanie określić, w który punk ekranu użytkownik aktualnie patrzy. Dane pozyskane w ten sposób używane są do analizy czasu znalezienia przez użytkownika potrzebnej mu funkcji oraz zidentyfikowania miejsc, gdzie tej funkcji poszukiwał. Można również wyznaczyć obszary ekranu, które często skupiają uwagę użytkownika." "Testy są przede wszystkim nagrywane. Nagrywany jest zarówno ekran komputera (screen capture), jak i sam użytkownik pracujący przy komputerze. To jednak nie wszystko - często stosuje się specjalne techniki eye-trackingu, które są w stanie określić, w który punk ekranu użytkownik aktualnie patrzy. Dane pozyskane w ten sposób używane są do analizy czasu znalezienia przez użytkownika potrzebnej mu funkcji oraz zidentyfikowania miejsc, gdzie tej funkcji poszukiwał. Można również wyznaczyć obszary ekranu, które często skupiają uwagę użytkownika."
] ]
@ -42,7 +50,9 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "analyzed-lodging", "id": "analyzed-lodging",
"metadata": {}, "metadata": {
"id": "analyzed-lodging"
},
"source": [ "source": [
"Dodatkowo stosuje się jeszcze jedną technikę, która jest szczególnie przydatna z punktu widzenia analizy procesu tłumaczenia. Wykonuje się pełny key logging, tj. zapisuje się każde uderzenie użytkownika w dowolny klawisz na klawiaturze wraz z precyzyjnym czasem tego uderzenia. Dane pozyskane w ten sposób pozwalają na przeprowadzenie szeregu interesujących analiz." "Dodatkowo stosuje się jeszcze jedną technikę, która jest szczególnie przydatna z punktu widzenia analizy procesu tłumaczenia. Wykonuje się pełny key logging, tj. zapisuje się każde uderzenie użytkownika w dowolny klawisz na klawiaturze wraz z precyzyjnym czasem tego uderzenia. Dane pozyskane w ten sposób pozwalają na przeprowadzenie szeregu interesujących analiz."
] ]
@ -50,7 +60,9 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "incredible-stress", "id": "incredible-stress",
"metadata": {}, "metadata": {
"id": "incredible-stress"
},
"source": [ "source": [
"Zapoznajmy się najpierw z programem typu key logger:" "Zapoznajmy się najpierw z programem typu key logger:"
] ]
@ -58,7 +70,9 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "arctic-horror", "id": "arctic-horror",
"metadata": {}, "metadata": {
"id": "arctic-horror"
},
"source": [ "source": [
"`sudo pip3 install keyboard`" "`sudo pip3 install keyboard`"
] ]
@ -67,9 +81,13 @@
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": null,
"id": "broken-workstation", "id": "broken-workstation",
"metadata": {}, "metadata": {
"id": "broken-workstation"
},
"outputs": [], "outputs": [],
"source": [ "source": [
"!pip install keyboard\n",
"\n",
"import keyboard\n", "import keyboard\n",
"\n", "\n",
"\n", "\n",
@ -83,7 +101,9 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "polish-census", "id": "polish-census",
"metadata": {}, "metadata": {
"id": "polish-census"
},
"source": [ "source": [
"UWAGA! Aby uruchomić powyższy kod na Linuxie konieczne są uprawnienia administratora (pytanie poza konkursem - dlaczego?)" "UWAGA! Aby uruchomić powyższy kod na Linuxie konieczne są uprawnienia administratora (pytanie poza konkursem - dlaczego?)"
] ]
@ -91,15 +111,40 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "incoming-hands", "id": "incoming-hands",
"metadata": {}, "metadata": {
"id": "incoming-hands"
},
"source": [ "source": [
"### Ćwiczenie 1: Wykorzystując powyższy kod napisz keylogger, który zapisuje wszystkie uderzenia w klawisze do pliku. Format pliku jest dowolny, każdy wpis musi zawierać precyzyjną godzinę uderzenia oraz uderzony klawisz. Uruchom program i przepisz paragraf dowolnie wybranego tekstu." "### Ćwiczenie 1: Wykorzystując powyższy kod napisz keylogger, który zapisuje wszystkie uderzenia w klawisze do pliku. Format pliku jest dowolny, każdy wpis musi zawierać precyzyjną godzinę uderzenia oraz uderzony klawisz. Uruchom program i przepisz paragraf dowolnie wybranego tekstu.\n"
] ]
}, },
{
"cell_type": "code",
"source": [
"import keyboard\n",
"import datetime\n",
"\n",
"# Funkcja zapisu do pliku\n",
"def log_key(event):\n",
" with open(\"key_log.txt\", \"a\") as log_file:\n",
" log_file.write(f\"{datetime.datetime.now()}: {event.name}\\n\")\n",
"\n",
"keyboard.on_release(callback=log_key)\n",
"keyboard.wait()"
],
"metadata": {
"id": "CjzM4sFTGIfJ"
},
"id": "CjzM4sFTGIfJ",
"execution_count": null,
"outputs": []
},
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "valuable-bearing", "id": "valuable-bearing",
"metadata": {}, "metadata": {
"id": "valuable-bearing"
},
"source": [ "source": [
"Celem powyższego ćwiczenia jest pozyskanie danych testowych. Dalsze analizy będziemy prowadzili już bez key loggera, starając się korzystać jedynie z danych zapisanych w pliku. Oczywiście, jeśli zajdzie taka konieczność, można w każdej chwili wygenerować sobie nowy plik." "Celem powyższego ćwiczenia jest pozyskanie danych testowych. Dalsze analizy będziemy prowadzili już bez key loggera, starając się korzystać jedynie z danych zapisanych w pliku. Oczywiście, jeśli zajdzie taka konieczność, można w każdej chwili wygenerować sobie nowy plik."
] ]
@ -107,26 +152,85 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "boxed-maple", "id": "boxed-maple",
"metadata": {}, "metadata": {
"id": "boxed-maple"
},
"source": [ "source": [
"### Ćwiczenie 2: Napisz program, który wyliczy średnią prędkość pisania. Wykryj, kiedy użytkownik zaczął pisać. Nie bierz pod uwagę przerw dłuższych niż 5 sekund. Podaj prędkość pisania w znakach na minutę oraz słowach na minutę." "### Ćwiczenie 2: Napisz program, który wyliczy średnią prędkość pisania. Wykryj, kiedy użytkownik zaczął pisać. Nie bierz pod uwagę przerw dłuższych niż 5 sekund. Podaj prędkość pisania w znakach na minutę oraz słowach na minutę."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 1, "execution_count": null,
"id": "possible-holder", "id": "possible-holder",
"metadata": {}, "metadata": {
"id": "possible-holder"
},
"outputs": [], "outputs": [],
"source": [ "source": [
"def calculate_typing_speed():\n", "import datetime\n",
" return 0" "\n",
"def read_log_file(file_path):\n",
" with open(file_path, \"r\") as file:\n",
" lines = file.readlines()\n",
" return lines\n",
"\n",
"def parse_log_lines(lines):\n",
" key_events = []\n",
" for line in lines:\n",
" timestamp_str, key = line.strip().split(\": \")\n",
" timestamp = datetime.datetime.strptime(timestamp_str, \"%Y-%m-%d %H:%M:%S.%f\")\n",
" key_events.append((timestamp, key))\n",
" return key_events\n",
"\n",
"def calculate_typing_speed(key_events):\n",
" total_chars = 0\n",
" total_words = 0\n",
" start_time = None\n",
" end_time = None\n",
" last_time = None\n",
" time_intervals = []\n",
"\n",
" for timestamp, key in key_events:\n",
" if start_time is None:\n",
" start_time = timestamp\n",
" if last_time is not None and (timestamp - last_time).total_seconds() > 5:\n",
" start_time = timestamp\n",
" last_time = timestamp\n",
" total_chars += 1\n",
" if key == 'space':\n",
" total_words += 1\n",
" end_time = timestamp\n",
"\n",
" if total_chars == 0:\n",
" return 0, 0 # No typing detected\n",
"\n",
" total_time = (end_time - start_time).total_seconds() / 60 # in minutes\n",
" cpm = total_chars / total_time\n",
" wpm = total_words / total_time\n",
"\n",
" return cpm, wpm\n",
"\n",
"\n",
"\n",
"def calculate_typing_speed(file_name):\n",
" log_lines = read_log_file(file_name)\n",
" key_events = parse_log_lines(log_lines)\n",
" cpm, wpm = calculate_typing_speed(key_events)\n",
"\n",
" print(f\"Średnia prędkość pisania: {cpm:.2f} znaków na minutę\")\n",
" print(f\"Średnia prędkość pisania: {wpm:.2f} słów na minutę\")\n",
" return cpm, wpm\n",
"\n",
"calculate_typing_speed(\"key_log.txt\")"
] ]
}, },
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "ceramic-birth", "id": "ceramic-birth",
"metadata": {}, "metadata": {
"id": "ceramic-birth"
},
"source": [ "source": [
"Wróćmy teraz do procesu tłumaczenia. Analiza uderzeń klawiszy wykonanych podczas tłumaczenia pozwala wykryć dłuższe pauzy. Pauzy te najczęściej wskazują miejsca, w których tłumacz musi się głębiej zastanowić nad tłumaczeniem danego słowa lub frazy. Przerwę tę wykorzystuje na przykład na sprawdzenie tłumaczenia lub definicji w słowniku, przeglądanie wyników z pamięci tłumaczeń lub korzystanie z innych pomocy (eye-tracking mógłby w tym przypadku rozstrzygnąć, czym w istocie zajmuje się w tym momencie tłuamcz). Jest też możliwe, że tłumacz poświęca pauzę na tzw. cognitive pause-and-unload - rodzaj zamyślenia, pozwalający oczyścić myśli. Z punktu widzenia projektowania systemu wspomagającego tłumaczenie niezwykle istotna jest informacja, nad czym tłumacz musi się dłużej zastanowić. Minimalizacja liczby i czasu trwania takich przerw jest szansą na usprawnienie procesu tłumaczenia." "Wróćmy teraz do procesu tłumaczenia. Analiza uderzeń klawiszy wykonanych podczas tłumaczenia pozwala wykryć dłuższe pauzy. Pauzy te najczęściej wskazują miejsca, w których tłumacz musi się głębiej zastanowić nad tłumaczeniem danego słowa lub frazy. Przerwę tę wykorzystuje na przykład na sprawdzenie tłumaczenia lub definicji w słowniku, przeglądanie wyników z pamięci tłumaczeń lub korzystanie z innych pomocy (eye-tracking mógłby w tym przypadku rozstrzygnąć, czym w istocie zajmuje się w tym momencie tłuamcz). Jest też możliwe, że tłumacz poświęca pauzę na tzw. cognitive pause-and-unload - rodzaj zamyślenia, pozwalający oczyścić myśli. Z punktu widzenia projektowania systemu wspomagającego tłumaczenie niezwykle istotna jest informacja, nad czym tłumacz musi się dłużej zastanowić. Minimalizacja liczby i czasu trwania takich przerw jest szansą na usprawnienie procesu tłumaczenia."
] ]
@ -134,20 +238,72 @@
{ {
"cell_type": "markdown", "cell_type": "markdown",
"id": "great-cable", "id": "great-cable",
"metadata": {}, "metadata": {
"id": "great-cable"
},
"source": [ "source": [
"### Ćwiczenie 3: Napisz program do wykrywania przerw w pisaniu. Raportuj długość oraz miejsce wystąpienia przerwy podając 20-znakowy kontekst z każdej strony. Wykryj każdą przerwę dłuższą niż 3 sekundy, posortuj wyniki malejąco po długości przerwy." "### Ćwiczenie 3: Napisz program do wykrywania przerw w pisaniu. Raportuj długość oraz miejsce wystąpienia przerwy podając 20-znakowy kontekst z każdej strony. Wykryj każdą przerwę dłuższą niż 3 sekundy, posortuj wyniki malejąco po długości przerwy."
] ]
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": 2, "execution_count": 4,
"id": "close-riverside", "id": "close-riverside",
"metadata": {}, "metadata": {
"outputs": [], "colab": {
"base_uri": "https://localhost:8080/",
"height": 305
},
"id": "close-riverside",
"outputId": "f26a611b-3cba-4e96-f922-8af210c7bf45"
},
"outputs": [
{
"output_type": "error",
"ename": "NameError",
"evalue": "name 'read_log_file' is not defined",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-4-edc7383ffb03>\u001b[0m in \u001b[0;36m<cell line: 32>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 30\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 31\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 32\u001b[0;31m \u001b[0mreport_pauses\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfind_pauses\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\"key_log.txt\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m<ipython-input-4-edc7383ffb03>\u001b[0m in \u001b[0;36mfind_pauses\u001b[0;34m(file_name, min_pause_duration)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfind_pauses\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_name\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mmin_pause_duration\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m3\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mlog_lines\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mread_log_file\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfile_name\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0mkey_events\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mparse_log_lines\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mlog_lines\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mpauses\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mfind_pauses\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mkey_events\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mprevious_time\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mNameError\u001b[0m: name 'read_log_file' is not defined"
]
}
],
"source": [ "source": [
"def find_pauses():\n", "def find_pauses(file_name, min_pause_duration=3):\n",
" return []" " log_lines = read_log_file(file_name)\n",
" key_events = parse_log_lines(log_lines)\n",
" pauses = find_pauses(key_events)\n",
" previous_time = None\n",
" context = []\n",
"\n",
" for i, (timestamp, key) in enumerate(key_events):\n",
" if previous_time:\n",
" pause_duration = (timestamp - previous_time).total_seconds()\n",
" if pause_duration > min_pause_duration:\n",
" start_context = max(0, i-20)\n",
" end_context = min(len(key_events), i+20)\n",
" context_before = ''.join(key[1] for key in key_events[start_context:i])\n",
" context_after = ''.join(key[1] for key in key_events[i:end_context])\n",
" pauses.append((pause_duration, context_before, context_after))\n",
" previous_time = timestamp\n",
" context.append(key)\n",
"\n",
" pauses.sort(reverse=True, key=lambda x: x[0])\n",
" return pauses\n",
"\n",
"def report_pauses(pauses):\n",
" for pause in pauses:\n",
" duration, context_before, context_after = pause\n",
" print(f\"Przerwa: {duration:.2f} sekund\")\n",
" print(f\"Kontekst przed: {context_before[-20:]}\")\n",
" print(f\"Kontekst po: {context_after[:20]}\")\n",
" print(\"-\" * 40)\n",
"\n",
"\n",
"report_pauses(find_pauses(\"key_log.txt\"))\n"
] ]
} }
], ],
@ -174,6 +330,9 @@
"nbconvert_exporter": "python", "nbconvert_exporter": "python",
"pygments_lexer": "ipython3", "pygments_lexer": "ipython3",
"version": "3.8.10" "version": "3.8.10"
},
"colab": {
"provenance": []
} }
}, },
"nbformat": 4, "nbformat": 4,