Skip to content

Commit 18e1ccb

Browse files
author
shawn
committed
Sequence Alignment, DP
1 parent e82a290 commit 18e1ccb

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

Alignment.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
序列比对(Sequence Alignment)——只使用 NumPy 的实现(全局比对 / Needleman–Wunsch 思想)
4+
目标:对比两段英文句子(按“单词”为单位),找出最优对齐方式,并输出对齐结果与最终得分。
5+
6+
核心思路:
7+
1) 动态规划 DP 表:dp[i, j] 表示“前 i 个单词的句子1”和“前 j 个单词的句子2”的最优对齐分数。
8+
2) 转移:来自三种情况的最大值
9+
- 对角线(↘):把 words1[i-1] 与 words2[j-1] 对齐(匹配或错配)
10+
- 上方(↑):words1[i-1] 与 GAP 对齐(相当于句子2这边空一个)
11+
- 左方(←):GAP 与 words2[j-1] 对齐(相当于句子1这边空一个)
12+
3) 回溯:从右下角开始,根据 dp 的来源方向,反推出具体的对齐路径(即何处加 GAP)。
13+
14+
说明:
15+
- 评分规则可以自由调整:match=+2, mismatch=-1, gap=-2(示例)
16+
- 这里采用“全局对齐”(两边都要对齐到头和尾),适合长度接近、需要整体比对的场景。
17+
"""
18+
19+
import numpy as np
20+
21+
22+
def sequence_alignment_numpy(sentence1, sentence2):
23+
"""
24+
功能:对两个英文句子做“按单词”的全局序列比对,并打印最优对齐与得分。
25+
参数:
26+
sentence1, sentence2: 字符串,英文句子。
27+
"""
28+
# 1) 预处理:把句子按空格切分为“单词序列”(列表)
29+
words1 = sentence1.split()
30+
words2 = sentence2.split()
31+
32+
# 两个序列的长度(m 表示句子1的单词数,n 表示句子2的单词数)
33+
m, n = len(words1), len(words2)
34+
35+
# 2) 定义评分规则(可根据业务调整)
36+
match_score = 2 # 单词完全相同时的加分
37+
mismatch_score = -1 # 单词不同(错配)时的扣分
38+
gap_penalty = -2 # 引入空位 GAP(插入/删除)的惩罚
39+
40+
# 3) 初始化 DP 表((m+1) x (n+1)),类型为 int
41+
# dp[i, j] 含义:句子1前 i 个单词 与 句子2前 j 个单词 的最优对齐分数
42+
dp = np.zeros((m + 1, n + 1), dtype=int)
43+
44+
# 3.1) 初始化第一行:dp[0, j]
45+
# 含义:句子1为空串,与句子2前 j 个单词对齐 → 只能不断对句子2加 GAP 在句子1这边
46+
# 因此得分是 j 次 gap_penalty 的累加:0, -2, -4, ...
47+
dp[0, :] = np.arange(0, (n + 1) * gap_penalty, gap_penalty)
48+
49+
# 3.2) 初始化第一列:dp[i, 0]
50+
# 含义:句子2为空串,与句子1前 i 个单词对齐 → 只能不断对句子1加 GAP
51+
dp[:, 0] = np.arange(0, (m + 1) * gap_penalty, gap_penalty)
52+
53+
# 4) 填表(自顶向下,自左向右)
54+
# 对于每个 (i, j),考虑三种转移来源:↘、↑、←,取最大值
55+
for i in range(1, m + 1):
56+
for j in range(1, n + 1):
57+
# 当前要对齐的两个单词(注意下标 -1)
58+
a = words1[i - 1]
59+
b = words2[j - 1]
60+
61+
# 对角线情况(↘):把 a 与 b 对齐
62+
# 若相同 → match_score;不同 → mismatch_score
63+
diag = dp[i - 1, j - 1] + (match_score if a == b else mismatch_score)
64+
65+
# 上方情况(↑):把 a 与 GAP 对齐(等价于在句子2这一侧插入一个空位)
66+
up = dp[i - 1, j] + gap_penalty
67+
68+
# 左方情况(←):把 GAP 与 b 对齐(等价于在句子1这一侧插入一个空位)
69+
left = dp[i, j - 1] + gap_penalty
70+
71+
# 取三者中的最大值作为 dp[i, j]
72+
dp[i, j] = max(diag, up, left)
73+
74+
# 5) 回溯(Traceback):从右下角 (m, n) 出发,反向找出具体对齐路径
75+
aligned1, aligned2 = [], [] # 分别存放回溯构造出的“句子1对齐序列”和“句子2对齐序列”
76+
i, j = m, n
77+
78+
# 只要还有任一序列未回溯完,就继续
79+
while i > 0 or j > 0:
80+
current = dp[i, j] # 当前格子的最优得分
81+
82+
# 情况A:来自对角线(↘)——把 words1[i-1] 与 words2[j-1] 对齐
83+
# 这里分“匹配”和“错配”两种计分,但来源判断都属于对角线
84+
if i > 0 and j > 0 and (
85+
(words1[i - 1] == words2[j - 1] and current == dp[i - 1, j - 1] + match_score)
86+
or (words1[i - 1] != words2[j - 1] and current == dp[i - 1, j - 1] + mismatch_score)
87+
):
88+
aligned1.insert(0, words1[i - 1]) # 回溯是反向构造,所以用 insert(0, ...)
89+
aligned2.insert(0, words2[j - 1])
90+
i -= 1
91+
j -= 1
92+
93+
# 情况B:来自上方(↑)——words1[i-1] 与 GAP 对齐(句子2“缺了一个”)
94+
elif i > 0 and current == dp[i - 1, j] + gap_penalty:
95+
aligned1.insert(0, words1[i - 1])
96+
aligned2.insert(0, "_") # 这里用下划线 '_' 表示 GAP(空位)
97+
i -= 1
98+
99+
# 情况C:来自左方(←)——GAP 与 words2[j-1] 对齐(句子1“缺了一个”)
100+
else:
101+
aligned1.insert(0, "_")
102+
aligned2.insert(0, words2[j - 1])
103+
j -= 1
104+
105+
# 6) 打印结果(对齐后的两行与最终得分)
106+
print("\n=== Sequence Alignment Result ===")
107+
print("Sentence 1:", " ".join(aligned1))
108+
print("Sentence 2:", " ".join(aligned2))
109+
print("Final Alignment Score:", dp[m, n])
110+
111+
# 可选:打印 DP 矩阵形状与内容,方便调试/学习
112+
print("\nDP matrix shape:", dp.shape)
113+
print(dp)
114+
115+
116+
# === 演示入口 ===
117+
if __name__ == "__main__":
118+
# 两段英文句子(你可以自由修改)
119+
s1 = "I love learning computer science every day"
120+
s2 = "I love to learn computer science everyday"
121+
122+
# 调用主函数,执行序列比对并打印结果
123+
sequence_alignment_numpy(s1, s2)

0 commit comments

Comments
 (0)