重啟撲克機器人之路 -10:當數據處理變得不再可怕

更新於 發佈於 閱讀時間約 28 分鐘
raw-image

今天開始著手處理對手模型訓練所需的數據。說實話,數據處理一直是我比較抗拒的部分 - 總覺得又繁瑣又無趣。但經過這幾年的經驗,我越來越清楚意識到:在機器學習中,數據的品質往往決定了成敗。就算你用了最先進的模型架構,餵進去的數據品質不佳,最終結果還是不會理想。

有趣的是,當我開始處理iPoker的XML格式牌局記錄時,發現事情並沒有想像中困難。回想幾年前第一次看到這些複雜的牌局記錄時,光是那些看不懂的程式碼就讓我望而卻步。但現在有了這幾年寫程式的經驗,再加上語言模型的協助,理解這些XML結構反而變得輕鬆許多。這種進步讓我感到欣慰 - 至少證明這幾年的摸索沒有白費。

目前的重點是要從這些牌局記錄中提取出有意義的數據,建立一個能預測玩家行為傾向的回歸模型。具體來說,就是分析在特定場景下(比如特定的牌面、底池大小、玩家位置等),玩家選擇raise、call或fold的機率分布。這讓我想到需要仔細梳理每個決策點的場景資訊,確保模型能"看到"玩家當下可見的所有資訊。

snapshot = { "round_no": round_no, "current_street": current_street, "blinds": blinds_info, "player_positions": positions, "player_stacks": stacks, "pot_size": pot_size, "board_cards": board_cards, "previous_actions": actions_history, "players_remaining": active_players, "is_button": is_button }

處理showdown資訊可能需要另外考慮 - 我還在思考是否該將其整合進同一個模型,還是分開處理會比較合適。這部分可能需要再多做一些研究和實驗。

經過今天的工作,我發現自己對數據處理的態度似乎有了微妙的改變。雖然還是不能說特別喜歡這個過程,但至少不再覺得那麼困難和可怕了。或許這就是所謂的成長吧 - 當你逐漸掌握了工具和方法,曾經令人生畏的事物也會變得平常。

這是我用來處理iPoker Hand History的程式碼:

import xml.etree.ElementTree as ET

import json

import re



def safe_float(text):

    """Convert a string to float after removing commas."""

    if text is None:

        return 0.0

    return float(re.sub(r",", "", text.strip()))



def parse_decision_logs(root, hero):

    """

    Parse the XML tree and, for every opponent decision (action) in rounds 1 and later

    (preflop and beyond), produce a snapshot log capturing the game state at the moment

    BEFORE the action is processed.

   

    In this snapshot:

      - The snapshot does NOT include any actions from round 0 (blinds/antes).

      - The re-indexed action counter is incremented for every action in rounds ≥ 1,

        so that no action gets a number of 0.

      - Only opponent actions are logged (the hero's actions are skipped).

      - The snapshot shows the acting opponent's hole cards (if available); the hero's

        or other players' hole cards are not revealed in the snapshot.

    """

    logs = []



    for game in root.findall('game'):

        general = game.find('general')

        if general is None:

            continue



        # --- Extract blinds and ante ---

        small_blind_elem = general.find('smallblind')

        big_blind_elem = general.find('bigblind')

        ante_elem = general.find('ante')

        small_blind = safe_float(small_blind_elem.text) if small_blind_elem is not None else 0.0

        big_blind = safe_float(big_blind_elem.text) if big_blind_elem is not None else 0.0

        ante = safe_float(ante_elem.text) if ante_elem is not None else 0.0



        # --- Process players ---

        players = {}        # key: player name, value: player info

        active_players = {} # tracks whether a player is still in the hand

        players_elem = general.find('players')

        if players_elem is None:

            continue



        for player_elem in players_elem.findall('player'):

            name = player_elem.attrib.get('name')

            seat = int(player_elem.attrib.get('seat'))

            is_dealer = (player_elem.attrib.get('dealer') == "1")

            chips = safe_float(player_elem.attrib.get('chips'))

            bet = safe_float(player_elem.attrib.get('bet'))

            win = safe_float(player_elem.attrib.get('win'))

            players[name] = {

                'seat': seat,

                'is_dealer': is_dealer,

                'chips': chips,

                'bet': bet,

                'win': win

            }

            active_players[name] = True



        # --- Determine relative positions ---

        # Sort players by seat number.

        sorted_players = sorted(players.items(), key=lambda item: item[1]['seat'])

        # Find the dealer (button). If none is marked, assume the first player is the dealer.

        dealer_index = None

        for i, (name, info) in enumerate(sorted_players):

            if info['is_dealer']:

                dealer_index = i

                break

        if dealer_index is None:

            dealer_index = 0



        button_player = sorted_players[dealer_index][0]

        small_blind_player = sorted_players[(dealer_index + 1) % len(sorted_players)][0]

        big_blind_player = sorted_players[(dealer_index + 2) % len(sorted_players)][0]



        # Assign relative position codes: small blind = 0, big blind = 1, button = 2, others = 3.

        player_positions = {}

        for name in players:

            if name == small_blind_player:

                players[name]['position_relative'] = 0

            elif name == big_blind_player:

                players[name]['position_relative'] = 1

            elif name == button_player:

                players[name]['position_relative'] = 2

            else:

                players[name]['position_relative'] = 3

            player_positions[name] = players[name]['position_relative']



        # Skip this game if the hero is not present.

        if hero not in players:

            continue



        # --- Initialize state variables ---

        pot_size = 0.0

        cumulative_actions = []  # will contain actions from rounds >= 1 only

        board_cards = []         # community cards seen so far

        pocket_cards = {}        # updated when encountering <cards type="Pocket">

        snapshot_action_counter = 0  # counter for actions in rounds >= 1



        # --- Process rounds in order (sorted by round number) ---

        rounds = game.findall('round')

        rounds = sorted(rounds, key=lambda r: int(r.attrib.get('no')))

        for r in rounds:

            round_no = int(r.attrib.get('no'))

            for elem in r:

                if elem.tag == 'cards':

                    card_type = elem.attrib.get('type')

                    if card_type == 'Pocket':

                        # Record pocket cards for the given player.

                        player = elem.attrib.get('player')

                        text = elem.text.strip() if elem.text else ""

                        cards = text.split()

                        # If cards are hidden (e.g., "X X"), record as unknown.

                        if any(card.upper().startswith("X") for card in cards):

                            pocket_cards[player] = ["unknown", "unknown"]

                        else:

                            pocket_cards[player] = cards

                    else:

                        # Community cards (flop, turn, river)

                        cards = elem.text.split() if elem.text else []

                        board_cards.extend(cards)

                elif elem.tag == 'action':

                    # Build an action dictionary.

                    action_details = {

                        'round': round_no,

                        'player': elem.attrib.get('player'),

                        'action_type': int(elem.attrib.get('type')),

                        'action_sum': safe_float(elem.attrib.get('sum'))

                    }



                    # For actions in round 0 (blinds/antes), update state only (do not record them).

                    if round_no < 1:

                        pot_size += action_details['action_sum']

                        # (If desired, you could update pocket_cards from round 0 as well.)

                        continue



                    # For actions in rounds >= 1, first assign a new re-indexed action number.

                    snapshot_action_counter += 1

                    # Copy the action_details and add the new action number.

                    action_details['action_no'] = snapshot_action_counter



                    # If the acting player is not the hero, produce a snapshot BEFORE processing the action.

                    # (The snapshot’s "previous_actions" will be the cumulative_actions so far.)

                    if action_details['player'] != hero:

                        is_button = (action_details['player'] == button_player)

                        actor_cards = pocket_cards.get(action_details['player'], ["unknown", "unknown"])

                        # Build the snapshot.

                        snapshot = {

                            "gamecode": game.attrib.get("gamecode"),

                            "round_no": round_no,

                            "current_street": (

                                "preflop" if round_no == 1 else

                                "flop" if round_no == 2 else

                                "turn" if round_no == 3 else

                                "river" if round_no == 4 else "unknown"

                            ),

                            "blinds": {

                                "small_blind": small_blind,

                                "big_blind": big_blind,

                                "ante": ante

                            },

                            "player_positions": player_positions,

                            "player_stacks": {name: players[name]['chips'] for name in players},

                            "pot_size": pot_size,  # state BEFORE processing this action

                            "board_cards": board_cards.copy(),

                            "previous_actions": cumulative_actions.copy(),  # only round>=1 actions so far

                            "action": action_details.copy(),

                            "players_remaining": sum(1 for active in active_players.values() if active),

                            "is_button": is_button,

                            "actor_hole_cards": pocket_cards.get(action_details['player'], ["unknown", "unknown"])

                        }

                        logs.append(snapshot)



                    # Now update the state: add this action to the cumulative history.

                    cumulative_actions.append(action_details)

                    pot_size += action_details['action_sum']

                    # Mark the actor as inactive if the action is a fold (we assume type==0 means fold).

                    if action_details['action_type'] == 0:

                        active_players[action_details['player']] = False



    return logs



if __name__ == '__main__':

    try:

        # Parse the XML file (adjust 'test.xml' to your filename)

        tree = ET.parse('test.xml')

        root = tree.getroot()



        # Determine the hero's nickname from the session-level <nickname> element.

        session_general = root.find('general')

        if session_general is not None and session_general.find('nickname') is not None:

            hero = session_general.find('nickname').text.strip()

        else:

            hero = ""  # fallback if not found



        # Parse opponent decision snapshots (skipping hero actions and round 0 actions)

        logs = parse_decision_logs(root, hero)

        print(json.dumps(logs, indent=4))

    except ET.ParseError as e:

        print("Error parsing XML file:", e)

avatar-img
1會員
11內容數
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
傑劉的沙龍 的其他內容
記錄了在分析200萬筆撲克歷史記錄時的思考過程,從最初被 Q-Learning 吸引,到理解其在不完整資訊遊戲中的侷限,最終決定轉向建立對手模型系統的過程。反映了在技術選擇時,如何在炫酷與實用之間找到平衡。
記錄了在開發撲克機器人時,從對機器學習模型的成功驗證,到意識到自己又回到solver策略老路的過程。最終決定改變方向,轉向分析實戰數據的心路歷程。
記錄了放棄使用大型語言模型作為撲克機器人核心的決定過程,以及新的混合策略方案的構思。文章探討了技術選擇的考量因素,並回顧了過去開發經驗帶來的啟發。
記錄了在開發撲克牌AI時,從機器學習到大型語言模型的技術選擇過程,以及對各種可能解決方案的思考與權衡。
記錄了在開發撲克牌辨識系統時遇到的按鈕辨識挑戰,以及從中學到的debug思維和版本控制重要性。文章分享了技術解決方案的演進過程,也反思了個人開發習慣需要改進的地方。
探討了在開發過程中過度依賴AI解決方案的問題,以及如何在專案開發和技術學習之間取得平衡。通過一個簡單的OCR問題,體會到有時最基本的方法反而是最有效的解決方案。
記錄了在分析200萬筆撲克歷史記錄時的思考過程,從最初被 Q-Learning 吸引,到理解其在不完整資訊遊戲中的侷限,最終決定轉向建立對手模型系統的過程。反映了在技術選擇時,如何在炫酷與實用之間找到平衡。
記錄了在開發撲克機器人時,從對機器學習模型的成功驗證,到意識到自己又回到solver策略老路的過程。最終決定改變方向,轉向分析實戰數據的心路歷程。
記錄了放棄使用大型語言模型作為撲克機器人核心的決定過程,以及新的混合策略方案的構思。文章探討了技術選擇的考量因素,並回顧了過去開發經驗帶來的啟發。
記錄了在開發撲克牌AI時,從機器學習到大型語言模型的技術選擇過程,以及對各種可能解決方案的思考與權衡。
記錄了在開發撲克牌辨識系統時遇到的按鈕辨識挑戰,以及從中學到的debug思維和版本控制重要性。文章分享了技術解決方案的演進過程,也反思了個人開發習慣需要改進的地方。
探討了在開發過程中過度依賴AI解決方案的問題,以及如何在專案開發和技術學習之間取得平衡。通過一個簡單的OCR問題,體會到有時最基本的方法反而是最有效的解決方案。
你可能也想看
Google News 追蹤
Thumbnail
/ 大家現在出門買東西還會帶錢包嗎 鴨鴨發現自己好像快一個禮拜沒帶錢包出門 還是可以天天買滿買好回家(? 因此為了記錄手機消費跟各種紅利優惠 鴨鴨都會特別注意銀行的App好不好用! 像是介面設計就是會很在意的地方 很多銀行通常會為了要滿足不同客群 會推出很多App讓使用者下載 每次
Thumbnail
體驗具有卓越性能的終極多功能性。讓您可以隨時隨地進行工作的先進精簡滑鼠。得益於 8K DPI 任意表面追踪和安靜的點按,現在精確度和響應能力都更上一層樓。
Thumbnail
記者:「上次採訪的萬物溝通軟體,在網路引爆熱潮,不過大部都是拿來當笑話!害我被老板給訓了一頓,還要我再找時間來採訪博士,然後一再叮嚀千萬不能再採訪超級電腦一號了,就怕碰見上次的「程式故障」!」 摩爾博士:「上次的確是失禮了!本想開個玩笑卻惹起這麼大的風波,還有人打電話到我們研究機構,說我們有沒有通
Thumbnail
  靜靜地坐在桌前,腦海中如潮水般不斷回想著 Open AI 發佈會的點點滴滴。那一場發佈會,宛如一道淩厲的閃電,猛然間照亮了未來那原本被重重迷霧所籠罩的道路,卻也在我的心中肆意地湧起了無盡的不確定與惶恐。   看著那些關於 AI 發展的詳細介紹,我仿佛看到了一個正以驚人速度變化著的世界在
Thumbnail
本文章探討了多智能體系統(MAS)在生成式AI領域中的應用,以及GenAI對於AI_MCU和Software defined hardware的影響。文章還總結了SDH設計模式對數據科學和人工智能時代的影響,並提供了有關GenAI的一些額外信息。
Thumbnail
*從Embedded World看到,AI在工業領域的發展,會比原本預期再慢一點。 *目前在消費端、服務端,例如顧問業者、客服、buy now pay later等業務,有很多AI功能、LLM模型導入。 --初階的碼農容易被AI取代。 *工業端,最早是PLC編程,到IPC,未來在IPC裡面 會
別小看語言模型,我們的歷史記載,不是都靠著文本嗎?
Thumbnail
本文討論了人類在面對變化時的應對策略,包括數據分析的重要性,科技趨勢對工作形態的影響,以及對無條件基本收入的討論。透過工程師職涯教練Yi姐豐富的經驗,分享如何運用數據進行更好的決策,探討寫作和自媒體創業的投資報酬率,以及對未來的靈活規劃。
Thumbnail
我們寫作業時,只能一個字一個字地寫,但是量子電腦卻可以同時做很多件事情,就像一位有很多隻手的魔法師一樣呢⋯⋯快來跟♥AI小可愛小艾♥一起探索世界的每一個角落,一起學習有趣又有用的新科普!
Thumbnail
★看似客觀中立的機器運算,可能在學習人類提供的資料後,再複製社會偏見與歧視,形成「自動不平等」!
Thumbnail
/ 大家現在出門買東西還會帶錢包嗎 鴨鴨發現自己好像快一個禮拜沒帶錢包出門 還是可以天天買滿買好回家(? 因此為了記錄手機消費跟各種紅利優惠 鴨鴨都會特別注意銀行的App好不好用! 像是介面設計就是會很在意的地方 很多銀行通常會為了要滿足不同客群 會推出很多App讓使用者下載 每次
Thumbnail
體驗具有卓越性能的終極多功能性。讓您可以隨時隨地進行工作的先進精簡滑鼠。得益於 8K DPI 任意表面追踪和安靜的點按,現在精確度和響應能力都更上一層樓。
Thumbnail
記者:「上次採訪的萬物溝通軟體,在網路引爆熱潮,不過大部都是拿來當笑話!害我被老板給訓了一頓,還要我再找時間來採訪博士,然後一再叮嚀千萬不能再採訪超級電腦一號了,就怕碰見上次的「程式故障」!」 摩爾博士:「上次的確是失禮了!本想開個玩笑卻惹起這麼大的風波,還有人打電話到我們研究機構,說我們有沒有通
Thumbnail
  靜靜地坐在桌前,腦海中如潮水般不斷回想著 Open AI 發佈會的點點滴滴。那一場發佈會,宛如一道淩厲的閃電,猛然間照亮了未來那原本被重重迷霧所籠罩的道路,卻也在我的心中肆意地湧起了無盡的不確定與惶恐。   看著那些關於 AI 發展的詳細介紹,我仿佛看到了一個正以驚人速度變化著的世界在
Thumbnail
本文章探討了多智能體系統(MAS)在生成式AI領域中的應用,以及GenAI對於AI_MCU和Software defined hardware的影響。文章還總結了SDH設計模式對數據科學和人工智能時代的影響,並提供了有關GenAI的一些額外信息。
Thumbnail
*從Embedded World看到,AI在工業領域的發展,會比原本預期再慢一點。 *目前在消費端、服務端,例如顧問業者、客服、buy now pay later等業務,有很多AI功能、LLM模型導入。 --初階的碼農容易被AI取代。 *工業端,最早是PLC編程,到IPC,未來在IPC裡面 會
別小看語言模型,我們的歷史記載,不是都靠著文本嗎?
Thumbnail
本文討論了人類在面對變化時的應對策略,包括數據分析的重要性,科技趨勢對工作形態的影響,以及對無條件基本收入的討論。透過工程師職涯教練Yi姐豐富的經驗,分享如何運用數據進行更好的決策,探討寫作和自媒體創業的投資報酬率,以及對未來的靈活規劃。
Thumbnail
我們寫作業時,只能一個字一個字地寫,但是量子電腦卻可以同時做很多件事情,就像一位有很多隻手的魔法師一樣呢⋯⋯快來跟♥AI小可愛小艾♥一起探索世界的每一個角落,一起學習有趣又有用的新科普!
Thumbnail
★看似客觀中立的機器運算,可能在學習人類提供的資料後,再複製社會偏見與歧視,形成「自動不平等」!