Python 语法与常用包
Python — Guido van Rossum, 1991
Python is a programming language that lets you work quickly and integrate systems more effectively.
Python 是一门让你能够快速开发并高效整合系统的编程语言。它是机器学习与人工智能领域最主流的编程语言,几乎所有深度学习框架都以 Python 为主要接口。
基础语法
变量与数据类型
Python 是动态类型语言,变量不需要声明类型,赋值即创建。
# 基本数据类型
x = 42 # int 整数
y = 3.14 # float 浮点数
name = "疾旋鼬" # str 字符串
is_cute = True # bool 布尔值
nothing = None # NoneType 空值
# 查看类型
print(type(x)) # <class 'int'>
print(type(y)) # <class 'float'>
print(type(name)) # <class 'str'>
print(type(is_cute))# <class 'bool'>
Python 3 中,int 没有大小限制,可以表示任意大的整数:
big_number = 10 ** 100 # 10 的 100 次方,googol
print(big_number) # 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
类型转换
# 显式类型转换
s = "123"
n = int(s) # 字符串 → 整数: 123
f = float(s) # 字符串 → 浮点数: 123.0
s2 = str(n) # 整数 → 字符串: "123"
# 浮点数与整数互转
a = int(3.9) # 3 (截断,不是四舍五入)
b = float(3) # 3.0
字符串操作
# 字符串是不可变的序列类型
s = "Hello, 疾旋鼬"
# 索引与切片(从 0 开始)
print(s[0]) # 'H'
print(s[-1]) # '鼬'(负索引从末尾开始)
print(s[0:5]) # 'Hello'(左闭右开区间)
# 常用方法
print(s.lower()) # 'hello, 疾旋鼬'
print(s.upper()) # 'HELLO, 疾旋鼬'
print(s.replace('Hello', 'Hi')) # 'Hi, 疾旋鼬'
print(s.split(', '))# ['Hello', '疾旋鼬']
print(len(s)) # 10
# f-string 格式化(Python 3.6+)
name = "疾旋鼬"
age = 1
print(f"{name}今年{age}岁") # '疾旋鼬今年1岁'
print(f"{3.14159:.2f}") # '3.14'(保留两位小数)
print(f"{'居中':^10}") # ' 居中 '(居中对齐)
# 多行字符串
poem = """
疾旋鼬,
蜷缩旋转,
速度惊人。
"""
运算符
# 算术运算符
print(7 + 3) # 10 加法
print(7 - 3) # 4 减法
print(7 * 3) # 21 乘法
print(7 / 3) # 2.3333... 除法(结果总是 float)
print(7 // 3) # 2 整除(向下取整)
print(7 % 3) # 1 取模(余数)
print(7 ** 3) # 343 幂运算
# 比较运算符(返回 bool)
print(3 == 3) # True 等于
print(3 != 4) # True 不等于
print(3 > 2) # True 大于
print(3 <= 3) # True 小于等于
# 逻辑运算符
print(True and False) # False 与
print(True or False) # True 或
print(not True) # False 非
# 身份运算符(判断是否是同一个对象)
a = [1, 2, 3]
b = a
c = [1, 2, 3]
print(a is b) # True (b 指向同一个对象)
print(a is c) # False (c 是新创建的对象)
print(a == c) # True (值相等)
# 成员运算符
print(3 in [1, 2, 3]) # True
print('疾' in '疾旋鼬') # True
# 链式比较
x = 5
print(1 < x < 10) # True
条件语句
score = 85
if score >= 90:
grade = "A"
elif score >= 80:
grade = "B"
elif score >= 70:
grade = "C"
else:
grade = "D"
print(f"成绩: {grade}") # 成绩: B
elif 是 else if 的缩写。条件判断从上到下,执行第一个满足条件的分支后跳过其余分支。
循环
for 循环
# 遍历列表
animals = ["疾旋鼬", "捣蛋猫", "桃旋鼬"]
for animal in animals:
print(animal)
# range(start, stop, step) 生成整数序列
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 8, 2): # 2, 4, 6
print(i)
# enumerate 同时获取索引和值
for i, animal in enumerate(animals):
print(f"{i}: {animal}")
# 0: 疾旋鼬
# 1: 捣蛋猫
# 2: 桃旋鼬
# zip 并行遍历多个序列
names = ["疾旋鼬", "捣蛋猫"]
types = ["龙", "无"]
for name, t in zip(names, types):
print(f"{name} 是 {t} 属性")
while 循环
break 与 continue
# break: 立即退出整个循环
for i in range(10):
if i == 5:
break # 当 i=5 时退出
print(i) # 输出 0 1 2 3 4
# continue: 跳过当前迭代,进入下一次
for i in range(10):
if i % 2 == 0:
continue # 跳过偶数
print(i) # 输出 1 3 5 7 9
List Comprehension
列表推导式(List Comprehension)是 Python 中创建列表的简洁方式,在机器学习代码中极其常用。
# 基本语法: [表达式 for 变量 in 可迭代对象]
squares = [x**2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 带条件过滤: [表达式 for 变量 in 可迭代对象 if 条件]
evens = [x for x in range(20) if x % 2 == 0]
print(evens) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# 嵌套推导: 展平二维列表
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for row in matrix for x in row]
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 带表达式的推导
labels = [0, 1, 2, 1, 0]
one_hot = [[1 if i == l else 0 for i in range(3)] for l in labels]
print(one_hot) # [[1,0,0], [0,1,0], [0,0,1], [0,1,0], [1,0,0]]
数据结构
列表(list)
列表是有序、可变的序列,是 Python 中最常用的数据结构。
# 创建列表
fruits = ["疾旋鼬", "捣蛋猫", "桃旋鼬"]
# 索引与切片
print(fruits[0]) # '疾旋鼬'
print(fruits[-1]) # '桃旋鼬'
print(fruits[0:2]) # ['疾旋鼬', '捣蛋猫']
# 修改元素
fruits[1] = "火绒狐"
print(fruits) # ['疾旋鼬', '火绒狐', '桃旋鼬']
# 添加元素
fruits.append("冰刺猬") # 末尾添加
fruits.insert(1, "雷角鹿") # 在索引 1 处插入
fruits.extend(["风灵龙", "水灵猫"]) # 扩展列表
# 删除元素
fruits.remove("桃旋鼬") # 按值删除
popped = fruits.pop() # 弹出末尾元素
del fruits[0] # 按索引删除
# 排序
numbers = [3, 1, 4, 1, 5, 9]
numbers.sort() # 原地排序: [1, 1, 3, 4, 5, 9]
numbers.sort(reverse=True) # 降序: [9, 5, 4, 3, 1, 1]
sorted_nums = sorted(numbers) # 返回新列表,不修改原列表
# 列表复制(注意浅拷贝与深拷贝)
a = [1, 2, [3, 4]]
b = a # 浅拷贝: b 和 a 指向同一对象
c = a.copy() # 浅拷贝: 新列表,但内部列表仍共享
import copy
d = copy.deepcopy(a)# 深拷贝: 完全独立的副本
# 常用操作
print(len(fruits)) # 列表长度
print(fruits.count("疾旋鼬")) # 统计出现次数
print(fruits.index("疾旋鼬")) # 查找第一次出现的索引
浅拷贝与深拷贝
在 Python 中,列表复制涉及浅拷贝和深拷贝,理解它们的区别对于避免意外的数据修改非常重要。
直接赋值
-b 是 a 的别名,两者指向同一个对象
- 修改其中一个会同时影响另一个
浅拷贝
- 创建新列表,但内嵌元素仍是原列表元素的引用 - 修改不可变元素(如整数)不会相互影响 - 修改可变元素(如内嵌列表)会相互影响a[0] = 100 # 修改整数,c 不变
a[2].append(5) # 修改内嵌列表,c 也会变化
print(a) # [100, 2, [3, 4, 5]]
print(c) # [1, 2, [3, 4, 5]]
深拷贝
- 递归复制所有对象,创建完全独立的副本
- 修改任何元素都不会相互影响
元组(tuple)
元组是有序、不可变的序列。一旦创建,不能修改。
# 创建元组
point = (3, 4)
single = (42,) # 单元素元组需要逗号
empty = ()
# 元组解包
x, y = point
print(x, y) # 3 4
# 交换变量(利用元组解包)
a, b = 1, 2
a, b = b, a # a=2, b=1
# 元组作为字典的键(因为不可变)
locations = {
(0, 0): "起点",
(3, 4): "疾旋鼬的家",
}
字典(dict)
字典是键值对的无序集合(Python 3.7+ 保持插入顺序)。
# 创建字典
pal = {
"name": "疾旋鼬",
"type": "龙",
"hp": 100,
"skills": ["旋转冲击", "蜷缩防御"],
}
# 访问元素
print(pal["name"]) # '疾旋鼬'
print(pal.get("mp", 0)) # 0(键不存在时返回默认值)
# 修改与添加
pal["hp"] = 120 # 修改
pal["attack"] = 85 # 添加新键
# 删除
del pal["attack"]
removed = pal.pop("skills") # 弹出并返回
# 遍历
for key, value in pal.items():
print(f"{key}: {value}")
for key in pal.keys():
print(key)
for value in pal.values():
print(value)
# 字典推导式
squares = {x: x**2 for x in range(6)}
print(squares) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
# 合并字典(Python 3.9+)
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}
d3 = d1 | d2 # {'a': 1, 'b': 3, 'c': 4}(后者覆盖前者)
集合(set)
集合是无序、不重复的元素集合,常用于去重和集合运算。
# 创建集合
types = {"龙", "火", "水", "龙"} # 自动去重
print(types) # {'龙', '火', '水'}
# 集合运算
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b) # 并集: {1, 2, 3, 4, 5, 6}
print(a & b) # 交集: {3, 4}
print(a - b) # 差集: {1, 2}
print(a ^ b) # 对称差集: {1, 2, 5, 6}
# 集合推导式
evens = {x for x in range(20) if x % 2 == 0}
函数
# 基本函数定义
def greet(name):
"""问候函数(这是 docstring,文档字符串)"""
return f"你好,{name}!"
print(greet("疾旋鼬")) # 你好,疾旋鼬!
# 默认参数
def power(base, exp=2):
return base ** exp
print(power(3)) # 9 (使用默认 exp=2)
print(power(3, 3)) # 27
# 可变参数 *args(接收任意数量的位置参数,打包为元组)
def mean(*numbers):
return sum(numbers) / len(numbers)
print(mean(1, 2, 3, 4, 5)) # 3.0
# 关键字可变参数 **kwargs(接收任意数量的关键字参数,打包为字典,参数key=value 形式)
def build_model(**kwargs):
for key, value in kwargs.items():
print(f"{key} = {value}")
build_model(d_model=512, num_heads=8, d_ff=2048)
# 参数组合顺序: 位置参数 → 默认参数 → *args → **kwargs
def transformer_forward(x, mask=None, *layers, **config):
pass
# Lambda 匿名函数(常用于简短的函数定义)
square = lambda x: x ** 2
print(square(5)) # 25
# 函数是一等公民,可以作为参数传递
def apply(func, value):
return func(value)
print(apply(square, 4)) # 16
print(apply(lambda x: x + 1, 4)) # 5
类与面向对象编程
Python 中一切皆对象。类是创建对象的蓝图。
class Pal:
"""帕鲁(游戏中的生物)的基类"""
# 类变量(所有实例共享)
kingdom = "帕鲁世界"
def __init__(self, name, element, hp=100):
"""构造函数,创建实例时自动调用"""
# 实例变量(每个实例独有)
self.name = name
self.element = element
self.hp = hp
self._secret = "内部变量" # 单下划线:约定为"私有"(但不强制)
self.__hidden = "真正私有" # 双下划线:名称改写(name mangling)
def attack(self):
"""实例方法:第一个参数必须是 self"""
return f"{self.name} 发动攻击!"
def take_damage(self, damage):
self.hp -= damage
if self.hp <= 0:
self.hp = 0
print(f"{self.name} 倒下了!")
@property
def is_alive(self):
"""属性装饰器:像属性一样访问,但有计算逻辑"""
return self.hp > 0
@staticmethod
def is_valid_element(element):
"""静态方法:不需要实例,不接收 self"""
valid = {"龙", "火", "水", "草", "电", "冰"}
return element in valid
@classmethod
def from_dict(cls, data):
"""类方法:接收类本身作为第一个参数(cls),常用于替代构造函数"""
return cls(data["name"], data["element"], data["hp"])
def __str__(self):
"""定义 print() 时的输出"""
return f"Pal({self.name}, {self.element}, HP={self.hp})"
def __repr__(self):
"""定义在交互式环境中的显示(更详细)"""
return f"Pal(name='{self.name}', element='{self.element}', hp={self.hp})"
def __len__(self):
"""定义 len() 的行为"""
return self.hp
# 使用
jxy = Pal("疾旋鼬", "龙", 120)
print(jxy) # Pal(疾旋鼬, 龙, HP=120)
print(jxy.attack()) # 疾旋鼬 发动攻击!
print(jxy.is_alive) # True
print(Pal.is_valid_element("龙")) # True
# 从字典创建
data = {"name": "桃旋鼬", "element": "龙", "hp": 90}
txy = Pal.from_dict(data)
print(txy) # Pal(桃旋鼬, 龙, HP=90)
继承
class DragonPal(Pal):
"""龙属性帕鲁,继承自 Pal"""
def __init__(self, name, hp=100, dragon_power=50):
# 调用父类构造函数
super().__init__(name, "龙", hp)
self.dragon_power = dragon_power
def attack(self):
"""重写父类方法"""
return f"{self.name} 发动龙之怒!(威力: {self.dragon_power})"
def fly(self):
"""子类独有方法"""
return f"{self.name} 展翅高飞!"
dragon = DragonPal("疾旋鼬", 120, 80)
print(dragon.attack()) # 疾旋鼬 发动龙之怒!(威力: 80)
print(dragon.fly()) # 疾旋鼬 展翅高飞!
print(dragon.is_alive) # True(继承的属性装饰器仍然可用)
# isinstance 判断类型
print(isinstance(dragon, DragonPal)) # True
print(isinstance(dragon, Pal)) # True(子类实例也是父类实例)
多重继承与 Mixin
class Flyable:
def fly(self):
return f"{self.name} 在飞!"
class Swimmable:
def swim(self):
return f"{self.name} 在游泳!"
class WaterDragonPal(Pal, Flyable, Swimmable):
"""多重继承:同时获得多个类的能力"""
pass
wd = WaterDragonPal("海龙", "水", 150)
print(wd.fly()) # 海龙 在飞!
print(wd.swim()) # 海龙 在游泳!
异常处理
try:
result = 10 / 0
except ZeroDivisionError as e:
print(f"错误: {e}") # 错误: division by zero
except (TypeError, ValueError) as e:
print(f"类型或值错误: {e}") # 可以同时捕获多种异常
else:
print("没有异常时执行")
finally:
print("无论是否异常都执行") # 常用于清理资源
# 自定义异常
class ModelNotFoundError(Exception):
pass
# 抛出异常
def load_model(path):
import os
if not os.path.exists(path):
raise ModelNotFoundError(f"模型文件不存在: {path}")
return "model"
# 上下文管理器(with 语句)
with open("data.txt", "r", encoding="utf-8") as f:
content = f.read()
# 离开 with 块后,文件自动关闭
模块与导入
# 导入整个模块
import math
print(math.sqrt(16)) # 4.0
# 导入特定函数
from math import sqrt, log
print(sqrt(16)) # 4.0
# 导入并重命名
import numpy as np
from torch.nn import functional as F
# 导入模块中的所有(不推荐,容易命名冲突)
from math import *
# 自定义模块
# 假设有一个文件 utils.py,其中定义了 function hello()
# import utils
# from utils import hello
文件操作
# 读取文件
with open("data.txt", "r", encoding="utf-8") as f:
content = f.read() # 读取全部内容
# 或逐行读取
for line in f:
print(line.strip())
# 写入文件
with open("output.txt", "w", encoding="utf-8") as f:
f.write("疾旋鼬\n")
f.write("捣蛋猫\n")
# 追加写入
with open("output.txt", "a", encoding="utf-8") as f:
f.write("桃旋鼬\n")
# 读写 JSON
import json
data = {"name": "疾旋鼬", "hp": 100}
with open("pal.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
with open("pal.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded) # {'name': '疾旋鼬', 'hp': 100}
数值计算基础:NumPy
NumPy 是 Python 科学计算的基石,提供了高效的多维数组对象和大量数学函数。理解 NumPy 是使用 PyTorch 等深度学习框架的前提。
为什么需要 NumPy?
Python 原生的列表虽然灵活,但存储和计算效率极低。NumPy 数组在内存中连续存储,底层用 C 实现,运算速度比纯 Python 快 10-100 倍。在机器学习中,数据几乎总是以数组(张量)的形式存在,NumPy 提供了操作这些数据的所有基础工具。
数组创建
import numpy as np
# 从列表创建
a = np.array([1, 2, 3, 4, 5]) # 一维数组
b = np.array([[1, 2, 3], [4, 5, 6]]) # 二维数组(矩阵)
# 常用创建函数
zeros = np.zeros((3, 4)) # 3×4 全零矩阵
ones = np.ones((2, 3)) # 2×3 全一矩阵
eye = np.eye(4) # 4×4 单位矩阵
arange = np.arange(0, 10, 2) # [0, 2, 4, 6, 8](类似 range)
linspace = np.linspace(0, 1, 5) # [0, 0.25, 0.5, 0.75, 1](等间隔)
random_normal = np.random.randn(3, 4) # 标准正态分布随机数
random_uniform = np.random.rand(3, 4) # [0, 1) 均匀分布随机数
random_int = np.random.randint(0, 10, (3, 4)) # 随机整数
# 数组属性
print(a.shape) # (5,) 形状
print(b.shape) # (2, 3) 形状
print(b.ndim) # 2 维度数
print(b.size) # 6 元素总数
print(b.dtype) # int64 数据类型
数组运算
NumPy 的核心特性是向量化运算——不需要显式循环,直接对整个数组进行运算。
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# 逐元素运算(element-wise)
print(a + b) # [5, 7, 9] 加法
print(a - b) # [-3, -3, -3] 减法
print(a * b) # [4, 10, 18] 逐元素乘法
print(a / b) # [0.25, 0.4, 0.5] 除法
print(a ** 2) # [1, 4, 9] 幂运算
print(np.sqrt(a)) # [1.0, 1.414, 1.732] 平方根
print(np.exp(a)) # [2.718, 7.389, 20.086] 指数
print(np.log(a)) # [0.0, 0.693, 1.099] 自然对数
# 聚合运算
print(np.sum(a)) # 6 求和
print(np.mean(a)) # 2.0 均值
print(np.std(a)) # 0.816 标准差
print(np.max(a)) # 3 最大值
print(np.min(a)) # 1 最小值
print(np.argmax(a)) # 2 最大值索引
# 矩阵运算
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# 矩阵乘法(三种等价写法)
C1 = A @ B # [[19, 22], [43, 50]] 推荐
C2 = np.dot(A, B) # 同上
C3 = A.dot(B) # 同上
# 转置
print(A.T) # [[1, 3], [2, 4]]
# 点积(向量内积)
v1 = np.array([1, 2, 3])
v2 = np.array([4, 5, 6])
print(np.dot(v1, v2)) # 32 = 1*4 + 2*5 + 3*6
索引与切片
A = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
# 基本索引
print(A[0, 1]) # 2 第0行第1列
print(A[1, :]) # [4, 5, 6] 第1行所有列
print(A[:, 2]) # [3, 6, 9] 所有行第2列
# 切片
print(A[0:2, 1:3]) # [[2, 3], [5, 6]] 子矩阵
# 步长切片
print(A[:, 0::2]) # [[1, 3], [4, 6], [7, 9]] 每隔一列
# 布尔索引(条件筛选)
print(A[A > 5]) # [6, 7, 8, 9] 大于5的元素
# 花式索引(用数组索引)
print(A[[0, 2], [1, 2]]) # [2, 9] (0,1) 和 (2,2) 位置的元素
形状操作
a = np.arange(12)
# 重塑
b = a.reshape(3, 4) # 3行4列
c = a.reshape(3, -1) # -1 表示自动推断: 3行4列
d = a.reshape(-1,) # 展平为一维
# 转置与轴操作
M = np.random.randn(2, 3, 4)
print(M.shape) # (2, 3, 4)
M_T = M.transpose(0, 2, 1) # 交换轴: (2, 4, 3)
M_swap = np.swapaxes(M, 1, 2) # 同上
# 拼接
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
print(np.concatenate([a, b], axis=0)) # 沿行拼接: (4, 2)
print(np.concatenate([a, b], axis=1)) # 沿列拼接: (2, 4)
print(np.stack([a, b], axis=0)) # 堆叠: (2, 2, 2)
# 增加维度
v = np.array([1, 2, 3])
print(v.shape) # (3,)
print(v[np.newaxis, :].shape) # (1, 3) 等价于 v.reshape(1, -1)
print(v[:, np.newaxis].shape) # (3, 1) 等价于 v.reshape(-1, 1)
广播(Broadcasting)
广播是 NumPy 中最强大的特性之一,它允许不同形状的数组进行运算。
# 规则:从末尾开始比较维度,满足以下条件之一即可广播:
# 1. 维度相等
# 2. 其中一个维度为 1
# 标量与数组运算
a = np.array([1, 2, 3])
print(a * 2) # [2, 4, 6] 标量 2 被广播到每个元素
# 二维与一维运算
A = np.array([[1, 2, 3],
[4, 5, 6]]) # (2, 3)
b = np.array([10, 20, 30]) # (3,) → 广播为 (2, 3)
print(A + b) # [[11, 22, 33], [14, 25, 36]]
# 列向量与行向量
col = np.array([[1], [2], [3]]) # (3, 1)
row = np.array([10, 20]) # (2,) → 广播为 (1, 2)
print(col + row) # (3, 2): [[11, 21], [12, 22], [13, 23]]
# 实际应用:对每行减去均值(数据归一化)
data = np.random.randn(5, 3) # 5个样本,3个特征
mean = data.mean(axis=0) # 每个特征的均值: (3,)
normalized = data - mean # 广播: (5,3) - (3,) = (5,3)
深度学习框架:PyTorch
PyTorch 是当前最流行的深度学习框架之一,以其动态计算图和 Pythonic 的设计哲学著称。Transformer.md 中的所有代码都基于 PyTorch。
张量(Tensor)
张量是 PyTorch 的核心数据结构,本质上是多维数组,与 NumPy 的 ndarray 类似,但额外支持 GPU 加速和自动微分。
import torch
# === 创建张量 ===
# 从列表创建
t = torch.tensor([1, 2, 3, 4]) # 一维张量
m = torch.tensor([[1, 2], [3, 4]]) # 二维张量
# 常用创建函数
zeros = torch.zeros(3, 4) # 全零
ones = torch.ones(2, 3) # 全一
eye = torch.eye(4) # 单位矩阵
rand = torch.randn(3, 4) # 标准正态分布
rand_uniform = torch.rand(3, 4) # [0, 1) 均匀分布
arange = torch.arange(0, 10, 2) # [0, 2, 4, 6, 8]
linspace = torch.linspace(0, 1, 5) # [0, 0.25, 0.5, 0.75, 1]
full = torch.full((3, 3), 3.14) # 全部填充指定值
# 指定数据类型
t_float = torch.tensor([1, 2, 3], dtype=torch.float32)
t_int = torch.tensor([1, 2, 3], dtype=torch.int64)
# 张量属性
x = torch.randn(2, 3, 4)
print(x.shape) # torch.Size([2, 3, 4]) 形状
print(x.dtype) # torch.float32 数据类型
print(x.device) # cpu 所在设备
print(x.dim()) # 3 维度数
print(x.numel()) # 24 元素总数
张量运算
a = torch.tensor([1.0, 2.0, 3.0])
b = torch.tensor([4.0, 5.0, 6.0])
# 逐元素运算
print(a + b) # tensor([5., 7., 9.])
print(a - b) # tensor([-3., -3., -3.])
print(a * b) # tensor([ 4., 10., 18.]) 逐元素乘法
print(a / b) # tensor([0.2500, 0.4000, 0.5000])
print(a ** 2) # tensor([1., 4., 9.])
# 聚合运算
print(a.sum()) # tensor(6.)
print(a.mean()) # tensor(2.)
print(a.max()) # tensor(3.)
print(a.min()) # tensor(1.)
print(a.argmax()) # tensor(2) 最大值索引
# 矩阵运算
A = torch.randn(2, 3)
B = torch.randn(3, 4)
# 矩阵乘法(三种等价写法)
C1 = A @ B # (2, 4) 推荐写法
C2 = torch.matmul(A, B) # (2, 4)
C3 = torch.mm(A, B) # (2, 4) 仅限二维
# 转置
print(A.T) # (3, 2)
print(A.transpose(0, 1)) # 等价写法,指定交换的维度
# 批量矩阵乘法(Transformer 中最常见的操作)
# 用于并行计算多个样本的注意力分数
batch_A = torch.randn(8, 3, 4) # 8个 3×4 矩阵
batch_B = torch.randn(8, 4, 5) # 8个 4×5 矩阵
batch_C = batch_A @ batch_B # (8, 3, 5) 逐个矩阵相乘
# 或者
batch_C = torch.bmm(batch_A, batch_B) # 等价
# 点积(向量内积)
v1 = torch.tensor([1.0, 2.0, 3.0])
v2 = torch.tensor([4.0, 5.0, 6.0])
print(torch.dot(v1, v2)) # tensor(32.) = 1*4 + 2*5 + 3*6
形状操作
PyTorch 的形状操作与 NumPy 非常相似,但有些方法名不同。
x = torch.arange(24).float()
# reshape / view:改变形状
a = x.reshape(2, 3, 4) # (2, 3, 4)
b = x.view(2, 3, 4) # 同上(view 要求内存连续)
c = x.reshape(2, -1) # (2, 12) -1 自动推断
# view 与 reshape 的区别:
# view 共享内存,要求张量在内存中连续;reshape 可能创建副本
# 在 Transformer 代码中,通常先 .contiguous() 再 .view()
# 转置与维度交换
M = torch.randn(2, 3, 4)
M_T = M.transpose(1, 2) # 交换维度1和2: (2, 4, 3)
M_perm = M.permute(0, 2, 1) # 更通用的维度重排: (2, 4, 3)
# contiguous:使张量在内存中连续
# transpose/permute 后的张量在内存中不连续,需要 contiguous() 才能 view
M_cont = M_T.contiguous() # 确保内存连续
# 拼接
a = torch.randn(2, 3)
b = torch.randn(2, 3)
cat_0 = torch.cat([a, b], dim=0) # (4, 3) 沿维度0拼接
cat_1 = torch.cat([a, b], dim=1) # (2, 6) 沿维度1拼接
stack = torch.stack([a, b], dim=0) # (2, 2, 3) 堆叠(增加新维度)
# 增加/减少维度
v = torch.tensor([1.0, 2.0, 3.0])
print(v.shape) # torch.Size([3])
v_unsq = v.unsqueeze(0) # (1, 3) 等价于 v[None, :]
v_unsq2 = v.unsqueeze(1) # (3, 1) 等价于 v[:, None]
s = torch.tensor([[1.0, 2.0, 3.0]])
s_sq = s.squeeze(0) # (3,) 去掉大小为1的维度
索引与切片
x = torch.arange(24).reshape(2, 3, 4).float()
# 形状 (2, 3, 4)
# 基本索引
print(x[0]) # 第0个: (3, 4)
print(x[0, 1]) # 第0个的第1行: (4,)
print(x[0, 1, 2]) # 标量: tensor(6.)
# 切片(与 NumPy 相同)
print(x[:, 0:2, :]) # 所有样本,前2行: (2, 2, 4)
print(x[0, :, 0::2]) # 第0个样本,所有行,每隔一列: (3, 2)
# 布尔索引
mask = x > 12
print(x[mask]) # 一维张量,包含所有大于12的元素
# 花式索引
indices = torch.tensor([0, 2])
print(x[0, indices]) # 第0个样本的第0行和第2行: (2, 4)
掩码操作
掩码操作在 Transformer 的注意力机制中至关重要。
# 创建掩码张量
scores = torch.randn(3, 3)
# 下三角掩码(因果掩码,用于解码器)
causal_mask = torch.tril(torch.ones(3, 3))
print(causal_mask)
# tensor([[1., 0., 0.],
# [1., 1., 0.],
# [1., 1., 1.]])
# masked_fill:将掩码为0的位置填充为指定值
scores_masked = scores.masked_fill(causal_mask == 0, float('-inf'))
# 掩码为 0 的位置被替换为 -inf,softmax 后变为 0
# 上三角掩码
upper_mask = torch.triu(torch.ones(3, 3), diagonal=1)
print(upper_mask)
# tensor([[0., 1., 1.],
# [0., 0., 1.],
# [0., 0., 0.]])
# 应用 softmax 后,-inf 位置变为 0
weights = torch.softmax(scores_masked, dim=-1)
print(weights)
# 每一行是一个概率分布,对角线以下(含对角线)有值,其余为0
设备管理(CPU / GPU)
# 检查 GPU 是否可用
print(torch.cuda.is_available()) # True / False
# 将张量移到 GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
x = torch.randn(3, 4)
x = x.to(device) # 移到 GPU(如果可用)
# 创建时直接指定设备
x_gpu = torch.randn(3, 4, device='cuda')
# 移回 CPU
x_cpu = x.cpu()
# 模型移到 GPU
# model = model.to(device)
# 获取张量值(从 GPU 移到 CPU 并转为 Python 数字)
val = x[0, 0].item() # 仅适用于标量张量
arr = x.detach().cpu().numpy() # 转为 NumPy 数组
自动微分(Autograd)
自动微分是深度学习的核心——它自动计算梯度,使得反向传播成为可能。
import torch
# requires_grad=True 告诉 PyTorch 需要追踪这个张量上的操作
x = torch.tensor([2.0, 3.0], requires_grad=True)
# 前向计算
y = x ** 2 + 3 * x + 1
z = y.sum() # 标量才能调用 .backward()
# 反向传播:计算 dz/dx
z.backward()
# 梯度:dz/dx = 2x + 3
print(x.grad) # tensor([7., 9.]) 即 [2*2+3, 2*3+3]
计算图与反向传播
PyTorch 使用动态计算图——每次前向传播时实时构建计算图,反向传播后自动释放。这使得调试和控制流(如 if、for)非常自然。
# 在 Transformer 中的实际使用
model = SomeModel()
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
# 训练循环
for epoch in range(num_epochs):
# 前向传播
logits = model(src, tgt) # 自动构建计算图
loss = F.cross_entropy(logits, target)
# 反向传播
optimizer.zero_grad() # 清除旧梯度
loss.backward() # 计算梯度,填充 .grad
optimizer.step() # 更新参数
torch.nn 模块
torch.nn 是 PyTorch 的神经网络模块,提供了构建网络所需的所有组件。
nn.Module:所有网络的基类
class MyModel(nn.Module):
def __init__(self):
super().__init__() # 必须调用父类构造函数
self.linear = nn.Linear(10, 5) # 线性层
self.relu = nn.ReLU() # 激活函数
def forward(self, x): # 定义前向传播
x = self.linear(x)
x = self.relu(x)
return x
model = MyModel()
print(model) # 打印网络结构
print(list(model.parameters())) # 列出所有可学习参数
常用层
# 线性层(全连接层): y = xW^T + b
linear = nn.Linear(in_features=512, out_features=2048, bias=True)
x = torch.randn(1, 10, 512) # (batch, seq_len, d_model)
y = linear(x) # (1, 10, 2048)
print(y.shape)
# 无偏置的线性层(Transformer 中常用)
linear_no_bias = nn.Linear(512, 512, bias=False)
# 嵌入层:将整数索引映射为向量
embedding = nn.Embedding(num_embeddings=10000, embedding_dim=512)
token_ids = torch.tensor([[1, 5, 42, 7]]) # token ID
embedded = embedding(token_ids) # (1, 4, 512)
print(embedded.shape)
# 参数
print(embedding.weight.shape) # (10000, 512) 可学习的嵌入表
# nn.Parameter:将普通张量注册为模型参数
class MyLayer(nn.Module):
def __init__(self, d_model):
super().__init__()
# 这两个参数会被 model.parameters() 自动追踪
self.gamma = nn.Parameter(torch.ones(d_model))
self.beta = nn.Parameter(torch.zeros(d_model))
# register_buffer:注册不参与梯度计算的"缓冲区"
class MyTransformer(nn.Module):
def __init__(self):
super().__init__()
# 位置编码是预计算的,不需要梯度
pe = torch.zeros(100, 512)
self.register_buffer('pos_encoding', pe)
# nn.ModuleList:管理子模块列表
class MyEncoder(nn.Module):
def __init__(self, num_layers=6):
super().__init__()
# ModuleList 中的模块会被正确注册
self.layers = nn.ModuleList([
nn.Linear(512, 512) for _ in range(num_layers)
])
def forward(self, x):
for layer in self.layers:
x = layer(x)
return x
激活函数
# ReLU: max(0, x)
relu = nn.ReLU()
x = torch.tensor([-2.0, -1.0, 0.0, 1.0, 2.0])
print(relu(x)) # tensor([0., 0., 0., 1., 2.])
# 也可以用函数式接口
print(F.relu(x)) # 同上
# GELU(现代 Transformer 中更常用)
gelu = nn.GELU()
print(gelu(x)) # tensor([-0.0455, -0.1588, 0.0000, 0.8412, 1.9545])
# Sigmoid: 1 / (1 + exp(-x))
sigmoid = nn.Sigmoid()
print(sigmoid(x)) # tensor([0.1192, 0.2689, 0.5000, 0.7311, 0.8808])
# Softmax: 将向量转换为概率分布
softmax = nn.Softmax(dim=-1)
logits = torch.tensor([2.0, 1.0, 0.1])
print(softmax(logits)) # tensor([0.6590, 0.2424, 0.0986])
# 注意: softmax 在 Transformer.md 中用 torch.softmax(scores, dim=-1) 调用
损失函数
# 交叉熵损失(分类任务最常用)
# 内部包含 Softmax,所以输入应该是 logits,不是概率
criterion = nn.CrossEntropyLoss()
logits = torch.randn(3, 5) # 3个样本,5个类别
targets = torch.tensor([0, 2, 4]) # 真实类别
loss = criterion(logits, targets)
print(loss) # tensor(1.6094)
# 函数式接口
loss = F.cross_entropy(logits, targets)
# 对于 Transformer 的输出
# logits: (batch, seq_len, vocab_size)
# targets: (batch, seq_len)
# 需要 reshape
logits_flat = logits.view(-1, 5) # (3, 5)
targets_flat = targets.view(-1) # (3,)
loss = F.cross_entropy(logits_flat, targets_flat)
优化器
import torch.optim as optim
model = MyModel()
# Adam 优化器(最常用)
optimizer = optim.Adam(model.parameters(), lr=1e-3)
# 带权重衰减(L2 正则化)
optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5)
# AdamW(现代 Transformer 训练推荐)
optimizer = optim.AdamW(model.parameters(), lr=1e-3, weight_decay=0.01)
# 学习率调度器
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)
# 每 10 个 epoch,学习率乘以 0.1
scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)
# 余弦退火:学习率按余弦曲线衰减
完整训练循环
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
# 1. 定义模型
class SimpleNet(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
self.fc1 = nn.Linear(input_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, output_dim)
def forward(self, x):
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
# 2. 准备数据
X_train = torch.randn(100, 10) # 100 个样本,10 维特征
y_train = torch.randint(0, 3, (100,)) # 100 个标签(0, 1, 2)
# 3. 创建模型和优化器
model = SimpleNet(10, 32, 3)
optimizer = optim.Adam(model.parameters(), lr=0.01)
# 4. 训练循环
for epoch in range(100):
# 前向传播
logits = model(X_train)
loss = F.cross_entropy(logits, y_train)
# 反向传播
optimizer.zero_grad() # 清除旧梯度(PyTorch 默认累加梯度)
loss.backward() # 计算梯度
optimizer.step() # 更新参数
if (epoch + 1) % 20 == 0:
acc = (logits.argmax(dim=1) == y_train).float().mean()
print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}, Acc: {acc.item():.4f}")
# 5. 推理
with torch.no_grad(): # 不追踪梯度,节省内存
test_x = torch.randn(5, 10)
pred = model(test_x).argmax(dim=1)
print(f"预测: {pred}")
完整的 Transformer 训练示例
结合以上所有知识,下面是 Transformer.md 中代码的完整训练流程:
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
# (此处包含 Transformer.md 中定义的所有组件)
# MultiHeadAttention, LayerNorm, FeedForward, EncoderBlock, DecoderBlock, TinyTransformer
# 准备数据
src_vocab_size = 50
tgt_vocab_size = 50
model = TinyTransformer(src_vocab_size, tgt_vocab_size,
d_model=16, num_heads=4, d_ff=64, num_layers=2)
# 模拟训练数据
src_ids = torch.tensor([[3, 7, 12, 5, 9]]) # 源序列
tgt_ids = torch.tensor([[2, 15, 8, 4]]) # 目标序列(输入)
target = torch.tensor([[5, 12, 3, 8]]) # 目标序列(标签,偏移一位)
# 优化器
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
# 训练一步
logits = model(src_ids, tgt_ids) # (1, 4, 50)
loss = F.cross_entropy(logits.view(-1, 50), target.view(-1))
optimizer.zero_grad() # 清除旧梯度
loss.backward() # 反向传播
optimizer.step() # 更新参数
print(f"Loss: {loss.item():.4f}")
数据处理与可视化
Matplotlib:绑图可视化
import matplotlib.pyplot as plt
import numpy as np
# 基本折线图
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)
plt.figure(figsize=(10, 6))
plt.plot(x, y, label='sin(x)', color='blue', linewidth=2)
plt.plot(x, np.cos(x), label='cos(x)', color='red', linewidth=2, linestyle='--')
plt.xlabel('x')
plt.ylabel('y')
plt.title('三角函数')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('trig.png', dpi=150, bbox_inches='tight')
plt.show()
# 子图
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes[0, 0].plot(x, np.sin(x))
axes[0, 0].set_title('sin(x)')
axes[0, 1].plot(x, np.cos(x))
axes[0, 1].set_title('cos(x)')
axes[1, 0].bar([1, 2, 3], [3, 1, 4])
axes[1, 0].set_title('柱状图')
axes[1, 1].scatter(np.random.randn(50), np.random.randn(50))
axes[1, 1].set_title('散点图')
plt.tight_layout()
plt.show()
# 训练曲线可视化
def plot_training(train_losses, val_losses, train_accs, val_accs):
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))
ax1.plot(train_losses, label='Train Loss')
ax1.plot(val_losses, label='Val Loss')
ax1.set_xlabel('Epoch')
ax1.set_ylabel('Loss')
ax1.set_title('Loss Curve')
ax1.legend()
ax2.plot(train_accs, label='Train Acc')
ax2.plot(val_accs, label='Val Acc')
ax2.set_xlabel('Epoch')
ax2.set_ylabel('Accuracy')
ax2.set_title('Accuracy Curve')
ax2.legend()
plt.tight_layout()
plt.show()
Pandas:数据分析
import pandas as pd
# 创建 DataFrame
data = {
'name': ['疾旋鼬', '捣蛋猫', '桃旋鼬', '火绒狐'],
'type': ['龙', '无', '龙', '火'],
'hp': [120, 80, 100, 90],
'attack': [85, 60, 75, 95],
}
df = pd.DataFrame(data)
# 查看数据
print(df.head()) # 前5行
print(df.describe()) # 统计摘要
print(df.info()) # 数据类型信息
# 筛选
dragon = df[df['type'] == '龙'] # 筛选龙属性
strong = df[df['attack'] > 80] # 筛选攻击>80
# 排序
sorted_df = df.sort_values('hp', ascending=False)
# 添加列
df['total'] = df['hp'] + df['attack']
# 分组统计
type_stats = df.groupby('type').mean(numeric_only=True)
# 读取/保存文件
# df = pd.read_csv('data.csv')
# df.to_csv('output.csv', index=False)
常用标准库与工具
os 与 pathlib:文件系统操作
import os
from pathlib import Path
# 获取当前工作目录
cwd = os.getcwd()
# 路径拼接
config_path = os.path.join(cwd, 'config', 'model.yaml')
# 使用 pathlib(更现代的方式)
p = Path('.')
config = p / 'config' / 'model.yaml'
print(config.exists()) # True / False
print(config.suffix) # '.yaml'
print(config.parent) # config/
# 列出目录下的文件
for f in Path('.').glob('*.py'):
print(f)
# 递归搜索
for f in Path('.').rglob('*.md'):
print(f)
collections:高级数据结构
from collections import defaultdict, Counter, OrderedDict
# defaultdict:带默认值的字典
word_count = defaultdict(int) # 默认值为 0
for word in ['疾旋鼬', '捣蛋猫', '疾旋鼬', '桃旋鼬', '疾旋鼬']:
word_count[word] += 1
print(dict(word_count)) # {'疾旋鼬': 3, '捣蛋猫': 1, '桃旋鼬': 1}
# Counter:计数器
words = ['疾旋鼬', '捣蛋猫', '疾旋鼬', '桃旋鼬', '疾旋鼬']
counter = Counter(words)
print(counter) # Counter({'疾旋鼬': 3, '捣蛋猫': 1, '桃旋鼬': 1})
print(counter.most_common(2)) # [('疾旋鼬', 3), ('捣蛋猫', 1)]
typing:类型注解
类型注解不影响运行时行为,但能让代码更易读,并被 IDE 和工具用于静态检查。
from typing import Optional, List, Tuple, Dict
def attention(
Q: torch.Tensor, # (batch, seq, d_model)
K: torch.Tensor, # (batch, seq, d_model)
V: torch.Tensor, # (batch, seq, d_model)
mask: Optional[torch.Tensor] = None, # 可选参数
) -> Tuple[torch.Tensor, torch.Tensor]:
"""返回 (注意力输出, 注意力权重)"""
d_k = Q.size(-1)
scores = Q @ K.transpose(-2, -1) / (d_k ** 0.5)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
weights = torch.softmax(scores, dim=-1)
return weights @ V, weights
# Python 3.10+ 更简洁的写法
def process(data: list[str]) -> dict[str, float]:
...
functools:函数工具
from functools import partial, lru_cache
# partial:创建偏函数(固定部分参数)
# 在 Transformer 中,常用 partial 来简化优化器的创建
from torch.optim import Adam
create_optimizer = partial(Adam, lr=1e-3, betas=(0.9, 0.98))
optimizer = create_optimizer(model.parameters())
# lru_cache:缓存函数结果(适用于纯函数)
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(100)) # 354224848179261915075(瞬间完成)
time 与 tqdm:计时与进度条
import time
from tqdm import tqdm
# 计时
start = time.time()
# ... 一些计算 ...
elapsed = time.time() - start
print(f"耗时: {elapsed:.2f}s")
# tqdm 进度条(训练循环中常用)
for epoch in tqdm(range(100), desc="Training"):
# 训练逻辑
time.sleep(0.01) # 模拟训练
random:随机数
import random
# 设置随机种子(确保可复现性)
random.seed(42)
torch.manual_seed(42)
np.random.seed(42)
if torch.cuda.is_available():
torch.cuda.manual_seed_all(42)
# 常用操作
print(random.random()) # [0, 1) 随机浮点数
print(random.randint(1, 100)) # [1, 100] 随机整数
print(random.choice([1, 2, 3])) # 随机选择一个
print(random.sample(range(100), 5)) # 无放回抽样5个
re:正则表达式
import re
text = "疾旋鼬的HP是120,桃旋鼬的HP是90"
# 查找数字
numbers = re.findall(r'\d+', text)
print(numbers) # ['120', '90']
# 匹配模式
pattern = r'(\w+)的HP是(\d+)'
matches = re.findall(pattern, text)
print(matches) # [('疾旋鼬', '120'), ('桃旋鼬', '90')]
# 替换
cleaned = re.sub(r'\d+', '***', text)
print(cleaned) # 疾旋鼬的HP是***,桃旋鼬的HP是***
附录:Python 语法速查表
| 语法 | 说明 | 示例 |
|---|---|---|
f"{x:.2f}" |
格式化字符串 | f"{3.14159:.2f}" → "3.14" |
*args |
可变位置参数 | def f(*args): print(args) |
**kwargs |
可变关键字参数 | def f(**kwargs): print(kwargs) |
[x for x in it] |
列表推导式 | [x**2 for x in range(5)] |
{k: v for k, v in it} |
字典推导式 | {x: x**2 for x in range(5)} |
{x for x in it} |
集合推导式 | {x % 3 for x in range(10)} |
a, b = b, a |
交换变量 | 利用元组解包 |
x if cond else y |
三元表达式 | a if a > 0 else 0 |
lambda x: expr |
匿名函数 | lambda x: x + 1 |
with open(...) as f: |
上下文管理器 | 自动关闭文件 |
try/except/finally |
异常处理 | 捕获并处理错误 |
@property |
属性装饰器 | 像属性一样调用方法 |
@staticmethod |
静态方法 | 不需要实例和 self |
@classmethod |
类方法 | 接收类本身作为 cls |
x @ y |
矩阵乘法 | PyTorch/NumPy 均支持 |
x.T |
转置 | 行列互换 |
x.shape |
形状 | (batch, seq, dim) |
x.view(...) |
重塑 | 改变形状,共享内存 |
x.reshape(...) |
重塑 | 改变形状,可能复制 |
x.unsqueeze(dim) |
增加维度 | (3,) → (1, 3) |
x.squeeze(dim) |
减少维度 | (1, 3) → (3,) |
x.item() |
转为 Python 数字 | 仅限标量张量 |
x.detach() |
从计算图分离 | 停止梯度追踪 |
我们也有一些关于安全的Python实用说明,您可以参见Python for Security。
今天让我emo的人问我怎么又emo了……嗯嘟……
2026.05.11