24b - Optimization#

import numpy as np
from scipy.optimize import minimize
import matplotlib.pyplot as plt
# Define the cities and their coordinates (x, y)
cities = {
    'A': (0, 0),
    'B': (2, 4),
    'C': (4, 2),
    'D': (6, 5)
}
num_cities = len(cities)
#now plot the city locations
fig,ax = plt.subplots(1,1)
plt.scatter([x for x, y in cities.values()], [y for x, y in cities.values()])
ax.text(0, 0.25, 'A');ax.text(2, 4.25, 'B');ax.text(4, 2.25, 'C');ax.text(6, 4.75, 'D');ax.text(7, 2.75, 'E')
plt.xlabel('x');plt.ylabel('y')
plt.show()
../_images/927f662d365a1105585830a3fcc552b1c429c39a0b115bccb63d4163d7dbdd58.png
# Function to calculate the Euclidean distance between two cities
def calculate_distance(city1, city2):
    x1, y1 = cities[city1]
    x2, y2 = cities[city2]
    return ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5

# Objective function for minimization
def objective_function(tour_order):
    tour_order = tour_order.astype(int)
    total_distance = 0
    startcity = list(cities.keys())[tour_order[0]]
    for i in range(num_cities - 1):
        city1 = list(cities.keys())[tour_order[i]]
        city2 = list(cities.keys())[tour_order[i + 1]]
        total_distance += calculate_distance(city1, city2)
    total_distance += calculate_distance(city2, startcity)  # Return to the starting city
def constraint_fun(solution):
    constrain= []
    # Check that each city is visited only once.
    for i in range(len(solution)):
        for j in range(i + 1, len(solution)):
            if solution[i] == solution[j]:
                constrain.append(1)
            else:
                constrain.append(0)

    return (sum(constrain), sum(solution) - 6)
# Define the bounds for the decision variables
bounds = [(0, num_cities - 1) for _ in range(num_cities)]

# Create a constraints dictionary for use in minimize
cons = {'type': 'eq', 'fun': constraint_fun}

# Initial guess (starting tour order)
initial_tour_order = np.array([1,2,0,3])
# Solve the TSP using scipy.optimize.minimize
minimize(objective_function, initial_tour_order, bounds=bounds, method='SLSQP')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[7], line 2
      1 # Solve the TSP using scipy.optimize.minimize
----> 2 minimize(objective_function, initial_tour_order, bounds=bounds, method='SLSQP')

File ~/opt/anaconda3/envs/jupiterbook/lib/python3.9/site-packages/scipy/optimize/_minimize.py:719, in minimize(fun, x0, args, method, jac, hess, hessp, bounds, constraints, tol, callback, options)
    716     res = _minimize_cobyla(fun, x0, args, constraints, callback=callback,
    717                            bounds=bounds, **options)
    718 elif meth == 'slsqp':
--> 719     res = _minimize_slsqp(fun, x0, args, jac, bounds,
    720                           constraints, callback=callback, **options)
    721 elif meth == 'trust-constr':
    722     res = _minimize_trustregion_constr(fun, x0, args, jac, hess, hessp,
    723                                        bounds, constraints,
    724                                        callback=callback, **options)

File ~/opt/anaconda3/envs/jupiterbook/lib/python3.9/site-packages/scipy/optimize/_slsqp_py.py:374, in _minimize_slsqp(func, x0, args, jac, bounds, constraints, maxiter, ftol, iprint, disp, eps, callback, finite_diff_rel_step, **unknown_options)
    371     xu[infbnd[:, 1]] = np.nan
    373 # ScalarFunction provides function and gradient evaluation
--> 374 sf = _prepare_scalar_function(func, x, jac=jac, args=args, epsilon=eps,
    375                               finite_diff_rel_step=finite_diff_rel_step,
    376                               bounds=new_bounds)
    377 # gh11403 SLSQP sometimes exceeds bounds by 1 or 2 ULP, make sure this
    378 # doesn't get sent to the func/grad evaluator.
    379 wrapped_fun = _clip_x_for_func(sf.fun, new_bounds)

File ~/opt/anaconda3/envs/jupiterbook/lib/python3.9/site-packages/scipy/optimize/_optimize.py:383, in _prepare_scalar_function(fun, x0, jac, args, bounds, epsilon, finite_diff_rel_step, hess)
    379     bounds = (-np.inf, np.inf)
    381 # ScalarFunction caches. Reuse of fun(x) during grad
    382 # calculation reduces overall function evaluations.
--> 383 sf = ScalarFunction(fun, x0, args, grad, hess,
    384                     finite_diff_rel_step, bounds, epsilon=epsilon)
    386 return sf

File ~/opt/anaconda3/envs/jupiterbook/lib/python3.9/site-packages/scipy/optimize/_differentiable_functions.py:158, in ScalarFunction.__init__(self, fun, x0, args, grad, hess, finite_diff_rel_step, finite_diff_bounds, epsilon)
    155     self.f = fun_wrapped(self.x)
    157 self._update_fun_impl = update_fun
--> 158 self._update_fun()
    160 # Gradient evaluation
    161 if callable(grad):

File ~/opt/anaconda3/envs/jupiterbook/lib/python3.9/site-packages/scipy/optimize/_differentiable_functions.py:251, in ScalarFunction._update_fun(self)
    249 def _update_fun(self):
    250     if not self.f_updated:
--> 251         self._update_fun_impl()
    252         self.f_updated = True

File ~/opt/anaconda3/envs/jupiterbook/lib/python3.9/site-packages/scipy/optimize/_differentiable_functions.py:155, in ScalarFunction.__init__.<locals>.update_fun()
    154 def update_fun():
--> 155     self.f = fun_wrapped(self.x)

File ~/opt/anaconda3/envs/jupiterbook/lib/python3.9/site-packages/scipy/optimize/_differentiable_functions.py:148, in ScalarFunction.__init__.<locals>.fun_wrapped(x)
    142     except (TypeError, ValueError) as e:
    143         raise ValueError(
    144             "The user-provided objective function "
    145             "must return a scalar value."
    146         ) from e
--> 148 if fx < self._lowest_f:
    149     self._lowest_x = x
    150     self._lowest_f = fx

TypeError: '<' not supported between instances of 'NoneType' and 'float'
# Get the optimal tour order
optimal_tour_order = result.x.astype(int)

# Calculate the optimal tour distance
optimal_distance = result.fun

# Reorder the cities based on the optimal tour order
optimal_tour = [list(cities.keys())[i] for i in optimal_tour_order]

# Output the optimal tour and distance
print("Optimal Tour:", " -> ".join(optimal_tour))
print("Optimal Distance:", optimal_distance)
#This code is a simple example of a nueral network
# Path: 21c-NeuralNetwork.ipynb
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split

# Generate a dataset and plot it
np.random.seed(0)
X, y = make_blobs(n_samples=1000, centers=2)
fig,ax = plt.subplots(1,1)
ax.scatter(X[:,0], X[:,1], c=y,alpha=0.5)
plt.xlabel('x0');plt.ylabel('x1')
plt.show()

# Split the dataset into training and test sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33)

# Define the neural network architecture

# Define the sigmoid function
def sigmoid(z):
    return 1 / (1 + np.exp(-z)) #np.array([max(0,each) for each in z]) which is relu

# Define the loss function
def loss(y, y_hat):
    return -(y * np.log(y_hat) + (1 - y) * np.log(1 - y_hat)).mean() #np.sum((y-y_hat)**2)#

# Define the forward propagation
def forward_propagation(x, w, b):
    return sigmoid(np.matmul(x, w) + b)

# Define the backward propagation
def backward_propagation(x, y, y_hat):
    return np.matmul(x.T, (y_hat - y)) / y_hat.shape[0], np.mean(y_hat - y)

# Define the training loop
def train(X, y, learning_rate, iters):
    # Initialize the parameters
    w = np.ones(X.shape[1])
    b = 0
    losses = []
    for i in range(iters):
        # Forward propagation
        y_hat = forward_propagation(X, w, b)
        # Backward propagation
        dw, db = backward_propagation(X, y, y_hat)
        # Update parameters
        w -= learning_rate * dw
        b -= learning_rate * db
        # Calculate loss
        losses.append(loss(y, y_hat))
    return w, b, losses

# Train the model
w, b, losses = train(X_train, y_train, 0.1, 1000)

# Plot the loss curve
fig,ax = plt.subplots(1,1)
ax.plot(losses)
plt.xlabel('Iteration');plt.ylabel('Loss')
plt.show()

# Define the predict function
def predict(X, w, b):
    return forward_propagation(X, w, b)

# Predict on the test set
y_pred = predict(X_test, w, b)

# Calculate the accuracy
accuracy = np.mean(y_pred.round() == y_test)
print("Accuracy:", accuracy)

# Plot the decision boundary
fig,ax = plt.subplots(1,1)
ax.scatter(X_test[:,0], X_test[:,1], c=y_pred)
ax.scatter(X_test[:,0], X_test[:,1], s=10, c=y_test, marker="P")
plt.xlabel('x');plt.ylabel('y')
plt.show()

#This code is a simple example of a nueral network
../_images/0beed9e6b7df851476ba98db5de020f75284df567a8ec110575e075d9e6ac303.png ../_images/4bcfbf1fcbcb1bd0e86dcfefee028f599a6cf3178a298c5a378b3ed90fa0fd0c.png
Accuracy: 0.9454545454545454
../_images/71d6d74ef4d30ccea8467a64462a447c4f252b73dc33b49d3357681e891508c8.png
w,b
(array([ 1.38209755, -1.85683344]), 2.464299200765141)
import numpy as np

# Sigmoid activation function
def sigmoid(x):
    return 1 / (1 + np.exp(-x))

# Derivative of the sigmoid function
def sigmoid_derivative(x):
    return x * (1 - x)

# Initialize the neural network architecture
input_size = 2
hidden_size = 2
output_size = 1
learning_rate = 0.1

# Initialize weights and biases
input_layer = np.random.rand(input_size, 1)
hidden_layer_weights = np.random.rand(input_size, hidden_size)
hidden_layer_bias = np.random.rand(1, hidden_size)
output_layer_weights = np.random.rand(hidden_size, output_size)
output_layer_bias = np.random.rand(1, output_size)

# Sample input data
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
# Corresponding target data
y = np.array([[0], [1], [1], [0]])

# Training loop
epochs = 10000
for epoch in range(epochs):
    # Forward propagation
    hidden_layer_input = np.dot(X, hidden_layer_weights) + hidden_layer_bias
    hidden_layer_output = sigmoid(hidden_layer_input)
    output_layer_input = np.dot(hidden_layer_output, output_layer_weights) + output_layer_bias
    output_layer_output = sigmoid(output_layer_input)

    # Calculate the loss
    loss = 0.5 * np.mean((y - output_layer_output) ** 2)

    # Backpropagation
    d_output = (y - output_layer_output) * sigmoid_derivative(output_layer_output)
    d_hidden = d_output.dot(output_layer_weights.T) * sigmoid_derivative(hidden_layer_output)

    # Update weights and biases
    output_layer_weights += hidden_layer_output.T.dot(d_output) * learning_rate
    output_layer_bias += np.sum(d_output, axis=0, keepdims=True) * learning_rate
    hidden_layer_weights += X.T.dot(d_hidden) * learning_rate
    hidden_layer_bias += np.sum(d_hidden, axis=0, keepdims=True) * learning_rate

    if epoch % 1000 == 0:
        print(f'Epoch {epoch}: Loss {loss}')

# Print the final predictions
final_predictions = output_layer_output
for i in range(len(X)):
    print(f"Input: {X[i]}, Predicted Output: {final_predictions[i]}")
Epoch 0: Loss 0.1676768296300326
Epoch 1000: Loss 0.11847158839028199
Epoch 2000: Loss 0.0974566507207803
Epoch 3000: Loss 0.0740089517739545
Epoch 4000: Loss 0.02156696325278639
Epoch 5000: Loss 0.007507287436516607
Epoch 6000: Loss 0.004118474157960643
Epoch 7000: Loss 0.0027544246973760966
Epoch 8000: Loss 0.0020421072420632753
Epoch 9000: Loss 0.0016110497809955966
Input: [0 0], Predicted Output: [0.05391764]
Input: [0 1], Predicted Output: [0.95062808]
Input: [1 0], Predicted Output: [0.9507009]
Input: [1 1], Predicted Output: [0.05313195]
hidden_layer_weights
array([[5.9149746 , 3.74818997],
       [5.88987791, 3.74328878]])