Menu iconMenu iconGenerative Deep Learning Updated Edition
Generative Deep Learning Updated Edition

Chapter 1: Introduction to Deep Learning

1.4 Practical Exercises - Chapter 1: Introduction to Deep Learning

Exercise 1: Implement a Simple Neural Network

Task: Implement a simple neural network using the code provided in section 1.1.1. Modify the network to include an additional hidden layer and observe how the performance changes.

Solution:

import numpy as np

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

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

# Input data (4 samples, 3 features each)
inputs = np.array([[0, 0, 1],
                   [1, 1, 1],
                   [1, 0, 1],
                   [0, 1, 1]])

# Output labels (4 samples, 1 output each)
outputs = np.array([[0], [1], [1], [0]])

# Seed for reproducibility
np.random.seed(1)

# Initialize weights randomly with mean 0
weights_input_hidden1 = 2 * np.random.random((3, 4)) - 1
weights_hidden1_hidden2 = 2 * np.random.random((4, 4)) - 1
weights_hidden2_output = 2 * np.random.random((4, 1)) - 1

# Training the neural network
for epoch in range(10000):
    # Forward propagation
    input_layer = inputs
    hidden_layer1 = sigmoid(np.dot(input_layer, weights_input_hidden1))
    hidden_layer2 = sigmoid(np.dot(hidden_layer1, weights_hidden1_hidden2))
    output_layer = sigmoid(np.dot(hidden_layer2, weights_hidden2_output))

    # Error calculation
    error = outputs - output_layer

    # Backward propagation
    output_layer_delta = error * sigmoid_derivative(output_layer)
    hidden_layer2_error = output_layer_delta.dot(weights_hidden2_output.T)
    hidden_layer2_delta = hidden_layer2_error * sigmoid_derivative(hidden_layer2)
    hidden_layer1_error = hidden_layer2_delta.dot(weights_hidden1_hidden2.T)
    hidden_layer1_delta = hidden_layer1_error * sigmoid_derivative(hidden_layer1)

    # Update weights
    weights_hidden2_output += hidden_layer2.T.dot(output_layer_delta)
    weights_hidden1_hidden2 += hidden_layer1.T.dot(hidden_layer2_delta)
    weights_input_hidden1 += input_layer.T.dot(hidden_layer1_delta)

print("Output after training:")
print(output_layer)

Exercise 2: Implement a ReLU Activation Function

Task: Implement a ReLU activation function and apply it to a simple neural network. Compare the results with the sigmoid activation function.

Solution:

import numpy as np

# ReLU activation function
def relu(x):
    return np.maximum(0, x)

# Derivative of ReLU function
def relu_derivative(x):
    return np.where(x > 0, 1, 0)

# Input data (4 samples, 3 features each)
inputs = np.array([[0, 0, 1],
                   [1, 1, 1],
                   [1, 0, 1],
                   [0, 1, 1]])

# Output labels (4 samples, 1 output each)
outputs = np.array([[0], [1], [1], [0]])

# Seed for reproducibility
np.random.seed(1)

# Initialize weights randomly with mean 0
weights_input_hidden = 2 * np.random.random((3, 4)) - 1
weights_hidden_output = 2 * np.random.random((4, 1)) - 1

# Training the neural network
for epoch in range(10000):
    # Forward propagation
    input_layer = inputs
    hidden_layer = relu(np.dot(input_layer, weights_input_hidden))
    output_layer = relu(np.dot(hidden_layer, weights_hidden_output))

    # Error calculation
    error = outputs - output_layer

    # Backward propagation
    output_layer_delta = error * relu_derivative(output_layer)
    hidden_layer_error = output_layer_delta.dot(weights_hidden_output.T)
    hidden_layer_delta = hidden_layer_error * relu_derivative(hidden_layer)

    # Update weights
    weights_hidden_output += hidden_layer.T.dot(output_layer_delta)
    weights_input_hidden += input_layer.T.dot(hidden_layer_delta)

print("Output after training:")
print(output_layer)

Exercise 3: Fine-Tune a Pre-trained BERT Model

Task: Fine-tune a pre-trained BERT model for a text classification task using the provided example in section 1.3.2. Use a different dataset for this exercise.

Solution:

from transformers import BertTokenizer, TFBertForSequenceClassification
from tensorflow.keras.optimizers import Adam
import tensorflow as tf

# Load pre-trained BERT model and tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = TFBertForSequenceClassification.from_pretrained('bert-base-uncased')

# Sample data
texts = ["I love this product!", "This is the worst experience I've ever had.", "It was okay, not great."]
labels = [1, 0, 1]  # 1 for positive, 0 for negative

# Tokenize the input texts
inputs = tokenizer(texts, return_tensors='tf', padding=True, truncation=True, max_length=128)

# Convert labels to tensor
labels = tf.convert_to_tensor(labels)

# Compile the model
optimizer = Adam(learning_rate=2e-5)
model.compile(optimizer=optimizer, loss=model.compute_loss, metrics=['accuracy'])

# Train the model
model.fit(inputs['input_ids'], labels, epochs=3, batch_size=8)

# Evaluate the model
predictions = model.predict(inputs['input_ids'])
print(predictions)

Exercise 4: Implement a Basic GAN

Task:
Implement a basic GAN for generating synthetic data. Follow the example provided in section 1.3.3 and generate new samples after training the GAN.

Solution:

import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten
from tensorflow.keras.models import Sequential
import numpy as np

# Generator model
def build_generator():
    model = Sequential([
        Dense(128, input_dim=100),
        LeakyReLU(alpha=0.01),
        Dense(784, activation='tanh'),
        Reshape((28, 28, 1))
    ])
    return model

# Discriminator model
def build_discriminator():
    model = Sequential([
        Flatten(input_shape=(28, 28, 1)),
        Dense(128),
        LeakyReLU(alpha=0.01),
        Dense(1, activation='sigmoid')
    ])
    return model

# Build and compile the GAN
generator = build_generator()
discriminator = build_discriminator()
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# GAN model
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(100,))
gan_output = discriminator(generator(gan_input))
gan = tf.keras.Model(gan_input, gan_output)
gan.compile(optimizer='adam', loss='binary_crossentropy')

# Training the GAN
(x_train, _), (_, _) = tf.keras.datasets.mnist.load_data()
x_train = (x_train.astype(np.float32) - 127.5) / 127.5  # Normalize to [-1, 1]
x_train = np.expand_dims(x_train, axis=-1)
batch_size = 128
epochs = 10000

for epoch in range(epochs):
    # Train discriminator
    idx = np.random.randint(0, x_train.shape[0], batch_size)
    real_images = x_train[idx]
    noise = np.random.normal(0, 1, (batch_size, 100))
    fake_images = generator.predict(noise)
    d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
    d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

    # Train generator
    noise = np.random.normal(0, 1, (batch_size, 100))
    g_loss = gan.train_on_batch(noise, np.ones((batch_size, 1)))

    # Print progress
    if epoch % 1000 == 0:
        print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")

# Generate new samples
noise = np.random.normal(0, 1, (10, 100))
generated_images = generator.predict(noise)
print(generated_images)

Exercise 5: Implement Q-Learning for a Simple Environment

Task: Implement Q-Learning for a simple grid world environment. Follow the example provided in section 1.3.4 and extend the grid size or modify the reward structure.

Solution:

import numpy as np

# Environment setup
grid_size = 5  # Extended grid size
rewards = np.zeros((grid_size, grid_size))
rewards[4, 4] = 1  # New goal state

# Q-Learning parameters
gamma = 0.9  # Discount factor
alpha = 0.1  # Learning rate
epsilon = 0.1  # Exploration rate
q_table = np.zeros((grid_size, grid_size, 4))  # Q-table for 4 actions

# Action

 selection
def choose_action(state):
    if np.random.rand() < epsilon:
        return np.random.randint(4)
    return np.argmax(q_table[state])

# Q-Learning algorithm
for episode in range(1000):
    state = (0, 0)
    while state != (4, 4):
        action = choose_action(state)
        next_state = (max(0, min(grid_size-1, state[0] + (action == 1) - (action == 0))),
                      max(0, min(grid_size-1, state[1] + (action == 3) - (action == 2))))
        reward = rewards[next_state]
        td_target = reward + gamma * np.max(q_table[next_state])
        td_error = td_target - q_table[state][action]
        q_table[state][action] += alpha * td_error
        state = next_state

print("Trained Q-Table:")
print(q_table)

These exercises should help reinforce your understanding of the concepts covered in this chapter. By implementing these models and experimenting with different configurations, you'll gain hands-on experience with deep learning techniques and their practical applications. Keep practicing, and don't hesitate to explore further on your own!

1.4 Practical Exercises - Chapter 1: Introduction to Deep Learning

Exercise 1: Implement a Simple Neural Network

Task: Implement a simple neural network using the code provided in section 1.1.1. Modify the network to include an additional hidden layer and observe how the performance changes.

Solution:

import numpy as np

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

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

# Input data (4 samples, 3 features each)
inputs = np.array([[0, 0, 1],
                   [1, 1, 1],
                   [1, 0, 1],
                   [0, 1, 1]])

# Output labels (4 samples, 1 output each)
outputs = np.array([[0], [1], [1], [0]])

# Seed for reproducibility
np.random.seed(1)

# Initialize weights randomly with mean 0
weights_input_hidden1 = 2 * np.random.random((3, 4)) - 1
weights_hidden1_hidden2 = 2 * np.random.random((4, 4)) - 1
weights_hidden2_output = 2 * np.random.random((4, 1)) - 1

# Training the neural network
for epoch in range(10000):
    # Forward propagation
    input_layer = inputs
    hidden_layer1 = sigmoid(np.dot(input_layer, weights_input_hidden1))
    hidden_layer2 = sigmoid(np.dot(hidden_layer1, weights_hidden1_hidden2))
    output_layer = sigmoid(np.dot(hidden_layer2, weights_hidden2_output))

    # Error calculation
    error = outputs - output_layer

    # Backward propagation
    output_layer_delta = error * sigmoid_derivative(output_layer)
    hidden_layer2_error = output_layer_delta.dot(weights_hidden2_output.T)
    hidden_layer2_delta = hidden_layer2_error * sigmoid_derivative(hidden_layer2)
    hidden_layer1_error = hidden_layer2_delta.dot(weights_hidden1_hidden2.T)
    hidden_layer1_delta = hidden_layer1_error * sigmoid_derivative(hidden_layer1)

    # Update weights
    weights_hidden2_output += hidden_layer2.T.dot(output_layer_delta)
    weights_hidden1_hidden2 += hidden_layer1.T.dot(hidden_layer2_delta)
    weights_input_hidden1 += input_layer.T.dot(hidden_layer1_delta)

print("Output after training:")
print(output_layer)

Exercise 2: Implement a ReLU Activation Function

Task: Implement a ReLU activation function and apply it to a simple neural network. Compare the results with the sigmoid activation function.

Solution:

import numpy as np

# ReLU activation function
def relu(x):
    return np.maximum(0, x)

# Derivative of ReLU function
def relu_derivative(x):
    return np.where(x > 0, 1, 0)

# Input data (4 samples, 3 features each)
inputs = np.array([[0, 0, 1],
                   [1, 1, 1],
                   [1, 0, 1],
                   [0, 1, 1]])

# Output labels (4 samples, 1 output each)
outputs = np.array([[0], [1], [1], [0]])

# Seed for reproducibility
np.random.seed(1)

# Initialize weights randomly with mean 0
weights_input_hidden = 2 * np.random.random((3, 4)) - 1
weights_hidden_output = 2 * np.random.random((4, 1)) - 1

# Training the neural network
for epoch in range(10000):
    # Forward propagation
    input_layer = inputs
    hidden_layer = relu(np.dot(input_layer, weights_input_hidden))
    output_layer = relu(np.dot(hidden_layer, weights_hidden_output))

    # Error calculation
    error = outputs - output_layer

    # Backward propagation
    output_layer_delta = error * relu_derivative(output_layer)
    hidden_layer_error = output_layer_delta.dot(weights_hidden_output.T)
    hidden_layer_delta = hidden_layer_error * relu_derivative(hidden_layer)

    # Update weights
    weights_hidden_output += hidden_layer.T.dot(output_layer_delta)
    weights_input_hidden += input_layer.T.dot(hidden_layer_delta)

print("Output after training:")
print(output_layer)

Exercise 3: Fine-Tune a Pre-trained BERT Model

Task: Fine-tune a pre-trained BERT model for a text classification task using the provided example in section 1.3.2. Use a different dataset for this exercise.

Solution:

from transformers import BertTokenizer, TFBertForSequenceClassification
from tensorflow.keras.optimizers import Adam
import tensorflow as tf

# Load pre-trained BERT model and tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = TFBertForSequenceClassification.from_pretrained('bert-base-uncased')

# Sample data
texts = ["I love this product!", "This is the worst experience I've ever had.", "It was okay, not great."]
labels = [1, 0, 1]  # 1 for positive, 0 for negative

# Tokenize the input texts
inputs = tokenizer(texts, return_tensors='tf', padding=True, truncation=True, max_length=128)

# Convert labels to tensor
labels = tf.convert_to_tensor(labels)

# Compile the model
optimizer = Adam(learning_rate=2e-5)
model.compile(optimizer=optimizer, loss=model.compute_loss, metrics=['accuracy'])

# Train the model
model.fit(inputs['input_ids'], labels, epochs=3, batch_size=8)

# Evaluate the model
predictions = model.predict(inputs['input_ids'])
print(predictions)

Exercise 4: Implement a Basic GAN

Task:
Implement a basic GAN for generating synthetic data. Follow the example provided in section 1.3.3 and generate new samples after training the GAN.

Solution:

import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten
from tensorflow.keras.models import Sequential
import numpy as np

# Generator model
def build_generator():
    model = Sequential([
        Dense(128, input_dim=100),
        LeakyReLU(alpha=0.01),
        Dense(784, activation='tanh'),
        Reshape((28, 28, 1))
    ])
    return model

# Discriminator model
def build_discriminator():
    model = Sequential([
        Flatten(input_shape=(28, 28, 1)),
        Dense(128),
        LeakyReLU(alpha=0.01),
        Dense(1, activation='sigmoid')
    ])
    return model

# Build and compile the GAN
generator = build_generator()
discriminator = build_discriminator()
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# GAN model
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(100,))
gan_output = discriminator(generator(gan_input))
gan = tf.keras.Model(gan_input, gan_output)
gan.compile(optimizer='adam', loss='binary_crossentropy')

# Training the GAN
(x_train, _), (_, _) = tf.keras.datasets.mnist.load_data()
x_train = (x_train.astype(np.float32) - 127.5) / 127.5  # Normalize to [-1, 1]
x_train = np.expand_dims(x_train, axis=-1)
batch_size = 128
epochs = 10000

for epoch in range(epochs):
    # Train discriminator
    idx = np.random.randint(0, x_train.shape[0], batch_size)
    real_images = x_train[idx]
    noise = np.random.normal(0, 1, (batch_size, 100))
    fake_images = generator.predict(noise)
    d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
    d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

    # Train generator
    noise = np.random.normal(0, 1, (batch_size, 100))
    g_loss = gan.train_on_batch(noise, np.ones((batch_size, 1)))

    # Print progress
    if epoch % 1000 == 0:
        print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")

# Generate new samples
noise = np.random.normal(0, 1, (10, 100))
generated_images = generator.predict(noise)
print(generated_images)

Exercise 5: Implement Q-Learning for a Simple Environment

Task: Implement Q-Learning for a simple grid world environment. Follow the example provided in section 1.3.4 and extend the grid size or modify the reward structure.

Solution:

import numpy as np

# Environment setup
grid_size = 5  # Extended grid size
rewards = np.zeros((grid_size, grid_size))
rewards[4, 4] = 1  # New goal state

# Q-Learning parameters
gamma = 0.9  # Discount factor
alpha = 0.1  # Learning rate
epsilon = 0.1  # Exploration rate
q_table = np.zeros((grid_size, grid_size, 4))  # Q-table for 4 actions

# Action

 selection
def choose_action(state):
    if np.random.rand() < epsilon:
        return np.random.randint(4)
    return np.argmax(q_table[state])

# Q-Learning algorithm
for episode in range(1000):
    state = (0, 0)
    while state != (4, 4):
        action = choose_action(state)
        next_state = (max(0, min(grid_size-1, state[0] + (action == 1) - (action == 0))),
                      max(0, min(grid_size-1, state[1] + (action == 3) - (action == 2))))
        reward = rewards[next_state]
        td_target = reward + gamma * np.max(q_table[next_state])
        td_error = td_target - q_table[state][action]
        q_table[state][action] += alpha * td_error
        state = next_state

print("Trained Q-Table:")
print(q_table)

These exercises should help reinforce your understanding of the concepts covered in this chapter. By implementing these models and experimenting with different configurations, you'll gain hands-on experience with deep learning techniques and their practical applications. Keep practicing, and don't hesitate to explore further on your own!

1.4 Practical Exercises - Chapter 1: Introduction to Deep Learning

Exercise 1: Implement a Simple Neural Network

Task: Implement a simple neural network using the code provided in section 1.1.1. Modify the network to include an additional hidden layer and observe how the performance changes.

Solution:

import numpy as np

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

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

# Input data (4 samples, 3 features each)
inputs = np.array([[0, 0, 1],
                   [1, 1, 1],
                   [1, 0, 1],
                   [0, 1, 1]])

# Output labels (4 samples, 1 output each)
outputs = np.array([[0], [1], [1], [0]])

# Seed for reproducibility
np.random.seed(1)

# Initialize weights randomly with mean 0
weights_input_hidden1 = 2 * np.random.random((3, 4)) - 1
weights_hidden1_hidden2 = 2 * np.random.random((4, 4)) - 1
weights_hidden2_output = 2 * np.random.random((4, 1)) - 1

# Training the neural network
for epoch in range(10000):
    # Forward propagation
    input_layer = inputs
    hidden_layer1 = sigmoid(np.dot(input_layer, weights_input_hidden1))
    hidden_layer2 = sigmoid(np.dot(hidden_layer1, weights_hidden1_hidden2))
    output_layer = sigmoid(np.dot(hidden_layer2, weights_hidden2_output))

    # Error calculation
    error = outputs - output_layer

    # Backward propagation
    output_layer_delta = error * sigmoid_derivative(output_layer)
    hidden_layer2_error = output_layer_delta.dot(weights_hidden2_output.T)
    hidden_layer2_delta = hidden_layer2_error * sigmoid_derivative(hidden_layer2)
    hidden_layer1_error = hidden_layer2_delta.dot(weights_hidden1_hidden2.T)
    hidden_layer1_delta = hidden_layer1_error * sigmoid_derivative(hidden_layer1)

    # Update weights
    weights_hidden2_output += hidden_layer2.T.dot(output_layer_delta)
    weights_hidden1_hidden2 += hidden_layer1.T.dot(hidden_layer2_delta)
    weights_input_hidden1 += input_layer.T.dot(hidden_layer1_delta)

print("Output after training:")
print(output_layer)

Exercise 2: Implement a ReLU Activation Function

Task: Implement a ReLU activation function and apply it to a simple neural network. Compare the results with the sigmoid activation function.

Solution:

import numpy as np

# ReLU activation function
def relu(x):
    return np.maximum(0, x)

# Derivative of ReLU function
def relu_derivative(x):
    return np.where(x > 0, 1, 0)

# Input data (4 samples, 3 features each)
inputs = np.array([[0, 0, 1],
                   [1, 1, 1],
                   [1, 0, 1],
                   [0, 1, 1]])

# Output labels (4 samples, 1 output each)
outputs = np.array([[0], [1], [1], [0]])

# Seed for reproducibility
np.random.seed(1)

# Initialize weights randomly with mean 0
weights_input_hidden = 2 * np.random.random((3, 4)) - 1
weights_hidden_output = 2 * np.random.random((4, 1)) - 1

# Training the neural network
for epoch in range(10000):
    # Forward propagation
    input_layer = inputs
    hidden_layer = relu(np.dot(input_layer, weights_input_hidden))
    output_layer = relu(np.dot(hidden_layer, weights_hidden_output))

    # Error calculation
    error = outputs - output_layer

    # Backward propagation
    output_layer_delta = error * relu_derivative(output_layer)
    hidden_layer_error = output_layer_delta.dot(weights_hidden_output.T)
    hidden_layer_delta = hidden_layer_error * relu_derivative(hidden_layer)

    # Update weights
    weights_hidden_output += hidden_layer.T.dot(output_layer_delta)
    weights_input_hidden += input_layer.T.dot(hidden_layer_delta)

print("Output after training:")
print(output_layer)

Exercise 3: Fine-Tune a Pre-trained BERT Model

Task: Fine-tune a pre-trained BERT model for a text classification task using the provided example in section 1.3.2. Use a different dataset for this exercise.

Solution:

from transformers import BertTokenizer, TFBertForSequenceClassification
from tensorflow.keras.optimizers import Adam
import tensorflow as tf

# Load pre-trained BERT model and tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = TFBertForSequenceClassification.from_pretrained('bert-base-uncased')

# Sample data
texts = ["I love this product!", "This is the worst experience I've ever had.", "It was okay, not great."]
labels = [1, 0, 1]  # 1 for positive, 0 for negative

# Tokenize the input texts
inputs = tokenizer(texts, return_tensors='tf', padding=True, truncation=True, max_length=128)

# Convert labels to tensor
labels = tf.convert_to_tensor(labels)

# Compile the model
optimizer = Adam(learning_rate=2e-5)
model.compile(optimizer=optimizer, loss=model.compute_loss, metrics=['accuracy'])

# Train the model
model.fit(inputs['input_ids'], labels, epochs=3, batch_size=8)

# Evaluate the model
predictions = model.predict(inputs['input_ids'])
print(predictions)

Exercise 4: Implement a Basic GAN

Task:
Implement a basic GAN for generating synthetic data. Follow the example provided in section 1.3.3 and generate new samples after training the GAN.

Solution:

import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten
from tensorflow.keras.models import Sequential
import numpy as np

# Generator model
def build_generator():
    model = Sequential([
        Dense(128, input_dim=100),
        LeakyReLU(alpha=0.01),
        Dense(784, activation='tanh'),
        Reshape((28, 28, 1))
    ])
    return model

# Discriminator model
def build_discriminator():
    model = Sequential([
        Flatten(input_shape=(28, 28, 1)),
        Dense(128),
        LeakyReLU(alpha=0.01),
        Dense(1, activation='sigmoid')
    ])
    return model

# Build and compile the GAN
generator = build_generator()
discriminator = build_discriminator()
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# GAN model
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(100,))
gan_output = discriminator(generator(gan_input))
gan = tf.keras.Model(gan_input, gan_output)
gan.compile(optimizer='adam', loss='binary_crossentropy')

# Training the GAN
(x_train, _), (_, _) = tf.keras.datasets.mnist.load_data()
x_train = (x_train.astype(np.float32) - 127.5) / 127.5  # Normalize to [-1, 1]
x_train = np.expand_dims(x_train, axis=-1)
batch_size = 128
epochs = 10000

for epoch in range(epochs):
    # Train discriminator
    idx = np.random.randint(0, x_train.shape[0], batch_size)
    real_images = x_train[idx]
    noise = np.random.normal(0, 1, (batch_size, 100))
    fake_images = generator.predict(noise)
    d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
    d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

    # Train generator
    noise = np.random.normal(0, 1, (batch_size, 100))
    g_loss = gan.train_on_batch(noise, np.ones((batch_size, 1)))

    # Print progress
    if epoch % 1000 == 0:
        print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")

# Generate new samples
noise = np.random.normal(0, 1, (10, 100))
generated_images = generator.predict(noise)
print(generated_images)

Exercise 5: Implement Q-Learning for a Simple Environment

Task: Implement Q-Learning for a simple grid world environment. Follow the example provided in section 1.3.4 and extend the grid size or modify the reward structure.

Solution:

import numpy as np

# Environment setup
grid_size = 5  # Extended grid size
rewards = np.zeros((grid_size, grid_size))
rewards[4, 4] = 1  # New goal state

# Q-Learning parameters
gamma = 0.9  # Discount factor
alpha = 0.1  # Learning rate
epsilon = 0.1  # Exploration rate
q_table = np.zeros((grid_size, grid_size, 4))  # Q-table for 4 actions

# Action

 selection
def choose_action(state):
    if np.random.rand() < epsilon:
        return np.random.randint(4)
    return np.argmax(q_table[state])

# Q-Learning algorithm
for episode in range(1000):
    state = (0, 0)
    while state != (4, 4):
        action = choose_action(state)
        next_state = (max(0, min(grid_size-1, state[0] + (action == 1) - (action == 0))),
                      max(0, min(grid_size-1, state[1] + (action == 3) - (action == 2))))
        reward = rewards[next_state]
        td_target = reward + gamma * np.max(q_table[next_state])
        td_error = td_target - q_table[state][action]
        q_table[state][action] += alpha * td_error
        state = next_state

print("Trained Q-Table:")
print(q_table)

These exercises should help reinforce your understanding of the concepts covered in this chapter. By implementing these models and experimenting with different configurations, you'll gain hands-on experience with deep learning techniques and their practical applications. Keep practicing, and don't hesitate to explore further on your own!

1.4 Practical Exercises - Chapter 1: Introduction to Deep Learning

Exercise 1: Implement a Simple Neural Network

Task: Implement a simple neural network using the code provided in section 1.1.1. Modify the network to include an additional hidden layer and observe how the performance changes.

Solution:

import numpy as np

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

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

# Input data (4 samples, 3 features each)
inputs = np.array([[0, 0, 1],
                   [1, 1, 1],
                   [1, 0, 1],
                   [0, 1, 1]])

# Output labels (4 samples, 1 output each)
outputs = np.array([[0], [1], [1], [0]])

# Seed for reproducibility
np.random.seed(1)

# Initialize weights randomly with mean 0
weights_input_hidden1 = 2 * np.random.random((3, 4)) - 1
weights_hidden1_hidden2 = 2 * np.random.random((4, 4)) - 1
weights_hidden2_output = 2 * np.random.random((4, 1)) - 1

# Training the neural network
for epoch in range(10000):
    # Forward propagation
    input_layer = inputs
    hidden_layer1 = sigmoid(np.dot(input_layer, weights_input_hidden1))
    hidden_layer2 = sigmoid(np.dot(hidden_layer1, weights_hidden1_hidden2))
    output_layer = sigmoid(np.dot(hidden_layer2, weights_hidden2_output))

    # Error calculation
    error = outputs - output_layer

    # Backward propagation
    output_layer_delta = error * sigmoid_derivative(output_layer)
    hidden_layer2_error = output_layer_delta.dot(weights_hidden2_output.T)
    hidden_layer2_delta = hidden_layer2_error * sigmoid_derivative(hidden_layer2)
    hidden_layer1_error = hidden_layer2_delta.dot(weights_hidden1_hidden2.T)
    hidden_layer1_delta = hidden_layer1_error * sigmoid_derivative(hidden_layer1)

    # Update weights
    weights_hidden2_output += hidden_layer2.T.dot(output_layer_delta)
    weights_hidden1_hidden2 += hidden_layer1.T.dot(hidden_layer2_delta)
    weights_input_hidden1 += input_layer.T.dot(hidden_layer1_delta)

print("Output after training:")
print(output_layer)

Exercise 2: Implement a ReLU Activation Function

Task: Implement a ReLU activation function and apply it to a simple neural network. Compare the results with the sigmoid activation function.

Solution:

import numpy as np

# ReLU activation function
def relu(x):
    return np.maximum(0, x)

# Derivative of ReLU function
def relu_derivative(x):
    return np.where(x > 0, 1, 0)

# Input data (4 samples, 3 features each)
inputs = np.array([[0, 0, 1],
                   [1, 1, 1],
                   [1, 0, 1],
                   [0, 1, 1]])

# Output labels (4 samples, 1 output each)
outputs = np.array([[0], [1], [1], [0]])

# Seed for reproducibility
np.random.seed(1)

# Initialize weights randomly with mean 0
weights_input_hidden = 2 * np.random.random((3, 4)) - 1
weights_hidden_output = 2 * np.random.random((4, 1)) - 1

# Training the neural network
for epoch in range(10000):
    # Forward propagation
    input_layer = inputs
    hidden_layer = relu(np.dot(input_layer, weights_input_hidden))
    output_layer = relu(np.dot(hidden_layer, weights_hidden_output))

    # Error calculation
    error = outputs - output_layer

    # Backward propagation
    output_layer_delta = error * relu_derivative(output_layer)
    hidden_layer_error = output_layer_delta.dot(weights_hidden_output.T)
    hidden_layer_delta = hidden_layer_error * relu_derivative(hidden_layer)

    # Update weights
    weights_hidden_output += hidden_layer.T.dot(output_layer_delta)
    weights_input_hidden += input_layer.T.dot(hidden_layer_delta)

print("Output after training:")
print(output_layer)

Exercise 3: Fine-Tune a Pre-trained BERT Model

Task: Fine-tune a pre-trained BERT model for a text classification task using the provided example in section 1.3.2. Use a different dataset for this exercise.

Solution:

from transformers import BertTokenizer, TFBertForSequenceClassification
from tensorflow.keras.optimizers import Adam
import tensorflow as tf

# Load pre-trained BERT model and tokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = TFBertForSequenceClassification.from_pretrained('bert-base-uncased')

# Sample data
texts = ["I love this product!", "This is the worst experience I've ever had.", "It was okay, not great."]
labels = [1, 0, 1]  # 1 for positive, 0 for negative

# Tokenize the input texts
inputs = tokenizer(texts, return_tensors='tf', padding=True, truncation=True, max_length=128)

# Convert labels to tensor
labels = tf.convert_to_tensor(labels)

# Compile the model
optimizer = Adam(learning_rate=2e-5)
model.compile(optimizer=optimizer, loss=model.compute_loss, metrics=['accuracy'])

# Train the model
model.fit(inputs['input_ids'], labels, epochs=3, batch_size=8)

# Evaluate the model
predictions = model.predict(inputs['input_ids'])
print(predictions)

Exercise 4: Implement a Basic GAN

Task:
Implement a basic GAN for generating synthetic data. Follow the example provided in section 1.3.3 and generate new samples after training the GAN.

Solution:

import tensorflow as tf
from tensorflow.keras.layers import Dense, LeakyReLU, Reshape, Flatten
from tensorflow.keras.models import Sequential
import numpy as np

# Generator model
def build_generator():
    model = Sequential([
        Dense(128, input_dim=100),
        LeakyReLU(alpha=0.01),
        Dense(784, activation='tanh'),
        Reshape((28, 28, 1))
    ])
    return model

# Discriminator model
def build_discriminator():
    model = Sequential([
        Flatten(input_shape=(28, 28, 1)),
        Dense(128),
        LeakyReLU(alpha=0.01),
        Dense(1, activation='sigmoid')
    ])
    return model

# Build and compile the GAN
generator = build_generator()
discriminator = build_discriminator()
discriminator.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# GAN model
discriminator.trainable = False
gan_input = tf.keras.Input(shape=(100,))
gan_output = discriminator(generator(gan_input))
gan = tf.keras.Model(gan_input, gan_output)
gan.compile(optimizer='adam', loss='binary_crossentropy')

# Training the GAN
(x_train, _), (_, _) = tf.keras.datasets.mnist.load_data()
x_train = (x_train.astype(np.float32) - 127.5) / 127.5  # Normalize to [-1, 1]
x_train = np.expand_dims(x_train, axis=-1)
batch_size = 128
epochs = 10000

for epoch in range(epochs):
    # Train discriminator
    idx = np.random.randint(0, x_train.shape[0], batch_size)
    real_images = x_train[idx]
    noise = np.random.normal(0, 1, (batch_size, 100))
    fake_images = generator.predict(noise)
    d_loss_real = discriminator.train_on_batch(real_images, np.ones((batch_size, 1)))
    d_loss_fake = discriminator.train_on_batch(fake_images, np.zeros((batch_size, 1)))
    d_loss = 0.5 * np.add(d_loss_real, d_loss_fake)

    # Train generator
    noise = np.random.normal(0, 1, (batch_size, 100))
    g_loss = gan.train_on_batch(noise, np.ones((batch_size, 1)))

    # Print progress
    if epoch % 1000 == 0:
        print(f"{epoch} [D loss: {d_loss[0]}, acc.: {d_loss[1] * 100}%] [G loss: {g_loss}]")

# Generate new samples
noise = np.random.normal(0, 1, (10, 100))
generated_images = generator.predict(noise)
print(generated_images)

Exercise 5: Implement Q-Learning for a Simple Environment

Task: Implement Q-Learning for a simple grid world environment. Follow the example provided in section 1.3.4 and extend the grid size or modify the reward structure.

Solution:

import numpy as np

# Environment setup
grid_size = 5  # Extended grid size
rewards = np.zeros((grid_size, grid_size))
rewards[4, 4] = 1  # New goal state

# Q-Learning parameters
gamma = 0.9  # Discount factor
alpha = 0.1  # Learning rate
epsilon = 0.1  # Exploration rate
q_table = np.zeros((grid_size, grid_size, 4))  # Q-table for 4 actions

# Action

 selection
def choose_action(state):
    if np.random.rand() < epsilon:
        return np.random.randint(4)
    return np.argmax(q_table[state])

# Q-Learning algorithm
for episode in range(1000):
    state = (0, 0)
    while state != (4, 4):
        action = choose_action(state)
        next_state = (max(0, min(grid_size-1, state[0] + (action == 1) - (action == 0))),
                      max(0, min(grid_size-1, state[1] + (action == 3) - (action == 2))))
        reward = rewards[next_state]
        td_target = reward + gamma * np.max(q_table[next_state])
        td_error = td_target - q_table[state][action]
        q_table[state][action] += alpha * td_error
        state = next_state

print("Trained Q-Table:")
print(q_table)

These exercises should help reinforce your understanding of the concepts covered in this chapter. By implementing these models and experimenting with different configurations, you'll gain hands-on experience with deep learning techniques and their practical applications. Keep practicing, and don't hesitate to explore further on your own!