├── .gitignore ├── README.md ├── cs224n └── README.md ├── fastai ├── README.md └── bird_or_plane.ipynb ├── kaggle ├── README.md ├── ensemble │ ├── README.md │ └── utils.py ├── metrics │ ├── README.md │ └── metrics.py ├── tuning │ └── optuna_classification.py ├── validation │ └── README.md └── viz │ └── eda.py ├── llms ├── README.md ├── fine_tuning │ └── REAMDE.md └── gpt_numpy │ └── gpt.py ├── math_for_ml ├── README.md └── linalg.ipynb ├── minitorch └── README.md ├── ml_from_scratch ├── README.md └── supervised │ ├── lineargression.py │ └── xgboost.py ├── nlp ├── BERT │ └── README.md ├── classic │ ├── LDA.py │ ├── LSA.py │ ├── PCA.py │ ├── SVD.py │ ├── TFIDF.py │ ├── TSNE.py │ └── UMAP.py └── embeddings │ ├── README.md │ ├── berttokenize.ipynb │ └── tfidf.ipynb ├── nn_zero_to_hero ├── README.md └── micrograd │ ├── derivatives.ipynb │ ├── exercises.ipynb │ ├── micrograd.ipynb │ ├── micrograd.py │ └── viz.py ├── roadmap.png └── tensor_puzzles └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI from scratch 2 | 3 | documenting what I learn 4 | 5 | from this [roadmap](https://medium.com/bitgrit-data-science-publication/a-roadmap-to-learn-ai-in-2024-cc30c6aa6e16) I made 6 | 7 | ![roadmap](roadmap.png) 8 | -------------------------------------------------------------------------------- /cs224n/README.md: -------------------------------------------------------------------------------- 1 | # CS224N: Natural Language Processing with Deep Learning 2 | 3 | [Course page](https://web.stanford.edu/class/archive/cs/cs224n/cs224n.1234/) 4 | 5 | Papers 6 | 7 | - [Efficient Estimation of Word Representations in Vector Space (original word2vec paper)](https://arxiv.org/pdf/1301.3781.pdf) 8 | - [Distributed Representations of Words and Phrases and their Compositionality (negative sampling paper)](https://proceedings.neurips.cc/paper_files/paper/2013/file/9aa42b31882ec039965f3c4923ce901b-Paper.pdf) 9 | -------------------------------------------------------------------------------- /fastai/README.md: -------------------------------------------------------------------------------- 1 | # Fast AI 2 | 3 | - [Practical Deep Learning for Coders - Practical Deep Learning](https://course.fast.ai/) 4 | - [Lectures](https://www.youtube.com/playlist?list=PLfYUBJiXbdtSvpQjSnJJ_PmDQB_VyT5iU) 5 | - [Practical Deep Learning for Coders - Part 2 overview](https://course.fast.ai/Lessons/part2.html) 6 | - [Walk with fastai - Introduction](https://walkwithfastai.com/revisited/) 7 | -------------------------------------------------------------------------------- /kaggle/README.md: -------------------------------------------------------------------------------- 1 | # Kaggle Notes 2 | 3 | Sources 4 | 5 | - [Kaggle Solutions](https://farid.one/kaggle-solutions/) 6 | - [The Kaggle Book](https://learning.oreilly.com/library/view/the-kaggle-book/9781801817479/) 7 | - [Winning Toolkit for Competitive ML](https://mlcontests.com/winning-toolkit/) 8 | - [[1811.12808] Model Evaluation, Model Selection, and Algorithm Selection in Machine Learning](https://arxiv.org/abs/1811.12808) 9 | 10 | ## Metrics 11 | 12 | - in real world, your model will be evaluated against multiple metrics, and some of the metrics won't even be related to how your predictions perform against the ground truth you are using for testing 13 | - ex: domain of knowledge you're working in, scope of project, number of features considered for model, overall memory usage, requirements for special hardware, latency of prediction process, complexity of prediction model, and many other aspects may count more that just predictive performance. 14 | - it is dominated by business and tech infrastructure concerns 15 | 16 | ### evaluation metrics and objective functions 17 | 18 | - objective functions: serves model during training, involved in process of error minimization (or score maximization) 19 | - evaluation metric: serves your model after it has been trained by providing a score 20 | - it cannot influence how model fits data, but it helps you select the best configurations within a model, and the best model among competing ones 21 | - analysis of evaluation metric should be your first act in competitions 22 | 23 | ### objective function, cost function and loss function 24 | 25 | - loss function: single data point (penalty = |pred - ground truth|) 26 | - cost function: on whole dataset (or a batch), sum or average over loss penalties. Can comprise further constraints, i.e. L1 or L2 penalties 27 | - objective function: related to scope of optimization during ML training, comprise cost function, but not limited to them. Can also take into account goals not related to target, ex: requiring sparse coefficients of estimated model or minimization of coefficients' values, i.e. L1 and L2 regularization. 28 | 29 | Loss & cost imply optimization based on minimization, objective function is neutral, it can be a maximization or minimization activity. 30 | 31 | Scoring function (higher score = better prediction, maximization process), error functions (smaller error = better prediction, minimization process) 32 | 33 | ### basic tasks 34 | 35 | - regression 36 | - a model that can predict a real number (often positive, sometimes negative) 37 | - evaluations: diff = dist(pred, true), square(diff) it to punish large errors / log(diff) to penalize predictions of the wrong scale 38 | - classification 39 | - binary: 0 or 1 / probabilities of class (in medical fields) 40 | - churn/not churn, cancer/not cancer (probability is important here) 41 | - !watch out for imbalance!, use eval metrics that take imbalance into account 42 | - multi-class: >2 classes 43 | - ex: leaf classification 44 | - ensure performance across class is comparable (model can underperform with respect to certain classes) 45 | - multi-label: predictions are not exclusive and you can predict multiple class ownership 46 | - ex: classify news articles with relevant topics 47 | - require further evaluations to control whether model is predicting correct classes, as well as the correct number and mix of classes 48 | - Ordinal 49 | - halfway between regression and classification 50 | - ex: magnitude of earthquake, customer preferences 51 | - as multiclass 52 | - get prediction as integer value, but not take into account the order of class 53 | - problem: probabilities distributed across entire range of possible values, depicting multi-model and often asymmetric distribution (you should expect Gaussian around max probability class) 54 | - as regression 55 | - output as a float number, and results include full range of values between integers of ordinal distribution, and possible outside of it 56 | - one solution is to crop the output values, cast into int by unit rounding, but may lead to inaccuracies, requiring more sophisticated post-processing 57 | 58 | ### common metrics 59 | 60 | Top Kaggle metrics 61 | 62 | - **AUC**: measures if your model's predicted probabilities tend to predict positive cases with high probabilities 63 | - **log loss**: how far your predicted probabilities are from the ground truth (as you optimize for log loss, you optimize for AUC metric) 64 | - **MAC@{k}**: common in recsys and search engines, used for information retrieval evaluations 65 | - ex: whale identification and having 5 possible guesses 66 | - ex2: quickdraw doodle recognition (guess the content of a drawn sketch in 3 attempts, score not just if you can guess correctly, but if your correct guess is among a certain number, the "K" in the name of the function, of other incorrect predictions) 67 | - RMSLE (root mean squared logarithmic error): 68 | - quadratic weigthed kappa: for ordinal scale problems (problems that involve guessing a progressive integer number) 69 | 70 | Metrics for regression 71 | 72 | - MSE 73 | - mean of sum of squared errors (SSE) 74 | - cautions 75 | - sensitive to outliers 76 | - imbalanced errors 77 | - not robust to non-gaussian errors 78 | - lack of sensitivity to small errors 79 | - R squared (coefficient of determination) 80 | -------------------------------------------------------------------------------- /kaggle/ensemble/README.md: -------------------------------------------------------------------------------- 1 | # Ensemble Learning 2 | 3 | ## What 4 | 5 | A technique that blends predictions from a diverse set of models. 6 | 7 | See [Wisdom of Crowds](https://arxiv.org/abs/1605.04074) 8 | 9 | ## Why they work 10 | 11 | - performance: ensemble reduce variance component of prediction error by adding bias 12 | - robustness: ensemble reduces reliance on any single model's prediction, making it better at handling noisy data. 13 | 14 | ## Diversity 15 | 16 | ensemble learning is based on concept of combining multiple weak learners, weak because individual models don't need to be very accurate, as long as they're better than a random model, combining them is beneficial. 17 | 18 | Diversity is a concept referring to the idea that individual models have to be **as different from each other as possible**. This is because different models are likely to make different types of errors. By combining predictions of a diverse set, we can reduce overall error of the ensemble. 19 | 20 | In order for accuracy of ensemble to be better than individual models, there needs to be diversity. 21 | 22 | ### how 23 | 24 | - train each model on different subset of data 25 | - bagging (w replacement) 26 | - pasting (w/out replacement) 27 | - ex: 28 | - random forest: achieves diversity using random number of features at each split 29 | - extremely randomized trees: a random split to lower correlation between trees 30 | - train each model with a different set of features 31 | - train each model using a different type of algorithm 32 | - voting and stacking meta-models 33 | 34 | ### good and bad 35 | 36 | good diversity: ensemble is already correct, low disagreement between classifier, several votes wasted 37 | 38 | bad diversity: ensemble is incorrect, any disagreement represent a wasted vote, as individual classifier did not contribute to correct decision. 39 | 40 | increase good diversity (where disagreements among classifiers contribute to correct decisions) and reduce bad diversity (where disagreements does not contribute to correct decisions). 41 | 42 | ### metrics 43 | 44 | - let f_1, f_2, f_3 be predictions of diff models in ensemble 45 | 46 | two types of measures 47 | 48 | - pairwise: computed for every f_i, f_j pair, represented by NxN matrix 49 | - global: computed on whole matrix of predictions, represented by a single value 50 | 51 | Measures 52 | 53 | - pearson correlation coefficient 54 | - disagreement 55 | - Yule's Q 56 | - entropy 57 | 58 | References 59 | 60 | - [Measures of Diversity in Classifier Ensembles and Their Relationship with the Ensemble Accuracy | Machine Learning](https://link.springer.com/article/10.1023/A:1022859003006) 61 | - [Understanding the Importance of Diversity in Ensemble Learning](https://towardsdatascience.com/understanding-the-importance-of-diversity-in-ensemble-learning-34fb58fd2ed0#:~:text=Ensemble%20learning%20is%20a%20powerful,of%20the%20ensemble%20also%20increased.) 62 | 63 | ## Methods 64 | 65 | 1. blending : averaging, weighted averaging, and rank averaging 66 | - average the outputs 67 | - weights given to model can be assigned explicitly or implicitly by [rank averaging](https://towardsdatascience.com/ensemble-averaging-improve-machine-learning-performance-by-voting-246106c753ee), which ranks models by performance and gives more accurate models greater weights 68 | - involves using optuna or hyperopt to find optimal blend by taking into account cross validation metrics 69 | 2. Voting : for classification 70 | - ex: majority voting: class that most models predict is chosen 71 | 3. classical trio: bagging, boosting and stacking 72 | - bagging: train multiple models on different subsets of training data and average prediction 73 | - boosting: sequentially training models, each new model focuses on errors made by predecessors. 74 | - Stacking: feed predictions of various models as input to higher-level model. 75 | 76 | ## Reality 77 | 78 | Building robust and highly accurate models are only half the solution. an equally challenging part is explainability and fairness. 79 | 80 | see: [On Transparency of Machine Learning Models: A Position Paper](https://crcs.seas.harvard.edu/sites/projects.iq.harvard.edu/files/crcs/files/ai4sg_2020_paper_62.pdf) and [[2105.06791] Agree to Disagree: When Deep Learning Models With Identical Architectures Produce Distinct Explanations](https://arxiv.org/abs/2105.06791) 81 | 82 | ## References 83 | 84 | - [Unreasonably Effective Ensemble Learning](https://www.kaggle.com/code/yeemeitsang/unreasonably-effective-ensemble-learning/notebook#Conclusion) 85 | 86 | ## implementations 87 | 88 | - [EnsemblesTutorial/ensemble_functions.py at main · PadraigC/EnsemblesTutorial](https://github.com/PadraigC/EnsemblesTutorial/blob/main/ensemble_functions.py) 89 | -------------------------------------------------------------------------------- /kaggle/ensemble/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def coefficients(preds): 5 | A = np.asarray(preds[:, 0], dtype=bool) 6 | B = np.asarray(preds[:, 1], dtype=bool) 7 | 8 | a = np.sum(A * B) # A right, B right 9 | b = np.sum(~A * B) # A wrong, B right 10 | c = np.sum(A * ~B) # A right, B wrong 11 | d = np.sum(~A * ~B) # A wrong, B wrong 12 | 13 | return a, b, c, d 14 | 15 | 16 | def disagreement(preds, i, j): 17 | L = preds.shape[1] 18 | a, b, c, d = coefficients(preds[:, [i, j]]) 19 | return float(b + c) / (a + b + c + d) 20 | 21 | 22 | def paired_q(preds, i, j): 23 | L = preds.shape[1] 24 | # div = np.zeros((L * (L - 1)) // 2) 25 | a, b, c, d = coefficients(preds[:, [i, j]]) 26 | return float(a * d - b * c) / ((a * d + b * c) + 10e-24) 27 | 28 | 29 | def entropy(preds): 30 | L = preds.shape[1] 31 | tmp = np.sum(preds, axis=1) 32 | tmp = np.minimum(tmp, L - tmp) 33 | ent = np.mean((1.0 / (L - np.ceil(0.5 * L))) * tmp) 34 | return ent 35 | -------------------------------------------------------------------------------- /kaggle/metrics/README.md: -------------------------------------------------------------------------------- 1 | # metrics 2 | 3 | [ajitsingh98/Evaluation-Metrics-In-Machine-Learning-Problems-Python: evaluation metrics implementation in Python from scratch](https://github.com/ajitsingh98/Evaluation-Metrics-In-Machine-Learning-Problems-Python) 4 | -------------------------------------------------------------------------------- /kaggle/metrics/metrics.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numba import jit 3 | 4 | 5 | def mse(y_pred: np.array, y_true: np.array) -> float: 6 | squared_diff = (y_pred - y_true) ** 2 7 | mse = np.mean(squared_diff) 8 | return mse 9 | 10 | 11 | def rmse(y_true, y_pred): 12 | return np.sqrt(mse) 13 | 14 | 15 | def r2(y_true, y_pred): 16 | pass 17 | 18 | 19 | @jit 20 | def fast_auc(y_true, y_prob): 21 | """ 22 | fast roc_auc computation: https://www.kaggle.com/c/microsoft-malware-prediction/discussion/76013 23 | """ 24 | y_true = np.asarray(y_true) 25 | y_true = y_true[np.argsort(y_prob)] 26 | nfalse = 0 27 | auc = 0 28 | n = len(y_true) 29 | for i in range(n): 30 | y_i = y_true[i] 31 | nfalse += 1 - y_i 32 | auc += y_i * nfalse 33 | auc /= nfalse * (n - nfalse) 34 | return auc 35 | -------------------------------------------------------------------------------- /kaggle/tuning/optuna_classification.py: -------------------------------------------------------------------------------- 1 | # paper: https://arxiv.org/pdf/1907.10902.pdf 2 | # examples: https://github.com/optuna/optuna-examples 3 | 4 | """ 5 | Optuna example that optimizes a classifier configuration for cancer dataset 6 | using XGBoost. 7 | 8 | In this example, we optimize the validation accuracy of cancer detection 9 | using XGBoost. We optimize both the choice of booster model and its 10 | hyperparameters. 11 | 12 | """ 13 | 14 | import numpy as np 15 | import optuna 16 | import sklearn.datasets 17 | import sklearn.metrics 18 | import xgboost as xgb 19 | from sklearn.model_selection import train_test_split 20 | 21 | 22 | def objective(trial): 23 | (data, target) = sklearn.datasets.load_breast_cancer(return_X_y=True) 24 | train_x, valid_x, train_y, valid_y = train_test_split(data, target, test_size=0.25) 25 | dtrain = xgb.DMatrix(train_x, label=train_y) 26 | dvalid = xgb.DMatrix(valid_x, label=valid_y) 27 | 28 | param = { 29 | "verbosity": 0, 30 | "objective": "binary:logistic", 31 | # use exact for small dataset. 32 | "tree_method": "exact", 33 | # defines booster, gblinear for linear functions. 34 | "booster": trial.suggest_categorical("booster", ["gbtree", "gblinear", "dart"]), 35 | # L2 regularization weight. 36 | "lambda": trial.suggest_float("lambda", 1e-8, 1.0, log=True), 37 | # L1 regularization weight. 38 | "alpha": trial.suggest_float("alpha", 1e-8, 1.0, log=True), 39 | # sampling ratio for training data. 40 | "subsample": trial.suggest_float("subsample", 0.2, 1.0), 41 | # sampling according to each tree. 42 | "colsample_bytree": trial.suggest_float("colsample_bytree", 0.2, 1.0), 43 | } 44 | 45 | if param["booster"] in ["gbtree", "dart"]: 46 | # maximum depth of the tree, signifies complexity of the tree. 47 | param["max_depth"] = trial.suggest_int("max_depth", 3, 9, step=2) 48 | # minimum child weight, larger the term more conservative the tree. 49 | param["min_child_weight"] = trial.suggest_int("min_child_weight", 2, 10) 50 | param["eta"] = trial.suggest_float("eta", 1e-8, 1.0, log=True) 51 | # defines how selective algorithm is. 52 | param["gamma"] = trial.suggest_float("gamma", 1e-8, 1.0, log=True) 53 | param["grow_policy"] = trial.suggest_categorical( 54 | "grow_policy", ["depthwise", "lossguide"] 55 | ) 56 | 57 | if param["booster"] == "dart": 58 | param["sample_type"] = trial.suggest_categorical( 59 | "sample_type", ["uniform", "weighted"] 60 | ) 61 | param["normalize_type"] = trial.suggest_categorical( 62 | "normalize_type", ["tree", "forest"] 63 | ) 64 | param["rate_drop"] = trial.suggest_float("rate_drop", 1e-8, 1.0, log=True) 65 | param["skip_drop"] = trial.suggest_float("skip_drop", 1e-8, 1.0, log=True) 66 | 67 | bst = xgb.train(param, dtrain) 68 | preds = bst.predict(dvalid) 69 | pred_labels = np.rint(preds) 70 | accuracy = sklearn.metrics.accuracy_score(valid_y, pred_labels) 71 | return accuracy 72 | 73 | 74 | if __name__ == "__main__": 75 | study = optuna.create_study(direction="maximize") 76 | study.optimize(objective, n_trials=100, timeout=600) 77 | 78 | print("Number of finished trials: ", len(study.trials)) 79 | print("Best trial:") 80 | trial = study.best_trial 81 | 82 | print(" Value: {}".format(trial.value)) 83 | print(" Params: ") 84 | for key, value in trial.params.items(): 85 | print(" {}: {}".format(key, value)) 86 | 87 | """ 88 | Optuna example that optimizes a classifier configuration for cancer dataset using LightGBM. 89 | 90 | In this example, we optimize the validation accuracy of cancer detection using LightGBM. 91 | We optimize both the choice of booster model and their hyperparameters. 92 | 93 | """ 94 | 95 | import lightgbm as lgb 96 | import numpy as np 97 | import optuna 98 | import sklearn.datasets 99 | import sklearn.metrics 100 | from sklearn.model_selection import train_test_split 101 | 102 | 103 | # FYI: Objective functions can take additional arguments 104 | # (https://optuna.readthedocs.io/en/stable/faq.html#objective-func-additional-args). 105 | def objective(trial): 106 | data, target = sklearn.datasets.load_breast_cancer(return_X_y=True) 107 | train_x, valid_x, train_y, valid_y = train_test_split(data, target, test_size=0.25) 108 | dtrain = lgb.Dataset(train_x, label=train_y) 109 | 110 | param = { 111 | "objective": "binary", 112 | "metric": "binary_logloss", 113 | "verbosity": -1, 114 | "boosting_type": "gbdt", 115 | "lambda_l1": trial.suggest_float("lambda_l1", 1e-8, 10.0, log=True), 116 | "lambda_l2": trial.suggest_float("lambda_l2", 1e-8, 10.0, log=True), 117 | "num_leaves": trial.suggest_int("num_leaves", 2, 256), 118 | "feature_fraction": trial.suggest_float("feature_fraction", 0.4, 1.0), 119 | "bagging_fraction": trial.suggest_float("bagging_fraction", 0.4, 1.0), 120 | "bagging_freq": trial.suggest_int("bagging_freq", 1, 7), 121 | "min_child_samples": trial.suggest_int("min_child_samples", 5, 100), 122 | } 123 | 124 | gbm = lgb.train(param, dtrain) 125 | preds = gbm.predict(valid_x) 126 | pred_labels = np.rint(preds) 127 | accuracy = sklearn.metrics.accuracy_score(valid_y, pred_labels) 128 | return accuracy 129 | 130 | 131 | if __name__ == "__main__": 132 | study = optuna.create_study(direction="maximize") 133 | study.optimize(objective, n_trials=100) 134 | 135 | print("Number of finished trials: {}".format(len(study.trials))) 136 | 137 | print("Best trial:") 138 | trial = study.best_trial 139 | 140 | print(" Value: {}".format(trial.value)) 141 | 142 | print(" Params: ") 143 | for key, value in trial.params.items(): 144 | print(" {}: {}".format(key, value)) 145 | 146 | """ 147 | Optuna example that optimizes a classifier configuration for cancer dataset using 148 | Catboost. 149 | 150 | In this example, we optimize the validation accuracy of cancer detection using 151 | Catboost. We optimize both the choice of booster model and their hyperparameters. 152 | 153 | """ 154 | 155 | import catboost as cb 156 | import numpy as np 157 | import optuna 158 | from sklearn.datasets import load_breast_cancer 159 | from sklearn.metrics import accuracy_score 160 | from sklearn.model_selection import train_test_split 161 | 162 | 163 | def objective(trial): 164 | data, target = load_breast_cancer(return_X_y=True) 165 | train_x, valid_x, train_y, valid_y = train_test_split(data, target, test_size=0.3) 166 | 167 | param = { 168 | "objective": trial.suggest_categorical( 169 | "objective", ["Logloss", "CrossEntropy"] 170 | ), 171 | "colsample_bylevel": trial.suggest_float("colsample_bylevel", 0.01, 0.1), 172 | "depth": trial.suggest_int("depth", 1, 12), 173 | "boosting_type": trial.suggest_categorical( 174 | "boosting_type", ["Ordered", "Plain"] 175 | ), 176 | "bootstrap_type": trial.suggest_categorical( 177 | "bootstrap_type", ["Bayesian", "Bernoulli", "MVS"] 178 | ), 179 | "used_ram_limit": "3gb", 180 | } 181 | 182 | if param["bootstrap_type"] == "Bayesian": 183 | param["bagging_temperature"] = trial.suggest_float("bagging_temperature", 0, 10) 184 | elif param["bootstrap_type"] == "Bernoulli": 185 | param["subsample"] = trial.suggest_float("subsample", 0.1, 1) 186 | 187 | gbm = cb.CatBoostClassifier(**param) 188 | 189 | gbm.fit( 190 | train_x, 191 | train_y, 192 | eval_set=[(valid_x, valid_y)], 193 | verbose=0, 194 | early_stopping_rounds=100, 195 | ) 196 | 197 | preds = gbm.predict(valid_x) 198 | pred_labels = np.rint(preds) 199 | accuracy = accuracy_score(valid_y, pred_labels) 200 | return accuracy 201 | 202 | 203 | if __name__ == "__main__": 204 | study = optuna.create_study(direction="maximize") 205 | study.optimize(objective, n_trials=100, timeout=600) 206 | 207 | print("Number of finished trials: {}".format(len(study.trials))) 208 | 209 | print("Best trial:") 210 | trial = study.best_trial 211 | 212 | print(" Value: {}".format(trial.value)) 213 | 214 | print(" Params: ") 215 | for key, value in trial.params.items(): 216 | print(" {}: {}".format(key, value)) 217 | -------------------------------------------------------------------------------- /kaggle/validation/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthecoder/AI/b77a9c354386bdb681a80f5dd913b4b7ee4f640c/kaggle/validation/README.md -------------------------------------------------------------------------------- /kaggle/viz/eda.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import seaborn as sns 4 | import matplotlib.pyplot as plt 5 | %matplotlib inline 6 | 7 | sns.set_theme(style="whitegrid", palette="muted", context="talk", font_scale=1.2) 8 | 9 | plt.rcParams.update({ 10 | 'figure.figsize': (10, 6), 11 | 'axes.titlesize': 18, 12 | 'axes.labelsize': 16, 13 | 'xtick.labelsize': 14, 14 | 'ytick.labelsize': 14, 15 | 'legend.fontsize': 12 16 | }) 17 | 18 | import warnings 19 | warnings.filterwarnings("ignore") 20 | 21 | def reduce_mem_usage(df, verbose=True): 22 | numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64'] 23 | start_mem = df.memory_usage().sum() / 1024**2 24 | for col in df.columns: 25 | col_type = df[col].dtypes 26 | if col_type in numerics: 27 | c_min = df[col].min() 28 | c_max = df[col].max() 29 | if str(col_type)[:3] == 'int': 30 | if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max: 31 | df[col] = df[col].astype(np.int8) 32 | elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max: 33 | df[col] = df[col].astype(np.int16) 34 | elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max: 35 | df[col] = df[col].astype(np.int32) 36 | elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max: 37 | df[col] = df[col].astype(np.int64) 38 | else: 39 | if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max: 40 | df[col] = df[col].astype(np.float16) 41 | elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max: 42 | df[col] = df[col].astype(np.float32) 43 | else: 44 | df[col] = df[col].astype(np.float64) 45 | end_mem = df.memory_usage().sum() / 1024**2 46 | if verbose: print('Mem. usage decreased to {:5.2f} Mb ({:.1f}% reduction)'.format(end_mem, 100 * (start_mem - end_mem) / start_mem)) 47 | return df 48 | 49 | ## EDA 50 | 51 | def plot_categorical(data, column_name): 52 | f, ax = plt.subplots(1, 2, figsize=(18, 8)) 53 | data[column_name].value_counts().plot.pie(explode=[0, 0.1], autopct='%1.1f%%', ax=ax[0], shadow=True) 54 | ax[0].set_title(column_name) 55 | ax[0].set_ylabel('') 56 | sns.countplot(x=column_name, data=data, ax=ax[1]) 57 | ax[1].set_title(column_name) 58 | plt.show() 59 | 60 | def plot_correlation_heatmap(df): 61 | corr = df.corr() 62 | mask = np.triu(corr) 63 | plt.figure(figsize=(15, 11)) 64 | sns.heatmap(corr, mask=mask, annot=True, fmt=".3f") 65 | plt.show() 66 | 67 | 68 | def plot_pairplot(data, numerical_cols, target_col): 69 | pairplot = sns.pairplot(data=data[numerical_cols + [target_col]], 70 | hue=target_col, 71 | corner=True, 72 | plot_kws={'alpha': 0.7, 's': 50, 'edgecolor': 'k'}, 73 | palette='Set1', 74 | diag_kws={'edgecolor':'k'}) 75 | pairplot.fig.suptitle("Pairplot of Numerical Variables", y=1.02) 76 | plt.show() 77 | 78 | def plot_boxplot(data, numerical_col, target_col): 79 | sns.boxplot(x=target_col, y=numerical_col, data=data) 80 | plt.title(f'Box Plot of {numerical_col} by {target_col}') 81 | plt.show() 82 | 83 | 84 | def plot_violinplot(data, numerical_col, target_col): 85 | sns.violinplot(x=target_col, y=numerical_col, data=data) 86 | plt.title(f'Violin Plot of {numerical_col} by {target_col}') 87 | plt.show() 88 | 89 | def plot_histograms(data, continuous_vars, target_col): 90 | for column in continuous_vars: 91 | if data[column].dtype == 'float16': 92 | data[column] = data[column].astype('float32') 93 | 94 | fig, ax = plt.subplots(figsize=(18, 4)) 95 | sns.histplot(data=data, x=column, hue=target_col, bins=50, kde=True) 96 | plt.show() 97 | 98 | def plot_countplot(data, column_name): 99 | sns.countplot(x=column_name, data=data) 100 | plt.title(f'Count Plot of {column_name}') 101 | plt.show() -------------------------------------------------------------------------------- /llms/README.md: -------------------------------------------------------------------------------- 1 | # LLMs 2 | 3 | Landscape 4 | 5 | - [Language Models Formulas](https://www.youtube.com/watch?v=KCXDr-UOb9A) 6 | 7 | From Scratch 8 | 9 | - [GPT Speed Optimization](https://www.dipkumar.dev/becoming-the-unbeatable/posts/gpt-kvcache/) 10 | - [GPT in 60 Lines of NumPy | Jay Mody](https://jaykmody.com/blog/gpt-from-scratch/) 11 | 12 | Mistral 13 | 14 | - [makeMoE: Implement a Sparse Mixture of Experts Language Model from Scratch](https://huggingface.co/blog/AviSoori1x/makemoe-from-scratch) 15 | -------------------------------------------------------------------------------- /llms/fine_tuning/REAMDE.md: -------------------------------------------------------------------------------- 1 | # Fine Tuning 2 | 3 | [Fine-Tune LLMs](https://www.philschmid.de/fine-tune-llms-in-2024-with-trl) 4 | [Fine-Tuning — The GenAI Guidebook](https://ravinkumar.com/GenAiGuidebook/language_models/finetuning.html) 5 | -------------------------------------------------------------------------------- /llms/gpt_numpy/gpt.py: -------------------------------------------------------------------------------- 1 | # https://jaykmody.com/blog/gpt-from-scratch/ 2 | -------------------------------------------------------------------------------- /math_for_ml/README.md: -------------------------------------------------------------------------------- 1 | # Math for ML 2 | 3 | - [Math for Machine Learning](https://www.youtube.com/playlist?list=PLD80i8An1OEGZ2tYimemzwC3xqkU0jKUg) 4 | - [edu/math-for-ml at main · wandb/edu](https://github.com/wandb/edu/tree/main/math-for-ml) 5 | 6 | - [The Matrix Calculus You Need For Deep Learning](https://arxiv.org/pdf/1802.01528.pdf) 7 | - [Essence of calculus](https://www.youtube.com/playlist?list=PLZHQObOWTQDMsr9K-rj53DwVRMYO3t5Yr) 8 | 9 | - [Computational Linear Algebra](https://www.fast.ai/posts/2017-07-17-num-lin-alg.html) ([video](https://www.youtube.com/playlist?list=PLtmWHNX-gukIc92m1K0P6bIOnZb-mg0hY), [code](https://github.com/fastai/numerical-linear-algebra)) 10 | - [Introduction to Linear Algebra for Applied Machine Learning with Python](https://pabloinsente.github.io/intro-linear-algebra) 11 | 12 | Videos 13 | 14 | - [Essence of linear algebra](https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab) 15 | -------------------------------------------------------------------------------- /math_for_ml/linalg.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [] 9 | } 10 | ], 11 | "metadata": { 12 | "kernelspec": { 13 | "display_name": "base", 14 | "language": "python", 15 | "name": "python3" 16 | }, 17 | "language_info": { 18 | "name": "python", 19 | "version": "3.9.10" 20 | } 21 | }, 22 | "nbformat": 4, 23 | "nbformat_minor": 2 24 | } 25 | -------------------------------------------------------------------------------- /minitorch/README.md: -------------------------------------------------------------------------------- 1 | # MiniTorch 2 | 3 | Resources 4 | 5 | - [MiniTorch](https://minitorch.github.io/) 6 | - [minitorch](https://github.com/minitorch/) 7 | - [MiniTorch: A DIY Course on Machine Learning Engineering](https://www.youtube.com/playlist?list=PLO45-80-XKkQyROXXpn4PfjF1J2tH46w8) 8 | -------------------------------------------------------------------------------- /ml_from_scratch/README.md: -------------------------------------------------------------------------------- 1 | # ML and DL from scratch 2 | 3 | - [Implement - YouTube](https://www.youtube.com/playlist?list=PLG8XxYPkVOUvVzz1ZKcGAJpIBK7GRrFYR) 4 | - [eriklindernoren/ML-From-Scratch](https://github.com/eriklindernoren/ML-From-Scratch) 5 | - [JeremyNixon/oracle](https://github.com/JeremyNixon/oracle) 6 | - [trekhleb/homemade-machine-learning](https://github.com/trekhleb/homemade-machine-learning) 7 | - [ethen8181/machine-learning](https://github.com/ethen8181/machine-learning) 8 | 9 | Specific ones 10 | 11 | - [Ekeany/XGBoost-From-Scratch](https://github.com/Ekeany/XGBoost-From-Scratch) 12 | - [HowUMAPWorks/HowUMAPWorks.ipynb](https://github.com/NikolayOskolkov/HowUMAPWorks/blob/c872b2feb1426992c7ef4528994aba7ad6fcc0d6/HowUMAPWorks.ipynb) 13 | 14 | Papers 15 | 16 | - [[2402.01502] Why do Random Forests Work? Understanding Tree Ensembles as Self-Regularizing Adaptive Smoothers](https://arxiv.org/abs/2402.01502) 17 | -------------------------------------------------------------------------------- /ml_from_scratch/supervised/lineargression.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from dataclasses import dataclass 3 | import matplotlib.pyplot as plt 4 | from sklearn.metrics import accuracy_score, precision_recall_fscore_support 5 | 6 | plt.style.use("bmh") 7 | 8 | 9 | """ 10 | f(x) = xW + b 11 | 12 | MSELoss = (actual - predicted)^2 / n_samples 13 | 14 | wrt weights 15 | ((y - f(x))^2)' = 2(y - f(x))( y - f(x))' 16 | = 2(y - f(x))(y - xW - b) 17 | = -2x(y - f(x)) 18 | 19 | wrt bias 20 | ((y - f(x))^2)' = 2(y - f(x))( y - f(x))' 21 | = 2(y - f(x))(y - xW - b) 22 | = -2(y - f(x)) 23 | """ 24 | 25 | 26 | @dataclass 27 | class LinearRegression: 28 | features: np.ndarray 29 | labels: np.ndarray 30 | learning_rate: float 31 | epochs: int 32 | logging: bool 33 | 34 | def fit(self, features: np.ndarray, labels: np.ndarray) -> None: 35 | """Fits LR model""" 36 | 37 | n_samples, n_features = features.shape 38 | self.weights, self.bias = np.zeros(n_features), 0 39 | 40 | for epoch in range(self.epochs): 41 | residuals = labels - self.predict(features) 42 | 43 | d_weights = -2 / n_samples * features.T.dot(residuals) 44 | 45 | d_bias = -2 / n_samples * residuals.sum() 46 | 47 | self.weights -= self.learning_rate * d_weights 48 | self.bias -= self.learning_rate * d_bias 49 | 50 | mse_loss = np.mean(np.square(residuals)) 51 | if self.logging: 52 | print(f"MSE loss [{epoch}] : {mse_loss:.15f}") 53 | 54 | def predict(self, features: np.ndarray) -> np.ndarray: 55 | """Perform inference using given features""" 56 | 57 | return features.dot(self.weights) + self.bias 58 | 59 | 60 | if __name__ == "__main__": 61 | # training data 62 | X_train = np.arange(0, 250).reshape(-1, 1) 63 | y_train = np.arange(0, 500, 2) 64 | 65 | # testing data 66 | X_test = np.arange(300, 400, 8).reshape(-1, 1) 67 | y_test = np.arange(600, 800, 16) 68 | 69 | # Train model 70 | LR = LinearRegression(X_train, y_train, learning_rate=1e-5, epochs=75, logging=True) 71 | 72 | LR.fit(X_train, y_train) 73 | 74 | preds = LR.predict(X_test).round() 75 | 76 | # Plot the data 77 | fig, axs = plt.subplots(nrows=1, ncols=3) 78 | fig.suptitle("f(x) = 2x") 79 | fig.tight_layout() 80 | fig.set_size_inches(18, 8) 81 | 82 | axs[0].set_title("Visualization for f(x) = 2x") 83 | axs[0].set_xlabel("x") 84 | axs[0].set_ylabel("y") 85 | axs[0].plot(X_train, y_train) 86 | 87 | axs[1].set_title("Scatterplot for f(x) = 2x Data") 88 | axs[1].set_xlabel("x") 89 | axs[1].set_ylabel("y") 90 | axs[1].scatter(X_test, y_test, color="blue") 91 | 92 | axs[2].set_title("Visualization for Approximated f(x) = 2x") 93 | axs[2].set_xlabel("x") 94 | axs[2].set_ylabel("y") 95 | axs[2].scatter(X_test, y_test, color="blue") 96 | axs[2].plot(X_test, preds) 97 | 98 | plt.show() 99 | 100 | accuracy = accuracy_score(preds, y_test) 101 | precision, recall, fscore, _ = precision_recall_fscore_support( 102 | y_test, preds, average="macro" 103 | ) 104 | 105 | print(f"Accuracy: {accuracy:.3f}") 106 | print(f"Precision: {recall:.3f}") 107 | print(f"Recall: {precision:.3f}") 108 | print(f"F-score: {fscore:.3f}") 109 | -------------------------------------------------------------------------------- /ml_from_scratch/supervised/xgboost.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthecoder/AI/b77a9c354386bdb681a80f5dd913b4b7ee4f640c/ml_from_scratch/supervised/xgboost.py -------------------------------------------------------------------------------- /nlp/BERT/README.md: -------------------------------------------------------------------------------- 1 | # BERT 2 | 3 | - [BERT](https://huggingface.co/docs/transformers/model_doc/bert) 4 | - [DistilBERT](https://huggingface.co/docs/transformers/model_doc/distilbert) 5 | - [DeBERTa-v2](https://huggingface.co/docs/transformers/model_doc/deberta-v2) 6 | - [RoBERTa](https://huggingface.co/docs/transformers/model_doc/roberta) 7 | 8 | Applications 9 | 10 | [BERTopic for Topic Modeling - Maarten Grootendorst - Talking Language AI Ep#1](https://www.youtube.com/watch?v=uZxQz87lb84&list=PLLalUvky4CLJ9ZgtZguDJ7dAYuI1bfaYW&index=7&t=840s) 11 | -------------------------------------------------------------------------------- /nlp/classic/LDA.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthecoder/AI/b77a9c354386bdb681a80f5dd913b4b7ee4f640c/nlp/classic/LDA.py -------------------------------------------------------------------------------- /nlp/classic/LSA.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthecoder/AI/b77a9c354386bdb681a80f5dd913b4b7ee4f640c/nlp/classic/LSA.py -------------------------------------------------------------------------------- /nlp/classic/PCA.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthecoder/AI/b77a9c354386bdb681a80f5dd913b4b7ee4f640c/nlp/classic/PCA.py -------------------------------------------------------------------------------- /nlp/classic/SVD.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthecoder/AI/b77a9c354386bdb681a80f5dd913b4b7ee4f640c/nlp/classic/SVD.py -------------------------------------------------------------------------------- /nlp/classic/TFIDF.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import Dict, List 3 | 4 | import matplotlib.pyplot as plt 5 | import numpy as np 6 | import pandas as pd 7 | import seaborn as sns 8 | from sklearn.feature_extraction.text import TfidfVectorizer 9 | 10 | pd.set_option("display.max_rows", None) 11 | 12 | 13 | def tokenize(text: str) -> List[str]: 14 | """Tokenize the input text by removing punctuation and splitting into words.""" 15 | cleaned_text = re.sub(r"[^\w\s]", "", text) 16 | tokens = cleaned_text.lower().split() 17 | return tokens 18 | 19 | 20 | def calculate_word_frequencies(document: List[str]) -> Dict[str, int]: 21 | """Calculate the frequency of each word in a document.""" 22 | frequencies = {} 23 | for word in document: 24 | frequencies[word] = frequencies.get(word, 0) + 1 25 | return frequencies 26 | 27 | 28 | def calculate_tf(word_counts: Dict[str, int], document_length: int) -> Dict[str, float]: 29 | """Calculate term frequency for each word in a document.""" 30 | 31 | tf_dict = { 32 | word: count / float(document_length) for word, count in word_counts.items() 33 | } 34 | 35 | return tf_dict 36 | 37 | 38 | def calculate_idf(documents_word_counts: List[Dict[str, int]]) -> Dict[str, float]: 39 | """Calculate inverse document frequency for each word across all documents.""" 40 | N = len(documents_word_counts) 41 | idf_dict = {} 42 | unique_words = set(word for doc in documents_word_counts for word in doc) 43 | 44 | for word in unique_words: 45 | # count number of docs containing the word 46 | doc_containing_word = sum( 47 | word in document for document in documents_word_counts 48 | ) 49 | 50 | idf_dict[word] = np.log10((N + 1) / (doc_containing_word + 1)) 51 | 52 | return idf_dict 53 | 54 | 55 | def calculate_tfidf( 56 | tf_dict: Dict[str, float], idf_dict: Dict[str, float] 57 | ) -> Dict[str, float]: 58 | """Calculate TF-IDF for each word in a document.""" 59 | 60 | tfidf_dict = {word: tf_val * idf_dict[word] for word, tf_val in tf_dict.items()} 61 | 62 | return tfidf_dict 63 | 64 | 65 | def visualize_tfidf(tfidf_matrix: pd.DataFrame): 66 | """Visualize the TF-IDF matrix using a heatmap.""" 67 | plt.figure(figsize=(10, 10)) 68 | sns.heatmap(tfidf_matrix, annot=True, cmap="YlGnBu") 69 | plt.xticks(rotation=45, ha="right") 70 | plt.tight_layout() 71 | plt.show() 72 | 73 | 74 | def main(): 75 | # seneca 76 | sentences = [ 77 | "Life, if well lived, is long enough.", 78 | "Your time is limited, so don't waste it living someone else's life.", 79 | ] 80 | 81 | documents = [tokenize(sentence) for sentence in sentences] 82 | 83 | documents_word_counts = [calculate_word_frequencies(doc) for doc in documents] 84 | 85 | idf_dict = calculate_idf(documents_word_counts) 86 | 87 | tfidfs = [] 88 | for doc, doc_word_counts in zip(documents, documents_word_counts): 89 | tf_dict = calculate_tf(doc_word_counts, len(doc)) 90 | tfidf_dict = calculate_tfidf(tf_dict, idf_dict) 91 | tfidfs.append(tfidf_dict) 92 | 93 | tfidf_matrix = pd.DataFrame(tfidfs, index=["Document A", "Document B"]).T 94 | visualize_tfidf(tfidf_matrix) 95 | 96 | # scikit-learn 97 | titles = ["seneca", "steve_jobs"] 98 | 99 | vectorizer = TfidfVectorizer() 100 | vector = vectorizer.fit_transform(sentences) 101 | dict(zip(vectorizer.get_feature_names_out(), vector.toarray()[0])) 102 | 103 | tfidf_df = pd.DataFrame( 104 | vector.toarray(), index=titles, columns=vectorizer.get_feature_names_out() 105 | ) 106 | 107 | visualize_tfidf(tfidf_df.T) 108 | 109 | 110 | if __name__ == "__main__": 111 | main() 112 | -------------------------------------------------------------------------------- /nlp/classic/TSNE.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthecoder/AI/b77a9c354386bdb681a80f5dd913b4b7ee4f640c/nlp/classic/TSNE.py -------------------------------------------------------------------------------- /nlp/classic/UMAP.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthecoder/AI/b77a9c354386bdb681a80f5dd913b4b7ee4f640c/nlp/classic/UMAP.py -------------------------------------------------------------------------------- /nlp/embeddings/README.md: -------------------------------------------------------------------------------- 1 | # Embeddings 2 | 3 | - [What Are Embeddings - Vicki Boykis](https://github.com/veekaybee/what_are_embeddings/blob/main/embeddings.pdf) 4 | - [hackerllama - Sentence Embeddings. Introduction to Sentence Embeddings](https://osanseviero.github.io/hackerllama/blog/posts/sentence_embeddings/) 5 | - [Embeddings: What they are and why they matter](https://simonwillison.net/2023/Oct/23/embeddings/) 6 | -------------------------------------------------------------------------------- /nlp/embeddings/tfidf.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 12, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "# seneca\n", 10 | "sentence_a = \"\"\"Life, if well lived, is long enough.\"\"\"\n", 11 | "# steve jobs\n", 12 | "sentence_b = \"\"\"Your time is limited, so don't waste it living someone else's life.\"\"\"" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 26, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "from typing import List\n", 22 | "import re\n", 23 | "\n", 24 | "\n", 25 | "def tokenize(text: str) -> List[str]:\n", 26 | " # Remove punctuation using regex, keeping words and numbers\n", 27 | " cleaned_text = re.sub(r\"[^\\w\\s]\", \"\", text)\n", 28 | " # Split the cleaned text into words\n", 29 | " tokens = cleaned_text.lower().split()\n", 30 | " return tokens" 31 | ] 32 | }, 33 | { 34 | "cell_type": "code", 35 | "execution_count": 27, 36 | "metadata": {}, 37 | "outputs": [ 38 | { 39 | "name": "stdout", 40 | "output_type": "stream", 41 | "text": [ 42 | "7\n", 43 | "12\n" 44 | ] 45 | }, 46 | { 47 | "data": { 48 | "text/plain": [ 49 | "17" 50 | ] 51 | }, 52 | "execution_count": 27, 53 | "metadata": {}, 54 | "output_type": "execute_result" 55 | } 56 | ], 57 | "source": [ 58 | "doc_a = tokenize(sentence_a)\n", 59 | "doc_b = tokenize(sentence_b)\n", 60 | "\n", 61 | "print(len(doc_a))\n", 62 | "print(len(doc_b))\n", 63 | "\n", 64 | "total_corpus = set(doc_a).union(set(doc_b))\n", 65 | "\n", 66 | "len(total_corpus)" 67 | ] 68 | }, 69 | { 70 | "cell_type": "markdown", 71 | "metadata": {}, 72 | "source": [ 73 | "### bag of words\n" 74 | ] 75 | }, 76 | { 77 | "cell_type": "code", 78 | "execution_count": 28, 79 | "metadata": {}, 80 | "outputs": [ 81 | { 82 | "data": { 83 | "text/html": [ 84 | "
\n", 85 | "\n", 98 | "\n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | "
01
so01
lived10
it01
life11
elses01
time01
well10
long10
if10
living01
your01
is11
limited01
dont01
waste01
someone01
enough10
\n", 194 | "
" 195 | ], 196 | "text/plain": [ 197 | " 0 1\n", 198 | "so 0 1\n", 199 | "lived 1 0\n", 200 | "it 0 1\n", 201 | "life 1 1\n", 202 | "elses 0 1\n", 203 | "time 0 1\n", 204 | "well 1 0\n", 205 | "long 1 0\n", 206 | "if 1 0\n", 207 | "living 0 1\n", 208 | "your 0 1\n", 209 | "is 1 1\n", 210 | "limited 0 1\n", 211 | "dont 0 1\n", 212 | "waste 0 1\n", 213 | "someone 0 1\n", 214 | "enough 1 0" 215 | ] 216 | }, 217 | "execution_count": 28, 218 | "metadata": {}, 219 | "output_type": "execute_result" 220 | } 221 | ], 222 | "source": [ 223 | "import pandas as pd\n", 224 | "\n", 225 | "\n", 226 | "word_count_a = dict.fromkeys(total_corpus, 0)\n", 227 | "word_count_b = dict.fromkeys(total_corpus, 0)\n", 228 | "\n", 229 | "for word in doc_a:\n", 230 | " word_count_a[word] += 1\n", 231 | "\n", 232 | "for word in doc_b:\n", 233 | " word_count_b[word] += 1\n", 234 | "\n", 235 | "pd.set_option(\"display.max_rows\", None)\n", 236 | "\n", 237 | "freq = pd.DataFrame([word_count_a, word_count_b])\n", 238 | "freq.T" 239 | ] 240 | }, 241 | { 242 | "cell_type": "markdown", 243 | "metadata": {}, 244 | "source": [ 245 | "### TF\n" 246 | ] 247 | }, 248 | { 249 | "cell_type": "code", 250 | "execution_count": 29, 251 | "metadata": {}, 252 | "outputs": [ 253 | { 254 | "data": { 255 | "text/plain": [ 256 | "{'so': 0.0,\n", 257 | " 'lived': 0.14285714285714285,\n", 258 | " 'it': 0.0,\n", 259 | " 'life': 0.14285714285714285,\n", 260 | " 'elses': 0.0,\n", 261 | " 'time': 0.0,\n", 262 | " 'well': 0.14285714285714285,\n", 263 | " 'long': 0.14285714285714285,\n", 264 | " 'if': 0.14285714285714285,\n", 265 | " 'living': 0.0,\n", 266 | " 'your': 0.0,\n", 267 | " 'is': 0.14285714285714285,\n", 268 | " 'limited': 0.0,\n", 269 | " 'dont': 0.0,\n", 270 | " 'waste': 0.0,\n", 271 | " 'someone': 0.0,\n", 272 | " 'enough': 0.14285714285714285}" 273 | ] 274 | }, 275 | "execution_count": 29, 276 | "metadata": {}, 277 | "output_type": "execute_result" 278 | } 279 | ], 280 | "source": [ 281 | "def tf(word_counts: dict, document: list[str]) -> dict:\n", 282 | " \"\"\"Calculate term frequency of each word in a document.\"\"\"\n", 283 | "\n", 284 | " tf_dict = {}\n", 285 | " corpus_count = len(document)\n", 286 | "\n", 287 | " for word, count in word_counts.items():\n", 288 | " tf_dict[word] = count / float(corpus_count)\n", 289 | "\n", 290 | " return tf_dict\n", 291 | "\n", 292 | "\n", 293 | "tf(word_count_a, doc_a)" 294 | ] 295 | }, 296 | { 297 | "cell_type": "markdown", 298 | "metadata": {}, 299 | "source": [ 300 | "### IDF\n" 301 | ] 302 | }, 303 | { 304 | "cell_type": "code", 305 | "execution_count": 30, 306 | "metadata": {}, 307 | "outputs": [ 308 | { 309 | "data": { 310 | "text/plain": [ 311 | "{'so': 0.17609125905568124,\n", 312 | " 'lived': 0.17609125905568124,\n", 313 | " 'it': 0.17609125905568124,\n", 314 | " 'life': 0.0,\n", 315 | " 'elses': 0.17609125905568124,\n", 316 | " 'time': 0.17609125905568124,\n", 317 | " 'well': 0.17609125905568124,\n", 318 | " 'long': 0.17609125905568124,\n", 319 | " 'if': 0.17609125905568124,\n", 320 | " 'living': 0.17609125905568124,\n", 321 | " 'your': 0.17609125905568124,\n", 322 | " 'is': 0.0,\n", 323 | " 'limited': 0.17609125905568124,\n", 324 | " 'dont': 0.17609125905568124,\n", 325 | " 'waste': 0.17609125905568124,\n", 326 | " 'someone': 0.17609125905568124,\n", 327 | " 'enough': 0.17609125905568124}" 328 | ] 329 | }, 330 | "execution_count": 30, 331 | "metadata": {}, 332 | "output_type": "execute_result" 333 | } 334 | ], 335 | "source": [ 336 | "import numpy as np\n", 337 | "\n", 338 | "\n", 339 | "def idf(word_counts: list[dict[str, int]]) -> dict:\n", 340 | " \"\"\"Given N documents, no. of documents in which the the term appears for each term\"\"\"\n", 341 | " idf_dict = {}\n", 342 | " N = len(word_counts)\n", 343 | "\n", 344 | " idf_dict = dict.fromkeys(word_counts[0].keys(), 0)\n", 345 | "\n", 346 | " for word in idf_dict.keys():\n", 347 | " idf_dict[word] = sum(doc[word] > 0 for doc in word_counts)\n", 348 | "\n", 349 | " for word, df in idf_dict.items():\n", 350 | " idf_dict[word] = np.log10((N + 1.0) / (df + 1.0))\n", 351 | "\n", 352 | " return idf_dict\n", 353 | "\n", 354 | "\n", 355 | "idfs = idf([word_count_a, word_count_b])\n", 356 | "idfs" 357 | ] 358 | }, 359 | { 360 | "cell_type": "markdown", 361 | "metadata": {}, 362 | "source": [ 363 | "### TF-IDF\n" 364 | ] 365 | }, 366 | { 367 | "cell_type": "code", 368 | "execution_count": 31, 369 | "metadata": {}, 370 | "outputs": [ 371 | { 372 | "data": { 373 | "text/html": [ 374 | "
\n", 375 | "\n", 388 | "\n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | "
01
so0.0000000.014674
lived0.0251560.000000
it0.0000000.014674
life0.0000000.000000
elses0.0000000.014674
time0.0000000.014674
well0.0251560.000000
long0.0251560.000000
if0.0251560.000000
living0.0000000.014674
your0.0000000.014674
is0.0000000.000000
limited0.0000000.014674
dont0.0000000.014674
waste0.0000000.014674
someone0.0000000.014674
enough0.0251560.000000
\n", 484 | "
" 485 | ], 486 | "text/plain": [ 487 | " 0 1\n", 488 | "so 0.000000 0.014674\n", 489 | "lived 0.025156 0.000000\n", 490 | "it 0.000000 0.014674\n", 491 | "life 0.000000 0.000000\n", 492 | "elses 0.000000 0.014674\n", 493 | "time 0.000000 0.014674\n", 494 | "well 0.025156 0.000000\n", 495 | "long 0.025156 0.000000\n", 496 | "if 0.025156 0.000000\n", 497 | "living 0.000000 0.014674\n", 498 | "your 0.000000 0.014674\n", 499 | "is 0.000000 0.000000\n", 500 | "limited 0.000000 0.014674\n", 501 | "dont 0.000000 0.014674\n", 502 | "waste 0.000000 0.014674\n", 503 | "someone 0.000000 0.014674\n", 504 | "enough 0.025156 0.000000" 505 | ] 506 | }, 507 | "execution_count": 31, 508 | "metadata": {}, 509 | "output_type": "execute_result" 510 | } 511 | ], 512 | "source": [ 513 | "def tfidf(doc_elements: dict[str, int], idfs: dict[str, int]) -> dict:\n", 514 | " \"\"\"TF * IDF per word given a single word in a single document\"\"\"\n", 515 | "\n", 516 | " tfidf_dict = {}\n", 517 | "\n", 518 | " for word, val in doc_elements.items():\n", 519 | " tfidf_dict[word] = val * idfs[word]\n", 520 | "\n", 521 | " return tfidf_dict\n", 522 | "\n", 523 | "\n", 524 | "# calculate term frequency for each document\n", 525 | "tf_a = tf(word_count_a, doc_a)\n", 526 | "tf_b = tf(word_count_b, doc_b)\n", 527 | "\n", 528 | "# calculate inverse document frequency for each document\n", 529 | "tfidf_a = tfidf(tf_a, idfs)\n", 530 | "tfidf_b = tfidf(tf_b, idfs)\n", 531 | "\n", 532 | "# return score\n", 533 | "document_tfidf = pd.DataFrame([tfidf_a, tfidf_b])\n", 534 | "document_tfidf.T" 535 | ] 536 | }, 537 | { 538 | "cell_type": "code", 539 | "execution_count": 32, 540 | "metadata": {}, 541 | "outputs": [ 542 | { 543 | "data": { 544 | "image/png": "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ixfrhhtuUFBQkEwmk9LT08+YIy4uTiaTyeG47777qpU3RS0AAAAA1HDvvfeeJk2apMcff1wbN25Uhw4dlJCQoKysrCrjv/vuOw0ZMkSjRo1SWlqa+vXrp379+mnr1q32mJKSEl199dWaNm2a0/e+5557dPjwYfvx73//u1q5myorKyurdQbOyrfZEFenAAA4TWlGkqtTAACcobWrE6iWVq+udnUK1bZ7dM9qxXfr1k1XXnmlXnrpJUmSzWZT06ZNNXbsWD366KNnxA8ePFglJSX65JNP7GPdu3dXTEyM5syZ4xC7f/9+hYWFKS0tTTExMQ6vxcXFKSYmRjNmzKhWvqdipRYAAAAAnDCZjXdUR1lZmTZs2KD4+Hj7mNlsVnx8vFJTU6s8JzU11SFekhISEs4a70xKSoqCg4PVrl07JSYm6tixY9U6n+fUAgAAAMBlxmq1ymq1OoxZLBZZLJYzYnNyclRRUaGGDRs6jDds2FA7duyocv7MzMwq4zMzM6uV5x133KHmzZsrJCREmzdv1uTJk7Vz504tXrz4nOegqAUAAACAy0xycrKSkhy34Dz++ON64oknXJPQWdx77732P7dv316NGzfWddddp71796ply5bnNAdFLQAAAABcZhITEzVp0iSHsapWaSUpODhYHh4eOnLkiMP4kSNH1KhRoyrPadSoUbXiz1W3bt0kSXv27DnnopY9tQAAAADghKsfz/NnDovFIj8/P4fjbEWtt7e3OnfurJUrV9rHbDabVq5cqR49elR5To8ePRziJWnFihVnjT9Xvz32p3Hjxud8Diu1AAAAAFDDTZo0SXfddZe6dOmirl27asaMGSopKdGIESMkScOGDVOTJk2UnJwsSRo/fryuvfZaTZ8+XX379tW7776r9evX67XXXrPPmZeXp4yMDB06dEiStHPnTkknV3kbNWqkvXv36u2331afPn0UFBSkzZs3a+LEierZs6eio6PPOXeKWgAAAACo4QYPHqzs7GxNnTpVmZmZiomJ0WeffWa/GVRGRobM5t8bfWNjY/X222/rn//8px577DG1atVKS5cuVbt27ewxH330kb0olqS//vWvkn7f2+vt7a0vv/zSXkA3bdpUAwYM0D//+c9q5c5zai8gnlMLAO6H59QCgDsy1nNqI98w3nNqfxpVvefUGhl7agEAAAAAhkVRCwAAAAAw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", 545 | "text/plain": [ 546 | "
" 547 | ] 548 | }, 549 | "metadata": {}, 550 | "output_type": "display_data" 551 | } 552 | ], 553 | "source": [ 554 | "import matplotlib.pyplot as plt\n", 555 | "import seaborn as sns\n", 556 | "\n", 557 | "\n", 558 | "def visualize_tfidf(tfidf_matrix: pd.DataFrame):\n", 559 | " plt.figure(figsize=(10, 10))\n", 560 | " sns.heatmap(tfidf_matrix, annot=True, cmap=\"YlGnBu\")\n", 561 | " plt.xticks(rotation=45, ha=\"right\")\n", 562 | " plt.tight_layout()\n", 563 | " plt.show()\n", 564 | "\n", 565 | "\n", 566 | "# Prepare the TF-IDF matrix for visualization and EDA\n", 567 | "tfidf_matrix = pd.DataFrame([tfidf_a, tfidf_b], index=[\"Document A\", \"Document B\"]).T\n", 568 | "\n", 569 | "# Visualize the TF-IDF matrix\n", 570 | "visualize_tfidf(tfidf_matrix)" 571 | ] 572 | }, 573 | { 574 | "cell_type": "markdown", 575 | "metadata": {}, 576 | "source": [ 577 | "The heatmap displays the TF-IDF scores of different words across two documents (A and B). The colors indicate the magnitude of the TF-IDF scores, with darker colors representing higher scores. Words that have non-zero scores in both documents are those that are shared between the documents. Words that have a high score in one document and a low or zero score in the other suggest uniqueness. For example, \"ignorance\" has a higher score in Document B, indicating it's more important or unique to that document within the context of these two documents.\n" 578 | ] 579 | }, 580 | { 581 | "cell_type": "markdown", 582 | "metadata": {}, 583 | "source": [ 584 | "# scikit-learn\n" 585 | ] 586 | }, 587 | { 588 | "cell_type": "code", 589 | "execution_count": 38, 590 | "metadata": {}, 591 | "outputs": [], 592 | "source": [ 593 | "from sklearn.feature_extraction.text import TfidfVectorizer\n", 594 | "\n", 595 | "corpus = [sentence_a, sentence_b]\n", 596 | "titles = [\"seneca\", \"steve_jobs\"]\n", 597 | "\n", 598 | "vectorizer = TfidfVectorizer()\n", 599 | "vector = vectorizer.fit_transform(corpus)\n", 600 | "dict(zip(vectorizer.get_feature_names_out(), vector.toarray()[0]))\n", 601 | "\n", 602 | "tfidf_df = pd.DataFrame(\n", 603 | " vector.toarray(), index=titles, columns=vectorizer.get_feature_names_out()\n", 604 | ")" 605 | ] 606 | }, 607 | { 608 | "cell_type": "code", 609 | "execution_count": 39, 610 | "metadata": {}, 611 | "outputs": [ 612 | { 613 | "data": { 614 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAA6wAAAPeCAYAAAAI0ZeOAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/H5lhTAAAACXBIWXMAAA9hAAAPYQGoP6dpAACxdUlEQVR4nOzdeVhV5fr/8c/eDBuRUXBAc8QBnGcTq2NFYpplWnqaLDIbbDK1jAaNrMiyNJvsq+ZQVvZTszp6zLKsRI6Z84iKGlooiAyKukHYvz+sXVthBymsBbxf17WuS5/9rGffa/U9fL2572cti8PhcAgAAAAAAJOxGh0AAAAAAADFIWEFAAAAAJgSCSsAAAAAwJRIWAEAAAAApkTCCgAAAAAwJRJWAAAAAIApkbACAAAAAEyJhBUAAAAAYEokrAAAAAAAU/I0OoCqZbfRAQAAzhE+fLPRIQAAzpEy62ajQyiTGo1uMTqEMjuV+rHRIVwUVFgBAAAAAKZEwgoAAAAAMCUSVgAAAACAKbGHFQAAAADcsFio8xmFOw8AAAAAMCUSVgAAAACAKdESDAAAAABuWKjzGYY7DwAAAAAwJRJWAAAAAIApkbACAAAAAEyJPawAAAAA4AavtTEOdx4AAAAAYEokrAAAAAAAvf3222rSpIl8fHzUo0cP/fTTT6U675NPPpHFYtHAgQNdxh0Oh8aPH6+wsDDVqFFD0dHR2rNnT5liImEFAAAAADcsFmulO8pqwYIFGj16tCZMmKANGzaoQ4cOiomJUXp6utvzDhw4oLFjx+ryyy8/77NXXnlF06ZN0/Tp07V27VrVrFlTMTExOn36dKnjImEFAAAAgGru9ddf14gRIxQbG6vWrVtr+vTp8vX11fvvv1/iOYWFhbrtttsUHx+vZs2auXzmcDg0depUPfPMM7rhhhvUvn17zZs3T7/99puWLFlS6rhIWAEAAACgirHb7crNzXU57HZ7sXPz8/O1fv16RUdHO8esVquio6OVlJRU4nc8//zzqlOnjoYPH37eZ/v379fhw4dd1gwMDFSPHj3crnkuElYAAAAAqGISEhIUGBjociQkJBQ79+jRoyosLFTdunVdxuvWravDhw8Xe87q1as1a9YszZgxo9jP/zivLGsWh9faAAAAAIAbFovF6BDKLC4uTqNHj3YZs9lsF2Xt48eP64477tCMGTMUGhp6UdYsSZVNWHv37q2OHTtq6tSpRocCAAAAABXKZrOVOkENDQ2Vh4eHjhw54jJ+5MgR1atX77z5KSkpOnDggAYMGOAcKyoqkiR5enoqOTnZed6RI0cUFhbmsmbHjh1LfR1VNmEF8M/Mn79Us2YtVkZGliIimurZZ+9T+/YtjQ4LAKq8268M14i+rVQ70Ec7D2Yr/qON2rI/q9i5fTo30Mj+EWpcx0+eHlYdOHJCs1Yka0lSagVHDaAq8Pb2VpcuXbRy5Urnq2mKioq0cuVKPfTQQ+fNj4iI0NatW13GnnnmGR0/flxvvPGGGjZsKC8vL9WrV08rV650Jqi5ublau3atHnjggVLHRsIKwGnZsh+VkDBT8fEPqkOHlpo79wsNHz5ey5dPV0hIkNHhAUCV1b/bJXpqaAc9+8EGbd6XqdhrWmrOY1fomqeXK/P4+Q9JycnL1zv/2amUw8dVcKZIV3UI06TYbsrMtevH7UeK+QYAF6bqP/pn9OjRuvPOO9W1a1d1795dU6dOVV5enmJjYyVJw4YNU4MGDZSQkCAfHx+1bdvW5fygoCBJchkfNWqUXnjhBbVo0UJNmzbVs88+q/r165/3vlZ3qsSdz8vL07Bhw+Tn56ewsDC99tprLp9nZWVp2LBhCg4Olq+vr6699lqXF9bOmTNHQUFB+uqrrxQZGSk/Pz/17dtXaWlpFX0pgKFmz16iIUNiNHhwtJo3b6T4+JHy8bFp0aKvjQ4NAKq0u/u01IIf9mtR4gHtTTuuZz5Yr1P5hbrpsibFzl+bnKEVG39TStpxpWbkac43e7XrUI66tijfvWQAqq6hQ4dq8uTJGj9+vDp27KhNmzZp+fLlzocmpaamljk/euKJJ/Twww/r3nvvVbdu3XTixAktX75cPj4+pV6jSiSsjz/+uL7//nt9/vnnWrFihVatWqUNGzY4P7/rrrv0888/64svvlBSUpIcDof69eungoIC55yTJ09q8uTJ+uCDD/TDDz8oNTVVY8eONeJyAEPk5xdo+/a9iorq4ByzWq2KiuqojRuTDYwMAKo2Lw+L2jYO1pqdf1ZGHQ5pzY4j6hQeUqo1oiLrqFk9f/20O6O8wgRQDTz00EP65ZdfZLfbtXbtWvXo0cP52apVqzRnzpwSz50zZ85571e1WCx6/vnndfjwYZ0+fVrffPONWrYs21azSt8SfOLECc2aNUsffvihrr76aknS3Llzdckll0iS9uzZoy+++EKJiYmKioqSJM2fP18NGzbUkiVLdPPNN0uSCgoKNH36dIWHh0s6+x/r+eefN+CKAGNkZeWqsLBIISHBLuMhIUHat++QQVEBQNUX7G+Tp4dVR3NPu4wfzT2tZmH+JZ7nV8NTayYPkLenVUUOh8Z/uEGJO9LLO1wAqFCVPmFNSUlRfn6+S/Zfq1YttWrVSpK0c+dOeXp6unweEhKiVq1aaefOnc4xX19fZ7IqSWFhYUpPL/mHvt1uP+/FuzZbvmw27wu+JgAAgL+Td/qMBsSvkK/NU1GRdfX00A46mJGntclUWYGLzWKpEo2plRJ3/ndeXl4uf7dYLHI4HCXOL/5FvO+Vd5hAuQkODpCHh1WZma5PpMzMzFZoaHAJZwEALlTWcbvOFBYpNMB1T1dogI8yck6XcNbZtuFf0vO082COZq3Yrf/+fEj394so73ABoEJV+oQ1PDxcXl5eWrt2rXMsKytLu3fvliRFRkbqzJkzLp9nZmYqOTlZrVu3/sffGxcXp5ycHJcjLu6+f34hgMG8vb3Upk1zJSVtcY4VFRUpKWmzOnVqZWBkAFC1FRQ6tO2XLEVF1nGOWSxSz8g62piSWep1rFaLvD0r/T/tAMBFpW8J9vPz0/Dhw/X4448rJCREderU0dNPPy2r9ewP7BYtWuiGG27QiBEj9N5778nf319PPvmkGjRooBtuuOEff2/xL+KlHRiVW2zsQI0bN0Vt2zZX+/YtNXfu5zp16rQGDYo2OjQAqNLeX7Fbrw7vrq0HsrR5/zHFRreQr81TCxMPSJImD++mw1mnNHnxNknS/f0itPXAMaWm58nby6re7cI08NLGGv/hBjffAuCfoiXYOJU+YZWkV199VSdOnNCAAQPk7++vMWPGKCcnx/n57Nmz9eijj+q6665Tfn6+rrjiCi1btuy8NmCguuvX73IdO5ajadPmKyMjS5GRzTRzZjwtwQBQzpauO6Ra/jaNGthGoQE+2nkwW7FTflRm7tnnZYTV8lXRX3Yq+do89PztnVUv2FenCwq1Ly1XY2au1dJ1PCQPQNVicbjbqIky2m10AACAc4QP32x0CACAc6TMutnoEMokqPn9RodQZtl7pxsdwkVBbRsAAAAAYEpVoiUYAAAAAMqLhTqfYbjzAAAAAABTImEFAAAAAJgSLcEAAAAA4AavtTEOdx4AAAAAYEokrAAAAAAAUyJhBQAAAACYEntYAQAAAMAN9rAahzsPAAAAADAlElYAAAAAgCnREgwAAAAAbtASbBzuPAAAAADAlEhYAQAAAACmRMIKAAAAADAl9rACAAAAgBsWWYwOodqiwgoAAAAAMCUSVgAAAACAKdESDAAAAABu8Fob43DnAQAAAACmRMIKAAAAADAlElYAAAAAgCmxhxUAAAAA3GAPq3G48wAAAAAAUyJhBQAAAACYEi3BAAAAAOAGLcHG4c4DAAAAAEyJhBUAAAAAYEokrAAAAAAAU2IPKwAAAAC4RZ3PKNx5AAAAAIApkbACAAAAAEyJlmAAAAAAcIPX2hiHOw8AAAAAMCUSVgAAAACAKZGwAgAAAABMiT2sAAAAAOAGe1iNw50HAAAAAJgSCSsAAAAAwJRoCQYAAAAANyzU+QzDnQcAAAAAmFKVTVhXrVoli8Wi7Oxso0MBAAAAAPwDtAQDcDF//lLNmrVYGRlZiohoqmefvU/t27c0OiwAqPJuvzJcI/q2Uu1AH+08mK34jzZqy/6sYuf26dxAI/tHqHEdP3l6WHXgyAnNWpGsJUmpFRw1AJQvElYATsuW/aiEhJmKj39QHTq01Ny5X2j48PFavny6QkKCjA4PAKqs/t0u0VNDO+jZDzZo875MxV7TUnMeu0LXPL1cmcft583PycvXO//ZqZTDx1VwpkhXdQjTpNhuysy168ftRwy4AqBq47U2xqnUd76oqEgJCQlq2rSpatSooQ4dOmjhwoXFzv3ll180YMAABQcHq2bNmmrTpo2WLVvm/Hzbtm269tpr5efnp7p16+qOO+7Q0aNHK+pSAFOYPXuJhgyJ0eDB0WrevJHi40fKx8emRYu+Njo0AKjS7u7TUgt+2K9FiQe0N+24nvlgvU7lF+qmy5oUO39tcoZWbPxNKWnHlZqRpznf7NWuQznq2iK0YgMHgHJWqRPWhIQEzZs3T9OnT9f27dv12GOP6fbbb9f3339/3twHH3xQdrtdP/zwg7Zu3apJkybJz89PkpSdna2rrrpKnTp10s8//6zly5fryJEjGjJkSEVfEmCY/PwCbd++V1FRHZxjVqtVUVEdtXFjsoGRAUDV5uVhUdvGwVqz88/KqMMhrdlxRJ3CQ0q1RlRkHTWr56+fdmeUV5gAYIhK2xJst9v10ksv6ZtvvlHPnj0lSc2aNdPq1av13nvv6d5773WZn5qaqsGDB6tdu3bOuX9466231KlTJ7300kvOsffff18NGzbU7t271bIl+/dQ9WVl5aqwsEghIcEu4yEhQdq375BBUQFA1Rfsb5Onh1VHc0+7jB/NPa1mYf4lnudXw1NrJg+Qt6dVRQ6Hxn+4QYk70ss7XKBaslgsRodQbVXahHXv3r06efKkrrnmGpfx/Px8derU6bz5jzzyiB544AGtWLFC0dHRGjx4sNq3by9J2rx5s7777jtnxfWvUlJSik1Y7Xa77HbXPSU2W75sNu8LuSwAAIBSyTt9RgPiV8jX5qmoyLp6emgHHczI09pkqqwAqo5K2xJ84sQJSdLSpUu1adMm57Fjx45i97Hec8892rdvn+644w5t3bpVXbt21Ztvvulca8CAAS7rbNq0SXv27NEVV1xR7PcnJCQoMDDQ5UhIeK/8LhgoZ8HBAfLwsCoz0/WJlJmZ2QoNDS7hLADAhco6bteZwiKFBvi4jIcG+Cgj53QJZ51tG/4lPU87D+Zo1ord+u/Ph3R/v4jyDhcAKlSlTVhbt24tm82m1NRUNW/e3OVo2LBhsec0bNhQ999/vxYvXqwxY8ZoxowZkqTOnTtr+/btatKkyXlr1axZs9i14uLilJOT43LExd1XbtcLlDdvby+1adNcSUlbnGNFRUVKStqsTp1aGRgZAFRtBYUObfslS1GRdZxjFovUM7KONqZklnodq9Uib89K+087AChWpW0J9vf319ixY/XYY4+pqKhIl112mXJycpSYmKiAgAA1btzYZf6oUaN07bXXqmXLlsrKytJ3332nyMhISWcfyDRjxgzdcssteuKJJ1SrVi3t3btXn3zyiWbOnCkPD4/zvt9ms8lms50zSjswKrfY2IEaN26K2rZtrvbtW2ru3M916tRpDRoUbXRoAFClvb9it14d3l1bD2Rp8/5jio1uIV+bpxYmHpAkTR7eTYezTmny4m2SpPv7RWjrgWNKTc+Tt5dVvduFaeCljTX+ww0GXgVQdfFaG+NU2oRVkiZOnKjatWsrISFB+/btU1BQkDp37qynnnpKRUVFLnMLCwv14IMP6tChQwoICFDfvn01ZcoUSVL9+vWVmJiocePGqU+fPrLb7WrcuLH69u0rq5X/40T10a/f5Tp2LEfTps1XRkaWIiObaebMeFqCAaCcLV13SLX8bRo1sI1CA3y082C2Yqf8qMzcs8/LCKvlqyLHn/N9bR56/vbOqhfsq9MFhdqXlqsxM9dq6ToekgegarE4HA7H309D6ew2OgAAwDnCh282OgQAwDlSZt1sdAhl0qjDC0aHUGapm58xOoSLolJXWAEAAACgvFkq76N/Kj3uPAAAAADAlEhYAQAAAACmRMIKAAAAADAl9rACAAAAgBu81sY43HkAAAAAgCmRsAIAAAAATImWYAAAAABwg5Zg43DnAQAAAACmRMIKAAAAADAlElYAAAAAgCmxhxUAAAAA3LBQ5zMMdx4AAAAAYEokrAAAAAAAU6IlGAAAAADc4bU2huHOAwAAAABMiYQVAAAAAGBKJKwAAAAAAFNiDysAAAAAuGFhD6thuPMAAAAAAFMiYQUAAAAAmBItwQAAAADghsViMTqEaouE9SKq0WiC0SEAAM5xKjXe6BAAAMA/REswAAAAAMCUSFgBAAAAAKZESzAAAAAAuGGhzmcY7jwAAAAAwJRIWAEAAAAApkRLMAAAAAC4YbFQ5zMKdx4AAAAAYEokrAAAAAAAUyJhBQAAAACYEntYAQAAAMAdi8XoCKotKqwAAAAAAFMiYQUAAAAAmBItwQAAAADgDmU+w3DrAQAAAACmRMIKAAAAADAlElYAAAAAgCmRsAIAAACAOxZL5Tv+gbfffltNmjSRj4+PevTooZ9++qnEuYsXL1bXrl0VFBSkmjVrqmPHjvrggw9c5tx1112yWCwuR9++fcsUEw9dAgAAAIBqbsGCBRo9erSmT5+uHj16aOrUqYqJiVFycrLq1Klz3vxatWrp6aefVkREhLy9vfWf//xHsbGxqlOnjmJiYpzz+vbtq9mzZzv/brPZyhQXFVZJc+bMUVBQkNFhAAAAAIAhXn/9dY0YMUKxsbFq3bq1pk+fLl9fX73//vvFzu/du7duvPFGRUZGKjw8XI8++qjat2+v1atXu8yz2WyqV6+e8wgODi5TXCSsQDVw37BrtCtxmrJ2z9UPn09U1w7hpTrv5gE9dSr1Y306Y7TL+A19u+nLD+N0aPP/6VTqx2rfunF5hA0A+N38+Ut11VXD1a7dIN188xht2bLb6JCA6sXo9t5/cNjtduXm5rocdru92MvLz8/X+vXrFR0d7RyzWq2Kjo5WUlLS394eh8OhlStXKjk5WVdccYXLZ6tWrVKdOnXUqlUrPfDAA8rMzCzTrSdhBaq4mwZcqknP3qEXpy5Sz/5PacvOX/TFh0+qdkiA2/MaXRKqhGdu0+q1O8/7zNfXpjXrkvVMwsflFTYA4HfLlv2ohISZevDBW/TZZ1MVEdFUw4ePV2ZmttGhATCxhIQEBQYGuhwJCQnFzj169KgKCwtVt25dl/G6devq8OHDJX5HTk6O/Pz85O3trf79++vNN9/UNddc4/y8b9++mjdvnlauXKlJkybp+++/17XXXqvCwsJSX0e5JqxFRUVKSEhQ06ZNVaNGDXXo0EELFy6UdDbTtlgsWrlypbp27SpfX19FRUUpOTnZZY13331X4eHh8vb2VqtWrVw28h44cEAWi0WbNm1yjmVnZ8tisWjVqlXOsS+++EItWrSQj4+PrrzySs2dO1cWi0XZ2dku3/XVV18pMjJSfn5+6tu3r9LS0i76PQEq2iP39Nfsj7/VB//ve+3a86sejpulU6fydefQ3iWeY7VaNGfaQ5r4+kLtT00/7/OPF69WwhuL9e3qreUYOQBAkmbPXqIhQ2I0eHC0mjdvpPj4kfLxsWnRoq+NDg2AicXFxSknJ8fliIuLu6jf4e/vr02bNmndunV68cUXNXr0aJc87N///reuv/56tWvXTgMHDtR//vMfrVu3zmXO3ynXhDUhIUHz5s3T9OnTtX37dj322GO6/fbb9f333zvnPP3003rttdf0888/y9PTU3fffbfzs88++0yPPvqoxowZo23btum+++5TbGysvvvuu1LHsH//ft10000aOHCgNm/erPvuu09PP/30efNOnjypyZMn64MPPtAPP/yg1NRUjR079sJuAGAwLy8PdWrXVN+u3uYcczgc+nb1NnXv3KLE854aNVgZR3M1d8GqCogSAFCS/PwCbd++V1FRHZxjVqtVUVEdtXFjspszAVR3NptNAQEBLkdJDzwKDQ2Vh4eHjhw54jJ+5MgR1atXr8TvsFqtat68uTp27KgxY8bopptuKrGKK0nNmjVTaGio9u7dW+rrKLenBNvtdr300kv65ptv1LNnT2eAq1ev1nvvvad7771XkvTiiy/qX//6lyTpySefVP/+/XX69Gn5+Pho8uTJuuuuuzRy5EhJ0ujRo/W///1PkydP1pVXXlmqON577z21atVKr776qiSpVatW2rZt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1K6pvl29zTnmcDj07ept6t65RYnnPTVqsDKO5mruglUVECUAoCT5+QXavn2voqI6OMesVquiojpq48ZkAyMDgIpRqRPWPXv26IsvvtDMmTN1+eWXq0OHDpo/f75+/fVXl4SzoKBA06dPV9euXdW5c2c99NBDWrlypfPzN998U3FxcbrxxhsVERGht956S0FBQRV/QcBFFlorQJ6eHko/muMynn40R/VqBxV7TlS3VrpraG+NHDejAiIEALiTlZWrwsIihYQEu4yHhATp6NEsg6ICgIpTqV9rs3PnTnl6eqpHjx7OsZCQELVq1Uo7d/65787X11fh4X8+ZCYsLEzp6WfbHHNycnTkyBF1797d+bmHh4e6dOmioqKiEr/bbrfLbre7jDkchbJYPC74ugCj+NX00awpIzVy3AxlZh03OhwAAABTsFocRodQbVXqhLW0vLy8XP5usVjkcFzY/9ElJCQoPj7eZcwjoI28Attd0LrAxXT0WK7OnClUndBAl/E6oYE6nJF93vxmjeuqSaM6WvT+484x6+8vHju+70O1v3K09v9y/p5WAED5CA4OkIeHVZmZrtXUzMxshYYGl3AWAFQdlbolODIyUmfOnNHatWudY5mZmUpOTlbr1q1LtUZgYKDq1q2rdevWOccKCwu1YcMGt+fFxcUpJyfH5fAMKN13AhWloKBQG7fu15W92jrHLBaLruzVRj9t2HPe/OSU39Ql+nH16Puk81j69Xp9n7RDPfo+qUO/ZVZk+ABQ7Xl7e6lNm+ZKStriHCsqKlJS0mZ16tTKwMgAoGJU6gprixYtdMMNN2jEiBF677335O/vryeffFINGjTQDTfcUOp1Hn74YSUkJKh58+aKiIjQm2++qaysLFkslhLPsdlsstlsLmO0A8OMps1cqhmvPaD1W/fp50179dDwa+Xra9O8T7+XJM2c8oB+O5yl8ZM+kd1eoB27D7mcn517UpJcxoMDa6phg1CF1T372/2W4WGSpCMZ2TqS4bpfFgBwYWJjB2rcuClq27a52rdvqblzP9epU6c1aFC00aEBQLmr1AmrJM2ePVuPPvqorrvuOuXn5+uKK67QsmXLzmsDdmfcuHE6fPiwhg0bJg8PD917772KiYmRhwcJKCq/hV/+T6G1AjR+9E2qWztIW3b8ohvueNn5IKaG9UNVVFS2Fvn+13TRjNcfcP79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KZGwAgAAAABMiZZgAAAAAHCDlmDjUGEFAAAAAJgSCSsAAAAAwJRIWAEAAAAApsQeVgAAAABww8PiMDqEaosKKwAAAADAlEhYAQAAAACmREswAAAAALjBa22MQ4UVAAAAAGBKJKwAAAAAAFMiYQUAAAAAmBJ7WAEAAADADfawGocKKwAAAADAlEhYAQAAAACmREswAAAAALhBS7BxqLACAAAAAEyJhBUAAAAAYEokrAAAAAAAU2IPKwAAAAC44cEeVsNQYQUAAAAAmBIJKwAAAADAlGgJBgAAAAA3rBaH0SFUW1RYAQAAAACmRMIKAAAAADAlElYAAAAAgCmxhxUAAAAA3KDKZxzuPQAAAADAlKiwAnAxf/5SzZq1WBkZWYqIaKpnn71P7du3NDosAKjybr8yXCP6tlLtQB/tPJit+I82asv+rGLn9uncQCP7R6hxHT95elh14MgJzVqRrCVJqRUcNQCULxJWAE7Llv2ohISZio9/UB06tNTcuV9o+PDxWr58ukJCgowODwCqrP7dLtFTQzvo2Q82aPO+TMVe01JzHrtC1zy9XJnH7efNz8nL1zv/2amUw8dVcKZIV3UI06TYbsrMtevH7UcMuAKgarNajI6g+ipzS/DChQvVrl071ahRQyEhIYqOjlZeXp6Kior0/PPP65JLLpHNZlPHjh21fPly53kHDhyQxWLRp59+qssvv1w1atRQt27dtHv3bq1bt05du3aVn5+frr32WmVkZLh858yZMxUZGSkfHx9FRETonXfecfl869atuuqqq5wx3XvvvTpx4oTz87vuuksDBw7U5MmTFRYWppCQED344IMqKChwzrHb7Ro7dqwaNGigmjVrqkePHlq1alVZbw9Qqc2evURDhsRo8OBoNW/eSPHxI+XjY9OiRV8bHRoAVGl392mpBT/s16LEA9qbdlzPfLBep/ILddNlTYqdvzY5Qys2/qaUtONKzcjTnG/2atehHHVtEVqxgQNAOStTwpqWlqZbbrlFd999t3bu3KlVq1Zp0KBBcjgceuONN/Taa69p8uTJ2rJli2JiYnT99ddrz549LmtMmDBBzzzzjDZs2CBPT0/deuuteuKJJ/TGG2/oxx9/1N69ezV+/Hjn/Pnz52v8+PF68cUXtXPnTr300kt69tlnNXfuXElSXl6eYmJiFBwcrHXr1un//b//p2+++UYPPfSQy/d+9913SklJ0Xfffae5c+dqzpw5mjNnjvPzhx56SElJSfrkk0+0ZcsW3Xzzzerbt+958QNVVX5+gbZv36uoqA7OMavVqqiojtq4MdnAyACgavPysKht42Ct2flnZdThkNbsOKJO4SGlWiMqso6a1fPXT7sz/n4yAFQiZWoJTktL05kzZzRo0CA1btxYktSuXTtJ0uTJkzVu3Dj9+9//liRNmjRJ3333naZOnaq3337bucbYsWMVExMjSXr00Ud1yy23aOXKlerVq5ckafjw4S6J5IQJE/Taa69p0KBBkqSmTZtqx44deu+993TnnXfqo48+0unTpzVv3jzVrFlTkvTWW29pwIABmjRpkurWrStJCg4O1ltvvSUPDw9FRESof//+WrlypUaMGKHU1FTNnj1bqampql+/vjPO5cuXa/bs2XrppZfKdleBSigrK1eFhUUKCQl2GQ8JCdK+fYcMigoAqr5gf5s8Paw6mnvaZfxo7mk1C/Mv8Ty/Gp5aM3mAvD2tKnI4NP7DDUrckV7e4QJAhSpTwtqhQwddffXVateunWJiYtSnTx/ddNNN8vDw0G+//eZMOv/Qq1cvbd682WWsffv2zj//kUz+kfT+MZaefvaHbV5enlJSUjR8+HCNGDHCOefMmTMKDAyUJO3cuVMdOnRwJqt/fG9RUZGSk5Od39GmTRt5eHg454SFhWnr1q2SzrYUFxYWqmVL1wfL2O12hYQU/5tNu90uu911T4nNli+bzbvY+QAAABdT3ukzGhC/Qr42T0VF1tXTQzvoYEae1iZTZQUuNg/2sBqmTAmrh4eHvv76a61Zs0YrVqzQm2++qaefflpff136/W1eXl7OP1sslmLHioqKJMm5D3XGjBnq0aPHebGUxV+/o7jv8fDw0Pr1689b18/Pr9j1EhISFB8f7zI2YcJDeu65h8sUF2AWwcEB8vCwKjPT9YmUmZnZCg0NLuEsAMCFyjpu15nCIoUG+LiMhwb4KCPndAlnnW0b/iU9T5K082COwsP8dX+/CBJWAFVKmR+6ZLFY1KtXL8XHx2vjxo3y9vbWypUrVb9+fSUmJrrMTUxMVOvWrf9xcHXr1lX9+vW1b98+NW/e3OVo2rSpJCkyMlKbN29WXl6ey/darVa1atWqVN/TqVMnFRYWKj09/bzvqVevXrHnxMXFKScnx+WIi7vvH18rYDRvby+1adNcSUlbnGNFRUVKStqsTp1K978lAEDZFRQ6tO2XLEVF1nGOWSxSz8g62piSWep1rFaLvD3L/E87ADC1MlVY165dq5UrV6pPnz6qU6eO1q5dq4yMDEVGRurxxx/XhAkTFB4ero4dO2r27NnatGmT5s+ff0EBxsfH65FHHlFgYKD69u0ru92un3/+WVlZWRo9erRuu+02TZgwQXfeeaeee+45ZWRk6OGHH9Ydd9zhbAf+Oy1bttRtt92mYcOG6bXXXlOnTp2UkZGhlStXqn379urfv/9559hsNtlstnNGaQdG5RYbO1Djxk1R27bN1b59S82d+7lOnTqtQYOijQ4NAKq091fs1qvDu2vrgSxt3n9MsdEt5Gvz1MLEA5KkycO76XDWKU1evE2SdH+/CG09cEyp6Xny9rKqd7swDby0scZ/uMHAqwCqLqvFYXQI1VaZEtaAgAD98MMPmjp1qnJzc9W4cWO99tpruvbaaxUTE6OcnByNGTNG6enpat26tb744gu1aNHiggK855575Ovrq1dffVWPP/64atasqXbt2mnUqFGSJF9fX3311Vd69NFH1a1bN/n6+mrw4MF6/fXXy/Q9s2fP1gsvvKAxY8bo119/VWhoqC699FJdd911FxQ/UJn063e5jh3L0bRp85WRkaXIyGaaOTOelmAAKGdL1x1SLX+bRg1so9AAH+08mK3YKT8qM/fs8zLCavmq6C//Xva1eej52zurXrCvThcUal9arsbMXKul63hIHoCqxeJwOPh1wUWz2+gAAADnCB+++e8nAQAqVMqsm40OoUx+PLzU6BDK7PJ653eJVkZsdAAAAAAAmFKZWoIBAAAAoLqx8lobw1BhBQAAAACYEgkrAAAAAMCUaAkGAAAAADdoCTYOFVYAAAAAgCmRsAIAAAAATImEFQAAAABgSiSsAAAAAOCGtRIe/8Tbb7+tJk2ayMfHRz169NBPP/1U4tzt27dr8ODBatKkiSwWi6ZOnXrBaxaHhBUAAAAAqrkFCxZo9OjRmjBhgjZs2KAOHTooJiZG6enpxc4/efKkmjVrppdffln16tW7KGsWh4QVAAAAAKq5119/XSNGjFBsbKxat26t6dOny9fXV++//36x87t166ZXX31V//73v2Wz2S7KmsUhYQUAAAAANyyWynfY7Xbl5ua6HHa7vdjry8/P1/r1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NG7cOB0/flw///yzQkJC9Prrrys7O1sVK1ZURkaGVq5cqccff1wJCQn66KOPJPEIMgBghBVwI+vXr9dTTz2lKVOmqHfv3ipbtqzjd61atdL333+vTp06qUqVKlq5cqX27NmjMmXKGFcwALipkJAQbdq0ScHBwVq2bJnOnz+vtWvX6r777pMkvf/++3r99ddVpkwZvfDCCzpz5ow+/PBD+fj4KC4uTp6enuwWDABi0yXALdjtduXn5+u5556Tt7e345t5SbLZbLJYLMrJydEXX3yh77//Xnl5eZo4caIaN25sYNUA4L7sdru2bdum0aNHKyEhQaVLl9aKFStUp04dx7rUt99+W2+++aYOHTqk6tWr6/Tp06patarMZjNhFQD+D1OCATdgMplksVh0+vRpWSwWSXJs8nHt55SUFD3zzDP64osvNH/+fMIqABQSu90uk8mkoKAgvfXWWwoMDNSxY8d0+fJlmUwmXbp0SZL07LPPytfXV3v37pUkVatWzfGoMcIqAFxFYAXcwLWJEiVKlNDBgwclXd2F8lr7uXPn9O9//9vxOy8vL2MKBQA3du2LwmsjqCaTSQ888IDGjh2rJk2a6LHHHtP58+dVokQJSVJOTo7MZvMfwim7tgPAfzAlGCjGEhMT5enpqczMTNWqVUt79uzRQw89pN69e+uTTz5x9Bs7dqy++eYbrV+/3rETJQDgzrn+WdabN29Wdna2rFar2rdvL7PZrF27dmnw4MFKSUnR66+/LqvVqvnz5+vMmTPas2ePYzYMAMAZgRUoplauXKm3335bmZmZstlsGjBggJ5//nktXbpUw4cPV0BAgGrUqKG8vDytX79eP/zwg5o3b2502QDg1l5++WUtWLBAJUuW1IkTJ/T4449rxIgRCg4O1s6dO/Xiiy8qNjZWffr0UbNmzTRixAh5eno69hsAADhjzglQDK1Zs0a9evXSk08+qYULF+rpp5/WmDFjFBcXpz59+mjHjh1q2LChLBaLqlatqtjYWMIqABSC67/3nzVrlubNm6fly5crNjZWu3btUmJiot59913t2rVLrVu3VmRkpOP9eNSoUY7dgAmrAHBjjLACxYzdbtfgwYNVoUIFvfHGG0pISNDDDz+sDh06OE0Dvub6aWoAgDvjiy++UNeuXeXt7e1oGzZsmJKSkrR48WLHe298fLy6d++u4OBgzZw5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", 615 | "text/plain": [ 616 | "
" 617 | ] 618 | }, 619 | "metadata": {}, 620 | "output_type": "display_data" 621 | } 622 | ], 623 | "source": [ 624 | "visualize_tfidf(tfidf_df.T)" 625 | ] 626 | }, 627 | { 628 | "cell_type": "code", 629 | "execution_count": null, 630 | "metadata": {}, 631 | "outputs": [], 632 | "source": [] 633 | } 634 | ], 635 | "metadata": { 636 | "kernelspec": { 637 | "display_name": "ai", 638 | "language": "python", 639 | "name": "python3" 640 | }, 641 | "language_info": { 642 | "codemirror_mode": { 643 | "name": "ipython", 644 | "version": 3 645 | }, 646 | "file_extension": ".py", 647 | "mimetype": "text/x-python", 648 | "name": "python", 649 | "nbconvert_exporter": "python", 650 | "pygments_lexer": "ipython3", 651 | "version": "3.12.1" 652 | } 653 | }, 654 | "nbformat": 4, 655 | "nbformat_minor": 2 656 | } 657 | -------------------------------------------------------------------------------- /nn_zero_to_hero/README.md: -------------------------------------------------------------------------------- 1 | # Neural Networks from scratch 2 | 3 | My code for Andrej Karpathy's NN from scratch series (~13 hours) 4 | 5 | ## installations 6 | 7 | ```bash 8 | brew install graphviz 9 | ``` 10 | 11 | ## Building micrograd (currently doing) 12 | 13 | What? A tiny scalar-valued autograd engine and a neural net library on top of it with PyTorch-like API 14 | 15 | ## Source 16 | 17 | - [Neural Networks: Zero to Hero](https://www.youtube.com/playlist?list=PLAqhIrjkxbuWI23v9cThsA9GvCAUhRvKZ) 18 | - [karpathy/nn-zero-to-hero: Neural Networks: Zero to Hero](https://github.com/karpathy/nn-zero-to-hero) 19 | -------------------------------------------------------------------------------- /nn_zero_to_hero/micrograd/derivatives.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import math\n", 10 | "import numpy as np\n", 11 | "import matplotlib.pyplot as plt\n", 12 | "\n", 13 | "%matplotlib inline" 14 | ] 15 | }, 16 | { 17 | "attachments": {}, 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "## Derivative of a function with single input\n" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 2, 27 | "metadata": {}, 28 | "outputs": [ 29 | { 30 | "data": { 31 | "text/plain": [ 32 | "20.0" 33 | ] 34 | }, 35 | "execution_count": 2, 36 | "metadata": {}, 37 | "output_type": "execute_result" 38 | } 39 | ], 40 | "source": [ 41 | "def f(x):\n", 42 | " return 3 * x**2 - 4 * x + 5\n", 43 | "\n", 44 | "\n", 45 | "f(3.0)" 46 | ] 47 | }, 48 | { 49 | "cell_type": "code", 50 | "execution_count": 4, 51 | "metadata": {}, 52 | "outputs": [ 53 | { 54 | "data": { 55 | "image/png": "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", 56 | "text/plain": [ 57 | "
" 58 | ] 59 | }, 60 | "metadata": { 61 | "needs_background": "light" 62 | }, 63 | "output_type": "display_data" 64 | } 65 | ], 66 | "source": [ 67 | "xs = np.arange(-5, 5, 0.25)\n", 68 | "ys = f(xs) # apply f to each element of xs\n", 69 | "plt.plot(xs, ys);" 70 | ] 71 | }, 72 | { 73 | "attachments": {}, 74 | "cell_type": "markdown", 75 | "metadata": {}, 76 | "source": [ 77 | "what is derivative at every point of this function? in class you would do it by hand\n" 78 | ] 79 | }, 80 | { 81 | "cell_type": "code", 82 | "execution_count": 5, 83 | "metadata": {}, 84 | "outputs": [ 85 | { 86 | "data": { 87 | "text/plain": [ 88 | "20.014003000000002" 89 | ] 90 | }, 91 | "execution_count": 5, 92 | "metadata": {}, 93 | "output_type": "execute_result" 94 | } 95 | ], 96 | "source": [ 97 | "h = 0.001\n", 98 | "x = 3.0\n", 99 | "f(x + h) # do you expect function to be greater or less after bumping h\n", 100 | "(f(x + h) - f(x)) / h # function responded in positive direction normalized by run" 101 | ] 102 | }, 103 | { 104 | "attachments": {}, 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "make h very small to converge to right amount\n" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 9, 114 | "metadata": {}, 115 | "outputs": [ 116 | { 117 | "data": { 118 | "text/plain": [ 119 | "14.000001158365194" 120 | ] 121 | }, 122 | "execution_count": 9, 123 | "metadata": {}, 124 | "output_type": "execute_result" 125 | } 126 | ], 127 | "source": [ 128 | "h = 0.0000000001\n", 129 | "x = 3.0\n", 130 | "(f(x + h) - f(x)) / h" 131 | ] 132 | }, 133 | { 134 | "attachments": {}, 135 | "cell_type": "markdown", 136 | "metadata": {}, 137 | "source": [ 138 | "doing this from function\n", 139 | "\n", 140 | "derivative of $f(x) = 3x^2 - 4x + 5 = 6x - 4$\n", 141 | "\n", 142 | "when $x = 3$, $6 \\cdot 3 - 4 = 14$\n" 143 | ] 144 | }, 145 | { 146 | "attachments": {}, 147 | "cell_type": "markdown", 148 | "metadata": {}, 149 | "source": [ 150 | "What if it's a negative number?\n", 151 | "\n", 152 | "looking at function, if we bump it, it'll go down, so it's going to be negative\n" 153 | ] 154 | }, 155 | { 156 | "cell_type": "code", 157 | "execution_count": 14, 158 | "metadata": {}, 159 | "outputs": [ 160 | { 161 | "data": { 162 | "text/plain": [ 163 | "-21.999966293151374" 164 | ] 165 | }, 166 | "execution_count": 14, 167 | "metadata": {}, 168 | "output_type": "execute_result" 169 | } 170 | ], 171 | "source": [ 172 | "h = 0.0000000001\n", 173 | "x = -3.0\n", 174 | "(f(x + h) - f(x)) / h" 175 | ] 176 | }, 177 | { 178 | "attachments": {}, 179 | "cell_type": "markdown", 180 | "metadata": {}, 181 | "source": [ 182 | "What if it's a number where slope is zero?\n", 183 | "\n", 184 | "nudging it doesn't change the value of the function\n" 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": 11, 190 | "metadata": {}, 191 | "outputs": [ 192 | { 193 | "data": { 194 | "text/plain": [ 195 | "0.0" 196 | ] 197 | }, 198 | "execution_count": 11, 199 | "metadata": {}, 200 | "output_type": "execute_result" 201 | } 202 | ], 203 | "source": [ 204 | "h = 0.0000000001\n", 205 | "x = 2 / 3 # slope is zero at this point\n", 206 | "(f(x + h) - f(x)) / h" 207 | ] 208 | }, 209 | { 210 | "cell_type": "code", 211 | "execution_count": 8, 212 | "metadata": {}, 213 | "outputs": [ 214 | { 215 | "data": { 216 | "text/plain": [ 217 | "0.0" 218 | ] 219 | }, 220 | "execution_count": 8, 221 | "metadata": {}, 222 | "output_type": "execute_result" 223 | } 224 | ], 225 | "source": [ 226 | "h = 0.0000000000000001 # floating point arithmetic, represetnation is finite\n", 227 | "x = 3.0\n", 228 | "(f(x + h) - f(x)) / h" 229 | ] 230 | }, 231 | { 232 | "attachments": {}, 233 | "cell_type": "markdown", 234 | "metadata": {}, 235 | "source": [ 236 | "## Derivative of a function with multiple input\n" 237 | ] 238 | }, 239 | { 240 | "cell_type": "code", 241 | "execution_count": 12, 242 | "metadata": {}, 243 | "outputs": [ 244 | { 245 | "name": "stdout", 246 | "output_type": "stream", 247 | "text": [ 248 | "4.0\n" 249 | ] 250 | } 251 | ], 252 | "source": [ 253 | "a = 2.0\n", 254 | "b = -3.0\n", 255 | "c = 10.0\n", 256 | "d = a * b + c\n", 257 | "\n", 258 | "print(d)" 259 | ] 260 | }, 261 | { 262 | "attachments": {}, 263 | "cell_type": "markdown", 264 | "metadata": {}, 265 | "source": [ 266 | "what happens if we bump b?\n", 267 | "\n", 268 | "because b is negative, it's going to go down\n" 269 | ] 270 | }, 271 | { 272 | "cell_type": "code", 273 | "execution_count": 15, 274 | "metadata": {}, 275 | "outputs": [ 276 | { 277 | "name": "stdout", 278 | "output_type": "stream", 279 | "text": [ 280 | "d1 4.0\n", 281 | "d2 3.99999997\n", 282 | "slope -2.999999981767587\n" 283 | ] 284 | } 285 | ], 286 | "source": [ 287 | "h = 0.00000001\n", 288 | "\n", 289 | "# fix inputs at values of interest\n", 290 | "a = 2.0\n", 291 | "b = -3.0\n", 292 | "c = 10.0\n", 293 | "\n", 294 | "d1 = a * b + c\n", 295 | "a += h\n", 296 | "d2 = a * b + c\n", 297 | "\n", 298 | "print(\"d1\", d1)\n", 299 | "print(\"d2\", d2)\n", 300 | "print(\"slope\", (d2 - d1) / h)" 301 | ] 302 | }, 303 | { 304 | "attachments": {}, 305 | "cell_type": "markdown", 306 | "metadata": {}, 307 | "source": [ 308 | "mathmatically:\n", 309 | "\n", 310 | "derivative of d with respect to a gives you b, and b is -3\n" 311 | ] 312 | }, 313 | { 314 | "attachments": {}, 315 | "cell_type": "markdown", 316 | "metadata": {}, 317 | "source": [ 318 | "what happens if we bump b?\n", 319 | "\n", 320 | "because a is positive, we'll be adding more to d.\n", 321 | "\n", 322 | "What is the sensitivity? the slope of the function? it's 2\n" 323 | ] 324 | }, 325 | { 326 | "cell_type": "code", 327 | "execution_count": 16, 328 | "metadata": {}, 329 | "outputs": [ 330 | { 331 | "name": "stdout", 332 | "output_type": "stream", 333 | "text": [ 334 | "d1 4.0\n", 335 | "d2 4.00000002\n", 336 | "slope 1.999999987845058\n" 337 | ] 338 | } 339 | ], 340 | "source": [ 341 | "h = 0.00000001\n", 342 | "\n", 343 | "# fix inputs at values of interest\n", 344 | "a = 2.0\n", 345 | "b = -3.0\n", 346 | "c = 10.0\n", 347 | "\n", 348 | "d1 = a * b + c\n", 349 | "b += h\n", 350 | "d2 = a * b + c\n", 351 | "\n", 352 | "print(\"d1\", d1)\n", 353 | "print(\"d2\", d2)\n", 354 | "print(\"slope\", (d2 - d1) / h)" 355 | ] 356 | }, 357 | { 358 | "cell_type": "code", 359 | "execution_count": 17, 360 | "metadata": {}, 361 | "outputs": [ 362 | { 363 | "name": "stdout", 364 | "output_type": "stream", 365 | "text": [ 366 | "d1 4.0\n", 367 | "d2 4.000000010000001\n", 368 | "slope 1.000000082740371\n" 369 | ] 370 | } 371 | ], 372 | "source": [ 373 | "h = 0.00000001\n", 374 | "\n", 375 | "# fix inputs at values of interest\n", 376 | "a = 2.0\n", 377 | "b = -3.0\n", 378 | "c = 10.0\n", 379 | "\n", 380 | "d1 = a * b + c\n", 381 | "c += h\n", 382 | "d2 = a * b + c\n", 383 | "\n", 384 | "print(\"d1\", d1)\n", 385 | "print(\"d2\", d2)\n", 386 | "print(\"slope\", (d2 - d1) / h)" 387 | ] 388 | }, 389 | { 390 | "cell_type": "code", 391 | "execution_count": null, 392 | "metadata": {}, 393 | "outputs": [], 394 | "source": [] 395 | } 396 | ], 397 | "metadata": { 398 | "kernelspec": { 399 | "display_name": "base", 400 | "language": "python", 401 | "name": "python3" 402 | }, 403 | "language_info": { 404 | "codemirror_mode": { 405 | "name": "ipython", 406 | "version": 3 407 | }, 408 | "file_extension": ".py", 409 | "mimetype": "text/x-python", 410 | "name": "python", 411 | "nbconvert_exporter": "python", 412 | "pygments_lexer": "ipython3", 413 | "version": "3.9.10" 414 | }, 415 | "orig_nbformat": 4, 416 | "vscode": { 417 | "interpreter": { 418 | "hash": "0f1e841692445df6c0f476977380d4c26cc40d52508098a18c340919add514d9" 419 | } 420 | } 421 | }, 422 | "nbformat": 4, 423 | "nbformat_minor": 2 424 | } 425 | -------------------------------------------------------------------------------- /nn_zero_to_hero/micrograd/exercises.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "id": "JnGHatCI51JP" 7 | }, 8 | "source": [ 9 | "# micrograd exercises\n", 10 | "\n", 11 | "1. watch the [micrograd video](https://www.youtube.com/watch?v=VMj-3S1tku0) on YouTube\n", 12 | "2. come back and complete these exercises to level up :)\n" 13 | ] 14 | }, 15 | { 16 | "cell_type": "markdown", 17 | "metadata": { 18 | "id": "OFt6NKOz6iBZ" 19 | }, 20 | "source": [ 21 | "## section 1: derivatives\n" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": null, 27 | "metadata": { 28 | "id": "3Jx9fCXl5xHd" 29 | }, 30 | "outputs": [], 31 | "source": [ 32 | "# here is a mathematical expression that takes 3 inputs and produces one output\n", 33 | "from math import sin, cos\n", 34 | "\n", 35 | "\n", 36 | "def f(a, b, c):\n", 37 | " return -(a**3) + sin(3 * b) - 1.0 / c + b**2.5 - a**0.5\n", 38 | "\n", 39 | "\n", 40 | "print(f(2, 3, 4))" 41 | ] 42 | }, 43 | { 44 | "cell_type": "code", 45 | "execution_count": null, 46 | "metadata": { 47 | "id": "qXaH59eL9zxf" 48 | }, 49 | "outputs": [], 50 | "source": [ 51 | "# write the function df that returns the analytical gradient of f\n", 52 | "# i.e. use your skills from calculus to take the derivative, then implement the formula\n", 53 | "# if you do not calculus then feel free to ask wolframalpha, e.g.:\n", 54 | "# https://www.wolframalpha.com/input?i=d%2Fda%28sin%283*a%29%29%29\n", 55 | "\n", 56 | "\n", 57 | "def gradf(a, b, c):\n", 58 | " return [0, 0, 0] # todo, return [df/da, df/db, df/dc]\n", 59 | "\n", 60 | "\n", 61 | "# expected answer is the list of\n", 62 | "ans = [-12.353553390593273, 10.25699027111255, 0.0625]\n", 63 | "yours = gradf(2, 3, 4)\n", 64 | "for dim in range(3):\n", 65 | " ok = \"OK\" if abs(yours[dim] - ans[dim]) < 1e-5 else \"WRONG!\"\n", 66 | " print(f\"{ok} for dim {dim}: expected {ans[dim]}, yours returns {yours[dim]}\")" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": null, 72 | "metadata": { 73 | "id": "_27n-KTA9Qla" 74 | }, 75 | "outputs": [], 76 | "source": [ 77 | "# now estimate the gradient numerically without any calculus, using\n", 78 | "# the approximation we used in the video.\n", 79 | "# you should not call the function df from the last cell\n", 80 | "\n", 81 | "# -----------\n", 82 | "numerical_grad = [0, 0, 0] # TODO\n", 83 | "# -----------\n", 84 | "\n", 85 | "for dim in range(3):\n", 86 | " ok = \"OK\" if abs(numerical_grad[dim] - ans[dim]) < 1e-5 else \"WRONG!\"\n", 87 | " print(\n", 88 | " f\"{ok} for dim {dim}: expected {ans[dim]}, yours returns {numerical_grad[dim]}\"\n", 89 | " )" 90 | ] 91 | }, 92 | { 93 | "cell_type": "code", 94 | "execution_count": null, 95 | "metadata": { 96 | "id": "BUqsGb5o_h2P" 97 | }, 98 | "outputs": [], 99 | "source": [ 100 | "# there is an alternative formula that provides a much better numerical\n", 101 | "# approximation to the derivative of a function.\n", 102 | "# learn about it here: https://en.wikipedia.org/wiki/Symmetric_derivative\n", 103 | "# implement it. confirm that for the same step size h this version gives a\n", 104 | "# better approximation.\n", 105 | "\n", 106 | "# -----------\n", 107 | "numerical_grad2 = [0, 0, 0] # TODO\n", 108 | "# -----------\n", 109 | "\n", 110 | "for dim in range(3):\n", 111 | " ok = \"OK\" if abs(numerical_grad2[dim] - ans[dim]) < 1e-5 else \"WRONG!\"\n", 112 | " print(\n", 113 | " f\"{ok} for dim {dim}: expected {ans[dim]}, yours returns {numerical_grad2[dim]}\"\n", 114 | " )" 115 | ] 116 | }, 117 | { 118 | "cell_type": "markdown", 119 | "metadata": { 120 | "id": "tklF9s_4AtlI" 121 | }, 122 | "source": [ 123 | "## section 2: support for softmax\n" 124 | ] 125 | }, 126 | { 127 | "cell_type": "code", 128 | "execution_count": null, 129 | "metadata": { 130 | "id": "nAPe_RVrCTeO" 131 | }, 132 | "outputs": [], 133 | "source": [ 134 | "# Value class starter code, with many functions taken out\n", 135 | "from math import exp, log\n", 136 | "\n", 137 | "\n", 138 | "class Value:\n", 139 | " def __init__(self, data, _children=(), _op=\"\", label=\"\"):\n", 140 | " self.data = data\n", 141 | " self.grad = 0.0\n", 142 | " self._backward = lambda: None\n", 143 | " self._prev = set(_children)\n", 144 | " self._op = _op\n", 145 | " self.label = label\n", 146 | "\n", 147 | " def __repr__(self):\n", 148 | " return f\"Value(data={self.data})\"\n", 149 | "\n", 150 | " def __add__(self, other): # exactly as in the video\n", 151 | " other = other if isinstance(other, Value) else Value(other)\n", 152 | " out = Value(self.data + other.data, (self, other), \"+\")\n", 153 | "\n", 154 | " def _backward():\n", 155 | " self.grad += 1.0 * out.grad\n", 156 | " other.grad += 1.0 * out.grad\n", 157 | "\n", 158 | " out._backward = _backward\n", 159 | "\n", 160 | " return out\n", 161 | "\n", 162 | " # ------\n", 163 | " # re-implement all the other functions needed for the exercises below\n", 164 | " # your code here\n", 165 | " # TODO\n", 166 | " # ------\n", 167 | "\n", 168 | " def backward(self): # exactly as in video\n", 169 | " topo = []\n", 170 | " visited = set()\n", 171 | "\n", 172 | " def build_topo(v):\n", 173 | " if v not in visited:\n", 174 | " visited.add(v)\n", 175 | " for child in v._prev:\n", 176 | " build_topo(child)\n", 177 | " topo.append(v)\n", 178 | "\n", 179 | " build_topo(self)\n", 180 | "\n", 181 | " self.grad = 1.0\n", 182 | " for node in reversed(topo):\n", 183 | " node._backward()" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "metadata": { 190 | "id": "VgWvwVQNAvnI" 191 | }, 192 | "outputs": [], 193 | "source": [ 194 | "# without referencing our code/video __too__ much, make this cell work\n", 195 | "# you'll have to implement (in some cases re-implemented) a number of functions\n", 196 | "# of the Value object, similar to what we've seen in the video.\n", 197 | "# instead of the squared error loss this implements the negative log likelihood\n", 198 | "# loss, which is very often used in classification.\n", 199 | "\n", 200 | "# this is the softmax function\n", 201 | "# https://en.wikipedia.org/wiki/Softmax_function\n", 202 | "def softmax(logits):\n", 203 | " counts = [logit.exp() for logit in logits]\n", 204 | " denominator = sum(counts)\n", 205 | " out = [c / denominator for c in counts]\n", 206 | " return out\n", 207 | "\n", 208 | "\n", 209 | "# this is the negative log likelihood loss function, pervasive in classification\n", 210 | "logits = [Value(0.0), Value(3.0), Value(-2.0), Value(1.0)]\n", 211 | "probs = softmax(logits)\n", 212 | "loss = -probs[3].log() # dim 3 acts as the label for this input example\n", 213 | "loss.backward()\n", 214 | "print(loss.data)\n", 215 | "\n", 216 | "ans = [\n", 217 | " 0.041772570515350445,\n", 218 | " 0.8390245074625319,\n", 219 | " 0.005653302662216329,\n", 220 | " -0.8864503806400986,\n", 221 | "]\n", 222 | "for dim in range(4):\n", 223 | " ok = \"OK\" if abs(logits[dim].grad - ans[dim]) < 1e-5 else \"WRONG!\"\n", 224 | " print(f\"{ok} for dim {dim}: expected {ans[dim]}, yours returns {logits[dim].grad}\")" 225 | ] 226 | }, 227 | { 228 | "cell_type": "code", 229 | "execution_count": null, 230 | "metadata": { 231 | "id": "q7ca1SVAGG1S" 232 | }, 233 | "outputs": [], 234 | "source": [ 235 | "# verify the gradient using the torch library\n", 236 | "# torch should give you the exact same gradient\n", 237 | "import torch" 238 | ] 239 | } 240 | ], 241 | "metadata": { 242 | "colab": { 243 | "provenance": [] 244 | }, 245 | "kernelspec": { 246 | "display_name": "Python 3", 247 | "language": "python", 248 | "name": "python3" 249 | }, 250 | "language_info": { 251 | "name": "python", 252 | "version": "3.10.0" 253 | }, 254 | "vscode": { 255 | "interpreter": { 256 | "hash": "50587d438b9934cf2712ee500622f7def3550698a6c70c07f7d3c00dd27cb653" 257 | } 258 | } 259 | }, 260 | "nbformat": 4, 261 | "nbformat_minor": 0 262 | } 263 | -------------------------------------------------------------------------------- /nn_zero_to_hero/micrograd/micrograd.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from viz import draw_dot" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 13, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "Value(data=-8.0)\n", 22 | "{Value(data=-2.0), Value(data=4.0)}\n", 23 | "*\n" 24 | ] 25 | } 26 | ], 27 | "source": [ 28 | "class Value:\n", 29 | " def __init__(self, data, _children=(), _op=\"\", label=\"\"):\n", 30 | " self.data = data\n", 31 | " self.grad = 0.0\n", 32 | " self._prev = set(_children)\n", 33 | " self._op = _op\n", 34 | " self.label = label\n", 35 | "\n", 36 | " def __repr__(self):\n", 37 | " return f\"Value(data={self.data})\"\n", 38 | "\n", 39 | " def __add__(self, other):\n", 40 | " return Value(self.data + other.data, (self, other), \"+\")\n", 41 | "\n", 42 | " def __mul__(self, other):\n", 43 | " return Value(self.data * other.data, (self, other), \"*\")\n", 44 | "\n", 45 | "\n", 46 | "a = Value(2.0, label=\"a\")\n", 47 | "b = Value(-3.0, label=\"b\")\n", 48 | "c = Value(10.0, label=\"c\")\n", 49 | "e = a * b\n", 50 | "e.label = \"e\"\n", 51 | "d = e + c\n", 52 | "d.label = \"d\"\n", 53 | "f = Value(-2.0, label=\"f\")\n", 54 | "L = d * f\n", 55 | "L.label = \"L\"\n", 56 | "\n", 57 | "print(L)\n", 58 | "print(L._prev) # the children of the value\n", 59 | "print(L._op) # the operation that created the value" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 15, 65 | "metadata": {}, 66 | "outputs": [ 67 | { 68 | "data": { 69 | "image/svg+xml": [ 70 | "\n", 71 | "\n", 73 | "\n", 75 | "\n", 76 | "\n", 78 | "\n", 79 | "\n", 80 | "\n", 81 | "\n", 82 | "4465548304\n", 83 | "\n", 84 | "f\n", 85 | "\n", 86 | "data -2.0000\n", 87 | "\n", 88 | "grad 0.0000\n", 89 | "\n", 90 | "\n", 91 | "\n", 92 | "4464374016*\n", 93 | "\n", 94 | "*\n", 95 | "\n", 96 | "\n", 97 | "\n", 98 | "4465548304->4464374016*\n", 99 | "\n", 100 | "\n", 101 | "\n", 102 | "\n", 103 | "\n", 104 | "4465548352\n", 105 | "\n", 106 | "d\n", 107 | "\n", 108 | "data 4.0000\n", 109 | "\n", 110 | "grad 0.0000\n", 111 | "\n", 112 | "\n", 113 | "\n", 114 | "4465548352->4464374016*\n", 115 | "\n", 116 | "\n", 117 | "\n", 118 | "\n", 119 | "\n", 120 | "4465548352+\n", 121 | "\n", 122 | "+\n", 123 | "\n", 124 | "\n", 125 | "\n", 126 | "4465548352+->4465548352\n", 127 | "\n", 128 | "\n", 129 | "\n", 130 | "\n", 131 | "\n", 132 | "4464373920\n", 133 | "\n", 134 | "a\n", 135 | "\n", 136 | "data 2.0000\n", 137 | "\n", 138 | "grad 0.0000\n", 139 | "\n", 140 | "\n", 141 | "\n", 142 | "4464377760*\n", 143 | "\n", 144 | "*\n", 145 | "\n", 146 | "\n", 147 | "\n", 148 | "4464373920->4464377760*\n", 149 | "\n", 150 | "\n", 151 | "\n", 152 | "\n", 153 | "\n", 154 | "4464374016\n", 155 | "\n", 156 | "L\n", 157 | "\n", 158 | "data -8.0000\n", 159 | "\n", 160 | "grad 0.0000\n", 161 | "\n", 162 | "\n", 163 | "\n", 164 | "4464374016*->4464374016\n", 165 | "\n", 166 | "\n", 167 | "\n", 168 | "\n", 169 | "\n", 170 | "4464376128\n", 171 | "\n", 172 | "c\n", 173 | "\n", 174 | "data 10.0000\n", 175 | "\n", 176 | "grad 0.0000\n", 177 | "\n", 178 | "\n", 179 | "\n", 180 | "4464376128->4465548352+\n", 181 | "\n", 182 | "\n", 183 | "\n", 184 | "\n", 185 | "\n", 186 | "4464377760\n", 187 | "\n", 188 | "e\n", 189 | "\n", 190 | "data -6.0000\n", 191 | "\n", 192 | "grad 0.0000\n", 193 | "\n", 194 | "\n", 195 | "\n", 196 | "4464377760->4465548352+\n", 197 | "\n", 198 | "\n", 199 | "\n", 200 | "\n", 201 | "\n", 202 | "4464377760*->4464377760\n", 203 | "\n", 204 | "\n", 205 | "\n", 206 | "\n", 207 | "\n", 208 | "4464374256\n", 209 | "\n", 210 | "b\n", 211 | "\n", 212 | "data -3.0000\n", 213 | "\n", 214 | "grad 0.0000\n", 215 | "\n", 216 | "\n", 217 | "\n", 218 | "4464374256->4464377760*\n", 219 | "\n", 220 | "\n", 221 | "\n", 222 | "\n", 223 | "\n" 224 | ], 225 | "text/plain": [ 226 | "" 227 | ] 228 | }, 229 | "execution_count": 15, 230 | "metadata": {}, 231 | "output_type": "execute_result" 232 | } 233 | ], 234 | "source": [ 235 | "draw_dot(L)" 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": null, 241 | "metadata": {}, 242 | "outputs": [], 243 | "source": [] 244 | } 245 | ], 246 | "metadata": { 247 | "kernelspec": { 248 | "display_name": "base", 249 | "language": "python", 250 | "name": "python3" 251 | }, 252 | "language_info": { 253 | "codemirror_mode": { 254 | "name": "ipython", 255 | "version": 3 256 | }, 257 | "file_extension": ".py", 258 | "mimetype": "text/x-python", 259 | "name": "python", 260 | "nbconvert_exporter": "python", 261 | "pygments_lexer": "ipython3", 262 | "version": "3.9.10" 263 | }, 264 | "orig_nbformat": 4, 265 | "vscode": { 266 | "interpreter": { 267 | "hash": "0f1e841692445df6c0f476977380d4c26cc40d52508098a18c340919add514d9" 268 | } 269 | } 270 | }, 271 | "nbformat": 4, 272 | "nbformat_minor": 2 273 | } 274 | -------------------------------------------------------------------------------- /nn_zero_to_hero/micrograd/micrograd.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthecoder/AI/b77a9c354386bdb681a80f5dd913b4b7ee4f640c/nn_zero_to_hero/micrograd/micrograd.py -------------------------------------------------------------------------------- /nn_zero_to_hero/micrograd/viz.py: -------------------------------------------------------------------------------- 1 | from graphviz import Digraph 2 | 3 | 4 | def trace(root): 5 | # builds a set of all nodes and edges in a graph 6 | nodes, edges = set(), set() 7 | 8 | def build(v): 9 | if v not in nodes: 10 | nodes.add(v) 11 | for child in v._prev: 12 | edges.add((child, v)) 13 | build(child) 14 | 15 | build(root) 16 | return nodes, edges 17 | 18 | 19 | def draw_dot(root): 20 | dot = Digraph(format="svg", graph_attr={"rankdir": "LR"}) # LR = left to right 21 | 22 | nodes, edges = trace(root) 23 | for n in nodes: 24 | uid = str(id(n)) 25 | # for any value in the graph, create a rectangular ('record') node for it 26 | dot.node( 27 | name=uid, 28 | label="{ %s | data %.4f | grad %.4f }" % (n.label, n.data, n.grad), 29 | shape="record", 30 | ) 31 | if n._op: 32 | # if this value is a result of some operation, create an op node for it 33 | dot.node(name=uid + n._op, label=n._op) 34 | # and connect this node to it 35 | dot.edge(uid + n._op, uid) 36 | 37 | for n1, n2 in edges: 38 | # connect n1 to the op node of n2 39 | dot.edge(str(id(n1)), str(id(n2)) + n2._op) 40 | 41 | return dot 42 | -------------------------------------------------------------------------------- /roadmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benthecoder/AI/b77a9c354386bdb681a80f5dd913b4b7ee4f640c/roadmap.png -------------------------------------------------------------------------------- /tensor_puzzles/README.md: -------------------------------------------------------------------------------- 1 | # Tensor Puzzles 2 | 3 | [srush/Tensor-Puzzles: Solve puzzles. Improve your pytorch.](https://github.com/srush/Tensor-Puzzles) 4 | --------------------------------------------------------------------------------