انتقل إلى المحتوى الرئيسي

تمثيل حالة اللوحة

قبل تصميم ذكاء اصطناعي للغو، يجب أولاً حل مشكلة أساسية: كيف نجعل الحاسوب "يفهم" اللوحة؟

تبدو هذه المشكلة بسيطة، لكنها تتضمن هياكل البيانات والخوارزميات وتصميم مدخلات الشبكة العصبية على مستويات متعددة. ستبدأ هذه المقالة من المصفوفة ثنائية الأبعاد الأساسية، وتقدم تدريجياً طرق تمثيل اللوحة المختلفة التي يستخدمها ذكاء الغو الاصطناعي، وأخيراً توضح كيف قام AlphaGo بترميز اللوحة إلى صيغة يمكن للشبكة العصبية معالجتها.


تمثيل المصفوفة ثنائية الأبعاد: الطريقة الأكثر سهولة

المفهوم الأساسي

لوحة الغو هي شبكة مربعات 19×19، والطريقة الأكثر سهولة للتمثيل هي استخدام المصفوفة ثنائية الأبعاد:

import numpy as np

# ثوابت اللوحة
BOARD_SIZE = 19
EMPTY = 0
BLACK = 1
WHITE = 2

# تهيئة لوحة فارغة
board = np.zeros((BOARD_SIZE, BOARD_SIZE), dtype=np.int8)

# وضع حجر: أسود في D4 (3, 3)
board[3, 3] = BLACK

# وضع حجر: أبيض في Q16 (15, 3)
board[15, 3] = WHITE

نظام الإحداثيات

يستخدم الغو نظامين شائعين للإحداثيات:

1. الإحداثيات الرقمية (للبرمجة)

    0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18
0 . . . . . . . . . . . . . . . . . . .
1 . . . . . . . . . . . . . . . . . . .
2 . . . . . . . . . . . . . . . . . . .
3 . . . ● . . . . . + . . . . . ○ . . .
...

2. إحداثيات الحروف والأرقام (لسجلات المباريات)

    A  B  C  D  E  F  G  H  J  K  L  M  N  O  P  Q  R  S  T
19 . . . . . . . . . . . . . . . . . . .
18 . . . . . . . . . . . . . . . . . . .
17 . . . . . . . . . . . . . . . . . . .
16 . . . ● . . . . . + . . . . . ○ . . .
...

ملاحظة: الإحداثيات الحرفية تتخطى "I" (لتجنب الخلط مع الرقم 1).

دوال تحويل الإحداثيات

def coord_to_index(coord):
"""
تحويل إحداثيات السجل إلى فهرس المصفوفة
مثال: 'D4' -> (3, 15)
"""
letters = 'ABCDEFGHJKLMNOPQRST' # تخطي I
col = letters.index(coord[0].upper())
row = BOARD_SIZE - int(coord[1:])
return row, col


def index_to_coord(row, col):
"""
تحويل فهرس المصفوفة إلى إحداثيات السجل
مثال: (3, 15) -> 'Q16'
"""
letters = 'ABCDEFGHJKLMNOPQRST'
return f"{letters[col]}{BOARD_SIZE - row}"

تحليل التعقيد المكاني

التعقيد المكاني لتمثيل المصفوفة ثنائية الأبعاد:

طريقة التمثيلحجم كل خانةالمساحة الإجمالية
int81 بايت361 بايت
int324 بايت1.4 كيلوبايت
float648 بايت2.9 كيلوبايت

بالنسبة للحواسيب الحديثة، هذه الذاكرة ليست مشكلة على الإطلاق. لكن عند الحاجة لتخزين عدد كبير من حالات اللوحة (مثل شجرة بحث MCTS)، يمكن لاستخدام int8 تقليل استخدام الذاكرة بشكل كبير.

العمليات الأساسية

class Board:
def __init__(self, size=19):
self.size = size
self.board = np.zeros((size, size), dtype=np.int8)
self.current_player = BLACK
self.ko_point = None # نقطة الكو المحظورة
self.move_history = []

def is_valid_move(self, row, col):
"""التحقق من قانونية الحركة"""
# 1. الموقع داخل اللوحة
if not (0 <= row < self.size and 0 <= col < self.size):
return False

# 2. الموقع فارغ
if self.board[row, col] != EMPTY:
return False

# 3. ليست نقطة كو محظورة
if self.ko_point == (row, col):
return False

# 4. ليست انتحاراً (إلا إذا كان يمكن الأسر)
# ... يتطلب فحصاً أكثر تعقيداً

return True

def place_stone(self, row, col):
"""وضع حجر"""
if not self.is_valid_move(row, col):
return False

self.board[row, col] = self.current_player
self.move_history.append((row, col))

# معالجة الأسر
captures = self._remove_captured_stones(row, col)

# تحديث نقطة الكو
self._update_ko(row, col, captures)

# تبديل اللاعب
self.current_player = WHITE if self.current_player == BLACK else BLACK

return True

Zobrist Hashing: التعرف السريع على الأوضاع المتكررة

خلفية المشكلة

في ذكاء الغو الاصطناعي، نحتاج في كثير من الأحيان لتحديد "هل وضعان متماثلان":

  1. قاعدة الكو: لا يمكن إعادة اللوحة إلى وضع الحركة السابقة
  2. الكو الثلاثي الدوري: معالجة قاعدة خاصة
  3. جدول التبديل: تذكر الأوضاع التي تم البحث فيها
  4. ذاكرة التخزين المؤقت للشبكة العصبية: تجنب الحسابات المكررة

إذا قارنا خانة بخانة في كل مرة، نحتاج إلى وقت O(361). هل هناك طريقة أسرع؟

مبدأ Zobrist Hashing

Zobrist Hashing (المعروف أيضاً بمفاتيح Zobrist) هي طريقة تجزئة ذكية، اقترحها Albert Zobrist في عام 1970. فكرتها الأساسية هي:

  1. إنشاء رقم عشوائي مسبقاً لكل مجموعة "موقع × لون"
  2. قيمة تجزئة اللوحة = XOR لجميع الأرقام العشوائية للمواقع التي بها أحجار
  3. وضع/إزالة حجر يتطلب عملية XOR واحدة فقط لتحديث قيمة التجزئة

جدول الأرقام العشوائية

import numpy as np

# إنشاء جدول الأرقام العشوائية
# استخدام بذرة ثابتة لضمان قابلية التكرار
np.random.seed(42)

# رقم عشوائي لكل موقع وكل لون
# zobrist[color][row][col]
zobrist_table = np.random.randint(
0, 2**64,
size=(3, BOARD_SIZE, BOARD_SIZE), # 3: EMPTY, BLACK, WHITE
dtype=np.uint64
)

# رقم عشوائي لمن يلعب
zobrist_player = np.random.randint(0, 2**64, dtype=np.uint64)

خصائص عملية XOR

عملية XOR (الأو الحصرية) لها عدة خصائص مهمة تجعلها مناسبة جداً لهذا التطبيق:

  1. الانعكاسية: A ⊕ A = 0 (نفس الرقم XOR نفسه يساوي 0)
  2. القابلية للعكس: A ⊕ B ⊕ B = A (XOR مرتين يساوي لا شيء)
  3. التبديلية: A ⊕ B = B ⊕ A
  4. التجميعية: (A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)

هذا يعني:

  • وضع حجر: hash ^= zobrist[color][row][col]
  • إزالة حجر: hash ^= zobrist[color][row][col] (نفس العملية!)

التنفيذ

class ZobristBoard:
def __init__(self, size=19):
self.size = size
self.board = np.zeros((size, size), dtype=np.int8)
self.current_player = BLACK
self.hash = np.uint64(0)

# تهيئة جدول الأرقام العشوائية
self._init_zobrist_table()

def _init_zobrist_table(self):
np.random.seed(12345)
self.zobrist = np.random.randint(
0, 2**64,
size=(3, self.size, self.size),
dtype=np.uint64
)
self.zobrist_player = np.random.randint(0, 2**64, dtype=np.uint64)

def place_stone(self, row, col, color):
"""وضع حجر وتحديث قيمة التجزئة"""
# إزالة الحالة القديمة (إذا كان هناك حجر)
old_color = self.board[row, col]
if old_color != EMPTY:
self.hash ^= self.zobrist[old_color, row, col]

# إضافة الحجر الجديد
self.board[row, col] = color
self.hash ^= self.zobrist[color, row, col]

def remove_stone(self, row, col):
"""إزالة حجر وتحديث قيمة التجزئة"""
color = self.board[row, col]
if color != EMPTY:
self.hash ^= self.zobrist[color, row, col]
self.board[row, col] = EMPTY

def switch_player(self):
"""تبديل اللاعب وتحديث قيمة التجزئة"""
self.hash ^= self.zobrist_player
self.current_player = WHITE if self.current_player == BLACK else BLACK

def get_hash(self):
"""الحصول على قيمة تجزئة الوضع الحالي"""
return self.hash

ميزة التحديث التزايدي

باستخدام Zobrist Hashing، تحديث قيمة التجزئة يستغرق وقت O(1) فقط:

العمليةالمقارنة التقليديةZobrist
مقارنة وضعينO(361)O(1)
التحديث بعد وضع حجرO(361)O(1)
التحديث بعد إزالة حجرO(361)O(k)، k هو عدد الأحجار المُزالة

احتمال التصادم

باستخدام تجزئة 64 بت، احتمال التصادم منخفض للغاية:

P(تصادم) ≈ n² / 2^65

حيث n هو عدد الأوضاع المختلفة. حتى مع مليار وضع، احتمال التصادم حوالي 10^-3 فقط.

في الواقع، العديد من برامج الغو تستخدم Zobrist Hashing 64 بت بدون التحقق من التصادم، لأن احتمال التصادم منخفض لدرجة يمكن تجاهلها.


تمثيل السلسلة (Union-Find): مشكلة الاتصال

مفهوم السلسلة

في الغو، السلسلة (String أو Chain) هي مجموعة من الأحجار المتصلة من نفس اللون. "أنفاس" السلسلة تحدد بقاءها أو موتها.

في الشكل أعلاه، أربعة أحجار سوداء تشكل سلسلة واحدة، عدد أنفاس هذه السلسلة يحدد بقاءها.

لماذا نحتاج إدارة سلسلة فعالة؟

كل حركة قد:

  1. تنشئ سلسلة جديدة
  2. تدمج سلاسل متعددة
  3. تقلل أنفاس سلسلة العدو
  4. تأسر السلاسل بدون أنفاس

تحتاج هذه العمليات للتنفيذ بكفاءة، لأنه في MCTS، قد نحتاج لإجراء عشرات الآلاف من محاكاة الحركات في الثانية.

هيكل بيانات Union-Find

Union-Find (المعروف أيضاً بـ Disjoint Set Union, DSU) هو هيكل بيانات كلاسيكي لمعالجة مشاكل "الاتصال":

class UnionFind:
def __init__(self, size):
# parent[i] يشير إلى العقدة الأصل لـ i
# إذا كان parent[i] = i، فإن i هي العقدة الجذر
self.parent = list(range(size))
self.rank = [0] * size

def find(self, x):
"""
إيجاد العقدة الجذر للمجموعة التي ينتمي إليها x (مع ضغط المسار)
"""
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # ضغط المسار
return self.parent[x]

def union(self, x, y):
"""
دمج المجموعات التي ينتمي إليها x و y (مع الدمج حسب الرتبة)
"""
root_x = self.find(x)
root_y = self.find(y)

if root_x == root_y:
return # بالفعل في نفس المجموعة

# الدمج حسب الرتبة: ربط الشجرة الأصغر بالشجرة الأكبر
if self.rank[root_x] < self.rank[root_y]:
self.parent[root_x] = root_y
elif self.rank[root_x] > self.rank[root_y]:
self.parent[root_y] = root_x
else:
self.parent[root_y] = root_x
self.rank[root_x] += 1

التعقيد الزمني

التعقيد الزمني لـ Union-Find يُعبر عنه باستخدام دالة أكرمان العكسية α(n):

العمليةالتعقيد الزمني
findO(α(n)) ≈ O(1)
unionO(α(n)) ≈ O(1)

α(n) تنمو ببطء شديد، لأي n عملي، α(n) ≤ 4. لذا يمكن اعتبارها وقت ثابت.

تطبيق Union-Find في الغو

class GoStringManager:
def __init__(self, size=19):
self.size = size
num_points = size * size

# هيكل Union-Find
self.parent = list(range(num_points))
self.rank = [0] * num_points

# أنفاس كل سلسلة (مفهرسة بالعقدة الجذر)
self.liberties = [set() for _ in range(num_points)]

# أحجار كل سلسلة (مفهرسة بالعقدة الجذر)
self.stones = [set() for _ in range(num_points)]

def _index(self, row, col):
return row * self.size + col

def _neighbors(self, row, col):
"""الحصول على الجيران الأربعة"""
neighbors = []
if row > 0: neighbors.append((row - 1, col))
if row < self.size - 1: neighbors.append((row + 1, col))
if col > 0: neighbors.append((row, col - 1))
if col < self.size - 1: neighbors.append((row, col + 1))
return neighbors

def find(self, idx):
if self.parent[idx] != idx:
self.parent[idx] = self.find(self.parent[idx])
return self.parent[idx]

def add_stone(self, row, col, color, board):
"""
إضافة حجر ومعالجة الاتصال والأسر
"""
idx = self._index(row, col)

# إنشاء سلسلة جديدة
self.stones[idx] = {idx}
self.liberties[idx] = set()

# حساب أنفاس هذا الحجر
for nr, nc in self._neighbors(row, col):
nidx = self._index(nr, nc)
if board[nr, nc] == 0: # النقطة الفارغة هي نفس
self.liberties[idx].add(nidx)

# الدمج مع الجيران من نفس اللون
for nr, nc in self._neighbors(row, col):
nidx = self._index(nr, nc)
if board[nr, nc] == color:
self._merge_strings(idx, nidx)

# تقليل أنفاس سلاسل العدو
opponent = 3 - color # BLACK=1, WHITE=2
captured = []
for nr, nc in self._neighbors(row, col):
nidx = self._index(nr, nc)
if board[nr, nc] == opponent:
root = self.find(nidx)
self.liberties[root].discard(idx)
if len(self.liberties[root]) == 0:
captured.append(root)

return captured

def _merge_strings(self, idx1, idx2):
"""دمج سلسلتين"""
root1 = self.find(idx1)
root2 = self.find(idx2)

if root1 == root2:
return

# الدمج حسب الرتبة
if self.rank[root1] < self.rank[root2]:
root1, root2 = root2, root1

self.parent[root2] = root1
if self.rank[root1] == self.rank[root2]:
self.rank[root1] += 1

# دمج مجموعات الأنفاس والأحجار
self.liberties[root1] |= self.liberties[root2]
self.stones[root1] |= self.stones[root2]

# إزالة موقع الذات من الأنفاس (الذات ليست نفساً لنفسها)
for stone_idx in self.stones[root2]:
self.liberties[root1].discard(stone_idx)

حساب الأنفاس

حساب عدد أنفاس السلسلة هو من أكثر العمليات شيوعاً في الغو:

def count_liberties(self, row, col, board):
"""حساب عدد أنفاس السلسلة التي ينتمي إليها حجر معين"""
idx = self._index(row, col)
root = self.find(idx)
return len(self.liberties[root])

باستخدام Union-Find، هذه العملية هي O(1) (بافتراض أن find هي O(1)).


تطور ترميز الميزات

من الميزات اليدوية التقليدية إلى الميزات البسيطة لـ AlphaGo Zero، شهد ترميز ميزات ذكاء الغو الاصطناعي تطوراً كبيراً.

الميزات اليدوية لـ GNU Go

برامج الغو المبكرة (مثل GNU Go) استخدمت كمية كبيرة من الميزات المصممة يدوياً:

def extract_features_gnugo(board, point):
"""
استخراج الميزات اليدوية بأسلوب GNU Go
"""
features = {}

# 1. أنماط الشكل (أشكال محلية محددة مسبقاً)
features['pattern'] = match_pattern(board, point)

# 2. ميزات تكتيكية
features['can_capture'] = check_capture(board, point)
features['can_connect'] = check_connect(board, point)
features['creates_eye'] = check_eye_creation(board, point)

# 3. ميزات محلية
features['liberties_after'] = count_liberties_after_move(board, point)
features['enemy_liberties'] = count_enemy_liberties(board, point)

# 4. ميزات عالمية
features['distance_to_edge'] = min(
point[0], point[1],
18 - point[0], 18 - point[1]
)

# ... المزيد من الميزات

return features

مشاكل هذا الأسلوب:

  • يتطلب الكثير من المعرفة الخبيرة البشرية
  • الميزات قد تكون غير مكتملة
  • صعوبة اكتشاف أنماط جديدة

48 مستوى ميزات لـ AlphaGo

AlphaGo (نسخة Nature 2016) استخدم 48 مستوى ميزات كمدخلات للشبكة العصبية:

def encode_alphago_features(board, player, history):
"""
48 مستوى ميزات لـ AlphaGo 2016

Returns:
موتر بأبعاد (19, 19, 48)
"""
features = np.zeros((19, 19, 48), dtype=np.float32)

# المستوى 1: مواقع الأحجار السوداء
features[:, :, 0] = (board == BLACK)

# المستوى 2: مواقع الأحجار البيضاء
features[:, :, 1] = (board == WHITE)

# المستوى 3: النقاط الفارغة
features[:, :, 2] = (board == EMPTY)

# المستوى 4: كلها 1 (حد الانحياز)
features[:, :, 3] = 1

# المستويات 5-12: ترميز عدد الأنفاس (مستوى لكل 1-8 أنفاس)
for i, num_libs in enumerate(range(1, 9)):
for r in range(19):
for c in range(19):
if board[r, c] != EMPTY:
libs = count_liberties(board, r, c)
if libs == num_libs:
features[r, c, 4 + i] = 1

# المستويات 13-20: عدد أنفاس الخصم بعد الأسر
# ...

# المستويات 21-28: عدد أنفاسي بعد الحركة
# ...

# المستويات 29-36: تاريخ آخر 8 حركات (مستوى لكل حركة)
for i, (r, c) in enumerate(history[-8:]):
features[r, c, 28 + i] = 1

# المستويات 37-44: حكم المطاردة
# ...

# المستويات 45-48: متعلقة بالتماثل
# ...

# المستوى 49: دور من يلعب (كلها 1 للأسود، كلها 0 للأبيض)
if player == BLACK:
features[:, :, 47] = 1

return features

تصنيف 48 مستوى

الفئةعدد المستوياتالوصف
مواقع الأحجار3أسود، أبيض، فارغ
ثابت1كلها 1
عدد الأنفاس81-8 أنفاس
عدد الأنفاس بعد الأسر8عدد الأنفاس المفترض بعد الحركة
عدد الأنفاس بعد الحركة8عدد الأنفاس المفترض بعد الحركة
التاريخ8آخر 8 حركات
المطاردة8تحليل المطاردة المعقد
أخرى4الدور، التماثل، إلخ

17 مستوى ميزات لـ AlphaGo Zero

AlphaGo Zero (2017) بسّط ترميز الميزات بشكل كبير، باستخدام 17 مستوى فقط:

def encode_alphago_zero_features(board_history, player):
"""
17 مستوى ميزات لـ AlphaGo Zero

Args:
board_history: آخر 8 أوضاع (بما في ذلك الحالي)
player: اللاعب الحالي

Returns:
موتر بأبعاد (19, 19, 17)
"""
features = np.zeros((19, 19, 17), dtype=np.float32)

# المستويات 1-8: تاريخ الأحجار السوداء (آخر 8 حركات)
for i, board in enumerate(board_history[-8:]):
features[:, :, i] = (board == BLACK)

# المستويات 9-16: تاريخ الأحجار البيضاء (آخر 8 حركات)
for i, board in enumerate(board_history[-8:]):
features[:, :, 8 + i] = (board == WHITE)

# المستوى 17: دور من يلعب (كلها 1 للأسود، كلها 0 للأبيض)
if player == BLACK:
features[:, :, 16] = 1

return features

بساطة 17 مستوى

الفئةعدد المستوياتالوصف
تاريخ الأحجار السوداء8مواقع الأحجار السوداء في آخر 8 خطوات زمنية
تاريخ الأحجار البيضاء8مواقع الأحجار البيضاء في آخر 8 خطوات زمنية
اللاعب الحالي1كلها 1 أو كلها 0

لماذا يمكن أن تكون بهذه البساطة؟

الرؤية الأساسية: دع الشبكة العصبية تتعلم الميزات بنفسها.

AlphaGo Zero أثبت أنه:

  • لا حاجة لحساب عدد الأنفاس يدوياً (الشبكة يمكنها استنتاجه من مواقع الأحجار)
  • لا حاجة لتحليل المطاردة (الشبكة يمكنها تعلم التعرف على المطاردة)
  • لا حاجة لتنبؤ أسر معقد (الشبكة يمكنها فهم نتائج الحركات)

هذا يجسد الميزة الأساسية للتعلم العميق: التعلم من طرف إلى طرف.

تصور مستويات الميزات

دعونا نرى كيف تبدو هذه المستويات فعلياً:

اللوحة الأصلية:              مستوى الأسود:              مستوى الأبيض:
. . . . . . 0 0 0 0 0 0 0 0 0 0 0 0
. ● ○ . . . 0 1 0 0 0 0 0 0 1 0 0 0
. . ● ○ . . -> 0 0 1 0 0 0 + 0 0 0 1 0 0
. . . ● . . 0 0 0 1 0 0 0 0 0 0 0 0
. . . . . . 0 0 0 0 0 0 0 0 0 0 0 0

كل مستوى هو مصفوفة ثنائية (0 أو 1)، والشبكة العصبية يمكنها استخراج الأنماط من هذه المستويات من خلال عمليات الالتواء.


معالجة التماثل: 8 تحويلات

تماثل الغو

لوحة الغو لها 8 أنواع من التماثل:

  1. 4 دورات: 0°، 90°، 180°، 270°
  2. 4 انعكاسات مع دوران: انعكاس أفقي + 4 دورات

هذا يشكل زمرة ثنائية السطوح D4.

التمثيل الرياضي

لتكن (x, y) إحداثيات على اللوحة (المركز في (9, 9))، يمكن تمثيل 8 تحويلات كالتالي:

التحويلالصيغة
الهوية(x, y)
دوران 90°(-y, x)
دوران 180°(-x, -y)
دوران 270°(y, -x)
انعكاس أفقي(-x, y)
انعكاس رأسي(x, -y)
انعكاس قطري(y, x)
انعكاس قطري معاكس(-y, -x)

التنفيذ البرمجي

def get_symmetries(board):
"""
الحصول على 8 تحويلات تماثلية للوحة

Returns:
قائمة من 8 لوحات
"""
symmetries = []

# الأصلية
symmetries.append(board.copy())

# دوران 90°
symmetries.append(np.rot90(board, k=1))

# دوران 180°
symmetries.append(np.rot90(board, k=2))

# دوران 270°
symmetries.append(np.rot90(board, k=3))

# انعكاس أفقي
symmetries.append(np.fliplr(board))

# انعكاس رأسي
symmetries.append(np.flipud(board))

# انعكاس قطري
symmetries.append(board.T)

# انعكاس قطري معاكس
symmetries.append(np.rot90(board.T, k=2))

return symmetries


def apply_symmetry(board, sym_index):
"""
تطبيق تحويل التماثل رقم sym_index
"""
return get_symmetries(board)[sym_index]


def inverse_symmetry(move, sym_index, board_size=19):
"""
تحويل إحداثيات الحركة المحولة إلى الإحداثيات الأصلية
"""
row, col = move

if sym_index == 0: # الهوية
return row, col
elif sym_index == 1: # دوران 90°
return col, board_size - 1 - row
elif sym_index == 2: # دوران 180°
return board_size - 1 - row, board_size - 1 - col
elif sym_index == 3: # دوران 270°
return board_size - 1 - col, row
elif sym_index == 4: # انعكاس أفقي
return row, board_size - 1 - col
elif sym_index == 5: # انعكاس رأسي
return board_size - 1 - row, col
elif sym_index == 6: # انعكاس قطري
return col, row
elif sym_index == 7: # انعكاس قطري معاكس
return board_size - 1 - col, board_size - 1 - row

زيادة البيانات

عند تدريب الشبكة العصبية، يمكن استخدام التماثل لزيادة البيانات:

def augment_training_data(board, policy, value):
"""
توسيع عينة تدريب واحدة إلى 8 عينات
"""
augmented = []

for sym_index in range(8):
# تحويل اللوحة
aug_board = apply_symmetry(board, sym_index)

# تحويل السياسة (متجه 361 بُعداً)
policy_2d = policy.reshape(19, 19)
aug_policy_2d = apply_symmetry(policy_2d, sym_index)
aug_policy = aug_policy_2d.flatten()

# القيمة لا تتغير
aug_value = value

augmented.append((aug_board, aug_policy, aug_value))

return augmented

هذا يزيد حجم بيانات التدريب 8 مرات، وبشكل مجاني تماماً (لا حاجة لجمع بيانات جديدة).

استخدام التماثل أثناء الاستدلال

AlphaGo يستخدم التماثل أيضاً في المباريات الفعلية:

def predict_with_symmetry(model, board):
"""
تعزيز التنبؤ باستخدام التماثل
"""
policies = []
values = []

for sym_index in range(8):
# تحويل المدخلات
aug_board = apply_symmetry(board, sym_index)

# تنبؤ الشبكة العصبية
policy, value = model.predict(aug_board)

# تحويل السياسة إلى نظام الإحداثيات الأصلي
policy_2d = policy.reshape(19, 19)
original_policy = inverse_symmetry_2d(policy_2d, sym_index)
policies.append(original_policy.flatten())

values.append(value)

# متوسط جميع التنبؤات
avg_policy = np.mean(policies, axis=0)
avg_value = np.mean(values)

return avg_policy, avg_value

هذا الأسلوب يمكن أن يحسن قليلاً دقة واستقرار التنبؤ.


ملخص طرق تمثيل اللوحة

الطريقةالاستخدامالتعقيدالميزات
المصفوفة ثنائية الأبعادالتخزين الأساسيمساحة O(361)بسيطة ومباشرة
Zobrist Hashingالتعرف على الأوضاعاستعلام O(1)تجزئة فعالة
Union-Findإدارة السلسلةعملية O(α(n))معالجة الاتصال
مستويات الميزاتمدخلات الشبكة العصبيةO(361×عدد المستويات)ترميز معلومات غنية
تحويلات التماثلزيادة البيانات8× حجم البياناتتوسيع مجاني

هذه التقنيات تعمل معاً لتشكل البنية التحتية لذكاء الغو الاصطناعي الحديث.


توافق الرسوم المتحركة

المفاهيم الأساسية في هذه المقالة وأرقام الرسوم المتحركة المقابلة:

الرقمالمفهومالمقابل الفيزيائي/الرياضي
🎬 A1تمثيل المصفوفة ثنائية الأبعادالمصفوفات، البيانات المتناثرة
🎬 A2Zobrist Hashingدوال التجزئة، عملية XOR
🎬 A8ترميز مستويات الميزاتالموترات، المدخلات متعددة القنوات
🎬 A5معالجة التماثلنظرية الزمر، زمرة ثنائية السطوح

قراءات إضافية


المراجع

  1. Zobrist, A. L. (1970). "A new hashing method with application for game playing." ICCA journal.
  2. Tarjan, R. E. (1975). "Efficiency of a Good But Not Linear Set Union Algorithm." Journal of the ACM, 22(2), 215-225.
  3. Silver, D., et al. (2016). "Mastering the game of Go with deep neural networks and tree search." Nature, 529, 484-489. — 48 مستوى ميزات لـ AlphaGo
  4. Silver, D., et al. (2017). "Mastering the game of Go without human knowledge." Nature, 550, 354-359. — 17 مستوى ميزات لـ AlphaGo Zero