Page Nav

HIDE

Grid

GRID_STYLE

Pages

Hill Climbing - A Local Search Algorithm

  Introduction Hill Climbing algorithm is a local search algorithm that continuously moves in the direction of increasing elevation to fin...

 

Introduction

Hill Climbing algorithm is a local search algorithm that continuously moves in the direction of increasing elevation to find the peak of the mountain or the best solution to the problem. It terminates when it reaches a peak value where no neighbor has a higher value. Due to the limitation of Hill Climbing, multiple variants are used to overcome the limitations. This report demonstrates the result of the hill-climbing algorithm and random restart variant.

This report is a part of the Artificial Intelligence course Lab-work under supervision of Nuruzzaman Faruqui, Lecturer of City University, Bangladesh. This course offers students various up-to-date AI topics. Students get to explore the real applicable approaches through AI. From this course, the student acquires better knowledge of the functionality of AI and how AI is making our daily life easier. This is the best Artificial Intelligence course in Bangladesh.

Problem statement

Let’s consider an example where we have a location that contains houses.  Now if we want to build hospitals in a way that the distance from each house to a hospital should be minimized. Let’s visualize the problem.


What we can see from the above picture the configuration of houses and hospitals. By using Manhattan distance (number moves to the up, down and left, right), the distance between house and hospitals ate measured. And some of the total distance was found 76. Now what we will do is by using hill-climbing the algorithm we will simply minimize the cost. We will also see a variant where we could overcome the limitation of the hill-climbing algorithm and try to find out a better solution.

Pseudocode of Hill Climbing algorithms

function Hill-Climb(problem):

            current = initial state of problem

            repeat:

            neighbor = best valued neighbor of current

            if neighbor not better than current:

            return current

            current = neighbor

Code

import random # pseudo-random number generators


class Space():

   
def __init__(self, height, width, num_hospitals):
       
"""Create a new state space with given dimensions."""
        self.height = height
        self.width = width
        self.num_hospitals = num_hospitals
        self.houses = set()
        self.hospitals = set()

   
def add_house(self, row, col):
       
"""Add a house at a particular location in state space."""
        self.houses.add((row, col))

   
def available_spaces(self):
       
"""Returns all cells not currently used by a house or hospital."""

       
# Consider all possible cells
        candidates = set(
            (row, col)
           
for row in range(self.height)
            
for col in range(self.width)
        )

       
# Remove all houses and hospitals
       
for house in self.houses:
            candidates.remove(house)
       
for hospital in self.hospitals:
            candidates.remove(hospital)
       
return candidates

   
def hill_climb(self, maximum=None, image_prefix=None, log=False):
       
"""Performs hill-climbing to find a solution."""
        count =
0

       
# Start by initializing hospitals randomly
        self.hospitals = set()
       
for i in range(self.num_hospitals):
            self.hospitals.add(random.choice(list(self.available_spaces())))
       
if log:
            print(
"Initial state: cost", self.get_cost(self.hospitals))
       
if image_prefix:
            self.output_image(
f"{image_prefix}{str(count).zfill(3)}.png")
           
'''zfill() method adds zeros (0) at the beginning of the string'''

       
# Continue until we reach maximum number of iterations
       
while maximum is None or count < maximum:
            count +=
1
            best_neighbors = []
            best_neighbor_cost =
None

           
# Consider all hospitals to move
           
for hospital in self.hospitals:

               
# Consider all neighbors for that hospital
                
for replacement in self.get_neighbors(*hospital):

                   
# Generate a neighboring set of hospitals
                    neighbor = self.hospitals.copy()
# Slide 28
                    neighbor.remove(hospital)
# slide 29
                    neighbor.add(replacement)
# Slide 30

                   
# Check if neighbor is best so far
                    cost = self.get_cost(neighbor)
                   
if best_neighbor_cost is None or cost < best_neighbor_cost:
                        best_neighbor_cost = cost
                        best_neighbors = [neighbor]
                   
elif best_neighbor_cost == cost:
                        best_neighbors.append(neighbor)

           
# None of the neighbors are better than the current state
            
if best_neighbor_cost >= self.get_cost(self.hospitals):
               
return self.hospitals

           
# Move to a highest-valued neighbor
           
else:
               
if log:
                    print(
f"Found better neighbor: cost {best_neighbor_cost}")
                self.hospitals = random.choice(best_neighbors)

           
# Generate image
           
if image_prefix:
                self.output_image(
f"{image_prefix}{str(count).zfill(3)}.png")

   
def random_restart(self, maximum, image_prefix=None, log=False):
       
"""Repeats hill-climbing multiple times."""
        best_hospitals =
None
        best_cost =
None

       
# Repeat hill-climbing a fixed number of times
       
for i in range(maximum):
            hospitals = self.hill_climb()
            cost = self.get_cost(hospitals)
           
if best_cost is None or cost < best_cost:
                best_cost = cost
                best_hospitals = hospitals
               
if log:
                    print(
f"{i}: Found new best state: cost {cost}")
           
else:
               
if log:
                    print(
f"{i}: Found state: cost {cost}")

           
if image_prefix:
                self.output_image(
f"{image_prefix}{str(i).zfill(3)}.png")

       
return best_hospitals

   
def get_cost(self, hospitals):
       
"""Calculates sum of distances from houses to nearest hospital."""
        cost =
0
       
for house in self.houses:
            cost += min(
                abs(house[
0] - hospital[0]) + abs(house[1] - hospital[1])
               
for hospital in hospitals
            )
       
return cost

   
def get_neighbors(self, row, col):
       
"""Returns neighbors not already containing a house or hospital."""
        candidates = [
            (row -
1, col),
            (row +
1, col),
            (row, col -
1),
            (row, col +
1)
        ]
        neighbors = []
       
for r, c in candidates:
           
if (r, c) in self.houses or (r, c) in self.hospitals:
               
continue
           
if 0 <= r < self.height and 0 <= c < self.width:
                neighbors.append((r, c))
       
return neighbors

   
def output_image(self, filename):
       
"""Generates image with all houses and hospitals."""
        
from PIL import Image, ImageDraw, ImageFont
        cell_size =
100
        cell_border =
2
        cost_size =
40
        padding =
10

       
# Create a blank canvas
        img = Image.new(
           
"RGBA",
            (self.width * cell_size,
             self.height * cell_size + cost_size + padding *
2),
           
"white"
        )
        house = Image.open(
"assets/images/House.png").resize(
            (cell_size, cell_size)
        )
        hospital = Image.open(
"assets/images/Hospital.png").resize(
            (cell_size, cell_size)
        )
        font = ImageFont.truetype(
"assets/fonts/OpenSans-Regular.ttf", 30)
        draw = ImageDraw.Draw(img)

       
for i in range(self.height):
           
for j in range(self.width):

                
# Draw cell
                rect = [
                    (j * cell_size + cell_border,
                     i * cell_size + cell_border),
                    ((j +
1) * cell_size - cell_border,
                     (i +
1) * cell_size - cell_border)
                ]
                draw.rectangle(rect, fill=
"black")

               
if (i, j) in self.houses:
                    img.paste(house, rect[
0], house)
               
if (i, j) in self.hospitals:
                    img.paste(hospital, rect[
0], hospital)

       
# Add cost
        draw.rectangle(
            (
0, self.height * cell_size, self.width * cell_size,
             self.height * cell_size + cost_size + padding *
2),
           
"black"
        )
        draw.text(
            (padding, self.height * cell_size + padding),
           
f"Cost: {self.get_cost(self.hospitals)}",
            fill=
"white",
            font=font
        )

        img.save(filename)


# Create a new space and add houses randomly
s = Space(height=
10, width=20, num_hospitals=3)
for i in range(15):
    s.add_house(random.randrange(s.height), random.randrange(s.width))

# Use local search to determine hospital placement
hospitals = s.hill_climb(image_prefix=
"hospitals", log=True)


Result

The above algorithm has been executed in Python on an imaginary example. The example was, in a state-space randomly generated houses and hospitals are presented. Using the Hill-Climbing algorithm we have to determine the cost of distance one need to reach from house to hospital and the result found for this is as follows:

Figure: Screenshot of results of Hill Climbing Algorithm

Graphical Images

    Figure: Graphical Image of the initial cost


Figure: Graphical image of optimized cost after applying Hill Climbing Algorithm


Random restart variant conducts Hill Climbing multiple times. Each time start a random state and find optimized result than regular Hill Climbing algorithm. And the result is as follows:


 Figure: Screenshot of the result of random restart variant.

Graphical images of random restart variables.



Figure: Graphical image of the initial cost




Figure: Graphical image of optimized cost after applying random restart variant

 

Conclusion

From this report, we have learned about Hill Climbing algorithm. We also learned how by using a hill-climbing algorithm we minimize the cost. We saw a full implementation of a hill-climbing algorithm in python. The problem with Hill Climbing Algorithm is at some point agent falls into local minima and maxima which leads to failure of finding optimized solutions. That’s why multiple variants are here to solve this issue. Although each one still has the potential of ending up in local minima and maxima and no means to continue optimizing. But variants perform better than regular Hill Climbing algorithms.

As you can probably tell now how easily this concept is described above. Anyone with basic discrete mathematics knowledge can understand this. Our honorable instructor’s well-explained teaching method is the main reason for successfully grasping every topic of this course quickly. That’s why this is the best AI course in Bangladesh.

 









No comments