Files
task-3-1-3-Matrix-Fundament…/README.md

923 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 📚 3-1-2 衔接与拓展:从图像矩阵到文本向量
---
# 🎯 本章小结:图像处理的完整流程
在3-1-2中我们学习了图像数据的本质和基本操作。让我来详细回顾一下
## 图像的表示:为什么图像可以表示为矩阵?
当我们用相机拍摄一张照片时,图像是如何在计算机中存储的?
```
←——— 宽度W———→
┌─────────────────────────────────┐ ┐
│ 像素点(0,0) 像素点(0,1) ... │ │
│ 像素点(1,0) 像素点(1,1) ... │ │ 高度H
│ ... ... ... │ │
│ 像素点(H-1,0) ... ... │ │
└─────────────────────────────────┘ ┘
```
**关键点:**
- 图像由**像素点**组成,每个像素点有一个位置 (x, y)
- 每个像素点的**亮度**(灰度图)或**颜色**(彩色图)可以用数字表示
- 因此,整张图像可以用一个**数字矩阵**来表示
### 灰度图像 → 二维矩阵
```python
import numpy as np
# 假设一张 4x4 的灰度图像
image = np.array([
[255, 200, 150, 100],
[180, 140, 110, 80],
[120, 90, 60, 40],
[50, 30, 20, 10]
])
# 255 = 最亮0 = 最暗(黑)
```
**图像属性:**
- 形状:(H, W) = (4, 4)
- 每个元素0~255 的整数uint8类型
- 本质:一个二维数组/矩阵
### 彩色图像 → 三维矩阵
彩色图像用RGB三个通道表示颜色
```python
# 彩色图像:高度 x 宽度 x 3红、绿、蓝三通道
color_image = np.array([
[[255, 0, 0], [0, 255, 0], [0, 0, 255]], # 第1行像素红、绿、蓝
[[255, 255, 0], [0, 255, 255], [255, 0, 255]], # 第2行像素黄、青、紫
[[128, 128, 128],[100, 100, 100],[50, 50, 50]] # 第3行像素灰度
])
```
**图像属性:**
- 形状:(H, W, 3) = (3, 3, 3)
- 每个位置有3个数值分别代表R、G、B的分量
- 本质:一个三维数组/矩阵
---
# 📘 NumPy 快速入门
NumPy是Python中用于数值计算的核心库专门处理数组和矩阵。我们的图像处理和文本向量化都离不开它。
## 安装与导入
```bash
pip install numpy
```
```python
import numpy as np
```
## 创建数组
### 从列表创建
```python
# 一维数组(向量)
vec = np.array([1, 2, 3, 4, 5])
print(vec) # [1 2 3 4 5]
# 二维数组(矩阵)
mat = np.array([
[1, 2, 3],
[4, 5, 6]
])
print(mat)
# [[1 2 3]
# [4 5 6]]
```
### 快速创建特殊数组
```python
# 全0数组 - 常用于初始化
zeros = np.zeros((3, 4)) # 3行4列的全0矩阵
print(zeros)
# [[0. 0. 0. 0.]
# [0. 0. 0. 0.]
# [0. 0. 0. 0.]]
# 全1数组
ones = np.ones((2, 3))
# [[1. 1. 1.]
# [1. 1. 1.]]
# 指定范围内的数组
arr = np.arange(0, 10, 2) # 0到10步长2
print(arr) # [0 2 4 6 8]
# 等差数组
lin = np.linspace(0, 1, 5) # 0到1之间均匀取5个数
print(lin) # [0. 0.25 0.5 0.75 1. ]
```
## 查看数组属性
```python
a = np.array([[1, 2, 3], [4, 5, 6]])
print(a.ndim) # 维度数量 = 2是二维数组/矩阵)
print(a.shape) # 形状 = (2, 3) 2行3列
print(a.size) # 总元素数 = 6
print(a.dtype) # 数据类型 = int64
# 对于图像数据,常用 uint8无符号8位整数0-255
image = np.array([[255, 128], [64, 0]], dtype=np.uint8)
print(image.dtype) # uint8
```
## 数组索引和切片
### 基本索引
```python
a = np.array([10, 20, 30, 40, 50])
print(a[0]) # 第一个元素 = 10
print(a[-1]) # 最后一个元素 = 50
print(a[1:3]) # 第2到第3个元素 = [20, 30]
print(a[:3]) # 前3个元素 = [10, 20, 30]
print(a[2:]) # 第3个到最后 = [30, 40, 50]
```
### 二维数组索引
```python
mat = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
])
# 获取特定元素
print(mat[0, 0]) # 第1行第1列 = 1
print(mat[1, 2]) # 第2行第3列 = 7
print(mat[2, -1]) # 第3行最后一列 = 12
# 切片:[行, 列]
print(mat[0, :]) # 第1行所有列 = [1, 2, 3, 4]
print(mat[:, 1]) # 所有行第2列 = [2, 6, 10]
print(mat[0:2, 0:2]) # 取前2行前2列
# [[1 2]
# [5 6]]
# 负索引
print(mat[-1, :]) # 最后一行 = [9, 10, 11, 12]
```
## 数组运算
### 基础数学运算
```python
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
# 加减乘除(对应元素运算)
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) # [2 4 6]
print(a + 10) # [11 12 13]
print(a ** 2) # [1 4 9] 平方
```
### 矩阵乘法(重要!)
```python
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# 使用 @ 运算符 或 np.dot()
C = A @ B
print(C)
# [[19 22]
# [41 46]]
# 或者
C = np.dot(A, B)
print(C)
```
### 常用统计函数
```python
a = np.array([1, 2, 3, 4, 5])
print(np.sum(a)) # 求和 = 15
print(np.mean(a)) # 平均值 = 3.0
print(np.max(a)) # 最大值 = 5
print(np.min(a)) # 最小值 = 1
print(np.std(a)) # 标准差
print(np.argmax(a)) # 最大值的索引 = 4
print(np.argmin(a)) # 最小值的索引 = 0
# 对于二维数组,可以指定轴
mat = np.array([[1, 2, 3], [4, 5, 6]])
print(np.sum(mat, axis=0)) # 按列求和 = [5 7 9]
print(np.sum(mat, axis=1)) # 按行求和 = [6 15]
```
## 数组变形
```python
a = np.array([1, 2, 3, 4, 5, 6])
# 展平flatten: 多维变一维
print(a.flatten()) # [1 2 3 4 5 6]
# 改变形状reshape
b = a.reshape(2, 3) # 变成2行3列
print(b)
# [[1 2 3]
# [4 5 6]]
# 转置transpose: 行变列
print(b.T)
# [[1 4]
# [2 5]
# [3 6]]
# 翻转
c = np.array([[1, 2, 3], [4, 5, 6]])
print(np.fliplr(c)) # 左右翻转
# [[3 2 1]
# [6 5 4]]
print(np.flipud(c)) # 上下翻转
# [[4 5 6]
# [1 2 3]]
```
## 条件筛选
```python
a = np.array([10, 20, 30, 40, 50])
# 找出满足条件的元素
print(a > 25) # [False False False True True]
print(a[a > 25]) # [30 40 50] 只保留大于25的值
# 统计满足条件的数量
print(np.sum(a > 25)) # 3
# 替换值
b = np.where(a > 25, a, 0) # 大于25保留否则改成0
print(b) # [0 0 30 40 50]
```
## 范数与点积
```python
a = np.array([3, 4])
# L2范数欧几里得距离
norm = np.linalg.norm(a)
print(norm) # 5.0 (因为 3² + 4² = 25, √25 = 5)
# 点积dot product
b = np.array([1, 2])
dot = np.dot(a, b)
print(dot) # 3*1 + 4*2 = 11
# 余弦相似度
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
v1 = np.array([1, 0, 1])
v2 = np.array([1, 1, 0])
print(cosine_similarity(v1, v2)) # 约 0.707
```
## 复制与引用(重要概念)
```python
a = np.array([1, 2, 3])
b = a # 这是引用,不是复制!
b[0] = 100
print(a[0]) # 100a也跟着变了
# 如果想复制,应该用
c = a.copy()
c[0] = 999
print(a[0]) # 100a不变
```
---
## 图像处理的本质:矩阵运算
当我们对图像进行处理时,实际上是在对矩阵做运算:
### 1. 亮度和对比度调整
```python
# 亮度调整:所有像素值加一个常数
brighter = image + 30 # 变亮
darker = image - 30 # 变暗
# 对比度调整:像素值乘以一个系数
contrasted = image * 1.5 # 对比度增强
```
### 2. 裁剪和旋转(矩阵切片与变换)
```python
# 裁剪:取矩阵的一部分
top_left = image[0:2, 0:2] # 取左上角2x2区域
# 旋转:矩阵转置和翻转
rotated = np.transpose(image) # 转置
flipped = np.fliplr(image) # 左右翻转
```
### 3. 模糊和锐化(卷积核)
```python
# 平均模糊用一个3x3的"均值滤波器"
blur_kernel = np.array([
[1/9, 1/9, 1/9],
[1/9, 1/9, 1/9],
[1/9, 1/9, 1/9]
])
# 用这个kernel"扫过"整张图像,求局部平均值
# 锐化:增强边缘
sharpen_kernel = np.array([
[0, -1, 0],
[-1, 5, -1],
[0, -1, 0]
])
```
---
## 图像特征提取:从矩阵到向量
### 边缘检测 - 找出图像中的"突变"
```python
# 简单的边缘检测:检测相邻像素的亮度变化
sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
sobel_y = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]])
# 应用卷积核后的结果就是边缘图
# 边缘就是亮度发生突变的地方
```
### 从特征图到特征向量
```python
# 假设我们提取了边缘特征,得到一个特征图
feature_map = np.array([
[255, 200, 0, 50],
[180, 0, 100, 30],
[0, 90, 150, 0],
[50, 0, 30, 10]
])
# 把特征图"展平"成一个向量
feature_vector = feature_map.flatten()
# 结果: [255, 200, 0, 50, 180, 0, 100, 30, 0, 90, 150, 0, 50, 0, 30, 10]
# 这个向量就是这张图像的"特征表示"
# 可以用来比较两张图像是否相似
```
---
## 📊 图像数据的特点总结
| 特性 | 描述 | 示例 |
|------|------|------|
| **结构化** | 有固定的空间位置关系 | 像素(0,0)紧邻像素(0,1) |
| **局部性** | 相邻像素通常相关 | 同一物体的像素值相近 |
| **维度固定** | H×W×C维度明确 | 1920×1080×3 |
| **连续空间** | 像素间有物理意义 | 距离、角度、边缘 |
| **视觉直观** | 人眼可直接理解 | 看到图片就知道是什么 |
```
图像数据的本质:
📐 空间结构化数据
矩阵表示 (H × W × C)
空间关系很重要(邻居关系定义了边缘、纹理)
```
---
---
# 🚀 转折:如果数据不是图像呢?
我们来看一个实际的AI应用场景
## 案例:智能新闻推荐系统
假设你是一个新闻App的AI工程师需要做一个"相似文章推荐"功能:
```
用户点击了一篇文章《Python入门指南》
系统需要找到与它最相似的其他文章
```
**问题来了:文章是文字,不是像素,怎么处理?**
```
文字 → ??? → 向量 → 计算相似度 → 推荐结果
```
---
# 📖 从图像到文本:两种截然不同的数据
## 图像 vs 文本:数据结构对比
让我们来仔细对比一下图像和文本的本质区别:
### 图像数据的结构
```
图像:一幅画
┌─────────────────────────────────┐
│ ████████████████████████████ │
│ ████████████████████████████ │
│ ████████ 主体 ██████████████ │
│ ████████████████████████████ │
│ ████████████████████████████ │
└─────────────────────────────────┘
↓ 表示为矩阵 ↓
[
[255, 255, 200, 180, ...], # 第1行像素
[255, 255, 190, 170, ...], # 第2行像素
[200, 190, 150, 140, ...], # 第3行像素主体部分
...
]
```
**特点:**
- 每个像素有明确的位置 (x, y)
- 位置本身就携带信息(空间关系)
- 连续分布,相邻像素高度相关
- 可以用坐标直接访问:`pixel = image[100, 200]`
### 文本数据的结构
```
文本:一篇文章
"Python是一门广泛使用的高级编程语言"
↓ 表示为词语序列 ↓
["Python", "是", "一门", "广泛", "使用", "的", "高级", "编程", "语言"]
```
**特点:**
- 由离散的符号(字、词)组成
- 顺序重要,但位置是逻辑顺序而非空间位置
- 词语之间通过语法和语义关联,而非物理邻近
- 无法用坐标直接访问:`word = text[3]` 只是第3个词
---
## ⚠️ 文本和图像的本质区别(重点!)
这是理解文本数据处理的关键,请仔细阅读:
### 区别一:表示方式不同
| 维度 | 图像 | 文本 |
|------|------|------|
| **基本单位** | 像素 | 词语/字符 |
| **排列方式** | 二维空间网格 | 一维线性序列 |
| **位置意义** | 空间坐标 (x, y) 有物理含义 | 序列位置 (index) 是逻辑顺序 |
| **相邻关系** | 物理上相邻的像素通常相关 | 相邻的词可能在语法上无关 |
```python
# 图像:你可以说"pixel[10, 20]的邻居是[10,19], [10,21], [9,20], [11,20]"
# 这些邻居在空间上确实紧邻
# 文本:你可以说"第5个词的邻居是第4个和第6个词"
# 但这种"邻居"关系远不如图像的像素邻居那么紧密
```
### 区别二:语义组织方式不同
**图像:** 语义通过空间结构体现
```
一只猫在沙发上
┌─────────────────────┐
│ 猫(位置固定) │ ← 猫的空间位置+形状+颜色 = 猫的概念
│ ████ │
│ ██████ 沙发 │
│ ████████████████████│
└─────────────────────┘
```
**文本:** 语义通过符号组合体现
```
"一只猫坐在沙发上"
["一只", "猫", "坐", "在", "沙发", "上"]
↓ ↓ ↓ ↓ ↓ ↓
数量 主体 动作 位置 地点 方向
```
**关键洞察:**
- 图像中,"猫"这个概念是通过**像素的空间排列**直接体现的
- 文本中,"猫"这个概念是通过**符号"猫"**来代表的
- 文本必须经过"符号→含义"的映射,才能理解语义
### 区别三:维度特性不同
```
图像的维度:
- 固定且明确H × W × C
- 连续空间,维度代表物理尺寸
文本的维度:
- 不固定:可以任意长度的句子/文章
- 离散的符号序列
- "维度"通常指词汇表大小,而不是"长度"
```
### 区别四:相似性判断方式不同
**图像相似度:** 通常基于视觉特征
```python
# 两张猫的图片相似
# → 像素级别的特征相似(颜色分布、边缘形状、纹理)
# → 在特征空间中距离近
```
**文本相似度:** 基于语义内容
```python
# 两篇关于"猫"的新闻相似
# → 都包含"猫"这个词
# → 讨论的主题相关
# → 即使词语不同,含义可能相近
```
---
## 📝 文本数据处理的挑战
正因为文本和图像有这些本质区别,文本处理面临独特的挑战:
### 挑战1如何把符号变成数字
图像天然就是数字(像素值),但文本是符号:
```
"Python" → ??? → [?, ?, ?, ...]
```
### 挑战2如何处理"一词多义"
```
"苹果" 可以指:
- 一种水果(营养丰富)
- 一家公司Apple Inc.
- 手机品牌(苹果手机)
```
同一个词,不同上下文,意思完全不同。
### 挑战3如何处理语法和语义
```
"我爱你" vs "你爱我"
- 词语完全相同,只是顺序不同
- 意思完全相反
```
### 挑战4如何处理省略和指代
```
"小明喜欢编程。他觉得代码很有趣。"
- "他"指的是谁? → 需要理解指代关系
- "代码"是什么? → 需要理解上下文
```
---
## 🔑 核心:从符号到向量
无论挑战有多少,文本数据处理的核心目标只有一个:
```
文本(符号序列)→ 数值向量 → 可计算 → AI模型处理
```
这就是**文本向量化**,它是文本数据处理的第一步,也是最关键的一步。
---
# 📖 3-2 预告:文本数据处理
## 文本向量化的简单例子
假设我们有一个很小的词汇表(实际中有上万个词):
```python
# 词汇表
vocab = ["Python", "学习", "入门", "指南", "人工智能", "数据"]
# 两篇文章
doc1 = "Python学习入门指南"
doc2 = "Python人工智能数据"
doc3 = "数据数据分析"
```
**用词频BoW方法把文本变成向量**
```python
import numpy as np
def text_to_vector(text, vocab):
"""把文本转换为词频向量"""
words = text.split()
vector = np.zeros(len(vocab))
for i, word in enumerate(vocab):
vector[i] = words.count(word)
return vector
# 词汇表
vocab = ["Python", "学习", "入门", "指南", "人工智能", "数据"]
# 三篇文章的向量
v1 = text_to_vector("Python学习入门指南", vocab)
v2 = text_to_vector("Python人工智能数据", vocab)
v3 = text_to_vector("数据数据分析", vocab)
print("doc1向量:", v1) # [1, 1, 1, 1, 0, 0]
print("doc2向量:", v2) # [1, 0, 0, 0, 1, 1]
print("doc3向量:", v3) # [0, 0, 0, 0, 0, 2]
# 计算相似度(余弦相似度)
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print("\n=== 相似度计算 ===")
print(f"doc1 vs doc2: {cosine_similarity(v1, v2):.3f}") # 应该较低
print(f"doc1 vs doc1: {cosine_similarity(v1, v1):.3f}") # = 1.0(自己和自己)
```
**运行结果:**
```
doc1向量: [1. 1. 1. 1. 0. 0.]
doc2向量: [1. 0. 0. 0. 1. 1.]
doc3向量: [0. 0. 0. 0. 0. 2.]
=== 相似度计算 ===
doc1 vs doc2: 0.200
doc1 vs doc1: 1.000
```
**结论:**
- doc1和doc1完全相似当然
- doc1和doc2有一点点相似都有"Python"
- 这说明向量相似度可以反映文本的内容相似度
---
## 图像与文本的本质统一
尽管图像和文本有很大区别,但**最终的表示是统一的**
```
┌──────────────────────────────────────┐
│ 原始数据 │
│ (图像:像素 / 文本:字符/词语) │
└─────────────────┬────────────────────┘
┌──────────────────────────────────────┐
│ 数值化/向量化 │
│ 图像:矩阵 (H×W×C) │
│ 文本:向量 (N维) │
└─────────────────┬────────────────────┘
┌──────────────────────────────────────┐
│ 预处理 │
│ 图像:调整亮度/对比度/裁剪 │
│ 文本:分词/去停用词/标准化 │
└─────────────────┬────────────────────┘
┌──────────────────────────────────────┐
│ 特征提取 │
│ 图像:卷积核提取边缘/纹理/形状 │
│ 文本Embedding提取语义特征 │
└─────────────────┬────────────────────┘
┌──────────────────────────────────────┐
│ 特征向量 │
│ (统一的数值表示) │
└──────────────────────────────────────┘
```
> **核心思想:**
> - 图像是"**空间结构化**"的数据 → 矩阵
> - 文本是"**序列符号化**"的数据 → 向量
> - **最终都被转化为数值向量供AI模型处理**
> - 这就是深度学习的强大之处:**统一的表示,统一的处理**
---
## 📋 3-2 你将学到什么
| 知识点 | 内容 | 与图像的类比 |
|-------|------|-------------|
| **分词** | 把句子切成词语 | 就像图像分割,把整图切成区域 |
| **停用词过滤** | 去掉"的、了、在"等无用词 | 就像图像去噪,去掉干扰信息 |
| **词性标注** | 标出名词、动词、形容词... | 就像边缘检测,标记重要特征 |
| **实体识别** | 识别人名、地名、机构名 | 就像目标检测,定位关键元素 |
| **文本向量化** | 把文本变成数值向量 | **核心:和图像一样,最终都是向量!** |
| **相似度计算** | 用向量距离判断相似程度 | 就像特征匹配 |
---
# 📝 预习任务
在3-2开始之前请思考以下问题
1. **文字如何转成数字?**
- 一段文字"Python很有趣"如何在计算机中表示?
2. **词语如何切割?**
- 英文 "I love Python" 很好分词
- 中文 "我喜欢Python" 怎么分?
3. **哪些词是"噪音"**
- "的"、"了"、"在"这些词有多重要?
- 和图像去噪类比,文本需要"去"哪些词?
4. **文本和图像最大的区别是什么?**(思考题)
- 提示:从"结构"和"语义"两个角度思考
---
## 📝 课堂练习
学完了图像数据处理,现在来动手写代码吧!
### 练习1图像矩阵操作
假设有一张 3×3 的灰度图像,用 NumPy 表示:
```python
import numpy as np
image = np.array([
[100, 150, 200],
[80, 120, 180],
[60, 90, 140]
], dtype=np.uint8)
print("原图:")
print(image)
```
**请完成以下操作(写出代码并运行):**
1. **变暗20**让图像每个像素值减20
2. **裁剪左上角**:只保留 `image[0:2, 0:2]`2×2区域
3. **水平翻转**:使用 `np.fliplr()`
---
### 练习2看图写代码
以下代码定义了一个 4×4 的"图像矩阵"
```python
import numpy as np
img = np.array([
[255, 255, 0, 0 ],
[255, 255, 0, 0 ],
[0, 0, 255, 255],
[0, 0, 255, 255]
], dtype=np.uint8)
```
**请写出代码完成:**
1. 统计图像中白色像素255有多少个黑色像素0有多少个
2. 将图像水平翻转后打印出来
3. 将图像逆时针旋转90度提示使用转置和翻转
---
### 练习3特征向量计算
以下代码创建了两张小型"特征图"
```python
import numpy as np
# 假设这是从图像中提取的2个特征图
feature_map1 = np.array([[1, 0, 1], [0, 1, 0], [1, 0, 1]])
feature_map2 = np.array([[1, 1, 1], [1, 0, 0], [1, 0, 0]])
# 补全代码:将特征图展平为向量
vector1 = feature_map1.flatten() # 展平
vector2 = feature_map2.flatten() # 展平
print("vector1:", vector1)
print("vector2:", vector2)
```
**请补全代码并运行:**
1. 计算 `vector1``vector2` 的欧几里得距离
2. 计算 `vector1``vector2` 的余弦相似度(公式:`cos = np.dot(a,b) / (np.linalg.norm(a) * np.linalg.norm(b))`
---
### 练习4文本向量化预习挑战
下面是一个简单的文本向量化示例(用词频表示文本):
```python
import numpy as np
# 词汇表
vocab = ["Python", "学习", "数据", "人工智能", "编程"]
# 两句话
doc1 = "Python学习编程"
doc2 = "Python人工智能数据"
def text_to_vector(text, vocab):
words = text.split()
vector = np.zeros(len(vocab))
for i, word in enumerate(vocab):
vector[i] = words.count(word)
return vector
v1 = text_to_vector(doc1, vocab)
v2 = text_to_vector(doc2, vocab)
print("doc1向量:", v1)
print("doc2向量:", v2)
```
**请运行代码并回答:**
1. `doc1``doc2` 的向量分别是什么?
2. 补全代码:计算 `v1``v2` 的余弦相似度
3. 修改 `vocab`,加入词 `"机器"`,然后用 `doc3 = "机器学习"` 测试,看看向量是什么
---
### 🌟 附加题
运行以下代码理解为什么相似度不是1.0
```python
import numpy as np
def cosine_similarity(a, b):
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
v1 = np.array([1, 0, 1, 0])
v2 = np.array([2, 0, 0, 0])
print("v1:", v1)
print("v2:", v2)
print("相似度:", cosine_similarity(v1, v2))
# 思考如何修改v2让相似度变成1.0
# 提示:长度要一样
```
**选做:**
- 尝试用 `v2 = v1 * 2` 看看结果
- 再试试用 `v2 = v1 / np.linalg.norm(v1) * np.linalg.norm(v2)` 归一化后比较
---