mirror of
https://github.com/Steffo99/unimore-bda-6.git
synced 2024-11-24 08:44:19 +00:00
Write chapter 1
This commit is contained in:
parent
b777236735
commit
ff4fff3052
1 changed files with 129 additions and 46 deletions
175
README.md
175
README.md
|
@ -167,43 +167,11 @@ L'esecuzione del modulo `unimore_bda_6.config`, senza variabili d'ambiente defin
|
|||
```console
|
||||
$ python -m unimore_bda_6.config
|
||||
===== Configuration =====
|
||||
|
||||
MONGO_HOST = '127.0.0.1'
|
||||
The hostname of the MongoDB database to connect to.
|
||||
Defaults to `"127.0.0.1"`.
|
||||
|
||||
MONGO_PORT = 27017
|
||||
The port of the MongoDB database to connect to.
|
||||
Defaults to `27017`.
|
||||
|
||||
WORKING_SET_SIZE = 1000000
|
||||
The number of reviews to consider from the database.
|
||||
Set this to a low number to prevent slowness due to the dataset's huge size.
|
||||
|
||||
...
|
||||
TRAINING_SET_SIZE = 4000
|
||||
The number of reviews from each category to fetch for the training dataset.
|
||||
Defaults to `4000`.
|
||||
|
||||
VALIDATION_SET_SIZE = 400
|
||||
The number of reviews from each category to fetch for the training dataset.
|
||||
Defaults to `400`.
|
||||
|
||||
EVALUATION_SET_SIZE = 1000
|
||||
The number of reviews from each category to fetch for the evaluation dataset.
|
||||
Defaults to `1000`.
|
||||
|
||||
TENSORFLOW_MAX_FEATURES = 300000
|
||||
The maximum number of features to use in Tensorflow models.
|
||||
Defaults to `300000`.
|
||||
|
||||
TENSORFLOW_EMBEDDING_SIZE = 12
|
||||
The size of the embeddings tensor to use in Tensorflow models.
|
||||
Defaults to `12`.
|
||||
|
||||
TENSORFLOW_EPOCHS = 3
|
||||
The number of epochs to train Tensorflow models for.
|
||||
Defaults to `3`.
|
||||
|
||||
...
|
||||
===== End =====
|
||||
```
|
||||
|
||||
|
@ -380,7 +348,7 @@ def sample_reviews_varied(collection: pymongo.collection.Collection, amount: int
|
|||
return cursor
|
||||
```
|
||||
|
||||
### Tokenizzatore astratto - `.tokenizer.base`
|
||||
### Tokenizzatore astratto - `.tokenizer.base` e `.tokenizer.plain`
|
||||
|
||||
Si è realizzata una classe astratta che rappresentasse un tokenizer qualcunque, in modo da avere la stessa interfaccia a livello di codice indipendentemente dal package di tokenizzazione utilizzato:
|
||||
|
||||
|
@ -398,6 +366,21 @@ class BaseTokenizer(metaclass=abc.ABCMeta):
|
|||
return TokenizedReview(rating=review.rating, tokens=tokens)
|
||||
```
|
||||
|
||||
Si sono poi realizzate due classi di esempio che implementassero i metodi astratti della precedente: `PlainTokenizer` e `LowerTokenizer`, che semplicemente separano il testo in tokens attraverso la funzione builtin [`str.split`] di Python, rispettivamente mantenendo e rimuovendo la capitalizzazione del testo.
|
||||
|
||||
```python
|
||||
class PlainTokenizer(BaseTokenizer):
|
||||
def tokenize(self, text: str) -> t.Iterator[str]:
|
||||
tokens = text.split()
|
||||
return tokens
|
||||
|
||||
class LowercaseTokenizer(BaseTokenizer):
|
||||
def tokenize(self, text: str) -> t.Iterator[str]:
|
||||
text = text.lower()
|
||||
tokens = text.split()
|
||||
return tokens
|
||||
```
|
||||
|
||||
### Analizzatore astratto - `.analysis.base`
|
||||
|
||||
Allo stesso modo, si è realizzato una classe astratta per tutti i modelli di Sentiment Analysis:
|
||||
|
@ -415,31 +398,130 @@ class BaseSentimentAnalyzer(metaclass=abc.ABCMeta):
|
|||
raise NotImplementedError()
|
||||
|
||||
def evaluate(self, evaluation_dataset_func: CachedDatasetFunc) -> EvaluationResults:
|
||||
"Perform a model evaluation by calling repeatedly `.use` on every text of the test dataset and by comparing its resulting category with the expected category."
|
||||
... # Descritta successivamente
|
||||
"""
|
||||
Perform a model evaluation by calling repeatedly `.use` on every text of the test dataset and by comparing its resulting category with the expected category.
|
||||
"""
|
||||
er = EvaluationResults()
|
||||
for review in evaluation_dataset_func():
|
||||
er.add(expected=review.rating, predicted=self.use(review.text))
|
||||
return er
|
||||
```
|
||||
|
||||
Si può notare che il metodo `evaluate` inserisce i risultati di ciascuna predizione effettuata in un oggetto di tipo `EvaluationResults`.
|
||||
|
||||
Esso tiene traccia della matrice di confusione per la valutazione, e da essa è in grado di ricavarne i valori di richiamo e precisione per ciascuna categoria implementata dal modello; inoltre, calcola l'errore medio assoluto e quadrato tra previsioni e valori effettivi:
|
||||
|
||||
```python
|
||||
class EvaluationResults:
|
||||
def __init__(self):
|
||||
self.confusion_matrix: dict[float, dict[float, int]] = collections.defaultdict(lambda: collections.defaultdict(lambda: 0))
|
||||
"Confusion matrix of the evaluation. First key is the expected rating, second key is the output label."
|
||||
|
||||
self.absolute_error_total: float = 0.0
|
||||
"Sum of the absolute errors committed in the evaluation."
|
||||
|
||||
self.squared_error_total: float = 0.0
|
||||
"Sum of the squared errors committed in the evaluation."
|
||||
|
||||
def keys(self) -> set[float]:
|
||||
"Return all processed categories."
|
||||
keys: set[float] = set()
|
||||
for expected, value in self.confusion_matrix.items():
|
||||
keys.add(expected)
|
||||
for predicted, _ in value.items():
|
||||
keys.add(predicted)
|
||||
return keys
|
||||
|
||||
def evaluated_count(self) -> int:
|
||||
"Return the total number of evaluated reviews."
|
||||
total: int = 0
|
||||
for row in self.confusion_matrix.values():
|
||||
for el in row.values():
|
||||
total += el
|
||||
return total
|
||||
|
||||
def perfect_count(self) -> int:
|
||||
"""
|
||||
Return the total number of perfect reviews.
|
||||
"""
|
||||
total: int = 0
|
||||
for key in self.keys():
|
||||
total += self.confusion_matrix[key][key]
|
||||
return total
|
||||
|
||||
def recall_count(self, rating: float) -> int:
|
||||
"Return the number of reviews processed with the given rating."
|
||||
total: int = 0
|
||||
for el in self.confusion_matrix[rating].values():
|
||||
total += el
|
||||
return total
|
||||
|
||||
def precision_count(self, rating: float) -> int:
|
||||
"Return the number of reviews for which the model returned the given rating."
|
||||
total: int = 0
|
||||
for col in self.confusion_matrix.values():
|
||||
total += col[rating]
|
||||
return total
|
||||
|
||||
def recall(self, rating: float) -> float:
|
||||
"Return the recall for a given rating."
|
||||
try:
|
||||
return self.confusion_matrix[rating][rating] / self.recall_count(rating)
|
||||
except ZeroDivisionError:
|
||||
return float("inf")
|
||||
|
||||
def precision(self, rating: float) -> float:
|
||||
"Return the precision for a given rating."
|
||||
try:
|
||||
return self.confusion_matrix[rating][rating] / self.precision_count(rating)
|
||||
except ZeroDivisionError:
|
||||
return float("inf")
|
||||
|
||||
def add(self, expected: float, predicted: float) -> None:
|
||||
"Count a new prediction."
|
||||
self.confusion_matrix[expected][predicted] += 1
|
||||
```
|
||||
|
||||
Si è inoltre realizzata un'implementazione di esempio della classe astratta, `ThreeCheat`, che "prevede" che tutte le recensioni siano di 3.0*, in modo da verificare facilmente la correttezza della precedente classe:
|
||||
|
||||
```python
|
||||
class ThreeCheat(BaseSentimentAnalyzer):
|
||||
def train(self, training_dataset_func: CachedDatasetFunc, validation_dataset_func: CachedDatasetFunc) -> None:
|
||||
pass
|
||||
|
||||
def use(self, text: str) -> float:
|
||||
return 3.0
|
||||
```
|
||||
|
||||
### Logging - `.log`
|
||||
|
||||
Si è configurato il modulo [`logging`] di Python affinchè esso scrivesse sia su console sia sul file `./data/logs/last_run.tsv` le operazioni eseguite dal programma.
|
||||
Si è configurato il modulo [`logging`] di Python affinchè esso scrivesse report sull'esecuzione:
|
||||
|
||||
Il livello di logging viene regolato attraverso la costante magica [`__debug__`] di Python, il cui valore cambia in base alla presenza dell'opzione di ottimizzazione [`-O`] dell'interprete Python.
|
||||
- nello stream stderr della console, in formato colorato e user-friendly
|
||||
- sul file `./data/logs/last_run.tsv`, in formato machine-readable
|
||||
|
||||
Il livello di logging viene regolato attraverso la costante magica [`__debug__`] di Python, il cui valore cambia in base alla presenza dell'opzione di ottimizzazione [`-O`] dell'interprete Python; senza quest'ultima, i log stampati su console saranno molto più dettagliati.
|
||||
|
||||
### Tester - `.__main__`
|
||||
|
||||
Infine, si è preparato un tester che effettuasse una valutazione di efficacia per ogni combinazione di funzione di campionamento, tokenizzatore, e modello di Sentiment Analysis:
|
||||
Infine, si è preparato un tester che effettuasse ripetute valutazioni di efficacia per ogni combinazione di funzione di campionamento, tokenizzatore, e modello di Sentiment Analysis, con una struttura simile alla seguente:
|
||||
|
||||
```python
|
||||
|
||||
# Pseudo-codice non corrispondente al main finale
|
||||
if __name__ == "__main__":
|
||||
...
|
||||
for sample_func in [...]:
|
||||
for SentimentAnalyzer in [...]:
|
||||
for Tokenizer in [...]:
|
||||
...
|
||||
for sample_func in [sample_reviews_polar, sample_reviews_varied]:
|
||||
for SentimentAnalyzer in [ThreeCheat, ...]:
|
||||
for Tokenizer in [PlainTokenizer, LowercaseTokenizer, ...]:
|
||||
for run in range(TARGET_RUNS):
|
||||
model = SentimentAnalyzer(tokenizer=Tokenizer())
|
||||
model.train(training_set=sample_func(amount=TRAINING_SET_SIZE), validation_set_func=sample_func(amount=VALIDATION_SET_SIZE))
|
||||
model.evaluate(evaluation_set_func=sample_func(amount=EVALUATION_SET_SIZE))
|
||||
```
|
||||
|
||||
Le valutazioni di efficacia vengono effettuate fino al raggiungimento di `TARGET_RUNS` addestramenti e valutazioni riuscite, o fino al raggiungimento di `MAXIMUM_RUNS` valutazioni totali (come descritto più avanti, l'addestramento di alcuni modelli potrebbe fallire e dover essere ripetuto).
|
||||
|
||||
## Ri-implementazione dell'esercizio con NLTK - `.analysis.nltk_sentiment`
|
||||
|
||||
### Wrapping del tokenizzatore di NLTK - `.tokenizer.nltk_word_tokenize`
|
||||
### Ri-creazione del tokenizer di Christopher Potts - `.tokenizer.potts`
|
||||
### Problemi di memoria
|
||||
|
@ -467,3 +549,4 @@ if __name__ == "__main__":
|
|||
[`$rand`]: https://www.mongodb.com/docs/v6.0/reference/operator/aggregation/rand/
|
||||
[`__debug__`]: https://docs.python.org/3/library/constants.html#debug__
|
||||
[`-O`]: https://docs.python.org/3/using/cmdline.html#cmdoption-O
|
||||
[`str.split`]: https://docs.python.org/3/library/stdtypes.html?highlight=str%20split#str.split
|
||||
|
|
Loading…
Reference in a new issue