Random vs Grid Search: วิธีไหนดีกว่ากัน?
By: Weerasak Thachai
Tags: Hyperparameter, Python, Grid Search, Machine Learning

ก่อนหน้านี้ตัวผมเองก็ใช้แต่ grid search เพื่อค้นหา best paramters ไม่ได้คิดถึง random search เลย จนกระทั่งมีโอกาส train model หนึ่งซึ่งมี dataset ขนาดใหญ่ และมี paramters ที่ต้องปรับแต่งเยอะมาก การทำ grid search เพื่อค้นหา best paramters ให้ผลลัพธ์ที่ไม่น่าพอใจซักเท่าไหร่

จึงเริ่มหาข้อมูลและพบกว่า random search น่าจะดีกว่าไหม?

เมื่อค้นหาข้อมูลจึงได้พบบทความนี้ Random vs Grid Search: Which is Better? คนที่เคยใช้มาบ้าง scikit-learn น่าจะเคยใช้ hyperparameter optimization มาบ้างแล้ว อาจมีคำถามในใจระหว่าง grid search กับ random search อันไหนน่าจะมีประสิทธิภาพที่ดีกว่ากัน มีงานวิจัยของ Bergstra และ Bengio ชื่อว่า Random Search for Hyper-Parameter Optimization สรุปว่า random search ให้ประสิทธิภาพที่ดีกว่า grid search

ในการทดสอบครั้งนี้เราจะสร้าง terrain space matrix ขนาด [1024,1024] (Terrain Generation Tutorial: Hill Algorithm) โดยให้แต่ละ algoritm หาค่าที่ดีที่สุด คือ algorithm ไหนสามารถหาค่าจุดที่ลึกที่สุด ภายในการทดสอบ 25 รอบ

SIZE = 1024

NUM_ITERATIONS = 200 terrain = np.zeros((SIZE, SIZE), dtype="float")

mod_iter = NUM_ITERATIONS // 10

for iter in range(NUM_ITERATIONS):

if iter % mod_iter == 0:

print("iteration: {:d}".format(iter))

r = int(np.random.uniform(0, 0.2 * SIZE))

xc, yc = np.random.randint(0, SIZE - 1, 2)

z = 0

xmin, xmax = int(max(xc - r, 0)), int(min(xc + r, SIZE))

ymin, ymax = int(max(yc - r, 0)), int(min(yc + r, SIZE))

for x in range(xmin, xmax):

for y in range(ymin, ymax):

z = (r ** 2) - ((x - xc) ** 2 + (y - yc) ** 2)

if z > 0:

terrain[x, y] += z

print("total iterations: {:d}".format(iter))

# normalize to unit height

zmin = np.min(terrain)

terrain -= zmin

zmax = np.max(terrain)

terrain /= zmax

# smooth the terrain by squaring the z values

terrain = np.power(terrain, 2)

# multiply by 255 so terrain can be visualized as grayscale image

terrain = terrain * 255

terrain = terrain.astype("uint8")

# convert mountains to valleys since stochastic gradient DESCENT

terrain = 255 - terrain plt.title("terrain")

image = plt.imshow(terrain, cmap="gray")

plt.ylim(max(plt.ylim()), min(plt.ylim()))

plt.colorbar(image, shrink=0.8) plt.title("contours")

contour = plt.contour(terrain, linewidths=2)

plt.ylim(max(plt.ylim()), min(plt.ylim()))

plt.colorbar(contour, shrink=0.8, extend='both') นี่คือหน้าตาของ terrain ที่สร้างขึ้นรูปด้านซ้ายจุดที่มีสีเข้มที่สุดคือจุดที่ลึกที่สุด ด้านขวาเป็นรูป contours ของ terrain เพื่อแบ่งระดับชั้นของความลึก

algorithm แรกคือ grid search อย่างที่ได้กล่าวเรามีจำนวน 25 รอบเท่านั้นเพื่อหาค่า x และ y ที่ดีที่สุด ดังนั้นเราจะหาค่า x และ y อย่างละ5 รอบ แล้วใช้ grid search เพื่อหาค่า x, y ตำแหน่งไหนที่มีค่าที่ดีที่สุด # do a grid search (5x5)

results = []

for x in np.linspace(0, SIZE-1, 5):

for y in np.linspace(0, SIZE-1, 5):

xi, yi = int(x), int(y)

results.append([xi, yi, terrain[xi, yi]]) best_xyz = [r for r in sorted(results, key=operator.itemgetter(2))][0]

grid_best = best_xyz[2]

print(best_xyz) xvals = [r[0] for r in results]

yvals = [r[1] for r in results] plt.title("grid search")

contour = plt.contour(terrain, linewidths=2)

plt.ylim(max(plt.ylim()), min(plt.ylim()))

plt.scatter(xvals, yvals, color="b", marker="o")

plt.scatter([best_xyz[0]], [best_xyz[1]], color='b', s=200, facecolors='none', edgecolors='b')

plt.ylim(max(plt.ylim()), min(plt.ylim()))

plt.colorbar(contour)

ค่าที่ดีที่สุดที่ได้จาก grid search คือ จุดตัดที่ (767, 767) ซึ่งมีค่า 109 algorithm ที่สองคือ random search ใช้ random search ทำการสุ่มค่าของ x และ y จำนวน 25 รอบ แล้วหาค่าที่ดีที่สุด # do a random search

results = []

xvals, yvals, zvals = [], [], []

for i in range(25):

x = np.random.randint(0, SIZE, 1)[0]

y = np.random.randint(0, SIZE, 1)[0]

results.append((x, y, terrain[x, y])) best_xyz = sorted(results, key=operator.itemgetter(2))[0]

rand_best = best_xyz[2]

print(best_xyz) xvals = [r[0] for r in results]

yvals = [r[1] for r in results] plt.title("random search")

contour = plt.contour(terrain, linewidths=2)

plt.ylim(max(plt.ylim()), min(plt.ylim()))

plt.scatter(xvals, yvals, color="b", marker="o")

plt.scatter([best_xyz[0]], [best_xyz[1]], color='b', s=200, facecolors='none', edgecolors='b')

plt.colorbar(contour)

ในกรณีนี้ random search ทำได้ดีกว่านิดหน่อยคือค้นหาได้จุดตัดที่ (663, 618) มีค่า 103 algorthm ที่สาม batched random search เรามาลองใช้ random searchแบ่งจำนวนการทดสอบจาก 25 ครั้งออกเป็น 5 กลุ่ม (batches) ทำการทดสอบกลุ่มละ 5 ครั้ง ในแต่ละรอบจะสุ่มค้นหาจุด (x, y) ค่าที่ได้จากการสุ่มในแต่ละกลุ่ม เลือกค่าที่ดีที่สุด 2 อันดับแรก จากนั้นจะกำหนดกรอบ (window) รอบๆ จุดเหล่านี้และทำการสุ่มค่าในบริเวณนี้ ในแต่ละรอบขนาดของกรอบจะแคบลงเพื่อพยายามหาจุดที่น่าจะมีค่าที่ดีที่สุด def cooling_schedule(n, k):

""" reduces jitter window size at each run

n - number of batches

k - index (0 based) of current batch

returns multiplier (0..1) of SIZE

"""

return (n - k) / n results, winners = [], []

for bid in range(5):

print("Batch #: {:d}".format(bid), end="")



# compute window size

window_size = int(0.25 * cooling_schedule(5, bid) * SIZE) # jitter the winners and add their values to results

# at any point we will only keep the top 2 global winners

for x, y, _, _ in winners:

if x < SIZE // 2:

# left of center

xleft = max(x - window_size // 2, 0)

xright = xleft + window_size

else:

# right of center

xright = min(x + window_size // 2, SIZE)

xleft = xright - window_size

if y < SIZE // 2:

# bottom half

ybot = max(y - window_size // 2, 0)

ytop = ybot + window_size

else:

# top half

ytop = min(y + window_size // 2, 0)

ybot = ytop - window_size

xnew = np.random.randint(xleft, xright, 1)[0]

ynew = np.random.randint(ybot, ytop, 1)[0]

znew = terrain[xnew, ynew]

results.append((xnew, ynew, znew, bid))



# add remaining random points

for i in range(5 - len(winners)):

x = np.random.randint(0, SIZE, 1)[0]

y = np.random.randint(0, SIZE, 1)[0]

z = terrain[x, y]

results.append((x, y, z, 2)) # find the top 2 global winners

winners = sorted(results, key=operator.itemgetter(2))[0:2]

print(" best: ", winners[0])



best_xyz = sorted(results, key=operator.itemgetter(2))[0]

print(best_xyz) xvals = [r[0] for r in results]

yvals = [r[1] for r in results] plt.title("batched random search")

contour = plt.contour(terrain, linewidths=2)

plt.ylim(max(plt.ylim()), min(plt.ylim()))

plt.scatter(xvals, yvals, color="b", marker="o")

plt.scatter([best_xyz[0]], [best_xyz[1]], color='b', s=200, facecolors='none', edgecolors='b')

plt.colorbar(contour)