Science Delight #12: I Needed a Break, So Here’s a Python Intro for Science Nerds
- abrokepostgradrese
- Mar 30, 2025
- 16 min read

I’m kinda tired, so here’s a little Python guide for physics and data science.
To be honest, I’ve been feeling a bit drained lately—stuck in writing my novel, navigating my postgraduate research, and figuring out financial support. I could ramble about it, but this blog isn’t about my personal dilemmas. Instead, let’s focus on something productive: coding. Or rather, using coding as a tool to make physics and data science easier.
Now, you might think, “Why should I care? Can’t I just solve my physics equations by hand?” Sure, go ahead—if you enjoy spending hours crunching through tedious algebraic manipulations. But in real-world research and development, those nice, compact textbook problems you solved in undergrad don’t scale well. Real equations are messy, datasets are enormous, and nobody has time to do a 10x10 matrix calculation by hand.
Why Bother Learning Python for Physics & Data Science?
There are three types of people when it comes to computational tools in STEM:
The ‘I Hate Coding’ Crowd – They believe everything can be done by hand and that coding is an unnecessary evil.
The Spreadsheet Enthusiasts – They live and breathe Excel, convinced that all computations can be solved with enough rows, columns, and functions.
The Pragmatists – They realize that some problems are best solved with the right tool and that Python (or MATLAB, or R) is a game-changer.
If you’re in the first group, let me ask: Would you manually balance a 50,000-element dataset? Would you compute a Fourier transform of a time series manually? Probably not. Even a 10x10 matrix can get tedious, and that’s considered small in engineering computations. Sure, you could use Excel, but have you ever tried handling anything beyond basic arithmetic there? Spreadsheets work, but they aren’t built for scalable, complex scientific analysis.
The truth is, most of us don’t need to become software engineers. We just need to write scripts that automate our calculations, process data efficiently, and visualize results in a meaningful way. That’s where Python shines.
Why Python Over Other Tools?
Alright, let’s address the alternatives:
MATLAB – Great for numerical computing, but expensive.
Octave – Free alternative to MATLAB, but mostly terminal-based, which means it lacks usability and convenience.
Scilab – Also free and powerful, but not as widely adopted.
C++ – Fast and efficient but requires significantly more effort to implement and debug.
R – Excellent for statistics, but let’s be real, its syntax isn’t exactly pretty.
Python, on the other hand, is free, flexible, and high-level. It’s designed to be readable and beginner-friendly while still being powerful enough for serious scientific computing. Compared to C, which is a lower-level language (meaning you have to handle memory management and low-level operations yourself), Python lets you focus on solving the problem rather than fighting with the syntax.
And let’s not forget: the libraries. The Python ecosystem has some of the best tools for physics, data science, and engineering:
NumPy – Fast numerical calculations, matrix operations, and more.
SciPy – Scientific computing, including integration, interpolation, and optimization.
Matplotlib & Seaborn – Data visualization to make your research digestible.
SymPy – Symbolic mathematics, useful for algebraic manipulation.
Pandas – Data manipulation and analysis.
TensorFlow/PyTorch – Machine learning, if you want to get fancy.
Python: The Chainsaw for Computational Physics
A professor once told me: Using Python (or MATLAB) for physics is like using a chainsaw—powerful and efficient for cutting through complex problems. Sure, you can do small computations by hand, but why use a chainsaw to cut paper when you can use it to slice through real challenges? (p.s. Honestly, some of us become so accustomed to using Python that we even use it for simple arithmetic, simply because Python is already running. However, we don't intentionally start Python just for those tasks.)
Let’s be honest, when reading research papers, we all have that moment: You come across an elegant (or terrifying) equation. You stare at it. You cry a little. Then you remember, “I have Jupyter Notebook or VS Code!” So you plug in some sample data (probably rand() values) and test it out. Within minutes, it makes sense, and now you can visualize and understand it. That’s the power of coding in research.
How to start?
Honestly, I wouldn't suggest anyone trying out Python for scripting to ask around; just head over to Google Colab and sign in with your Gmail account. (P.S. If you're in engineering or science, I bet you've already downloaded Anaconda or VS Code, though.)
In Colab, simply open a new notebook to experiment with. Since it's cloud-based, you won't need to worry about your computer's memory; it's all about your Google Drive space.
To begin, if you wish to work with your own data, you have two options:
Place your file in the temporary folder (note that it will be deleted each time the runtime disconnects).
Link your Google Drive:
from google.colab import drive
drive.mount('/content/drive')Next, I prefer not to alter the file locations, so I utilize this code to simplify the file location as a foundation:
import os # Load necessary libraries
import sys
sys.path.append('/content/drive/MyDrive/') #to store the save files in this location
os.chdir('/content/drive/MyDrive/') #this sets the directory Now that we have the os and sys paths set up, we can import a script named "main.py" (just an example, so you know you can import from scripts) from our drive as follows:
'''
The code wouldn't function without os.chdir because it wouldn't be able to locate the file, and from x import*, the * signifies importing everything.
'''
from main import* Later, when saving the CSV, we won't need to worry about the directory because the system path has already been appended, ensuring the file is saved to the drive.
import numpy as np
import pandas as pd
# Set the random seed for reproducibility
np.random.seed(42)
# Generate an array of random data
data = np.random.rand(10, 5) # 10 rows, 5 columns
# Create a Pandas DataFrame from the array
df = pd.DataFrame(data, columns=['col1', 'col2', 'col3', 'col4', 'col5'])
# Save the DataFrame to a CSV file
df.to_csv('random_data.csv', index=False)At this stage, we've set up the code's environment. Next, we need to install and import the dependencies. We use `!pip install` to install dependencies and `import as` to bring them into our code. Keep in mind that even if you're using VSCode and the dependencies are already installed, you still need to import them to use them!
!pip install numpy, matplotlib
import numpy as np #import as to shorten the name, you can import as n as well but might be a little messy when you import more
import matplotlib.pyplot as plt #the dependency.class/method is a trick to use less compute, or import a specific part of the dependency to use.So, indeed, this is basically what we need to know to begin with Python, especially if you're familiar with MATLAB, as they are quite similar. However, with Python, you must import functions from other scripts before using them.
Understanding Python Basics
Variables and Data Types
# Integers and Floats
x = 10 # Integer
y = 3.14 # Float
print(type(x), type(y))# Strings
s = "Hello world!"
print(s.upper())
'''
there are several methods to convert the string to uppercase, lowercase, or sentence case. However, in computation, this is often not a primary concern, so I won't elaborate on it. Refer to the documentation if you're interested.
'''Lists and Dictionaries
Typically, we frequently use lists for computation, especially for data vectorization. I use dictionaries less often, although many of my colleagues highly recommend them. Personally, I prefer declaring global variables more.
# Lists (arrays in Python)
data = [1, 2, 3, 4, 5]
print(data[0]) # First element# Dictionaries (key-value pairs)
constants = {"pi": 3.1415, "g": 9.81}
print(constants["pi"]) # Access valueNumPy Arrays vs. Lists
NumPy arrays (numpy.ndarray) are designed for efficient numerical computations. While Python lists are flexible, they are not optimized for mathematical operations, which makes NumPy a better choice for scientific computing.
1. Element-wise Operations
In a Python list, multiplying by 2 duplicates the elements, but in NumPy, it performs element-wise multiplication.
# Using a Python list
lst = [1, 2, 3, 4]
print(lst * 2) # Output: [1, 2, 3, 4, 1, 2, 3, 4] (Repeats elements, not multiplication)
# Using a NumPy array
import numpy as np
arr = np.array([1, 2, 3, 4])
print(arr * 2) # Output: [ 2 4 6 8 ] (Multiplies each element)2. Faster and More Memory-Efficient
NumPy arrays are stored in contiguous memory blocks, allowing much faster access and computations than Python lists, which store references to objects.
import time
# Large list computation
lst = list(range(1_000_000))
start = time.time()
lst_result = [x * 2 for x in lst]
print("List time:", time.time() - start)
# Large NumPy array computation
arr = np.array(range(1_000_000))
start = time.time()
arr_result = arr * 2
print("NumPy time:", time.time() - start)👉 NumPy is significantly faster because it uses optimized C and Fortran routines internally.
3. Built-in Mathematical Functions
NumPy provides vectorized operations, meaning you can apply math functions directly without writing loops.
arr = np.array([1, 2, 3, 4])
# Square each element
print(np.square(arr)) # [ 1 4 9 16 ]
# Compute the sine of each element
print(np.sin(arr))
# Mean and standard deviation
print(np.mean(arr), np.std(arr))4. Support for Multi-Dimensional Arrays
Python lists are one-dimensional by default, while NumPy supports matrices and multi-dimensional arrays for physics, engineering, and ML.
matrix = np.array([[1, 2], [3, 4]])
print(matrix * 2)5. Broadcasting for Different Shapes
NumPy allows operations between arrays of different shapes without explicit looping.
A = np.array([[1, 2], [3, 4]])
B = np.array([10, 20]) # Smaller array
print(A + B) # Automatically expands B for element-wise additionFunctions in Python
In Python, functions or methods serve as reusable pieces of code. If a segment of code is going to be used more than three times, I typically convert it into a function. This is just my rule of thumb, as it significantly tidies up my code. However, I'm not a software engineer, so I don't focus on clean code unless it's necessary for presentation.
Remember the ":", as we sometimes forget it and then wonder about the source of the error. Also, "def" is somewhat like defining the function (that's how I refer to it when teaching the juniors).
def kinetic_energy(m, v):
return 0.5 * m * v**2
print(kinetic_energy(2, 5)) # Outputs 25.0Object-Oriented Programming (OOP) in Python
So, some of you might already be familiar with class, init, and other dunder (double underscore) methods. If you're dealing with basic data analysis, you probably don’t need them often. But when structuring large projects or working with things like Neural Networks (NN), simulations, or reusable physics models, classes become extremely useful.
Why Use Classes in Physics and Data Science?
While simple scripts and functions work well for quick calculations, larger projects can get messy. Classes help by grouping related functions (methods) and variables (attributes) together, making code modular, reusable, and organized.
Defining a Simple Class
Let’s define a basic class for particles in physics:
class Particle:
def __init__(self, mass, velocity):
self.mass = mass # Assign mass attribute
self.velocity = velocity # Assign velocity attribute
def kinetic_energy(self):
return 0.5 * self.mass * self.velocity**2 # K.E formula
# Creating an instance (object) of the Particle class
electron = Particle(9.11e-31, 2.2e6)
print(electron.kinetic_energy()) # Outputs KE for an electron
Here, init is a dunder method (double underscore) that initializes the object with given values. Instead of passing mass and velocity repeatedly into functions, we store them inside an object, making it easier to track attributes.
Adding More Methods
We can extend the class by adding more physics-related methods:
class Particle:
def __init__(self, mass, velocity, charge):
self.mass = mass
self.velocity = velocity
self.charge = charge # Adding charge attribute
def kinetic_energy(self):
return 0.5 * self.mass * self.velocity**2
def momentum(self):
return self.mass * self.velocity # p = mv
def lorentz_force(self, E, B):
"""Calculates Lorentz force F = q(E + v × B)"""
return self.charge * (E + self.velocity * B)
# Example usage
proton = Particle(1.67e-27, 1.0e5, 1.6e-19)
print(proton.momentum()) # Outputs momentum
print(proton.lorentz_force(5, 0.1)) # Example force calculation
This structure is cleaner and avoids repetitive code—especially useful for simulating multiple particles instead of defining separate functions for each one.
Dunder Methods (Magic Methods)
Python has special "dunder" (double underscore) methods, which allow objects to behave like built-in data types.
String Representation (__str__)
If you print an object, Python normally prints something unreadable like <__main__.Particle object at 0x0000...>. To make it meaningful, override str:
class Particle:
def __init__(self, mass, velocity):
self.mass = mass
self.velocity = velocity
def kinetic_energy(self):
return 0.5 * self.mass * self.velocity**2
def __str__(self):
return f"Particle(mass={self.mass}, velocity={self.velocity})"
electron = Particle(9.11e-31, 2.2e6)
print(electron) # Outputs: Particle(mass=9.11e-31, velocity=2200000.0)
Operator Overloading (__add__, mul, etc.)
Let's say we want to add two particles' momenta using + instead of manually calling functions:
class Particle:
def __init__(self, mass, velocity):
self.mass = mass
self.velocity = velocity
def momentum(self):
return self.mass * self.velocity
def __add__(self, other):
return self.momentum() + other.momentum()
proton = Particle(1.67e-27, 1e5)
neutron = Particle(1.67e-27, -1e5)
total_momentum = proton + neutron # Calls __add__ method
print(total_momentum) # Output: 0.0 (since their momenta cancel out)
Here, add lets us use + to sum momenta, making the code more intuitive.
Making an Iterable Class (__iter__, next)
We can even make a class iterable to loop over multiple instances:
class ParticleCollection:
def __init__(self, particles):
self.particles = particles
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index < len(self.particles):
particle = self.particles[self.index]
self.index += 1
return particle
else:
raise StopIteration
# Example Usage
particles = [Particle(1.67e-27, v) for v in [1e5, 2e5, 3e5]]
collection = ParticleCollection(particles)
for p in collection:
print(p.momentum()) # Iterates through the collection
This is useful when dealing with large simulations of physical objects.
When to Use OOP in Physics & Data Science?
Object-oriented programming is useful when:✔️ You need to simulate multiple objects (e.g., planetary motion, molecular dynamics).✔️ You want reusable, structured code (e.g., multiple experiment setups).✔️ You're building Neural Networks (e.g., PyTorch and TensorFlow use OOP heavily).❌ If you're just running quick calculations, OOP might be overkill.
🐍 Python Control Flow: If, For, and While Loops (Simplified & Fun)
Control flow helps us make decisions and repeat actions in Python. Let’s break it down simply before diving into complex logic! 🚀
When I first started learning C++ programming, I recall avoiding Python because I found loops somewhat complex. Now, I'm trying to simplify the learning process.
🔹 IF Statements: Making Decisions
💡 Analogy: Imagine a smart door that opens only if you have the right passcode.
passcode = "open123"
if passcode == "open123":
print("✅ Door Unlocked!")
else:
print("❌ Wrong Passcode, Try Again!")➡ Explanation: If the condition (passcode == "open123") is True, it unlocks the door. Otherwise, it denies access.
📌 Bonus: Add an elif (else-if) condition!
passcode = "guest123"
if passcode == "open123":
print("✅ Door Unlocked!")
elif passcode == "guest123":
print("🔑 Guest Mode Enabled!")
else:
print("❌ Wrong Passcode, Try Again!")🔹 FOR Loops: Repeating Actions
💡 Analogy: Instead of writing print(1), print(2), etc., a loop does it automatically!
for num in range(1, 6):
print(f"🔢 Number: {num}")📌 Breakdown:
range(1, 6): Counts from 1 to 5 (6 is excluded).
Each iteration, num takes the next value in the sequence.
🔹 WHILE Loops: Repeat Until a Condition is Met
💡 Analogy: A robot continues to walk until it encounters a wall. (although, this might not be the best method to program a robot)
distance = 0
while distance < 5:
print(f"🚶 Walking... Distance: {distance}")
distance += 1 # Move forward📌 Breakdown:
The loop keeps running while distance < 5.
It stops when distance == 5.
🔴 ⚠ Warning: If you forget to update distance, it will loop forever (infinite loop)!
🔹 ENUMERATE: Numbering Items in Loops
💡 Analogy: You have a list of players, and you want to print them with rankings.
🛑 Without enumerate():
players = ["Alice", "Bob", "Charlie"]
rank = 1 # Start at 1
for player in players:
print(f"🏆 Rank {rank}: {player}")
rank += 1 # Increase rank✅ With enumerate():
players = ["Alice", "Bob", "Charlie"]
for rank, player in enumerate(players, start=1):
print(f"🏆 Rank {rank}: {player}")📌 Why use enumerate()?
It automatically counts for us!
No need for extra rank variables!
🧠 Challenge: Nested Loops & Complex Logic
Loops can be combined to create complex logic. Here’s a multiplication table generator!
for i in range(1, 6): # Rows
for j in range(1, 6): # Columns
print(f"{i} × {j} = {i * j}", end=" ")
print() # Newline after each row📌 Breakdown:
The outer loop (i) represents the row number.
The inner loop (j) represents the column number.
end=" " keeps the output on the same line.
For your information, Python is sensitive to indentation. Therefore, if your print statement aligns with the first for loop, it will output results from the first for loop, and the same applies in reverse.
Why do we choose different dependencies/libraries for different use cases
Case study: 🧠 Building a Simple Neural Network in NumPy, PyTorch, and TensorFlow/Keras
Now that control flow is clear, let’s explore how Python builds a basic Neural Network (NN)!
📌 Goal: Create a 3-layer NN (Input → Hidden → Output) using:
NumPy (Manual Approach) 🛠️
PyTorch (Deep Learning Framework) 🔥
TensorFlow/Keras (High-Level API) 🤖
1️⃣ NumPy: Building an NN from Scratch 🏗️
💡 Concept: We manually define weights, activation functions, and forward propagation.
import numpy as np
# Activation Function: Sigmoid
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# Input Data (2 Samples, 2 Features)
X = np.array([[0.1, 0.2], [0.3, 0.4]])
# Randomly Initialize Weights and Biases
np.random.seed(42)
W1 = np.random.randn(2, 3) # 2 Inputs → 3 Hidden Neurons
b1 = np.zeros((1, 3))
W2 = np.random.randn(3, 1) # 3 Hidden → 1 Output
b2 = np.zeros((1, 1))
# Forward Pass
hidden_layer = sigmoid(np.dot(X, W1) + b1)
output_layer = sigmoid(np.dot(hidden_layer, W2) + b2)
print("🔢 Output (NumPy NN):", output_layer)
📌 What’s happening?
Step 1: Input X (2 samples, 2 features)
Step 2: We apply weights W1 and bias b1 for the hidden layer
Step 3: Another weight W2 and bias b2 for the output layer
Step 4: Sigmoid activation squashes values between 0 and 1
Step 5: Final prediction is printed!
🔥 Why use NumPy? It's great for learning the math behind NNs!
2️⃣ PyTorch: Simpler and More Powerful! 🔥
💡 Why PyTorch? It has automatic differentiation (backpropagation is easy!).
import torch
import torch.nn as nn
import torch.optim as optim
# Define a Simple NN (2 Inputs → 3 Hidden → 1 Output)
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
self.hidden = nn.Linear(2, 3) # Input to Hidden
self.output = nn.Linear(3, 1) # Hidden to Output
self.activation = nn.Sigmoid() # Sigmoid Activation
def forward(self, x):
x = self.activation(self.hidden(x)) # Hidden Layer
x = self.activation(self.output(x)) # Output Layer
return x
# Create Model
model = SimpleNN()
# Example Input (Tensor)
X_torch = torch.tensor([[0.1, 0.2], [0.3, 0.4]], dtype=torch.float32)
# Forward Pass
output = model(X_torch)
print("🔢 Output (PyTorch NN):", output.detach().numpy())
📌 Why PyTorch?
No need to manually calculate matrix multiplications!
nn.Linear() creates automatic weights & biases.
forward() defines the flow of data.
Backpropagation and optimization are easy!
3️⃣ TensorFlow/Keras: The Easiest API! 🤖
💡 Why Keras? It's straightforward, comprehensible, and easy to train! P.S. I don't use TensorFlow or Keras because Colab seems to prefer Torch. Additionally, I can't import TensorFlow or Keras due to limited space on my machine.
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
# Create a Simple Model
model = keras.Sequential([
layers.Dense(3, activation="sigmoid", input_shape=(2,)), # Input → Hidden
layers.Dense(1, activation="sigmoid") # Hidden → Output
])
# Example Input (Tensor)
X_tf = tf.constant([[0.1, 0.2], [0.3, 0.4]])
# Forward Pass
output = model(X_tf)
print("🔢 Output (TensorFlow/Keras NN):", output.numpy())
📌 Why TensorFlow/Keras?
Sequential() makes NN creation super simple
No need to manually write the forward() function
Handles big models with ease
Now that we’ve built simple neural networks, let's train them to make meaningful predictions! 🚀
📌 Goal: Train our 3-layer NN to map inputs [x1, x2] to a target output [y]📌 Dataset:
Input (X) → Two features [x1, x2]
Target (y) → A single output
1️⃣ NumPy: Manual Training with Gradient Descent 🛠️
Since NumPy doesn't handle backpropagation, we must manually compute gradients.
import numpy as np
# Sigmoid Activation and Derivative
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(x):
return x * (1 - x) # Derivative of Sigmoid
# Training Data (X: Inputs, y: Targets)
X = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]])
y = np.array([[0.3], [0.7], [0.9]]) # Some arbitrary target values
# Initialize Weights and Biases
np.random.seed(42)
W1 = np.random.randn(2, 3) # Input → Hidden
b1 = np.zeros((1, 3))
W2 = np.random.randn(3, 1) # Hidden → Output
b2 = np.zeros((1, 1))
# Training Loop
learning_rate = 0.1
epochs = 5000 # Number of Training Iterations
for epoch in range(epochs):
# Forward Pass
hidden_layer = sigmoid(np.dot(X, W1) + b1)
output_layer = sigmoid(np.dot(hidden_layer, W2) + b2)
# Compute Error
error = y - output_layer
# Backpropagation
d_output = error * sigmoid_derivative(output_layer)
d_hidden = d_output.dot(W2.T) * sigmoid_derivative(hidden_layer)
# Update Weights and Biases
W2 += hidden_layer.T.dot(d_output) * learning_rate
b2 += np.sum(d_output, axis=0, keepdims=True) * learning_rate
W1 += X.T.dot(d_hidden) * learning_rate
b1 += np.sum(d_hidden, axis=0, keepdims=True) * learning_rate
# Print Loss Occasionally
if epoch % 1000 == 0:
loss = np.mean(np.square(error))
print(f"Epoch {epoch} | Loss: {loss:.4f}")
# Final Prediction
print("🔢 Final Output (NumPy NN):", output_layer)
📝 Explanation:
Forward Pass: Compute outputs layer by layer
Loss Computation: Find the difference between predictions (output_layer) and actual (y)
Backpropagation: Compute gradients to adjust weights (W1, W2)
Gradient Descent Update: Apply small updates to weights in the direction that reduces error
📌 Downside:
We manually coded backpropagation, which is complex 😵
No GPU acceleration (NumPy runs on CPU only)
🔥 Great for learning, but let's move to PyTorch for easier training!
2️⃣ PyTorch: Automatic Differentiation & Backpropagation 🔥
PyTorch automates gradients, making training much simpler!
import torch
import torch.nn as nn
import torch.optim as optim
# Define a Neural Network
class SimpleNN(nn.Module):
def __init__(self):
super(SimpleNN, self).__init__()
self.hidden = nn.Linear(2, 3) # Input → Hidden
self.output = nn.Linear(3, 1) # Hidden → Output
self.activation = nn.Sigmoid()
def forward(self, x):
x = self.activation(self.hidden(x)) # Hidden Layer
x = self.activation(self.output(x)) # Output Layer
return x
# Create Model
model = SimpleNN()
# Training Data (Tensors)
X_torch = torch.tensor([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]], dtype=torch.float32)
y_torch = torch.tensor([[0.3], [0.7], [0.9]], dtype=torch.float32)
# Define Loss Function & Optimizer
criterion = nn.MSELoss() # Mean Squared Error
optimizer = optim.SGD(model.parameters(), lr=0.1) # Stochastic Gradient Descent
# Training Loop
epochs = 5000
for epoch in range(epochs):
optimizer.zero_grad() # Reset Gradients
output = model(X_torch) # Forward Pass
loss = criterion(output, y_torch) # Compute Loss
loss.backward() # Compute Gradients
optimizer.step() # Update Weights
# Print Loss Occasionally
if epoch % 1000 == 0:
print(f"Epoch {epoch} | Loss: {loss.item():.4f}")
# Final Prediction
print("🔢 Final Output (PyTorch NN):", model(X_torch).detach().numpy())
🔥 Why PyTorch?
No manual gradient calculations (loss.backward() does it)
optimizer.step() updates weights automatically
Supports GPUs! (.cuda() moves data to GPU)
3️⃣ TensorFlow/Keras: The Easiest Training Setup 🤖
Keras makes training even simpler with fit().
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
# Create a Simple Model
model = keras.Sequential([
layers.Dense(3, activation="sigmoid", input_shape=(2,)), # Input → Hidden
layers.Dense(1, activation="sigmoid") # Hidden → Output
])
# Compile Model (Loss & Optimizer)
model.compile(optimizer="sgd", loss="mse") # MSE Loss, SGD Optimizer
# Training Data (NumPy Arrays)
X_tf = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]])
y_tf = np.array([[0.3], [0.7], [0.9]])
# Train Model
model.fit(X_tf, y_tf, epochs=5000, verbose=0) # Train for 5000 epochs
# Final Prediction
print("🔢 Final Output (TensorFlow/Keras NN):", model.predict(X_tf))
📝 Why TensorFlow/Keras?
Shortest code!
model.fit() handles training, backpropagation, and updates automatically
Best for production & deployment (runs on mobile, web, and cloud!)
📊 Framework Comparison: NumPy vs PyTorch vs TensorFlow
Feature | NumPy 🛠️ | PyTorch 🔥 | TensorFlow 🤖 |
Ease of Use | Hard (Manual Math) | Medium (Some Automation) | Easy (High-Level) |
Performance | Slow | Fast (GPU Support) | Very Fast (Optimized) |
Best For | Learning Concepts | Research & Flexibility | Production & Deployment |
🚀 Framework Comparison: Training in NumPy, PyTorch, and TensorFlow
Feature | NumPy 🛠️ | PyTorch 🔥 | TensorFlow 🤖 |
Ease of Training | Hard (Manual Gradients) | Medium (Automatic Backprop) | Very Easy (fit()) |
GPU Support | ❌ No | ✅ Yes | ✅ Yes |
Best For | Learning Theory | Research, Flexibility | Production, Deployment |
🎯 Key Takeaways
NumPy teaches how NNs work mathematically 📖
PyTorch gives research flexibility and GPU acceleration 🚀
TensorFlow/Keras is the simplest way to train models quickly 🎯
💡 Summary: How Do These NNs Work?
Each framework does the same thing: Takes an input, applies weights, and gives an output.
NumPy: Fully manual (good for learning).
PyTorch: More flexibility (great for research).
TensorFlow/Keras: Simplest & best for big projects.
🚀 Final Thoughts: Where to Go Next?
Learning Python for scientific computing is a journey, and mastering both functional and object-oriented programming (OOP) will make your code more organized, reusable, and scalable. You don’t need OOP for everything, but when your physics, data science, or machine learning projects grow in complexity, structuring your code properly can save you time and effort.
💡 Next Steps:
Experiment with OOP: Try building your own particle simulation, system model, or data processing pipeline using Python classes.
Explore real-world scientific projects: Check out libraries like Astropy (astronomy), SimPy (discrete event simulation), and SciPy (scientific computing) to see how Python is used professionally.
Apply Python classes in ML & simulations: Machine learning frameworks like PyTorch and TensorFlow rely on OOP, so getting comfortable with it will help when implementing custom models and advanced simulations.
Balance self-learning with tutorials (tutorial hell): Writing your own code is crucial, but don’t avoid tutorials entirely. They’re useful when you're stuck, but the key is to apply what you learn rather than just passively watching.
Ignore the 'vibe coding' critics: If your learning approach is working, stick with it! Whether it's hands-on coding, AI-assisted programming, or structured courses—progress matters more than the method.
And finally, whenever you hit a roadblock—Google it, read the docs, and experiment! That’s how every great coder learns. 😉
Happy coding! 🚀



Comments