用Python写AI版石头剪刀布:教你用机器学习预测对手出拳(TensorFlow实战)
用Python构建AI驱动的石头剪刀布游戏:从数据收集到模型部署全流程
石头剪刀布这个看似简单的游戏,实际上蕴含着丰富的决策模式和人类行为规律。作为一名长期研究游戏AI的开发者,我发现用机器学习预测玩家出拳模式远比随机选择有趣得多。本文将带你从零开始,构建一个能够学习并预测人类玩家行为的AI对手,而不仅仅是随机出拳的电脑程序。
这个项目非常适合想要入门机器学习应用的Python开发者。你不需要深厚的数学背景,我们将使用TensorFlow的Keras API来简化模型构建过程。整个流程包括数据收集、模型训练、评估优化以及最终集成到游戏逻辑中,完整呈现一个机器学习项目的生命周期。
1. 环境准备与基础游戏搭建
在开始机器学习部分之前,我们需要先建立一个基础的游戏框架。这个框架将作为我们收集训练数据和最终集成AI模型的基础。
import random
from collections import defaultdict
# 游戏选项和胜负规则
GESTURES = ['石头', '剪刀', '布']
WIN_RULES = {
'石头': '剪刀',
'剪刀': '布',
'布': '石头'
}
class RockPaperScissors:
def __init__(self):
self.history = []
self.score = {'player': 0, 'ai': 0}
def play_round(self, player_choice):
# 验证输入
if player_choice not in GESTURES:
raise ValueError("无效的选择")
# AI随机选择(后续将替换为模型预测)
ai_choice = random.choice(GESTURES)
# 记录当前回合
self.history.append({
'player': player_choice,
'ai': ai_choice,
'timestamp': time.time()
})
# 判断胜负
if WIN_RULES[player_choice] == ai_choice:
result = 'win'
self.score['player'] += 1
elif WIN_RULES[ai_choice] == player_choice:
result = 'lose'
self.score['ai'] += 1
else:
result = 'draw'
return ai_choice, result
这个基础实现包含了游戏的核心逻辑,但关键点在于我们添加了历史记录功能,这将为后续的机器学习模型提供训练数据。每次玩家和AI的出拳选择都会被记录下来,包括时间戳(这对分析玩家行为模式很有帮助)。
2. 数据收集与特征工程
要训练一个能够预测玩家行为的模型,我们需要收集足够多的游戏数据。在实际应用中,你可以:
- 让多个测试者玩这个游戏并记录他们的选择
- 收集在线石头剪刀布游戏的数据(如果有API接口)
- 使用已有的公开数据集
import pandas as pd
import numpy as np
def prepare_dataset(history, window_size=3):
"""将游戏历史转换为机器学习友好的格式"""
data = []
targets = []
for i in range(window_size, len(history)):
# 获取滑动窗口内的历史记录
window = history[i-window_size:i]
# 提取特征
features = {
'player_prev_1': window[-1]['player'],
'player_prev_2': window[-2]['player'],
'player_prev_3': window[-3]['player'],
'ai_prev_1': window[-1]['ai'],
'ai_prev_2': window[-2]['ai'],
'ai_prev_3': window[-3]['ai'],
'time_since_last': window[-1]['timestamp'] - window[-2]['timestamp']
}
# 当前回合的玩家选择作为目标
target = history[i]['player']
data.append(features)
targets.append(target)
return pd.DataFrame(data), pd.Series(targets)
这个特征工程函数采用了滑动窗口方法,使用过去3个回合的数据来预测玩家下一回合的选择。我们考虑了:
- 玩家之前的出拳序列
- AI之前的出拳序列
- 玩家做出决策的时间间隔(快速决策可能暗示某种模式)
重要提示:在实际项目中,你应该收集至少1000轮以上的游戏数据才能训练出有效的模型。小样本数据可能导致模型过拟合或表现不佳。
3. 构建预测模型
现在我们来构建一个能够学习玩家行为模式的神经网络模型。我们将使用TensorFlow的Keras API来简化这个过程。
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.utils import to_categorical
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
def build_model(input_shape, num_classes):
"""构建预测玩家选择的神经网络模型"""
model = Sequential([
Dense(64, activation='relu', input_shape=input_shape),
Dropout(0.3),
Dense(32, activation='relu'),
Dropout(0.2),
Dense(num_classes, activation='softmax')
])
model.compile(
optimizer='adam',
loss='categorical_crossentropy',
metrics=['accuracy']
)
return model
# 数据预处理
def preprocess_data(df, target_series):
# 将类别特征转换为数值
encoder = LabelEncoder()
encoded_features = df.apply(encoder.fit_transform)
# 对目标变量进行one-hot编码
encoded_target = encoder.transform(target_series)
one_hot_target = to_categorical(encoded_target)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
encoded_features.values,
one_hot_target,
test_size=0.2,
random_state=42
)
return X_train, X_test, y_train, y_test, encoder
这个模型结构相对简单,但对于石头剪刀布这样的离散选择问题已经足够。关键点在于:
- 使用ReLU激活函数的全连接层
- 添加Dropout层防止过拟合
- 输出层使用softmax激活函数,输出每个选择的概率
- 对类别特征进行标签编码,对目标变量进行one-hot编码
4. 模型训练与评估
有了数据和模型架构,现在我们可以开始训练过程了。
def train_and_evaluate(model, X_train, X_test, y_train, y_test):
"""训练模型并评估性能"""
history = model.fit(
X_train, y_train,
epochs=50,
batch_size=32,
validation_split=0.1,
verbose=1
)
# 评估测试集性能
test_loss, test_acc = model.evaluate(X_test, y_test, verbose=0)
print(f"测试集准确率: {test_acc:.2f}")
return model, history
# 使用示例
# 假设df和targets是从prepare_dataset获得的
X_train, X_test, y_train, y_test, encoder = preprocess_data(df, targets)
model = build_model((X_train.shape[1],), len(encoder.classes_))
trained_model, training_history = train_and_evaluate(model, X_train, X_test, y_train, y_test)
训练过程中,你应该关注以下指标:
- 训练集和验证集的准确率
- 训练集和验证集的损失值
- 模型在测试集上的表现
模型性能分析表:
| 指标 | 期望值 | 说明 |
|---|---|---|
| 训练准确率 | >65% | 低于随机猜测(33%)说明模型有问题 |
| 验证准确率 | 接近训练准确率 | 差距过大可能过拟合 |
| 测试准确率 | >60% | 实际应用中可接受的水平 |
如果模型表现不佳,可以尝试:
- 增加训练数据量
- 调整模型架构(更多层或神经元)
- 调整超参数(学习率、批次大小等)
- 改进特征工程(添加更多有意义的特征)
5. 集成AI到游戏逻辑
现在我们已经有了一个训练好的模型,可以将其集成到原始游戏中,替换掉随机选择的AI。
class AIPlayer:
def __init__(self, model, encoder, window_size=3):
self.model = model
self.encoder = encoder
self.window_size = window_size
self.memory = []
def record_round(self, player_choice, ai_choice):
"""记录当前回合结果"""
self.memory.append({
'player': player_choice,
'ai': ai_choice,
'timestamp': time.time()
})
# 保持内存不超过窗口大小
if len(self.memory) > self.window_size:
self.memory.pop(0)
def predict(self):
"""预测玩家下一回合的选择"""
if len(self.memory) < self.window_size:
return random.choice(GESTURES)
# 准备输入数据
features, _ = prepare_dataset(self.memory, self.window_size)
encoded_features = features.apply(self.encoder.transform)
# 预测概率
proba = self.model.predict(encoded_features.values[-1:])[0]
# 选择最佳应对策略
predicted_index = np.argmax(proba)
predicted_gesture = self.encoder.inverse_transform([predicted_index])[0]
# 根据预测选择能够击败玩家的手势
return WIN_RULES[predicted_gesture]
这个AIPlayer类会:
- 记住最近几轮的游戏历史
- 使用训练好的模型预测玩家下一回合最可能的选择
- 根据预测选择能够击败玩家的手势
将AI集成到主游戏类中:
class RockPaperScissorsAI(RockPaperScissors):
def __init__(self, model, encoder):
super().__init__()
self.ai_player = AIPlayer(model, encoder)
def play_round(self, player_choice):
# 验证输入
if player_choice not in GESTURES:
raise ValueError("无效的选择")
# AI预测并选择
ai_choice = self.ai_player.predict()
# 记录当前回合
self.history.append({
'player': player_choice,
'ai': ai_choice,
'timestamp': time.time()
})
# 更新AI记忆
self.ai_player.record_round(player_choice, ai_choice)
# 判断胜负
if WIN_RULES[player_choice] == ai_choice:
result = 'win'
self.score['player'] += 1
elif WIN_RULES[ai_choice] == player_choice:
result = 'lose'
self.score['ai'] += 1
else:
result = 'draw'
return ai_choice, result
6. 高级优化策略
基础版本的AI已经可以工作,但我们可以进一步优化它的表现:
6.1 混合策略AI
单纯的预测模型可能会被玩家发现模式并利用。我们可以结合多种策略:
class HybridAIPlayer(AIPlayer):
def __init__(self, model, encoder, strategies=None):
super().__init__(model, encoder)
self.strategies = strategies or [
('predictive', 0.7), # 70%时间使用预测模型
('random', 0.2), # 20%时间随机选择
('counter', 0.1) # 10%时间针对玩家最常见选择
]
self.player_pattern = defaultdict(int)
def predict(self):
# 更新玩家模式统计
if self.memory:
last_player_choice = self.memory[-1]['player']
self.player_pattern[last_player_choice] += 1
# 选择策略
strategy = np.random.choice(
[s[0] for s in self.strategies],
p=[s[1] for s in self.strategies]
)
if strategy == 'predictive' and len(self.memory) >= self.window_size:
return super().predict()
elif strategy == 'random':
return random.choice(GESTURES)
else: # counter strategy
if not self.player_pattern:
return random.choice(GESTURES)
most_common = max(self.player_pattern.items(), key=lambda x: x[1])[0]
return WIN_RULES[most_common]
6.2 实时学习
我们可以让模型在游戏过程中继续学习玩家的行为模式:
class OnlineLearningAIPlayer(AIPlayer):
def __init__(self, model, encoder, window_size=3, learning_rate=0.1):
super().__init__(model, encoder, window_size)
self.learning_rate = learning_rate
self.online_model = clone_model(model)
self.online_model.set_weights(model.get_weights())
def update_model(self, player_choice):
if len(self.memory) < self.window_size:
return
# 准备数据
features, _ = prepare_dataset(self.memory, self.window_size)
encoded_features = features.apply(self.encoder.transform)
target = self.encoder.transform([player_choice])
one_hot_target = to_categorical(target, num_classes=len(self.encoder.classes_))
# 小批次在线学习
self.online_model.train_on_batch(
encoded_features.values[-1:],
one_hot_target
)
def predict(self):
if len(self.memory) < self.window_size:
return random.choice(GESTURES)
# 使用在线学习后的模型预测
features, _ = prepare_dataset(self.memory, self.window_size)
encoded_features = features.apply(self.encoder.transform)
proba = self.online_model.predict(encoded_features.values[-1:])[0]
predicted_index = np.argmax(proba)
predicted_gesture = self.encoder.inverse_transform([predicted_index])[0]
return WIN_RULES[predicted_gesture]
6.3 心理模型集成
人类玩家往往会表现出一些心理模式,我们可以将这些模式编码到AI中:
class PsychologicalAIPlayer(AIPlayer):
def __init__(self, model, encoder, window_size=3):
super().__init__(model, encoder, window_size)
self.psychological_factors = {
'win_stay': 0.6, # 赢了后保持同样选择的概率
'lose_shift': 0.75, # 输了后改变选择的概率
'draw_shift': 0.5 # 平局后改变选择的概率
}
def predict(self):
if len(self.memory) < self.window_size:
return random.choice(GESTURES)
# 考虑最近一轮的结果
last_round = self.memory[-1]
last_player_choice = last_round['player']
last_result = (
'win' if WIN_RULES[last_player_choice] == last_round['ai'] else
'lose' if WIN_RULES[last_round['ai']] == last_player_choice else
'draw'
)
# 根据心理模型调整预测
base_prediction = super().predict()
if last_result == 'win' and random.random() < self.psychological_factors['win_stay']:
return WIN_RULES[last_player_choice]
elif last_result == 'lose' and random.random() < self.psychological_factors['lose_shift']:
# 玩家可能会改变选择,预测一个不同的选择
options = [g for g in GESTURES if g != last_player_choice]
return WIN_RULES[random.choice(options)]
elif last_result == 'draw' and random.random() < self.psychological_factors['draw_shift']:
# 玩家可能会改变选择
options = [g for g in GESTURES if g != last_player_choice]
return WIN_RULES[random.choice(options)]
return base_prediction
7. 部署与用户交互
最后,我们需要创建一个用户友好的界面来与AI对战:
def main():
# 加载预训练模型和编码器
model = load_model('rps_predictor.h5')
encoder = joblib.load('encoder.pkl')
# 初始化游戏
game = RockPaperScissorsAI(model, encoder)
print("欢迎来到AI石头剪刀布游戏!")
print("输入'石头'、'剪刀'或'布'开始游戏,输入'退出'结束游戏")
while True:
player_input = input("你的选择是? ").strip()
if player_input == '退出':
print("游戏结束!")
print(f"最终比分 - 玩家: {game.score['player']} AI: {game.score['ai']}")
break
if player_input not in GESTURES:
print("无效输入,请重新尝试")
continue
ai_choice, result = game.play_round(player_input)
print(f"AI选择了: {ai_choice}")
if result == 'win':
print("你赢了这一轮!")
elif result == 'lose':
print("AI赢了这一轮!")
else:
print("平局!")
print(f"当前比分 - 玩家: {game.score['player']} AI: {game.score['ai']}")
print()
if __name__ == "__main__":
main()
这个交互式程序允许玩家:
- 选择石头、剪刀或布
- 查看AI的选择和每轮结果
- 跟踪累计比分
- 随时退出游戏
8. 进一步改进方向
虽然我们的AI已经相当强大,但仍有改进空间:
- 多模态输入:除了选择历史,还可以考虑玩家输入时间、游戏节奏等因素
- 强化学习:使用RL算法让AI自我对弈学习最优策略
- 个性化模型:为每个玩家建立单独的模型档案,适应其独特玩法
- 在线学习:在游戏过程中持续更新模型权重
- 情感分析:结合自然语言处理分析玩家聊天内容(如果有)来调整策略
# 示例:强化学习环境设置
class RPSEnvironment:
def __init__(self):
self.action_space = ['石头', '剪刀', '布']
self.observation_space = ... # 定义状态空间
def reset(self):
"""重置环境状态"""
self.state = ... # 初始状态
return self.state
def step(self, action):
"""执行一个动作并返回新状态、奖励和是否结束"""
# 模拟玩家反应(可以用预训练模型)
player_action = ...
# 计算奖励
reward = self._calculate_reward(action, player_action)
# 更新状态
self.state = ...
done = False # 是否结束一局游戏
return self.state, reward, done
def _calculate_reward(self, ai_action, player_action):
"""根据游戏结果计算奖励"""
if WIN_RULES[ai_action] == player_action:
return 1 # 赢
elif WIN_RULES[player_action] == ai_action:
return -1 # 输
return 0 # 平
在实际项目中,我发现玩家行为模式会随着时间变化。一个实用的技巧是定期重新训练模型,或者在检测到玩家胜率异常时触发模型更新。另一个有趣的发现是,加入少量随机性实际上能让AI显得更"人性化",完全理性的AI反而容易被玩家识破模式。
更多推荐


所有评论(0)