庙算兵棋推演AI开发初探(6-神经网络开发)

碎碎念:

老师让我和同学组队参加10月底截止报名的庙算比赛,我俩走运进了64强,打的过程中发现了一个重要问题------为什么别人总能打我,但是我都看不见!就像玩dota被对面英雄莫名其妙单杀了但是他就一直隐身我都不知道怎么死的。还一个就是,步兵在这里非常厉害,在一个地方趴窝除了火炮集火,就像一个魔免英雄,坦克战车来了只能被步兵干死。

这些都源于一张规则表,但是打起来很离谱------环境和距离视野卡对了,这和不会玩的就是两个游戏!

(碎碎念,这个游戏移动过程中都没法更改目标位置,比星际魔兽操控起来死板,而且停止和冷却的cd太长了,75秒和120秒!什么RTS也不会设这么长的冷却时间......只能说这是一个不好玩的游戏)

------2024.12以上


一、神经网络简介

本质:不断求导找拟合,使得loss收敛,使得acc预测准确率变高

用法:编码映射到标签

结构:层级输入输出、loss函数设计、优化器

二、兵棋神经网络设计

参考alphaStar的星际2的监督学习-强化学习的构建道路,同时他们有强大算力来搞大规模的"联盟对抗"(不断的自己对战自己,利用强化学习然后挑选出更好的智能体)

1.明确实现目标

普通人都能叨叨两句"用AI就能实现","人工智能经过训练就可以实现"......这些都是不负责任的随口乱说,我十分讨厌这种空架子(好像当初我本科毕设就是讨厌空架子写的嗅图狗来帮我识图),我们得明确我们的任务是什么!

任务:经过挑选的样本的训练,得到可以准确预测敌方兵力运动方向的神经网络。(这一步还没到可以进行对战的智能体呢!)

2.明确输出、输入,构建数据集

既然输出是预测方位,那就按六边形构建行为空间

兵棋是六边形的格子,每个格子有6个临边。

定义:0~5,0是正右侧,按逆时针递进,加上原地不同作为6

行为空间是[0,6],即0123456共个行为空间。

于是输出是一个动作向量,长度为7,分别代表7个行动的概率。

有因果关系的输入,我们可以定义各种行为作为输入

比如其他兵力的位置、兵力的血量状态、位置所处的地形......

位置是一个n*m的矩阵,可以作为一个张量输入

血量可以定义为离散的1、2、3、4,作为向量输入

......

最后将这些离散的状态组合到一起,变成一个大张量作为输入

3.数学理论基础

神经网络的数学原理本质上是一个非线性函数逼近器 ,它通过层层线性变换+非线性激活函数来学习数据中的复杂关系。

为什么神经网络能拟合复杂函数?

  • 线性变换+非线性激活 :单独的线性变换无法拟合复杂数据,但非线性激活函数(如 ReLU、Sigmoid)允许网络学到更复杂的映射。

  • 通用逼近定理(Universal Approximation Theorem):只要有足够的隐藏层和神经元,神经网络可以逼近任意连续函数。

完整的神经网络训练过程:

  1. 前向传播:计算神经网络输出(经过各个节点的函数得到最后的输出)

  2. 计算损失:衡量误差(预测值、真实值之间的差距,评估方式不同loss函数也可以不同)

  3. 反向传播:计算梯度( 用链式法则(Chain Rule)传播误差,从输出层向输入层更新权重)

  4. 梯度下降更新权重

  5. 重复迭代,直到收敛

常见的神经网络的不同架构(Architecture):

  • MLP(多层感知机):如果输入足够丰富(如历史轨迹、环境状态),MLP 也能拟合出未来轨迹的函数关系。

  • RNN / LSTM / GRU :这些模型可以记住 过去的信息,从而学会时间序列的依赖关系,对短期轨迹预测效果更好。

  • CNN(卷积神经网络) :如果数据是空间+时间序列(如战场局势随时间变化的热力图),CNN 也可以提取空间特征并用于未来位置预测。

三、编码及实践

1.onehot编码(独热编码)

对于离散的无关的,使用onehot编码。这里采用的都是没有前后关系的离散数据,于是就用0和1来表示一切的独热编码作为编码。

2.将特征转化为编码

.举一个简单的例子,将json中的占领状态定义成离散的四种独热编码(两个点的,所以是4位长度)

python 复制代码
# eg. [1, 0, 1, 0]
def get_city_states(stepData):
    """
    获取夺控点状态信息
    :param stepData: 样本中一帧的数据
    :return:  夺控点信息   2个夺控点共4个值 每两位对应一个夺控点, 如果红色方占领则10, 蓝色方01, 未占领00
    """
    cities = stepData['cities'] # [{'coord': 3636, 'value': 80, 'flag': -1, 'name': '主要夺控点'}, {'coord': 3739, 'value': 50, 'flag': -1, 'name': '次要夺控点'}]
    cities_info = [0] * 4 # [0, 0, 0, 0]
    num = 0
    for loc in cities:
        if loc['flag'] == -1: # 未占领
            cities_info[num:num + 2] = [0, 0]
        elif loc['flag'] == 0: # 红方占领
            cities_info[num:num + 2] = [1, 0]
        else: # 蓝方占领
            cities_info[num:num + 2] = [0, 1]
        num += 2
    # print(cities_info) # eg. [1, 0, 1, 0]
    return cities_info

将1-1800的连续值,变成5位的离散01独热编码。 (这里展示的是利用np.hstack(np.eye(n)函数创建n阶单位矩阵,np.eye(5)[m]是提取出矩阵中的第m行)

python 复制代码
 cur_stage = np.hstack(np.eye(5)[int(cur_step / 400)]) # eg. [0. 1. 0. 0. 0.]

还一种,选取第m行的单位阵的值,这个方法速度上经试验与上述方法一样高效,更规范不容易出错。

python 复制代码
def OneHotcode(self,curdata,list):
   onehot_encoded = [0]*len(list)
   if curdata in list:
      onehot_encoded[list.index(curdata)] = 1
   else:
      onehot_encoded = [-1] * len(list)
   return onehot_encoded

OneHotcode(m, [x for x in range(0, 5)])

3. nn.Module使用

3-1模型部分

在 PyTorch 中,所有的神经网络模型都建议继承自 nn.Module,这是因为 nn.Module 提供了许多构建和管理神经网络的基础功能。

python 复制代码
import torch
import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        # 定义模型的层
        self.fc1 = nn.Linear(10, 20)  # 全连接层
        self.relu = nn.ReLU()        # 激活函数
        self.fc2 = nn.Linear(20, 1)  # 输出层

    def forward(self, x):
        # 定义前向传播逻辑
        x = self.fc1(x)
        x = self.relu(x)
        x = self.fc2(x)
        return x

继承 nn.Module 后,需要在 init 方法中定义模型的层或子模块,并在 forward 方法中定义前向传播逻辑(输入数据x如何穿过这些层训练,又如何把这些层的函数固化让输入穿过后得到期望的输出)。

3-2训练部分

python 复制代码
# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# 模拟训练
for epoch in range(10):
    optimizer.zero_grad()  # 清空梯度
    output = model(input_data)  # 前向传播
    loss = criterion(output, torch.randn(5, 5))  # 计算损失
    loss.backward()  # 反向传播
    optimizer.step()  # 更新参数
    print(f"Epoch {epoch}, Loss: {loss.item()}")

可以通过 model.parameters()model.state_dict() 访问模型的参数。

python 复制代码
# 查看模型的参数
for name, param in model.named_parameters():
    print(name, param.shape)

# 保存模型参数
torch.save(model.state_dict(), "model.pth")

# 加载模型参数
model.load_state_dict(torch.load("model.pth"))

3-3调用部分

创建模型实例后,可以像调用函数一样调用模型,输入数据会自动经过 forward 方法。

python 复制代码
model = MyModel()
input_data = torch.randn(5, 10)  # 假设输入是一个 (5, 10) 的张量
output = model(input_data)       # 前向传播
print(output)

4.我暂时的神经网络(仅预测兵力的移动方位)

这部分目前的实现,我考虑多层的决策处理,简单的方位预测我觉得还不够实用。

以后的通用RTS游戏智能体的自定义搭建我要更灵活通用。

python 复制代码
# 通用兵力策略网络(坦克、战车、步兵)------输出是位置预测
class troopsNN(nn.Module): 
    """
    输入:游戏状态、兵种特征、空间特征、位置嵌入
    """
     
    def __init__(self):
        """
        game_stats: torch.Size([batch_size, 10, 12]) 
        bop_features: torch.Size([batch_size, 10, 12, 44])
        spatial_features:torch.Size([batch_size, 10, 14, 92, 77]) -> 送入cnn的形状应该是(b, n, h, w)
        bop_embeddings:torch.Size([batch_size, 10, 6, 92, 77])
        """
        super(troopsNN, self).__init__()
        self.mlp_fg = torch.nn.Linear(12, 6)
        self.mlp_fb = torch.nn.Linear(44, 25)
        self.cnn_fs = nn.ModuleList() 
        self.cnn_fs.append(self.conv_layer([17, 24], kernel=3))
        self.cnn_fs.append(self.conv_layer([24, 48], kernel=3))
        self.down_sampling = nn.MaxPool2d(kernel_size=2, stride=2)
        self.mlp_move_direction = self.mlp_layer(7)     # TODO: 这里用mask掩码机制,使得不同兵种的动作空间不同

    def conv_layer(self, channel, kernel):
        conv_block = nn.Sequential(
            nn.Conv2d(in_channels=channel[0], out_channels=channel[1], kernel_size=kernel, stride=2, padding=1),
            nn.BatchNorm2d(num_features=channel[1]),
            nn.ReLU(inplace=True),
        )
        return conv_block

    def mlp_layer(self, out_dim):
        mlp_block = nn.Sequential(
            nn.Linear(5430, 2048), # notemporal 5430; temporal 54300
            nn.ReLU(inplace=True),
            nn.Linear(2048, out_dim),
        ) 
        return mlp_block

    def forward(self, game_stats, bop_features, spatial_features, bop_embeddings):
        # 处理特征
        game_stats = game_stats.permute(1, 0, 2) # 1, b, 12
        bop_features = bop_features.permute(1, 2, 0, 3) # 1, 12, b, 44
        spatial_features = spatial_features.permute(1, 0, 2, 3, 4) # 1, b, 23, 92, 77
        bop_embeddings = bop_embeddings.permute(1, 0, 2, 3, 4) # 1, b, 3, 92, 77
        # final_feature_bop_frame = torch.zeros((6, 10, game_stats.shape[1], 2048), device=device) # (bop, frame, batch_size, length of final feature). too big?
        final_feature_frame = ([0] * 1)
        for i in range(1):
            final_feature_frame[i] = [0] * game_stats.shape[1]
        for i in range(1):
            for j in range(game_stats.shape[1]):
                final_feature_frame[i][j] = [0] * 2048
        # print(np.shape(final_feature_frame)) # (1, batch_size, 2048)

        for i in range(1):
            f_game = self.mlp_fg(game_stats[i]) # input (b, 12), output (b, 6)
            for j in range(6): # bops' features (our_info + enemy_info)
                fb_j_total = self.mlp_fb(bop_features[i][j]) # in (b, 44), out(b, 25)
                fb_j = fb_j_total[:, :-1] # (b, 24)
                if j == 0:
                    f_bops = fb_j
                else:
                    f_bops = torch.cat((f_bops, fb_j), 1) # final f_bops: (b, 24*3=72)
            # basic网络中不需要区分算子的位置嵌入,直接把bop_embeddings与空间特征做concat。不再需要从共享特征池做att。
            f_spatial = torch.cat((spatial_features[i], bop_embeddings[i]), 1) # (b, 14+3, 92, 77)
            for u in range(len(self.cnn_fs)):
                f_spatial = self.cnn_fs[u](f_spatial)
            f_spatial = self.down_sampling(f_spatial)
            f_spatial = f_spatial.view(f_spatial.shape[0], -1) # 拉平成(b, )
            final_feature_frame[i] = torch.cat((f_game, f_bops, f_spatial), 1) # (b, )
            # print(final_feature_frame[i].shape) # torch.Size([8, 5430])

        pred_move_dir = self.mlp_move_direction(final_feature_frame[0])
        return pred_move_dir

------就像高中时悟到的那样,自己的笔记是一定要记的,就算别人的笔记再详细,也不是你所经历过的路,也有很多缺失的或多余的,所以不能依赖所谓的学霸笔记,要自己有自己的笔记,自己才能随时取用自己需要的知识。

相关推荐
wei_shuo12 分钟前
DeepSeek-R1 模型现已在亚马逊云科技上推出
人工智能·amazon
枉费红笺18 分钟前
目标检测竞赛训练策略解析与拓展
人工智能·目标检测·计算机视觉
小白的高手之路32 分钟前
常用的卷积神经网络及Pytorch示例实现
人工智能·pytorch·python·深度学习·神经网络·cnn
IT古董39 分钟前
【漫话机器学习系列】173.模型可识别度(Model Identifiability)
人工智能·机器学习
Julian.zhou43 分钟前
MCP服务:五分钟实现微服务治理革命,无缝整合Nacos/Zookeeper/OpenResty
人工智能·微服务·zookeeper·交互·openresty
weixin_4094110243 分钟前
支持 MCP 协议的开源 AI Agent 项目
人工智能·microsoft
jndingxin1 小时前
OpenCV 图形API(6)将一个矩阵(或图像)与一个标量值相加的函数addC()
人工智能·opencv
神经星星1 小时前
在线教程丨YOLO系列重要创新!清华团队发布YOLOE,直击开放场景物体实时检测与分割
人工智能·深度学习·机器学习
李帅14969824191671 小时前
🌟《从一到二:基于Trae的多智能体合作Prompt自动生成迭代指南》
人工智能
全栈开发圈1 小时前
新书速览|深入理解DSP:基于TMS320F28379D的开发与实践
人工智能·信号处理·dsp开发