├── .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 | 
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 | " 0 | \n",
103 | " 1 | \n",
104 | "
\n",
105 | " \n",
106 | " \n",
107 | " \n",
108 | " so | \n",
109 | " 0 | \n",
110 | " 1 | \n",
111 | "
\n",
112 | " \n",
113 | " lived | \n",
114 | " 1 | \n",
115 | " 0 | \n",
116 | "
\n",
117 | " \n",
118 | " it | \n",
119 | " 0 | \n",
120 | " 1 | \n",
121 | "
\n",
122 | " \n",
123 | " life | \n",
124 | " 1 | \n",
125 | " 1 | \n",
126 | "
\n",
127 | " \n",
128 | " elses | \n",
129 | " 0 | \n",
130 | " 1 | \n",
131 | "
\n",
132 | " \n",
133 | " time | \n",
134 | " 0 | \n",
135 | " 1 | \n",
136 | "
\n",
137 | " \n",
138 | " well | \n",
139 | " 1 | \n",
140 | " 0 | \n",
141 | "
\n",
142 | " \n",
143 | " long | \n",
144 | " 1 | \n",
145 | " 0 | \n",
146 | "
\n",
147 | " \n",
148 | " if | \n",
149 | " 1 | \n",
150 | " 0 | \n",
151 | "
\n",
152 | " \n",
153 | " living | \n",
154 | " 0 | \n",
155 | " 1 | \n",
156 | "
\n",
157 | " \n",
158 | " your | \n",
159 | " 0 | \n",
160 | " 1 | \n",
161 | "
\n",
162 | " \n",
163 | " is | \n",
164 | " 1 | \n",
165 | " 1 | \n",
166 | "
\n",
167 | " \n",
168 | " limited | \n",
169 | " 0 | \n",
170 | " 1 | \n",
171 | "
\n",
172 | " \n",
173 | " dont | \n",
174 | " 0 | \n",
175 | " 1 | \n",
176 | "
\n",
177 | " \n",
178 | " waste | \n",
179 | " 0 | \n",
180 | " 1 | \n",
181 | "
\n",
182 | " \n",
183 | " someone | \n",
184 | " 0 | \n",
185 | " 1 | \n",
186 | "
\n",
187 | " \n",
188 | " enough | \n",
189 | " 1 | \n",
190 | " 0 | \n",
191 | "
\n",
192 | " \n",
193 | "
\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 | " 0 | \n",
393 | " 1 | \n",
394 | "
\n",
395 | " \n",
396 | " \n",
397 | " \n",
398 | " so | \n",
399 | " 0.000000 | \n",
400 | " 0.014674 | \n",
401 | "
\n",
402 | " \n",
403 | " lived | \n",
404 | " 0.025156 | \n",
405 | " 0.000000 | \n",
406 | "
\n",
407 | " \n",
408 | " it | \n",
409 | " 0.000000 | \n",
410 | " 0.014674 | \n",
411 | "
\n",
412 | " \n",
413 | " life | \n",
414 | " 0.000000 | \n",
415 | " 0.000000 | \n",
416 | "
\n",
417 | " \n",
418 | " elses | \n",
419 | " 0.000000 | \n",
420 | " 0.014674 | \n",
421 | "
\n",
422 | " \n",
423 | " time | \n",
424 | " 0.000000 | \n",
425 | " 0.014674 | \n",
426 | "
\n",
427 | " \n",
428 | " well | \n",
429 | " 0.025156 | \n",
430 | " 0.000000 | \n",
431 | "
\n",
432 | " \n",
433 | " long | \n",
434 | " 0.025156 | \n",
435 | " 0.000000 | \n",
436 | "
\n",
437 | " \n",
438 | " if | \n",
439 | " 0.025156 | \n",
440 | " 0.000000 | \n",
441 | "
\n",
442 | " \n",
443 | " living | \n",
444 | " 0.000000 | \n",
445 | " 0.014674 | \n",
446 | "
\n",
447 | " \n",
448 | " your | \n",
449 | " 0.000000 | \n",
450 | " 0.014674 | \n",
451 | "
\n",
452 | " \n",
453 | " is | \n",
454 | " 0.000000 | \n",
455 | " 0.000000 | \n",
456 | "
\n",
457 | " \n",
458 | " limited | \n",
459 | " 0.000000 | \n",
460 | " 0.014674 | \n",
461 | "
\n",
462 | " \n",
463 | " dont | \n",
464 | " 0.000000 | \n",
465 | " 0.014674 | \n",
466 | "
\n",
467 | " \n",
468 | " waste | \n",
469 | " 0.000000 | \n",
470 | " 0.014674 | \n",
471 | "
\n",
472 | " \n",
473 | " someone | \n",
474 | " 0.000000 | \n",
475 | " 0.014674 | \n",
476 | "
\n",
477 | " \n",
478 | " enough | \n",
479 | " 0.025156 | \n",
480 | " 0.000000 | \n",
481 | "
\n",
482 | " \n",
483 | "
\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": "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Kjk5WVdccYXLZ6tWrVKdOnXUqlUrPfDAA8rMzCzTrSdhBaq4mwZcqknP3qEXpy5Sz/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"
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 |
--------------------------------------------------------------------------------