用GitHub Copilot学AI是什么体验?手把手教你用代码理解神经网络核心概念
用GitHub Copilot学AI是什么体验?手把手教你用代码理解神经网络核心概念
最近几年,AI从一个遥不可及的学术概念,变成了我们身边触手可及的工具。但很多朋友在入门时,总会卡在理论到实践的鸿沟上——那些复杂的数学公式、抽象的算法描述,看懂了,却又好像没完全懂。直到我开始尝试一种新的学习方式:让GitHub Copilot这位“AI编程伙伴”带着我,用一行行可运行的代码,去“触摸”和“感受”AI的核心原理。
这感觉就像学开车,光看说明书永远学不会,必须手握方向盘,感受油门和刹车的反馈。学习AI也是如此。本文将分享我如何利用VS Code和GitHub Copilot,搭建一个沉浸式的“代码实验室”,通过亲手实现从简单的感知机到反向传播、再到遗传算法等经典模型,将书本上晦涩的概念,转化为屏幕上清晰可见的数据流和可视化图表。无论你是正在啃《人工智能导论》的学生,还是希望夯实基础的开发者,这套方法或许能为你打开一扇新的大门。
1. 环境搭建:从零开始的AI代码实验室
工欲善其事,必先利其器。一个顺手的开发环境,能让你把精力完全集中在算法逻辑本身,而不是纠结于包版本冲突或环境配置。我们的核心工具链非常简单:VS Code + Python + Jupyter Notebook扩展 + GitHub Copilot。
1.1 核心工具安装与配置
首先,确保你的机器上安装了Python(推荐3.8及以上版本)。接下来,在VS Code中安装几个必不可少的扩展:
- Python扩展:由Microsoft官方提供,集成了代码调试、智能提示、Jupyter Notebook支持等核心功能。
- Jupyter扩展:让我们能在VS Code内无缝地编写和运行
.ipynb笔记本文件,这是进行交互式AI实验的绝佳载体。 - GitHub Copilot:本篇文章的“主角”。安装后,你需要登录GitHub账号并完成授权。它的核心能力是代码补全和代码建议,但我们将用它来辅助理解和生成算法实现。
安装完成后,创建一个新的文件夹作为项目目录,并在VS Code中打开它。接着,我们通过终端初始化项目环境。我强烈建议为每个项目使用独立的虚拟环境,这能避免依赖污染。
# 在项目根目录下执行
python -m venv .venv
# 激活虚拟环境
# Windows:
.venv\Scripts\activate
# macOS/Linux:
source .venv/bin/activate
# 安装基础科学计算库
pip install numpy matplotlib pandas
# 安装Jupyter内核,以便在VS Code中使用
pip install ipykernel
提示:如果你在激活虚拟环境后,VS Code的终端没有显示
(.venv)前缀,可能需要手动选择解释器。按下Ctrl+Shift+P,输入“Python: Select Interpreter”,然后选择路径中包含.venv的那个Python即可。
1.2 与Copilot协作:从“提问”开始
环境就绪,现在让我们感受一下Copilot如何改变学习方式。传统的学习路径是:看书 -> 理解概念 -> 尝试编码。而有了Copilot,我们可以尝试:提出想法 -> Copilot生成代码骨架 -> 阅读并理解代码 -> 修改和实验。
例如,新建一个neural_network_basics.ipynb的Jupyter Notebook文件。在第一格,我们不用写代码,而是先写一个Markdown单元格,描述我们想实现什么:
# 目标:实现一个最简单的单层感知机(Perceptron),用于学习逻辑AND运算。
# 输入:两个二进制特征 (x1, x2)
# 输出:0 或 1
# 算法步骤:
# 1. 初始化权重w和偏置b(通常为小随机数或0)。
# 2. 定义激活函数(这里用阶跃函数)。
# 3. 前向传播计算输出。
# 4. 根据误差更新权重(感知机学习规则)。
写完这段描述后,新建一个代码单元格,直接开始输入函数定义:
def perceptron_train(X, y, learning_rate=0.1, epochs=100):
"""
训练一个感知机模型。
参数:
X: 输入特征矩阵,形状为 (n_samples, n_features)
y: 目标标签,形状为 (n_samples,)
learning_rate: 学习率
epochs: 训练轮数
返回:
weights: 训练后的权重向量
bias: 训练后的偏置
"""
当你输入完函数签名和文档字符串后,暂停一下,Copilot很可能会自动为你补全整个训练循环的代码框架。它生成的代码可能类似这样:
n_samples, n_features = X.shape
weights = np.zeros(n_features)
bias = 0
for epoch in range(epochs):
for idx in range(n_samples):
# 计算线性输出
linear_output = np.dot(X[idx], weights) + bias
# 应用阶跃函数
y_pred = 1 if linear_output >= 0 else 0
# 计算误差
error = y[idx] - y_pred
# 更新权重和偏置
weights += learning_rate * error * X[idx]
bias += learning_rate * error
return weights, bias
这就是我们第一个“可运行”的AI概念。你不必一开始就完全理解每一行,但你可以立刻运行它,用真实的数据去测试,观察权重如何变化,从而直观理解“权重更新”这个抽象概念。接下来,我们就用这个感知机,去解决一个经典问题。
2. 第一行AI代码:用感知机理解“学习”的本质
让我们把上面Copilot生成的函数用起来。我们准备AND运算的数据,并可视化训练过程。
2.1 实现与可视化
在下一个单元格中,我们准备数据并调用训练函数:
import numpy as np
import matplotlib.pyplot as plt
# AND 运算的真值表
X = np.array([[0, 0],
[0, 1],
[1, 0],
[1, 1]])
y = np.array([0, 0, 0, 1]) # AND 运算结果
# 训练感知机
weights, bias = perceptron_train(X, y, learning_rate=0.1, epochs=10)
print(f"训练后的权重: {weights}")
print(f"训练后的偏置: {bias}")
# 定义一个预测函数
def perceptron_predict(X, weights, bias):
linear_output = np.dot(X, weights) + bias
return np.where(linear_output >= 0, 1, 0)
# 测试
predictions = perceptron_predict(X, weights, bias)
print(f"预测结果: {predictions}")
print(f"是否全部正确: {np.array_equal(predictions, y)}")
运行后,你会看到感知机成功地学会了AND逻辑。但这还不够直观。我们修改一下训练函数,让它记录下每一轮迭代后的决策边界,并动态绘制出来。
def perceptron_train_visualize(X, y, learning_rate=0.1, epochs=10):
n_samples, n_features = X.shape
weights = np.zeros(n_features)
bias = 0
history = [] # 用于记录权重和偏置的历史
for epoch in range(epochs):
for idx in range(n_samples):
linear_output = np.dot(X[idx], weights) + bias
y_pred = 1 if linear_output >= 0 else 0
error = y[idx] - y_pred
weights += learning_rate * error * X[idx]
bias += learning_rate * error
history.append((weights.copy(), bias.copy())) # 记录本轮结束后的状态
return weights, bias, history
# 重新训练并获取历史
weights_final, bias_final, history = perceptron_train_visualize(X, y, epochs=5)
# 可视化
plt.figure(figsize=(12, 8))
for i, (w, b) in enumerate(history):
plt.subplot(2, 3, i+1) # 创建2行3列的子图
# 绘制数据点
plt.scatter(X[y==0, 0], X[y==0, 1], c='blue', label='0', s=100)
plt.scatter(X[y==1, 0], X[y==1, 1], c='red', marker='s', label='1', s=100)
# 绘制决策边界: w1*x1 + w2*x2 + b = 0 => x2 = -(w1*x1 + b)/w2
if w[1] != 0: # 避免除零错误
x1_range = np.array([-0.5, 1.5])
x2_range = -(w[0] * x1_range + b) / w[1]
plt.plot(x1_range, x2_range, 'k--', linewidth=2, label=f'Epoch {i}')
plt.xlim(-0.5, 1.5)
plt.ylim(-0.5, 1.5)
plt.grid(True, linestyle='--', alpha=0.7)
plt.title(f'Epoch {i}: w={w.round(2)}, b={b:.2f}')
plt.legend()
plt.tight_layout()
plt.show()
这段代码会生成一系列子图,清晰展示感知机如何通过一次次调整权重和偏置,最终找到那条完美分割蓝色点和红色正方形的直线。你亲眼看到了“学习”的发生。这就是代码带来的、理论无法替代的具象理解。
2.2 从感知机到多层网络:引入非线性
感知机能解决AND问题,但它解决不了XOR(异或)问题。这是AI历史上一个著名的瓶颈,也直接推动了多层神经网络的发展。我们可以让Copilot帮我们快速构建一个简单的两层网络(含一个隐藏层)来尝试解决XOR。
# 让我们构建一个简单的两层神经网络来解决XOR问题
# 网络结构:输入层(2) -> 隐藏层(2, 使用sigmoid) -> 输出层(1, 使用sigmoid)
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def sigmoid_derivative(x):
return x * (1 - x)
# 初始化参数
def initialize_parameters(input_size, hidden_size, output_size):
np.random.seed(42)
W1 = np.random.randn(input_size, hidden_size) * 0.1
b1 = np.zeros((1, hidden_size))
W2 = np.random.randn(hidden_size, output_size) * 0.1
b2 = np.zeros((1, output_size))
return W1, b1, W2, b2
# 前向传播
def forward_propagation(X, W1, b1, W2, b2):
Z1 = np.dot(X, W1) + b1
A1 = sigmoid(Z1)
Z2 = np.dot(A1, W2) + b2
A2 = sigmoid(Z2)
return Z1, A1, Z2, A2
# 计算损失(均方误差)
def compute_loss(y, A2):
m = y.shape[0]
loss = (1/(2*m)) * np.sum(np.square(A2 - y))
return loss
# 反向传播
def backward_propagation(X, y, Z1, A1, Z2, A2, W2):
m = y.shape[0]
dZ2 = A2 - y
dW2 = (1/m) * np.dot(A1.T, dZ2)
db2 = (1/m) * np.sum(dZ2, axis=0, keepdims=True)
dA1 = np.dot(dZ2, W2.T)
dZ1 = dA1 * sigmoid_derivative(A1)
dW1 = (1/m) * np.dot(X.T, dZ1)
db1 = (1/m) * np.sum(dZ1, axis=0, keepdims=True)
return dW1, db1, dW2, db2
# 更新参数
def update_parameters(W1, b1, W2, b2, dW1, db1, dW2, db2, learning_rate):
W1 -= learning_rate * dW1
b1 -= learning_rate * db1
W2 -= learning_rate * dW2
b2 -= learning_rate * db2
return W1, b1, W2, b2
现在,我们可以用Copilot快速补全训练循环,并观察这个小型网络如何学习XOR。
# XOR 数据
X_xor = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_xor = np.array([[0], [1], [1], [0]]) # 注意形状为(4,1)
# 超参数
input_size = 2
hidden_size = 2
output_size = 1
learning_rate = 0.5
epochs = 10000
# 初始化
W1, b1, W2, b2 = initialize_parameters(input_size, hidden_size, output_size)
loss_history = []
# 训练循环
for i in range(epochs):
# 前向传播
Z1, A1, Z2, A2 = forward_propagation(X_xor, W1, b1, W2, b2)
# 计算损失
loss = compute_loss(y_xor, A2)
loss_history.append(loss)
# 反向传播
dW1, db1, dW2, db2 = backward_propagation(X_xor, y_xor, Z1, A1, Z2, A2, W2)
# 更新参数
W1, b1, W2, b2 = update_parameters(W1, b1, W2, b2, dW1, db1, dW2, db2, learning_rate)
if i % 1000 == 0:
print(f"Epoch {i}, Loss: {loss:.6f}")
# 预测
_, _, _, predictions = forward_propagation(X_xor, W1, b1, W2, b2)
print("\n最终预测值(四舍五入):")
print(np.round(predictions))
print("真实标签:")
print(y_xor)
# 绘制损失下降曲线
plt.figure(figsize=(10, 5))
plt.plot(loss_history)
plt.yscale('log') # 使用对数坐标更清晰地观察损失下降
plt.xlabel('Epoch')
plt.ylabel('Loss (Log Scale)')
plt.title('Training Loss over Epochs for XOR Problem')
plt.grid(True)
plt.show()
运行这段代码,你会看到损失从较高的值逐渐下降到接近0,并且网络最终能正确预测XOR。通过这个亲手搭建的微型网络,你不仅理解了反向传播的代码实现,更关键的是,你直观地看到了非线性激活函数(sigmoid)和隐藏层如何赋予网络解决线性不可分问题的能力。Copilot在这里扮演了“高级脚手架”的角色,它帮你处理了繁琐的矩阵求导和维度对齐的细节,让你能聚焦于算法的主干逻辑。
3. 可视化反向传播:看清梯度如何流动
反向传播(Backpropagation)是神经网络训练的引擎,但它的数学推导常常让人望而生畏。我们可以利用一些可视化技巧,让这个“黑箱”过程变得透明。我们将创建一个更简单的网络,并手动“追踪”一次前向和反向传播,用数值和图表展示每一步。
3.1 构建一个可追踪的微型网络
假设我们有一个极其简单的网络:输入层1个神经元,隐藏层2个神经元,输出层1个神经元。我们使用Sigmoid激活函数和均方误差损失。我们为所有参数赋予一个固定的初始值,以便于跟踪。
# 固定随机种子,确保可复现
np.random.seed(123)
# 定义网络结构和固定初始参数
# 输入: x = 0.5, 真实标签: y_true = 0.8
x = np.array([[0.5]])
y_true = np.array([[0.8]])
# 初始化参数(我们手动指定以便追踪)
W1 = np.array([[0.1, 0.2]]) # 形状 (1, 2)
b1 = np.array([[0.05, 0.05]]) # 形状 (1, 2)
W2 = np.array([[0.3], [0.4]]) # 形状 (2, 1)
b2 = np.array([[0.1]]) # 形状 (1, 1)
print("初始参数:")
print(f"W1: {W1}")
print(f"b1: {b1}")
print(f"W2: {W2}")
print(f"b2: {b2}")
print("-" * 40)
3.2 分步计算与可视化
接下来,我们进行单次前向传播和反向传播,并打印出每一个中间变量的值。
# 步骤1: 前向传播
print("=== 前向传播 ===")
z1 = np.dot(x, W1) + b1 # 线性变换
print(f"z1 (隐藏层加权输入): {z1}")
a1 = sigmoid(z1) # 激活
print(f"a1 (隐藏层激活输出): {a1}")
z2 = np.dot(a1, W2) + b2 # 线性变换
print(f"z2 (输出层加权输入): {z2}")
a2 = sigmoid(z2) # 激活,即最终预测值 y_pred
print(f"a2 (最终输出 y_pred): {a2}")
print("-" * 40)
# 步骤2: 计算损失
print("=== 计算损失 ===")
loss = 0.5 * (a2 - y_true) ** 2
print(f"单个样本的损失 (MSE): {loss[0][0]:.6f}")
print("-" * 40)
# 步骤3: 反向传播 (链式法则)
print("=== 反向传播 (梯度计算) ===")
# 输出层误差
d_loss_d_a2 = a2 - y_true
print(f"∂Loss/∂a2: {d_loss_d_a2}")
d_a2_d_z2 = sigmoid_derivative(a2)
print(f"∂a2/∂z2 (sigmoid导数): {d_a2_d_z2}")
d_loss_d_z2 = d_loss_d_a2 * d_a2_d_z2
print(f"∂Loss/∂z2: {d_loss_d_z2}")
# 隐藏层误差
d_z2_d_a1 = W2.T
print(f"∂z2/∂a1 (即 W2.T): {d_z2_d_a1}")
d_loss_d_a1 = np.dot(d_loss_d_z2, d_z2_d_a1)
print(f"∂Loss/∂a1: {d_loss_d_a1}")
d_a1_d_z1 = sigmoid_derivative(a1)
print(f"∂a1/∂z1 (sigmoid导数): {d_a1_d_z1}")
d_loss_d_z1 = d_loss_d_a1 * d_a1_d_z1
print(f"∂Loss/∂z1: {d_loss_d_z1}")
print("-" * 40)
# 步骤4: 计算参数梯度
print("=== 参数梯度 ===")
# 关于W2和b2的梯度
d_loss_d_W2 = np.dot(a1.T, d_loss_d_z2)
d_loss_d_b2 = np.sum(d_loss_d_z2, axis=0, keepdims=True)
print(f"∂Loss/∂W2: {d_loss_d_W2}")
print(f"∂Loss/∂b2: {d_loss_d_b2}")
# 关于W1和b1的梯度
d_loss_d_W1 = np.dot(x.T, d_loss_d_z1)
d_loss_d_b1 = np.sum(d_loss_d_z1, axis=0, keepdims=True)
print(f"∂Loss/∂W1: {d_loss_d_W1}")
print(f"∂Loss/∂b1: {d_loss_d_b1}")
运行这段代码,控制台会输出每一步的计算结果。这就像给网络做了一次“X光检查”,你能清晰地看到误差(Loss)是如何从输出层一层层反向传播到输入层,并分解到每一个权重和偏置上的。
为了更直观,我们可以用图表来展示这个微型网络的结构和数据流动。我们可以用matplotlib的箭头和文本框来绘制。
# 绘制网络结构与数据流图(简化示意)
fig, ax = plt.subplots(figsize=(10, 6))
ax.axis('off')
# 定义节点位置
layers = [['x'], ['h1', 'h2'], ['y_pred']]
pos = {}
pos['x'] = (1, 5)
pos['h1'] = (3, 6)
pos['h2'] = (3, 4)
pos['y_pred'] = (5, 5)
pos['y_true'] = (6, 5) # 真实标签节点
# 绘制节点
for node, (x, y) in pos.items():
ax.plot(x, y, 'o', markersize=40, color='lightblue' if node != 'y_true' else 'lightgreen')
ax.text(x, y, node, ha='center', va='center', fontsize=12, fontweight='bold')
# 绘制连接和标注(以W1[0,0]和W2[0,0]为例)
# 连接 x -> h1
ax.annotate('', xy=pos['h1'], xytext=pos['x'], arrowprops=dict(arrowstyle='->', lw=2, color='gray'))
ax.text(2, 5.7, f'W1[0,0]={W1[0,0]:.2f}\nz1_h1={z1[0,0]:.3f}\na1_h1={a1[0,0]:.3f}', fontsize=9, bbox=dict(boxstyle="round,pad=0.3", fc="wheat", alpha=0.7))
# 连接 h1 -> y_pred
ax.annotate('', xy=pos['y_pred'], xytext=pos['h1'], arrowprops=dict(arrowstyle='->', lw=2, color='gray'))
ax.text(4, 5.7, f'W2[0,0]={W2[0,0]:.2f}\nz2={z2[0,0]:.3f}\ny_pred={a2[0,0]:.3f}', fontsize=9, bbox=dict(boxstyle="round,pad=0.3", fc="lightcoral", alpha=0.7))
# 标注损失和梯度
ax.text(5.5, 4, f'Loss={loss[0,0]:.4f}', fontsize=10, bbox=dict(boxstyle="round,pad=0.3", fc="yellow", alpha=0.7))
ax.text(4, 4.3, f'∂L/∂W2≈{d_loss_d_W2[0,0]:.4f}', fontsize=9, color='darkred')
ax.text(2.2, 4.3, f'∂L/∂W1≈{d_loss_d_W1[0,0]:.4f}', fontsize=9, color='darkred')
ax.set_xlim(0, 7)
ax.set_ylim(3, 7)
ax.set_title('反向传播数据流与梯度计算示意', fontsize=14)
plt.show()
这张图虽然简单,但它将前向传播的数值计算(z, a)和反向传播的梯度(∂L/∂W)关联了起来。当你看到∂L/∂W2这个数字时,你就能立刻对应到它是如何通过∂L/∂a2 -> ∂a2/∂z2 -> ∂z2/∂W2这条链计算出来的。这种将抽象数学与具体代码、可视化结合的方式,能极大地深化你对核心机制的理解。
4. 超越监督学习:用遗传算法解决优化问题
神经网络是连接主义AI的代表,而遗传算法(Genetic Algorithm, GA)则属于进化计算范畴。它模拟自然选择过程,为解决复杂的组合优化问题提供了另一种思路。让我们用Copilot辅助,实现一个经典的GA应用:解决旅行商问题(TSP)的简化版。
4.1 定义问题与编码
假设有5个城市,我们需要找到访问所有城市一次并回到起点的最短路径。我们首先定义城市坐标和距离函数。
# 定义5个城市的坐标 (简化问题)
cities = {
0: (0, 0),
1: (1, 5),
2: (3, 2),
3: (5, 6),
4: (7, 1)
}
def calculate_distance(path):
"""计算一条路径的总距离。路径是城市的排列,如 [0, 2, 1, 4, 3]"""
total_distance = 0
num_cities = len(path)
for i in range(num_cities):
city_a = path[i]
city_b = path[(i + 1) % num_cities] # 回到起点
x1, y1 = cities[city_a]
x2, y2 = cities[city_b]
total_distance += np.sqrt((x2 - x1)**2 + (y2 - y1)**2)
return total_distance
# 测试距离计算
sample_path = [0, 1, 2, 3, 4]
print(f"路径 {sample_path} 的总距离: {calculate_distance(sample_path):.2f}")
在遗传算法中,一条路径(解)就是一个“染色体”。我们使用城市索引的排列来表示。适应度函数(我们希望最大化)通常取距离的倒数。
4.2 实现遗传算法核心操作
现在,让我们实现GA的核心步骤:初始化种群、选择、交叉、变异。
def initialize_population(pop_size, num_cities):
"""初始化种群:生成pop_size个随机排列"""
population = []
for _ in range(pop_size):
individual = list(range(num_cities))
np.random.shuffle(individual)
population.append(individual)
return population
def fitness_evaluation(population):
"""评估种群中每个个体的适应度(距离越短,适应度越高)"""
fitness_scores = []
for individual in population:
distance = calculate_distance(individual)
# 适应度取距离的倒数,并乘以一个缩放因子避免过小
fitness = 1000.0 / (distance + 1e-5) # 加上一个小数防止除零
fitness_scores.append(fitness)
return np.array(fitness_scores)
def selection(population, fitness_scores, num_parents):
"""轮盘赌选择:根据适应度比例选择父代"""
probs = fitness_scores / fitness_scores.sum()
selected_indices = np.random.choice(len(population), size=num_parents, p=probs, replace=True)
parents = [population[i] for i in selected_indices]
return parents
def crossover(parent1, parent2):
"""顺序交叉 (OX):产生一个子代"""
size = len(parent1)
# 随机选择两个切点
start, end = sorted(np.random.choice(range(size), 2, replace=False))
child = [None] * size
# 将父代1的切片复制到子代
child[start:end+1] = parent1[start:end+1]
# 从父代2填充剩余位置,保持顺序
pointer = 0
for gene in parent2:
if gene not in child:
while child[pointer] is not None:
pointer += 1
child[pointer] = gene
return child
def mutation(individual, mutation_rate):
"""交换变异:以一定概率随机交换两个位置的城市"""
if np.random.rand() < mutation_rate:
size = len(individual)
idx1, idx2 = np.random.choice(range(size), 2, replace=False)
individual[idx1], individual[idx2] = individual[idx2], individual[idx1]
return individual
4.3 运行进化并可视化
有了这些基础函数,我们就可以构建主循环,并观察种群是如何一步步进化出更优解的。
# 遗传算法主函数
def genetic_algorithm_tsp(cities_dict, pop_size=50, generations=200, mutation_rate=0.1):
num_cities = len(cities_dict)
global cities
cities = cities_dict # 更新全局变量
# 初始化
population = initialize_population(pop_size, num_cities)
best_fitness_history = []
avg_fitness_history = []
best_individual_all_time = None
best_fitness_all_time = -np.inf
for gen in range(generations):
# 评估
fitness_scores = fitness_evaluation(population)
avg_fitness = fitness_scores.mean()
best_fitness_idx = fitness_scores.argmax()
best_fitness = fitness_scores[best_fitness_idx]
best_individual = population[best_fitness_idx]
# 更新历史最佳
if best_fitness > best_fitness_all_time:
best_fitness_all_time = best_fitness
best_individual_all_time = best_individual.copy()
best_fitness_history.append(1/(best_fitness/1000)) # 转换回距离便于观察
avg_fitness_history.append(1/(avg_fitness/1000))
# 选择
parents = selection(population, fitness_scores, pop_size)
# 交叉与变异,生成新一代
new_population = []
for i in range(0, pop_size, 2):
parent1, parent2 = parents[i], parents[i+1]
child1 = crossover(parent1, parent2)
child2 = crossover(parent2, parent1) # 可以交换父母顺序产生不同子代
child1 = mutation(child1, mutation_rate)
child2 = mutation(child2, mutation_rate)
new_population.extend([child1, child2])
population = new_population[:pop_size] # 确保种群大小不变
if gen % 20 == 0:
print(f"Generation {gen:3d} | Best Dist: {1/(best_fitness/1000):.2f} | Avg Dist: {1/(avg_fitness/1000):.2f}")
print(f"\n最终找到的最佳路径: {best_individual_all_time}")
print(f"最短距离: {1/(best_fitness_all_time/1000):.2f}")
return best_individual_all_time, best_fitness_history, avg_fitness_history
# 运行算法
best_path, best_history, avg_history = genetic_algorithm_tsp(cities, pop_size=30, generations=150)
# 可视化结果
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 子图1:绘制最佳路径
ax1 = axes[0]
city_ids = list(cities.keys())
city_coords = np.array(list(cities.values()))
# 绘制城市点
ax1.scatter(city_coords[:, 0], city_coords[:, 1], c='red', s=200, zorder=5)
for i, (x, y) in enumerate(city_coords):
ax1.text(x, y, str(i), fontsize=12, ha='center', va='center', color='white', fontweight='bold')
# 绘制路径
path_coords = city_coords[best_path + [best_path[0]]] # 回到起点形成闭环
ax1.plot(path_coords[:, 0], path_coords[:, 1], 'b-', linewidth=2, marker='o')
ax1.set_title(f'最佳旅行商路径 (距离: {calculate_distance(best_path):.2f})')
ax1.set_xlabel('X坐标')
ax1.set_ylabel('Y坐标')
ax1.grid(True)
# 子图2:绘制进化曲线
ax2 = axes[1]
generations = range(len(best_history))
ax2.plot(generations, best_history, 'r-', label='每代最佳距离', linewidth=2)
ax2.plot(generations, avg_history, 'b--', label='每代平均距离', linewidth=1.5)
ax2.set_xlabel('进化代数')
ax2.set_ylabel('路径距离')
ax2.set_title('遗传算法进化过程')
ax2.legend()
ax2.grid(True)
plt.tight_layout()
plt.show()
运行这段代码,你会看到控制台打印出进化过程中每代的最佳距离和平均距离,同时两张图会展示最终找到的路径和进化曲线。观察曲线,你会发现距离(我们想最小化的值)总体呈下降趋势,但过程中有波动,这体现了遗传算法通过交叉和变异进行探索的特性。
通过这个完整的例子,你不仅用代码实现了遗传算法,还看到了它如何应用于一个经典的NP-hard优化问题。Copilot在其中的作用是帮你快速搭建了算法骨架(如轮盘赌选择、顺序交叉的实现),让你能更专注于调整超参数(种群大小、变异率)和观察算法行为,理解“选择压力”、“探索与利用的平衡”这些概念的实际含义。
在这个过程中,我最大的体会是,Copilot像一个不知疲倦的结对编程伙伴。它不会直接给你答案,但能根据你的意图和上下文,快速生成高质量的代码草稿。这迫使你必须去思考“我想要什么”,然后阅读、调试、修改它生成的代码。这个“思考-生成-验证-理解”的循环,恰恰是深度学习最有效的方式。当你亲手让这些算法在屏幕上跑起来,看着损失曲线下降、看着路径被优化、看着决策边界移动时,那些原本停留在纸面上的公式和定理,才真正内化成了你的直觉和经验。
更多推荐

所有评论(0)