├── Uebung 3 ├── ff ├── map.png ├── office.png ├── shoes │ ├── p.pddl │ └── d.pddl ├── abc │ ├── abc-p.pddl │ └── abc-d.pddl └── 03-CSPs_Planning.ipynb ├── requirements.txt ├── Uebung 5 ├── daten.csv ├── iris.csv └── 05-ML.ipynb ├── README.md ├── .gitignore ├── Uebung 4 └── 04-reasoning.ipynb ├── Uebung 1 └── 01-agents.ipynb ├── Uebung 0 └── 00-pythonintro.ipynb └── Uebung 2 └── 02-search.ipynb /Uebung 3/ff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofenske/SS-KI1/HEAD/Uebung 3/ff -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib 2 | numpy 3 | scipy 4 | pandas 5 | scikit-learn -------------------------------------------------------------------------------- /Uebung 3/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofenske/SS-KI1/HEAD/Uebung 3/map.png -------------------------------------------------------------------------------- /Uebung 3/office.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofenske/SS-KI1/HEAD/Uebung 3/office.png -------------------------------------------------------------------------------- /Uebung 3/shoes/p.pddl: -------------------------------------------------------------------------------- 1 | (define (problem shoes) 2 | (:domain shoes) 3 | (:objects left right - foot) 4 | (:init todo) 5 | (:goal todo) 6 | ) -------------------------------------------------------------------------------- /Uebung 5/daten.csv: -------------------------------------------------------------------------------- 1 | Name,Alter,Stadt,Beruf 2 | Alice,25,Berlin,Ingenieur 3 | Bob,30,München,Lehrer 4 | Charlie,35,Hamburg,Arzt 5 | Dave,40,Frankfurt,Anwalt 6 | Eve,45,Köln,Designer 7 | Klaus,12,Rostock,INformatiker 8 | -------------------------------------------------------------------------------- /Uebung 3/shoes/d.pddl: -------------------------------------------------------------------------------- 1 | (define (domain shoes) 2 | (:types 3 | foot - object 4 | ) 5 | 6 | (:predicates 7 | (hasSock ?f - foot) 8 | (hasShoe ?f - foot) 9 | ) 10 | 11 | (:action putOnSock 12 | :parameters (?f - foot) 13 | :precondition (not (hasSock ?f)) 14 | :effect (hasSock ?f) 15 | ) 16 | 17 | (:action putOnShoe 18 | :parameters (todo) 19 | :precondition (todo) 20 | :effect (todo) 21 | ) 22 | 23 | ) 24 | -------------------------------------------------------------------------------- /Uebung 3/abc/abc-p.pddl: -------------------------------------------------------------------------------- 1 | (define (problem abc1) 2 | (:domain abc) 3 | (:objects a b c - job 4 | alice bob - agent) 5 | (:init 6 | (at outside alice) 7 | (at outside bob) 8 | (provides water water-tap) 9 | (provides paper paper-stack) 10 | (provides ground-coffee coffee-jar) 11 | (resource-of paper printer) 12 | (resource-of water coffee-machine) 13 | (resource-of ground-coffee coffee-machine) 14 | (hands-free alice) 15 | (hands-free bob)) 16 | (:goal ;TODO) 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SS-KI1 2 | This is the repository for the exercise lecture of the module "Smart Computing"/"Grundlagen der Künstlichen Intelligenz" at the University of Rostock. 3 | There are two ways to execute the material: 4 | 1. Download and execute everything locally on your own laptop. 5 | 2. Execute the material with Binder. 6 | 7 | For the first variant there are the following requirements: 8 | 1. Python and the required packages installed 9 | 2. Jupyter notebook/lab installed 10 | 11 | We would recommend to install all of the above mentioned software via Anaconda (https://www.anaconda.com/). 12 | 13 | Additionally, you can execute the whole repository via Binder (without installing anything): [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/turing-tester95/SS2023-KI1/HEAD) 14 | -------------------------------------------------------------------------------- /Uebung 3/abc/abc-d.pddl: -------------------------------------------------------------------------------- 1 | (define (domain abc) 2 | (:types item location job agent) 3 | (:constants door printer paper-stack coffee-machine water-tap coffee-jar outside - location 4 | water paper ground-coffee coffee - item) 5 | (:predicates 6 | (at ?l ?a) 7 | (has ?i ?l) 8 | (hands-free ?a) 9 | (holds ?i ?a) 10 | (resource-of ?i ?l) 11 | (provides ?i ?l) 12 | (printed ?j) 13 | ) 14 | 15 | (:action print 16 | :parameters (;TODO) 17 | :precondition (;TODO) 18 | :effect (;TODO) 19 | ) 20 | 21 | 22 | (:action get-coffee 23 | :parameters (;TODO) 24 | :precondition (;TODO) 25 | :effect (;TODO) 26 | ) 27 | 28 | (:action fetch 29 | :parameters (;TODO) 30 | :precondition (;TODO) 31 | :effect (;TODO) 32 | ) 33 | 34 | (:action replenish 35 | :parameters (;TODO) 36 | :precondition (;TODO) 37 | :effect (;TODO) 38 | ) 39 | 40 | (:action goto 41 | :parameters (?a - agent ?x ?y - location) 42 | :precondition (and (not (= ?x ?y)) 43 | (at ?x ?a)) 44 | :effect (and (not (at ?x ?a)) 45 | (at ?y ?a))) 46 | ) 47 | 48 | -------------------------------------------------------------------------------- /.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 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 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 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # MacOS 132 | .DS_Store 133 | .idea 134 | *lsg.ipynb 135 | 136 | # project specific 137 | backup 138 | 139 | -------------------------------------------------------------------------------- /Uebung 3/03-CSPs_Planning.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 3. Constraint Satisfaction Probleme und Planung\n", 8 | "\n", 9 | "## 3.1. Kartenfärbungsprobleme als CSP\n", 10 | "Gegeben sei die folgende Karte von Mecklenburg-Vorpommern. \n", 11 | "![map.png](map.png)\n", 12 | "\n", 13 | "* Spezifizieren Sie, analog zur Vorlesung, das zugehörige Kartenfärbungsproblem für 3 Farben als Constraint Satisfaction Problem. Geben Sie für alle Variablen die Domänen und alle notwendigen Bedingungen an.\n", 14 | "\n", 15 | "* Bestimmen Sie eine Lösung für dieses Problem über Suche mit Backtracking! Benutzen Sie dabei die Heuristik Minimum remaining values und geben Sie für jeden Zwischenschritt und jedes Land die möglichen Farben an!" 16 | ] 17 | }, 18 | { 19 | "cell_type": "markdown", 20 | "metadata": {}, 21 | "source": [ 22 | "## 3.2. Ein einfaches Planungs-Problem\n", 23 | "Das Anziehen von Socken und Schuhen ist ein Planungsproblem. Schuhe können nur angezogen werden, wenn die jeweilige Socke schon angezogen ist. In dieser Aufgabe soll das Anziehen von Socken und Schuhen modelliert werden.\n", 24 | "* Zeichnen Sie den partiell geordneten Plan, und alle total geordneten Pläne auf.\n", 25 | "* Modellieren und lösen Sie das Problem mithilfe der Problem-Domain-Definition-Language (PDDL). \n", 26 | " * Folgen Sie dafür dem folgenden Link: http://editor.planning.domains/#\n", 27 | " * Definieren Sie jeweils eine Datei problem.pddl für das Problem und domain.pddl für die Domäne.\n", 28 | " * Führen den Solver aus und lassen Sie sich eine Lösung ausgeben.\n", 29 | "* Alternativ starten Sie ein neues Terminal hier im Jupyter Dashboard und navigieren in den Ordner \"U4\".\n", 30 | "* Den Planer können Sie wie folgt aufrufen: ./ff -o shoes/d.pddl -f shoes/p.pddl. Interpretieren Sie die Ausgabe." 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "## 3.3. Das Office-Planungsproblem\n", 38 | "In dieser Aufgabe soll der typische Arbeitstag der Mitarbeiter am Lehrstuhl MMIS (Kaffee holen, umherlaufen, Dinge ausdrucken) als Planungsproblem modelliert werden. Mitarbeiter können nur Kaffee machen, wenn die Kaffeemaschine mit Kaffee und Wasser befüllt ist. Kaffee kann am Kaffee-Glas geholt werden, Wasser am Wasserhahn. Mitarbeiter können immer nur ein Ding gleichzeitig in der Hand halten. Am Drucker kann nur gedruckt werden, wenn der Drucker mit Papier befüllt ist. Papier gibt es am Papier-Schrank. Das Ziel der Mitarbeiter ist es, alle Printjobs gedruckt zu haben, alle einen Kaffee in der Hand zu halten, und am Ausgang zu stehen.\n", 39 | "\n", 40 | "![office.png](office.png)\n", 41 | "\n", 42 | "Die verfügbare Aktionen der Mitarbeiter sind:\n", 43 | "\n", 44 | "* goto: Ein Mitarbeiter bewegt sich zwischen zwei Positionen. Die beiden Positionen dürfen nicht gleich sein.\n", 45 | "* fetch: Ein Mitarbeiter nimmt Papier, Kaffee oder Wasser, wenn er sich an der jeweigen Location befindet.\n", 46 | "* replenish: Ein Mitarbeiter füllt den Drucker oder die Kaffeemaschine auf. Das geht dann, wenn der die entsprechende Ressource in der Hand hält.\n", 47 | "* get-coffee: Ein Mitarbeiter nimmt sich einen Kaffee, wenn er an der Maschine steht, und diese befüllt ist.\n", 48 | "* print: Ein Mitarbeiter druckt einen Printjob, wenn er am Drucker steht, und der Drucker Papier enthält.\n", 49 | "\n", 50 | "Definieren Sie die entsprechenden Aktionen in der Domänen-Datei abc-d.pddl. Rufen Sie den Planer wie folgt auf: ./ff -o abc/abc-d.pddl -f abc/abc-p1.pddl. Interpretieren Sie die Ausgabe." 51 | ] 52 | }, 53 | { 54 | "cell_type": "markdown", 55 | "metadata": {}, 56 | "source": [] 57 | } 58 | ], 59 | "metadata": { 60 | "kernelspec": { 61 | "display_name": "Python 3 (ipykernel)", 62 | "language": "python", 63 | "name": "python3" 64 | }, 65 | "language_info": { 66 | "codemirror_mode": { 67 | "name": "ipython", 68 | "version": 3 69 | }, 70 | "file_extension": ".py", 71 | "mimetype": "text/x-python", 72 | "name": "python", 73 | "nbconvert_exporter": "python", 74 | "pygments_lexer": "ipython3", 75 | "version": "3.10.2" 76 | } 77 | }, 78 | "nbformat": 4, 79 | "nbformat_minor": 4 80 | } 81 | -------------------------------------------------------------------------------- /Uebung 5/iris.csv: -------------------------------------------------------------------------------- 1 | ,SepalLength,SepalWidth,PetalLength,PetalWidth,Name 2 | 0,5.1,3.5,1.4,0.2,Iris-setosa 3 | 1,4.9,3.0,1.4,0.2,Iris-setosa 4 | 2,4.7,3.2,1.3,0.2,Iris-setosa 5 | 3,4.6,3.1,1.5,0.2,Iris-setosa 6 | 4,5.0,3.6,1.4,0.2,Iris-setosa 7 | 5,5.4,3.9,1.7,0.4,Iris-setosa 8 | 6,4.6,3.4,1.4,0.3,Iris-setosa 9 | 7,5.0,3.4,1.5,0.2,Iris-setosa 10 | 8,4.4,2.9,1.4,0.2,Iris-setosa 11 | 9,4.9,3.1,1.5,0.1,Iris-setosa 12 | 10,5.4,3.7,1.5,0.2,Iris-setosa 13 | 11,4.8,3.4,1.6,0.2,Iris-setosa 14 | 12,4.8,3.0,1.4,0.1,Iris-setosa 15 | 13,4.3,3.0,1.1,0.1,Iris-setosa 16 | 14,5.8,4.0,1.2,0.2,Iris-setosa 17 | 15,5.7,4.4,1.5,0.4,Iris-setosa 18 | 16,5.4,3.9,1.3,0.4,Iris-setosa 19 | 17,5.1,3.5,1.4,0.3,Iris-setosa 20 | 18,5.7,3.8,1.7,0.3,Iris-setosa 21 | 19,5.1,3.8,1.5,0.3,Iris-setosa 22 | 20,5.4,3.4,1.7,0.2,Iris-setosa 23 | 21,5.1,3.7,1.5,0.4,Iris-setosa 24 | 22,4.6,3.6,1.0,0.2,Iris-setosa 25 | 23,5.1,3.3,1.7,0.5,Iris-setosa 26 | 24,4.8,3.4,1.9,0.2,Iris-setosa 27 | 25,5.0,3.0,1.6,0.2,Iris-setosa 28 | 26,5.0,3.4,1.6,0.4,Iris-setosa 29 | 27,5.2,3.5,1.5,0.2,Iris-setosa 30 | 28,5.2,3.4,1.4,0.2,Iris-setosa 31 | 29,4.7,3.2,1.6,0.2,Iris-setosa 32 | 30,4.8,3.1,1.6,0.2,Iris-setosa 33 | 31,5.4,3.4,1.5,0.4,Iris-setosa 34 | 32,5.2,4.1,1.5,0.1,Iris-setosa 35 | 33,5.5,4.2,1.4,0.2,Iris-setosa 36 | 34,4.9,3.1,1.5,0.1,Iris-setosa 37 | 35,5.0,3.2,1.2,0.2,Iris-setosa 38 | 36,5.5,3.5,1.3,0.2,Iris-setosa 39 | 37,4.9,3.1,1.5,0.1,Iris-setosa 40 | 38,4.4,3.0,1.3,0.2,Iris-setosa 41 | 39,5.1,3.4,1.5,0.2,Iris-setosa 42 | 40,5.0,3.5,1.3,0.3,Iris-setosa 43 | 41,4.5,2.3,1.3,0.3,Iris-setosa 44 | 42,4.4,3.2,1.3,0.2,Iris-setosa 45 | 43,5.0,3.5,1.6,0.6,Iris-setosa 46 | 44,5.1,3.8,1.9,0.4,Iris-setosa 47 | 45,4.8,3.0,1.4,0.3,Iris-setosa 48 | 46,5.1,3.8,1.6,0.2,Iris-setosa 49 | 47,4.6,3.2,1.4,0.2,Iris-setosa 50 | 48,5.3,3.7,1.5,0.2,Iris-setosa 51 | 49,5.0,3.3,1.4,0.2,Iris-setosa 52 | 50,7.0,3.2,4.7,1.4,Iris-versicolor 53 | 51,6.4,3.2,4.5,1.5,Iris-versicolor 54 | 52,6.9,3.1,4.9,1.5,Iris-versicolor 55 | 53,5.5,2.3,4.0,1.3,Iris-versicolor 56 | 54,6.5,2.8,4.6,1.5,Iris-versicolor 57 | 55,5.7,2.8,4.5,1.3,Iris-versicolor 58 | 56,6.3,3.3,4.7,1.6,Iris-versicolor 59 | 57,4.9,2.4,3.3,1.0,Iris-versicolor 60 | 58,6.6,2.9,4.6,1.3,Iris-versicolor 61 | 59,5.2,2.7,3.9,1.4,Iris-versicolor 62 | 60,5.0,2.0,3.5,1.0,Iris-versicolor 63 | 61,5.9,3.0,4.2,1.5,Iris-versicolor 64 | 62,6.0,2.2,4.0,1.0,Iris-versicolor 65 | 63,6.1,2.9,4.7,1.4,Iris-versicolor 66 | 64,5.6,2.9,3.6,1.3,Iris-versicolor 67 | 65,6.7,3.1,4.4,1.4,Iris-versicolor 68 | 66,5.6,3.0,4.5,1.5,Iris-versicolor 69 | 67,5.8,2.7,4.1,1.0,Iris-versicolor 70 | 68,6.2,2.2,4.5,1.5,Iris-versicolor 71 | 69,5.6,2.5,3.9,1.1,Iris-versicolor 72 | 70,5.9,3.2,4.8,1.8,Iris-versicolor 73 | 71,6.1,2.8,4.0,1.3,Iris-versicolor 74 | 72,6.3,2.5,4.9,1.5,Iris-versicolor 75 | 73,6.1,2.8,4.7,1.2,Iris-versicolor 76 | 74,6.4,2.9,4.3,1.3,Iris-versicolor 77 | 75,6.6,3.0,4.4,1.4,Iris-versicolor 78 | 76,6.8,2.8,4.8,1.4,Iris-versicolor 79 | 77,6.7,3.0,5.0,1.7,Iris-versicolor 80 | 78,6.0,2.9,4.5,1.5,Iris-versicolor 81 | 79,5.7,2.6,3.5,1.0,Iris-versicolor 82 | 80,5.5,2.4,3.8,1.1,Iris-versicolor 83 | 81,5.5,2.4,3.7,1.0,Iris-versicolor 84 | 82,5.8,2.7,3.9,1.2,Iris-versicolor 85 | 83,6.0,2.7,5.1,1.6,Iris-versicolor 86 | 84,5.4,3.0,4.5,1.5,Iris-versicolor 87 | 85,6.0,3.4,4.5,1.6,Iris-versicolor 88 | 86,6.7,3.1,4.7,1.5,Iris-versicolor 89 | 87,6.3,2.3,4.4,1.3,Iris-versicolor 90 | 88,5.6,3.0,4.1,1.3,Iris-versicolor 91 | 89,5.5,2.5,4.0,1.3,Iris-versicolor 92 | 90,5.5,2.6,4.4,1.2,Iris-versicolor 93 | 91,6.1,3.0,4.6,1.4,Iris-versicolor 94 | 92,5.8,2.6,4.0,1.2,Iris-versicolor 95 | 93,5.0,2.3,3.3,1.0,Iris-versicolor 96 | 94,5.6,2.7,4.2,1.3,Iris-versicolor 97 | 95,5.7,3.0,4.2,1.2,Iris-versicolor 98 | 96,5.7,2.9,4.2,1.3,Iris-versicolor 99 | 97,6.2,2.9,4.3,1.3,Iris-versicolor 100 | 98,5.1,2.5,3.0,1.1,Iris-versicolor 101 | 99,5.7,2.8,4.1,1.3,Iris-versicolor 102 | 100,6.3,3.3,6.0,2.5,Iris-virginica 103 | 101,5.8,2.7,5.1,1.9,Iris-virginica 104 | 102,7.1,3.0,5.9,2.1,Iris-virginica 105 | 103,6.3,2.9,5.6,1.8,Iris-virginica 106 | 104,6.5,3.0,5.8,2.2,Iris-virginica 107 | 105,7.6,3.0,6.6,2.1,Iris-virginica 108 | 106,4.9,2.5,4.5,1.7,Iris-virginica 109 | 107,7.3,2.9,6.3,1.8,Iris-virginica 110 | 108,6.7,2.5,5.8,1.8,Iris-virginica 111 | 109,7.2,3.6,6.1,2.5,Iris-virginica 112 | 110,6.5,3.2,5.1,2.0,Iris-virginica 113 | 111,6.4,2.7,5.3,1.9,Iris-virginica 114 | 112,6.8,3.0,5.5,2.1,Iris-virginica 115 | 113,5.7,2.5,5.0,2.0,Iris-virginica 116 | 114,5.8,2.8,5.1,2.4,Iris-virginica 117 | 115,6.4,3.2,5.3,2.3,Iris-virginica 118 | 116,6.5,3.0,5.5,1.8,Iris-virginica 119 | 117,7.7,3.8,6.7,2.2,Iris-virginica 120 | 118,7.7,2.6,6.9,2.3,Iris-virginica 121 | 119,6.0,2.2,5.0,1.5,Iris-virginica 122 | 120,6.9,3.2,5.7,2.3,Iris-virginica 123 | 121,5.6,2.8,4.9,2.0,Iris-virginica 124 | 122,7.7,2.8,6.7,2.0,Iris-virginica 125 | 123,6.3,2.7,4.9,1.8,Iris-virginica 126 | 124,6.7,3.3,5.7,2.1,Iris-virginica 127 | 125,7.2,3.2,6.0,1.8,Iris-virginica 128 | 126,6.2,2.8,4.8,1.8,Iris-virginica 129 | 127,6.1,3.0,4.9,1.8,Iris-virginica 130 | 128,6.4,2.8,5.6,2.1,Iris-virginica 131 | 129,7.2,3.0,5.8,1.6,Iris-virginica 132 | 130,7.4,2.8,6.1,1.9,Iris-virginica 133 | 131,7.9,3.8,6.4,2.0,Iris-virginica 134 | 132,6.4,2.8,5.6,2.2,Iris-virginica 135 | 133,6.3,2.8,5.1,1.5,Iris-virginica 136 | 134,6.1,2.6,5.6,1.4,Iris-virginica 137 | 135,7.7,3.0,6.1,2.3,Iris-virginica 138 | 136,6.3,3.4,5.6,2.4,Iris-virginica 139 | 137,6.4,3.1,5.5,1.8,Iris-virginica 140 | 138,6.0,3.0,4.8,1.8,Iris-virginica 141 | 139,6.9,3.1,5.4,2.1,Iris-virginica 142 | 140,6.7,3.1,5.6,2.4,Iris-virginica 143 | 141,6.9,3.1,5.1,2.3,Iris-virginica 144 | 142,5.8,2.7,5.1,1.9,Iris-virginica 145 | 143,6.8,3.2,5.9,2.3,Iris-virginica 146 | 144,6.7,3.3,5.7,2.5,Iris-virginica 147 | 145,6.7,3.0,5.2,2.3,Iris-virginica 148 | 146,6.3,2.5,5.0,1.9,Iris-virginica 149 | 147,6.5,3.0,5.2,2.0,Iris-virginica 150 | 148,6.2,3.4,5.4,2.3,Iris-virginica 151 | 149,5.9,3.0,5.1,1.8,Iris-virginica 152 | -------------------------------------------------------------------------------- /Uebung 4/04-reasoning.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 4. Schließen unter Unsicherheit\n", 8 | "## 4.1. Satz von Bayes\n", 9 | "\n", 10 | "Bob möchte gerne wissen, ob es regnet. Dazu schaut er aus dem Fenster, ob die Straße nass ist. Wenn es tatsächlich regnet, ist die Straße mit einer Wahrscheinlichkeit von 95 % nass (in 5 % der Fälle regnet es noch nicht lange genug, damit die Straße für Bob nass aussieht). Wenn es nicht regnet, ist die Straße mit einer Wahrscheinlichkeit von 25 % nass (weil der Nachbar von Bob den Rasensprenger laufen lässt, der auch die Straße trifft). Darüber hinaus weiß Bob, dass es an 40 % aller Tage regnet. Benutzen Sie für die folgenden Aufgaben die folgende Notation:\n", 11 | "\n", 12 | "* $r$: Es regnet.\n", 13 | "* $\\bar{r}$: Es regnet nicht.\n", 14 | "* $n$: Die Straße ist nass.\n", 15 | "\n", 16 | "Berechnen Sie die folgenden Wahrscheinlichkeiten:\n", 17 | "* $p(r) = $\n", 18 | "* $p(\\bar{r}) = $\n", 19 | "* $p(n|r) = $\n", 20 | "* $p(n|\\bar{r}) = $\n", 21 | "* $p(r|n) = $" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": { 27 | "tags": [] 28 | }, 29 | "source": [ 30 | "## 4.2. Hidden-Markov-Modelle\n", 31 | "Für eine Smartwatch soll ein System entwickelt werden, das automatisch erkennt, ob der\n", 32 | "Träger gerade schläft. Die Smartwatch enthält einen Beschleunigungssensor, der entwe-\n", 33 | "der den Wert wenig Bewegung“ oder den Wert viel Bewegung“ zurückliefert. Wenn der\n", 34 | "Träger schläft, ist die Wahrscheinlichkeit für viel Bewegung“ 5 % und die Wahrscheinlichkeit für wenig Bewegung“ 95 %. Wenn der Träger wach ist, ist die Wahrscheinlichkeit\n", 35 | "für viel Bewegung“ 60 % und die Wahrscheinlichkeit für wenig Bewegung“ 40 %. Wenn\n", 36 | "der Träger schläft, ist die Wahrscheinlichkeit, dass er im nächsten Zeitschritt wach ist,\n", 37 | "30 %. Wenn der Träger wach ist, ist die Wahrscheinlichkeit, dass er im nächsten Schritt\n", 38 | "schläft, 20 %. Zu Anfang schläft der Träger mit einer Wahrscheinlichkeit von 50 %.\n", 39 | "\n", 40 | "1. Spezifizieren Sie diesen Prozess als HMM, wobei der Zustand beschreibt, ob die Person schläft oder wach ist. Notieren Sie die Initialzustand und das Beobachtungsmodell. \n", 41 | "\n", 42 | "2. Berechnen Sie, ausgehend von der initialen Wahrscheinlichkeitsverteilung über den Zuständen, ... \n", 43 | " a) die Vorhersage, d.h., Wahrscheinlichkeitsverteilung allein auf Basis des Transitionsmodelles! \n", 44 | "\n", 45 | " b) die Korrektur nach der Beobachtung wenig Bewegung“! \n" 46 | ] 47 | }, 48 | { 49 | "cell_type": "markdown", 50 | "metadata": {}, 51 | "source": [ 52 | "## 4.3. Implementierung\n", 53 | "\n", 54 | "### Aufgabe 1 \n", 55 | "Implementieren Sie zunächst für das oben beschriebene HMM das die Modelle für Transition und Observation, wie den Initalzustand des Systems! " 56 | ] 57 | }, 58 | { 59 | "cell_type": "code", 60 | "execution_count": 1, 61 | "metadata": { 62 | "ExecuteTime": { 63 | "end_time": "2023-06-30T12:08:16.955975Z", 64 | "start_time": "2023-06-30T12:08:16.886967Z" 65 | }, 66 | "tags": [] 67 | }, 68 | "outputs": [], 69 | "source": [ 70 | "import numpy as np" 71 | ] 72 | }, 73 | { 74 | "cell_type": "code", 75 | "execution_count": 2, 76 | "metadata": { 77 | "ExecuteTime": { 78 | "end_time": "2023-06-30T12:11:04.708235Z", 79 | "start_time": "2023-06-30T12:11:04.686898Z" 80 | } 81 | }, 82 | "outputs": [], 83 | "source": [ 84 | "#transition model\n", 85 | "T = \"Implement me!\"\n", 86 | "#prior\n", 87 | "s0 = \"Implement me!\"\n", 88 | "\n", 89 | "#obs model\n", 90 | "O = \"Implement me!\"" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "metadata": {}, 96 | "source": [ 97 | "### Aufgabe 2\n", 98 | "Implementieren Sie eine Funktion `predict(s,T)` die als Parameter eine Prior-Verteilung $p(x_t | y_{1:t})$ als Vektor $s$ und das Transitionsmodell als Matrix `A` bekommt und als Ausgabe die Verteilung $p(x_{t+1} | y_{1:t})$ liefert." 99 | ] 100 | }, 101 | { 102 | "cell_type": "code", 103 | "execution_count": 3, 104 | "metadata": { 105 | "ExecuteTime": { 106 | "end_time": "2023-06-30T12:35:47.771412Z", 107 | "start_time": "2023-06-30T12:35:47.754756Z" 108 | } 109 | }, 110 | "outputs": [], 111 | "source": [ 112 | "def predict(s, T):\n", 113 | " return \"Implement me!\"" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "### Aufgabe 3\n", 121 | "Implementieren Sie eine Funktion `update(s,O,y)` die als Parameter eine Verteilung $p(x_{t+1} | y_{1:t})$ als Vektor $s$, ein Observationsmodell als Matrix `O` und eine Beobachtung $y_{t+1}$ als Integer `y` bekommt und als Ausgabe $p(x_{t+1} | y_{1:t+1})$ liefert." 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 4, 127 | "metadata": { 128 | "ExecuteTime": { 129 | "end_time": "2023-06-30T12:35:49.078538Z", 130 | "start_time": "2023-06-30T12:35:49.063814Z" 131 | } 132 | }, 133 | "outputs": [], 134 | "source": [ 135 | "def update(s, O, y):\n", 136 | " return \"Implement me!\"" 137 | ] 138 | }, 139 | { 140 | "cell_type": "markdown", 141 | "metadata": {}, 142 | "source": [ 143 | "### Aufgabe 4\n", 144 | "Implementieren Sie eine Funktion `filter(s0,Y,T,O)` die als Parameter eine Prior-Verteilung $p(x_0)$ als Vektor `s0`, ein Transitions- und Beobachtungsmodell als Matrizen `T` und `O` und eine Sequenz von Beobachtungen $y_{1:T}$ als Vektor `yy` bekommt und für jedes $t = 1,...,T$ die Verteilung $p(x_t | y_{1:t})$ als Vektor berechnet und als Aufgabe somit eine $T \\times 2$-Matrix liefert." 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": 5, 150 | "metadata": { 151 | "ExecuteTime": { 152 | "end_time": "2023-06-30T12:35:50.991153Z", 153 | "start_time": "2023-06-30T12:35:50.974017Z" 154 | }, 155 | "collapsed": false, 156 | "jupyter": { 157 | "outputs_hidden": false 158 | } 159 | }, 160 | "outputs": [], 161 | "source": [ 162 | "def filter(s0, O, T, yy):\n", 163 | " return \"Implement me!\"" 164 | ] 165 | }, 166 | { 167 | "cell_type": "markdown", 168 | "metadata": {}, 169 | "source": [ 170 | "Testen Sie nun ihre Implementierung mit dem untenstehenden Code." 171 | ] 172 | }, 173 | { 174 | "cell_type": "code", 175 | "execution_count": 6, 176 | "metadata": { 177 | "ExecuteTime": { 178 | "end_time": "2023-06-30T12:37:11.902249Z", 179 | "start_time": "2023-06-30T12:37:11.880352Z" 180 | }, 181 | "collapsed": false, 182 | "jupyter": { 183 | "outputs_hidden": false 184 | } 185 | }, 186 | "outputs": [ 187 | { 188 | "data": { 189 | "text/plain": [ 190 | "[array([[0.5],\n", 191 | " [0.5]]),\n", 192 | " array([[0.33976834],\n", 193 | " [0.66023166]]),\n", 194 | " array([[0.2717807],\n", 195 | " [0.7282193]]),\n", 196 | " array([[0.90265233],\n", 197 | " [0.09734767]])]" 198 | ] 199 | }, 200 | "execution_count": 6, 201 | "metadata": {}, 202 | "output_type": "execute_result" 203 | } 204 | ], 205 | "source": [ 206 | "yy = [\"W\",\"W\",\"V\"]\n", 207 | "ss = filter(s0,O,T,yy)\n", 208 | "ss" 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": null, 214 | "metadata": { 215 | "collapsed": false, 216 | "jupyter": { 217 | "outputs_hidden": false 218 | } 219 | }, 220 | "outputs": [], 221 | "source": [] 222 | } 223 | ], 224 | "metadata": { 225 | "kernelspec": { 226 | "display_name": "Python 3 (ipykernel)", 227 | "language": "python", 228 | "name": "python3" 229 | }, 230 | "language_info": { 231 | "codemirror_mode": { 232 | "name": "ipython", 233 | "version": 3 234 | }, 235 | "file_extension": ".py", 236 | "mimetype": "text/x-python", 237 | "name": "python", 238 | "nbconvert_exporter": "python", 239 | "pygments_lexer": "ipython3", 240 | "version": "3.11.4" 241 | } 242 | }, 243 | "nbformat": 4, 244 | "nbformat_minor": 4 245 | } 246 | -------------------------------------------------------------------------------- /Uebung 5/05-ML.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "id": "38ea447f-f229-4a64-908d-1e632e4c28dc", 6 | "metadata": { 7 | "tags": [] 8 | }, 9 | "source": [ 10 | "# 5. Maschinelles Lernen\n", 11 | "Heute werden wir uns mit dem Thema \"Maschinelles Lernen\" beschäftigen. Dazu schauen wir uns zunächst kurz an, welche Datenstruktur wir in Python verwenden können, um unsere Daten zunächst zu explorieren und vorzubereiten. \n", 12 | "\n", 13 | "Anschließend schauen wir uns ein einfaches Klassifikationsproblem an und probieren verschiedene Klassifikationsalgorithmen aus." 14 | ] 15 | }, 16 | { 17 | "cell_type": "markdown", 18 | "id": "e14f30d8-825f-44fe-be84-770d43ec0b92", 19 | "metadata": {}, 20 | "source": [ 21 | "## 5.1. Pandas\n", 22 | "\n", 23 | "Ein weit verbreitetes Python-Paket für Datenmanagement und -analyse ist `pandas`. Die grundlegende Datenstruktur die dieses Paket bereitstellt ist das Data Frame. Ein Data Frame ist eine Tabelle, die im Prinzip so verwendet werden kann wie eine Tabelle in einer relationalen Datenbank. So können z.B. Zeilen oder Spalten (oder beides) selektiert werden." 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": null, 29 | "id": "0ac6488c-ec63-4957-81b1-b0a4ba205259", 30 | "metadata": { 31 | "tags": [] 32 | }, 33 | "outputs": [], 34 | "source": [ 35 | "import pandas as pd\n", 36 | "\n", 37 | "data = {'Name': ['Alice', 'Bob', 'Charlie', 'Dave', 'Eve'],\n", 38 | " 'Alter': [25, 30, 35, 40, 45],\n", 39 | " 'Stadt': ['Berlin', 'München', 'Hamburg', 'Frankfurt', 'Köln']}\n", 40 | "df = pd.DataFrame(data)\n", 41 | "\n", 42 | "# show complete dataframe\n", 43 | "df" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "id": "f95cff74-b3a3-479a-af29-78c8abdd6be3", 49 | "metadata": {}, 50 | "source": [ 51 | "Wir können uns auch nur die ersten oder letzten Zeilen des DataFrames anschauen." 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": null, 57 | "id": "ce68cd3f-4915-4cab-8fd7-99decdb5567b", 58 | "metadata": { 59 | "tags": [] 60 | }, 61 | "outputs": [], 62 | "source": [ 63 | "print(f\"Die ersten zwei Zeilen: \\n {df.head(2)}\\n\")\n", 64 | "print(f\"Die letzten zwei Zeilen: \\n {df.tail(2)}\\n\")" 65 | ] 66 | }, 67 | { 68 | "cell_type": "markdown", 69 | "id": "9ac44525-a900-4fb6-a8d5-dea38f6ab0a5", 70 | "metadata": {}, 71 | "source": [ 72 | "Weitere Informationen über unser DataFrame können wir uns mit folgenden Methoden holen:" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "id": "e6b8bae2-ded6-4ea6-89c4-6c6b86c444f0", 79 | "metadata": { 80 | "tags": [] 81 | }, 82 | "outputs": [], 83 | "source": [ 84 | "# Anzeigen der Spaltennamen\n", 85 | "print(\"\\nSpaltennamen:\")\n", 86 | "print(df.columns)\n", 87 | "\n", 88 | "# Anzeigen der Informationen zum DataFrame\n", 89 | "print(\"\\nInfo zum DataFrame:\")\n", 90 | "print(df.info())" 91 | ] 92 | }, 93 | { 94 | "cell_type": "markdown", 95 | "id": "a2995d9f-cbb4-40a4-9c56-d4f6be84c735", 96 | "metadata": {}, 97 | "source": [ 98 | "### Zeilen und Spalten auswählen\n", 99 | "\n", 100 | "Mit pandas können einfach bestimmte Zeilen und Spalten aus dem Data Frame ausgewählt werden. Eine Option besteht darin, Spalten über ihren Namen auszuwählen." 101 | ] 102 | }, 103 | { 104 | "cell_type": "code", 105 | "execution_count": null, 106 | "id": "7282e8ae-e038-47ee-ab02-6e9f8ad6ba7d", 107 | "metadata": { 108 | "tags": [] 109 | }, 110 | "outputs": [], 111 | "source": [ 112 | "names = df.loc[:,\"Name\"]\n", 113 | "print(names)" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "id": "44975173-d8b2-49e7-84ed-01062ae42807", 119 | "metadata": {}, 120 | "source": [ 121 | "Der `:` steht für \"Wähle alle Zeilen\". Wenn nur eine einzige Spalte gewählt wird, ist das Ergebnis vom Typ `Series`, ansonsten ist das Ergebnis wieder ein Data Frame. Spalten können auch über ihren Index ausgewählt werden:" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "id": "7bfccccb-c57e-4fe3-a9f6-04e6ebae13fb", 128 | "metadata": { 129 | "tags": [] 130 | }, 131 | "outputs": [], 132 | "source": [ 133 | "mult_columns = df.iloc[:,1:3]\n", 134 | "mult_columns" 135 | ] 136 | }, 137 | { 138 | "cell_type": "markdown", 139 | "id": "5f326750-cc8b-48f6-8e58-32957e23c854", 140 | "metadata": {}, 141 | "source": [ 142 | "Auf Zeilen kann auf die gleiche Art zugegrifen werden. Der folgende Ausdruck liefert beispielsweise die ersten 5 Zeilen:" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "id": "78a58750-f32e-4927-b786-ccb3bb58c0b6", 149 | "metadata": { 150 | "tags": [] 151 | }, 152 | "outputs": [], 153 | "source": [ 154 | "df.iloc[0:4,:]" 155 | ] 156 | }, 157 | { 158 | "cell_type": "code", 159 | "execution_count": null, 160 | "id": "9e0b6ddb-2573-46d9-bbd6-da23b86f1fef", 161 | "metadata": { 162 | "tags": [] 163 | }, 164 | "outputs": [], 165 | "source": [ 166 | "df.loc[0:4,[\"Alter\", \"Stadt\"]]" 167 | ] 168 | }, 169 | { 170 | "cell_type": "markdown", 171 | "id": "359f1e4d-aad1-4841-adfd-b49771b93a0d", 172 | "metadata": {}, 173 | "source": [ 174 | "Eine andere praktische Möglichkeit besteht darin, Zeilen oder Spalten über Bool'sche Ausdrücke auszuwählen. Der folgende Ausdruck liefert z.B. alle Zeilen, bei denen der Wert von `Alter` kleiner als 36 ist." 175 | ] 176 | }, 177 | { 178 | "cell_type": "code", 179 | "execution_count": null, 180 | "id": "dd583ebe-ac41-4b30-b8fd-a23a667a5e34", 181 | "metadata": { 182 | "tags": [] 183 | }, 184 | "outputs": [], 185 | "source": [ 186 | "df[df['Alter'] < 36]" 187 | ] 188 | }, 189 | { 190 | "cell_type": "markdown", 191 | "id": "4bd15c86-3831-4dd9-b79a-9d827e191a6d", 192 | "metadata": {}, 193 | "source": [ 194 | "### Werte Einfügen\n", 195 | "\n", 196 | "Das Einfügen von Werten in ein Data Frame funktioniert genau so. Der folgende Ausdruck setzt alle Werde der Spalte `Sensor_T8_Acceleration_Y`, die kleiner als 0 sind, auf den Wert 0." 197 | ] 198 | }, 199 | { 200 | "cell_type": "code", 201 | "execution_count": null, 202 | "id": "25088c51-bce3-41d8-bed6-f4fd9e199bff", 203 | "metadata": {}, 204 | "outputs": [], 205 | "source": [ 206 | "# Hinzufügen einer neuen Spalte\n", 207 | "df['Beruf'] = ['Ingenieur', 'Lehrer', 'Arzt', 'Anwalt', 'Designer']\n", 208 | "print(\"DataFrame nach Hinzufügen der Spalte 'Beruf':\")\n", 209 | "print(df)\n", 210 | "df.loc[len(df)] = ['Klaus', 12, 'Rostock', 'Informatiker']\n", 211 | "df" 212 | ] 213 | }, 214 | { 215 | "cell_type": "markdown", 216 | "id": "522c5d8a-01ef-49ad-8922-b68a3514504a", 217 | "metadata": {}, 218 | "source": [ 219 | "### Speichern und Laden von DataFrames\n", 220 | "Wir können DataFrames auch in CSV-Dateien speichern oder Daten aus CSV-Dateien in ein DataFrame importieren: " 221 | ] 222 | }, 223 | { 224 | "cell_type": "code", 225 | "execution_count": null, 226 | "id": "7a6fddf0-ab82-4e62-9046-ddb76b9b501d", 227 | "metadata": { 228 | "tags": [] 229 | }, 230 | "outputs": [], 231 | "source": [ 232 | "# Speichern des DataFrames in einer CSV-Datei\n", 233 | "df.to_csv('daten.csv', index=False)\n", 234 | "print(\"\\nDataFrame wurde in 'daten.csv' gespeichert.\")\n", 235 | "\n", 236 | "# Speichern des DataFrames in einer CSV-Datei\n", 237 | "df = pd.read_csv('iris.csv', index_col=0)\n", 238 | "print(f\"\\nEin neues DataFrame wurde aus der Datei 'iris.csv' geladen: \\n {df}\")" 239 | ] 240 | }, 241 | { 242 | "cell_type": "markdown", 243 | "id": "f4333994-d1ac-4d69-a796-c0a5f5b9446b", 244 | "metadata": {}, 245 | "source": [ 246 | "## Maschinelles Lernen\n", 247 | "Für das Maschinelle Lernen in Python stehen Ihnen unterschiedliche Packages zur Verfügung. Eines dieser Packages ist scikit-learn, welches fertige Implementierung von verschiedenen ML-Verfahren anbietet. Im weiteren Verlauf der Übung werden wir zunächst sehen aus welchen Schritten eine klassische ML-Pipeline besteht und wie diese mithilfe von Pandas und scikit-learn umgesetzt werden kann. \n", 248 | "\n", 249 | "Zunächst beginnen wir damit die benötigten Bibliotheken zu importieren und unseren Datensatz aus einer CSV-Date zu laden: \n", 250 | "\n", 251 | "Aufgabe: Importieren Sie die Daten aus der Datei `iris.csv` in eine DataFrame. Schauen sie sich anschließend die ersten paar Zeilen des DataFrame an und lassen Sie sich eine statistische Zusammenfassung aller Spalten generieren. \n", 252 | "\n", 253 | "Zusatz: Überprüfen Sie das DataFrame auf fehlende Werte. Wie können Sie gegebenenfalls Null-Werte ersetzen? " 254 | ] 255 | }, 256 | { 257 | "cell_type": "code", 258 | "execution_count": null, 259 | "id": "fb2e3ba2-b864-4cdf-8d1a-adc395680ddc", 260 | "metadata": { 261 | "tags": [] 262 | }, 263 | "outputs": [], 264 | "source": [ 265 | "# Importieren der erforderlichen Bibliotheken\n", 266 | "from sklearn.model_selection import train_test_split\n", 267 | "from sklearn.tree import DecisionTreeClassifier\n", 268 | "from sklearn.ensemble import RandomForestClassifier\n", 269 | "from sklearn.metrics import classification_report, confusion_matrix\n", 270 | "\n", 271 | "# 1. Importieren von Daten aus einer CSV-Datei\n", 272 | "data = \"Implement me!\"\n", 273 | "\n", 274 | "# 2. Datenexploration\n", 275 | "print(\"Erste paar Zeilen des DataFrames:\")\n", 276 | "print(\"Implement me!\")\n", 277 | "\n", 278 | "print(\"\\nStatistische Zusammenfassung:\")\n", 279 | "print(\"Implement me!\")\n", 280 | "\n", 281 | "print(\"\\nAnzahl der fehlenden Werte pro Spalte:\")\n", 282 | "print(\"Implement me!\")" 283 | ] 284 | }, 285 | { 286 | "cell_type": "markdown", 287 | "id": "ab272d45-0527-4084-ae35-cfae7e6f0538", 288 | "metadata": {}, 289 | "source": [ 290 | "Nachdem wir die ersten beiden Schritte (Importieren der Daten, Exploration der Daten) erledigt haben, müssen wir die Daten für das Training des Klassifikators vorbereiten. \n", 291 | "\n", 292 | "Aufgabe: Was müssen wir nun mit den Daten machen, um sie anschließend für das Training eines Klassifikators verwenden zu können?" 293 | ] 294 | }, 295 | { 296 | "cell_type": "code", 297 | "execution_count": null, 298 | "id": "0a40f024-f046-43a8-922b-afc63e236cec", 299 | "metadata": { 300 | "tags": [] 301 | }, 302 | "outputs": [], 303 | "source": [ 304 | "# 3. Datenvorbereitung\n", 305 | "# Aufteilen der Daten in Features und Zielvariable\n", 306 | "X = \"Implement me!\"\n", 307 | "y = \"Implement me!\"" 308 | ] 309 | }, 310 | { 311 | "cell_type": "markdown", 312 | "id": "6b33e0f3-492b-4e5b-a501-395632240f19", 313 | "metadata": {}, 314 | "source": [ 315 | "Nachdem wir nun auch den dritten Schritt, die Datenvorbereitung, erleidgt haben, können schlussendlich unseren Klassifikator trainieren. Wir verwenden hier zunächst den `DecisionTreeClassifier`. Nachdem diesen trainiert haben, lassen wir uns eine Zusammenfassung über die Qualität unseres Klassifikators ausgeben." 316 | ] 317 | }, 318 | { 319 | "cell_type": "code", 320 | "execution_count": null, 321 | "id": "2b9bf049-e64f-43be-a7b1-11c59707018c", 322 | "metadata": {}, 323 | "outputs": [], 324 | "source": [ 325 | "# 4. Training und Evaluation eines Klassifikators\n", 326 | "# Aufteilen der Daten in Trainings- und Testdaten\n", 327 | "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n", 328 | "# Initialisieren des Klassifikators\n", 329 | "classifier = RandomForestClassifier()\n", 330 | "\n", 331 | "# Trainieren des Klassifikators\n", 332 | "classifier.fit(X_train, y_train)\n", 333 | "\n", 334 | "# Vorhersagen für die Testdaten\n", 335 | "y_pred = classifier.predict(X_test)\n", 336 | "\n", 337 | "# Auswertung der Vorhersagen\n", 338 | "cm = confusion_matrix(y_test, y_pred)\n", 339 | "report = classification_report(y_test, y_pred)\n", 340 | "print(f'Confusion matrix: \\n {cm}')\n", 341 | "print(f'Classification report: \\n {report}')" 342 | ] 343 | }, 344 | { 345 | "cell_type": "markdown", 346 | "id": "c67d8b00-6b3d-45fe-be61-745b2afb5c05", 347 | "metadata": {}, 348 | "source": [ 349 | "## Titanic-Challenge\n", 350 | "Programmieren Sie nun einen Klassifikator für die Titanic-Challenge. Dabei soll ihr Klassifikator vorhersagen können, ob ein Passagier überlebt oder nicht. Verwenden Sie dazu den gegebenen Titanic-Datensatz und führen Sie alle Schritte durch, die Sie in dieser Übung kennengelernt haben. Auf welche Accuracy kommen Sie am Ende? " 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": null, 356 | "id": "a32173c2-a921-43d4-be4c-6deb31fe55e8", 357 | "metadata": { 358 | "tags": [] 359 | }, 360 | "outputs": [], 361 | "source": [ 362 | "# Importieren der erforderlichen Bibliotheken\n", 363 | "import pandas as pd\n", 364 | "import numpy as np\n", 365 | "from sklearn.model_selection import train_test_split\n", 366 | "from sklearn.ensemble import RandomForestClassifier\n", 367 | "from sklearn.linear_model import LogisticRegression\n", 368 | "from sklearn.metrics import classification_report, confusion_matrix\n", 369 | "from sklearn.preprocessing import StandardScaler, MinMaxScaler\n", 370 | "import ssl\n", 371 | "\n", 372 | "# SSL-Verifizierung deaktivieren\n", 373 | "ssl._create_default_https_context = ssl._create_unverified_context\n", 374 | "\n", 375 | "# 1. Importieren der Daten\n", 376 | "url = 'https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv'\n", 377 | "data = pd.read_csv(url)\n", 378 | "\n", 379 | "# 2. Datenexploration\n", 380 | "\n", 381 | "print(\"\\nStatistische Zusammenfassung:\")\n", 382 | "print(\"Implement me!\")\n", 383 | "\n", 384 | "print(\"\\nAnzahl der fehlenden Werte pro Spalte:\")\n", 385 | "print(\"Implement me!\")\n", 386 | "\n", 387 | "# 3. Datenvorbereitung\n", 388 | "data = \"Implement me!\"\n", 389 | "\n", 390 | "# 4. Training und Evaluation eines Klassifikators\n", 391 | "# Initialisieren des Klassifikators\n", 392 | "classifier = \"Implement me!\"\n", 393 | "\n", 394 | "# Trainieren des Klassifikators\n", 395 | "\"Implement me!\"\n", 396 | "\n", 397 | "# Vorhersagen für die Testdaten\n", 398 | "y_pred = \"Implement me!\"\n", 399 | "\n", 400 | "# Auswertung der Vorhersagen\n", 401 | "cm = confusion_matrix(y_test, y_pred)\n", 402 | "report = classification_report(y_test, y_pred)\n", 403 | "print(f'Confusion matrix: \\n {cm}')\n", 404 | "print(f'Classification report: \\n {report}')" 405 | ] 406 | } 407 | ], 408 | "metadata": { 409 | "kernelspec": { 410 | "display_name": "Lehre-oZkq39sg", 411 | "language": "python", 412 | "name": "python3" 413 | }, 414 | "language_info": { 415 | "codemirror_mode": { 416 | "name": "ipython", 417 | "version": 3 418 | }, 419 | "file_extension": ".py", 420 | "mimetype": "text/x-python", 421 | "name": "python", 422 | "nbconvert_exporter": "python", 423 | "pygments_lexer": "ipython3", 424 | "version": "3.11.4" 425 | } 426 | }, 427 | "nbformat": 4, 428 | "nbformat_minor": 5 429 | } 430 | -------------------------------------------------------------------------------- /Uebung 1/01-agents.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 1. Intelligente Agenten\n", 8 | "Beantworten Sie die folgenden Fragen!\n", 9 | "## Der Turing-Test\n", 10 | "1. Wie funktioniert der Turing-Test?\n", 11 | "2. Was braucht eine Maschine um den Test zu bestehen?\n", 12 | "3. Was ist der totale Turing-Test?\n", 13 | "4. Was braucht eine Maschine um den totalen Turing-Test zu bestehen?\n", 14 | "5. Welche Probleme existieren beim Turing-Test?" 15 | ] 16 | }, 17 | { 18 | "cell_type": "markdown", 19 | "metadata": {}, 20 | "source": [ 21 | "## Agenten\n", 22 | "1. Was ist ein Agent? Aus welchen Bestandteilen besteht ein Agent?\n", 23 | "2. Welche Arten von Agenten kennen Sie? Wie zeichnen Sich die einzelnen Agententypen aus?\n", 24 | "3. Durch welche Eigenschaften zeichnet sich eine Umgebung aus in der sich ein Agent bewegt?:
" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "## Der Staubsauger als Agent\n", 32 | "Wir werden heute einen Staubsauger-Agenten und die dazugehörige Umgebung bzw. \"Welt\" programmieren." 33 | ] 34 | }, 35 | { 36 | "cell_type": "code", 37 | "execution_count": null, 38 | "metadata": { 39 | "tags": [] 40 | }, 41 | "outputs": [], 42 | "source": [ 43 | "import random" 44 | ] 45 | }, 46 | { 47 | "cell_type": "markdown", 48 | "metadata": {}, 49 | "source": [ 50 | "Zunächst definieren wir uns eine Klasse \"Environment\". Diese Klasse repräsentiert unsere Welt in der sich der Agent bewegen soll." 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": { 57 | "tags": [] 58 | }, 59 | "outputs": [], 60 | "source": [ 61 | "class InvalidActionError(Exception):\n", 62 | " \"\"\"Is triggered, if the agent wants to execute an ivalid action.\"\"\"\n", 63 | " pass\n", 64 | "\n", 65 | "class Environment:\n", 66 | " \"\"\"Class representing an Environment. Other environment classes can\n", 67 | " inherit from this. They typically need to implement:\n", 68 | " percept: Define the percept that an agent sees.\n", 69 | " execute_action: Define the effects of executing an action.\n", 70 | " Also update the agent.performance slot.\n", 71 | " The environment keeps a dict of .loc_status which includes locations and their respective status. \n", 72 | " There is also a list of .agents.\n", 73 | " \"\"\"\n", 74 | "\n", 75 | " def __init__(self):\n", 76 | " \n", 77 | " self.loc_status = {(0, 0): random.choice(['Clean', 'Dirty']),\n", 78 | " (0, 1): random.choice(['Clean', 'Dirty']),\n", 79 | " (1, 0): random.choice(['Clean', 'Dirty']),\n", 80 | " (1, 1): random.choice(['Clean', 'Dirty'])}\n", 81 | " self.agents = []\n", 82 | " self.time = 0\n", 83 | "\n", 84 | "\n", 85 | " def percept(self, agent):\n", 86 | " \"\"\"Returns the agent's location, and the location status (Dirty/Clean).\"\"\"\n", 87 | " return (agent.location, self.loc_status[agent.location])\n", 88 | "\n", 89 | " def execute_action(self, agent, action):\n", 90 | " \"\"\"Change agent's location and/or location's status\"\"\"\n", 91 | " former_location = agent.location\n", 92 | " if action == 'move_left' and agent.location[1] > 0:\n", 93 | " agent.location = (agent.location[0], agent.location[1]-1)\n", 94 | " print(f\"Moving left: {former_location} -> {agent.location}\")\n", 95 | " print(\"World state: \", self.loc_status)\n", 96 | " elif action == 'move_right' and agent.location[1] < 1:\n", 97 | " agent.location = (agent.location[0], agent.location[1]+1)\n", 98 | " print(f\"Moving right: {former_location} -> {agent.location}\")\n", 99 | " print(\"World state: \", self.loc_status)\n", 100 | " elif action == 'move_down' and agent.location[0] < 1:\n", 101 | " agent.location = (agent.location[0]+1, agent.location[1])\n", 102 | " print(f\"Moving down: {former_location} -> {agent.location}\")\n", 103 | " print(\"World state: \", self.loc_status)\n", 104 | " elif action == 'move_up' and agent.location[0] > 0:\n", 105 | " agent.location = (agent.location[0]-1, agent.location[1])\n", 106 | " print(f\"Moving up: {former_location} -> {agent.location}\")\n", 107 | " print(\"World state: \", self.loc_status)\n", 108 | " elif action == 'clean':\n", 109 | " self.loc_status[agent.location] = 'Clean'\n", 110 | " print(\"Cleaning: \", agent.location)\n", 111 | " print(\"World state: \", self.loc_status)\n", 112 | " elif action == 'do_nothing':\n", 113 | " print(\"Doing nothing at location:\", agent.location)\n", 114 | " print(\"World state: \", self.loc_status)\n", 115 | " else:\n", 116 | " error_message = f\"Invalid action '{action}' at position {former_location}!\"\n", 117 | " raise InvalidActionError(error_message)\n", 118 | " \n", 119 | " \n", 120 | "\n", 121 | " def default_location(self, thing):\n", 122 | " \"\"\"Agents start in either location at random.\"\"\"\n", 123 | " return random.choice([(0,0), (0,1), (1,0), (1,1)])\n", 124 | "\n", 125 | " def exogenous_change(self):\n", 126 | " \"\"\"If there is spontaneous change in the world, override this.\"\"\"\n", 127 | " pass\n", 128 | "\n", 129 | " def step(self):\n", 130 | " \"\"\"Run the environment for one time step. If the\n", 131 | " actions and exogenous changes are independent, this method will\n", 132 | " do. If there are interactions between them, you'll need to\n", 133 | " override this method.\"\"\"\n", 134 | " actions = []\n", 135 | " \n", 136 | " for agent in self.agents:\n", 137 | " actions.append(agent.program(self.percept(agent)))\n", 138 | " for (agent, action) in zip(self.agents, actions):\n", 139 | " self.execute_action(agent, action)\n", 140 | " self.exogenous_change()\n", 141 | "\n", 142 | " def run(self, steps=10):\n", 143 | " \"\"\"Run the Environment for given number of time steps.\"\"\"\n", 144 | " print(\"Initial world state: \", self.loc_status)\n", 145 | " for step in range(steps):\n", 146 | " self.time += 1\n", 147 | " self.step()\n", 148 | " if all(value == \"Clean\" for value in self.loc_status.values()):\n", 149 | " break\n", 150 | "\n", 151 | " def add_agent(self, agent, location=None):\n", 152 | " \"\"\"Add a thing to the environment, setting its location. For\n", 153 | " convenience, if thing is an agent program we make a new agent\n", 154 | " for it. (Shouldn't need to override this.)\"\"\"\n", 155 | " if agent in self.agents:\n", 156 | " print(\"Can't add the same agent twice\")\n", 157 | " else:\n", 158 | " agent.location = location if location is not None else self.default_location(agent)\n", 159 | " self.agents.append(agent)\n", 160 | "\n", 161 | " def delete_agent(self, agent):\n", 162 | " \"\"\"Remove agent from the environment.\"\"\"\n", 163 | " try:\n", 164 | " self.agents.remove(agent)\n", 165 | " except ValueError as e:\n", 166 | " print(e)\n", 167 | " print(\" in Environment delete_agent\")\n", 168 | " print(\" Agent to be removed: {} at {}\".format(agent, agent.location))\n", 169 | " print(\" from list: {}\".format([(agent, agent.location) for agent in self.agents]))" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": {}, 175 | "source": [ 176 | "Dann benötigen wir noch eine Klasse für unsere Agenten. Da wir im Laufe der Übung unterschiedliche Agenten implementieren möchten verwenden wir zunächst eine sogenannte \"abstrakte\" Klasse `TraceAgent`, von der unsere späteren konkreten Agent-Klassen erben können." 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": null, 182 | "metadata": { 183 | "tags": [] 184 | }, 185 | "outputs": [], 186 | "source": [ 187 | "class TraceAgent:\n", 188 | " \"\"\"An abstract class of an agent. Concrete agent classes wil inherit from this class and have to provide (at least) a program function.\n", 189 | " Optionally you can also provide an __init__ function if the agent should have internal states.\"\"\"\n", 190 | " def __init__(self):\n", 191 | " self.loc_status = None\n", 192 | " self.location = None\n", 193 | " \n", 194 | " def programm(self):\n", 195 | " pass" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "Hier sehen wir nun eine mögliche Implementierung einer `SimpleAgent`-Klasse. Dabei erbt diese Klasse alle Methoden und Attribute von der abstrakten Klasse `TraceAgent` und überschreibt diese gegebenenfalls mit ihrer eigenen Logik. " 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": null, 208 | "metadata": { 209 | "tags": [] 210 | }, 211 | "outputs": [], 212 | "source": [ 213 | "class SimpleAgent(TraceAgent):\n", 214 | " def program(self, perception):\n", 215 | " self.location, loc_status = perception\n", 216 | " if loc_status == \"Clean\":\n", 217 | " action = \"do_nothing\"\n", 218 | " else:\n", 219 | " action = \"clean\"\n", 220 | " return action" 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "metadata": {}, 226 | "source": [ 227 | "Nun könnne wir uns mit folgender Zelle eine neue Welt erstellen und unseren `SimpleAgent`zu dieser Welt hinzufügen. Anschließend Simulieren wir 10 Zeitschritte in dieser Welt." 228 | ] 229 | }, 230 | { 231 | "cell_type": "code", 232 | "execution_count": null, 233 | "metadata": { 234 | "tags": [] 235 | }, 236 | "outputs": [], 237 | "source": [ 238 | "e = Environment()\n", 239 | "e.add_agent(SimpleAgent())\n", 240 | "e.run(10)" 241 | ] 242 | }, 243 | { 244 | "cell_type": "markdown", 245 | "metadata": {}, 246 | "source": [ 247 | "## Aufgabe 1\n", 248 | "Implementieren Sie die Klasse ReflexAgent als einfachen Reflex-Agenten. Das Verhalten können sie dabei selbst festlegen. Der Agent soll dabei in unserer Welt \"Environment\" funktionieren. Beachten Sie diesen Umstand bei der Festlegung möglicher Aktionen, die der Agent in der genannten Welt ausführen soll. Die Liste der Aktionen ist folgende: \"move_left\", \"move_right\", \"move_up\", \"move_down\", \"do_nothing\" und \"clean\"." 249 | ] 250 | }, 251 | { 252 | "cell_type": "code", 253 | "execution_count": null, 254 | "metadata": { 255 | "tags": [] 256 | }, 257 | "outputs": [], 258 | "source": [ 259 | "class ReflexAgent(TraceAgent):\n", 260 | " def program(self, perception):\n", 261 | " self.location, loc_status = perception\n", 262 | " return \"Implement me!\"\n", 263 | "\n", 264 | "e1 = Environment()\n", 265 | "a1 = ReflexAgent()\n", 266 | "e1.add_agent(a1)\n", 267 | "e1.run()" 268 | ] 269 | }, 270 | { 271 | "cell_type": "markdown", 272 | "metadata": {}, 273 | "source": [ 274 | "## Aufgabe 2\n", 275 | "Implementieren Sie die Klasse \"ModelBasedAgent\" als modellbasierten Agenten. Auch dieser Agent soll in der \"Environment\" funktionieren. " 276 | ] 277 | }, 278 | { 279 | "cell_type": "code", 280 | "execution_count": null, 281 | "metadata": { 282 | "tags": [] 283 | }, 284 | "outputs": [], 285 | "source": [ 286 | "class ModelBasedAgent(TraceAgent):\n", 287 | " def __init__(self):\n", 288 | " self.loc_status = {(0, 0): None,\n", 289 | " (0, 1): None,\n", 290 | " (1, 0): None,\n", 291 | " (1, 1): None}\n", 292 | " self.location = None\n", 293 | " def program(self, perception):\n", 294 | " location, loc_status = perception\n", 295 | " self.location = location\n", 296 | " self.loc_status[self.location] = loc_status\n", 297 | " \n", 298 | " return \"Implement my logic!\"\n", 299 | "\n", 300 | "e2 = Environment()\n", 301 | "a2 = ModelBasedAgent()\n", 302 | "e2.add_agent(a2)\n", 303 | "e2.run()" 304 | ] 305 | }, 306 | { 307 | "cell_type": "markdown", 308 | "metadata": {}, 309 | "source": [ 310 | "## Aufgabe 3\n", 311 | "Erweitern Sie die Implementierung der Environment, sodass es beliebig viele, auf einem 2D-Gitter angeordnete Positionen gibt. Erweitern Sie auch die Implementierung der beiden Agenten entsprechend, sodass diese sinnvoll in der neuen Umgebung agieren können" 312 | ] 313 | }, 314 | { 315 | "cell_type": "code", 316 | "execution_count": null, 317 | "metadata": { 318 | "tags": [] 319 | }, 320 | "outputs": [], 321 | "source": [ 322 | "class ExtendedEnvironment(Environment):\n", 323 | "\n", 324 | " \"\"\"This environment has nxn locations. Each can be Dirty\n", 325 | " or Clean. The agent perceives its location and the location's\n", 326 | " status. This serves as an example of how to implement a simple\n", 327 | " Environment.\"\"\"\n", 328 | "\n", 329 | " def __init__(self, grid_size: int):\n", 330 | " super().__init__()\n", 331 | " \"Implement me!\"\n", 332 | "\n", 333 | " def execute_action(self, agent, action):\n", 334 | " \"\"\"Change agent's location and/or location's status\"\"\"\n", 335 | " \n", 336 | " \"Implement me!\"" 337 | ] 338 | }, 339 | { 340 | "cell_type": "code", 341 | "execution_count": null, 342 | "metadata": { 343 | "tags": [] 344 | }, 345 | "outputs": [], 346 | "source": [ 347 | "class ExtendedModelBasedAgent(TraceAgent):\n", 348 | " def __init__(self, loc_status):\n", 349 | " self.loc_status = \"Implement me!\"\n", 350 | " self.location = None\n", 351 | " def program(self, perception):\n", 352 | " location, loc_status = perception\n", 353 | " self.location = location\n", 354 | " self.loc_status[self.location] = loc_status\n", 355 | " \n", 356 | " return \"Implement my logic!\"\n", 357 | "\n", 358 | "e4 = Environment()\n", 359 | "a4 = ExtendedModelBasedAgent(e4.loc_status)\n", 360 | "e4.add_agent(a4)\n", 361 | "e4.run()" 362 | ] 363 | }, 364 | { 365 | "cell_type": "code", 366 | "execution_count": null, 367 | "metadata": {}, 368 | "outputs": [], 369 | "source": [] 370 | } 371 | ], 372 | "metadata": { 373 | "kernelspec": { 374 | "display_name": "Lehre-oZkq39sg", 375 | "language": "python", 376 | "name": "python3" 377 | }, 378 | "language_info": { 379 | "codemirror_mode": { 380 | "name": "ipython", 381 | "version": 3 382 | }, 383 | "file_extension": ".py", 384 | "mimetype": "text/x-python", 385 | "name": "python", 386 | "nbconvert_exporter": "python", 387 | "pygments_lexer": "ipython3", 388 | "version": "3.11.4" 389 | } 390 | }, 391 | "nbformat": 4, 392 | "nbformat_minor": 4 393 | } 394 | -------------------------------------------------------------------------------- /Uebung 0/00-pythonintro.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Einführung in Python \n", 8 | "\n", 9 | "Dieses Tutorial stützt sich stark auf [dieses Tutorial über Audiosignalverarbeitung](https://github.com/spatialaudio/selected-topics-in-audio-signal-processing-exercises).\n", 10 | "\n", 11 | "Für die Übungen werden wir die sehr beliebte Programmiersprache [Python](https://www.python.org) zusammen mit einigen externen Bibliotheken aus dem [Scientific Python Stack](http://scipy.org) verwenden.\n", 12 | "Für den Anfang können Sie auch einen Blick auf diese werfen:\n", 13 | "\n", 14 | "* [Python Einführung](http://nbviewer.ipython.org/github/mgeier/python-audio/blob/master/intro-python.ipynb) (reines Python, kein NumPy)\n", 15 | "\n", 16 | "* [Simple Signal Processing Example](http://nbviewer.ipython.org/github/mgeier/python-audio/blob/master/simple-signals.ipynb) (ziemlich ähnlich zu den Dingen auf dieser Seite)\n", 17 | "\n", 18 | "Beachten Sie, dass Python nicht die einzige Option für die Art von Aufgaben ist, die wir hier angehen werden.\n", 19 | "Wenn Sie sich für einige Alternativen interessieren, schauen Sie sich [Julia](http://julialang.org/), [R](http://www.r-project.org/), [Octave](http://octave.org/) oder [Scilab](http://www.scilab.org/) an.\n", 20 | "Alle genannten Anwendungen sind Open-Source-Software und es gibt natürlich noch mehr Alternativen (sowohl kostenlose als auch proprietäre).\n", 21 | "\n", 22 | "Die meisten Übungen in diesem Kurs (einschließlich derjenigen, die Sie gerade lesen) werden als [Jupyter (früher bekannt als IPython) notebooks](http://jupyter.org/) präsentiert.\n", 23 | "Sie können [online](http://nbviewer.jupyter.org/github/spatialaudio/communication-acoustics-exercises/blob/master/index.ipynb) angesehen werden, aber es ist viel sinnvoller, sie herunterzuladen und sie lokal mit [Jupyter](http://jupyter.org/) zu öffnen und zu erkunden.\n", 24 | "\n", 25 | "Anweisungen zur Installation finden Sie im Abschnitt [Getting Started](index.ipynb#Getting-Started) auf der Hauptseite.\n", 26 | "\n", 27 | "Um eine Vorstellung davon zu bekommen, worum es bei Jupyter geht, werfen Sie einen Blick auf diese [Jupyter-Einführung](http://nbviewer.jupyter.org/github/mgeier/python-audio/blob/master/intro-jupyter.ipynb)." 28 | ] 29 | }, 30 | { 31 | "cell_type": "markdown", 32 | "metadata": { 33 | "tags": [] 34 | }, 35 | "source": [ 36 | "## Was werden wir heute lernen?\n", 37 | "\n", 38 | "* Grundlagen von Python, Jupyter/IPython, NumPy, SciPy, matplotlib und einigen anderen externen Bibliotheken" 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": { 44 | "tags": [] 45 | }, 46 | "source": [ 47 | "## Notebook Cells\n", 48 | "\n", 49 | "Das Notebook besteht aus sogenannten \"Zellen\", die für normalen Text (siehe oben) oder für Python-Code (siehe unten) verwendet werden können.\n", 50 | "*Code-Zellen* können per Mausklick (oder mit den Pfeiltasten nach oben/unten und *Enter*) ausgewählt werden, der Code kann bearbeitet und dann durch Drücken von *Shift+Enter* oder durch Anklicken der Schaltfläche im oberen Teil der Seite ausgeführt werden.\n", 51 | "\n", 52 | "Seien Sie nicht schüchtern, probieren Sie es aus:" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": null, 58 | "metadata": {}, 59 | "outputs": [], 60 | "source": [ 61 | "50 - 5 * 4 + 12\n", 62 | "a = 10" 63 | ] 64 | }, 65 | { 66 | "cell_type": "markdown", 67 | "metadata": {}, 68 | "source": [ 69 | "Codezellen können aus mehreren Zeilen bestehen (verwenden Sie *Enter* für Zeilenumbrüche).\n", 70 | "Wenn die Codezelle ausgeführt wird, werden alle Zeilen ausgeführt, aber nur der Wert der letzten Zeile wird angezeigt (es sei denn, es gibt keinen Wert anzuzeigen).\n", 71 | "\n", 72 | "Hier ist eine weitere Codezelle, mit der Sie spielen können:" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "metadata": {}, 79 | "outputs": [], 80 | "source": [] 81 | }, 82 | { 83 | "cell_type": "markdown", 84 | "metadata": {}, 85 | "source": [ 86 | "Neue Zellen können durch Drücken der Tasten *a* oder *b* (zum Einfügen *oberhalb* oder *unterhalb* der aktuellen Zelle) oder über das Menü eingefügt werden. Sie sollten auch einen Blick auf \"Hilfe\" -> \"Tastaturkürzel\" werfen.\n", 87 | "\n", 88 | "Durch wiederholtes Drücken der Tastenkombination *Umschalttaste+Eingabetaste* können Sie alle Zellen des Notebooks durchlaufen.\n", 89 | "Alternativ können Sie auch im Menü \"Run\" auf \"Run All Cells\" klicken." 90 | ] 91 | }, 92 | { 93 | "cell_type": "markdown", 94 | "metadata": {}, 95 | "source": [ 96 | "# Basics\n", 97 | "Im Folgenden finden Sie ein Python-Programm, das viele der typischerweise benötigten Operationen enthält: Zuweisungen, Arithmetik, logische Operatoren, Ausgabe, Kommentare. Wie Sie sehen, ist Python recht einfach zu lesen. Ich bin sicher, Sie können die Bedeutung jeder Zeile selbst herausfinden." 98 | ] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "execution_count": null, 103 | "metadata": {}, 104 | "outputs": [], 105 | "source": [ 106 | "x = 34 - 23 # A comment.\n", 107 | "y = \"Hello\" # Another one.\n", 108 | "z = 3.45\n", 109 | "if z == 3.45 or y == \"Hello\":\n", 110 | " x = x + 1\n", 111 | " y = y + \" World\"\n", 112 | "print(x)\n", 113 | "print(y)" 114 | ] 115 | }, 116 | { 117 | "cell_type": "markdown", 118 | "metadata": {}, 119 | "source": [ 120 | "## Einrückung\n", 121 | "Python behandelt Blöcke anders als andere Programmiersprachen, die Sie vielleicht kennen, wie Java oder C: Die erste Zeile mit weniger Einrückung steht außerhalb des Blocks, die erste Zeile mit mehr Einrückung beginnt einen verschachtelten Block. Ein Doppelpunkt leitet oft einen neuen Block ein. Im nachstehenden Code wird zum Beispiel die vierte Zeile immer ausgeführt, da sie nicht Teil des Blocks ist:" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": null, 127 | "metadata": {}, 128 | "outputs": [], 129 | "source": [ 130 | "if 17<16:\n", 131 | " print(\"executed conditionally\")\n", 132 | " print(\"also conditionally\")\n", 133 | "print(\"always executed, because not part of the block above\")" 134 | ] 135 | }, 136 | { 137 | "cell_type": "markdown", 138 | "metadata": {}, 139 | "source": [ 140 | "## Referenz-Semantik\n", 141 | "Zuweisungen verhalten sich so, wie Sie es vielleicht aus Java kennen: Für atomare Datentypen funktionieren Zuweisungen \"by value\", für alle anderen Datentypen (z.B. Listen) funktionieren Zuweisungen \"by reference\": Wenn wir ein Objekt manipulieren, wirkt sich das auf alle Referenzen aus." 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": null, 147 | "metadata": {}, 148 | "outputs": [], 149 | "source": [ 150 | "a=17\n", 151 | "b=a #assign the *value* of a to b\n", 152 | "a=12\n", 153 | "print(b) #still 12, because assinment by value\n", 154 | "\n", 155 | "x=[1,2,3] #this is what lists look like\n", 156 | "y=x #assign reference to the list to y\n", 157 | "x.append(4) #manipulate the list by adding a value\n", 158 | "print(y) #y also changed, because of assingment by reference" 159 | ] 160 | }, 161 | { 162 | "cell_type": "markdown", 163 | "metadata": {}, 164 | "source": [ 165 | "# Listen\n", 166 | "Listen werden in eckigen Klammern geschrieben, wie Sie oben gesehen haben. Listen können Werte gemischten Typs enthalten. Listenindizes beginnen mit 0, wie Sie hier sehen können:" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": null, 172 | "metadata": {}, 173 | "outputs": [], 174 | "source": [ 175 | "li = [17,\"Hello\",4.1,\"Bar\",5,6]\n", 176 | "li[3]" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "Sie können auch negative Indizes verwenden, d. h. wir beginnen die Zählung von rechts:" 184 | ] 185 | }, 186 | { 187 | "cell_type": "code", 188 | "execution_count": null, 189 | "metadata": {}, 190 | "outputs": [], 191 | "source": [ 192 | "li[-3]" 193 | ] 194 | }, 195 | { 196 | "cell_type": "markdown", 197 | "metadata": {}, 198 | "source": [ 199 | "Sie können auch Teilmengen von Listen auswählen (\"slicing\"), etwa so:" 200 | ] 201 | }, 202 | { 203 | "cell_type": "code", 204 | "execution_count": null, 205 | "metadata": {}, 206 | "outputs": [], 207 | "source": [ 208 | "li[2:5]" 209 | ] 210 | }, 211 | { 212 | "cell_type": "markdown", 213 | "metadata": {}, 214 | "source": [ 215 | "Beachten Sie, dass das Slicing eine Kopie der Teilliste zurückgibt." 216 | ] 217 | }, 218 | { 219 | "cell_type": "markdown", 220 | "metadata": {}, 221 | "source": [ 222 | "## Einige weitere Listenoperatoren\n", 223 | "Hier sind einige weitere Operatoren, die Sie vielleicht nützlich finden." 224 | ] 225 | }, 226 | { 227 | "cell_type": "code", 228 | "execution_count": null, 229 | "metadata": { 230 | "tags": [] 231 | }, 232 | "outputs": [], 233 | "source": [ 234 | "# Boolean test whether a value is in a list: the in operator\n", 235 | "t = [1,2,3,4]\n", 236 | "2 in t \n", 237 | "\n", 238 | "# Concatenate lists: the + operator\n", 239 | "a = [1,2,3,4]\n", 240 | "b = [5,6,7]\n", 241 | "c = a + b\n", 242 | "c\n", 243 | "\n", 244 | "# Repeat a list n times: the * operator\n", 245 | "a=[1,2,3]\n", 246 | "3*a\n", 247 | "\n", 248 | "# Append lists\n", 249 | "a=[1,2,3]\n", 250 | "a.append(4)\n", 251 | "\n", 252 | "# Index of first occurence\n", 253 | "a.index(2)\n", 254 | "\n", 255 | "# Number of occurences\n", 256 | "a = [1,2,3,2,1,2]\n", 257 | "a.count(2)\n", 258 | "\n", 259 | "# Remove first occurence\n", 260 | "a.remove(2)\n", 261 | "\n", 262 | "# Revese the list\n", 263 | "a.reverse()\n", 264 | "\n", 265 | "# Sort the list\n", 266 | "a.sort()" 267 | ] 268 | }, 269 | { 270 | "cell_type": "markdown", 271 | "metadata": {}, 272 | "source": [ 273 | "*Aufgabe*: Probieren Sie aus was passiert, wenn Sie eine Liste von Strings mit den unten verwendeten Methoden sortieren! Was könnte der Grund für das gezeigte Verhalten sein?" 274 | ] 275 | }, 276 | { 277 | "cell_type": "code", 278 | "execution_count": null, 279 | "metadata": { 280 | "tags": [] 281 | }, 282 | "outputs": [], 283 | "source": [ 284 | "string_list = ['c', 'd', 'a', 'y', 'x']\n", 285 | "# Sorting with sorted()-method\n", 286 | "print('Value which is returned by sorted()-method: ', sorted(string_list))\n", 287 | "print('Original string_list: ', string_list)\n", 288 | "# Sorting with sort()-method\n", 289 | "print('Value which is returned by sort()-method: ', string_list.sort())\n", 290 | "print('Original string_list: ', str(string_list))" 291 | ] 292 | }, 293 | { 294 | "cell_type": "markdown", 295 | "metadata": {}, 296 | "source": [ 297 | "## Dictionaries: Ein Mapping-Typ\n", 298 | "Dictionaries sind in anderen Sprachen als Maps bekannt: Sie speichern eine Zuordnung zwischen einer Menge von Schlüsseln und einer Menge von Werten. Im Folgenden finden Sie ein Beispiel für die Verwendung von Dictionaries:" 299 | ] 300 | }, 301 | { 302 | "cell_type": "code", 303 | "execution_count": null, 304 | "metadata": { 305 | "tags": [] 306 | }, 307 | "outputs": [], 308 | "source": [ 309 | "# Create a new dictionary\n", 310 | "d = {'user':'bozo', \n", 311 | " 'pswd':1234}\n", 312 | "\n", 313 | "# Access the values via a key\n", 314 | "print('Value for key \\'user\\': ', d['user'])\n", 315 | "\n", 316 | "# Add key-value pairs\n", 317 | "d['id'] = 17\n", 318 | "\n", 319 | "# List of keys\n", 320 | "print('List of keys: ', d.keys())\n", 321 | "\n", 322 | "# List of values\n", 323 | "print('List of values: ', d.values())" 324 | ] 325 | }, 326 | { 327 | "cell_type": "markdown", 328 | "metadata": {}, 329 | "source": [ 330 | "# Funktionen\n", 331 | "Funktionen in Python funktionieren so, wie Sie es erwarten würden: Argumente an Funktionen werden durch Zuweisung übergeben, das heißt, übergebene Argumente werden lokalen Namen zugewiesen. Zuweisungen an Argumente können sich nicht auf den Aufrufer auswirken, aber veränderbare Argumente können sich ändern. Hier ist ein Beispiel für die Definition und den Aufruf einer Funktion:\n" 332 | ] 333 | }, 334 | { 335 | "cell_type": "code", 336 | "execution_count": null, 337 | "metadata": { 338 | "tags": [] 339 | }, 340 | "outputs": [], 341 | "source": [ 342 | "def myfun(x: int, y: float):\n", 343 | " print(\"The function is executed.\")\n", 344 | " y[0]=8 # This changes the list that y points to\n", 345 | " return(y[1]+x)\n", 346 | "\n", 347 | "mylist = [1,2,3]\n", 348 | "result=myfun(17,mylist)\n", 349 | "print(\"Function returned: \",result)\n", 350 | "print(\"List is now: \",mylist)" 351 | ] 352 | }, 353 | { 354 | "cell_type": "markdown", 355 | "metadata": {}, 356 | "source": [ 357 | "Beachten Sie, das `x: int` eine sogenannter *Type-Hint* ist. Das bedeutet, dass das `int` nach dem `:` vom Python-Interpreter nicht beachtet wird. Diese Art von Anotation soll also dem Entwickler nur ein Hinweis darauf geben, welchen Datentypen für welchen Parameter er erwarten kann, erzwingt aber keine Typsicherheit in Python!" 358 | ] 359 | }, 360 | { 361 | "cell_type": "markdown", 362 | "metadata": {}, 363 | "source": [ 364 | "## Optionale Argumente\n", 365 | "Wir können Standardwerte für Argumente definieren, die nicht übergeben werden müssen:" 366 | ] 367 | }, 368 | { 369 | "cell_type": "code", 370 | "execution_count": null, 371 | "metadata": {}, 372 | "outputs": [], 373 | "source": [ 374 | "def func(a, b, c=10, d=100):\n", 375 | " print(a, b, c, d)\n", 376 | "type(func(b=1,a=2))\n", 377 | "myfun(1,2)" 378 | ] 379 | }, 380 | { 381 | "cell_type": "markdown", 382 | "metadata": {}, 383 | "source": [ 384 | "Einige weitere Fakten über Funktionen:\n", 385 | "* Alle Funktionen in Python haben einen Rückgabewert, Funktionen ohne Rückgabewert haben den speziellen Rückgabewert `None` (wie z. B. die sort()-Funktion)\n", 386 | "* Es gibt keine Funktionsüberladung in Python.\n", 387 | "* Funktionen können wie jeder andere Datentyp verwendet werden: Sie können Argumente für Funktionen sein, Rückgabewerte von Funktionen, Variablen zugewiesen, usw. Das bedeutet, dass Python eine funktionale Programmiersprache ist, und wir können viele Dinge tun, die Sie aus Haskell kennen und lieben, wie Funktionen höherer Ordnung oder auch Lambda-Ausdrücke!" 388 | ] 389 | }, 390 | { 391 | "cell_type": "markdown", 392 | "metadata": {}, 393 | "source": [ 394 | "# Kontrollstrukturen\n", 395 | "Wir haben oben bereits If-Anweisungen gesehen. For- und While-Schleifen funktionieren auch genau so, wie Sie es erwarten würden, hier sind nur einige Beispiele:" 396 | ] 397 | }, 398 | { 399 | "cell_type": "code", 400 | "execution_count": null, 401 | "metadata": {}, 402 | "outputs": [], 403 | "source": [ 404 | "x = 3\n", 405 | "while x < 10:\n", 406 | " if x > 7:\n", 407 | " x += 2\n", 408 | " continue\n", 409 | " x = x + 1\n", 410 | " print(\"Still in the loop.\")\n", 411 | " if x == 8:\n", 412 | " break\n", 413 | "print(\"Outside of the loop.\")" 414 | ] 415 | }, 416 | { 417 | "cell_type": "code", 418 | "execution_count": null, 419 | "metadata": {}, 420 | "outputs": [], 421 | "source": [ 422 | "for x in range(10):\n", 423 | " if x > 7:\n", 424 | " x += 2\n", 425 | " continue\n", 426 | " x = x + 1\n", 427 | " print(\"Still in the loop.\")\n", 428 | " if x == 8:\n", 429 | " break\n", 430 | "print(\"Outside of the loop.\")" 431 | ] 432 | }, 433 | { 434 | "cell_type": "markdown", 435 | "metadata": {}, 436 | "source": [ 437 | "*Aufgabe*: Implementieren Sie eine Funktion, die prüft, ob eine gegebene Zahl eine Primzahl ist." 438 | ] 439 | }, 440 | { 441 | "cell_type": "code", 442 | "execution_count": null, 443 | "metadata": {}, 444 | "outputs": [], 445 | "source": [ 446 | "def isPrime(n: int) -> int:\n", 447 | " print('Implement me!')\n", 448 | "isPrime(18)" 449 | ] 450 | }, 451 | { 452 | "cell_type": "markdown", 453 | "metadata": {}, 454 | "source": [ 455 | "# List Comprehensions\n", 456 | "Es gibt eine spezielle Syntax für List Comprehensions (die Sie vielleicht aus Haskell kennen)." 457 | ] 458 | }, 459 | { 460 | "cell_type": "code", 461 | "execution_count": null, 462 | "metadata": {}, 463 | "outputs": [], 464 | "source": [ 465 | "evens1 = []\n", 466 | "for x in range(3,100):\n", 467 | " if x%3 == 0:\n", 468 | " evens1.append(x)\n", 469 | "print(evens1)\n", 470 | "\n", 471 | "# List of all multiples of 3 that are <100:\n", 472 | "evens2 = [x for x in range(3,100) if x%3==0]\n", 473 | "print(evens2)" 474 | ] 475 | }, 476 | { 477 | "cell_type": "markdown", 478 | "metadata": {}, 479 | "source": [ 480 | "Aufgabe: Erstellen Sie mit Hilfe eines List Comprehension eine Liste aller Primzahlen < 1000." 481 | ] 482 | }, 483 | { 484 | "cell_type": "code", 485 | "execution_count": null, 486 | "metadata": {}, 487 | "outputs": [], 488 | "source": [ 489 | "primes = ['Implement me!']" 490 | ] 491 | }, 492 | { 493 | "cell_type": "markdown", 494 | "metadata": {}, 495 | "source": [ 496 | "# Numpy\n", 497 | "Numpy ist ein sehr beliebtes Python-Paket, das die Arbeit mit numerischen Arrays erleichtert. Es ist die Grundlage für vieles, was Sie in diesem Kurs sehen werden." 498 | ] 499 | }, 500 | { 501 | "cell_type": "markdown", 502 | "metadata": {}, 503 | "source": [ 504 | "## Importieren von Modulen/Paketen\n", 505 | "\n", 506 | "Um mit numerischen Arrays arbeiten zu können, importieren wir das Paket [NumPy](http://www.numpy.org)." 507 | ] 508 | }, 509 | { 510 | "cell_type": "code", 511 | "execution_count": null, 512 | "metadata": { 513 | "tags": [] 514 | }, 515 | "outputs": [], 516 | "source": [ 517 | "import numpy as np" 518 | ] 519 | }, 520 | { 521 | "cell_type": "markdown", 522 | "metadata": {}, 523 | "source": [ 524 | "Jetzt können wir alle NumPy-Funktionen verwenden (durch Voranstellen von \"`np.`\")." 525 | ] 526 | }, 527 | { 528 | "cell_type": "code", 529 | "execution_count": null, 530 | "metadata": {}, 531 | "outputs": [], 532 | "source": [ 533 | "np.zeros(10000)" 534 | ] 535 | }, 536 | { 537 | "cell_type": "markdown", 538 | "metadata": {}, 539 | "source": [ 540 | "## Tabulatorvervollständigung\n", 541 | "\n", 542 | "*Übung:* Tippen Sie \"`np.ze`\" (ohne Anführungszeichen) und drücken Sie dann die *Tab*-Taste ..." 543 | ] 544 | }, 545 | { 546 | "cell_type": "code", 547 | "execution_count": null, 548 | "metadata": {}, 549 | "outputs": [], 550 | "source": [ 551 | "np." 552 | ] 553 | }, 554 | { 555 | "cell_type": "markdown", 556 | "metadata": {}, 557 | "source": [ 558 | "## Array, Vektor, Matrix\n", 559 | "\n", 560 | "Arrays können beliebig viele Dimensionen haben, aber lassen Sie uns für den Moment nur eindimensionale Arrays verwenden.\n", 561 | "Arrays können mit [numpy.array()](http://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html) erstellt werden:" 562 | ] 563 | }, 564 | { 565 | "cell_type": "code", 566 | "execution_count": null, 567 | "metadata": {}, 568 | "outputs": [], 569 | "source": [ 570 | "a = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])" 571 | ] 572 | }, 573 | { 574 | "cell_type": "markdown", 575 | "metadata": {}, 576 | "source": [ 577 | "Beachten Sie, dass das Ergebnis nicht angezeigt wird, wenn Sie einer Variablen einen Wert zuweisen (da die Zuweisung eine *Anweisung* und kein *Ausdruck* ist).\n", 578 | "Um die Daten anzuzeigen, schreiben Sie den Variablennamen separat in die letzte (oder einzige) Zeile einer Codezelle." 579 | ] 580 | }, 581 | { 582 | "cell_type": "code", 583 | "execution_count": null, 584 | "metadata": {}, 585 | "outputs": [], 586 | "source": [ 587 | "a" 588 | ] 589 | }, 590 | { 591 | "cell_type": "markdown", 592 | "metadata": {}, 593 | "source": [ 594 | "Übrigens gibt es einen einfacheren Weg, dieses spezielle Array zu erhalten (mit [numpy.arange()](http://docs.scipy.org/doc/numpy/reference/generated/numpy.arange.html)):" 595 | ] 596 | }, 597 | { 598 | "cell_type": "code", 599 | "execution_count": null, 600 | "metadata": {}, 601 | "outputs": [], 602 | "source": [ 603 | "b = np.arange(1,10)\n", 604 | "b" 605 | ] 606 | }, 607 | { 608 | "cell_type": "markdown", 609 | "metadata": {}, 610 | "source": [ 611 | "## Hilfe erhalten\n", 612 | "\n", 613 | "Wenn Sie Einzelheiten über die Verwendung von `np.arange()` und alle unterstützten Argumente wissen wollen, werfen Sie einen Blick in den Hilfetext.\n", 614 | "Hängen Sie einfach ein Fragezeichen an den Funktionsnamen (ohne Klammern!):" 615 | ] 616 | }, 617 | { 618 | "cell_type": "code", 619 | "execution_count": null, 620 | "metadata": {}, 621 | "outputs": [], 622 | "source": [ 623 | "np.arange?" 624 | ] 625 | }, 626 | { 627 | "cell_type": "markdown", 628 | "metadata": {}, 629 | "source": [ 630 | "Im unteren Teil des Browserfensters sollte sich ein Hilfefenster öffnen.\n", 631 | "Dieses Fenster kann durch Drücken der Taste *q* (wie \"quit\") geschlossen werden.\n", 632 | "\n", 633 | "Holen wir uns mehr Hilfe:" 634 | ] 635 | }, 636 | { 637 | "cell_type": "code", 638 | "execution_count": null, 639 | "metadata": {}, 640 | "outputs": [], 641 | "source": [ 642 | "np.zeros?" 643 | ] 644 | }, 645 | { 646 | "cell_type": "markdown", 647 | "metadata": {}, 648 | "source": [ 649 | "Sie können auch Hilfe für das gesamte NumPy-Paket erhalten:" 650 | ] 651 | }, 652 | { 653 | "cell_type": "code", 654 | "execution_count": null, 655 | "metadata": {}, 656 | "outputs": [], 657 | "source": [ 658 | "np?" 659 | ] 660 | }, 661 | { 662 | "cell_type": "markdown", 663 | "metadata": {}, 664 | "source": [ 665 | "Sie können für jedes Objekt Hilfe erhalten, indem Sie ein Fragezeichen an den Namen des Objekts anhängen (oder voranstellen).\n", 666 | "Schauen wir uns an, was das Hilfesystem uns über unsere Variable `a` sagen kann:" 667 | ] 668 | }, 669 | { 670 | "cell_type": "code", 671 | "execution_count": null, 672 | "metadata": {}, 673 | "outputs": [], 674 | "source": [ 675 | "a?" 676 | ] 677 | }, 678 | { 679 | "cell_type": "markdown", 680 | "metadata": {}, 681 | "source": [ 682 | "Das Hilfesystem kann bei der Lösung der folgenden Aufgaben sehr nützlich sein ..." 683 | ] 684 | }, 685 | { 686 | "cell_type": "markdown", 687 | "metadata": {}, 688 | "source": [ 689 | "## `np.arange()`\n", 690 | "\n", 691 | "Wir werden oft Sequenzen von gleichmäßig verteilten Zahlen brauchen, also lasst uns welche erstellen.\n", 692 | "\n", 693 | "*Übung:* Erzeugen Sie eine Zahlenfolge mit `np.arange()`, beginnend mit 0 und bis (aber nicht einschließlich) 6 mit einer Schrittweite von 1." 694 | ] 695 | }, 696 | { 697 | "cell_type": "code", 698 | "execution_count": null, 699 | "metadata": {}, 700 | "outputs": [], 701 | "source": [ 702 | "'Implement me!'" 703 | ] 704 | }, 705 | { 706 | "cell_type": "markdown", 707 | "metadata": {}, 708 | "source": [ 709 | "*Übung:* Erzeugen Sie eine Zahlenfolge mit `np.arange()`, beginnend mit 0 und bis (aber nicht einschließlich) 0,6 mit einer Schrittweite von 0,1." 710 | ] 711 | }, 712 | { 713 | "cell_type": "code", 714 | "execution_count": null, 715 | "metadata": {}, 716 | "outputs": [], 717 | "source": [ 718 | "'Implement me!'" 719 | ] 720 | }, 721 | { 722 | "cell_type": "markdown", 723 | "metadata": {}, 724 | "source": [ 725 | "*Übung:* Erzeugen Sie eine Zahlenfolge mit `np.arange()`, beginnend mit 0,5 bis (aber nicht einschließlich) 1,1 mit einer Schrittweite von 0,1." 726 | ] 727 | }, 728 | { 729 | "cell_type": "code", 730 | "execution_count": null, 731 | "metadata": {}, 732 | "outputs": [], 733 | "source": [ 734 | "'Implement me!'" 735 | ] 736 | }, 737 | { 738 | "cell_type": "markdown", 739 | "metadata": {}, 740 | "source": [ 741 | "Die vorherige Übung ist etwas knifflig.\n", 742 | "Wenn Sie es richtig gemacht haben, schauen Sie unter [arange considered harmful](http://nbviewer.ipython.org/github/mgeier/python-audio/blob/master/misc/arange.ipynb) nach, was Sie falsch gemacht haben *könnten*.\n", 743 | "Wenn Sie ein unerwartetes Ergebnis erhalten haben, schauen Sie unter [arange considered harmful](http://nbviewer.ipython.org/github/mgeier/python-audio/blob/master/misc/arange.ipynb) nach einer Erklärung.\n", 744 | "\n", 745 | "*Übung:* Können Sie das Problem beheben?" 746 | ] 747 | }, 748 | { 749 | "cell_type": "code", 750 | "execution_count": null, 751 | "metadata": {}, 752 | "outputs": [], 753 | "source": [] 754 | }, 755 | { 756 | "cell_type": "markdown", 757 | "metadata": {}, 758 | "source": [ 759 | "Was lernen wir aus all dem?\n", 760 | "$\\Rightarrow$\n", 761 | "`np.arange()` ist großartig, aber verwenden Sie es nur mit ganzzahligen Schrittweiten!" 762 | ] 763 | }, 764 | { 765 | "cell_type": "markdown", 766 | "metadata": {}, 767 | "source": [ 768 | "## `np.linspace()`\n", 769 | "\n", 770 | "Eine andere, etwas andere Methode, um eine Folge von Zahlen mit gleichmäßigen Abständen zu erzeugen, ist [numpy.linspace()](http://docs.scipy.org/doc/numpy/reference/generated/numpy.linspace.html).\n", 771 | "Werfen Sie einen Blick in die Dokumentation." 772 | ] 773 | }, 774 | { 775 | "cell_type": "code", 776 | "execution_count": null, 777 | "metadata": {}, 778 | "outputs": [], 779 | "source": [ 780 | "np.linspace?" 781 | ] 782 | }, 783 | { 784 | "cell_type": "markdown", 785 | "metadata": {}, 786 | "source": [ 787 | "*Übung:* Erzeugen Sie eine Zahlenfolge mit `np.linspace()`, beginnend mit 0 und bis (einschließlich) 6 mit einer Schrittweite von 1." 788 | ] 789 | }, 790 | { 791 | "cell_type": "code", 792 | "execution_count": null, 793 | "metadata": {}, 794 | "outputs": [], 795 | "source": [ 796 | "'Implement me!'" 797 | ] 798 | }, 799 | { 800 | "cell_type": "markdown", 801 | "metadata": {}, 802 | "source": [ 803 | "Beachten Sie, dass das resultierende Array einen *Gleitkomma*-Datentyp hat, auch wenn alle Eingaben (und die Schrittweite) Ganzzahlen sind.\n", 804 | "Dies ist bei `np.arange()` nicht der Fall.\n", 805 | "\n", 806 | "*Übung:* Erzeugen Sie mit `np.linspace()` eine Folge von Zahlen, beginnend mit 0 und bis (aber nicht einschließlich) 6 mit einer Schrittweite von 1." 807 | ] 808 | }, 809 | { 810 | "cell_type": "code", 811 | "execution_count": null, 812 | "metadata": {}, 813 | "outputs": [], 814 | "source": [ 815 | "'Implement me!'" 816 | ] 817 | }, 818 | { 819 | "cell_type": "markdown", 820 | "metadata": {}, 821 | "source": [ 822 | "*Übung:* Erzeugen Sie eine Zahlenfolge mit `np.linspace()`, beginnend mit 0 und bis (aber nicht einschließlich) 0,6 mit einer Schrittweite von 0,1." 823 | ] 824 | }, 825 | { 826 | "cell_type": "code", 827 | "execution_count": null, 828 | "metadata": {}, 829 | "outputs": [], 830 | "source": [ 831 | "'Implement me!'" 832 | ] 833 | }, 834 | { 835 | "cell_type": "markdown", 836 | "metadata": {}, 837 | "source": [ 838 | "*Übung:* Erzeugen Sie eine Zahlenfolge mit `np.linspace()`, beginnend mit 0,5 und bis zu (aber nicht einschließlich) 1,1 mit einer Schrittweite von 0,1." 839 | ] 840 | }, 841 | { 842 | "cell_type": "code", 843 | "execution_count": null, 844 | "metadata": {}, 845 | "outputs": [], 846 | "source": [ 847 | "'Implement me!'" 848 | ] 849 | }, 850 | { 851 | "cell_type": "markdown", 852 | "metadata": {}, 853 | "source": [ 854 | "Beachten Sie, dass `np.linspace()` nicht das oben erwähnte Problem hat, das wir mit `np.arange()` hatten." 855 | ] 856 | }, 857 | { 858 | "cell_type": "markdown", 859 | "metadata": {}, 860 | "source": [ 861 | "## Erstellen einer Sinuswelle\n", 862 | "\n", 863 | "Erstellen wir nun ein interessanteres Array, das ein digitales Sinussignal darstellt. Das Signal folgt der Gleichung $y(t) = A\\sin(\\omega t)$ mit $\\omega = 2\\pi f$ und $f$ ist die Frequenz des Sinus.\n", 864 | "Die maximale Signalamplitude ist durch $A$ gegeben.\n", 865 | "Die Variable $t$ steht natürlich für die Zeit.\n", 866 | "Wir wollen ein digitales Signal mit gleichmäßig verteilten Werten für $t$ erzeugen.\n", 867 | "\n", 868 | "Wir können die Funktion [numpy.sin()](http://docs.scipy.org/doc/numpy/reference/generated/numpy.sin.html) verwenden, um einen Sinuston zu erzeugen. Schauen wir uns zunächst den Hilfetext an." 869 | ] 870 | }, 871 | { 872 | "cell_type": "code", 873 | "execution_count": null, 874 | "metadata": {}, 875 | "outputs": [], 876 | "source": [ 877 | "np.sin?" 878 | ] 879 | }, 880 | { 881 | "cell_type": "markdown", 882 | "metadata": {}, 883 | "source": [ 884 | "Da wir nun wissen, welche Funktion wir aufrufen müssen, brauchen wir eine geeignete Eingabe.\n", 885 | "Und hier kommen unsere Sequenzen von gleichmäßig verteilten Werten von oben ins Spiel.\n", 886 | "\n", 887 | "Das Schöne an NumPy-Funktionen wie `np.sin()` ist, dass sie auf ganze Arrays auf einmal wirken können, so dass es nicht notwendig ist, die Funktion für jeden einzelnen Wert separat aufzurufen.\n", 888 | "Daher können wir den gesamten Wertebereich für unsere Zeitvariable $t$ in einem Array speichern.\n", 889 | "\n", 890 | "Gemäß der Gleichung muss jeder Wert von $t$ mit (der Konstante) $\\omega$ multipliziert werden.\n", 891 | "Das ist eine weitere nette Sache an NumPy: wir müssen nicht jeden Wert des Arrays $t$ einzeln mit $\\omega$ multiplizieren, wir können das ganze Array auf einmal mit einem Skalar multiplizieren, und NumPy übernimmt die elementweise Multiplikation für uns.\n", 892 | "Dies wird [\"broadcasting\"](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html) genannt, falls Sie über dieses Wort in der Dokumentation stolpern.\n", 893 | "Das Array, das von `np.sin()` zurückgegeben wird, kann (wiederum mittels Broadcasting) mit dem konstanten Skalar $A$ multipliziert werden, um das Endergebnis zu erhalten.\n", 894 | "\n", 895 | "Das einzige, was noch fehlt, ist $\\pi$, aber das ist einfach:" 896 | ] 897 | }, 898 | { 899 | "cell_type": "code", 900 | "execution_count": null, 901 | "metadata": {}, 902 | "outputs": [], 903 | "source": [ 904 | "np.pi" 905 | ] 906 | }, 907 | { 908 | "cell_type": "markdown", 909 | "metadata": {}, 910 | "source": [ 911 | "Nun wollen wir eine Sinuswelle mit einer Frequenz von 2 Hz, einer Dauer von 1 Sekunde und einer Amplitude von 0,3 erzeugen.\n", 912 | "Wir verwenden eine Abtastrate von 44,1 kHz." 913 | ] 914 | }, 915 | { 916 | "cell_type": "code", 917 | "execution_count": null, 918 | "metadata": { 919 | "tags": [] 920 | }, 921 | "outputs": [], 922 | "source": [ 923 | "dur = 1 # duration in seconds\n", 924 | "amp = 0.3 # maximum amplitude\n", 925 | "freq = 2 # frequency of the sine tone in Hertz\n", 926 | "fs = 44100 # sampling frequency in Hertz\n", 927 | "\n", 928 | "t = np.arange(np.ceil(dur * fs)) / fs\n", 929 | "y = amp * np.sin(2 * np.pi * freq * t)" 930 | ] 931 | }, 932 | { 933 | "cell_type": "markdown", 934 | "metadata": {}, 935 | "source": [ 936 | "## Plotten\n", 937 | "\n", 938 | "Python und NumPy können nicht selbst plotten, sie benötigen die Hilfe von [matplotlib](http://matplotlib.org/)." 939 | ] 940 | }, 941 | { 942 | "cell_type": "code", 943 | "execution_count": null, 944 | "metadata": {}, 945 | "outputs": [], 946 | "source": [ 947 | "import matplotlib.pyplot as plt\n", 948 | "%matplotlib inline" 949 | ] 950 | }, 951 | { 952 | "cell_type": "markdown", 953 | "metadata": {}, 954 | "source": [ 955 | "Jetzt können wir die Daten aus unserem Array aufzeichnen:" 956 | ] 957 | }, 958 | { 959 | "cell_type": "code", 960 | "execution_count": null, 961 | "metadata": {}, 962 | "outputs": [], 963 | "source": [ 964 | "plt.plot(y)" 965 | ] 966 | }, 967 | { 968 | "cell_type": "markdown", 969 | "metadata": {}, 970 | "source": [ 971 | "Wie immer, für weitere Informationen:" 972 | ] 973 | }, 974 | { 975 | "cell_type": "code", 976 | "execution_count": null, 977 | "metadata": {}, 978 | "outputs": [], 979 | "source": [ 980 | "%matplotlib?" 981 | ] 982 | }, 983 | { 984 | "cell_type": "markdown", 985 | "metadata": {}, 986 | "source": [ 987 | "## Verändern des Plots\n", 988 | "\n", 989 | "Schauen wir uns noch einmal unseren Plot an." 990 | ] 991 | }, 992 | { 993 | "cell_type": "code", 994 | "execution_count": null, 995 | "metadata": {}, 996 | "outputs": [], 997 | "source": [ 998 | "plt.plot(y);" 999 | ] 1000 | }, 1001 | { 1002 | "cell_type": "markdown", 1003 | "metadata": {}, 1004 | "source": [ 1005 | "Da wir nur ein einziges Array an die Funktion `plot()` übergeben haben, zeigt die x-Achse den Sample-Index von 0 bis zur Länge des Signals in Samples (minus eins).\n", 1006 | "Es könnte sinnvoller sein, die Zeit in Sekunden anzugeben.\n", 1007 | "\n", 1008 | "Aber lassen Sie uns zuerst die vorherige Darstellung schließen." 1009 | ] 1010 | }, 1011 | { 1012 | "cell_type": "code", 1013 | "execution_count": null, 1014 | "metadata": {}, 1015 | "outputs": [], 1016 | "source": [ 1017 | "plt.close()" 1018 | ] 1019 | }, 1020 | { 1021 | "cell_type": "markdown", 1022 | "metadata": {}, 1023 | "source": [ 1024 | "Wenn wir zwei Arrays an die Funktion `plot()` übergeben, definiert das erste die Zuordnung von Stichprobenindizes zu den tatsächlichen Werten, die auf der x-Achse angezeigt werden, das zweite gibt die entsprechenden y-Werte an." 1025 | ] 1026 | }, 1027 | { 1028 | "cell_type": "code", 1029 | "execution_count": null, 1030 | "metadata": {}, 1031 | "outputs": [], 1032 | "source": [ 1033 | "plt.plot(t, y);" 1034 | ] 1035 | }, 1036 | { 1037 | "cell_type": "markdown", 1038 | "metadata": {}, 1039 | "source": [ 1040 | "Gut, jetzt zeigt die x-Achse die Zeit in Sekunden an.\n", 1041 | "Lassen Sie uns Achsenbeschriftungen erstellen, damit jeder Bescheid weiß." 1042 | ] 1043 | }, 1044 | { 1045 | "cell_type": "code", 1046 | "execution_count": null, 1047 | "metadata": {}, 1048 | "outputs": [], 1049 | "source": [ 1050 | "plt.plot(t, y)\n", 1051 | "plt.xlabel(\"Time / Seconds\")\n", 1052 | "plt.ylabel(\"Amplitude\")\n", 1053 | "plt.title(\"Sine Tone with {} Hz\".format(freq));" 1054 | ] 1055 | }, 1056 | { 1057 | "cell_type": "markdown", 1058 | "metadata": {}, 1059 | "source": [ 1060 | "Weitere Informationen finden Sie unter [Getting Started With `matplotlib`](http://nbviewer.ipython.org/github/mgeier/python-audio/blob/master/plotting/matplotlib.ipynb)." 1061 | ] 1062 | }, 1063 | { 1064 | "cell_type": "markdown", 1065 | "metadata": {}, 1066 | "source": [ 1067 | "## Zweidimensionale Arrays\n", 1068 | "\n", 1069 | "Zweidimensionale Arrays sehen ein wenig aus wie Listen von Listen, aber intern werden sie immer noch in einem zusammenhängenden Speicherbereich gespeichert.\n", 1070 | "\n", 1071 | "Es gibt mehrere Funktionen zur Erstellung von Arrays, mit denen die Anzahl der Zeilen und Spalten angegeben werden kann, z. B. [numpy.zeros()](http://docs.scipy.org/doc/numpy/reference/generated/numpy.zeros.html) und [numpy.ones()](http://docs.scipy.org/doc/numpy/reference/generated/numpy.ones.html)." 1072 | ] 1073 | }, 1074 | { 1075 | "cell_type": "code", 1076 | "execution_count": null, 1077 | "metadata": {}, 1078 | "outputs": [], 1079 | "source": [ 1080 | "np.zeros((4, 2))" 1081 | ] 1082 | }, 1083 | { 1084 | "cell_type": "code", 1085 | "execution_count": null, 1086 | "metadata": {}, 1087 | "outputs": [], 1088 | "source": [ 1089 | "np.ones((4, 2))" 1090 | ] 1091 | }, 1092 | { 1093 | "cell_type": "markdown", 1094 | "metadata": {}, 1095 | "source": [ 1096 | "Arrays können auch aus Listen von Listen (auch bekannt als *verschachtelte* Listen) mit [numpy.array()](http://docs.scipy.org/doc/numpy/reference/generated/numpy.array.html) erstellt werden:" 1097 | ] 1098 | }, 1099 | { 1100 | "cell_type": "code", 1101 | "execution_count": null, 1102 | "metadata": {}, 1103 | "outputs": [], 1104 | "source": [ 1105 | "np.array([[.1, .2], [.3, .4], [.5, .6], [.7, .8]])" 1106 | ] 1107 | }, 1108 | { 1109 | "cell_type": "markdown", 1110 | "metadata": {}, 1111 | "source": [ 1112 | "Beachten Sie, dass die inneren Listen die einzelnen Zeilen des Arrays liefern.\n", 1113 | "\n", 1114 | "Zweidimensionale Arrays können auch durch spaltenweises Verketten einer Liste von eindimensionalen Arrays (oder Listen) mit [numpy.column_stack()](http://docs.scipy.org/doc/numpy/reference/generated/numpy.column_stack.html) erstellt werden:" 1115 | ] 1116 | }, 1117 | { 1118 | "cell_type": "code", 1119 | "execution_count": null, 1120 | "metadata": { 1121 | "tags": [] 1122 | }, 1123 | "outputs": [], 1124 | "source": [ 1125 | "a = np.column_stack([[.1, .2, .3, .4], [.5, .6, .7, .8]])\n", 1126 | "a" 1127 | ] 1128 | }, 1129 | { 1130 | "cell_type": "markdown", 1131 | "metadata": {}, 1132 | "source": [ 1133 | "Wenn Sie Zeilen und Spalten umdrehen wollen, können Sie das Array transponieren:" 1134 | ] 1135 | }, 1136 | { 1137 | "cell_type": "code", 1138 | "execution_count": null, 1139 | "metadata": { 1140 | "tags": [] 1141 | }, 1142 | "outputs": [], 1143 | "source": [ 1144 | "a.T\n" 1145 | ] 1146 | }, 1147 | { 1148 | "cell_type": "markdown", 1149 | "metadata": {}, 1150 | "source": [ 1151 | "Das transponierte Array ist *nicht* eine Kopie des ursprünglichen Arrays, es ist vielmehr eine andere *Sicht* auf denselben Speicher.\n", 1152 | "Das heißt, wenn Sie ein Element des transponierten Arrays ändern, wird diese Änderung auch im ursprünglichen Array sichtbar!" 1153 | ] 1154 | }, 1155 | { 1156 | "cell_type": "code", 1157 | "execution_count": null, 1158 | "metadata": {}, 1159 | "outputs": [], 1160 | "source": [ 1161 | "b[1, 2] = 0\n", 1162 | "a" 1163 | ] 1164 | }, 1165 | { 1166 | "cell_type": "markdown", 1167 | "metadata": {}, 1168 | "source": [ 1169 | "## Array-Eigenschaften\n", 1170 | "\n", 1171 | "Lassen Sie uns ein zweidimensionales Array erstellen:" 1172 | ] 1173 | }, 1174 | { 1175 | "cell_type": "code", 1176 | "execution_count": null, 1177 | "metadata": { 1178 | "tags": [] 1179 | }, 1180 | "outputs": [], 1181 | "source": [ 1182 | "x = np.random.normal(scale=0.2, size=(int(1.5 * fs), 2))\n", 1183 | "x" 1184 | ] 1185 | }, 1186 | { 1187 | "cell_type": "markdown", 1188 | "metadata": {}, 1189 | "source": [ 1190 | "*Übung:* Probieren Sie diese verschiedenen Möglichkeiten aus, um die Größe des Feldes zu erhalten:" 1191 | ] 1192 | }, 1193 | { 1194 | "cell_type": "code", 1195 | "execution_count": null, 1196 | "metadata": { 1197 | "tags": [] 1198 | }, 1199 | "outputs": [], 1200 | "source": [ 1201 | "len(a)" 1202 | ] 1203 | }, 1204 | { 1205 | "cell_type": "code", 1206 | "execution_count": null, 1207 | "metadata": { 1208 | "tags": [] 1209 | }, 1210 | "outputs": [], 1211 | "source": [ 1212 | "a.shape" 1213 | ] 1214 | }, 1215 | { 1216 | "cell_type": "code", 1217 | "execution_count": null, 1218 | "metadata": { 1219 | "tags": [] 1220 | }, 1221 | "outputs": [], 1222 | "source": [ 1223 | "a.size" 1224 | ] 1225 | }, 1226 | { 1227 | "cell_type": "code", 1228 | "execution_count": null, 1229 | "metadata": { 1230 | "tags": [] 1231 | }, 1232 | "outputs": [], 1233 | "source": [ 1234 | "a.nbytes" 1235 | ] 1236 | }, 1237 | { 1238 | "cell_type": "markdown", 1239 | "metadata": {}, 1240 | "source": [ 1241 | "*Übung:* Es gibt noch viel mehr Informationen über das Array, probiere die folgenden Befehle aus und finde heraus, was sie bedeuten." 1242 | ] 1243 | }, 1244 | { 1245 | "cell_type": "code", 1246 | "execution_count": null, 1247 | "metadata": {}, 1248 | "outputs": [], 1249 | "source": [ 1250 | "x.ndim" 1251 | ] 1252 | }, 1253 | { 1254 | "cell_type": "code", 1255 | "execution_count": null, 1256 | "metadata": {}, 1257 | "outputs": [], 1258 | "source": [ 1259 | "x.dtype" 1260 | ] 1261 | }, 1262 | { 1263 | "cell_type": "code", 1264 | "execution_count": null, 1265 | "metadata": {}, 1266 | "outputs": [], 1267 | "source": [ 1268 | "x.itemsize" 1269 | ] 1270 | }, 1271 | { 1272 | "cell_type": "code", 1273 | "execution_count": null, 1274 | "metadata": {}, 1275 | "outputs": [], 1276 | "source": [ 1277 | "x.strides" 1278 | ] 1279 | }, 1280 | { 1281 | "cell_type": "code", 1282 | "execution_count": null, 1283 | "metadata": {}, 1284 | "outputs": [], 1285 | "source": [ 1286 | "x.flags" 1287 | ] 1288 | }, 1289 | { 1290 | "cell_type": "markdown", 1291 | "metadata": {}, 1292 | "source": [ 1293 | "*Übung:*\n", 1294 | "Sie können auch einige statistische Werte über die Daten im Array erhalten.\n", 1295 | "Prüfen Sie, ob sie mit dem gegebenen normalverteilten Rauschsignal übereinstimmen." 1296 | ] 1297 | }, 1298 | { 1299 | "cell_type": "code", 1300 | "execution_count": null, 1301 | "metadata": {}, 1302 | "outputs": [], 1303 | "source": [ 1304 | "x.max()" 1305 | ] 1306 | }, 1307 | { 1308 | "cell_type": "code", 1309 | "execution_count": null, 1310 | "metadata": {}, 1311 | "outputs": [], 1312 | "source": [ 1313 | "x.min()" 1314 | ] 1315 | }, 1316 | { 1317 | "cell_type": "code", 1318 | "execution_count": null, 1319 | "metadata": {}, 1320 | "outputs": [], 1321 | "source": [ 1322 | "x.ptp()" 1323 | ] 1324 | }, 1325 | { 1326 | "cell_type": "code", 1327 | "execution_count": null, 1328 | "metadata": {}, 1329 | "outputs": [], 1330 | "source": [ 1331 | "x.mean()" 1332 | ] 1333 | }, 1334 | { 1335 | "cell_type": "code", 1336 | "execution_count": null, 1337 | "metadata": {}, 1338 | "outputs": [], 1339 | "source": [ 1340 | "x.std()" 1341 | ] 1342 | }, 1343 | { 1344 | "cell_type": "code", 1345 | "execution_count": null, 1346 | "metadata": {}, 1347 | "outputs": [], 1348 | "source": [ 1349 | "x.var()" 1350 | ] 1351 | }, 1352 | { 1353 | "cell_type": "markdown", 1354 | "metadata": {}, 1355 | "source": [ 1356 | "Die meisten dieser *Methoden* existieren auch als *Funktionen*, z.B." 1357 | ] 1358 | }, 1359 | { 1360 | "cell_type": "code", 1361 | "execution_count": null, 1362 | "metadata": {}, 1363 | "outputs": [], 1364 | "source": [ 1365 | "np.max(x)" 1366 | ] 1367 | }, 1368 | { 1369 | "cell_type": "markdown", 1370 | "metadata": {}, 1371 | "source": [ 1372 | "Sowohl die Funktionen als auch die Methoden haben ein optionales Argument *Achse*.\n", 1373 | "\n", 1374 | "*Übung:* Versuchen Sie `axis=0` mit allen oben genannten Funktionen/Methoden." 1375 | ] 1376 | }, 1377 | { 1378 | "cell_type": "code", 1379 | "execution_count": null, 1380 | "metadata": {}, 1381 | "outputs": [], 1382 | "source": [ 1383 | "x.std(axis=0)" 1384 | ] 1385 | }, 1386 | { 1387 | "cell_type": "code", 1388 | "execution_count": null, 1389 | "metadata": {}, 1390 | "outputs": [], 1391 | "source": [ 1392 | "np.mean(x, axis=0)" 1393 | ] 1394 | }, 1395 | { 1396 | "cell_type": "markdown", 1397 | "metadata": {}, 1398 | "source": [ 1399 | "*Übung:* Was ist der Unterschied zwischen `axis=0` und `axis=1`?\n", 1400 | "Was bedeutet `axis=-1`?" 1401 | ] 1402 | }, 1403 | { 1404 | "cell_type": "code", 1405 | "execution_count": null, 1406 | "metadata": {}, 1407 | "outputs": [], 1408 | "source": [] 1409 | }, 1410 | { 1411 | "cell_type": "markdown", 1412 | "metadata": {}, 1413 | "source": [ 1414 | "## Broadcasting\n", 1415 | "\n", 1416 | "Wir haben bereits gesehen, dass wenn ein Skalar mit einem Array multipliziert wird, diese Multiplikation elementweise auf dem Array durchgeführt wird.\n", 1417 | "Die NumPy-Leute nennen dies [broadcasting](http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html).\n", 1418 | "\n", 1419 | "Das Tolle daran ist, dass es nicht auf Operationen zwischen einem Skalar und einem Array beschränkt ist, sondern auch zwischen Arrays unterschiedlicher Anzahl von Dimensionen.\n", 1420 | "\n", 1421 | "Nehmen wir zum Beispiel ein eindimensionales Array mit zwei Werten und multiplizieren es mit unserem zweidimensionalen Array `x` von vorhin:" 1422 | ] 1423 | }, 1424 | { 1425 | "cell_type": "code", 1426 | "execution_count": null, 1427 | "metadata": {}, 1428 | "outputs": [], 1429 | "source": [ 1430 | "np.array([0.5, 100]) * x" 1431 | ] 1432 | }, 1433 | { 1434 | "cell_type": "markdown", 1435 | "metadata": {}, 1436 | "source": [ 1437 | "Obwohl diese beiden Arrays eindeutig eine unterschiedliche Form und eine unterschiedliche Anzahl von Dimensionen haben, hat die Multiplikation funktioniert.\n", 1438 | "In diesem Fall wurde jedes Element der ersten Spalte von \"x\" mit dem ersten Wert der anderen Matrix multipliziert, und dasselbe gilt für die zweite Spalte und den zweiten Wert der Matrix.\n", 1439 | "\n", 1440 | "In diesem Beispiel hat das Ergebnis die gleiche Form wie einer der Operanden, und der andere Operand wurde entlang seiner einzigen (oder eher fehlenden) Dimension \"gestreckt\".\n", 1441 | "\n", 1442 | "Aber das muss nicht so sein.\n", 1443 | "Erstellen wir ein zweidimensionales Array, das nur aus einer Spalte besteht:" 1444 | ] 1445 | }, 1446 | { 1447 | "cell_type": "code", 1448 | "execution_count": null, 1449 | "metadata": {}, 1450 | "outputs": [], 1451 | "source": [ 1452 | "y = np.random.normal(scale=0.2, size=(int(1.5 * fs), 1))\n", 1453 | "y" 1454 | ] 1455 | }, 1456 | { 1457 | "cell_type": "code", 1458 | "execution_count": null, 1459 | "metadata": {}, 1460 | "outputs": [], 1461 | "source": [ 1462 | "y.shape" 1463 | ] 1464 | }, 1465 | { 1466 | "cell_type": "markdown", 1467 | "metadata": {}, 1468 | "source": [ 1469 | "Wenn wir ein eindimensionales Array mit diesem zweidimensionalen Spaltenarray multiplizieren, hat keine der Dimensionen die gleiche Größe.\n", 1470 | "Dennoch können wir sie multiplizieren und beide Felder werden \"gestreckt\" (entlang ihrer singulären/fehlenden Dimension), was zu einem Ergebnis führt, das eine größere Form hat als einer der Operanden:" 1471 | ] 1472 | }, 1473 | { 1474 | "cell_type": "code", 1475 | "execution_count": null, 1476 | "metadata": {}, 1477 | "outputs": [], 1478 | "source": [ 1479 | "np.array([0.5, 100]) * y" 1480 | ] 1481 | }, 1482 | { 1483 | "cell_type": "markdown", 1484 | "metadata": {}, 1485 | "source": [ 1486 | "Die linke Spalte des Ergebnisses ist \"y\" multipliziert mit dem ersten Element der eindimensionalen Matrix, die rechte Spalte ist das gleiche \"y\" multipliziert mit dem zweiten Element." 1487 | ] 1488 | }, 1489 | { 1490 | "cell_type": "markdown", 1491 | "metadata": {}, 1492 | "source": [ 1493 | "

\n", 1494 | " \n", 1496 | " \"CC0\"\n", 1497 | " \n", 1498 | "
\n", 1499 | " To the extent possible under law,\n", 1500 | " the person who associated CC0\n", 1501 | " with this work has waived all copyright and related or neighboring\n", 1502 | " rights to this work.\n", 1503 | "

" 1504 | ] 1505 | } 1506 | ], 1507 | "metadata": { 1508 | "kernelspec": { 1509 | "display_name": "Python 3 (ipykernel)", 1510 | "language": "python", 1511 | "name": "python3" 1512 | }, 1513 | "language_info": { 1514 | "codemirror_mode": { 1515 | "name": "ipython", 1516 | "version": 3 1517 | }, 1518 | "file_extension": ".py", 1519 | "mimetype": "text/x-python", 1520 | "name": "python", 1521 | "nbconvert_exporter": "python", 1522 | "pygments_lexer": "ipython3", 1523 | "version": "3.10.2" 1524 | } 1525 | }, 1526 | "nbformat": 4, 1527 | "nbformat_minor": 4 1528 | } 1529 | -------------------------------------------------------------------------------- /Uebung 2/02-search.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# 2. Suchalgorithmen\n", 8 | "## 2.1 Eigenschaften von Suchproblemen\n", 9 | "Diskutieren Sie die folgenden Begriffe:\n", 10 | "* Zustandsraum\n", 11 | "* Aktionen\n", 12 | "* Zielzustände\n", 13 | "* Initialzustand\n", 14 | "* Pfad im Zustandsraum\n", 15 | "* Kostenfunktion\n", 16 | "* Schrittkosten\n", 17 | "* Pfadkosten\n", 18 | "* Optimale Lösung" 19 | ] 20 | }, 21 | { 22 | "attachments": { 23 | "grafik.png": { 24 | "image/png": "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" 25 | } 26 | }, 27 | "cell_type": "markdown", 28 | "metadata": {}, 29 | "source": [ 30 | "## 2.2 Manuelles Ausrechnen\n", 31 | "Bevor wir die Suchalgorithmen implementieren, soll es in dieser Aufgabe darum gehen, die Algorithmen einmal \"per Hand\", mit Papier und Stift, durchzurechnen. Betrachten Sie dazu folgende Karte. Die Zahlen in Klammern geben jeweils den Wert der Heuristik an.\n", 32 | "\n", 33 | "![grafik.png](attachment:grafik.png)\n", 34 | "\n", 35 | "* Benutzen Sie die folgenden Suchalgorithmen um einen Weg zwischen dem Start und dem Ziel zu finden. Notieren Sie Ihre Berechnungen für jeden Schritt. Geben Sie Insbesondere den Inhalt des Stacks / der Queue in jedem Schritt an.\n", 36 | " * Breitensuche\n", 37 | " * Tiefensuche \n", 38 | " * A* " 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": {}, 44 | "source": [ 45 | "## 2.3 Roboter-Navigation\n", 46 | "\n", 47 | "Nun schauen wir uns an, wie die Suchalgorithmen benutzt werden können, um einen Roboter kürzeste Wege finden zu lassen.\n", 48 | "\n", 49 | "Wir nehmen an, dass der Roboter eine Karte der Umgebung hat, und weiß, an welchem Punkt er sich befindet. Um das Ganze einfacher zu machen, nehmen wir außerdem an, dass die Karte ein einfaches 2D-Raster von Zellen ist. Der Roboter kann jede Zelle entweder betreten (`0`) oder die Zelle ist blockiert (`1`). \n", 50 | "In unserer Implementierung ist die Karte eine Matrix von Integers. Zusätzlich gibt es Convenience-Funktionen, um die Nachbarn einer Zelle zu erhalten, und die Distanz zwischen zwei Zellen auszurechnen." 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": {}, 57 | "outputs": [], 58 | "source": [ 59 | "\n", 60 | "import numpy as np\n", 61 | "import math\n", 62 | "\n", 63 | "def distance(x: int, y: int) -> int:\n", 64 | " return (((x[0]-y[0]))**2+((x[1]-y[1])**2))\n", 65 | "\n", 66 | "\n", 67 | "class Map:\n", 68 | " def __init__(self, m: np.ndarray) -> None:\n", 69 | " self.m = m\n", 70 | " def neighbors(self,cell):\n", 71 | " nrow,ncol = m.shape\n", 72 | " x,y = cell\n", 73 | " nb = []\n", 74 | " if x>0:\n", 75 | " if m[x-1,y]==0:\n", 76 | " nb = nb + [(x-1,y)]\n", 77 | " if x<(nrow-1):\n", 78 | " if m[x+1,y]==0:\n", 79 | " nb = nb + [(x+1,y)]\n", 80 | " if y>0:\n", 81 | " if m[x,y-1]==0:\n", 82 | " nb = nb + [(x,y-1)]\n", 83 | " if y<(ncol-1):\n", 84 | " if m[x,y+1]==0:\n", 85 | " nb = nb + [(x,y+1)]\n", 86 | " return nb\n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | "m = np.array([[0,1,0,0,0,0,0],\n", 91 | " [0,1,0,1,0,0,1],\n", 92 | " [0,1,0,1,0,1,0],\n", 93 | " [0,1,0,1,0,0,0],\n", 94 | " [0,0,0,1,1,0,0],\n", 95 | " [0,0,0,1,1,0,0]])" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "Weiterhin sind die Implementierungen eines Stacks und einer Priority Queue (Prioritätswarteschlange) gegeben: " 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "import heapq\n", 112 | "import math\n", 113 | "\n", 114 | "class PriorityQueue:\n", 115 | " def __init__(self):\n", 116 | " self.elements = []\n", 117 | " \n", 118 | " def empty(self):\n", 119 | " return len(self.elements) == 0\n", 120 | " \n", 121 | " def put(self, item, priority):\n", 122 | " heapq.heappush(self.elements, (priority, item))\n", 123 | " \n", 124 | " def get(self):\n", 125 | " return heapq.heappop(self.elements)[1]\n", 126 | " \n", 127 | "class Stack:\n", 128 | " def __init__(self):\n", 129 | " self.elements = []\n", 130 | " \n", 131 | " def empty(self):\n", 132 | " return len(self.elements) == 0\n", 133 | " \n", 134 | " def put(self, x):\n", 135 | " self.elements.append(x)\n", 136 | " \n", 137 | " def get(self):\n", 138 | " return self.elements.pop()" 139 | ] 140 | }, 141 | { 142 | "cell_type": "markdown", 143 | "metadata": {}, 144 | "source": [ 145 | "Ebenfalls ist eine Funktion `make_path(came_from,start,goal)` gegeben, die als Parameter jeweils ein Dictionary von Vorgängern bekommt,wie auch jeweils ein Tupel mit x- und y-Koordinate des Anfangs- und Zielknotens. Das Dictionary speichert für jeden Knoten (=Key) von welchem (Vorgänger-)Knoten (=Value) aus er exploriert wurde. Diese Funktion soll am Ende eines Suchalgorithmus verwendet werden, um das jeweilige Dictionary in einen kürzesten Pfad umzuwandeln! " 146 | ] 147 | }, 148 | { 149 | "cell_type": "code", 150 | "execution_count": null, 151 | "metadata": {}, 152 | "outputs": [], 153 | "source": [ 154 | "def make_path(came_from: dict, start:tuple[int,int], goal:tuple[int,int]) -> list:\n", 155 | " path=[goal]\n", 156 | " if start==goal:\n", 157 | " return [start]\n", 158 | " nex = came_from[goal]\n", 159 | " while nex != start:\n", 160 | " path.append(nex)\n", 161 | " nex = came_from[nex]\n", 162 | " path.reverse()\n", 163 | " return [start] + path\n", 164 | "\n", 165 | "example_output = {(0,0): None,\n", 166 | " (1,0): (0,0),\n", 167 | " (1,1): (1,0),\n", 168 | " (2,0): (1,0),\n", 169 | " (2,1): (2,0),\n", 170 | " (2,2): (2,1)\n", 171 | " }\n", 172 | "start = (0,0)\n", 173 | "end = (2,2)\n", 174 | "print(make_path(example_output, start, end))" 175 | ] 176 | }, 177 | { 178 | "cell_type": "markdown", 179 | "metadata": {}, 180 | "source": [ 181 | "## Aufgabe 1\n", 182 | "* Erstellen Sie aus der Variable `m` eine Map und lassen Sie sich die Nachbarn für die Zelle `4,1` ausgeben \n", 183 | "* Ermitteln Sie die Distanz von Zelle `0,0` zur Zelle `4,1`\n", 184 | "* Erstellen Sie einen Stack mit den Elementen `4,3,2,1` und lassen sie sich das letzte Element ausgeben " 185 | ] 186 | }, 187 | { 188 | "cell_type": "code", 189 | "execution_count": null, 190 | "metadata": {}, 191 | "outputs": [], 192 | "source": [ 193 | "mm = Map(m)\n", 194 | "'Todo'" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "metadata": {}, 200 | "source": [ 201 | "## Aufgabe 2\n", 202 | "Implementieren Sie den Tiefensuche-Algorithmus, um einen Weg vom Start zum Ziel zu finden. Dabei soll für jeden besuchten Knoten gespeichert werden, von welchem anderen (Vorgänger-)Knoten aus er exploriert wurde. Verwenden Sie dafür ein Dictionary, welches als Key den jeweils explorierten Knoten und als Value den Vorgänger-Knoten enthält." 203 | ] 204 | }, 205 | { 206 | "cell_type": "code", 207 | "execution_count": null, 208 | "metadata": {}, 209 | "outputs": [], 210 | "source": [ 211 | "def dfs(map : Map, start:tuple[int,int], goal:tuple[int,int]) -> list:\n", 212 | " visited_nodes = 'Welche Datenstruktur benötigen wir hier?'\n", 213 | " visited_nodes.put(start)\n", 214 | " # Dictionary indem wir für jeden besuchten Knoten speichern, von welchem Vorgänger-Knoten aus er exploriert wurde\n", 215 | " came_from = {}\n", 216 | " came_from[start] = None\n", 217 | " \n", 218 | " # Implement DFS\n", 219 | " while not visited_nodes.empty():\n", 220 | " pass # Lösche bzw. ersetze mich mit dem DFS-Algorithmus\n", 221 | " \n", 222 | " # Umwandeln des Dictionarys mit besuchten Knoten und deren Vorgängern in die den gefundenen Pfad\n", 223 | " return make_path(visited_nodes, start, goal)\n", 224 | "\n", 225 | "path_dfs = dfs(mm,(0,0),(5,6))" 226 | ] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "metadata": {}, 231 | "source": [ 232 | "## Aufgabe 3\n", 233 | "Implementieren Sie den A*-Algorithmus, um von einem gegebenen Start den kürzesten Weg zum Ziel zu finden. Dabei soll für jeden besuchten Knoten gespeichert werden, von welchem anderen (Vorgänger-)Knoten aus er exploriert wurde. Verwenden Sie dafür ein Dictionary, welches als Key den jeweils exploriert Knoten und als Value den Vorgänger-Knoten enthält." 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": null, 239 | "metadata": {}, 240 | "outputs": [], 241 | "source": [ 242 | "def astar(map : Map, start:tuple[int,int], goal:tuple[int,int], verbose:int = 0) -> tuple[list, dict]:\n", 243 | " visited_nodes = 'Welche Datenstruktur benötigen wir hier?'\n", 244 | " visited_nodes.put(start, 0)\n", 245 | " # Dictionary in dem wir für jeden besuchten Knoten speichern, von welchem Vorgänger-Knoten aus er expandiert wurde\n", 246 | " came_from = {}\n", 247 | " came_from[start] = None\n", 248 | " # Dictionary in dem für jeden besuchten Knoten die jeweils geschätzen Kosten bis zum Zielknoten speichern\n", 249 | " cost_so_far = {}\n", 250 | " cost_so_far[start] = 0\n", 251 | " \n", 252 | " # Implement A*\n", 253 | " while not visited_nodes.empty():\n", 254 | " pass # Lösche bzw. ersetze mich mit dem A*-Algorithmus\n", 255 | " \n", 256 | " return make_path(visited_nodes,start,goal), cost_so_far\n", 257 | "\n", 258 | "\n", 259 | "astar(mm,(0,0),(5,6), verbose=0)[0]" 260 | ] 261 | }, 262 | { 263 | "cell_type": "markdown", 264 | "metadata": { 265 | "tags": [] 266 | }, 267 | "source": [ 268 | "## Aufgabe 4\n", 269 | "Verwenden Sie die unte definierte Funktion um sich die Map und den jeweils berechneten Weg plotten zu lassen!" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": null, 275 | "metadata": {}, 276 | "outputs": [], 277 | "source": [ 278 | "import matplotlib.pyplot as plt\n", 279 | "%matplotlib inline\n", 280 | "\n", 281 | "path_dfs = dfs(mm,(0,0),(5,6))\n", 282 | "path_astar, _ = astar(mm,(0,0),(5,6), verbose=0)\n", 283 | "\n", 284 | "def drawMap(map, path = None):\n", 285 | " plt.matshow(map)\n", 286 | " if path is not None:\n", 287 | " x = [x[0] for x in path]\n", 288 | " y = [x[1] for x in path]\n", 289 | " plt.plot(y,x)\n", 290 | "\n", 291 | "\n", 292 | "drawMap(mm.m, path_dfs)\n", 293 | "drawMap(mm.m, path_astar)" 294 | ] 295 | } 296 | ], 297 | "metadata": { 298 | "kernelspec": { 299 | "display_name": "Lehre-oZkq39sg", 300 | "language": "python", 301 | "name": "python3" 302 | }, 303 | "language_info": { 304 | "codemirror_mode": { 305 | "name": "ipython", 306 | "version": 3 307 | }, 308 | "file_extension": ".py", 309 | "mimetype": "text/x-python", 310 | "name": "python", 311 | "nbconvert_exporter": "python", 312 | "pygments_lexer": "ipython3", 313 | "version": "3.11.4" 314 | } 315 | }, 316 | "nbformat": 4, 317 | "nbformat_minor": 4 318 | } 319 | --------------------------------------------------------------------------------