mirror of
https://github.com/kuhyx/WUT_Computer_Science.git
synced 2026-11-10 20:27:08 +01:00
NLP: zero findings, and two real bugs found on the way
384 findings to none across ten scripts. Most of it is annotation and
docstring work, but the lint surfaced two defects that had nothing to do with
style:
- `gpt_chunks.py` stripped its GPT preamble with
`input_string.strip("iSTS chunks:")`. str.strip takes a SET OF CHARACTERS,
not a prefix, so it also ate any leading i/S/T/s/c/h/u/n/k/space/colon:
"iSTS chunks: sentence one" came out as "entence one", silently losing the
first letter of every chunked sentence. Now removeprefix. ruff found it as
B005 + PLE1310.
- `is_numbered_line_format` had `for string in string` in its list branch,
which raises NameError the moment a list is passed (F821).
- `processing.test_XML()` read a module-level `alignments_data` that does not
exist at module scope; it only ever ran when pasted into a live session. It
takes the data as an argument now and is renamed `log_first_alignment` so
pytest stops collecting a non-test as a test.
Also real rather than cosmetic:
- The OpenAI key was a placeholder string literal in three files, one of them
carrying a "Replace me with os.environ['API_KEY']" comment. All three read
OPENAI_API_KEY now and say so when it is missing -- as committed, none of
these scripts could actually run.
- `rating.rate_similarity` was submitted unfinished: it built a client, never
touched its two arguments and returned None despite `-> str`. It now makes
the same call the rest of the package makes.
- The two 2 kB alignment prompts and one 344-character chunking prompt moved
to prompts/*.txt. Wrapping them in source would have put newlines into the
middle of the prompt.
- `format_alignments` parsed GPT's replies with `eval()`; the file holds one
repr()'d string per line, so ast.literal_eval is both sufficient and safe.
- NLP/__init__.py deleted -- it made the course directory a package named
"NLP", which is an invalid module name (N999) and which nothing imports;
the scripts use plain `import processing`.
INP001 is settled the same way throughout: a coursework file run as
`python x.py` gets a shebang and the executable bit, which is what it is,
rather than an __init__.py that would make a course code into a module name.
Verified: every module compiles and `./run.sh run Programming/NLP` is clean.
The scripts themselves need the SemEval corpus and a paid API key, so they
are not executed here.
Repo-wide: 3,159 findings -> 2,775.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172dj34FDpao6wsix8ZuCzE
This commit is contained in:
parent
a8a2710ebd
commit
b017c7500e
43
Programming/NLP/compare_chunks.py
Normal file → Executable file
43
Programming/NLP/compare_chunks.py
Normal file → Executable file
@ -1,10 +1,23 @@
|
|||||||
def reformat_sentence(sentence):
|
#!/usr/bin/env python3
|
||||||
|
"""Compare kuhy's GPT chunkings against the SemEval gold standard.
|
||||||
|
|
||||||
|
Reads one GPT output file and the corresponding gold chunk file, normalises
|
||||||
|
the GPT side, and reports every sentence where the two disagree on words.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def reformat_sentence(sentence: str) -> str:
|
||||||
"""Reformats a sentence by replacing slashes '/' with ' ]'."""
|
"""Reformats a sentence by replacing slashes '/' with ' ]'."""
|
||||||
return sentence.replace("/", " ]")
|
return sentence.replace("/", " ]")
|
||||||
|
|
||||||
|
|
||||||
def insert_commas(sentence, reference_sentence):
|
def insert_commas(sentence: str, reference_sentence: str) -> str:
|
||||||
"""Inserts commas into 'sentence' at the same positions as they appear in 'reference_sentence'."""
|
"""Insert commas into *sentence* where *reference_sentence* has them."""
|
||||||
# Splitting the sentences into chunks
|
# Splitting the sentences into chunks
|
||||||
sentence_chunks = sentence.split(" ] [ ")
|
sentence_chunks = sentence.split(" ] [ ")
|
||||||
reference_chunks = reference_sentence.split(" ] [ ")
|
reference_chunks = reference_sentence.split(" ] [ ")
|
||||||
@ -18,11 +31,13 @@ def insert_commas(sentence, reference_sentence):
|
|||||||
return " ] [ ".join(sentence_chunks)
|
return " ] [ ".join(sentence_chunks)
|
||||||
|
|
||||||
|
|
||||||
def process_sentences(sentences1, sentences2):
|
def process_sentences(
|
||||||
"""Processes two lists of sentences according to the specified rules."""
|
sentences1: list[str], sentences2: list[str]
|
||||||
|
) -> list[dict[str, object]]:
|
||||||
|
"""Compare each GPT chunking against its gold counterpart."""
|
||||||
processed_sentences = []
|
processed_sentences = []
|
||||||
|
|
||||||
for sentence1, sentence2 in zip(sentences1, sentences2):
|
for sentence1, sentence2 in zip(sentences1, sentences2, strict=False):
|
||||||
# Reformat the first sentence
|
# Reformat the first sentence
|
||||||
reformatted_sentence1 = reformat_sentence(sentence1)
|
reformatted_sentence1 = reformat_sentence(sentence1)
|
||||||
reformatted_sentence1 = insert_commas(reformatted_sentence1, sentence2)
|
reformatted_sentence1 = insert_commas(reformatted_sentence1, sentence2)
|
||||||
@ -50,7 +65,7 @@ def process_sentences(sentences1, sentences2):
|
|||||||
if "[ . ]" in sentence2:
|
if "[ . ]" in sentence2:
|
||||||
output_sentence += " [ . ]"
|
output_sentence += " [ . ]"
|
||||||
|
|
||||||
# Adding the differences and the output sentence to the processed_sentences list
|
# Record the differences alongside the merged sentence.
|
||||||
processed_sentences.append(
|
processed_sentences.append(
|
||||||
{
|
{
|
||||||
"sentence1": reformatted_sentence1.strip(),
|
"sentence1": reformatted_sentence1.strip(),
|
||||||
@ -60,21 +75,21 @@ def process_sentences(sentences1, sentences2):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if diff and diff != {"."}:
|
if diff and diff != {"."}:
|
||||||
print("Difference found!")
|
logger.info("Difference found!")
|
||||||
print("Sentence 1:", reformatted_sentence1.strip())
|
logger.info("Sentence 1: %s", reformatted_sentence1.strip())
|
||||||
print("Sentence 2:", sentence2.strip())
|
logger.info("Sentence 2: %s", sentence2.strip())
|
||||||
print("Differences:", diff)
|
logger.info("Differences: %s", diff)
|
||||||
print("\n")
|
logger.info("\n")
|
||||||
|
|
||||||
return processed_sentences
|
return processed_sentences
|
||||||
|
|
||||||
|
|
||||||
# First, let's open and read the contents of both files to understand their structure and content.
|
# The GPT output and the gold chunking for the same corpus half.
|
||||||
file_path_1 = "test_goldStandard/images/images-chunks-gpt-one.txt"
|
file_path_1 = "test_goldStandard/images/images-chunks-gpt-one.txt"
|
||||||
file_path_2 = "test_goldStandard/images/STSint.testinput.images.sent1.chunk.txt"
|
file_path_2 = "test_goldStandard/images/STSint.testinput.images.sent1.chunk.txt"
|
||||||
|
|
||||||
# Reading the files
|
# Reading the files
|
||||||
with open(file_path_1) as file1, open(file_path_2) as file2:
|
with Path(file_path_1).open() as file1, Path(file_path_2).open() as file2:
|
||||||
sentences_file_1 = file1.readlines()
|
sentences_file_1 = file1.readlines()
|
||||||
sentences_file_2 = file2.readlines()
|
sentences_file_2 = file2.readlines()
|
||||||
|
|
||||||
|
|||||||
20
Programming/NLP/create_alignments.py
Normal file → Executable file
20
Programming/NLP/create_alignments.py
Normal file → Executable file
@ -1,3 +1,8 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Ask GPT to align the headline chunks, and save the raw replies."""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import gpt_alignment
|
import gpt_alignment
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import processing
|
import processing
|
||||||
@ -6,9 +11,6 @@ import processing
|
|||||||
file_path = "alignments_unformatted_headlines.txt"
|
file_path = "alignments_unformatted_headlines.txt"
|
||||||
|
|
||||||
# paths to students answers database
|
# paths to students answers database
|
||||||
# chunked_path1 = "test_goldStandard/student/STSint.testinput.answers-students.sent1.chunk.txt"
|
|
||||||
# chunked_path2 = "test_goldStandard/student/STSint.testinput.answers-students.sent2.chunk.txt"
|
|
||||||
# alignment_path = "test_goldStandard/student/STSint.testinput.answers-students.wa"
|
|
||||||
|
|
||||||
# paths to headlines
|
# paths to headlines
|
||||||
chunked_path1 = "test_goldStandard/headlines/STSint.testinput.headlines.sent1.chunk.txt"
|
chunked_path1 = "test_goldStandard/headlines/STSint.testinput.headlines.sent1.chunk.txt"
|
||||||
@ -16,23 +18,19 @@ chunked_path2 = "test_goldStandard/headlines/STSint.testinput.headlines.sent1.ch
|
|||||||
alignment_path = "test_goldStandard/headlines/STSint.testinput.headlines.wa"
|
alignment_path = "test_goldStandard/headlines/STSint.testinput.headlines.wa"
|
||||||
|
|
||||||
# load data
|
# load data
|
||||||
# studentAnserws = processing.load_sentences(studentAnswers1_path, studentAnswers1_path)
|
|
||||||
goldstandard_chunked = processing.load_chunked(chunked_path1, chunked_path2)
|
goldstandard_chunked = processing.load_chunked(chunked_path1, chunked_path2)
|
||||||
goldstandard_alignment = processing.load_alignment(alignment_path)
|
goldstandard_alignment = processing.load_alignment(alignment_path)
|
||||||
|
|
||||||
# get a nice anwser-student table
|
# get a nice anwser-student table
|
||||||
data = pd.merge(
|
data = pd.DataFrame.merge(
|
||||||
goldstandard_chunked, goldstandard_alignment, left_index=True, right_index=True
|
goldstandard_chunked, goldstandard_alignment, left_index=True, right_index=True
|
||||||
)
|
)
|
||||||
# print(data)
|
|
||||||
|
|
||||||
data_for_chat, indexes = processing.get_chunks_as_text(data)
|
data_for_chat, indexes = processing.get_chunks_as_text(data)
|
||||||
|
|
||||||
client = gpt_alignment.createGPT()
|
client = gpt_alignment.create_gpt()
|
||||||
responses = []
|
responses = [gpt_alignment.call_api(client, chunks) for chunks in data_for_chat]
|
||||||
for i in range(len(data_for_chat)):
|
|
||||||
responses.append(gpt_alignment.callApi(client, data_for_chat[i]))
|
|
||||||
|
|
||||||
# Writing to the file with repr() to preserve "\n" characters
|
# Writing to the file with repr() to preserve "\n" characters
|
||||||
with open(file_path, "w") as file:
|
with Path(file_path).open("w") as file:
|
||||||
file.writelines(repr(string) + "\n" for string in responses)
|
file.writelines(repr(string) + "\n" for string in responses)
|
||||||
|
|||||||
36
Programming/NLP/create_alignments_with_examples.py
Normal file → Executable file
36
Programming/NLP/create_alignments_with_examples.py
Normal file → Executable file
@ -1,14 +1,19 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Ask GPT to align chunks, few-shot prompted with gold examples."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import gpt_alignment
|
import gpt_alignment
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import processing
|
import processing
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# Specify the output file path
|
# Specify the output file path
|
||||||
file_path = "alignments_with_training_headlines2.wa"
|
file_path = "alignments_with_training_headlines2.wa"
|
||||||
|
|
||||||
# paths to students andsewrs database
|
# paths to students andsewrs database
|
||||||
# chunked_path1 = "test_goldStandard/student/STSint.testinput.answers-students.sent1.chunk.txt"
|
|
||||||
# chunked_path2 = "test_goldStandard/student/STSint.testinput.answers-students.sent2.chunk.txt"
|
|
||||||
# alignment_path = "test_goldStandard/student/STSint.testinput.answers-students.wa"
|
|
||||||
|
|
||||||
# paths to headlines
|
# paths to headlines
|
||||||
chunked_path1 = "test_goldStandard/headlines/STSint.testinput.headlines.sent1.chunk.txt"
|
chunked_path1 = "test_goldStandard/headlines/STSint.testinput.headlines.sent1.chunk.txt"
|
||||||
@ -20,10 +25,9 @@ goldstandard_chunked = processing.load_chunked(chunked_path1, chunked_path2)
|
|||||||
goldstandard_alignment = processing.load_alignment(alignment_path)
|
goldstandard_alignment = processing.load_alignment(alignment_path)
|
||||||
|
|
||||||
# get a nice anwser-student table
|
# get a nice anwser-student table
|
||||||
data = pd.merge(
|
data = pd.DataFrame.merge(
|
||||||
goldstandard_chunked, goldstandard_alignment, left_index=True, right_index=True
|
goldstandard_chunked, goldstandard_alignment, left_index=True, right_index=True
|
||||||
)
|
)
|
||||||
# print(data)
|
|
||||||
|
|
||||||
train, test = processing.generate_train_test_split(data)
|
train, test = processing.generate_train_test_split(data)
|
||||||
|
|
||||||
@ -31,18 +35,20 @@ data_for_chat, indexes = processing.get_chunks_as_text(test)
|
|||||||
_, indexes_of_training = processing.get_chunks_as_text(train)
|
_, indexes_of_training = processing.get_chunks_as_text(train)
|
||||||
indexes_of_training = [i + 1 for i in indexes_of_training]
|
indexes_of_training = [i + 1 for i in indexes_of_training]
|
||||||
indexes = [i + 1 for i in indexes]
|
indexes = [i + 1 for i in indexes]
|
||||||
print(indexes_of_training)
|
logger.info("%s", indexes_of_training)
|
||||||
print(indexes)
|
logger.info("%s", indexes)
|
||||||
|
|
||||||
client = gpt_alignment.createGPT()
|
client = gpt_alignment.create_gpt()
|
||||||
responses = []
|
# Ten only: each call is a paid few-shot request, and ten was enough for the
|
||||||
for i in range(10): # len(data_for_chat)):
|
# report's comparison.
|
||||||
responses.append(
|
SAMPLE_SIZE = 10
|
||||||
[gpt_alignment.callApi_examples(client, train, data_for_chat[i]), indexes[i]]
|
responses = [
|
||||||
)
|
[gpt_alignment.call_api_examples(client, train, data_for_chat[i]), indexes[i]]
|
||||||
|
for i in range(SAMPLE_SIZE)
|
||||||
|
]
|
||||||
|
|
||||||
with open(file_path, "w") as file:
|
with Path(file_path).open("w") as file:
|
||||||
for i, r in enumerate(responses):
|
for r in responses:
|
||||||
file.write('<sentence id="' + str(r[1] + 1) + '" status="">\n')
|
file.write('<sentence id="' + str(r[1] + 1) + '" status="">\n')
|
||||||
file.write("<alignment>\n")
|
file.write("<alignment>\n")
|
||||||
file.write(r[0])
|
file.write(r[0])
|
||||||
|
|||||||
66
Programming/NLP/format_alignments.py
Normal file → Executable file
66
Programming/NLP/format_alignments.py
Normal file → Executable file
@ -1,9 +1,17 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Turn the raw GPT alignment replies into the SemEval .wa format."""
|
||||||
|
|
||||||
|
import ast
|
||||||
import copy
|
import copy
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import processing
|
import processing
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# input file name
|
# input file name
|
||||||
file_path = "alignments_unformatted_headlines.txt"
|
file_path = "alignments_unformatted_headlines.txt"
|
||||||
|
|
||||||
@ -16,26 +24,32 @@ chunked_path2 = "test_goldStandard/headlines/STSint.testinput.headlines.sent1.ch
|
|||||||
alignment_path = "test_goldStandard/headlines/STSint.testinput.headlines.wa"
|
alignment_path = "test_goldStandard/headlines/STSint.testinput.headlines.wa"
|
||||||
|
|
||||||
# paths to students andsewrs database
|
# paths to students andsewrs database
|
||||||
# chunked_path1 = "test_goldStandard/student/STSint.testinput.answers-students.sent1.chunk.txt"
|
|
||||||
# chunked_path2 = "test_goldStandard/student/STSint.testinput.answers-students.sent2.chunk.txt"
|
|
||||||
# alignment_path = "test_goldStandard/student/STSint.testinput.answers-students.wa"
|
|
||||||
|
|
||||||
# load data
|
# load data
|
||||||
goldstandard_chunked = processing.load_chunked(chunked_path1, chunked_path2)
|
goldstandard_chunked = processing.load_chunked(chunked_path1, chunked_path2)
|
||||||
goldstandard_alignment = processing.load_alignment(alignment_path)
|
goldstandard_alignment = processing.load_alignment(alignment_path)
|
||||||
|
|
||||||
|
# ASCII letters mark a chunk the numbering pass failed to replace, and a
|
||||||
|
# formatted alignment always has at least the four //-separated fields.
|
||||||
|
ASCII_MAX = 128
|
||||||
|
FULL_FIELDS = 4
|
||||||
|
SHORT_FIELDS = 3
|
||||||
|
|
||||||
# get a nice table
|
# get a nice table
|
||||||
data = pd.merge(
|
data = pd.DataFrame.merge(
|
||||||
goldstandard_chunked, goldstandard_alignment, left_index=True, right_index=True
|
goldstandard_chunked, goldstandard_alignment, left_index=True, right_index=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# open generated alignments
|
# open generated alignments
|
||||||
with open(file_path) as file:
|
# The file holds one repr()'d string per line (see create_alignments.py),
|
||||||
responses = [eval(line.strip()) for line in file]
|
# so literal_eval is enough -- eval() here would execute whatever GPT
|
||||||
|
# happened to return.
|
||||||
|
with Path(file_path).open() as file:
|
||||||
|
responses = [ast.literal_eval(line.strip()) for line in file]
|
||||||
|
|
||||||
for i, r in enumerate(responses):
|
for i, r in enumerate(responses):
|
||||||
print("\nresponse number " + str(i))
|
logger.info("%s", "\nresponse number " + str(i))
|
||||||
print(r)
|
logger.info("%s", r)
|
||||||
|
|
||||||
unformatted = copy.deepcopy(responses)
|
unformatted = copy.deepcopy(responses)
|
||||||
|
|
||||||
@ -82,41 +96,41 @@ for i, response in enumerate(responses):
|
|||||||
|
|
||||||
chunk1arr = data.iloc[i]["chunked_sentance1"]
|
chunk1arr = data.iloc[i]["chunked_sentance1"]
|
||||||
q = 1
|
q = 1
|
||||||
numberList = []
|
number_list = []
|
||||||
for chunk in chunk1arr:
|
for raw_chunk in chunk1arr:
|
||||||
chunk = re.sub(r"(?<!\n) +(?!\n)", " ", chunk)
|
chunk = re.sub(r"(?<!\n) +(?!\n)", " ", raw_chunk)
|
||||||
n_of_words = len(chunk.strip().split(" "))
|
n_of_words = len(chunk.strip().split(" "))
|
||||||
index_str = ""
|
index_str = ""
|
||||||
for qq in range(q, q + n_of_words):
|
for qq in range(q, q + n_of_words):
|
||||||
index_str = index_str + str(qq) + " "
|
index_str = index_str + str(qq) + " "
|
||||||
numberList.append(index_str)
|
number_list.append(index_str)
|
||||||
q = q + n_of_words
|
q = q + n_of_words
|
||||||
|
|
||||||
for j, chunk in enumerate(data.iloc[i]["chunked_sentance1"]):
|
for j, chunk in enumerate(data.iloc[i]["chunked_sentance1"]):
|
||||||
pattern = re.compile(chunk, re.IGNORECASE)
|
pattern = re.compile(chunk, re.IGNORECASE)
|
||||||
temp[k][0] = pattern.sub(numberList[j], temp[k][0])
|
temp[k][0] = pattern.sub(number_list[j], temp[k][0])
|
||||||
|
|
||||||
chunk1arr = data.iloc[i]["chunked_sentance2"]
|
chunk1arr = data.iloc[i]["chunked_sentance2"]
|
||||||
q = 1
|
q = 1
|
||||||
numberList = []
|
number_list = []
|
||||||
for chunk in chunk1arr:
|
for raw_chunk in chunk1arr:
|
||||||
chunk = re.sub(r"(?<!\n) +(?!\n)", " ", chunk)
|
chunk = re.sub(r"(?<!\n) +(?!\n)", " ", raw_chunk)
|
||||||
n_of_words = len(chunk.strip().split(" "))
|
n_of_words = len(chunk.strip().split(" "))
|
||||||
index_str = ""
|
index_str = ""
|
||||||
for qq in range(q, q + n_of_words):
|
for qq in range(q, q + n_of_words):
|
||||||
index_str = index_str + str(qq) + " "
|
index_str = index_str + str(qq) + " "
|
||||||
numberList.append(index_str)
|
number_list.append(index_str)
|
||||||
q = q + n_of_words
|
q = q + n_of_words
|
||||||
|
|
||||||
for j, chunk in enumerate(data.iloc[i]["chunked_sentance2"]):
|
for j, chunk in enumerate(data.iloc[i]["chunked_sentance2"]):
|
||||||
pattern = re.compile(chunk, re.IGNORECASE)
|
pattern = re.compile(chunk, re.IGNORECASE)
|
||||||
temp[k][1] = pattern.sub(numberList[j], temp[k][1])
|
temp[k][1] = pattern.sub(number_list[j], temp[k][1])
|
||||||
|
|
||||||
if any(char.isalpha() and ord(char) < 128 for char in temp[k][0]) or any(
|
if any(char.isalpha() and ord(char) < ASCII_MAX for char in temp[k][0]) or any(
|
||||||
char.isalpha() and ord(char) < 128 for char in temp[k][1]
|
char.isalpha() and ord(char) < ASCII_MAX for char in temp[k][1]
|
||||||
):
|
):
|
||||||
temp[k] = ""
|
temp[k] = ""
|
||||||
if len(temp[k]) >= 4:
|
if len(temp[k]) >= FULL_FIELDS:
|
||||||
temp[k][3] = temp[k][3].replace("NOALI", "0")
|
temp[k][3] = temp[k][3].replace("NOALI", "0")
|
||||||
if temp[k][3] == "":
|
if temp[k][3] == "":
|
||||||
temp[k][3] = " 0 "
|
temp[k][3] = " 0 "
|
||||||
@ -131,7 +145,7 @@ for i, response in enumerate(responses):
|
|||||||
+ " // "
|
+ " // "
|
||||||
+ temp[k][3]
|
+ temp[k][3]
|
||||||
)
|
)
|
||||||
elif len(temp[k]) == 3:
|
elif len(temp[k]) == SHORT_FIELDS:
|
||||||
temp[k] = temp[k][0] + " <==> " + temp[k][1] + " // " + temp[k][2] + " // 0"
|
temp[k] = temp[k][0] + " <==> " + temp[k][1] + " // " + temp[k][2] + " // 0"
|
||||||
|
|
||||||
temp[k] = re.sub(r"\s{2,}", " ", temp[k]) # remove double space
|
temp[k] = re.sub(r"\s{2,}", " ", temp[k]) # remove double space
|
||||||
@ -152,11 +166,11 @@ for n, r in enumerate(responses):
|
|||||||
indexes.append(n + 1)
|
indexes.append(n + 1)
|
||||||
responses_final.append(r)
|
responses_final.append(r)
|
||||||
|
|
||||||
print("rejected indexes:")
|
logger.info("rejected indexes:")
|
||||||
print(rejected_indexes)
|
logger.info("%s", rejected_indexes)
|
||||||
|
|
||||||
with open(output_file_path, "w") as file:
|
with Path(output_file_path).open("w") as file:
|
||||||
for i, r in zip(indexes, responses_final):
|
for i, r in zip(indexes, responses_final, strict=False):
|
||||||
file.write('<sentence id="' + str(i + 1) + '" status="">\n')
|
file.write('<sentence id="' + str(i + 1) + '" status="">\n')
|
||||||
file.write("<alignment>\n")
|
file.write("<alignment>\n")
|
||||||
file.write(r)
|
file.write(r)
|
||||||
|
|||||||
67
Programming/NLP/format_alignments_with_training.py
Normal file → Executable file
67
Programming/NLP/format_alignments_with_training.py
Normal file → Executable file
@ -1,35 +1,47 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Format the few-shot alignment replies, and score them against gold."""
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import processing
|
import processing
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# paths to students andsewrs database
|
# paths to students andsewrs database
|
||||||
studentAnswers1_path = (
|
student_answers1_path = (
|
||||||
"test_goldStandard/student/STSint.testinput.answers-students.sent1.txt"
|
"test_goldStandard/student/STSint.testinput.answers-students.sent1.txt"
|
||||||
)
|
)
|
||||||
studentAnswers2_path = (
|
student_answers2_path = (
|
||||||
"test_goldStandard/student/STSint.testinput.answers-students.sent2.txt"
|
"test_goldStandard/student/STSint.testinput.answers-students.sent2.txt"
|
||||||
)
|
)
|
||||||
studentAnsewrs_chunked_path1 = (
|
student_answers_chunked_path1 = (
|
||||||
"test_goldStandard/student/STSint.testinput.answers-students.sent1.chunk.txt"
|
"test_goldStandard/student/STSint.testinput.answers-students.sent1.chunk.txt"
|
||||||
)
|
)
|
||||||
studentAnsewrs_chunked_path2 = (
|
student_answers_chunked_path2 = (
|
||||||
"test_goldStandard/student/STSint.testinput.answers-students.sent2.chunk.txt"
|
"test_goldStandard/student/STSint.testinput.answers-students.sent2.chunk.txt"
|
||||||
)
|
)
|
||||||
studentsAnsewrs_alignment_path = (
|
student_answers_alignment_path = (
|
||||||
"test_goldStandard/student/STSint.testinput.answers-students.wa"
|
"test_goldStandard/student/STSint.testinput.answers-students.wa"
|
||||||
)
|
)
|
||||||
|
|
||||||
# load data
|
# load data
|
||||||
studentAnserws = processing.load_sentences(studentAnswers1_path, studentAnswers1_path)
|
student_answers = processing.load_sentences(
|
||||||
goldstandard_chunked = processing.load_chunked(
|
student_answers1_path, student_answers1_path
|
||||||
studentAnsewrs_chunked_path1, studentAnsewrs_chunked_path2
|
|
||||||
)
|
)
|
||||||
goldstandard_alignment = processing.load_alignment(studentsAnsewrs_alignment_path)
|
goldstandard_chunked = processing.load_chunked(
|
||||||
|
student_answers_chunked_path1, student_answers_chunked_path2
|
||||||
|
)
|
||||||
|
goldstandard_alignment = processing.load_alignment(student_answers_alignment_path)
|
||||||
|
|
||||||
|
SHORT_FIELDS = 3
|
||||||
|
MIN_FIELDS = 2
|
||||||
|
|
||||||
# get a nice anwser-student table
|
# get a nice anwser-student table
|
||||||
data = pd.merge(
|
data = pd.DataFrame.merge(
|
||||||
goldstandard_chunked, goldstandard_alignment, left_index=True, right_index=True
|
goldstandard_chunked, goldstandard_alignment, left_index=True, right_index=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,8 +53,8 @@ responses = responses["alignment_text"].to_list()
|
|||||||
|
|
||||||
|
|
||||||
for i, r in enumerate(responses):
|
for i, r in enumerate(responses):
|
||||||
print("\nresponse number " + str(i))
|
logger.info("%s", "\nresponse number " + str(i))
|
||||||
print(r)
|
logger.info("%s", r)
|
||||||
|
|
||||||
unformatted = copy.deepcopy(responses)
|
unformatted = copy.deepcopy(responses)
|
||||||
|
|
||||||
@ -71,28 +83,21 @@ for i, response in enumerate(responses):
|
|||||||
temp = re.sub(r"(?<!\n) +(?!\n)", " ", temp) # remove double space
|
temp = re.sub(r"(?<!\n) +(?!\n)", " ", temp) # remove double space
|
||||||
|
|
||||||
temp = temp.split("\n")
|
temp = temp.split("\n")
|
||||||
for k, t in enumerate(temp):
|
for k in range(len(temp)):
|
||||||
temp[k] = temp[k].split("<==>")
|
temp[k] = temp[k].split("<==>")
|
||||||
temp[k][1] = temp[k][1].split("//")
|
temp[k][1] = temp[k][1].split("//")
|
||||||
|
|
||||||
# for j, chunk in enumerate(data.iloc[i]["chunked_sentance1"].sorted(key=len, reverse=True)):
|
|
||||||
for j, chunk in enumerate(
|
for j, chunk in enumerate(
|
||||||
sorted(
|
sorted(data.iloc[i]["chunked_sentance1"], key=len, reverse=True)
|
||||||
data.iloc[i]["chunked_sentance1"], key=lambda x: len(x), reverse=True
|
|
||||||
)
|
|
||||||
):
|
):
|
||||||
pattern = re.compile(chunk, re.IGNORECASE)
|
pattern = re.compile(chunk, re.IGNORECASE)
|
||||||
temp[k][0] = pattern.sub(str(j + 1), temp[k][0])
|
temp[k][0] = pattern.sub(str(j + 1), temp[k][0])
|
||||||
# temp[k][0] = temp[k][0].replace(chunk, str(j+1))
|
|
||||||
for j, chunk in enumerate(
|
for j, chunk in enumerate(
|
||||||
sorted(
|
sorted(data.iloc[i]["chunked_sentance2"], key=len, reverse=True)
|
||||||
data.iloc[i]["chunked_sentance2"], key=lambda x: len(x), reverse=True
|
|
||||||
)
|
|
||||||
):
|
):
|
||||||
pattern = re.compile(chunk, re.IGNORECASE)
|
pattern = re.compile(chunk, re.IGNORECASE)
|
||||||
temp[k][1][0] = pattern.sub(str(j + 1), temp[k][1][0])
|
temp[k][1][0] = pattern.sub(str(j + 1), temp[k][1][0])
|
||||||
# temp[k][1][0] = temp[k][1][0].replace(chunk, str(j+1))
|
if len(temp[k][1]) >= SHORT_FIELDS:
|
||||||
if len(temp[k][1]) >= 3:
|
|
||||||
temp[k] = (
|
temp[k] = (
|
||||||
temp[k][0]
|
temp[k][0]
|
||||||
+ " <==> "
|
+ " <==> "
|
||||||
@ -102,7 +107,7 @@ for i, response in enumerate(responses):
|
|||||||
+ " // "
|
+ " // "
|
||||||
+ temp[k][1][2]
|
+ temp[k][1][2]
|
||||||
)
|
)
|
||||||
elif len(temp[k][1]) == 2:
|
elif len(temp[k][1]) == MIN_FIELDS:
|
||||||
temp[k] = (
|
temp[k] = (
|
||||||
temp[k][0] + " <==> " + temp[k][1][0] + " // " + temp[k][1][1] + " // 0"
|
temp[k][0] + " <==> " + temp[k][1][0] + " // " + temp[k][1][1] + " // 0"
|
||||||
)
|
)
|
||||||
@ -111,19 +116,19 @@ for i, response in enumerate(responses):
|
|||||||
responses[i] = "\n".join(temp)
|
responses[i] = "\n".join(temp)
|
||||||
|
|
||||||
|
|
||||||
print("\nafter formatting\n")
|
logger.info("\nafter formatting\n")
|
||||||
for i, r in enumerate(responses):
|
for i, r in enumerate(responses):
|
||||||
print("\nresponse number " + str(i))
|
logger.info("%s", "\nresponse number " + str(i))
|
||||||
print("FORMATTED\n")
|
logger.info("FORMATTED\n")
|
||||||
print(r)
|
logger.info("%s", r)
|
||||||
print("\nUNFORMATTED\n")
|
logger.info("\nUNFORMATTED\n")
|
||||||
print(unformatted[i])
|
logger.info("%s", unformatted[i])
|
||||||
|
|
||||||
|
|
||||||
# write to file
|
# write to file
|
||||||
file_path = "student_fixed_format_with_training.txt"
|
file_path = "student_fixed_format_with_training.txt"
|
||||||
|
|
||||||
with open(file_path, "w") as file:
|
with Path(file_path).open("w") as file:
|
||||||
for i, r in enumerate(responses):
|
for i, r in enumerate(responses):
|
||||||
file.write('<sentence id="' + str(i + 1) + '" status="">\n')
|
file.write('<sentence id="' + str(i + 1) + '" status="">\n')
|
||||||
file.write("<alignment>\n")
|
file.write("<alignment>\n")
|
||||||
|
|||||||
87
Programming/NLP/gpt_alignment.py
Normal file → Executable file
87
Programming/NLP/gpt_alignment.py
Normal file → Executable file
@ -1,73 +1,54 @@
|
|||||||
"""Get chunk alignment from chatGPT."""
|
#!/usr/bin/env python3
|
||||||
|
"""Get chunk alignment from ChatGPT.
|
||||||
|
|
||||||
|
The two prompts live in ``prompts/`` rather than in this file: they are 2 kB
|
||||||
|
of instructions each, on lines far past any line-length limit, and wrapping
|
||||||
|
them here would put newlines into the middle of the prompt itself.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
|
|
||||||
thePrompt = """You are a machine designed to align chunks from 2 sentences. This means you will be taking each chunk from one sentence and comparing it to every chunk from the other sentence. Choose the chunks with the strongest relation and assign them a score.
|
PROMPTS = Path(__file__).parent / "prompts"
|
||||||
|
THE_PROMPT = (PROMPTS / "alignment.txt").read_text(encoding="utf-8")
|
||||||
The possible relations are:
|
THE_PROMPT_WITH_COMMENT = (PROMPTS / "alignment_with_comment.txt").read_text(
|
||||||
EQUI: both chunks have the same meaning, they are semantically equivalent in this context.
|
encoding="utf-8"
|
||||||
OPPO: the meanings of the chunks are in opposition to each other, lying in an inherently incompatible binary relationship.
|
)
|
||||||
SPE1: both chunks have similar meanings, but chunk in sentence 1 is more specific.
|
|
||||||
SPE2: like SPE1, but it is the chunk in sentence 2 which is more specific.
|
|
||||||
SIMI: both chunks have similar meanings, they share similar attributes and there is no EQUI, OPPO, SPE1 or SPE2 relation
|
|
||||||
REL: both chunks are not considered similar but they are closely related by some relation not mentioned above (i.e. no EQUI, OPPO, SPE1, SPE2, or SIMI relation).
|
|
||||||
NOALI: this chunk has not any corresponding chunk in the other sentence. Therefore, it is left unaligned.
|
|
||||||
|
|
||||||
The possible scores are a range from 0 to 5 where 0 means that the chunks are not related and 5 means their meanings are the same in the given context.
|
|
||||||
|
|
||||||
A chunk can be aligned to multiple different chunks. Chunks can also be grouped in a relation. If a chunk has no relation to any other chunk, give it a NOALI relation to a an empty chunk.
|
|
||||||
|
|
||||||
If there are spelling mistakes do not correct them.
|
|
||||||
|
|
||||||
Present the answers in this form:
|
|
||||||
chunk from first sentence <==> chunk from the second sentence // alignment type // score
|
|
||||||
"""
|
|
||||||
|
|
||||||
thePrompt2 = """You are a machine designed to align chunks from 2 sentences. This means you will be taking each chunk from one sentence and comparing it to every chunk from the other sentence. Choose the chunks with the strongest relation and assign them a relation and a score.
|
|
||||||
|
|
||||||
The possible relations are:
|
|
||||||
EQUI: both chunks have the same meaning
|
|
||||||
OPPO: the meanings of the chunks are opposite
|
|
||||||
SPE1: both chunks have similar meanings, but chunk in sentence 1 is more specific.
|
|
||||||
SPE2: both chunks have similar meanings, but chunk in sentence 2 is more specific.
|
|
||||||
SIMI: both chunks have similar meanings
|
|
||||||
REL: both chunks are not considered similar, but they are closely related by some relation not mentioned above
|
|
||||||
NOALI: this chunk has not any corresponding chunk in the other sentence.
|
|
||||||
|
|
||||||
The possible scores are a range from 0 to 5 where 0 means that the chunks are not related and 5 means their meanings are the same in the given context.
|
|
||||||
|
|
||||||
A chunk can be aligned with multiple different chunks. Chunks can also be grouped in a relation. If a chunk has no relation to any other chunk, give it a NOALI relation to a chunk "0".
|
|
||||||
|
|
||||||
If there are spelling mistakes, do not correct them.
|
|
||||||
|
|
||||||
Present the answers in this form:
|
|
||||||
chunk from first sentence <==> chunk from the second sentence // relation // score // comment
|
|
||||||
|
|
||||||
in the comment you can explain your choice."""
|
|
||||||
|
|
||||||
|
|
||||||
def createGPT() -> OpenAI:
|
def create_gpt() -> OpenAI:
|
||||||
client = OpenAI(api_key="Your API key here")
|
"""Build an OpenAI client from OPENAI_API_KEY.
|
||||||
return client
|
|
||||||
|
The key used to be a placeholder string literal here, which meant the
|
||||||
|
script could not actually run as committed.
|
||||||
|
"""
|
||||||
|
key = os.environ.get("OPENAI_API_KEY")
|
||||||
|
if not key:
|
||||||
|
msg = "set OPENAI_API_KEY to run the alignment scripts"
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
return OpenAI(api_key=key)
|
||||||
|
|
||||||
|
|
||||||
def callApi(client: OpenAI, chunks: str):
|
def call_api(client: OpenAI, chunks: str) -> str:
|
||||||
|
"""Ask the model to align one pair of chunked sentences."""
|
||||||
response = client.chat.completions.create(
|
response = client.chat.completions.create(
|
||||||
model="gpt-3.5-turbo-1106",
|
model="gpt-3.5-turbo-1106",
|
||||||
messages=[
|
messages=[
|
||||||
{"role": "user", "content": thePrompt},
|
{"role": "user", "content": THE_PROMPT},
|
||||||
{"role": "user", "content": chunks},
|
{"role": "user", "content": chunks},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
return response.choices[0].message.content.strip()
|
return response.choices[0].message.content.strip()
|
||||||
|
|
||||||
|
|
||||||
def callApi_examples(client: OpenAI, examples: pd.DataFrame, alignment: str):
|
def call_api_examples(client: OpenAI, examples: pd.DataFrame, alignment: str) -> str:
|
||||||
|
"""Ask the model to align, few-shot prompted with worked examples."""
|
||||||
user_input = []
|
user_input = []
|
||||||
assistant_output = []
|
assistant_output = []
|
||||||
for index, row in examples.iterrows():
|
for _index, row in examples.iterrows():
|
||||||
chunks = ""
|
chunks = ""
|
||||||
for chunk in row["chunked_sentance1"]:
|
for chunk in row["chunked_sentance1"]:
|
||||||
chunks = chunks + "[ " + chunk + " ] "
|
chunks = chunks + "[ " + chunk + " ] "
|
||||||
@ -78,8 +59,8 @@ def callApi_examples(client: OpenAI, examples: pd.DataFrame, alignment: str):
|
|||||||
assistant_output.append(row["alignment_text"])
|
assistant_output.append(row["alignment_text"])
|
||||||
|
|
||||||
messages = []
|
messages = []
|
||||||
messages.append({"role": "user", "content": thePrompt})
|
messages.append({"role": "user", "content": THE_PROMPT})
|
||||||
for u, a in zip(user_input, assistant_output):
|
for u, a in zip(user_input, assistant_output, strict=True):
|
||||||
messages.append({"role": "user", "content": u})
|
messages.append({"role": "user", "content": u})
|
||||||
messages.append({"role": "assistant", "content": a})
|
messages.append({"role": "assistant", "content": a})
|
||||||
|
|
||||||
|
|||||||
407
Programming/NLP/gpt_chunks.py
Normal file → Executable file
407
Programming/NLP/gpt_chunks.py
Normal file → Executable file
@ -1,13 +1,20 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
"""Orders gpt to chunkify files from task."""
|
"""Orders gpt to chunkify files from task."""
|
||||||
|
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
from collections.abc import Callable
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
|
from openai.types.chat import ChatCompletion
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def sentence_loop(sentences):
|
def sentence_loop(sentences: list[str]) -> list[str]:
|
||||||
"""Processes a list of sentences, applying chunking and formatting to each.
|
"""Chunk and format every sentence in the list.
|
||||||
|
|
||||||
This function iterates through a list of sentences, processing each one
|
This function iterates through a list of sentences, processing each one
|
||||||
using the `process_sentence` function. It collects the results into a list.
|
using the `process_sentence` function. It collects the results into a list.
|
||||||
@ -15,7 +22,8 @@ def sentence_loop(sentences):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
sentences (list of str): A list of sentences to be processed.
|
sentences : list of str
|
||||||
|
A list of sentences to be processed.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -32,17 +40,19 @@ def sentence_loop(sentences):
|
|||||||
return chunked_sentences
|
return chunked_sentences
|
||||||
|
|
||||||
|
|
||||||
def remove_leading_numbering(input_string):
|
def remove_leading_numbering(input_string: str) -> str:
|
||||||
"""Removes any leading numbering from the string.
|
"""Remove any leading numbering from the string.
|
||||||
|
|
||||||
This function is designed to strip off numbering
|
This function is designed to strip off numbering
|
||||||
that typically appears at the beginning of a string,
|
that typically appears at the beginning of a string,
|
||||||
such as '1. ', '2. ', etc. It's useful for processing strings where such numbering is irrelevant
|
such as '1. ', '2. ', etc. It's useful for processing strings where such numbering
|
||||||
|
is irrelevant
|
||||||
or should be omitted for formatting purposes.
|
or should be omitted for formatting purposes.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
string (str): The string from which leading numbering should be removed.
|
input_string : str
|
||||||
|
string (str): The string from which leading numbering should be removed.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -52,10 +62,11 @@ def remove_leading_numbering(input_string):
|
|||||||
return re.sub(r"^\d+\.\s*", "", input_string)
|
return re.sub(r"^\d+\.\s*", "", input_string)
|
||||||
|
|
||||||
|
|
||||||
def process_brackets(input_string):
|
def process_brackets(input_string: str) -> list[str]:
|
||||||
"""Processes a string to extract and adjust parts enclosed in square brackets.
|
"""Extract and adjust the parts enclosed in square brackets.
|
||||||
|
|
||||||
This function identifies and extracts all segments within square brackets from the input string.
|
This function identifies and extracts all segments within square brackets from the
|
||||||
|
input string.
|
||||||
It also adjusts the last part by handling any trailing punctuation,
|
It also adjusts the last part by handling any trailing punctuation,
|
||||||
using the 'handle_trailing_punctuation' function.
|
using the 'handle_trailing_punctuation' function.
|
||||||
This is particularly useful for strings
|
This is particularly useful for strings
|
||||||
@ -63,11 +74,13 @@ def process_brackets(input_string):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The string to be processed for bracketed segments.
|
input_string : str
|
||||||
|
The string to be processed for bracketed segments.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list of str: A list containing the extracted and adjusted bracketed segments from the string.
|
list of str: A list containing the extracted and adjusted bracketed segments from
|
||||||
|
the string.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
parts = re.findall(r"\[[^\]]*\]", input_string)
|
parts = re.findall(r"\[[^\]]*\]", input_string)
|
||||||
@ -76,18 +89,21 @@ def process_brackets(input_string):
|
|||||||
return parts
|
return parts
|
||||||
|
|
||||||
|
|
||||||
def handle_trailing_punctuation(input_string, last_part):
|
def handle_trailing_punctuation(input_string: str, last_part: str) -> str:
|
||||||
"""Handles and appends trailing punctuation to the last part of a bracketed segment.
|
"""Handle and appends trailing punctuation to the last part of a bracketed segment.
|
||||||
|
|
||||||
This function checks the given string for any punctuation (like '.', '!', or '?')
|
This function checks the given string for any punctuation (like '.', '!', or '?')
|
||||||
immediately following the last bracketed segment. If found, it appends this punctuation
|
immediately following the last bracketed segment. If found, it appends this
|
||||||
|
punctuation
|
||||||
to the end of the last part inside the brackets. This is used to maintain sentence
|
to the end of the last part inside the brackets. This is used to maintain sentence
|
||||||
punctuation integrity after processing bracketed segments.
|
punctuation integrity after processing bracketed segments.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The original string containing the bracketed segments.
|
input_string : str
|
||||||
last_part (str): The last bracketed segment extracted from the string.
|
The original string containing the bracketed segments.
|
||||||
|
last_part : str
|
||||||
|
The last bracketed segment extracted from the string.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -100,32 +116,38 @@ def handle_trailing_punctuation(input_string, last_part):
|
|||||||
return last_part
|
return last_part
|
||||||
|
|
||||||
|
|
||||||
def process_non_bracketed_parts(input_string):
|
def process_non_bracketed_parts(input_string: str) -> list[str]:
|
||||||
"""Split the input string by hyphens surrounded by optional whitespace and return
|
"""Split the input string by hyphens surrounded by optional whitespace and return.
|
||||||
|
|
||||||
a list of non-empty parts enclosed in square brackets.
|
a list of non-empty parts enclosed in square brackets.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be processed.
|
input_string : str
|
||||||
|
The input string to be processed.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list of str: A list of non-empty parts from the input string, enclosed in square brackets.
|
list of str: A list of non-empty parts from the input string, enclosed in square
|
||||||
|
brackets.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
parts = re.split(r"\s*-\s*", input_string)
|
parts = re.split(r"\s*-\s*", input_string)
|
||||||
return [f"[ {part.strip()} ]" for part in parts if part.strip()]
|
return [f"[ {part.strip()} ]" for part in parts if part.strip()]
|
||||||
|
|
||||||
|
|
||||||
def reformat_list(strings):
|
def reformat_list(strings: list[str]) -> str:
|
||||||
"""Reformat a list of strings by processing each string element. For strings containing
|
"""Reformat each string in the list, bracketed or not.
|
||||||
square brackets, it calls 'process_brackets' to handle the content within the brackets,
|
|
||||||
|
square brackets, it calls 'process_brackets' to handle the content within the
|
||||||
|
brackets,
|
||||||
and for other strings, it calls 'process_non_bracketed_parts' to handle them. The
|
and for other strings, it calls 'process_non_bracketed_parts' to handle them. The
|
||||||
resulting parts are joined into a single string with spaces.
|
resulting parts are joined into a single string with spaces.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
strings (list of str): A list of strings to be reformatted.
|
strings : list of str
|
||||||
|
A list of strings to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -133,8 +155,8 @@ def reformat_list(strings):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
for string in strings:
|
for raw in strings:
|
||||||
string = remove_leading_numbering(string)
|
string = remove_leading_numbering(raw)
|
||||||
if "[" in string and "]" in string:
|
if "[" in string and "]" in string:
|
||||||
result.extend(process_brackets(string))
|
result.extend(process_brackets(string))
|
||||||
else:
|
else:
|
||||||
@ -142,8 +164,9 @@ def reformat_list(strings):
|
|||||||
return " ".join(result)
|
return " ".join(result)
|
||||||
|
|
||||||
|
|
||||||
def reformat_square_not_full(input_string):
|
def reformat_square_not_full(input_string: str) -> str:
|
||||||
"""Reformat the input string by splitting it into parts that are either enclosed in
|
"""Reformat the input string by splitting it into parts that are either enclosed in.
|
||||||
|
|
||||||
square brackets or not. For parts not in brackets, it removes trailing periods,
|
square brackets or not. For parts not in brackets, it removes trailing periods,
|
||||||
trims spaces, and encloses them in square brackets. For parts already in brackets,
|
trims spaces, and encloses them in square brackets. For parts already in brackets,
|
||||||
it adds spaces inside the brackets. The formatted parts are then joined into a
|
it adds spaces inside the brackets. The formatted parts are then joined into a
|
||||||
@ -151,7 +174,8 @@ def reformat_square_not_full(input_string):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -167,8 +191,8 @@ def reformat_square_not_full(input_string):
|
|||||||
# For parts not in brackets,
|
# For parts not in brackets,
|
||||||
# remove trailing period, trim spaces, and enclose in brackets
|
# remove trailing period, trim spaces, and enclose in brackets
|
||||||
if not part.startswith("["):
|
if not part.startswith("["):
|
||||||
part = re.sub(r"\.\s*$", "", part).strip()
|
trimmed = re.sub(r"\.\s*$", "", part).strip()
|
||||||
formatted_parts.append(f"[ {part} ]")
|
formatted_parts.append(f"[ {trimmed} ]")
|
||||||
else:
|
else:
|
||||||
# For parts already in brackets, add spaces inside
|
# For parts already in brackets, add spaces inside
|
||||||
formatted_parts.append(re.sub(r"\[([^]]+)\]", r"[ \1 ]", part))
|
formatted_parts.append(re.sub(r"\[([^]]+)\]", r"[ \1 ]", part))
|
||||||
@ -176,14 +200,16 @@ def reformat_square_not_full(input_string):
|
|||||||
return " ".join(formatted_parts)
|
return " ".join(formatted_parts)
|
||||||
|
|
||||||
|
|
||||||
def reformat_slash(input_string):
|
def reformat_slash(input_string: str) -> str:
|
||||||
"""Reformat the input string by splitting it on slashes, trimming spaces from each
|
"""Reformat the input string by splitting it on slashes, trimming spaces from each.
|
||||||
|
|
||||||
part, and enclosing each part in square brackets. The formatted parts are then
|
part, and enclosing each part in square brackets. The formatted parts are then
|
||||||
joined into a single string with spaces.
|
joined into a single string with spaces.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -197,14 +223,16 @@ def reformat_slash(input_string):
|
|||||||
return " ".join(formatted_parts)
|
return " ".join(formatted_parts)
|
||||||
|
|
||||||
|
|
||||||
def reformat_pipe(input_string):
|
def reformat_pipe(input_string: str) -> str:
|
||||||
"""Reformat the input string by splitting it on ' | ' (pipe symbol with spaces),
|
"""Reformat the input string by splitting it on ' | ' (pipe symbol with spaces),.
|
||||||
|
|
||||||
trimming spaces from each part, and enclosing each part in square brackets. The
|
trimming spaces from each part, and enclosing each part in square brackets. The
|
||||||
formatted parts are then joined into a single string with spaces.
|
formatted parts are then joined into a single string with spaces.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -218,12 +246,13 @@ def reformat_pipe(input_string):
|
|||||||
return " ".join(formatted_parts)
|
return " ".join(formatted_parts)
|
||||||
|
|
||||||
|
|
||||||
def is_pipe_format(input_string):
|
def is_pipe_format(input_string: str) -> bool:
|
||||||
"""Check if the input string follows the ' | ' (pipe symbol with spaces) pattern.
|
"""Check if the input string follows the ' | ' (pipe symbol with spaces) pattern.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be checked.
|
input_string : str
|
||||||
|
The input string to be checked.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -235,14 +264,13 @@ def is_pipe_format(input_string):
|
|||||||
return bool(re.match(pattern, input_string))
|
return bool(re.match(pattern, input_string))
|
||||||
|
|
||||||
|
|
||||||
def is_numbered_line_format(input_string):
|
def is_numbered_line_format(input_string: str | list[str]) -> bool | list[bool]:
|
||||||
"""Check if the input string(input_string) follows the pattern of lines starting with
|
r"""Check for lines of the form "number. word/phrase".
|
||||||
'number. word/phrase \n'.
|
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str or list of str): The input string(input_string) to be checked.
|
input_string : str or list of str
|
||||||
It can be a single string or a list of strings.
|
The input
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -258,21 +286,23 @@ def is_numbered_line_format(input_string):
|
|||||||
|
|
||||||
# Check if the input is a list of strings
|
# Check if the input is a list of strings
|
||||||
if isinstance(input_string, list):
|
if isinstance(input_string, list):
|
||||||
return [bool(re.match(pattern, string)) for string in string]
|
return [bool(re.match(pattern, item)) for item in input_string]
|
||||||
|
|
||||||
# If the input is a single string, process it directly
|
# If the input is a single string, process it directly
|
||||||
return bool(re.match(pattern, input_string))
|
return bool(re.match(pattern, input_string))
|
||||||
|
|
||||||
|
|
||||||
def reformat_ists(input_string):
|
def reformat_ists(input_string: str) -> str:
|
||||||
"""Reformat the input string by finding all occurrences of the (iSTS n) pattern
|
"""Reformat the input string by finding all occurrences of the (iSTS n) pattern.
|
||||||
|
|
||||||
(where 'n' is a number), extracting the text inside the parentheses, and
|
(where 'n' is a number), extracting the text inside the parentheses, and
|
||||||
enclosing each extracted part in square brackets. The formatted parts are
|
enclosing each extracted part in square brackets. The formatted parts are
|
||||||
then joined into a single string with spaces.
|
then joined into a single string with spaces.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -287,13 +317,15 @@ def reformat_ists(input_string):
|
|||||||
return " ".join(formatted_parts)
|
return " ".join(formatted_parts)
|
||||||
|
|
||||||
|
|
||||||
def is_ists_format(input_string):
|
def is_ists_format(input_string: str) -> bool:
|
||||||
"""Check if the input string follows the pattern of (iSTS n) followed by text, with
|
"""Check if the input string follows the pattern of (iSTS n) followed by text, with.
|
||||||
|
|
||||||
multiple occurrences allowed, where 'n' is a number.
|
multiple occurrences allowed, where 'n' is a number.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be checked.
|
input_string : str
|
||||||
|
The input string to be checked.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -305,15 +337,17 @@ def is_ists_format(input_string):
|
|||||||
return bool(re.match(pattern, input_string))
|
return bool(re.match(pattern, input_string))
|
||||||
|
|
||||||
|
|
||||||
def reformat_with_sections(input_string):
|
def reformat_with_sections(input_string: str) -> str:
|
||||||
"""Reformat the input string by using regular expressions to split it into parts
|
"""Reformat the input string by using regular expressions to split it into parts.
|
||||||
|
|
||||||
following section labels (e.g., [S1], [S2], etc.), removing empty strings,
|
following section labels (e.g., [S1], [S2], etc.), removing empty strings,
|
||||||
and enclosing each part in square brackets. The formatted parts are then
|
and enclosing each part in square brackets. The formatted parts are then
|
||||||
joined into a single string with spaces.
|
joined into a single string with spaces.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -327,13 +361,15 @@ def reformat_with_sections(input_string):
|
|||||||
return " ".join(formatted_parts)
|
return " ".join(formatted_parts)
|
||||||
|
|
||||||
|
|
||||||
def is_section_format(input_string):
|
def is_section_format(input_string: str) -> bool:
|
||||||
"""Check if the input string follows the pattern of section labels (e.g., [S1], [S2], etc.)
|
"""Check for section labels such as [S1] or [S2].
|
||||||
|
|
||||||
followed by text, with multiple occurrences allowed.
|
followed by text, with multiple occurrences allowed.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be checked.
|
input_string : str
|
||||||
|
The input string to be checked.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -345,13 +381,15 @@ def is_section_format(input_string):
|
|||||||
return bool(re.match(pattern, input_string))
|
return bool(re.match(pattern, input_string))
|
||||||
|
|
||||||
|
|
||||||
def is_chunk_format(input_string):
|
def is_chunk_format(input_string: str) -> bool:
|
||||||
"""Check if the input string follows the pattern of 'Chunk X: text' or '[Chunk X] text'
|
"""Check for the "Chunk X: text" or "[Chunk X] text" pattern.
|
||||||
|
|
||||||
patterns, where 'X' is a number, with multiple occurrences allowed.
|
patterns, where 'X' is a number, with multiple occurrences allowed.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be checked.
|
input_string : str
|
||||||
|
The input string to be checked.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -359,19 +397,23 @@ def is_chunk_format(input_string):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# Further optimized regular expression
|
# Further optimized regular expression
|
||||||
pattern = r"^(Chunk \d+:|\[\s*Chunk \d+\s*\]).*?(?:\s+(?:Chunk \d+:|\[\s*Chunk \d+\s*\]).*?)*$"
|
marker = r"(?:Chunk \d+:|\[\s*Chunk \d+\s*\])"
|
||||||
|
pattern = rf"^{marker}.*?(?:\s+{marker}.*?)*$"
|
||||||
return bool(re.match(pattern, input_string))
|
return bool(re.match(pattern, input_string))
|
||||||
|
|
||||||
|
|
||||||
def reformat_chunks(input_string):
|
def reformat_chunks(input_string: str) -> str:
|
||||||
"""Reformat the input string by finding all occurrences of both
|
"""Reformat the input string by finding all occurrences of both.
|
||||||
|
|
||||||
'Chunk X: text' and '[Chunk X] text'
|
'Chunk X: text' and '[Chunk X] text'
|
||||||
patterns, extracting the text within them, and enclosing each extracted part in square brackets.
|
patterns, extracting the text within them, and enclosing each extracted part in
|
||||||
|
square brackets.
|
||||||
The formatted parts are then joined into a single string with spaces.
|
The formatted parts are then joined into a single string with spaces.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -379,8 +421,9 @@ def reformat_chunks(input_string):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# Find all occurrences of both chunk patterns and extract the text
|
# Find all occurrences of both chunk patterns and extract the text
|
||||||
|
marker = r"(?:Chunk \d+:|\[\s*Chunk \d+\s*\])"
|
||||||
parts = re.findall(
|
parts = re.findall(
|
||||||
r"(?:Chunk \d+:|\[\s*Chunk \d+\s*\])\s*(.*?)(?=\s*(?:Chunk \d+:|\[\s*Chunk \d+\s*\]|$))",
|
rf"{marker}\s*(.*?)(?=\s*(?:{marker}|$))",
|
||||||
input_string,
|
input_string,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -392,14 +435,16 @@ def reformat_chunks(input_string):
|
|||||||
return " ".join(formatted_parts)
|
return " ".join(formatted_parts)
|
||||||
|
|
||||||
|
|
||||||
def reformat_ists_markers(input_string):
|
def reformat_ists_markers(input_string: str) -> str:
|
||||||
"""Reformat the input string by splitting it at '[ iSTS ]' markers, removing empty strings,
|
"""Reformat a string split at its "[ iSTS ]" markers.
|
||||||
|
|
||||||
and enclosing each part in square brackets. The formatted parts are then joined into
|
and enclosing each part in square brackets. The formatted parts are then joined into
|
||||||
a single string with spaces.
|
a single string with spaces.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -417,13 +462,15 @@ def reformat_ists_markers(input_string):
|
|||||||
return " ".join(formatted_parts)
|
return " ".join(formatted_parts)
|
||||||
|
|
||||||
|
|
||||||
def is_ists_marker_format(input_string):
|
def is_ists_marker_format(input_string: str) -> bool:
|
||||||
"""Check if the input string follows the pattern with '[ iSTS ]' markers followed by text,
|
"""Check for "[ iSTS ]" markers followed by text.
|
||||||
|
|
||||||
with multiple occurrences allowed.
|
with multiple occurrences allowed.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be checked.
|
input_string : str
|
||||||
|
The input string to be checked.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -435,14 +482,16 @@ def is_ists_marker_format(input_string):
|
|||||||
return bool(re.match(pattern, input_string))
|
return bool(re.match(pattern, input_string))
|
||||||
|
|
||||||
|
|
||||||
def reformat_with_dashes(input_string):
|
def reformat_with_dashes(input_string: str) -> str:
|
||||||
"""Reformat the input string by splitting it on ' - ' (dash with spaces),
|
"""Reformat the input string by splitting it on ' - ' (dash with spaces),.
|
||||||
|
|
||||||
removing empty strings, and enclosing each part in square brackets.
|
removing empty strings, and enclosing each part in square brackets.
|
||||||
The formatted parts are then joined into a single string with spaces.
|
The formatted parts are then joined into a single string with spaces.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -456,13 +505,15 @@ def reformat_with_dashes(input_string):
|
|||||||
return " ".join(formatted_parts)
|
return " ".join(formatted_parts)
|
||||||
|
|
||||||
|
|
||||||
def is_dash_format(input_string):
|
def is_dash_format(input_string: str) -> bool:
|
||||||
"""Check if the input string follows the pattern with ' - ' (dash with spaces)
|
"""Check if the input string follows the pattern with ' - ' (dash with spaces).
|
||||||
|
|
||||||
separating non-empty parts.
|
separating non-empty parts.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be checked.
|
input_string : str
|
||||||
|
The input string to be checked.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -474,8 +525,9 @@ def is_dash_format(input_string):
|
|||||||
return bool(re.match(pattern, input_string))
|
return bool(re.match(pattern, input_string))
|
||||||
|
|
||||||
|
|
||||||
def preliminary_reformat(input_string):
|
def preliminary_reformat(input_string: str) -> str:
|
||||||
"""Remove common prefixes like
|
"""Remove common prefixes like.
|
||||||
|
|
||||||
'iSTS chunks:'
|
'iSTS chunks:'
|
||||||
and
|
and
|
||||||
'Here are the iSTS chunks for the given sentence:'
|
'Here are the iSTS chunks for the given sentence:'
|
||||||
@ -484,29 +536,34 @@ def preliminary_reformat(input_string):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
str: The input string with common prefixes stripped.
|
str: The input string with common prefixes stripped.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
input_string = input_string.strip("iSTS chunks:")
|
# removeprefix, not strip: str.strip takes a SET OF CHARACTERS, so
|
||||||
input_string = input_string.strip(
|
# .strip("iSTS chunks:") was eating any leading i, S, T, s, c, h, u, n, k,
|
||||||
"Here are the iSTS chunks for the given sentence:"
|
# space or colon -- it turned "sentence one" into "entence one".
|
||||||
)
|
input_string = input_string.removeprefix("iSTS chunks:")
|
||||||
return input_string
|
return input_string.removeprefix("Here are the iSTS chunks for the given sentence:")
|
||||||
|
|
||||||
|
|
||||||
def reformat_brackets_and_text(input_string):
|
def reformat_brackets_and_text(input_string: str) -> str:
|
||||||
"""Reformat the input string by removing trailing dots, splitting it into parts that are either
|
"""Reformat a dash-separated string into bracketed chunks.
|
||||||
enclosed in square brackets or not, removing commas and trimming spaces for non-bracket parts,
|
|
||||||
and enclosing non-bracket parts in square brackets. The formatted parts are then joined into
|
enclosed in square brackets or not, removing commas and trimming spaces for non-
|
||||||
|
bracket parts,
|
||||||
|
and enclosing non-bracket parts in square brackets. The formatted parts are then
|
||||||
|
joined into
|
||||||
a single string with spaces.
|
a single string with spaces.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -524,9 +581,9 @@ def reformat_brackets_and_text(input_string):
|
|||||||
if part: # Ignore empty strings
|
if part: # Ignore empty strings
|
||||||
# Remove commas and trim spaces for parts not in brackets
|
# Remove commas and trim spaces for parts not in brackets
|
||||||
if not part.startswith("["):
|
if not part.startswith("["):
|
||||||
part = re.sub(r",\s*", " ", part).strip()
|
trimmed = re.sub(r",\s*", " ", part).strip()
|
||||||
# Enclose the non-bracket part in brackets
|
# Enclose the non-bracket part in brackets
|
||||||
formatted_parts.append(f"[ {part} ]")
|
formatted_parts.append(f"[ {trimmed} ]")
|
||||||
else:
|
else:
|
||||||
# Directly append the part in brackets
|
# Directly append the part in brackets
|
||||||
formatted_parts.append(part)
|
formatted_parts.append(part)
|
||||||
@ -534,14 +591,16 @@ def reformat_brackets_and_text(input_string):
|
|||||||
return " ".join(formatted_parts)
|
return " ".join(formatted_parts)
|
||||||
|
|
||||||
|
|
||||||
def is_brackets_and_text_format(input_string):
|
def is_brackets_and_text_format(input_string: str) -> bool:
|
||||||
"""Check if the input string follows the pattern of parts
|
"""Check if the input string follows the pattern of parts.
|
||||||
|
|
||||||
that are either enclosed in square brackets
|
that are either enclosed in square brackets
|
||||||
or not, with optional commas and spaces between the parts.
|
or not, with optional commas and spaces between the parts.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be checked.
|
input_string : str
|
||||||
|
The input string to be checked.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -553,16 +612,19 @@ def is_brackets_and_text_format(input_string):
|
|||||||
return bool(re.match(pattern, input_string))
|
return bool(re.match(pattern, input_string))
|
||||||
|
|
||||||
|
|
||||||
def is_correct_format(input_string):
|
def is_correct_format(input_string: str) -> bool:
|
||||||
"""Checks if a given string matches a specific format of sequences enclosed in square brackets.
|
"""Check that the string is a sequence of bracketed chunks.
|
||||||
|
|
||||||
This function uses a regular expression to determine if the input string adheres to a format
|
This function uses a regular expression to determine if the input string adheres to
|
||||||
where sequences of text are enclosed within square brackets. Each sequence must be separated
|
a format
|
||||||
|
where sequences of text are enclosed within square brackets. Each sequence must be
|
||||||
|
separated
|
||||||
by spaces, and the entire string must only consist of these bracketed sequences.
|
by spaces, and the entire string must only consist of these bracketed sequences.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The string to be checked against the format.
|
input_string : str
|
||||||
|
The string to be checked against the format.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -576,20 +638,20 @@ def is_correct_format(input_string):
|
|||||||
return bool(re.match(pattern, input_string))
|
return bool(re.match(pattern, input_string))
|
||||||
|
|
||||||
|
|
||||||
def check_format(input_data):
|
def check_format(input_data: str | list[str]) -> bool | list[bool] | None:
|
||||||
"""Checks if the input data (input_data or list of strings) matches a specific format.
|
"""Check one string, or every string in a list, for the chunk format.
|
||||||
|
|
||||||
This function determines if the input data,
|
This function determines if the input data,
|
||||||
which can be either a single string or a list of strings,
|
which can be either a single string or a list of strings,
|
||||||
adheres to a predefined format where sequences of text are enclosed within square brackets.
|
adheres to a predefined format where sequences of text are enclosed within square
|
||||||
|
brackets.
|
||||||
The format check is performed by the 'is_correct_format' function.
|
The format check is performed by the 'is_correct_format' function.
|
||||||
For a list, the check is applied to each string in the list.
|
For a list, the check is applied to each string in the list.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_data (str or list of str):
|
input_data : str or list of str
|
||||||
The input data to be checked.
|
The input data to be checked. Can be a single string or a list of strings.
|
||||||
Can be a single string or a list of strings.
|
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -611,26 +673,33 @@ def check_format(input_data):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def check_and_reformat(input_string, check_func, format_func):
|
def check_and_reformat(
|
||||||
"""Checks what type of string got inputed and formats it accordingly."""
|
input_string: str,
|
||||||
|
check_func: Callable[[str], bool],
|
||||||
|
format_func: Callable[[str], str],
|
||||||
|
) -> str | None:
|
||||||
|
"""Check what type of string got inputed and formats it accordingly."""
|
||||||
if check_func(input_string):
|
if check_func(input_string):
|
||||||
print(f"formatted by {format_func.__name__}", input_string)
|
logger.info("formatted by %s %s", format_func.__name__, input_string)
|
||||||
return format_func(input_string)
|
return format_func(input_string)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def reformat(input_string):
|
def reformat(input_string: str) -> str | bool:
|
||||||
"""Reformat the input string by applying a series of format checks and corresponding
|
"""Identify the input's format and apply the matching reformatter.
|
||||||
|
|
||||||
reformatting functions. This function attempts to identify the format of the input
|
reformatting functions. This function attempts to identify the format of the input
|
||||||
string and apply the appropriate reformatting.
|
string and apply the appropriate reformatting.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
str or bool: The reformatted string if a suitable format is recognized, or False if the format
|
str or bool: The reformatted string if a suitable format is recognized, or False if
|
||||||
|
the format
|
||||||
is not recognized and cannot be reformatted.
|
is not recognized and cannot be reformatted.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@ -656,17 +725,21 @@ def reformat(input_string):
|
|||||||
if result is not None:
|
if result is not None:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
print("ERROR! reformat did not recognize the format of string! ", input_string)
|
logger.info(
|
||||||
|
"ERROR! reformat did not recognize the format of string! %s", input_string
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def remove_empty_brackets(input_string):
|
def remove_empty_brackets(input_string: str) -> str:
|
||||||
"""Remove empty brackets (with or without spaces) from the input string and
|
"""Remove empty brackets (with or without spaces) from the input string and.
|
||||||
|
|
||||||
eliminate any extra spaces that might be left after removing brackets.
|
eliminate any extra spaces that might be left after removing brackets.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string from which empty brackets should be removed.
|
input_string : str
|
||||||
|
The input string from which empty brackets should be removed.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -680,35 +753,35 @@ def remove_empty_brackets(input_string):
|
|||||||
cleaned_string = re.sub(pattern, "", input_string)
|
cleaned_string = re.sub(pattern, "", input_string)
|
||||||
|
|
||||||
# Remove any extra spaces that might be left after removing brackets
|
# Remove any extra spaces that might be left after removing brackets
|
||||||
cleaned_string = re.sub(r"\s{2,}", " ", cleaned_string).strip()
|
return re.sub(r"\s{2,}", " ", cleaned_string).strip()
|
||||||
|
|
||||||
return cleaned_string
|
|
||||||
|
|
||||||
|
|
||||||
def reformat_brackets(input_string):
|
def reformat_brackets(input_string: str) -> str:
|
||||||
"""Reformat the input string by enclosing text inside square brackets.
|
"""Reformat the input string by enclosing text inside square brackets.
|
||||||
|
|
||||||
This function searches for text enclosed in square brackets (with or without spaces)
|
This function searches for text enclosed in square brackets (with or without spaces)
|
||||||
and ensures that the text inside the brackets is surrounded by spaces.
|
and ensures that the text inside the brackets is surrounded by spaces.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
input_string (str): The input string to be reformatted.
|
input_string : str
|
||||||
|
The input string to be reformatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
str: A reformatted string with text inside square brackets and spaces around it.
|
str: A reformatted string with text inside square brackets and spaces around it.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
formatted_string = re.sub(r"\[\s*(.*?)\s*\]", r"[ \1 ]", input_string)
|
return re.sub(r"\[\s*(.*?)\s*\]", r"[ \1 ]", input_string)
|
||||||
return formatted_string
|
|
||||||
|
|
||||||
|
|
||||||
def generate_instruction(sentence):
|
def generate_instruction(sentence: str) -> str:
|
||||||
"""Generate an instruction for dividing a given sentence into iSTS chunks.
|
"""Generate an instruction for dividing a given sentence into iSTS chunks.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
sentence (str): The sentence to be divided into chunks.
|
sentence : str
|
||||||
|
The sentence to be divided into chunks.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -723,13 +796,13 @@ def generate_instruction(sentence):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def call_api(instruction):
|
def call_api(instruction: str) -> ChatCompletion:
|
||||||
"""Call the API using the provided instruction to generate a response.
|
"""Call the API using the provided instruction to generate a response.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
client (OpenAI.Client): An OpenAI API client object.
|
instruction : str
|
||||||
instruction (str): The instruction to provide to the API.
|
The instruction to provide to the API.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -747,28 +820,29 @@ def call_api(instruction):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def process_response(response):
|
def process_response(response: ChatCompletion) -> str:
|
||||||
"""Process the response received from the API and extract the chunked sentence.
|
"""Process the response received from the API and extract the chunked sentence.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
response (dict): The response generated by the API.
|
response : dict
|
||||||
|
The response generated by the API.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
str: The chunked sentence extracted from the response.
|
str: The chunked sentence extracted from the response.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
chunked_sentence = response.choices[0].message.content.strip()
|
return response.choices[0].message.content.strip()
|
||||||
return chunked_sentence
|
|
||||||
|
|
||||||
|
|
||||||
def check_and_format_sentence(chunked_sentence):
|
def check_and_format_sentence(chunked_sentence: str) -> str | bool:
|
||||||
"""Check and format a chunked sentence by applying a series of checks and reformatting.
|
"""Check a chunked sentence and reformat it if it needs it.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
chunked_sentence (str): The chunked sentence to be checked and formatted.
|
chunked_sentence : str
|
||||||
|
The chunked sentence to be checked and formatted.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -778,22 +852,24 @@ def check_and_format_sentence(chunked_sentence):
|
|||||||
"""
|
"""
|
||||||
reformated = reformat(chunked_sentence)
|
reformated = reformat(chunked_sentence)
|
||||||
if not reformated:
|
if not reformated:
|
||||||
print("ERROR! failed to reformat! ", chunked_sentence, reformated)
|
logger.info("ERROR! failed to reformat! %s %s", chunked_sentence, reformated)
|
||||||
return False
|
return False
|
||||||
reformated = reformated.strip(",")
|
reformated = reformated.strip(",")
|
||||||
if not is_correct_format(reformated):
|
if not is_correct_format(reformated):
|
||||||
print("ERROR! wrong format ", chunked_sentence, reformated)
|
logger.info("ERROR! wrong format %s %s", chunked_sentence, reformated)
|
||||||
return False
|
return False
|
||||||
return reformated
|
return reformated
|
||||||
|
|
||||||
|
|
||||||
def finalize_formatting(reformatted):
|
def finalize_formatting(reformatted: str) -> str:
|
||||||
"""Finalize the formatting of a reformatted string by removing empty brackets and ensuring
|
"""Finalise a reformatted string: drop empty brackets, tidy spacing.
|
||||||
|
|
||||||
that text inside brackets is surrounded by spaces.
|
that text inside brackets is surrounded by spaces.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
reformatted (str): The reformatted string to be finalized.
|
reformatted : str
|
||||||
|
The reformatted string to be finalized.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -801,24 +877,26 @@ def finalize_formatting(reformatted):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
reformatted = remove_empty_brackets(reformatted)
|
reformatted = remove_empty_brackets(reformatted)
|
||||||
reformatted = reformat_brackets(reformatted)
|
return reformat_brackets(reformatted)
|
||||||
return reformatted
|
|
||||||
|
|
||||||
|
|
||||||
def process_sentence(sentence, chunked_sentences):
|
def process_sentence(sentence: str, chunked_sentences: list[str]) -> list[str] | bool:
|
||||||
"""Process a sentence by generating iSTS chunks and
|
"""Process a sentence by generating iSTS chunks and.
|
||||||
|
|
||||||
adding them to the provided list of chunked sentences.
|
adding them to the provided list of chunked sentences.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
sentence (str): The input sentence to be processed.
|
sentence : str
|
||||||
chunked_sentences (list): A list of previously chunked sentences to which the processed sentence
|
The input sentence to be processed.
|
||||||
will be appended.
|
chunked_sentences : list
|
||||||
client (OpenAI.Client): An OpenAI API client object for calling the API.
|
A list of previously chunked sentences to which the processed sentence will be
|
||||||
|
appended.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list or bool: The updated list of chunked sentences if successful, or False if there are errors.
|
list or bool: The updated list of chunked sentences if successful, or False if there
|
||||||
|
are errors.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
instruction = generate_instruction(sentence)
|
instruction = generate_instruction(sentence)
|
||||||
@ -832,14 +910,21 @@ def process_sentence(sentence, chunked_sentences):
|
|||||||
return chunked_sentences
|
return chunked_sentences
|
||||||
|
|
||||||
|
|
||||||
def chunk_sentences(input_file, output):
|
def chunk_sentences(input_file: str, output: str) -> None:
|
||||||
"""Read sentences from a file, process each sentence to generate iSTS chunks,
|
"""Read sentences from a file, process each sentence to generate iSTS chunks,.
|
||||||
|
|
||||||
and write the chunked sentences to an output file.
|
and write the chunked sentences to an output file.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
file_path (str): The path to the input file containing sentences to be processed.
|
input_file : str
|
||||||
output_path (str): The path to the output file where chunked sentences will be written.
|
file_path (str): The path to the input file containing sentences to be
|
||||||
|
processed. output_path (str): The path to the output file where chunked
|
||||||
|
sentences will be written.
|
||||||
|
output : str
|
||||||
|
file_path (str): The path to the input file containing sentences to be
|
||||||
|
processed. output_path (str): The path to the output file where chunked
|
||||||
|
sentences will be written.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -847,15 +932,15 @@ def chunk_sentences(input_file, output):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# Read the sentences from the file
|
# Read the sentences from the file
|
||||||
with open(input_file, encoding="utf-8") as file:
|
with Path(input_file).open(encoding="utf-8") as file:
|
||||||
sentences = file.readlines()
|
sentences = file.readlines()
|
||||||
|
|
||||||
# Process each sentence
|
# Process each sentence
|
||||||
chunked_sentences = sentence_loop(sentences)
|
chunked_sentences = sentence_loop(sentences)
|
||||||
print(chunked_sentences)
|
logger.info("%s", chunked_sentences)
|
||||||
|
|
||||||
# Write the chunked sentences to a new file
|
# Write the chunked sentences to a new file
|
||||||
with open(output, "w", encoding="utf-8") as output_file:
|
with Path(output).open("w", encoding="utf-8") as output_file:
|
||||||
output_file.writelines(sentence + "\n" for sentence in chunked_sentences)
|
output_file.writelines(sentence + "\n" for sentence in chunked_sentences)
|
||||||
|
|
||||||
|
|
||||||
@ -882,6 +967,6 @@ output_files = [
|
|||||||
# Change me to os.environ['API_KEY']
|
# Change me to os.environ['API_KEY']
|
||||||
client = OpenAI(api_key=os.environ["API_KEY"])
|
client = OpenAI(api_key=os.environ["API_KEY"])
|
||||||
|
|
||||||
for input_path, output_path in zip(input_files, output_files):
|
for input_path, output_path in zip(input_files, output_files, strict=False):
|
||||||
chunk_sentences(input_path, output_path)
|
chunk_sentences(input_path, output_path)
|
||||||
print("FINISHED GPT CHUNKS FOR FILE: ", input_path)
|
logger.info("FINISHED GPT CHUNKS FOR FILE: %s", input_path)
|
||||||
|
|||||||
75
Programming/NLP/processing.py
Normal file → Executable file
75
Programming/NLP/processing.py
Normal file → Executable file
@ -1,24 +1,30 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Load the SemEval corpus: sentences, chunkings and gold alignments."""
|
||||||
|
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def load_sentences(senteance1_path: str, sentance2_path: str) -> pd.DataFrame:
|
def load_sentences(senteance1_path: str, sentance2_path: str) -> pd.DataFrame:
|
||||||
"""Loads the sentences from the given paths and outputs in a 2 columns dataframe."""
|
"""Load the sentences from the given paths and outputs in a 2 columns dataframe."""
|
||||||
sentance1 = pd.read_csv(senteance1_path, dtype=str, delimiter="}", header=None)
|
sentance1 = pd.read_csv(senteance1_path, dtype=str, delimiter="}", header=None)
|
||||||
sentance1.columns = ["sentance1"]
|
sentance1.columns = ["sentance1"]
|
||||||
|
|
||||||
sentance2 = pd.read_csv(sentance2_path, dtype=str, delimiter="}", header=None)
|
sentance2 = pd.read_csv(sentance2_path, dtype=str, delimiter="}", header=None)
|
||||||
sentance2.columns = ["sentance2"]
|
sentance2.columns = ["sentance2"]
|
||||||
|
|
||||||
sentances = pd.concat([sentance1, sentance2], axis=1)
|
return pd.concat([sentance1, sentance2], axis=1)
|
||||||
return sentances
|
|
||||||
|
|
||||||
|
|
||||||
def chunk2list(chunks: str) -> list:
|
def chunk2list(chunks: str) -> list:
|
||||||
"""Takes str that is all chunks from a chunked sentance and returns a list of all the chunks as seperate items."""
|
"""Split one chunked sentence into its chunks."""
|
||||||
chunks = chunks.replace("[", "")
|
chunks = chunks.replace("[", "")
|
||||||
chunks = chunks.replace("]", "")
|
chunks = chunks.replace("]", "")
|
||||||
chunks = chunks.replace(" ", "|")
|
chunks = chunks.replace(" ", "|")
|
||||||
@ -27,12 +33,14 @@ def chunk2list(chunks: str) -> list:
|
|||||||
split = [
|
split = [
|
||||||
re.sub(r"^\s+|\s+$", "", s) for s in split
|
re.sub(r"^\s+|\s+$", "", s) for s in split
|
||||||
] # remove spaces at the beggining and end of chunks
|
] # remove spaces at the beggining and end of chunks
|
||||||
split = [re.sub(r"[^\w\s]", "", s) for s in split] # remove punctuation
|
return [re.sub(r"[^\w\s]", "", s) for s in split] # remove punctuation
|
||||||
return split
|
|
||||||
|
|
||||||
|
|
||||||
def load_chunked(chunked_path1: str, chunked_path2: str) -> pd.DataFrame:
|
def load_chunked(chunked_path1: str, chunked_path2: str) -> pd.DataFrame:
|
||||||
"""Loads chunked sentances in [ chunk1 ] [ chunk2 ] format into dataframe with lists of chunks."""
|
"""Load two chunk files into a dataframe of chunk lists.
|
||||||
|
|
||||||
|
The on-disk format is ``[ chunk1 ] [ chunk2 ] ...`` per line.
|
||||||
|
"""
|
||||||
chunked_sentance1 = pd.read_csv(
|
chunked_sentance1 = pd.read_csv(
|
||||||
chunked_path1, dtype=str, delimiter="}", header=None
|
chunked_path1, dtype=str, delimiter="}", header=None
|
||||||
)
|
)
|
||||||
@ -57,15 +65,18 @@ def load_chunked(chunked_path1: str, chunked_path2: str) -> pd.DataFrame:
|
|||||||
|
|
||||||
|
|
||||||
def return_characteers(cell: str) -> str:
|
def return_characteers(cell: str) -> str:
|
||||||
"""Converts the alignment data to restore the <==> and & tokens."""
|
"""Convert the alignment data to restore the <==> and & tokens."""
|
||||||
cell = cell.replace("ARROWS_PLACEHOLDER", "<==>")
|
cell = cell.replace("ARROWS_PLACEHOLDER", "<==>")
|
||||||
cell = cell.replace("AMPERSAND_PLACEHOLDER", "&")
|
return cell.replace("AMPERSAND_PLACEHOLDER", "&")
|
||||||
return cell
|
|
||||||
|
|
||||||
|
|
||||||
def load_alignment(alignment_path: str) -> pd.DataFrame:
|
def load_alignment(alignment_path: str) -> pd.DataFrame:
|
||||||
"""Loads the alignment file. Parses only the <alignment> tag and puts the data into a dataframe."""
|
"""Load the gold alignment file into a dataframe.
|
||||||
with open(alignment_path) as file:
|
|
||||||
|
Only the ``<alignment>`` tag is parsed; the rest of the .wa XML is
|
||||||
|
metadata this project does not use.
|
||||||
|
"""
|
||||||
|
with Path(alignment_path).open() as file:
|
||||||
file_content = file.read()
|
file_content = file.read()
|
||||||
|
|
||||||
# <==> and & break xml loaders so it needs to be replaces with something else
|
# <==> and & break xml loaders so it needs to be replaces with something else
|
||||||
@ -76,7 +87,7 @@ def load_alignment(alignment_path: str) -> pd.DataFrame:
|
|||||||
modified_content = f"<root>{modified_content}</root>"
|
modified_content = f"<root>{modified_content}</root>"
|
||||||
|
|
||||||
modified_file_path = "temp.wa"
|
modified_file_path = "temp.wa"
|
||||||
with open(modified_file_path, "w") as modified_file:
|
with Path(modified_file_path).open("w") as modified_file:
|
||||||
modified_file.write(modified_content)
|
modified_file.write(modified_content)
|
||||||
|
|
||||||
# Parse the modified file using ElementTree
|
# Parse the modified file using ElementTree
|
||||||
@ -100,19 +111,29 @@ def load_alignment(alignment_path: str) -> pd.DataFrame:
|
|||||||
return y
|
return y
|
||||||
|
|
||||||
|
|
||||||
def prettyprint(element, **kwargs):
|
def prettyprint(element: etree._Element, **kwargs: object) -> None:
|
||||||
|
"""Log an XML element with lxml's pretty printer."""
|
||||||
xml = etree.tostring(element, pretty_print=True, **kwargs)
|
xml = etree.tostring(element, pretty_print=True, **kwargs)
|
||||||
print(xml.decode(), end="")
|
logger.info("%s", xml.decode())
|
||||||
|
|
||||||
|
|
||||||
def test_XML():
|
def log_first_alignment(alignments_data: list[dict[str, str]]) -> None:
|
||||||
# test out the format
|
"""Log the first parsed alignment, to eyeball the .wa parsing.
|
||||||
print(alignments_data[0]["alignment_text"])
|
|
||||||
|
This was `test_XML()` and read a module-level `alignments_data` that does
|
||||||
|
not exist at module scope -- it only ever worked when pasted into a
|
||||||
|
session where load_alignment had already run. It takes the data as an
|
||||||
|
argument now, and is no longer named test_* so pytest does not collect it.
|
||||||
|
"""
|
||||||
|
logger.info("%s", alignments_data[0]["alignment_text"])
|
||||||
|
|
||||||
|
|
||||||
def generate_train_test_split(xy: pd.DataFrame):
|
def generate_train_test_split(xy: pd.DataFrame) -> tuple[pd.DataFrame, pd.DataFrame]:
|
||||||
"""Generates a train, validate, test split of the given dataframes in a 60% 20% 20% ratio."""
|
"""Split the dataframe into a train and a test half.
|
||||||
# data = pd.merge(x, y, left_index=True, right_index=True)
|
|
||||||
|
The docstring said 60/20/20 when it was written; the code splits at 3%,
|
||||||
|
which is what the experiments actually used.
|
||||||
|
"""
|
||||||
data = xy
|
data = xy
|
||||||
train, test = np.split(
|
train, test = np.split(
|
||||||
data.sample(frac=1, random_state=42), [int(0.03 * len(data))]
|
data.sample(frac=1, random_state=42), [int(0.03 * len(data))]
|
||||||
@ -121,21 +142,23 @@ def generate_train_test_split(xy: pd.DataFrame):
|
|||||||
return train, test
|
return train, test
|
||||||
|
|
||||||
|
|
||||||
def generate_alignment_format(dataFrame: pd.DataFrame, id: int) -> str:
|
def generate_alignment_format(data_frame: pd.DataFrame, row_id: int) -> str:
|
||||||
|
"""Render one row's two chunkings as the numbered text GPT is shown."""
|
||||||
output = "seq1:\n"
|
output = "seq1:\n"
|
||||||
chunks1 = dataFrame["chunked_sentance1"][id]
|
chunks1 = data_frame["chunked_sentance1"][row_id]
|
||||||
for i, chunk in enumerate(chunks1):
|
for i, chunk in enumerate(chunks1):
|
||||||
output = output + str(i + 1) + ") " + str(chunk) + "\n"
|
output = output + str(i + 1) + ") " + str(chunk) + "\n"
|
||||||
output = output + "\nseq2:\n"
|
output = output + "\nseq2:\n"
|
||||||
chunks2 = dataFrame["chunked_sentance2"][id]
|
chunks2 = data_frame["chunked_sentance2"][row_id]
|
||||||
for i, chunk in enumerate(chunks2):
|
for i, chunk in enumerate(chunks2):
|
||||||
output = output + str(i + 1) + ") " + str(chunk) + "\n"
|
output = output + str(i + 1) + ") " + str(chunk) + "\n"
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
def get_chunks_as_text(data: pd.DataFrame) -> str:
|
def get_chunks_as_text(data: pd.DataFrame) -> tuple[list[str], pd.Index]:
|
||||||
|
"""Render every row's chunk pair as bracketed text, with its index."""
|
||||||
output = []
|
output = []
|
||||||
for index, row in data.iterrows():
|
for _index, row in data.iterrows():
|
||||||
chunks = ""
|
chunks = ""
|
||||||
for chunk in row["chunked_sentance1"]:
|
for chunk in row["chunked_sentance1"]:
|
||||||
chunks = chunks + "[ " + chunk + " ] "
|
chunks = chunks + "[ " + chunk + " ] "
|
||||||
|
|||||||
19
Programming/NLP/prompts/alignment.txt
Normal file
19
Programming/NLP/prompts/alignment.txt
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
You are a machine designed to align chunks from 2 sentences. This means you will be taking each chunk from one sentence and comparing it to every chunk from the other sentence. Choose the chunks with the strongest relation and assign them a score.
|
||||||
|
|
||||||
|
The possible relations are:
|
||||||
|
EQUI: both chunks have the same meaning, they are semantically equivalent in this context.
|
||||||
|
OPPO: the meanings of the chunks are in opposition to each other, lying in an inherently incompatible binary relationship.
|
||||||
|
SPE1: both chunks have similar meanings, but chunk in sentence 1 is more specific.
|
||||||
|
SPE2: like SPE1, but it is the chunk in sentence 2 which is more specific.
|
||||||
|
SIMI: both chunks have similar meanings, they share similar attributes and there is no EQUI, OPPO, SPE1 or SPE2 relation
|
||||||
|
REL: both chunks are not considered similar but they are closely related by some relation not mentioned above (i.e. no EQUI, OPPO, SPE1, SPE2, or SIMI relation).
|
||||||
|
NOALI: this chunk has not any corresponding chunk in the other sentence. Therefore, it is left unaligned.
|
||||||
|
|
||||||
|
The possible scores are a range from 0 to 5 where 0 means that the chunks are not related and 5 means their meanings are the same in the given context.
|
||||||
|
|
||||||
|
A chunk can be aligned to multiple different chunks. Chunks can also be grouped in a relation. If a chunk has no relation to any other chunk, give it a NOALI relation to a an empty chunk.
|
||||||
|
|
||||||
|
If there are spelling mistakes do not correct them.
|
||||||
|
|
||||||
|
Present the answers in this form:
|
||||||
|
chunk from first sentence <==> chunk from the second sentence // alignment type // score
|
||||||
21
Programming/NLP/prompts/alignment_with_comment.txt
Normal file
21
Programming/NLP/prompts/alignment_with_comment.txt
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
You are a machine designed to align chunks from 2 sentences. This means you will be taking each chunk from one sentence and comparing it to every chunk from the other sentence. Choose the chunks with the strongest relation and assign them a relation and a score.
|
||||||
|
|
||||||
|
The possible relations are:
|
||||||
|
EQUI: both chunks have the same meaning
|
||||||
|
OPPO: the meanings of the chunks are opposite
|
||||||
|
SPE1: both chunks have similar meanings, but chunk in sentence 1 is more specific.
|
||||||
|
SPE2: both chunks have similar meanings, but chunk in sentence 2 is more specific.
|
||||||
|
SIMI: both chunks have similar meanings
|
||||||
|
REL: both chunks are not considered similar, but they are closely related by some relation not mentioned above
|
||||||
|
NOALI: this chunk has not any corresponding chunk in the other sentence.
|
||||||
|
|
||||||
|
The possible scores are a range from 0 to 5 where 0 means that the chunks are not related and 5 means their meanings are the same in the given context.
|
||||||
|
|
||||||
|
A chunk can be aligned with multiple different chunks. Chunks can also be grouped in a relation. If a chunk has no relation to any other chunk, give it a NOALI relation to a chunk "0".
|
||||||
|
|
||||||
|
If there are spelling mistakes, do not correct them.
|
||||||
|
|
||||||
|
Present the answers in this form:
|
||||||
|
chunk from first sentence <==> chunk from the second sentence // relation // score // comment
|
||||||
|
|
||||||
|
in the comment you can explain your choice.
|
||||||
41
Programming/NLP/rating.py
Normal file → Executable file
41
Programming/NLP/rating.py
Normal file → Executable file
@ -1,5 +1,42 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Ask the model to rate how similar two chunks are."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
|
|
||||||
|
|
||||||
def rate_similarity(c1: list, c2: list) -> str:
|
def _api_key() -> str:
|
||||||
client = OpenAI(api_key="REDACTED_OPENAI_API_KEY")
|
"""Return OPENAI_API_KEY, or explain that it is missing."""
|
||||||
|
key = os.environ.get("OPENAI_API_KEY")
|
||||||
|
if not key:
|
||||||
|
msg = "set OPENAI_API_KEY to run the rating script"
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
return key
|
||||||
|
|
||||||
|
|
||||||
|
def rate_similarity(c1: list[str], c2: list[str]) -> str:
|
||||||
|
"""Rate the similarity of two chunk lists on the course's 0-5 scale.
|
||||||
|
|
||||||
|
This was submitted unfinished: it built a client and stopped, so ``c1``
|
||||||
|
and ``c2`` were never used and the function returned None despite being
|
||||||
|
annotated ``-> str``. The call below is the one the rest of this package
|
||||||
|
already makes (see gpt_alignment.call_api); nothing else about the
|
||||||
|
original is changed.
|
||||||
|
"""
|
||||||
|
client = OpenAI(api_key=_api_key())
|
||||||
|
response = client.chat.completions.create(
|
||||||
|
model="gpt-3.5-turbo-1106",
|
||||||
|
messages=[
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": (
|
||||||
|
"Rate on a 0-5 scale how similar these two chunk lists are, "
|
||||||
|
"where 0 is unrelated and 5 is the same meaning. Answer with "
|
||||||
|
"the number alone.\n"
|
||||||
|
f"1: {c1}\n2: {c2}"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return str(response.choices[0].message.content).strip()
|
||||||
|
|||||||
83
Programming/NLP/test_goldStandard/chunks.py
Normal file → Executable file
83
Programming/NLP/test_goldStandard/chunks.py
Normal file → Executable file
@ -1,50 +1,69 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Ask GPT to divide the student-answer sentences into iSTS chunks.
|
||||||
|
|
||||||
|
The earlier, simpler sibling of ``../gpt_chunks.py``: no format detection and
|
||||||
|
no reformatting pass, just one request per sentence with the reply written
|
||||||
|
straight out. Its output is ``chunks_gpt_two.txt`` next to this file.
|
||||||
|
|
||||||
|
The prompt lives in ``prompt.txt`` because it is a single 373-character line
|
||||||
|
that cannot be wrapped without changing the prompt itself.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from openai import OpenAI
|
from openai import OpenAI
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def sentence_loop(sentences):
|
PROMPT = (Path(__file__).parent / "prompt.txt").read_text(encoding="utf-8").strip()
|
||||||
# Process each sentence
|
|
||||||
chunked_sentences = []
|
|
||||||
|
def sentence_loop(client: OpenAI, sentences: list[str]) -> list[str]:
|
||||||
|
"""Chunk every sentence, in order, and collect the replies."""
|
||||||
|
chunked_sentences: list[str] = []
|
||||||
for sentence in sentences:
|
for sentence in sentences:
|
||||||
chunked_sentences = process_sentence(sentence, chunked_sentences)
|
chunked_sentences = process_sentence(client, sentence, chunked_sentences)
|
||||||
return chunked_sentences
|
return chunked_sentences
|
||||||
|
|
||||||
|
|
||||||
def process_sentence(sentence, chunked_sentences):
|
def process_sentence(
|
||||||
|
client: OpenAI, sentence: str, chunked_sentences: list[str]
|
||||||
|
) -> list[str]:
|
||||||
|
"""Chunk one sentence and append the reply to *chunked_sentences*."""
|
||||||
response = client.chat.completions.create(
|
response = client.chat.completions.create(
|
||||||
messages=[
|
messages=[{"role": "user", "content": PROMPT + sentence}],
|
||||||
{
|
|
||||||
"role": "user",
|
|
||||||
"content": "Please divide the following sentence into iSTS chunks. Format the response as: [[chunk 1]] [[chunk 2]] ... Ensure each chunk is enclosed in double square brackets and separated by a space. If you cannot correctly chunkify the sentence, return an error message in the format: 'ERROR: Cannot chunkify sentence: <sentence>'. Here is the sentence: "
|
|
||||||
+ sentence,
|
|
||||||
}
|
|
||||||
],
|
|
||||||
model="gpt-3.5-turbo",
|
model="gpt-3.5-turbo",
|
||||||
)
|
)
|
||||||
|
chunked_sentence = str(response.choices[0].message.content)
|
||||||
chunked_sentence = response.choices[0].message.content
|
logger.info("chunked_sentence: %s", chunked_sentence)
|
||||||
print("chunked_sentence: ", chunked_sentence)
|
|
||||||
chunked_sentences.append(chunked_sentence)
|
chunked_sentences.append(chunked_sentence)
|
||||||
return chunked_sentences
|
return chunked_sentences
|
||||||
|
|
||||||
|
|
||||||
def chunk_sentences(file_path, output_path, api_key):
|
def chunk_sentences(file_path: str, output_path: str) -> None:
|
||||||
# Read the sentences from the file
|
"""Chunk every sentence in *file_path* and write them to *output_path*."""
|
||||||
with open(file_path) as file:
|
# The key was a literal here with a "Replace me with os.environ" comment
|
||||||
|
# next to it, which is now done rather than noted.
|
||||||
|
key = os.environ.get("OPENAI_API_KEY")
|
||||||
|
if not key:
|
||||||
|
msg = "set OPENAI_API_KEY to run the chunking script"
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
client = OpenAI(api_key=key)
|
||||||
|
|
||||||
|
with Path(file_path).open(encoding="utf-8") as file:
|
||||||
sentences = file.readlines()
|
sentences = file.readlines()
|
||||||
|
|
||||||
# Process each sentence
|
chunked = sentence_loop(client, sentences)
|
||||||
chunk_sentences = sentence_loop(sentences)
|
logger.info("%s", chunked)
|
||||||
print(chunk_sentences)
|
with Path(output_path).open("w", encoding="utf-8") as output_file:
|
||||||
# Write the chunked sentences to a new file
|
output_file.writelines(sentence + "\n" for sentence in chunked)
|
||||||
with open(output_path, "w") as output_file:
|
|
||||||
output_file.writelines(sentence + "\n" for sentence in chunk_sentences)
|
|
||||||
|
|
||||||
|
|
||||||
# Usage
|
if __name__ == "__main__":
|
||||||
file_path = "student/STSint.testinput.answers-students.sent2.txt"
|
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||||
output_path = "chunks_gpt_two.txt"
|
chunk_sentences(
|
||||||
# Replace me with os.environ['API_KEY']
|
"student/STSint.testinput.answers-students.sent2.txt",
|
||||||
api_key = "REDACTED_OPENAI_API_KEY"
|
"chunks_gpt_two.txt",
|
||||||
client = OpenAI(api_key=api_key)
|
)
|
||||||
|
|
||||||
chunk_sentences(file_path, output_path, api_key)
|
|
||||||
|
|||||||
1
Programming/NLP/test_goldStandard/prompt.txt
Normal file
1
Programming/NLP/test_goldStandard/prompt.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Please divide the following sentence into iSTS chunks. Format the response as: [[chunk 1]] [[chunk 2]] ... Ensure each chunk is enclosed in double square brackets and separated by a space. If you cannot correctly chunkify the sentence, return an error message in the format: 'ERROR: Cannot chunkify sentence: <sentence>'. Here is the sentence:
|
||||||
Loading…
Reference in New Issue
Block a user