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
|
```console
|
||||||
$ python -m unimore_bda_6.config
|
$ python -m unimore_bda_6.config
|
||||||
===== Configuration =====
|
===== 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
|
TRAINING_SET_SIZE = 4000
|
||||||
The number of reviews from each category to fetch for the training dataset.
|
The number of reviews from each category to fetch for the training dataset.
|
||||||
Defaults to `4000`.
|
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 =====
|
===== End =====
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -380,7 +348,7 @@ def sample_reviews_varied(collection: pymongo.collection.Collection, amount: int
|
||||||
return cursor
|
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:
|
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)
|
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`
|
### Analizzatore astratto - `.analysis.base`
|
||||||
|
|
||||||
Allo stesso modo, si è realizzato una classe astratta per tutti i modelli di Sentiment Analysis:
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def evaluate(self, evaluation_dataset_func: CachedDatasetFunc) -> EvaluationResults:
|
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`
|
### 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__`
|
### 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
|
```python
|
||||||
|
# Pseudo-codice non corrispondente al main finale
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
...
|
for sample_func in [sample_reviews_polar, sample_reviews_varied]:
|
||||||
for sample_func in [...]:
|
for SentimentAnalyzer in [ThreeCheat, ...]:
|
||||||
for SentimentAnalyzer in [...]:
|
for Tokenizer in [PlainTokenizer, LowercaseTokenizer, ...]:
|
||||||
for Tokenizer in [...]:
|
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`
|
## Ri-implementazione dell'esercizio con NLTK - `.analysis.nltk_sentiment`
|
||||||
|
|
||||||
### Wrapping del tokenizzatore di NLTK - `.tokenizer.nltk_word_tokenize`
|
### Wrapping del tokenizzatore di NLTK - `.tokenizer.nltk_word_tokenize`
|
||||||
### Ri-creazione del tokenizer di Christopher Potts - `.tokenizer.potts`
|
### Ri-creazione del tokenizer di Christopher Potts - `.tokenizer.potts`
|
||||||
### Problemi di memoria
|
### Problemi di memoria
|
||||||
|
@ -467,3 +549,4 @@ if __name__ == "__main__":
|
||||||
[`$rand`]: https://www.mongodb.com/docs/v6.0/reference/operator/aggregation/rand/
|
[`$rand`]: https://www.mongodb.com/docs/v6.0/reference/operator/aggregation/rand/
|
||||||
[`__debug__`]: https://docs.python.org/3/library/constants.html#debug__
|
[`__debug__`]: https://docs.python.org/3/library/constants.html#debug__
|
||||||
[`-O`]: https://docs.python.org/3/using/cmdline.html#cmdoption-O
|
[`-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