Skip to content
Snippets Groups Projects
Commit 0c9ebf45 authored by oschmanf's avatar oschmanf
Browse files

Merge branch 'dev-train-models' into 'main'

Dev train models

See merge request !2
parents 49e4d81c a11e862f
No related branches found
No related tags found
1 merge request!2Dev train models
This diff is collapsed.
%% Cell type:code id:a7eee72f tags:
``` python
from src.preprocessing_text import TextLoader, TextProcessor
import spacy
```
%% Cell type:code id:1cad5dc2 tags:
``` python
input_data = '/Users/franziskaoschmann/Documents/public_policy/data/tamedia_for_classifier_v2_preproc.csv'
tl = TextLoader(input_data)
df_de = tl.load_text_csv(newspaper = 'tagesanzeiger', lang ='de')
nlp = spacy.load('de_core_news_sm')
```
%% Cell type:code id:71ddb0ba tags:
``` python
import numpy as np
np.where(['@' in t for t in df_de.text[:10000]])
```
%% Output
(array([ 566, 1061, 1381, 1445, 1496, 1589, 1723, 1934, 2221, 2641, 2947,
3628, 3899, 4007, 4280, 4650, 4852, 5202, 5656, 5770, 5985, 6260,
7141, 7204, 7804, 7972, 8005, 8261, 8504, 8846, 8857]),)
%% Cell type:code id:28887dee tags:
``` python
sample_text = df_de.iloc[566].text
```
%% Cell type:code id:163a698c tags:
``` python
sample_text
```
%% Output
'Die Übersterblichkeit wird aufgrund der Todesfallzahlen des 7-Tage-Durchschnitts und nicht eines Jahresdurchschnitts berechnet. So sind übrigens auch saisonale Unterschiede berücksichtigt.\nUnd nein @John Zürcher, es gab keine statistisch relevante Untersterblichkeit im Sommer, sondern nur eine, die sich am unteren Band der durchschnittlichen Sterblichkeit der letzten 5 Jahre bewegt hat. Den Grund dafür habe ich erwähnt.\n@Martin Mader: was Sie schreiben ist Unsinn. Die durchschnittliche Sterblichkeit wird anhand der tatsächlichen Todesfälle über die letzten 5 Jahre festgehalten. Da sind alle massgebenden Parameter automatisch enthalten. Logisch, dass dieser so berechnete Durchschnitt sich jährlich aufgrund der Lebenserwartung verändert.\n@Mark Keller: Ich habe nie etwas anderes behauptet. Man kann sogar soweit gehen, nachträglich die durchschnittliche Lebenszeit zu berechnen, die einem an dieser Pandemie verstorbenen Menschen genommen wurde. Sobald sich die Todesfallzahlen nach einer Phase der Übersterblichkeit durch eine unterdurchschnittliche Sterblichkeit oder durch eine Untersterblichkeit ausgeglichen haben, erhält man diesen Wert (ich gehe von rund 6 Monaten aus). Da es aber nur ein Durchschnittswert ist, sagt er nicht über die Lebenszeit aus, die einer an Corona verstorbenen Person wirklich weggenommen wurde. Bei den einen mögen es ein paar Tage oder Wochen sein, bei anderen aber mehrere Jahre oder gar Jahrzehnte.'
%% Cell type:markdown id:3babd6fc tags:
### Remove spaces
%% Cell type:code id:d114f6a2 tags:
``` python
tp = TextProcessor(nlp)
text_proc = tp.remove_spaces(sample_text)
text_proc
```
%% Output
'Die Übersterblichkeit wird aufgrund der Todesfallzahlen des 7-Tage-Durchschnitts und nicht eines Jahresdurchschnitts berechnet. So sind übrigens auch saisonale Unterschiede berücksichtigt. Und nein @John Zürcher, es gab keine statistisch relevante Untersterblichkeit im Sommer, sondern nur eine, die sich am unteren Band der durchschnittlichen Sterblichkeit der letzten 5 Jahre bewegt hat. Den Grund dafür habe ich erwähnt. @Martin Mader: was Sie schreiben ist Unsinn. Die durchschnittliche Sterblichkeit wird anhand der tatsächlichen Todesfälle über die letzten 5 Jahre festgehalten. Da sind alle massgebenden Parameter automatisch enthalten. Logisch, dass dieser so berechnete Durchschnitt sich jährlich aufgrund der Lebenserwartung verändert. @Mark Keller: Ich habe nie etwas anderes behauptet. Man kann sogar soweit gehen, nachträglich die durchschnittliche Lebenszeit zu berechnen, die einem an dieser Pandemie verstorbenen Menschen genommen wurde. Sobald sich die Todesfallzahlen nach einer Phase der Übersterblichkeit durch eine unterdurchschnittliche Sterblichkeit oder durch eine Untersterblichkeit ausgeglichen haben, erhält man diesen Wert (ich gehe von rund 6 Monaten aus). Da es aber nur ein Durchschnittswert ist, sagt er nicht über die Lebenszeit aus, die einer an Corona verstorbenen Person wirklich weggenommen wurde. Bei den einen mögen es ein paar Tage oder Wochen sein, bei anderen aber mehrere Jahre oder gar Jahrzehnte.'
%% Cell type:markdown id:bfc38df2 tags:
### Remove punctuation
%% Cell type:code id:216b53db tags:
``` python
text_proc = tp.remove_punctuation(text_proc)
text_proc
```
%% Output
'Die Übersterblichkeit wird aufgrund der Todesfallzahlen des 7TageDurchschnitts und nicht eines Jahresdurchschnitts berechnet So sind übrigens auch saisonale Unterschiede berücksichtigt Und nein @John Zürcher es gab keine statistisch relevante Untersterblichkeit im Sommer sondern nur eine die sich am unteren Band der durchschnittlichen Sterblichkeit der letzten 5 Jahre bewegt hat Den Grund dafür habe ich erwähnt @Martin Mader was Sie schreiben ist Unsinn Die durchschnittliche Sterblichkeit wird anhand der tatsächlichen Todesfälle über die letzten 5 Jahre festgehalten Da sind alle massgebenden Parameter automatisch enthalten Logisch dass dieser so berechnete Durchschnitt sich jährlich aufgrund der Lebenserwartung verändert @Mark Keller Ich habe nie etwas anderes behauptet Man kann sogar soweit gehen nachträglich die durchschnittliche Lebenszeit zu berechnen die einem an dieser Pandemie verstorbenen Menschen genommen wurde Sobald sich die Todesfallzahlen nach einer Phase der Übersterblichkeit durch eine unterdurchschnittliche Sterblichkeit oder durch eine Untersterblichkeit ausgeglichen haben erhält man diesen Wert ich gehe von rund 6 Monaten aus Da es aber nur ein Durchschnittswert ist sagt er nicht über die Lebenszeit aus die einer an Corona verstorbenen Person wirklich weggenommen wurde Bei den einen mögen es ein paar Tage oder Wochen sein bei anderen aber mehrere Jahre oder gar Jahrzehnte'
%% Cell type:markdown id:0df7f111 tags:
### Remove @-mentions
%% Cell type:code id:67edcb18 tags:
``` python
text_proc = tp.remove_mentions(text_proc)
text_proc
```
%% Output
'Die Übersterblichkeit wird aufgrund der Todesfallzahlen des 7TageDurchschnitts und nicht eines Jahresdurchschnitts berechnet So sind übrigens auch saisonale Unterschiede berücksichtigt Und nein Zürcher es gab keine statistisch relevante Untersterblichkeit im Sommer sondern nur eine die sich am unteren Band der durchschnittlichen Sterblichkeit der letzten 5 Jahre bewegt hat Den Grund dafür habe ich erwähnt Mader was Sie schreiben ist Unsinn Die durchschnittliche Sterblichkeit wird anhand der tatsächlichen Todesfälle über die letzten 5 Jahre festgehalten Da sind alle massgebenden Parameter automatisch enthalten Logisch dass dieser so berechnete Durchschnitt sich jährlich aufgrund der Lebenserwartung verändert Keller Ich habe nie etwas anderes behauptet Man kann sogar soweit gehen nachträglich die durchschnittliche Lebenszeit zu berechnen die einem an dieser Pandemie verstorbenen Menschen genommen wurde Sobald sich die Todesfallzahlen nach einer Phase der Übersterblichkeit durch eine unterdurchschnittliche Sterblichkeit oder durch eine Untersterblichkeit ausgeglichen haben erhält man diesen Wert ich gehe von rund 6 Monaten aus Da es aber nur ein Durchschnittswert ist sagt er nicht über die Lebenszeit aus die einer an Corona verstorbenen Person wirklich weggenommen wurde Bei den einen mögen es ein paar Tage oder Wochen sein bei anderen aber mehrere Jahre oder gar Jahrzehnte'
%% Cell type:markdown id:6783795b tags:
### Lemmatization
%% Cell type:code id:2421eeb4 tags:
``` python
text_proc = tp.lemmatize_text(text_proc)
text_proc
```
%% Output
'der Übersterblichkeit werden aufgrund der Todesfallzahle der 7TageDurchschnitts und nicht ein Jahresdurchschnitt berechnen so sein übrigens auch saisonal Unterschied berücksichtigen und nein Zürcher es geben kein statistisch relevant Untersterblichkeit in Sommer sondern nur einer der sich an unterer Band der durchschnittlich Sterblichkeit der letzter 5 Jahr bewegen haben der Grund dafür haben ich erwähnen Mader was sie schreiben sein Unsinn der durchschnittlich Sterblichkeit werden anhand der tatsächlich Todesfall über der letzter 5 Jahr festhalten da sein aller massgebend Parameter automatisch enthalt Logisch dass dieser so berechnen Durchschnitt sich jährlich aufgrund der Lebenserwartung verändern Keller ich haben nie etwas anderer behaupten man können sogar soweit gehen nachträglich der durchschnittlich Lebenszeit zu berechnen der ein an dieser Pandemie verstorben Mensch nehmen werden Sobald sich der Todesfallzahle nach ein Phase der Übersterblichkeit durch ein unterdurchschnittlich Sterblichkeit oder durch ein Untersterblichkeit ausgleichen haben erhalten man dieser Wert ich gehen von rund 6 Monat aus da es aber nur ein Durchschnittswert sein sagen er nicht über der Lebenszeit aus der ein an Corona verstorben Person wirklich wegnommen werden bei der einer mögen es ein paar Tag oder Woche sein bei anderer aber mehrere Jahr oder gar Jahrzehnt'
%% Cell type:markdown id:7ebfb311 tags:
### Lowercase
%% Cell type:code id:d7414b09 tags:
``` python
text_proc = tp.fold_case(text_proc)
text_proc
```
%% Output
'der übersterblichkeit werden aufgrund der todesfallzahle der 7tagedurchschnitts und nicht ein jahresdurchschnitt berechnen so sein übrigens auch saisonal unterschied berücksichtigen und nein zürcher es geben kein statistisch relevant untersterblichkeit in sommer sondern nur einer der sich an unterer band der durchschnittlich sterblichkeit der letzter 5 jahr bewegen haben der grund dafür haben ich erwähnen mader was sie schreiben sein unsinn der durchschnittlich sterblichkeit werden anhand der tatsächlich todesfall über der letzter 5 jahr festhalten da sein aller massgebend parameter automatisch enthalt logisch dass dieser so berechnen durchschnitt sich jährlich aufgrund der lebenserwartung verändern keller ich haben nie etwas anderer behaupten man können sogar soweit gehen nachträglich der durchschnittlich lebenszeit zu berechnen der ein an dieser pandemie verstorben mensch nehmen werden sobald sich der todesfallzahle nach ein phase der übersterblichkeit durch ein unterdurchschnittlich sterblichkeit oder durch ein untersterblichkeit ausgleichen haben erhalten man dieser wert ich gehen von rund 6 monat aus da es aber nur ein durchschnittswert sein sagen er nicht über der lebenszeit aus der ein an corona verstorben person wirklich wegnommen werden bei der einer mögen es ein paar tag oder woche sein bei anderer aber mehrere jahr oder gar jahrzehnt'
%% Cell type:code id:d9c3cfee tags:
``` python
```
This diff is collapsed.
,logs
path_repo,.
path_model,bert-base-german-cased
input_data,data/tamedia_for_classifier_v4_preproc_test.csv
text_preprocessing,TRUE
newspaper,tagesanzeiger
lang,de
topic,
remove_duplicates,TRUE
min_num_words,3
val_score,
hsprob,"[0.0,1.0]"
pretrained_model,bert-base-german-cased
,logs
path_repo,.
path_model,deepset/bert-base-german-cased-hatespeech-GermEval18Coarse
input_data,data/tamedia_for_classifier_v4_preproc_test.csv
text_preprocessing,TRUE
newspaper,tagesanzeiger
lang,de
topic,
remove_duplicates,TRUE
min_num_words,3
val_score,
hsprob,"[0.0, 1.0]"
pretrained_model,deepset/bert-base-german-cased-hatespeech-GermEval18Coarse
import numpy as np
import tensorflow as tf
from tqdm import tqdm
from typing import List
def split_batches(text: np.ndarray, batch_size: int=100) -> List:
"""
Splits list with comments into batches
:param text: Array containing comments
:param batch_size: Number of comments per batch
"""
text_list = list(text)
text_batches=[text_list[idx:idx+batch_size] for idx in range(0, len(text_list), batch_size)]
return text_batches
def predict_batches(text: np.ndarray, model, tokenizer) -> np.ndarray:
"""
Makes prediction for all batches and combines all predictions
:param text: Array containing comments
:param model:
:param tokenizer:
"""
text_batches = split_batches(text)
y_pred_all = []
y_prob_all = []
for batch in tqdm(text_batches):
inputs = tokenizer(batch, return_tensors="tf", padding=True, truncation=True)
logits = model(**inputs).logits
y_pred_batch = tf.argmax(logits,axis=1)
y_prob_batch = tf.math.softmax(logits, axis=-1)[:,1]
y_pred_all.append(y_pred_batch)
y_prob_all.append(y_prob_batch)
y_pred_all = np.concatenate(y_pred_all)
y_prob_all = np.concatenate(y_prob_all)
return y_pred_all, y_prob_all
\ No newline at end of file
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from nltk.corpus import stopwords
import datetime
from joblib import dump, load
import os
from pathlib import Path
from typing import Union
from src.preprocessing_text import TextProcessor
def create_pipeline() -> Pipeline:
"""
Creates classification pipeline
"""
# define preprocessor
tp = TextProcessor()
# define vectorizer
stop_words_ge = stopwords.words("german")
vectorizer = TfidfVectorizer(
stop_words=stop_words_ge, ngram_range=(1, 4), max_features=3000
)
# define model
mnb = MultinomialNB(alpha=0.1)
# set pipeline
pipe = Pipeline([("processor", tp), ("vectorizer", vectorizer), ("mnb", mnb)])
return pipe
def create_path() -> Union[str, os.PathLike]:
"""
Creates path to store trained model
"""
if not os.path.exists("saved_models/MNB/"):
os.makedirs("saved_models/MNB/")
timestemp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
return Path("saved_models/MNB/" + timestemp + ".joblib")
def save_model(pipe: Pipeline, path):
"""
Saves trained model
:param pipe: Trained pipeline
"""
dump(pipe, path)
def load_model(path: Union[str, os.PathLike]) -> Pipeline:
"""
Loads trained model
:param path: Path to pipeline
"""
pipe = load(path)
return pipe
def gen_scores_dict(precision: float, recall: float, f1: float, accuracy: float):
"""
Generates dictionary containing most important scores
:param precision: Precision score
:param recall: Recall score
:param f1: F1 score
:param accuracy: Accuracy score
"""
results = dict()
results["precision"] = precision
results["recall"] = recall
results["f1"] = f1
results["accuracy"] = accuracy
return results
import pandas as pd
from datasets import Dataset, DatasetDict
from sklearn.model_selection import train_test_split
import evaluate
import numpy as np
from transformers import create_optimizer
def df2dict(df: pd.DataFrame, test_size: float = 0.2, split_data: bool = True):
"""
Converts Dataframe into Huggingface Dataset
:param df: input dataframe
:param test_size: size of test set
:param split_data: whether data should be split or not
"""
#df.sample(10000, replace=True)
if split_data:
train, test = train_test_split(df, test_size=test_size)
ds_train = Dataset.from_pandas(train)
ds_test = Dataset.from_pandas(test)
ds = DatasetDict()
ds["train"] = ds_train
ds["test"] = ds_test
else:
ds = Dataset.from_pandas(df)
#ds = DatasetDict()
#ds["test"] = ds_all
return ds
def compute_metrics(eval_pred):
"""
Computes metrics during training
"""
accuracy = evaluate.load("accuracy")
predictions, labels = eval_pred
predictions = np.argmax(predictions, axis=1)
return accuracy.compute(predictions=predictions, references=labels)
def prepare_training(dataset, batch_size: int = 16, num_epochs: int = 5):
"""
Prepares training and sets params
"""
batches_per_epoch = len(dataset["train"]) // batch_size
total_train_steps = int(batches_per_epoch * num_epochs)
optimizer, schedule = create_optimizer(
init_lr=2e-5, num_warmup_steps=0, num_train_steps=total_train_steps
)
return optimizer, schedule
......@@ -7,34 +7,28 @@ import pandas as pd
from pathlib import Path
from typing import Union
import time
class DataProcessor(object):
def __init__(self, path_data: Union[str, os.PathLike]):
"""
:param path_data: Path to input dataframe.
"""
self.path_data = path_data
def get_lang_detector(self, nlp, name):
"""
Gets language detector.
"""
return LanguageDetector(seed=42)
def detect_language(self, text: str, nlp_model):
def detect_language(self, text: str, nlp_model):
"""Detect language per comment.
:param text: Text of comment.
"""
doc = nlp_model(text)
language = doc._.language
return language['language']
return language["language"]
def init_nlp_model(self):
"""
......@@ -42,12 +36,10 @@ class DataProcessor(object):
"""
self.nlp_model = spacy.load("en_core_web_sm")
Language.factory("language_detector", func=self.get_lang_detector)
self.nlp_model.add_pipe('language_detector', last=True)
self.nlp_model.add_pipe("language_detector", last=True)
def add_language(self):
"""Add language column to dataframe and saves new file.
"""
"""Add language column to dataframe and saves new file."""
# Load data
df = pd.read_csv(self.path_data)
......@@ -55,14 +47,11 @@ class DataProcessor(object):
# Detect language
self.init_nlp_model()
lang = df_new.text.apply(self.detect_language, nlp_model = self.nlp_model)
df_new['language'] = lang
lang = df_new.text.apply(self.detect_language, nlp_model=self.nlp_model)
df_new["language"] = lang
# Save new file
f = self.path_data
fname_new = f"{os.path.splitext(os.path.basename(f))[0]}_preproc.csv"
path_new = Path(Path(self.path_data).parent).joinpath(fname_new)
df_new.to_csv(path_new)
import spacy
from spacy.language import Language
from spacy_language_detection import LanguageDetector
import os
import pandas as pd
from pathlib import Path
from typing import Union
class DataProcessor(object):
def __init__(self, path_data: Union[str, os.PathLike]):
"""
:param path_data: Path to input dataframe.
"""
self.path_data = path_data
def get_lang_detector(self, nlp, name):
"""
Gets language detector.
"""
return LanguageDetector(seed=42)
def detect_language(self, text: str, nlp_model):
"""Detect language per comment.
:param text: Text of comment.
"""
doc = nlp_model(text)
language = doc._.language
return language["language"]
def init_nlp_model(self):
"""
Initializes NLP model for langugae detection
"""
self.nlp_model = spacy.load("en_core_web_sm")
Language.factory("language_detector", func=self.get_lang_detector)
self.nlp_model.add_pipe("language_detector", last=True)
def add_language(self):
"""Add language column to dataframe and saves new file."""
# Load data
df = pd.read_csv(self.path_data)
df_new = df.copy()
# Detect language
self.init_nlp_model()
lang = df_new.text.apply(self.detect_language, nlp_model=self.nlp_model)
df_new["language"] = lang
# Save new file
f = self.path_data
fname_new = f"{os.path.splitext(os.path.basename(f))[0]}_preproc.csv"
path_new = Path(Path(self.path_data).parent).joinpath(fname_new)
df_new.to_csv(path_new)
import string
from collections import Counter
import emoji
import itertools
import numpy as np
import os
import pandas as pd
import re
from sklearn.base import BaseEstimator, TransformerMixin
import spacy
from tqdm import tqdm
from typing import Union, List
class TextLoader(object):
"""
Loads text data from specific path
"""
def __init__(self, path: Union[str, os.PathLike]):
self.path = path
def load_col(self, col_name: str) -> List:
"""
Loads specific column of dataframe to use less memory
:param col_name: Column name in dataframe
"""
col = pd.read_csv(self.path, usecols=[col_name]).values
col = list(itertools.chain.from_iterable(col))
return col
def load_text_csv(
self,
newspaper: str = None,
lang: str = None,
topic: str = None,
hsprob: list = None,
load_subset: bool = False,
remove_duplicates: bool = False,
min_num_words: int = None,
) -> pd.DataFrame:
"""
Loads dataframe and extracts text depending on newspaper and langugae
"""
if load_subset:
newspaper_col = self.load_col(col_name="originTenantId")
language_col = self.load_col(col_name="language")
text_col = self.load_col(col_name="text")
rejected_col = self.load_col(col_name="rejected")
df = pd.DataFrame(
{
"text": text_col,
"originTenantId": newspaper_col,
"language": language_col,
"rejected": rejected_col,
}
)
else:
df = pd.read_csv(self.path)
# df = df.sample(100000)
df = df.rename(columns={"rejected": "label"})
df_filter = self.filter_df(
df,
min_num_words,
remove_duplicates,
newspaper,
lang,
topic,
hsprob
)
return df_filter
def filter_df(
self,
df: pd.DataFrame,
min_num_words: int,
remove_duplicates: bool,
newspaper: str,
lang: str,
topic: str,
hsprob: list,
) -> pd.DataFrame:
"""
Filters data depending on given arguments.
:param df: Input dataframe
:param min_words: minimal amount of words per topic
:param remove_duplicates: Boolean flag whether or not to remove duplicates.
:param newspaper: Name of newspaper
:param lang: Language
:param topic: Topic
:param hsprob: List with min max values for hate speech probability
"""
if min_num_words:
df = self.filter_min_words(df)
if newspaper:
df = self.filter_newspaper(df, newspaper=newspaper)
if lang:
df = self.filter_language(df, lang=lang)
if topic:
df = self.filter_topic(df, topic=topic)
if hsprob:
df = self.filter_hsprob(df, thresh=hsprob)
if remove_duplicates:
df = self.remove_duplicate_comments(df)
#df = df[["text", "originTenantId", "label", "topic"]]
df = df[["text", "originTenantId", "label", "topic", "hsprob"]]
return df
def filter_newspaper(self, df: pd.DataFrame, newspaper: str):
"""
Filters out comments from specific newspaper.
:param df: Input dataframe
:param newspaper: Name of newspaper
"""
return df.loc[(df.originTenantId == newspaper)]
def filter_language(self, df: pd.DataFrame, lang: str):
"""
Filters out comments with specific language
:param df: Input dataframe
:param lang: Language
"""
return df.loc[(df.language == lang)]
def filter_topic(self, df: pd.DataFrame, topic: str):
"""
Filters out comments with specific topic
:param df: Input dataframe
:param lang: Language
"""
return df.loc[(df.topic == topic)]
def filter_min_words(self, df: pd.DataFrame, min_words: int = 3):
"""Filters out comments with less than min words
:param df: Input dataframe
:param min_words: minimal amount of words per topic
"""
return df[np.array([len((re.findall(r"\w+", t))) for t in df.text]) > min_words]
def filter_hsprob(self, df: pd.DataFrame, thresh: list):
"""
Filters out comments from specific newspaper.
:param df: Input dataframe
:param newspaper: Name of newspaper
"""
return df.loc[(df.hsprob > thresh[0])&(df.hsprob < thresh[1])]
def get_comments_per_topic(self, df, num_topic: int = 10) -> dict:
"""
Returns dictionary containing df's per topic for most common topics.
:param num_topic: Number f most common topics
"""
# df = pd.read_csv(self.path)
# df = df.rename(columns={"rejected": "label"})
topics = Counter(df["topic"]).most_common(num_topic)
return topics
def find_duplicate_comments(self, df: pd.DataFrame) -> np.ndarray:
""" "
Find duplicate comments in dataframe
:param df: Input dataframe
"""
c_comm = Counter(df.text.values)
duplicate_comments = np.array(list(c_comm.keys()))[
np.where(np.array(list(c_comm.values())) > 1)
]
# indices_repetitions = np.concatenate(
# [
# np.where(df.text == d)[0][
# np.argsort(df.createdAt.iloc[np.where(df.text == d)[0]].values)[:-1]
# ]
# for d in tqdm(duplicate_comments)
# ]
# )
if len(duplicate_comments) > 0:
indices_repetitions = np.concatenate(
[np.where(df.text == d)[0] for d in tqdm(duplicate_comments)]
)
else:
indices_repetitions = np.array([])
return indices_repetitions
def remove_duplicate_comments(self, df: pd.DataFrame) -> pd.DataFrame:
"""Removes duplicates from dataframe
:param df: Input dataframe
"""
print("Find and remove duplicates")
indices = self.find_duplicate_comments(df)
if len(indices) > 0:
return df.drop(df.index[indices])
else:
return df
class TextProcessor(BaseEstimator, TransformerMixin):
def __init__(self, lowercase=True): # params setting single steps to True or False
self.punctuation = list(string.punctuation)
self.punctuation.remove("@")
self.punctuation = "".join(self.punctuation)
self.lowercase = lowercase
def fit(self, X, y=None):
"""
Fits preprocessing to data
"""
return self
def transform(self, X):
"""
Transforms data after fitting
"""
text_proc = X.apply(self.preprocess)
return text_proc
def preprocess(self, text) -> str:
"""
Applies preprocessing to text
:param text: Input text
:param nlp: Loaded nlp model
"""
text_proc = self.transcripe_emojis(text)
text_proc = self.remove_spaces(text)
text_proc = self.remove_punctuation(text_proc)
text_proc = self.remove_mentions(text_proc)
if self.lowercase:
text_proc = self.fold_case(text_proc)
return text_proc
def remove_spaces(self, text: str) -> str:
"""
Removes extra white spaces and linebreaks
:param text: Input text
"""
return " ".join(text.split())
def remove_punctuation(self, text: str) -> str:
"""
Removes puntuation from text except @
:param text: Input text
"""
return text.translate(str.maketrans("", "", self.punctuation))
def fold_case(self, text: str) -> str:
"""
Transforms text to lowercase
:param text: Input text
"""
return text.casefold()
def remove_mentions(self, text: str) -> str:
"""
Removes @-mentions from text
:param text: Input text
"""
return re.sub("@([a-zA-Z0-9]{1,15})", "", text)
def lemmatize_text(self, text: str) -> str:
"""
Lemmatizes text
:param text: Input text
"""
doc = self.nlp(text)
return " ".join([word.lemma_ for word in doc])
def transcripe_emojis(self, text: str) -> str:
"""
Transcripes emojis into words
"""
return emoji.demojize(text, language="de", delimiters=("", "")).replace(
"_", " "
)
from typing import Tuple, Union, Optional
import os
import pandas as pd
import numpy as np
def save_logs(
path_repo: Union[str, os.PathLike],
path_model: Union[str, os.PathLike],
input_data: Union[str, os.PathLike],
text_preprocessing: bool,
newspaper: str,
lang: str,
topic: str,
hsprob: list,
remove_duplicates: bool,
min_num_words: int,
model_name: str,
val_score: Optional[Union[str, os.PathLike]] = None,
pretrained_model: Optional[str] = None,
):
"""
Saves training logs which can be used during evaluation
:param path_repo: Path to repository
:param path_model: Path to trained model
:param input_data: Path to used train data
:param text_preprocessing: Boolean flag whether preprocessing was used or not
:param newspaper: Name of newspaper
:param lang: Selected language
:param topic: Selected topic
:param hsprob: List with min max values for hate speech probability
:param remove_duplicates: Boolean flag whether duplicates should be removed
:param min_num_words: Minimum number of words per comment
:param model_name: Name of model
:param pretrained_model: Name of pretrained BERT model
"""
logs = dict()
logs["path_repo"] = path_repo
logs["path_model"] = path_model
logs["input_data"] = input_data
logs["text_preprocessing"] = text_preprocessing
logs["newspaper"] = newspaper
logs["lang"] = lang
logs["topic"] = topic
logs["hsprob"] = hsprob
logs["remove_duplicates"] = remove_duplicates
logs["min_num_words"] = min_num_words
logs["val_score"] = val_score
logs["pretrained_model"] = pretrained_model
path_logs = (path_repo).joinpath("saved_models/" + model_name + "_logs/")
if not os.path.exists(path_logs):
os.makedirs(path_logs)
df_logs = pd.DataFrame.from_dict(logs, orient="index", columns=["logs"])
df_logs.to_csv(path_logs.joinpath(path_model.stem).with_suffix(".csv"))
def load_logs(
train_logs: Union[str, os.PathLike]
) -> Tuple[
Union[str, os.PathLike],
Union[str, os.PathLike],
str,
bool,
str,
str,
str,
list,
bool,
int,
]:
"""
Loads training logs
:param train_logs: Path to csv-file containing logs
"""
df = pd.read_csv(train_logs, index_col="Unnamed: 0")
path_repo = df.loc["path_repo"].values[0]
path_model = df.loc["path_model"].values[0]
input_data = df.loc["input_data"].values[0].replace("train", "test")
text_preprocessing = df.loc["text_preprocessing"].values[0]
newspaper = df.loc["newspaper"].values[0]
lang = df.loc["lang"].values[0]
topic = df.loc["topic"].values[0]
hsprob = eval(df.loc["hsprob"].values[0])
remove_duplicates = df.loc["remove_duplicates"].values[0]
min_num_words = df.loc["min_num_words"].values[0]
pretrained_model = df.loc["pretrained_model"].values[0]
# check whether topic is str or NaN
if topic != topic:
topic = None
if pretrained_model:
return (
path_repo,
path_model,
input_data,
text_preprocessing,
newspaper,
lang,
topic,
hsprob,
remove_duplicates,
min_num_words,
pretrained_model,
)
else:
return (
path_repo,
path_model,
input_data,
text_preprocessing,
newspaper,
lang,
topic,
hsprob,
remove_duplicates,
min_num_words,
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment