Introduction To AI and ML
Introduction To AI and ML
Imagine you want a computer to be smart, like a human. That's the main idea behind Artificial Intelligence (AI). It's a
big field in computer science that tries to make machines do things that normally need human brains.
Machine Learning (ML) is a big part of AI. Think of it as a way to teach computers without telling them every single
step. Instead, you show them lots and lots of examples (data), and they learn from those examples.
• Old way (not ML): You'd write very specific rules: "If it has pointy ears AND whiskers AND a tail AND says
'meow', it's a cat." This gets really complicated for every tiny detail!
• ML way: You show the computer thousands of pictures – some with cats, some without. You tell it, "This one
is a cat, this one isn't." After seeing enough examples, the computer figures out for itself what makes a cat a
cat. It learns the patterns.
So, ML is about computers learning from data, without being told every single rule. It's like learning from experience.
• It helps computers tackle problems that are too complex to program with simple rules.
• ML systems can get smarter over time as they get more data.
• They can find hidden patterns in huge amounts of information that humans would never see.
• Understanding Language:
• Recommendations:
• Healthcare:
• Robots:
In Supervised Learning, we train the machine using "labelled" data. This means the data comes with the correct
answers or outcomes already provided. It's like a teacher supervising a student, giving them examples with solutions
so they can learn the rules.
• The Idea: The computer learns from labelled data. This means for every piece of information you give it, you
also tell it the correct answer.
• How it works:
o You give the computer lots of examples: "This picture is a cat," "This picture is a dog." (The picture is
the data, "cat" or "dog" is the label/answer).
o The computer studies these examples and learns the connection between the picture and its label.
o Once it's learned, you can show it a new picture it's never seen, and it will try to guess if it's a cat or a
dog.
• When to use it:
▪ Examples: Is this email spam or not spam? Is this fruit an apple or an orange? Does this patient
have disease A or disease B?
▪ Examples: How much will this house sell for? What will the temperature be tomorrow?
• Think of it like: A student doing homework with an answer key. They check their answers to learn.
Unsupervised Learning deals with "unlabelled" data, meaning there are no pre-defined correct answers. The
machine must find hidden patterns or structures within the data all by itself. It's like letting a student explore and
discover things on their own without direct guidance.
• The Idea: The computer gets unlabelled data. This means you just give it information, but you don't tell it any
correct answers or categories. The computer must find hidden patterns or structures on its own.
• How it works:
o You give the computer a big pile of data (like a box full of mixed LEGO bricks).
o The computer looks for similarities and groups the data into natural categories based on what it finds.
o It's like finding natural groups or ways to organize the data without any help.
▪ Examples: Finding different types of customers in a store's data based on what they buy,
without being told what those types are beforehand. Grouping similar news articles together.
▪ Examples: People who buy barbecue grills often also buy charcoal, lighter fluid, etc.
• Think of it like: A student exploring a new topic on their own, trying to find out how different ideas connect.
3. Reinforcement Learning (you may say like “Learning by Trial and Error”)
Reinforcement Learning is about an agent learning to make decisions by trial and error in an environment. The agent
receives rewards for good actions and penalties for bad ones, just like training a pet. The goal is to learn a policy
that maximizes the total reward.
• The Idea: The computer (called an "agent") learns by trying things out in an "environment." It gets rewards
for doing good things and penalties for bad things. Its goal is to learn a strategy that gets it the most rewards
over time.
• How it works:
o The agent tries an Action (moves a chess piece, makes a robot take a step).
o The environment gives the agent a Reward (positive points for a good move) or a Penalty (negative
points for a bad move).
o The agent uses these rewards/penalties to learn which actions are best in different situations, slowly
getting better and better.
• When to use it:
o Playing Games: Teaching AI to play complex games like chess, Go, or even video games, often better
than humans.
o Self-Driving Cars: Helping cars learn how to make decisions on the road (like when to speed up, slow
down, or turn).
• Think of it like: Training a pet. When it does something good, you give it a treat. If it does something bad, you
might say "no." Over time, the pet learns what to do to get treats.
Feature Supervised Learning Unsupervised Learning Reinforcement Learning
Predict a specific output Discover hidden patterns, Learn optimal sequences of actions
Primary Goal variable (label) based on structures, or relationships in an environment to maximize
input features. within the data. cumulative reward.
Interaction-based: Learns by
Labelled Data: Each row has interacting with an environment.
Unlabelled Data: Rows contain
Data input features (X) and a Data (states, actions, rewards) is
only input features (X); no
Requirement corresponding known output generated through experience, often
predefined output target.
target (Y). represented in tabular form (e.g., Q-
table).
Algorithm learns a mapping Algorithm explores the inherent Agent learns through trial and error
from X to Y by finding structure of the data itself, by taking actions in different states
Learning
correlations and identifying groups, dimensions, and receiving rewards/penalties,
Process
dependencies in the Labelled or anomalies without external iteratively updating its "knowledge"
data. guidance. (e.g., Q-values).
NumPy
NumPy (Numerical Python) is the foundational library for numerical computing in Python. It provides a high-
performance multidimensional array object, and tools for working with these arrays. It is the cornerstone for many
other scientific Python libraries, including SciPy, Matplotlib, and scikit-learn.
Core Advantage: NumPy's primary benefit lies in its ability to perform operations on large datasets significantly
faster and with greater memory efficiency than standard Python lists, largely due to its implementation in C and
Fortran.
Less Efficient: Stores references to objects, Highly Efficient: Stores elements directly and
Memory Efficiency which can be scattered in memory. Higher contiguously in memory. Lower overhead per
overhead per element. element.
General Purpose: Basic list operations Specialized for Numerics: Extensive math
Functionality (append, insert, pop, sort, concatenate). operations (element-wise, ufuncs), linear
Limited mathematical functions. algebra, statistics, reshaping, broadcasting, etc.
Fixed Size: Size is fixed upon creation.
Dynamic Size: Can grow or shrink (add/remove
Size/Mutability Reshaping creates a view or a new array.
elements) after creation. Mutable.
Mutable element values.
Requires loops for element-wise math (+ Direct arithmetic operations are element-wise
Ease of Use (Math)
concatenates lists, * repeats lists). (+, -, *, / operate on corresponding elements).
Demonstrating the execution time difference between a Python list and a NumPy array
import time
import numpy as np
N = 10**6 # One million elements (small enough for quick run, large enough for difference)
start_time_list = time.perf_counter()
list_result = [x + x for x in range(N)] # Double each number
end_time_list = time.perf_counter()
print(f"List time: {end_time_list - start_time_list:.6f} seconds")
Even with just 1 million elements, you'll see that the NumPy operation completes in milliseconds, while the
equivalent Python list operation takes significantly longer (tens or hundreds of milliseconds). This illustrates the
efficiency gain of NumPy's vectorized operations.
import numpy as np
This allows you to refer to NumPy functions and objects using the convenient np. prefix.
Example:
import numpy as np
print(f"NumPy version installed: {np.__version__}")
Convert standard Python lists (or nested lists for higher dimensions) into NumPy arrays.
import numpy as np
import numpy as np
NumPy provides functions for generating arrays with specific initial values or patterns.
import numpy as np
Example 4: np.arange()
Generate an array with values within a specified range (similar to Python's range()).
import numpy as np
Example 5: np.linspace()
import numpy as np
import numpy as np
Accessing specific elements or subsets of an array is crucial. NumPy's indexing and slicing capabilities are powerful
and flexible.
import numpy as np
import numpy as np
Extract sub-arrays using [start:stop:step] notation, similar to Python lists, but extended for multiple dimensions.
import numpy as np
arr = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print("\nOriginal Array:", arr)
print("Elements from index 2 to 7 (exclusive):", arr[2:7]) # Output: [2 3 4 5 6]
print("Every other element:", arr[::2]) # Output: [0 2 4 6 8]
import numpy as np
Select elements based on a boolean condition. This creates a "mask" which filters the array.
import numpy as np
Array Manipulation
NumPy provides functions to change array shape, combine, and split arrays.
Change the dimensions of an array without altering its data. The new shape must have the same total number of
elements.
Example 1: Reshaping 1D to 2D
import numpy as np
arr_1d = np.arange(9) # [0 1 2 3 4 5 6 7 8]
reshaped_arr = arr_1d.reshape((3, 3)) # Reshape to 3 rows, 3 columns
print("\nOriginal 1D:", arr_1d)
print("Reshaped to 3x3:\n", reshaped_arr)
● You can use -1 for one dimension and NumPy will infer its size: arr_1d.reshape((3, -1)).
import numpy as np
import numpy as np
NumPy's core strength is its ability to perform element-wise operations efficiently, a concept known as vectorization.
This eliminates the need for explicit loops, leading to cleaner and faster code.
import numpy as np
print("\nArray 'a':", a)
print("Array 'b':", b)
print("a + b (element-wise):", a + b) # [11 22 33]
print("a * b (element-wise):", a * b) # [10 40 90]
print("a ** 2 (element-wise square):", a ** 2) # [100 400 900]
NumPy provides a wide array of mathematical functions (ufuncs) that operate element-wise.
import numpy as np
print("\nAngles:", angles)
print("Sine of angles:", np.sin(angles)) # [0. 1. 0.]
print("\nValues:", values)
print("Square root of values:", np.sqrt(values)) # [1. 2. 3.]
import numpy as np
import numpy as np
print("\nMatrix A:\n", A)
print("Matrix B:\n", B)
# Determinant of A
det_A = np.linalg.det(A)
print("Determinant of A:", det_A)
# Inverse of A
inv_A = np.linalg.inv(A)
print("Inverse of A:\n", inv_A)
import numpy as np
import numpy as np
import numpy as np
import time
# Python Lists
list1 = list(range(data_size))
list2 = list(range(data_size))
start_time = time.perf_counter()
result_list = [x + y for x, y in zip(list1, list2)]
end_time = time.perf_counter()
print(f"\nTime for Python list addition ({data_size} elements): {end_time - start_time:.4f} seconds")
# NumPy Arrays
arr1 = np.arange(data_size)
arr2 = np.arange(data_size)
start_time = time.perf_counter()
result_arr = arr1 + arr2
end_time = time.perf_counter()
print(f"Time for NumPy array addition ({data_size} elements): {end_time - start_time:.4f} seconds")
Running this comparison typically demonstrates NumPy performing the operation orders of magnitude faster.
When you perform standard arithmetic operations (+, -, *, /, **, %) between NumPy arrays of the same shape, or
between a NumPy array and a scalar (a single number), the operation is applied element-by-element.
1.1 Array and Scalar Operations
Example:
import numpy as np
# Addition
print("\nArray + Scalar (Addition):", arr + scalar) # Each element gets 5 added
# Output: [15 25 35 45]
# Subtraction
print("Array - Scalar (Subtraction):", arr - scalar) # Each element gets 5 subtracted
# Output: [ 5 15 25 35]
# Multiplication
print("Array * Scalar (Multiplication):", arr * scalar) # Each element gets multiplied by 5
# Output: [ 50 100 150 200]
# Division
print("Array / Scalar (Division):", arr / scalar) # Each element gets divided by 5
# Output: [ 2. 4. 6. 8.]
# Power
print("Array ** Scalar (Power):", arr ** 2) # Each element gets squared
# Output: [ 100 400 900 1600]
# Modulus (Remainder)
print("Array % Scalar (Modulus):", arr % 3) # Remainder when divided by 3
# Output: [1 2 0 1]
Example:
import numpy as np
# Addition
print("\nArray1 + Array2 (Element-wise Addition):", arr1 + arr2)
# Output: [ 6 8 10 12] (1+5, 2+6, 3+7, 4+8)
# Subtraction
print("Array1 - Array2 (Element-wise Subtraction):", arr1 - arr2)
# Output: [-4 -4 -4 -4]
# Multiplication
print("Array1 * Array2 (Element-wise Multiplication):", arr1 * arr2)
# Output: [ 5 12 21 32] (This is NOT matrix multiplication/dot product)
# Division
print("Array1 / Array2 (Element-wise Division):", arr1 / arr2)
# Output: [0.2 0.33333333 0.42857143 0.5 ]
# Power
print("Array1 ** Array2 (Element-wise Power):", arr1 ** arr2)
# Output: [1 64 2187 65536] (1^5, 2^6, 3^7, 4^8)
In NumPy, the * operator performs element-wise multiplication. If you need matrix multiplication (dot product), you
should use np.dot() or the @ operator (available in Python 3.5+).
import numpy as np
Broadcasting is NumPy's incredibly powerful feature that allows operations to be performed on arrays of different
shapes. It implicitly "stretches" the smaller array(s) to match the larger array's shape for the operation, without
actually creating copies in memory (unless necessary).
If these rules are not met, a ValueError: operands could not be broadcast together occurs.
print("Matrix:\n", matrix)
print("Row Vector:", row_vector)
# Matrix + Row Vector (Broadcasting)
# NumPy broadcasts row_vector (shape (3,)) to (1, 3) and then stretches it along axis 0 to (2, 3)
# It's like:
# [[1 2 3]
# [1 2 3]]
result = matrix + row_vector
print("\nMatrix + Row Vector (Broadcasted Addition):\n", result)
# Output:
# [[11 22 33]
# [41 52 63]]
To broadcast a 1D array as a column vector, you need to explicitly make it 2D with a column dimension of 1 using
np.newaxis or reshape(-1, 1).
import numpy as np
print("Matrix:\n", matrix)
print("Column Vector (original):", col_vector)
print("Column Vector (reshaped for broadcasting):\n", col_vector_reshaped)
3. Comparison Operators
NumPy also supports element-wise comparison operators (==, !=, <, >, <=, >=). These operations return a boolean
array of the same shape as the input, indicating True or False for each element's comparison.
Example:
import numpy as np
These boolean arrays are extremely useful for boolean indexing (masking), as demonstrated in the previous notes.
4. Bitwise Operations
NumPy also supports element-wise bitwise operations (& - AND, | - OR, ^ - XOR, ~ - NOT) for integer arrays.
Example:
import numpy as np
print("Array a:", a)
print("Array b:", b)
print("Bitwise OR (a | b):", a | b)
# Output: [0 1 6 7]
# Binary for 2 (0010) | 4 (0100) = 6 (0110)
# Binary for 3 (0011) | 5 (0101) = 7 (0111)
In essence, NumPy array arithmetic is a cornerstone of efficient numerical computing in Python. By leveraging
vectorized operations and broadcasting, you can write concise, readable, and incredibly fast code for complex
mathematical tasks, avoiding the performance bottlenecks of explicit Python loops.
Conclusion
NumPy is an essential library for data manipulation and numerical computation in Python. Mastering its ndarray
object, creation methods, indexing, manipulation techniques, and vectorized operations, along with understanding
broadcasting, will significantly enhance your productivity and performance in any data-intensive task. It forms the
backbone for advanced data analysis, machine learning, and scientific computing workflows.