def print_ml_programs(indx):
    ml2_progs=[
r"""
#Program1a:
import csv  
a=[]  
with open('enjoysport.csv', 'r') as csvfile:  
    for row in csv.reader(csvfile): 
        a.append(row)  
print("\n The total number of training instances are: " ,len(a)-1)  
num_attribute =len(a[0])-1  
print("\n The initial hypothesis is: ")  
hypothesis =['0']*num_attribute  
print(hypothesis)  
for i in range(1,len(a)):  
    if a[i][num_attribute] == 'yes':  
        for j in range(0, num_attribute):  
            if hypothesis[j] =='0' or hypothesis[j] == a[i][j]:  
                hypothesis[j] =a[i][j]  
            else:  
                hypothesis[j] = '?'  
print("\n The hypothesis for the training instance {} is :\n".format(i),hypothesis)  
print("\n The Maximally specific hypothesis for the training instance is ") 
print(hypothesis)

#Program1b: 

import numpy as np  
import pandas as pd  
data = pd.DataFrame(data=pd.read_csv('enjoysport.csv'))  
concepts = np.array(data.iloc[:,0:-1])  
print(concepts)
target = np.array(data.iloc[:,-1])  
print(target)  
def learn(concepts, target):  
    specific_h = concepts[0].copy()  
    print("initialization of specific_h and general_h")  
    print(specific_h)  
    general_h = [["?" for i in range(len(specific_h))] for i in range(len(specific_h))]  
    print(general_h)  
    for i, h in enumerate(concepts):  
        if target[i] == "yes":  
            for x in range(len(specific_h)):  
                if h[x]!= specific_h[x]:  
                    specific_h[x]='?'  
                    general_h[x][x]='?' 
        if target[i] == "no":  
            for x in range(len(specific_h)):  
                if h[x]!= specific_h[x]:  
                    general_h[x][x]= specific_h[x]  
                else:  
                    general_h[x][x]='?'  
        print(" steps of Candidate Elimination Algorithm",i+1)  
        print(specific_h)  
        print(general_h)  
    indices =[i for i, val in enumerate(general_h) if val ==['?', '?', '?', '?', '?', '?']]  
    for i in indices:  
        general_h.remove (['?','?','?','?','?','?'])  
    return specific_h, general_h  
s_final, g_final = learn(concepts, target)  
print("Final Specific_h:", s_final, sep="\n")  
print("Final General_h:", g_final, sep="\n")""",
r"""
#Program 2:
import pandas as pd
import math

data = {
    "S.No": [1, 2, 3, 4, 5],
    "CGPA": [">=9", "<8", ">=9", "<8", ">=8"],
    "Interactiveness": ["Yes", "Yes", "Yes", "No", "Yes"],
    "Practical Knowledge": ["Good", "Good", "Average", "Good", "Good"],
    "Job Offer": ["Yes", "Yes", "No", "No", "No"]
}
df = pd.DataFrame(data)

def foil_gain(pos, neg, new_pos, new_neg):
    if new_pos == 0:
        return 0
    gain = new_pos * (math.log2(new_pos / (new_pos + new_neg)) - math.log2(pos / (pos + neg)))
    return gain

total_pos = len(df[df["Job Offer"] == "Yes"])
total_neg = len(df[df["Job Offer"] == "No"])

attributes = ["CGPA", "Interactiveness", "Practical Knowledge"]
values = {
    "CGPA": df["CGPA"].unique(),
    "Interactiveness": df["Interactiveness"].unique(),
    "Practical Knowledge": df["Practical Knowledge"].unique()
}
gains = []
for attr in attributes:
    for val in values[attr]:
        subset = df[df[attr] == val]
        new_pos = len(subset[subset["Job Offer"] == "Yes"])
        new_neg = len(subset[subset["Job Offer"] == "No"])
        gain = foil_gain(total_pos, total_neg, new_pos, new_neg)
        gains.append((f'{attr}={val}', gain, new_pos, new_neg))

gains.sort(key=lambda x: x[1], reverse=True)

print("FOIL Gain and Rule Candidates:\n")
for rule, gain, pos, neg in gains:
    print(f"Rule: IF {rule} THEN Job Offer = Yes | FOIL Gain = {gain:.4f} | Positives = {pos} | Negatives = {neg}")""",
r"""
#program3:
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.ensemble import AdaBoostClassifier
from sklearn.metrics import accuracy_score, classification_report

data = load_breast_cancer()
X = data.data
y = data.target

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

from sklearn.ensemble import BaggingClassifier
from sklearn.tree import DecisionTreeClassifier
bag_model = BaggingClassifier(
    estimator=DecisionTreeClassifier(),
    n_estimators=50,
    random_state=42
)
bag_model.fit(X_train, y_train)
y_pred_bag = bag_model.predict(X_test)

print("Bagging Accuracy:", accuracy_score(y_test, y_pred_bag))
print("\n Classification Report:\n", classification_report(y_test, y_pred_bag))
print("\n Bagging Accuracy:", accuracy_score(y_test, y_pred_bag))
print("\nClassification Report:\n", classification_report(y_test, y_pred_bag))

from sklearn.ensemble import AdaBoostClassifier
boost_model = AdaBoostClassifier(
    estimator=DecisionTreeClassifier(max_depth=1),
    n_estimators=50,
    random_state=42
)
boost_model.fit(X_train, y_train)
y_pred_boost = boost_model.predict(X_test)

print(" Boosting Accuracy:", accuracy_score(y_test, y_pred_boost))
print("\n Classification Report:\n", classification_report(y_test, y_pred_boost))""",
r"""
#Program4:
from sklearn.datasets import load_breast_cancer
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt

data = load_breast_cancer()
X = data.data

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

k = 2
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
clusters = kmeans.fit_predict(X_scaled)

from sklearn.decomposition import PCA
pca = PCA(n_components=2)
principal_components = pca.fit_transform(X_scaled)

plt.figure(figsize=(10, 7))
for cluster_id in sorted(set(clusters)):
    plt.scatter(principal_components[clusters == cluster_id, 0],
                principal_components[clusters == cluster_id, 1],
                label=f'Cluster {cluster_id}', alpha=0.7)
plt.title(f'K-Means Clusters (K={k}) - PCA Reduced Data')
plt.xlabel('Principal Component 1')
plt.ylabel('Principal Component 2')
plt.legend()
plt.grid(True)
plt.show()""",
r"""
# PROGRAM 5

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from minisom import MiniSom
from sklearn.preprocessing import MinMaxScaler
from sklearn.datasets import load_iris

iris = load_iris()
X = iris.data
y = iris.target
target_names = iris.target_names

data = pd.DataFrame(X, columns=iris.feature_names)
data['Species'] = [target_names[i] for i in y]

print("Dataset Head:")
print(data.head())

scaler = MinMaxScaler()
X_scaled = scaler.fit_transform(X)

som_x, som_y = 7, 7

som = MiniSom(
    x=som_x,
    y=som_y,
    input_len=X_scaled.shape[1],
    sigma=1.0,
    learning_rate=0.5
)

som.random_weights_init(X_scaled)

print("Training SOM...")
som.train_random(data=X_scaled, num_iteration=100)
print("Training Complete!")

plt.figure(figsize=(8, 8))
plt.pcolor(som.distance_map().T, cmap='coolwarm')
plt.colorbar(label='Distance')

markers = ['o', 's', 'D']
colors = ['r', 'g', 'b']

for i, x in enumerate(X_scaled):
    w = som.winner(x)
    plt.plot(
        w[0] + 0.5,
        w[1] + 0.5,
        markers[y[i]],
        markerfacecolor='None',
        markeredgecolor=colors[y[i]],
        markersize=12,
        markeredgewidth=2
    )

plt.title("Self-Organizing Map (SOM) - Iris Dataset")
plt.show()

clusters = [som.winner(x) for x in X_scaled]
data['Cluster'] = clusters

print("\nClustered Data:")
print(data.head())

data.to_csv("iris_som_clustered.csv", index=False)
print("Clustered data saved to 'iris_som_clustered.csv'")
""",
r"""
#PROGRAM 6

import numpy as np
import matplotlib.pyplot as plt

def uniform_random(n):
    return np.random.rand(n)

def f(x):
    return np.exp(x)

n = 10
x_samples = uniform_random(n)
f_samples = f(x_samples)

I_estimated = np.mean(f_samples)
I_true = np.e - 1

x = np.linspace(0, 1, 100)
y = f(x)

plt.figure(figsize=(8, 5))
plt.plot(x, y, 'b-', label='f(x) = e^x')
plt.fill_between(x, y, color='lightblue', alpha=0.3,
                 label='True area (integral e^x dx)')
plt.scatter(x_samples, f_samples, color='red', zorder=5, label='Random samples')
plt.hlines(np.mean(f_samples), 0, 1, colors='green', linestyles='--',
           label='Mean(f(x))')

plt.title('Monte Carlo Estimation of integral 0 to 1 e^x dx')
plt.xlabel('x')
plt.ylabel('f(x)')
plt.legend()
plt.grid(True)
plt.show()

print("Monte Carlo estimated integral =", I_estimated)
print("True integral value =", I_true)
print("Absolute error =", abs(I_estimated - I_true))
""",
r"""
# PROGRAM 7

import pandas as pd
from pgmpy.models import DiscreteBayesianNetwork
from pgmpy.estimators import MaximumLikelihoodEstimator
from pgmpy.inference import VariableElimination

data = pd.DataFrame({
    'Weather': ['Sunny', 'Sunny', 'Overcast', 'Rain', 'Rain', 'Rain', 'Overcast', 'Sunny', 'Sunny',
                'Rain', 'Sunny', 'Overcast', 'Overcast', 'Rain'],
    'Temperature': ['Hot', 'Hot', 'Hot', 'Mild', 'Cool', 'Cool', 'Cool', 'Mild', 'Cool', 'Mild',
                    'Mild', 'Mild', 'Hot', 'Mild'],
    'Humidity': ['High', 'High', 'High', 'High', 'Normal', 'Normal', 'Normal', 'High', 'Normal',
                 'Normal', 'Normal', 'High', 'Normal', 'High'],
    'Wind': ['Weak', 'Strong', 'Weak', 'Weak', 'Weak', 'Strong', 'Strong', 'Weak', 'Weak', 'Weak',
             'Strong', 'Strong', 'Weak', 'Strong'],
    'Play': ['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes',
             'Yes', 'Yes', 'Yes', 'Yes', 'No']
})

model = DiscreteBayesianNetwork([
    ('Weather', 'Play'),
    ('Temperature', 'Play'),
    ('Humidity', 'Play'),
    ('Wind', 'Play')
])

model.fit(data, estimator=MaximumLikelihoodEstimator)

inference = VariableElimination(model)

result = inference.query(variables=['Play'], evidence={'Weather': 'Sunny'})
print("Likelihood of Play when Weather is Sunny:\n", result)

query_result = inference.query(
    variables=['Play'],
    evidence={'Weather': 'Rain', 'Humidity': 'High'}
)
print("\n=== Likelihood of Event ===")
print("If Weather = Rain and Humidity = High:\n", query_result)
""",
r"""
# PROGRAM 8

import pandas as pd
from pgmpy.models import DiscreteBayesianNetwork
from pgmpy.estimators import MaximumLikelihoodEstimator
from pgmpy.inference import VariableElimination

data = pd.DataFrame({
    'Weather': ['Sunny', 'Sunny', 'Overcast', 'Rain', 'Rain', 'Rain', 'Overcast', 'Sunny', 'Sunny',
                'Rain', 'Sunny', 'Overcast', 'Overcast', 'Rain'],
    'Temperature': ['Hot', 'Hot', 'Hot', 'Mild', 'Cool', 'Cool', 'Cool', 'Mild', 'Cool',
                    'Mild', 'Mild', 'Mild', 'Hot', 'Mild'],
    'Humidity': ['High', 'High', 'High', 'High', 'Normal', 'Normal', 'Normal', 'High', 'Normal',
                 'Normal', 'Normal', 'High', 'Normal', 'High'],
    'Wind': ['Weak', 'Strong', 'Weak', 'Weak', 'Weak', 'Strong', 'Strong', 'Weak', 'Weak',
             'Weak', 'Strong', 'Strong', 'Weak', 'Strong'],
    'Play': ['No', 'No', 'Yes', 'Yes', 'Yes', 'No', 'Yes', 'No', 'Yes',
             'Yes', 'Yes', 'Yes', 'Yes', 'No']
})

model = DiscreteBayesianNetwork([
    ('Weather', 'Play'),
    ('Temperature', 'Play'),
    ('Humidity', 'Play'),
    ('Wind', 'Play')
])

model.fit(data, estimator=MaximumLikelihoodEstimator)
inference = VariableElimination(model)

queries = [
    {'Weather': 'Sunny', 'Humidity': 'High'},
    {'Weather': 'Rain', 'Wind': 'Weak'},
    {'Weather': 'Overcast'}
]

print("\n--- Inferences Based on Given Conditions ---")
for evidence in queries:
    prob = inference.query(variables=['Play'], evidence=evidence)
    print(f"\nEvidence: {evidence}")
    print(prob)
"""
    ]
    print(ml2_progs[indx-1])

def print_dl_programs(indx):
    dl_progs=[
r"""
#Program 1

import torch
import torch.nn as nn
import torch.optim as optim
import random
import numpy as np
import matplotlib.pyplot as plt
from sklearn.manifold import TSNE

corpus = [
    "the cat sits on the mat",
    "dogs and cats are animals",
    "the mat is soft and comfortable"
]

window_size = 2
embedding_dim = 50
epochs = 100
learning_rate = 0.01

tokenized_corpus = [sentence.lower().split() for sentence in corpus]
vocab = set(word for sentence in tokenized_corpus for word in sentence)
word2idx = {word: idx for idx, word in enumerate(vocab)}
idx2word = {idx: word for word, idx in word2idx.items()}
vocab_size = len(vocab)


def generate_skip_gram(tokenized_text, window):
    pairs = []
    for sentence in tokenized_text:
        for center_pos in range(len(sentence)):
            center_word = sentence[center_pos]
            context_range = list(
                range(
                    max(0, center_pos - window),
                    min(len(sentence), center_pos + window + 1)
                )
            )
            context_range.remove(center_pos)
            for context_pos in context_range:
                pairs.append(
                    (word2idx[center_word], word2idx[sentence[context_pos]])
                )
    return pairs


training_pairs = generate_skip_gram(tokenized_corpus, window_size)


class SkipGramModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super(SkipGramModel, self).__init__()
        self.embeddings = nn.Embedding(vocab_size, embedding_dim)
        self.output_layer = nn.Linear(embedding_dim, vocab_size)

    def forward(self, input_word):
        embedding = self.embeddings(input_word)
        out = self.output_layer(embedding)
        return out


model = SkipGramModel(vocab_size, embedding_dim)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

print("Training started...")
for epoch in range(epochs):
    total_loss = 0
    for center, context in training_pairs:
        center_tensor = torch.tensor([center], dtype=torch.long)
        context_tensor = torch.tensor([context], dtype=torch.long)

        optimizer.zero_grad()
        output = model(center_tensor)
        loss = criterion(output, context_tensor)
        loss.backward()
        optimizer.step()

        total_loss += loss.item()

    if (epoch + 1) % 10 == 0:
        print(f"Epoch {epoch+1}/{epochs}, Loss: {total_loss:.4f}")
print("Training complete.")

embeddings = model.embeddings.weight.data
with open("word_embeddings.txt", "w") as f:
    for idx, vector in enumerate(embeddings):
        word = idx2word[idx]
        vec_str = " ".join(map(str, vector.tolist()))
        f.write(f"{word} {vec_str}\n")
print("Saved word embeddings to 'word_embeddings.txt'.")


def visualize_embeddings(embeddings, word2idx, words=None):
    if words is None:
        words = list(word2idx.keys())

    vecs = torch.stack([embeddings[word2idx[word]] for word in words]).numpy()

    perplexity = min(30, len(words) - 1)
    tsne = TSNE(
        n_components=2,
        init="pca",
        random_state=0,
        perplexity=perplexity
    )
    reduced = tsne.fit_transform(vecs)

    plt.figure(figsize=(10, 8))
    for i, word in enumerate(words):
        x, y = reduced[i]
        plt.scatter(x, y)
        plt.annotate(word, (x, y), fontsize=12)
    plt.title("t-SNE Visualization of Word Embeddings")
    plt.grid(True)
    plt.show()


visualize_embeddings(embeddings, word2idx)

""",
r"""
#Program 2
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler, LabelBinarizer
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense
from tensorflow.keras.utils import plot_model

iris = load_iris()
X = iris.data
y = iris.target

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

encoder = LabelBinarizer()
y_encoded = encoder.fit_transform(y)

X_train, X_test, y_train, y_test = train_test_split(
    X_scaled,
    y_encoded,
    test_size=0.2,
    random_state=42
)

model = Sequential([
    Dense(64, input_shape=(X.shape[1],), activation="relu"),
    Dense(32, activation="relu"),
    Dense(16, activation="relu"),
    Dense(3, activation="softmax")
])

model.compile(
    optimizer="adam",
    loss="categorical_crossentropy",
    metrics=["accuracy"]
)

model.summary()

history = model.fit(
    X_train,
    y_train,
    epochs=100,
    validation_split=0.2,
    batch_size=8,
    verbose=0
)

loss, accuracy = model.evaluate(X_test, y_test)
print(f"\nTest Accuracy: {accuracy * 100:.2f}%")

plt.plot(history.history["accuracy"], label="Train Accuracy")
plt.plot(history.history["val_accuracy"], label="Validation Accuracy")
plt.xlabel("Epochs")
plt.ylabel("Accuracy")
plt.legend()
plt.title("Training & Validation Accuracy")
plt.grid(True)
plt.show()

""",
r"""
#Program 3
import tensorflow as tf
from tensorflow.keras import layers, models
import matplotlib.pyplot as plt

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()

x_train, x_test = x_train / 255.0, x_test / 255.0

class_names = [
    "Airplane",
    "Automobile",
    "Bird",
    "Cat",
    "Deer",
    "Dog",
    "Frog",
    "Horse",
    "Ship",
    "Truck"
]

model = models.Sequential([
    layers.Conv2D(32, (3, 3), activation="relu", input_shape=(32, 32, 3)),
    layers.MaxPooling2D((2, 2)),

    layers.Conv2D(64, (3, 3), activation="relu"),
    layers.MaxPooling2D((2, 2)),

    layers.Conv2D(64, (3, 3), activation="relu"),

    layers.Flatten(),
    layers.Dense(64, activation="relu"),
    layers.Dense(10, activation="softmax")
])

model.compile(
    optimizer="adam",
    loss="sparse_categorical_crossentropy",
    metrics=["accuracy"]
)

model.summary()

history = model.fit(
    x_train,
    y_train,
    epochs=10,
    validation_split=0.2,
    batch_size=64
)

test_loss, test_accuracy = model.evaluate(x_test, y_test, verbose=2)
print(f"\nTest Accuracy: {test_accuracy * 100:.2f}%")

plt.plot(history.history["accuracy"], label="Train Accuracy")
plt.plot(history.history["val_accuracy"], label="Validation Accuracy")
plt.xlabel("Epoch")
plt.ylabel("Accuracy")
plt.legend()
plt.title("Training and Validation Accuracy")
plt.grid(True)
plt.show()
""",
r"""
#Program 4
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dense
from tensorflow.keras import regularizers

(x_train, _), (x_test, _) = mnist.load_data()
x_train = x_train.astype("float32") / 255.0
x_test = x_test.astype("float32") / 255.0
x_train = x_train.reshape((len(x_train), -1))
x_test = x_test.reshape((len(x_test), -1))

encoding_dim = 32

input_img = Input(shape=(784,))
encoded = Dense(128, activation="relu")(input_img)
encoded = Dense(64, activation="relu")(encoded)
encoded = Dense(encoding_dim, activation="relu")(encoded)

decoded = Dense(64, activation="relu")(encoded)
decoded = Dense(128, activation="relu")(decoded)
decoded = Dense(784, activation="sigmoid")(decoded)

autoencoder = Model(input_img, decoded)
encoder = Model(input_img, encoded)

autoencoder.compile(optimizer="adam", loss="binary_crossentropy")

autoencoder.fit(
    x_train,
    x_train,
    epochs=50,
    batch_size=256,
    shuffle=True,
    validation_data=(x_test, x_test)
)

encoded_imgs = encoder.predict(x_test)
decoded_imgs = autoencoder.predict(x_test)

n = 10
plt.figure(figsize=(18, 6))

for i in range(n):
    ax = plt.subplot(3, n, i + 1)
    plt.imshow(x_test[i].reshape(28, 28), cmap="gray")
    plt.title("Original")
    plt.axis("off")

    ax = plt.subplot(3, n, i + 1 + n)
    plt.imshow(encoded_imgs[i].reshape(8, 4), cmap="viridis")
    plt.title("Encoded")
    plt.axis("off")

    ax = plt.subplot(3, n, i + 1 + 2 * n)
    plt.imshow(decoded_imgs[i].reshape(28, 28), cmap="gray")
    plt.title("Reconstructed")
    plt.axis("off")

plt.show()
""",
r"""
#Program 5
import tensorflow as tf
from tensorflow.keras.datasets import imdb
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Embedding, LSTM, Dense, Dropout

vocab_size = 10000
maxlen = 200
embedding_dim = 128

(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=vocab_size)

x_train = pad_sequences(x_train, maxlen=maxlen)
x_test = pad_sequences(x_test, maxlen=maxlen)

model = Sequential([
    Embedding(vocab_size, embedding_dim, input_length=maxlen),
    LSTM(128, return_sequences=False),
    Dropout(0.5),
    Dense(1, activation="sigmoid")
])

model.compile(
    loss="binary_crossentropy",
    optimizer="adam",
    metrics=["accuracy"]
)

model.summary()

history = model.fit(
    x_train,
    y_train,
    epochs=5,
    batch_size=64,
    validation_split=0.2
)

loss, accuracy = model.evaluate(x_test, y_test)
print(f"Test Accuracy: {accuracy:.4f}")
""",
r"""
#Program 6
import numpy as np
import matplotlib.pyplot as plt
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense
from sklearn.preprocessing import MinMaxScaler

np.random.seed(42)
time = np.arange(0, 200, 0.1)
series = np.sin(time) + 0.1 * np.random.normal(size=len(time))

plt.plot(time, series)
plt.title("Synthetic Time Series Data")
plt.show()


def create_dataset(data, window_size=20):
    X, y = [], []
    for i in range(len(data) - window_size):
        X.append(data[i : i + window_size])
        y.append(data[i + window_size])
    return np.array(X), np.array(y)


window_size = 20
X, y = create_dataset(series, window_size)

X = X.reshape((X.shape[0], X.shape[1], 1))

scaler = MinMaxScaler()
X_reshaped = X.reshape(-1, 1)
X_scaled = scaler.fit_transform(X_reshaped).reshape(X.shape)
y_scaled = scaler.transform(y.reshape(-1, 1))

model = Sequential([
    LSTM(50, activation="relu", input_shape=(window_size, 1)),
    Dense(1)
])

model.compile(optimizer="adam", loss="mse")

history = model.fit(
    X_scaled,
    y_scaled,
    epochs=30,
    batch_size=32,
    validation_split=0.2
)

y_pred_scaled = model.predict(X_scaled)
y_pred = scaler.inverse_transform(y_pred_scaled)

plt.figure(figsize=(12, 6))
plt.plot(series[window_size:], label="Actual")
plt.plot(y_pred.flatten(), label="Predicted")
plt.title("Time Series Forecasting with LSTM")
plt.xlabel("Time Steps")
plt.ylabel("Value")
plt.legend()
plt.show()
""",
r"""
#Program 7
import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.applications import ResNet50
from tensorflow.keras.models import Model
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.preprocessing import image
import matplotlib.pyplot as plt
import numpy as np
import os

print("TensorFlow Version:", tf.__version__)

train_dir = "dataset/train"
val_dir = "dataset/val"
test_dir = "dataset/test"

img_size = 224
batch_size = 32

train_gen = ImageDataGenerator(
    rescale=1.0 / 255,
    rotation_range=20,
    zoom_range=0.2,
    horizontal_flip=True,
)

val_gen = ImageDataGenerator(rescale=1.0 / 255)

train_data = train_gen.flow_from_directory(
    train_dir, target_size=(img_size, img_size), batch_size=batch_size, class_mode="categorical"
)

val_data = val_gen.flow_from_directory(
    val_dir, target_size=(img_size, img_size), batch_size=batch_size, class_mode="categorical"
)

base_model = ResNet50(weights="imagenet", include_top=False, input_shape=(img_size, img_size, 3))

for layer in base_model.layers:
    layer.trainable = False

x = GlobalAveragePooling2D()(base_model.output)
x = Dense(256, activation="relu")(x)
output = Dense(train_data.num_classes, activation="softmax")(x)

model = Model(inputs=base_model.input, outputs=output)
model.compile(optimizer=Adam(1e-4), loss="categorical_crossentropy", metrics=["accuracy"])

model.summary()

history = model.fit(train_data, validation_data=val_data, epochs=5)

plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.plot(history.history["accuracy"])
plt.plot(history.history["val_accuracy"])
plt.title("Accuracy")
plt.legend(["Train", "Validation"])

plt.subplot(1, 2, 2)
plt.plot(history.history["loss"])
plt.plot(history.history["val_loss"])
plt.title("Loss")
plt.legend(["Train", "Validation"])

plt.show()


def predict_image(img_path):
    img = image.load_img(img_path, target_size=(img_size, img_size))
    img_array = image.img_to_array(img) / 255.0
    img_array = np.expand_dims(img_array, axis=0)

    prediction = model.predict(img_array)
    class_index = np.argmax(prediction)
    class_names = list(train_data.class_indices.keys())

    print("Predicted Class:", class_names[class_index])
""",
r"""
#Program 8
class GridWorld:
    def __init__(self):
        self.rows = 5
        self.cols = 5

        self.start = (0, 0)
        self.goal = (4, 4)

        self.obstacles = {(1, 1), (2, 2), (3, 1)}

        self.agent_pos = self.start

    def reset(self):
        self.agent_pos = self.start
        return self.agent_pos

    def step(self, action):
        x, y = self.agent_pos

        if action == 0:
            next_pos = (x - 1, y)
        elif action == 1:
            next_pos = (x, y + 1)
        elif action == 2:
            next_pos = (x + 1, y)
        elif action == 3:
            next_pos = (x, y - 1)

        if not (0 <= next_pos[0] < self.rows and 0 <= next_pos[1] < self.cols):
            reward = -2
            next_pos = self.agent_pos
        elif next_pos in self.obstacles:
            reward = -2
            next_pos = self.agent_pos
        elif next_pos == self.goal:
            reward = +10
            self.agent_pos = next_pos
            return next_pos, reward, True
        else:
            reward = -1

        self.agent_pos = next_pos
        done = False
        return next_pos, reward, done

    def render(self):
        for i in range(self.rows):
            for j in range(self.cols):
                if (i, j) == self.agent_pos:
                    print("A", end=" ")
                elif (i, j) == self.start:
                    print("S", end=" ")
                elif (i, j) == self.goal:
                    print("G", end=" ")
                elif (i, j) in self.obstacles:
                    print("X", end=" ")
                else:
                    print(".", end=" ")
            print()
        print()


env = GridWorld()

state = env.reset()
env.render()

actions = [1, 1, 2, 2, 2, 1, 1]

for action in actions:
    print("Action:", action)
    next_state, reward, done = env.step(action)
    env.render()
    print("Reward:", reward)
    print("Done:", done)
    print("------------------")
    if done:
        break
"""
    ]
    print(dl_progs[indx-1])

def print_ml_theory():
    answers=r"""
Certainly! Here is the entire response converted into plain text, with no bold formatting, no tables, and all LaTeX rendered in line or removed where contextually appropriate, suitable for pasting into a word file.

Part A

1 (a) Explain the concept of a well-posed learning problem with an appropriate example. (5 Marks)

A learning problem is considered well-posed if it is defined by three specific features:
1. The class of tasks (T) the system is to perform.
2. The measure of performance (P) to be improved.
3. The source of experience (E) the system will use to learn.

A computer program is formally said to learn from experience E with respect to some class of tasks T and performance measure P, if its performance at tasks in T, as measured by P, improves with experience E.

---

Appropriate Example: A Checkers Learning Problem

One of the foundational examples used to illustrate a well-posed learning problem is a program learning to play checkers:

* Task (T): Playing checkers.
* Performance Measure (P): The percentage of games won against opponents.
* Training Experience (E): Playing practice games against itself.

In this example, the learning system's task is to play the game, and its performance is easily and directly quantifiable by the percentage of games won. The experience consists of playing a large number of games, which is the mechanism that drives the performance improvement. The entire problem is well-defined because all three components are explicitly specified, allowing for the design and evaluation of a learning algorithm.

---
1 (b) Illustrate the final design of the checkers learning program. (7 Marks)

The final design of the checkers learning program is typically described by four distinct, interacting program modules. This structure is central to many machine learning systems and shows how experience (E), performance (P), and the learning process are integrated.



The four key modules are:

1. Performance System
    * Role: This module executes the task (playing checkers) using the current knowledge, which is the learned target function V-hat (evaluation function). It takes a new problem (e.g., an initial game board) as input and produces a solution trace (game history).
    * Improvement: Its performance improves as the learned evaluation function (V-hat) becomes more accurate.
2. Critic
    * Role: It takes the game history/solution trace from the Performance System as input. Its job is to generate a set of training examples for the target function. In the checkers example, it assigns an estimated target function value, V_train(b), to each observed board state b in the trace.
    * Rule: For checkers, the key rule for estimating training values is V_train(b) <- V-hat(Successor(b)). This uses the current (and presumably more accurate for later states) evaluation function to estimate the value of the current state.
3. Generalizer
    * Role: It receives the training examples from the Critic and produces a revised hypothesis (V-hat) that is an improved estimate of the ideal target function (V). It performs the core generalization step, taking specific examples and hypothesizing a general function that covers unseen cases.
    * Algorithm: In this design, the Generalizer uses the LMS (Least Mean Squares) training rule to adjust the weights (w_i) of the linear evaluation function V-hat to minimize the squared error E = sum (V_train(b) - V-hat(b))^2.
4. Experiment Generator
    * Role: It takes the current hypothesis (V-hat) as input and selects the next problem (e.g., the initial board state) for the Performance System to explore.
    * Strategy: Its goal is to pick problems that maximize the learning rate. In the simplest design, it always proposes the same initial board, but more sophisticated versions might choose problems to explore difficult parts of the state space.

---
1 (c) Apply the Candidate-Elimination algorithm. Explain its working taking enjoy sport concept and training instances given below.

| Day | Sky | AirTemp | Humidity | Wind | Water | Forecast | EnjoySport |
| 1 | Sunny | Warm | Normal | Strong | Warm | Same | Yes |
| 2 | Sunny | Warm | High | Strong | Warm | Same | Yes |
| 3 | Rainy | Cold | High | Strong | Warm | Change | No |
| 4 | Sunny | Warm | High | Strong | Cool | Change | Yes |

The Candidate-Elimination (CE) algorithm is a concept learning algorithm that computes the version space (VS_H,D), which is the set of all hypotheses in the hypothesis space (H) consistent with the observed training examples (D). It represents this set compactly by keeping track of only its most general boundary (G) and its most specific boundary (S).

The hypothesis space H is restricted to conjunctions of constraints, where each attribute can be a specific value, '?' (any value is acceptable), or 'null-set' (no value is acceptable).

Initialization

The algorithm starts by initializing G to the set of maximally general hypotheses and S to the set of maximally specific hypotheses in H:

* G_0 <- { < ?, ?, ?, ?, ?, ? > }
* S_0 <- { < null-set, null-set, null-set, null-set, null-set, null-set > }

Processing Training Examples

1. Example 1 (+): < Sunny, Warm, Normal, Strong, Warm, Same >
* S Boundary Update: S_0 is too specific and fails to cover this positive example. It is generalized to the least general hypothesis that covers the example.
* G Boundary Update: G_0 correctly covers the positive example, so it remains unchanged.
* S_1: < Sunny, Warm, Normal, Strong, Warm, Same >
* G_1: < ?, ?, ?, ?, ?, ? >

2. Example 2 (+): < Sunny, Warm, High, Strong, Warm, Same >
* S Boundary Update: S_1 fails to cover this positive example because Humidity=High and S_1 requires Normal. It is minimally generalized by replacing the specific attribute value with a '?' where the new example differs.
* G Boundary Update: G_1 remains unchanged.
* S_2: < Sunny, Warm, ?, Strong, Warm, Same >
* G_2: < ?, ?, ?, ?, ?, ? >

3. Example 3 (-): < Rainy, Cold, High, Strong, Warm, Change >
* S Boundary Update: S_2 correctly classifies this negative example (it only covers Sunny days). S_3 remains S_2.
* G Boundary Update: G_2 is overly general; it incorrectly classifies this negative example as positive. It is specialized to all minimal specializations that correctly classify the example AND are more general than S_3:
* G_2 must be specialized to exclude Rainy OR Cold OR Change. Possible minimal specializations that are also more general than S_2 are:
    * < Sunny, ?, ?, ?, ?, ? > (excludes Rainy)
    * < ?, Warm, ?, ?, ?, ? > (excludes Cold)
    * < ?, ?, ?, ?, ?, Same > (excludes Change)
* G_3: { < Sunny, ?, ?, ?, ?, ? >, < ?, Warm, ?, ?, ?, ? >, < ?, ?, ?, ?, ?, Same > }

4. Example 4 (+): < Sunny, Warm, High, Strong, Cool, Change >
* S Boundary Update: S_3 fails because Water=Cool and Forecast=Change. Minimal generalization by generalizing Water to '?' and Forecast to '?'.
* G Boundary Update: G_3 is checked for consistency. < ?, ?, ?, ?, ?, Same > is inconsistent because it fails to cover the positive example with Forecast=Change. This hypothesis is removed.
* S_4: < Sunny, Warm, ?, Strong, ?, ? >
* G_4: { < Sunny, ?, ?, ?, ?, ? >, < ?, Warm, ?, ?, ?, ? > }

Final Version Space

After processing all four examples, the final version space VS_H,D is delimited by the sets S_4 and G_4:

* S: { < Sunny, Warm, ?, Strong, ?, ? > }
* G: { < Sunny, ?, ?, ?, ?, ? >, < ?, Warm, ?, ?, ?, ? > }

The final version space VS_H,D contains every hypothesis h in H such that < Sunny, ?, ?, ?, ?, ? > is more general than or equal to h, and h is more general than or equal to < Sunny, Warm, ?, Strong, ?, ? > AND < ?, Warm, ?, ?, ?, ? > is more general than or equal to h, and h is more general than or equal to < Sunny, Warm, ?, Strong, ?, ? >.

This includes S itself, G itself, and any hypothesis that falls between them (e.g., < Sunny, Warm, ?, ?, ?, ? > and < Sunny, ?, ?, Strong, ?, ? >).

---
2 (a) Explain the following with respect to 'A checkers learning problem'

i. Choosing the training experience

Choosing the right training experience is a critical first step in designing a learning system, as it significantly impacts success.

For the checkers learning problem, the design choice made was:
* Training Experience (E): Games played against itself.

Benefits and Rationale
* Self-Generated Data: This approach eliminates the need for an external trainer or expert human player. The system can generate an arbitrarily large amount of training data as time permits, which is a significant advantage for complex games with huge search spaces.
* No Teacher Present: The system has complete control over the board states and the classification (indirectly through the game outcome). The cost of acquiring new experience is low.

Challenges
* Indirect Feedback (Credit Assignment Problem): The only direct training information is the final outcome (win/loss/draw) of the game. The system faces a credit assignment problem, where it must infer which specific moves, particularly those early in the game, deserve credit or blame for the eventual outcome. This makes learning more difficult than learning from direct feedback (e.g., being told the correct move at every step).
* Representativeness: The experience (games against itself) might not perfectly represent the distribution of board states over which the final performance will be measured (e.g., games against a human world champion). This mismatch can lead to poor performance in the final task if the model overfits its self-play distribution.

---
ii. Choosing the target function

The target function is the piece of knowledge the system attempts to learn to improve its performance.

For a program that can already generate legal moves, the most obvious choice is a function that selects the best move: ChooseMove: B -> M (Board to Move).

Chosen Alternative: Evaluation Function V

* Target Function V: V: B -> R (Board to Real Value).
    * Goal: The function is intended to assign higher scores to better board states.
* Operational Definition (How V is Used): Once an approximation V-hat is learned, the program can select the best move by generating all possible successor states and choosing the move that leads to the successor state with the highest V-hat score.

Ideal Definition (What V Should Be)
The ideal target function V(b) is defined recursively:
1. Won final state: V(b) = +100.
2. Lost final state: V(b) = -100.
3. Drawn final state: V(b) = 0.
4. Non-final state: V(b) = V(b'), where b' is the best final board state achievable starting from b, assuming optimal play from both sides.

The Learning Goal: Function Approximation
The ideal definition of V is nonoperational because computing it requires a massive, complex search all the way to the end of the game. Therefore, the goal of learning is to discover an operational description of V, a function V-hat, that is an efficiently computable approximation of the ideal target function V.

---
2 (b) Discuss the biased hypothesis space and unbiased learner. (7 Marks)

Biased Hypothesis Space (Biased Learner)

A learning algorithm operates within a chosen hypothesis space (H), which is the set of all possible hypotheses (concepts) that the learner can represent.

* Definition: A hypothesis space is biased if it excludes some of the possible target concepts that can be defined over the instance space.
    * Example (EnjoySport): The original hypothesis space H consisting only of conjunctive descriptions (e.g., < Sunny, Warm, ?, Strong, ?, ? >) is heavily biased. Out of 2^96 possible concepts, H can only represent 973 distinct concepts.
* Inductive Bias: The assumption that the true target concept must be contained within this restrictive hypothesis space H is the inductive bias of the learning algorithm (e.g., the Candidate-Elimination algorithm).
* Advantage: This strong bias (i.e., making assumptions about the function's form) is what allows the learner to generalize beyond the observed training examples. The version space collapses quickly, requiring fewer examples to converge to a plausible hypothesis.
* Disadvantage: If the true target concept lies outside of H (e.g., a simple disjunction like "Sky=Sunny or Sky=Cloudy"), the learner is certain to fail. It will find no consistent hypothesis, or incorrectly classify all instances, because its assumption (bias) is wrong.

---

Unbiased Learner (Unbiased Hypothesis Space)

An unbiased learner is created by selecting a hypothesis space that is expressive enough to represent every possible target concept.

* Hypothesis Space (H'): The ideal unbiased space is the power set of the instance space X. This space is capable of representing every possible subset of instances.
    * Example (EnjoySport): If H' includes all disjunctions, conjunctions, and negations, it can represent all 2^96 possible target concepts.
* Inductive Bias: An unbiased learner that only outputs hypotheses consistent with the training data has no inductive bias. The classifications it provides for new instances are justified purely by the data and deductive inference, with no additional assumptions.
* Futility of Bias-Free Learning: The major problem is that an unbiased learner cannot generalize beyond the observed training examples.
    * Lack of Convergence: In the power-set H', the S boundary is simply the disjunction of all observed positive examples, and the G boundary rules out all observed negative examples. The version space remains huge. To converge to a single hypothesis, the learner would theoretically need to observe every single instance in X as a training example.
    * Voting Failure: For any unobserved instance, exactly half of the hypotheses in the version space will classify it as positive, and half as negative, leading to a vote that provides no confident classification.

Conclusion: The Futility of Bias-Free Learning
The necessity of a restricted hypothesis space (a bias) is a fundamental property of inductive inference. A learner must make a priori assumptions (a bias) regarding the nature of the target concept in order to have a rational basis for classifying unseen instances.

---
2 (c) Discuss the Find-S algorithm and discuss the issues with the algorithm.

The FIND-S algorithm is a concept learning algorithm that uses the general-to-specific ordering to search the hypothesis space (H).

FIND-S Algorithm Overview

1. Initialization: Initialize the hypothesis h to the most specific hypothesis in H (e.g., < null-set, null-set, null-set, null-set, null-set, null-set >).
2. Processing Positive Examples: For each positive training instance x:
    * If x satisfies h, do nothing.
    * If x does not satisfy h, generalize h just enough so that it covers x. This involves replacing attribute constraints with the next most general constraint (either the attribute value from x or a '?') that x satisfies.
3. Processing Negative Examples: The algorithm ignores negative examples. This is justified by the crucial assumption that the target concept c is present in H and the training data is noise-free. Under this assumption, the current hypothesis h (being the most specific consistent with all positive examples) will never be overly general and thus will not need revision for a negative example.
4. Output: Output the final hypothesis h.

Key Property: FIND-S is guaranteed to output the most specific hypothesis in H consistent with the positive training examples.

Issues with the FIND-S Algorithm

While simple and efficient, FIND-S has several limitations:

1. Does Not Guarantee Convergence to the Correct Concept:
    * The algorithm only finds one hypothesis consistent with the data—the most specific one. It provides no information on whether other hypotheses also exist (the version space size). The learner has no way to determine if it has found the unique correct target concept or if there is significant ambiguity remaining.

2. Unclear Preference for Most Specific Hypothesis:
    * FIND-S implicitly embodies an inductive bias: all instances are negative instances unless the opposite is entailed by its other knowledge (a closed world assumption). It simply finds the most specific hypothesis consistent with the positive examples and uses this to classify everything. It is unclear whether this "most specific" bias is preferable over other possibilities (e.g., the most general consistent hypothesis, or a hypothesis based on maximum likelihood).

3. Lacks Robustness to Noise/Inconsistencies:
    * The algorithm fundamentally assumes the training data is noise-free and that the target concept is in H.
    * If a positive example is mislabeled as negative (a false negative), the algorithm is simply wrong, as it ignores the example but the correct concept is now inconsistent with the label.
    * If a negative example is mislabeled as positive (false positive), the minimal generalization step may over-generalize the hypothesis, potentially excluding the true target concept from the region of the search it is exploring.
    * If the hypothesis space H is not expressive enough (e.g., the concept is disjunctive, but H is conjunctive), FIND-S will overgeneralize in its attempt to cover all positive examples, resulting in a hypothesis that covers unintended negative examples. In either case, it cannot detect when the training data is inconsistent.

4. Handling Multiple Maximally Specific Hypotheses:
    * For the simple conjunction hypothesis space, there is a unique most specific hypothesis consistent with the data. However, for other hypothesis representations, there might be multiple distinct, maximally specific hypotheses. FIND-S cannot naturally handle this, and would need to be extended to backtrack and explore multiple branches of the partial ordering.

---
---

Part B

3 (a) Explain Sequential Covering algorithm with a suitable example. (6 Marks)

The Sequential Covering Algorithm is a fundamental strategy for learning an expressive hypothesis represented as a disjunctive set of if-then rules (or Horn clauses). It simplifies the complex problem of learning a disjunction (a set of rules) by breaking it down into a sequence of simpler problems, each involving learning a single, accurate conjunctive rule.

Algorithm Principle

The algorithm iteratively learns one rule at a time, removes the positive training examples covered by that rule, and repeats the process on the remaining examples until a stopping criterion is met (e.g., no positive examples remain, or no sufficiently accurate rule can be found).

1. Initialize the set of Learned Rules to empty: L <- {}.
2. Iterate: While there are uncovered positive Examples (Pos):
    * Learn One Rule: Call a subroutine, LEARN-ONE-RULE, which takes the remaining positive and negative examples and returns a single, highly accurate conjunctive rule R. (The rule aims for high accuracy but may have low coverage).
    * Check Performance: If the rule's performance (accuracy/quality) is above a set Threshold, proceed; otherwise, stop.
    * Add Rule: Add the new rule R to the set of learned rules: L <- L + R.
    * Cover Examples: Remove all positive examples correctly classified (covered) by R from the set of remaining Examples (Pos).
3. Finalize: Optionally sort the learned rules for optimal ordering, then return L.

Example: Learning the Target Concept Ancestor

The sequential covering algorithm is conceptually suitable for learning relations like Ancestor(x, y), which is defined as y being an ancestor of x (mother, father, grandmother, etc.).

The goal is to learn the following set of recursive rules that define the concept:

1. Base Case: If Parent(x, y), then Ancestor(x, y).
2. Recursive Case: If Parent(x, z) AND Ancestor(z, y), then Ancestor(x, y).

Trace (Conceptual):

1. Initial State: Set of positive examples includes all (child, parent) pairs, (grandchild, grandparent) pairs, etc.
2. Iteration 1: Learn First Rule
    * LEARN-ONE-RULE searches and finds the shortest, most accurate rule covering the simplest positive examples (direct parent-child relationship).
    * Rule R1 Learned: R_1 = IF Parent(x, y) THEN Ancestor(x, y).
    * Covering: All positive examples that are direct parent-child relations are covered and removed from the training set.
3. Iteration 2: Learn Second Rule
    * The remaining examples are all relations spanning two or more generations (e.g., grandchild-grandparent).
    * LEARN-ONE-RULE searches and finds a rule that explains these remaining examples, possibly using the target predicate itself for recursion.
    * Rule R2 Learned: R_2 = IF Parent(x, z) ^ Ancestor(z, y) THEN Ancestor(x, y).
    * Covering: This recursive rule can now cover all remaining examples (e.g., using R2 once to reduce a grandparent case to a parent case which R1 covers, or using R2 multiple times for great-grandparents). All remaining positive examples are covered and removed.
4. Termination: Since all positive examples are covered, the algorithm terminates, resulting in the complete set of rules {R_1, R_2}.

This illustrates how the complex concept is learned by sequentially generating relatively simpler, specialized rules.

---
3 (b) Discuss three different methods for using prior knowledge to alter the search performed by inductive methods. (6 Marks)

Prior knowledge (also called background knowledge, B) is information given to the learner beyond the training examples. It can be used by inductive learning methods (which generalize from empirical data) to subtly or significantly change the search through the hypothesis space (H).

Here are three ways prior knowledge is used to alter or guide the search:

1. Constraining the Hypothesis Space (Inductive Bias)

* Method: Prior knowledge is used to define a restricted, smaller hypothesis space H. This is the primary mechanism of inductive bias.
* Mechanism in Search: This knowledge determines the form of the acceptable hypotheses, thereby guiding the search by ruling out large regions of the initially possible hypothesis space H. The learner does not even search those ruled-out regions.
* Example (Candidate-Elimination/Find-S): The prior constraint that the target concept must be representable as a conjunction of attribute values (e.g., < Sunny, Warm, ?, Strong, ?, ? >) excludes all disjunctive concepts. This massive, explicit constraint on the hypothesis space enables the algorithm to generalize from few examples.

---

2. Guiding the Search with Preference Bias

* Method: Prior knowledge (often in the form of domain-specific heuristics or constraints) is used to establish a preference ordering among the hypotheses within H that are consistent with the data.
* Mechanism in Search: This knowledge serves as a heuristic for the search algorithm, favoring one type of hypothesis (e.g., the simplest, shortest, or most general) over another. The search algorithm actively chooses a preferred path in the search space based on this preference.
* Example (FIND-S): The design of FIND-S incorporates a preference for the most specific hypothesis. Its internal logic embodies the implicit assumption: "all instances are negative instances unless the opposite is entailed by its other knowledge". It follows only one branch of the general-to-specific ordering, thereby avoiding the expense of exploring all possible generalizations.

---

3. Augmenting the Instance Description (Constructive Induction / ILP)

* Method: Prior knowledge (background information, B) is used in the Inductive Logic Programming (ILP) framework (e.g., in systems like FOIL or CIGOL) to logically deduce or construct new, higher-level descriptive features for the instances.
* Mechanism in Search: This process enriches the raw data, allowing the search space to include more expressive hypotheses (e.g., first-order rules with variables and relational predicates) that mention these derived features.
* Example (ILP): When learning the concept Child(u, v) (child u of parent v), the raw data might only contain Father(Sharon, Bob). The prior knowledge B: Parent(u, v) <- Father(u, v) can be used to augment the data with the feature Parent(Sharon, Bob), allowing the learner to discover the more general rule Child(u, v) <- Parent(v, u). The search is altered by the expansion of the set of acceptable hypotheses.

---
3 (c) Explain Learn-One-Rule for General-to-Specific search process with the algorithm. (8 Marks)

The LEARN-ONE-RULE subroutine is the core component of the Sequential Covering Algorithm. Its goal is to find a single, highly accurate conjunctive rule. The version based on a General-to-Specific (G-to-S) Beam Search (similar to the CN2 program) is a widely explored method.

General-to-Specific Beam Search Principle

The search begins with the most general rule possible (the empty precondition, which matches every instance) and iteratively specializes it by greedily adding attribute tests until the rule achieves an acceptable level of performance (high accuracy).

* Direction: Starts with the most general hypothesis (empty set) and moves towards more specific hypotheses.
* Mechanism (Beam Search): Instead of keeping just one best candidate at each step (greedy depth-first search), a beam search maintains a list of the k best candidate hypotheses (conjunctive rules). This reduces the risk of making a suboptimal choice early on.

LEARN-ONE-RULE (General-to-Specific Beam Search) Algorithm

The algorithm below outlines the implementation used by CN2.

1. Initialization: B_hyp <- empty set; C_hyp <- {empty set}. Initialize Best hypothesis (B_hyp) to the most general hypothesis. Initialize Candidate hypotheses (C_hyp) to the set containing only the most general hypothesis.
2. Loop: While C_hyp is not empty, Do. The loop continues until no promising generalizations can be found.
3. Generate Specializations: Generate N_hyp. Compute all possible next more specific candidate hypotheses. A new hypothesis is generated by adding one new constraint (e.g., a=v) to an existing hypothesis in C_hyp.
4. Update Best: For each h in N_hyp, check if PERFORMANCE(h) is better than PERFORMANCE(B_hyp). The score is typically defined as -Entropy of the examples covered by h, aiming to maximize homogeneity/accuracy in the covered subset. Update B_hyp if a better rule is found.
5. Update Candidates: C_hyp <- the k best members of N_hyp. Reduce the set of new candidates N_hyp to the k most promising members based on the performance measure. These form the starting point for the next iteration's specializations.
6. Output: Return R. Return a rule R: "IF B_hyp THEN prediction", where the prediction is the most frequent value of the Target Attribute among the examples matched by B_hyp.

This process ensures that the function returns a single conjunctive rule that has the highest measured performance over the data encountered during the search.

---
4 (a) Discuss the explanation-based learning of search control knowledge. (6 Marks)

Explanation-Based Learning (EBL) is a form of analytical learning that uses a domain theory (prior knowledge) to analyze (explain) a single training example and construct a justified, general hypothesis, typically a rule.

Learning search control knowledge is one of the most important and successful applications of EBL, as it can satisfy the EBL requirement for a correct and complete domain theory. The goal is speedup learning, i.e., improving the problem solver's efficiency by learning explicit rules to guide its search.

Domain Theory in Search Problems

In search-intensive tasks (like planning, scheduling, or playing complex games like chess), the prior knowledge is the domain theory:
* Components of B: The definitions of the legal search operators, the initial states, and the goal state definitions (G) provide a complete and correct domain theory.
* The Dilemma: Although this domain theory is complete enough to find the optimal solution in principle, doing so is computationally intractable (e.g., the massive search space in chess). EBL's purpose is to reformulate this knowledge into a more operational form (i.e., fast, useful rules).

Mechanism: Learning Control Rules

EBL systems learn rules that help the performance system (planner/searcher) resolve internal search impasses.

1. Encountering an Impasse (Training Example): The system attempts to solve a problem and encounters a choice (e.g., "Which operator should I use?" or "Which subgoal should I solve first?") and eventually finds a successful/efficient path, or an inefficient/failing path (a negative example).
2. Explanation (Proof): The system constructs a proof (explanation), using the domain theory (B), of why the chosen sequence of operators/subgoal ordering leads to a goal (or avoids a failure).
3. Generalization (Rule Extraction): The EBL component (like PROLOG-EBG's regression) generalizes this explanation to find the most general preconditions under which the same advantageous choice (the same explanation/proof structure) is still valid. This results in a new, operational control rule.
4. Application: This learned rule is added to the system's control knowledge. When the system encounters a state that matches the rule's general preconditions, it can immediately use the rule's conclusion (e.g., "Apply Operator A") without repeating the extensive search that was originally required to justify the choice.

Example (PRODIGY):
A planning system like PRODIGY might learn the target concept: "The set of states in which subgoal A should be solved before subgoal B".

* Learned Rule: IF One subgoal is On(x, y) AND One subgoal is On(y, z) THEN Solve On(y, z) before On(x, y).
* Justification: The explanation (derived from rules of block-stacking) proves that solving On(x, y) first would lead to an eventual conflict that forces undoing the work. The rule is generalized to work for any blocks x, y, z.

---
4 (b) Illustrate FOIL Algorithm with its working principle. (6 Marks)

FOIL (First-Order Inductive Learner) is an algorithm designed for Inductive Logic Programming (ILP). It is a natural extension of the propositional Sequential Covering and LEARN-ONE-RULE algorithms to learn first-order rules (Horn clauses with variables).

Working Principle

FOIL learns a hypothesis H as a set of first-order rules, H = {R_1, R_2, ...}, which together predict when a Target-predicate is True. The process has two nested loops:

1. Outer Loop (Sequential Covering): Learns rules one at a time.
    * It begins with all Positive examples (Pos) and iteratively calls the inner loop to find a single new rule.
    * After learning a rule R_new, it removes all positive examples covered by R_new from Pos.
    * The loop continues until Pos is empty.

2. Inner Loop (LEARN-ONE-RULE - General-to-Specific Search): Learns a single rule R_new (the rule body/preconditions).
    * It starts with the most general rule (empty preconditions).
    * It performs a General-to-Specific, hill-climbing search (beam width of 1).
    * At each step, it specializes the current rule by greedily adding a single new literal to its preconditions.

Guiding the Search: FOIL Gain

To choose the best literal to add in the inner loop, FOIL uses the Foil Gain measure, which is based on information theory:

Foil_Gain(L, R) = t * ( log_2 (p_1 / (p_1 + n_1)) - log_2 (p_0 / (p_0 + n_0)) )

* R: The current rule being specialized.
* L: The candidate literal to add.
* R': The resulting rule after adding L.
* p_0, n_0: Number of positive/negative variable bindings covered by R.
* p_1, n_1: Number of positive/negative variable bindings covered by R'.
* t: Number of positive bindings of R that remain covered by R'.

Interpretation: Foil Gain measures the reduction in the total number of bits needed to encode the classification of the remaining positive bindings of R. The literal L that maximizes this gain is chosen.

Generating Candidate Literals (Specializations)

A crucial difference from propositional learners is how FOIL generates the candidate literals (L_{n+1}) to specialize the current rule P(x_1, ..., x_k) <- L_1...L_n. The new literal L_{n+1} can be:

1. Q(v_1, ..., v_r): Where Q is any predicate (other than the target) in the environment, and the v_i are either variables already in the rule (in the head or body) or new variables. At least one v_i must be an existing variable.
2. Equal(x_j, x_k): Equating two variables already in the rule.
3. Negation of either of the above.

This process allows FOIL to build highly expressive first-order rules, including rules that introduce new, existentially quantified variables (like z in Parent(y, z)) not present in the head, and potentially recursive rules by generating literals that refer to the target predicate itself (e.g., Ancestor(z, y)).

---
4 (c) Identify the reasons for learning perfect domain theory and explain explanation-based learning algorithm PROLOG-EBG. (8 Marks)

Reasons for Learning with a Perfect Domain Theory

A perfect domain theory (B) is one that is both correct (all assertions are true) and complete (covers every positive example of the target concept). If the learner already has a perfect theory, why does it need to learn?

The reasons center on efficiency and reformulation of knowledge:

1. Tractability/Efficiency Improvement (Speedup Learning):
    * In domains like chess or planning, the rules are perfectly known, but finding the optimal move or plan (the classification) requires searching a computationally intractable space.
    * EBL learns a new, highly operational hypothesis (rule) that maps directly from observable features to the target classification in a single inference step. The learned rule is a pre-compiled piece of knowledge that avoids the expensive, multi-step search of the original domain theory, thereby significantly speeding up performance.

2. Knowledge Reformulation:
    * Learning in this context is viewed as example-guided reformulation of theories or knowledge compilation. The learner uses examples to focus its reformulation efforts, turning the initial, abstract, and slow-to-use domain theory into a set of practical, efficient, special-case rules. It uncovers the useful consequences of its own knowledge, which is a key aspect of human learning in technical domains (like physics or chess).

PROLOG-EBG Algorithm

PROLOG-EBG (Explanation-Based Generalization) is a representative EBL algorithm that uses first-order Horn clauses for both the Domain Theory (B) and the learned hypotheses (h). It is a sequential covering algorithm that creates a justified hypothesis for each new positive example.

Core Steps (for each uncovered positive example, < x_i, f(x_i) >):

1. Explain the Training Example
    * Goal: Construct a formal proof (explanation) using the Domain Theory (B) showing how the input instance x_i satisfies the target concept f(x_i).
    * Mechanism: Typically performed using a backward chaining search (like a PROLOG proof tree). This proof implicitly determines which features of x_i are relevant (those mentioned in the proof) and which are irrelevant.

2. Analyze the Explanation (Generalize)
    * Goal: Compute the most general rule that is still logically justified by the structure of the proof.
    * Mechanism: Uses a procedure called regression (or weakest preimage calculation). This process works backward from the target concept at the root of the proof tree, generalizing the constraints at each step until it derives a set of maximally general initial assertions (the weakest preimage) at the leaf nodes.
    * Key Outcome: This process derives constraints on generalized variables (e.g., LessThan(wx, 5) instead of LessThan(0.6, 5)) and can construct new, useful features not explicit in the input (e.g., the constraint Equal(wx, times(vx, dx))).

3. Refine the Current Hypothesis
    * Goal: Create a new Horn clause rule h and add it to the hypothesis set H_learned.
    * Rule Construction: The body of h is the weakest preimage (the generalized leaf node constraints). The head is the Target Concept with consistent variable substitutions.
    * Conclusion: The output hypothesis h is a sufficient condition for satisfying the target concept, and it follows deductively from the domain theory.

---
---

Part C

5 (a) Discuss the issues in machine learning. (5 Marks)

The field of machine learning faces several fundamental and practical questions that guide research and development:

1. Algorithm Convergence and Performance
    * What algorithms exist for learning different types of target functions (e.g., numerical, symbolic, neural networks)?.
    * In which settings (e.g., specific hypothesis spaces, training conditions) can we guarantee that a particular algorithm will converge to the correct function, and how quickly?.

2. Data Sufficiency and Generalization Bounds
    * How much training data (E) is enough to achieve a desired level of accuracy?.
    * What are the fundamental theoretical bounds that relate the confidence in a learned hypothesis to the volume of training experience and the complexity (size) of the learner's hypothesis space (H)? (This is addressed by Computational Learning Theory, like the PAC model).

3. The Role of Prior Knowledge (Inductive Bias)
    * How and when should the learner's existing prior knowledge (inductive bias, domain theory) be used to guide the process of generalization beyond the raw data?.
    * Can this knowledge be useful even if it is only partially correct or noisy? (Addressed by combined inductive-analytical learning).

4. Optimal Training Strategy
    * What is the best strategy for selecting the next informative training experience or query?.
    * How does the choice of this data collection strategy affect the complexity and efficiency of the learning problem (e.g., passive versus active learning)?.

5. Representation and Feature Engineering
    * How should the overall learning task be broken down into specific function approximation problems (i.e., what specific function should the system try to learn, e.g., an evaluation function V-hat or a move function ChooseMove)?.
    * How can the learner automatically and constructively alter its own representation (e.g., generating new features or creating hidden units in a neural network) to improve its ability to represent and learn the target function?.

---
5 (b) Differentiate between inductive and analytical learning problems. (5 Marks)

The fundamental difference between inductive and analytical learning lies in the assumptions made about the learning task, particularly regarding the role and type of prior knowledge available.

| Feature | Inductive Learning Problem (Generalization from Data) | Analytical Learning Problem (Theory-Guided Generalization) |
| Goal | Find a hypothesis h in H that is consistent with the training examples (D). | Find a hypothesis h in H that is consistent with both the training examples (D) AND the Domain Theory (B). |
| Input Knowledge | Training Examples (D). | Training Examples (D) AND a Domain Theory (B) (background knowledge). |
| Process | Generalization: Finds features that empirically distinguish positive from negative examples (e.g., statistical correlation, entropy reduction). | Deduction/Explanation: Uses B to formally explain why an example is positive, then generalizes this explanation via logical reasoning (e.g., weakest preimage). |
| Hypothesis Space (H) | Learner's Inductive Bias (implicit assumptions about the form of h) often restricts the space (e.g., conjunctions) to enable generalization. | H is restricted by B. The constraint B entails not h effectively reduces the set of acceptable hypotheses, thereby reducing the total uncertainty (sample complexity). |
| Reliance | Performance is bound by the amount of training data available (statistical bounds). | Can generalize accurately from just one or a handful of examples because the proof provides the necessary information. |
| Example | Decision Tree Learning, Neural Networks, FIND-S. | Explanation-Based Learning (EBL), PROLOG-EBG. |

---
6 (a) Define version space and explain List-then-Eliminate algorithm. (5 Marks)

Version Space Definition

The Version Space (VS_H,D) is a concept learning construct related to a specific hypothesis space H and a set of training examples D.

VS_H,D is defined as the set of all hypotheses h in H such that h is consistent with D.

* Hypothesis Consistency: A hypothesis h is consistent with D if and only if it correctly classifies every example in D.
* Purpose: The version space contains all plausible versions of the target concept that have not yet been ruled out by the observed data.

List-Then-Eliminate Algorithm

The List-Then-Eliminate algorithm is the most straightforward (though impractical) method for computing the version space.

1. Initialization: VS <- All h in H. Initialize the Version Space (VS) list to contain every hypothesis in the entire hypothesis space H.
2. Iteration (Elimination): For each training example < x, c(x) > in D: Process each example incrementally. Remove h from VS if h(x) is not equal to c(x). Eliminate any hypothesis h from the list that is inconsistent with the current training example.
3. Output: Return VS. Output the final list. As more examples are seen, VS incrementally shrinks until ideally only one hypothesis remains.

Limitation: This algorithm requires exhaustively enumerating all hypotheses in H. This makes it infeasible for any realistic problem where the hypothesis space is large or infinite (e.g., 10^28 possible concepts in the EnjoySport problem). The Candidate-Elimination algorithm solves this by using the highly compact S and G boundary sets to represent the version space implicitly.

---
6 (b) Explain the learning problem in inductive-analytical approaches. (5 Marks)

The pure inductive and pure analytical approaches, while effective in some domains, have complementary weaknesses. Inductive-Analytical (or Hybrid) learning attempts to overcome these weaknesses by combining the strengths of both, particularly when the ideal conditions for each (plentiful data for induction, or a perfect theory for EBL) are not met.

The Problem of Imperfect Knowledge

The hybrid approach is primarily concerned with learning when the Domain Theory (B) is imperfect (i.e., incorrect, incomplete, or both).

* Incomplete Theory: If B is incomplete, EBL fails because it cannot construct an explanation/proof for all observed positive examples, preventing it from generalizing the concept from those examples.
* Incorrect Theory (Noise in B): If B contains errors, the hypothesis derived purely deductively from it will also be wrong. A "justified" but incorrect generalization is still incorrect.

The Hybrid Learning Problem

The combined problem formulation seeks a hypothesis h that optimally balances the constraints imposed by the imperfect domain theory B with the constraints imposed by the limited empirical data D.

1. Induction (Data-Driven) Role:
    * Correcting the Theory: Empirical generalization is used to statistically test and, if necessary, correct the generalizations suggested by the incomplete/incorrect theory. The inductive process handles the noise (in data or theory) that EBL cannot.
    * Extending Coverage: For examples that the incomplete theory (B) cannot explain, the learner falls back on purely inductive methods to find a hypothesis that covers these unexplained examples (new rules based purely on data correlation).

2. Analytical (Theory-Driven) Role:
    * Guiding Induction: The (imperfect) explanation is used as a strong form of inductive bias to constrain the space of hypotheses considered by the inductive component. The generalized structure suggested by the partial explanation guides the inductive search, making it much more focused and efficient than a purely statistical search.

In summary, the core problem in inductive-analytical learning is to find a hypothesis h that provides the best fit to the empirical data (D) while simultaneously remaining maximally consistent with the potentially flawed domain theory (B). The result is a single framework that can learn from both sparse data (using B) and correct or discover new knowledge (using D).
"""
    print(answers)