2688 字
7 分钟
《科研综合训练实践教程》阅读汇报
2026-10-08

本篇按教材章节整理工具配置、操作截图和算法示例。安装部分保留必要步骤,算法部分结合无人机巡检说明;截图中的结果只对应当时的环境或教学任务。

一、参考文献管理软件 Zotero#

1.1 安装与操作截图#

安装桌面端和浏览器 Connector,再确认 Word 中能够正常插入引用。教材的重点操作包括导入、标签、笔记、同步和群组文献库。

Zotero 文献列表,展示文献标题、作者、期刊、影响因子及分区等信息

图 1:Zotero 文献列表。标题、作者、条目类型等字段用于整理题录;图中的影响因子和期刊分区可辅助筛选,但不能代替对论文方法和内容的判断。导入后还应检查题录是否识别正确。

Zotero 的 llm-for-zotero 插件设置页,展示 AI 服务商、连接方式和模型配置

图 2:llm-for-zotero 插件设置页。这里配置服务商、连接方式与模型;界面配置和文献题录管理是不同环节,阅读辅助产生的内容仍需与论文原文核对。

同步前要区分题录数据和附件文件;本地迁移前保留备份。工具能自动生成引文格式,但不能代替人工核对元数据。

二、Visio 绘图#

2.1 界面与基本操作#

打开 Visio 后,可以从左侧形状库拖入图形,在画布中编辑,再通过工具栏设置文字、连接线、对齐和样式。

Visio 工作界面,左侧为基本形状库,画布中选中了一个矩形

图 3:Visio 的基本绘图界面。矩形周围的控制点用于调整大小,上方的旋转控件用于改变方向。这张图展示的是单个形状的编辑状态,后续还需添加文字和连接线才能形成完整流程图。

三、Origin 绘图#

3.1 使用 Codex 辅助 Origin 绘图#

下面保留双 Y 轴绘图示例,重点观察坐标轴、曲线颜色和图例的对应关系。

双 Y 轴点线图,黑色曲线对应左侧 POR 轴,红色曲线对应右侧 RISK 轴

图 4:双 Y 轴点线图。横轴为 ε,黑色曲线读取左侧 POR(%)轴,红色曲线读取右侧 RISK(%)轴;不同标记对应图例中的 φ 设置。解释曲线前,应先说明 ε、φ 和两个指标在原实验中的定义,不能仅凭图形推断其业务含义。

四、程序代码规范#

4.1 规范对复现实验的作用#

本章强调命名、导入、函数拆分、注释和程序入口。对科研代码来说,我最看重“几个月后还能知道当时跑了什么”:变量名要带上物理含义,参数要集中管理,日志要记录种子和依赖版本。

例如 battery_limit_wh 比 q 更容易理解;route_distance() 应只负责计算距离,不同时画图和训练模型。注释可以解释“为什么终止状态不再估计未来回报”,不必反复解释赋值和循环语法。

教材部分排版规则带有项目约定。实际编程时应遵循所在项目的规范,统一缩进,避免混用 Tab 与空格。

4.2 算法项目建议结构#

下面是便于学习的建议结构,不是已经收到的教材源码目录:

inspection_demo/
├── environment.py # 地图、动作规则、奖励与任务结束条件
├── ga_route.py # 染色体、交叉变异、路线评价
├── qlearning.py # Q 表、动作选择、价值更新
├── train.py # 组织训练并保存结果
├── evaluate.py # 使用固定测试任务评估
└── results/ # 实验记录、曲线和路线图

读源码时先找入口,再跟着一条数据流读:输入在哪里产生,传给哪个函数,返回值代表什么,最后如何进入结果文件。比从第一行开始逐字读更容易把握整体。

五、数学规划求解引擎 Gurobi#

5.1 教材案例与巡检任务的联系#

教材正文第 143–152 页给出 0-1 整数规划、CVRP 和 TSP。虽然第一个例子位于“LP”小节,它的变量只能取 0 或 1,因此实际是整数规划。

TSP 可以对应单架无人机从机巢出发,访问所有杆塔并返回;CVRP 则加入多机与容量限制。需要注意,车辆的载重容量不能直接等同于无人机电量:飞行段能耗和点位作业能耗都应计入预算。

5.2 环境创建与安装截图#

安装 Python 接口并配置所需许可证后,先求解一个小模型。检查标准是正常返回求解状态和目标值,而不只是 import gurobipy 没有报错。

先创建独立环境,再激活环境并安装 Gurobi。

使用 Conda 创建名为 gurobi 的 Python 3.11 环境

图 5:创建 Gurobi 专用环境。截图显示软件包元数据收集与依赖求解过程;这些日志说明安装流程已启动,尚不能单独证明环境创建和许可证配置均已完成。

激活 gurobi 环境,并通过 gurobi 渠道安装软件包

图 6:激活环境并执行安装命令。提示符中的 (gurobi) 表明当前已进入该环境;截图记录了安装命令,不是许可证验证或模型求解结果。

六、遗传算法与工具箱 Geatpy#

6.1 我对遗传算法的理解#

遗传算法同时维护一批方案,每轮评价它们,再通过选择、交叉和变异生成新方案。放到巡检任务中,一个个体可以是一条杆塔访问顺序,整个种群就是多条候选航线。

它适合难以求导、包含离散顺序或需要仿真评价的问题。代价是评价次数多,结果有随机性;找到一条很短的航线并不等于已经证明它是最短航线。

6.2 安装与环境检查#

教材使用 Geatpy 2.7.0,并讨论 Python 与 NumPy 的兼容问题。复现时应按该版本的依赖要求建立单独环境,记录实际可用的组合;不把教材中的版本要求当成所有后续版本的通用结论。

创建并激活环境:

conda create -n geatpy python=3.10 pip -y
conda activate geatpy

使用 Conda 创建名为 geatpy 的 Python 3.10 环境

图 7:创建 Geatpy 专用环境。环境名与其他算法环境分开,便于保留这组依赖配置并复现实验。

安装固定版本的依赖和 Geatpy:

python -m pip install "numpy==1.23.5" "matplotlib==3.7.5"
python -m pip install "geatpy==2.7.0"

安装后检查实际版本:

Geatpy 环境版本检查结果:Python 3.10.22、NumPy 1.23.5、Geatpy 2.7.0

图 8:版本检查结果。该截图记录的组合为 Python 3.10.22、NumPy 1.23.5 和 Geatpy 2.7.0,可用于说明当时的运行环境;不代表其他版本组合一定不可用。

6.3 用 Geatpy 求解巡检参数问题#

示例优化巡航速度与作业距离,综合考虑能耗、任务时间和成像距离惩罚,并限制任务能耗不超过 50 Wh、作业距离不低于 6 m。功率公式和权重均为教学假设。

import geatpy as ea
import numpy as np
MISSION_DISTANCE = 6_000.0
INSPECTION_POINTS = 12
BATTERY_LIMIT = 50.0
def mission_metrics(variables: np.ndarray):
"""计算 Geatpy 传入的一批候选方案的任务指标。"""
# variables 每一行依次是 [速度(m/s), 作业距离(m)]。
speed = variables[:, 0]
stand_off = variables[:, 1]
mission_time = MISSION_DISTANCE / speed + INSPECTION_POINTS * 20
power = 150 + 2.2 * (speed - 7) ** 2 + 0.8 * (stand_off - 8) ** 2
energy = power * mission_time / 3_600
image_penalty = 1.5 * (stand_off - 8) ** 2
return energy, mission_time, image_penalty
class DroneInspectionProblem(ea.Problem):
"""把无人机参数优化问题封装成 Geatpy 可识别的 Problem。"""
def __init__(self) -> None:
# ea.Problem 保存目标数量、变量类型、上下界及端点是否可取。
super().__init__(
name="DroneInspection",
M=1, # 目标函数数量
maxormins=[1], # 1 表示最小化,-1 表示最大化
Dim=2, # 决策变量数量
varTypes=[0, 0], # 0 为连续变量,1 为离散变量
lb=[4.0, 5.0], # 速度、作业距离的下界
ub=[12.0, 15.0],
lbin=[1, 1], # 包含下界
ubin=[1, 1], # 包含上界
)
def evalVars(self, variables: np.ndarray):
"""同时评价整个种群,并返回目标矩阵 ObjV 与约束矩阵 CV。"""
energy, mission_time, image_penalty = mission_metrics(variables)
objective = energy + 0.015 * mission_time + image_penalty
# Geatpy 约定 CV 的每一列对应一条约束,CV <= 0 表示满足约束。
# 例如 energy - 50 <= 0 等价于 energy <= 50。
constraint_violation = np.column_stack(
[
energy - BATTERY_LIMIT, # 能耗不得超过 50 Wh
6 - variables[:, 1], # 作业距离不得小于 6 m
]
)
return objective.reshape(-1, 1), constraint_violation
problem = DroneInspectionProblem()
# RI 表示实数/整数混合编码;本例两个变量的 varTypes 都为 0,因此均按实数处理。
population = ea.Population(Encoding="RI", NIND=80)
# SEGA 是增强精英保留遗传算法模板,适合单目标优化入门。
algorithm = ea.soea_SEGA_templet(problem, population)
algorithm.MAXGEN = 150 # 最大进化代数
algorithm.logTras = 10 # 每 10 代记录一次日志
algorithm.trappedValue = 1e-8 # 小于该改进量视为目标没有明显改善
algorithm.maxTrappedCount = 20 # 连续 20 次无明显改善时允许提前停止
# seed 用于复现实验;关闭绘图和落盘,方便在脚本或终端中直接运行。
result = ea.optimize(
algorithm,
seed=42,
verbose=True,
drawing=0,
outputMsg=True,
drawLog=False,
saveFlag=False,
)
# 先判断是否找到可行结果,再读取变量,避免空结果直接索引。
if result["Vars"] is None or len(result["Vars"]) == 0:
raise RuntimeError("本次没有找到可行巡检方案,请检查约束和搜索预算")
best = result["Vars"][0]
energy, mission_time, _ = mission_metrics(best.reshape(1, -1))
print(f"speed: {best[0]:.2f} m/s")
print(f"stand-off distance: {best[1]:.2f} m")
print(f"energy: {energy[0]:.2f} Wh")
print(f"mission time: {mission_time[0]:.1f} s")
print(f"objective: {result['ObjV'][0, 0]:.2f}")

运行结果与读图说明#

Geatpy 巡检参数优化日志及输出,显示速度10.52米每秒、作业距离8.00米和能耗39.90瓦时

图 9:巡检参数优化的运行记录。截图显示 3,120 次目标评价,最终综合目标约为 52.06;对应速度约为 10.52 m/s、作业距离约为 8.00 m、预计能耗为 39.90 Wh、任务时间为 810.5 s。

日志中的 gen 是代数,eval 是累计目标评价次数,f_opt 是记录的最好目标,f_avg 和 f_std 分别反映种群目标值的平均水平与分散程度。截图中各个体目标逐渐接近,说明这次搜索的种群趋于集中,但不等于证明了全局最优。

输出能耗低于 50 Wh,距离大于 6 m,符合示例约束。这个结论仅针对教学代理模型,不能据此判断真实无人机的续航或安全作业距离。

七、GPU 编程环境#

7.1 先确认任务是否需要 GPU#

教材介绍 Conda、PyTorch、CUDA 检查和设备选择。小型 TSP、手写 GA 和 Q 表通常先在 CPU 上运行即可;神经网络训练或大批量图像处理更可能受益于 GPU。即便使用 DQN,若网络很小、环境交互很慢,GPU 也未必更快。

安装完成后,要同时核对解释器、PyTorch 构建、驱动和设备。把模型放到 GPU 上但输入仍在 CPU 上,会出现设备不一致错误。

7.2 包版本截图与最小检查#

pytorch 环境中的 PyTorch 相关包列表,包含 torch 2.7.1+cu118、torchaudio 和 torchvision

图 10:PyTorch 相关包的版本记录。截图中的 torch 为 2.7.1+cu118,表示安装了相应 CUDA 构建的软件包;这不等于 GPU 已经可用,还需检查驱动、设备和实际张量运算。

下面的代码在 CUDA 可用时选择 GPU,否则使用 CPU,并执行一次矩阵乘法检查:

import torch
# 教材中的 CUDA 检查应写为 torch.cuda,而不是 torch.CUDA。
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("PyTorch:", torch.__version__)
print("运行设备:", device)
if device.type == "cuda":
print("GPU:", torch.cuda.get_device_name(0))
# 真正进行一次张量运算,确认分配与计算能够完成。
x = torch.ones((2, 2), device=device)
print(x @ x)

这是检查代码,不表示本机已经配置 GPU。实际实验应在记录中写明使用了 CPU 还是 GPU。

八、强化学习基础#

8.1 安装与环境配置#

创建环境并安装依赖。这组命令选择 CPU 版 PyTorch,适合先运行基础示例:

conda create -n rl-basic python=3.11 pip -y
conda activate rl-basic
python -m pip install "numpy==1.26.4"
python -m pip install "torch==2.7.1" --index-url https://download.pytorch.org/whl/cpu
python -m pip install "gymnasium[classic-control]==1.2.0" "stable-baselines3==2.7.0" "matplotlib==3.9.2"
python -m pip check

pip check 用来检查已安装依赖是否存在版本冲突,不用于判断算法是否学会任务。下面的 Q-learning 示例只依赖 Python 标准库,不需要先安装这些深度学习依赖。

8.2 基础强化学习示例#

8.2.1 Q-learning:5×5 迷宫#

该案例沿用参考书的网格寻路主题,环境重新编写为无需 GUI 的教学实现。障碍为不可进入的墙,撞墙留在原地;不是参考书陷阱终止语义的逐行复刻。

"""无第三方依赖的 Q-learning:5×5 迷宫。Python 3.10+。"""
import argparse
import json
import random
from pathlib import Path
SIZE = 5
START, GOAL = (0, 0), (4, 4)
WALLS = {(1, 1), (1, 3), (2, 3), (3, 1)}
MOVES = ((-1, 0), (1, 0), (0, -1), (0, 1)) # 上、下、左、右
def transition(state, action):
"""撞墙留在原地;每步付出成本,鼓励找到短路径。"""
# 用 row * SIZE + col 将二维坐标编码为 Q 表的行号。
row, col = divmod(state, SIZE)
dr, dc = MOVES[action]
nxt = (row + dr, col + dc)
if not (0 <= nxt[0] < SIZE and 0 <= nxt[1] < SIZE) or nxt in WALLS:
nxt = (row, col)
terminated = nxt == GOAL
return nxt[0] * SIZE + nxt[1], 10.0 if terminated else -1.0, terminated
def greedy(values, rng):
# 随机打破并列,避免全零初始化时始终选择第一个动作。
best = max(values)
return rng.choice([i for i, v in enumerate(values) if v == best])
def train(episodes=3000, seed=42):
if episodes <= 0:
raise ValueError("episodes 必须为正整数")
rng = random.Random(seed)
# 每个状态占一行,四列分别对应上下左右动作的长期价值估计。
q = [[0.0] * 4 for _ in range(SIZE * SIZE)]
# alpha 控制 TD 更新步长;gamma 决定未来奖励的折扣程度。
alpha, gamma = 0.1, 0.95
for episode in range(episodes):
state = 0
# 随训练减少随机探索,但保留至少 5% 的探索机会。
epsilon = max(0.05, 1.0 - episode / (episodes * 0.8))
for _ in range(100):
action = rng.randrange(4) if rng.random() < epsilon else greedy(q[state], rng)
nxt, reward, terminated = transition(state, action)
# 真实终止不 bootstrap;100 步是采样保护上限,不属于任务终止。
target = reward + (0.0 if terminated else gamma * max(q[nxt]))
# target - Q(s,a) 是 TD 误差;只更新本次访问的状态动作。
q[state][action] += alpha * (target - q[state][action])
state = nxt
if terminated:
break
return q
def evaluate(q, seed=10042):
"""使用独立随机流,不探索、不更新 Q 表。"""
rng = random.Random(seed)
state, reward_sum = 0, 0.0
route = [START]
for _ in range(100):
state, reward, terminated = transition(state, greedy(q[state], rng))
route.append(divmod(state, SIZE))
reward_sum += reward
if terminated:
return {"success": True, "steps": len(route) - 1,
"return": reward_sum, "route": route}
return {"success": False, "steps": 100, "return": reward_sum, "route": route}
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--episodes", type=int, default=3000)
parser.add_argument("--seed", type=int, default=42)
parser.add_argument("--output", type=Path, default=Path("q_table.json"))
args = parser.parse_args()
table = train(args.episodes, args.seed)
# 用独立的随机流打破并列动作,不让评估消耗训练的随机序列。
result = evaluate(table, args.seed + 10000)
args.output.write_text(json.dumps({"seed": args.seed, "q": table,
"evaluation": result}, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False))

8.2.2 测试结果与路径说明#

Q-learning 迷宫测试输出,success为true,8步到达终点,累计奖励为3.0

图 11:训练后策略的测试输出。success: true 表示到达终点,steps: 8 表示执行了 8 次移动,return: 3.0 是这一回合的累计奖励;route 列出从起点 (0,0) 到终点 (4,4) 的网格坐标。

代码中普通移动奖励为 −1,到达终点奖励为 +10。因此 7 次普通移动和 1 次到达终点的移动,累计得到 7 × (−1) + 10 = 3,与截图一致。起终点的曼哈顿距离为 8,图中的路径也避开了给定墙格,因此它是这个固定迷宫的一条最短可行路径。

这张图记录的是一次测试,不代表不同地图或随机种子下都能成功。换到无人机多点巡检时,还需要把已完成点位、剩余电量和返航条件纳入任务设计。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

《科研综合训练实践教程》阅读汇报
https://minikou.cloud/posts/comprehensive-training_practice_tutorial/
作者
minikou
发布于
2026-10-08
许可协议
Unlicensed

部分信息可能已经过时

目录