marisming commited on
Commit
34a6d4f
·
verified ·
1 Parent(s): 321fe11

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +6 -0
  2. .ipynb_checkpoints/readme-checkpoint.md +39 -0
  3. .ipynb_checkpoints/readme_cn-checkpoint.ipynb +113 -0
  4. .ipynb_checkpoints/readme_cn-checkpoint.md +39 -0
  5. 1-data/.ipynb_checkpoints/1-get_sample_uniprot_sprot-checkpoint.ipynb +112 -0
  6. 1-data/.ipynb_checkpoints/2-get_non_homologous_pairs-checkpoint.ipynb +150 -0
  7. 1-data/.ipynb_checkpoints/3-get_homology_pairs-checkpoint.ipynb +260 -0
  8. 1-data/.ipynb_checkpoints/4-get_distant homology_pairs-checkpoint.ipynb +286 -0
  9. 1-data/1-get_sample_uniprot_sprot.ipynb +112 -0
  10. 1-data/2-get_non_homologous_pairs.ipynb +150 -0
  11. 1-data/3-get_homology_pairs.ipynb +260 -0
  12. 1-data/4-get_distant homology_pairs.ipynb +286 -0
  13. 1-data/mysql_part/.ipynb_checkpoints/1-insert_protein_to_db-checkpoint.ipynb +646 -0
  14. 1-data/mysql_part/.ipynb_checkpoints/2-get_non_homologous_pairs-checkpoint.py +146 -0
  15. 1-data/mysql_part/.ipynb_checkpoints/3-get_homology_pairs-checkpoint.py +153 -0
  16. 1-data/mysql_part/.ipynb_checkpoints/4-get_ft_protein_data_250a-checkpoint.ipynb +187 -0
  17. 1-data/mysql_part/.ipynb_checkpoints/4-get_sft_protein_data_full-checkpoint.ipynb +224 -0
  18. 1-data/mysql_part/.ipynb_checkpoints/5-upload_data_hf-checkpoint.ipynb +270 -0
  19. 1-data/mysql_part/1-insert_protein_to_db.ipynb +646 -0
  20. 1-data/mysql_part/2-get_non_homologous_pairs.py +146 -0
  21. 1-data/mysql_part/3-get_homology_pairs.py +153 -0
  22. 1-data/mysql_part/4-get_ft_protein_data_250a.ipynb +187 -0
  23. 1-data/mysql_part/4-get_sft_protein_data_full.ipynb +224 -0
  24. 1-data/mysql_part/5-upload_data_hf.ipynb +270 -0
  25. 1-data/mysql_part/protein_pair.sql +3 -0
  26. 1-data/mysql_part/protein_seq.sql +0 -0
  27. 1-data/protein_pair_20k_length_restricted_balanced.csv +0 -0
  28. 1-data/protein_pair_20k_no_length_limit_but_diff_limited.csv +3 -0
  29. 1-data/protein_pair_remote.csv +3 -0
  30. 1-data/sampled_10000_proteins.fasta +0 -0
  31. 2-gpt_ft_test_explain/.ipynb_checkpoints/1-gpt2_ft_en_test_protein_confusion-checkpoint.ipynb +534 -0
  32. 2-gpt_ft_test_explain/.ipynb_checkpoints/2-gpt2_test_protein-checkpoint.ipynb +287 -0
  33. 2-gpt_ft_test_explain/.ipynb_checkpoints/3-acc distribution-checkpoint.ipynb +0 -0
  34. 2-gpt_ft_test_explain/.ipynb_checkpoints/4-explain_layer_wise-checkpoint.ipynb +0 -0
  35. 2-gpt_ft_test_explain/.ipynb_checkpoints/4-explain_mutation awareness-checkpoint.ipynb +247 -0
  36. 2-gpt_ft_test_explain/.ipynb_checkpoints/4-explain_t_sne-checkpoint.ipynb +0 -0
  37. 2-gpt_ft_test_explain/.ipynb_checkpoints/gpt2_ft_en_test_protein_en-checkpoint.jsonl +100 -0
  38. 2-gpt_ft_test_explain/1-gpt2_ft_en_test_protein_confusion.ipynb +534 -0
  39. 2-gpt_ft_test_explain/2-gpt2_test_protein.ipynb +0 -0
  40. 2-gpt_ft_test_explain/3-acc distribution.ipynb +0 -0
  41. 2-gpt_ft_test_explain/4-explain_layer_wise.ipynb +0 -0
  42. 2-gpt_ft_test_explain/4-explain_mutation awareness.ipynb +247 -0
  43. 2-gpt_ft_test_explain/4-explain_t_sne.ipynb +0 -0
  44. 2-gpt_ft_test_explain/batch_run/.ipynb_checkpoints/gpt2_ft_en_test_protein-checkpoint.py +168 -0
  45. 2-gpt_ft_test_explain/batch_run/.ipynb_checkpoints/gpt2_test_protein-checkpoint.py +115 -0
  46. 2-gpt_ft_test_explain/batch_run/.ipynb_checkpoints/run_ft_test-checkpoint.sh +9 -0
  47. 2-gpt_ft_test_explain/batch_run/.ipynb_checkpoints/run_test-checkpoint.sh +9 -0
  48. 2-gpt_ft_test_explain/batch_run/.ipynb_checkpoints/stat_token_len-checkpoint.ipynb +169 -0
  49. 2-gpt_ft_test_explain/batch_run/gpt2_ft_en_test_protein.py +168 -0
  50. 2-gpt_ft_test_explain/batch_run/gpt2_test_protein.py +115 -0
.gitattributes CHANGED
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ 1-data/mysql_part/protein_pair.sql filter=lfs diff=lfs merge=lfs -text
37
+ 1-data/protein_pair_20k_no_length_limit_but_diff_limited.csv filter=lfs diff=lfs merge=lfs -text
38
+ 1-data/protein_pair_remote.csv filter=lfs diff=lfs merge=lfs -text
39
+ 3-llama_sft_test/Llama-3.1-8B-PAWS-En-Finetuned/tokenizer.json filter=lfs diff=lfs merge=lfs -text
40
+ 3-llama_sft_test/seq_a_render.png filter=lfs diff=lfs merge=lfs -text
41
+ 3-llama_sft_test/seq_b_render.png filter=lfs diff=lfs merge=lfs -text
.ipynb_checkpoints/readme-checkpoint.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Code Repository for the Paper
2
+
3
+ ### 1. Basic Data Preparation: `1-data`
4
+
5
+ - `1-get_sample_uniprot_sprot.ipynb`: Sample 10,000 protein sequences from UniProtKB/Swiss-Prot
6
+ - `2-get_non_homologous_pairs.ipynb`: Generate non-homologous protein sequence pairs
7
+ - `3-get_homology_pairs.ipynb`: Generate homologous protein sequence pairs
8
+ - `4-get_distant_homology_pairs.ipynb`: Generate distantly homologous protein sequence pairs
9
+ - `mysql_part`: Engineering implementation using MySQL tables to accelerate data processing; includes ready-to-import SQL dump files
10
+
11
+ ### 2. GPT-2 Fine-tuning and Interpretability Experiments: `2-gpt_ft_test_explain`
12
+
13
+ - `1-gpt2_ft_en_test_protein_confusion.ipynb`: Fine-tune GPT-2 on English PAWS-X dataset and evaluate on protein sequences (with confusion matrix)
14
+ - `2-gpt2_test_protein.ipynb`: Directly test pretrained GPT-2 on protein homology tasks (with confusion matrix)
15
+ - `3-acc_distribution.ipynb`: Accuracy distribution analysis for both fine-tuned and base models
16
+ - `4-explain_***`: Interpretability studies on cross-domain language capability transfer
17
+ - `batch_run`: Scripts for batch execution of experiments
18
+
19
+ ### 3. LLaMA-3 Fine-tuning and Evaluation: `3-llama_sft_test`
20
+
21
+ - `1-llama_sft_**`: Fine-tuning code for LLaMA-3.1 with various quantization strategies
22
+ - `2-llama_sft_test.py`: Evaluate fine-tuned models on protein homology classification
23
+ - `3-llama**`: Benchmark results using official pretrained and fine-tuned LLaMA models
24
+ - `4-*_standard_protein`: Performance of state-of-the-art (SOTA) large models on standard protein homology detection
25
+ - `5-*_remote_protein`: Performance of SOTA large models on **distant homology** detection
26
+ - `6-qwen3_explain-`: Chain-of-Thought (CoT)-based interpretability analysis
27
+
28
+ ### 4. BioPAWS Dataset Evaluation: `4-biopaws`
29
+
30
+ - `1-qwen3_dna`: DNA sequence homology classification
31
+ - `2-qwen3_dna_protein`: Assessing DNA–protein coding relationship
32
+ - `3-qwen3_dna_single`: Single DNA sequence classification
33
+ - `4-qwen3_protein_single`: Single protein sequence classification
34
+
35
+ ---
36
+
37
+ > **Note**: Wildcards (`*`) in original notebook filenames have been preserved or generalized for clarity while maintaining semantic meaning.
38
+
39
+ ---
.ipynb_checkpoints/readme_cn-checkpoint.ipynb ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "241cf91d-28de-4ad5-9ebd-2d978126c90a",
6
+ "metadata": {},
7
+ "source": [
8
+ "## 论文对应代码"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "markdown",
13
+ "id": "c175e730-5c72-48ee-b949-a64960287fbc",
14
+ "metadata": {},
15
+ "source": [
16
+ "### 1 基础数据:1-data"
17
+ ]
18
+ },
19
+ {
20
+ "cell_type": "markdown",
21
+ "id": "26a21b66-feaa-41ff-b26d-6d778b714aa6",
22
+ "metadata": {},
23
+ "source": [
24
+ "- 1-get_sample_uniprot_sprot.ipynb 获得10000条采样蛋白质数据\n",
25
+ "- 2-get_non_homologous_pairs.ipynb 获得非同源蛋白质序列\n",
26
+ "- 3-get_homology_pairs.ipynb 获得同源蛋白质序列\n",
27
+ "- 4-get_distant homology_pairs.ipynb 获得远程同源序列\n",
28
+ "- mysql_part 基于mysql表的工程化实现,主要解决速度问题,可直接导入mysql数据文件"
29
+ ]
30
+ },
31
+ {
32
+ "cell_type": "markdown",
33
+ "id": "278fe551-7c95-4430-afe0-04b55464e908",
34
+ "metadata": {},
35
+ "source": [
36
+ "### 2 gpt2相关的微调和可解释实验:2-gpt_ft_test_explain"
37
+ ]
38
+ },
39
+ {
40
+ "cell_type": "markdown",
41
+ "id": "290f505e-f989-40a3-8608-0c50b499ee1a",
42
+ "metadata": {},
43
+ "source": [
44
+ "- 1-gpt2_ft_en_test_protein_confusion.ipynb gpt2基于英文paws-x微调,测试蛋白质序列,提供混淆矩阵\n",
45
+ "- 2-gpt2_test_protein.ipynb gpt2 直接测试蛋白质序列,提供混淆矩阵\n",
46
+ "- 3-acc distribution.ipynb acc的分布统计,包括微调的和没有微调的\n",
47
+ "- 4-explain_*** 语言能力迁移的可解释性分析\n",
48
+ "- batch_run 批量运行代码"
49
+ ]
50
+ },
51
+ {
52
+ "cell_type": "markdown",
53
+ "id": "e9e6609b-69ab-40a8-bea6-fe4227d991eb",
54
+ "metadata": {},
55
+ "source": [
56
+ "### 3 llama3微调测试:3-llama_sft_test"
57
+ ]
58
+ },
59
+ {
60
+ "cell_type": "markdown",
61
+ "id": "33576550-5bed-4e59-a413-a18d468403e2",
62
+ "metadata": {},
63
+ "source": [
64
+ "- 1-llama_sft_** llama 3.1 微调代码,使用不同的量化\n",
65
+ "- 2-llama_sft_test.py 微调模型。测试蛋白质同源\n",
66
+ "- 3-llama**, 官方预训练模型和微调模型的测试结果\n",
67
+ "- 4-*_standard_protein , sota大模型常见同源蛋白质判定\n",
68
+ "- 5-*_remote_protein , sota大模型远程同源蛋白质判定\n",
69
+ "- 6-qwen3_explain-, 基于思维链的可解释分析"
70
+ ]
71
+ },
72
+ {
73
+ "cell_type": "markdown",
74
+ "id": "746cc328-16eb-418f-97f8-5d8bf7fc6140",
75
+ "metadata": {},
76
+ "source": [
77
+ "### biopaws数据集测试:4-biopaws"
78
+ ]
79
+ },
80
+ {
81
+ "cell_type": "markdown",
82
+ "id": "b3c4bb37-41a5-4593-9874-0ed74d1f0de2",
83
+ "metadata": {},
84
+ "source": [
85
+ "- 1-qwen3_dna dna同源判定\n",
86
+ "- 2-qwen3_dna_protein dna-蛋白质编码判定\n",
87
+ "- 3-qwen3_dna_single dna单序列分类问题\n",
88
+ "- 4-qwen3_protein_single 蛋白质单序列分类问题"
89
+ ]
90
+ }
91
+ ],
92
+ "metadata": {
93
+ "kernelspec": {
94
+ "display_name": "Python 3 (ipykernel)",
95
+ "language": "python",
96
+ "name": "python3"
97
+ },
98
+ "language_info": {
99
+ "codemirror_mode": {
100
+ "name": "ipython",
101
+ "version": 3
102
+ },
103
+ "file_extension": ".py",
104
+ "mimetype": "text/x-python",
105
+ "name": "python",
106
+ "nbconvert_exporter": "python",
107
+ "pygments_lexer": "ipython3",
108
+ "version": "3.12.3"
109
+ }
110
+ },
111
+ "nbformat": 4,
112
+ "nbformat_minor": 5
113
+ }
.ipynb_checkpoints/readme_cn-checkpoint.md ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## 论文对应代码
2
+
3
+ ### 1 基础数据: `1-data`
4
+
5
+ - `1-get_sample_uniprot_sprot.ipynb`:获得10000条采样蛋白质数据
6
+ - `2-get_non_homologous_pairs.ipynb`:获得非同源蛋白质序列
7
+ - `3-get_homology_pairs.ipynb`:获得同源蛋白质序列
8
+ - `4-get_distant homology_pairs.ipynb`:获得远程同源序列
9
+ - `mysql_part`:基于 MySQL 表的工程化实现,主要解决速度问题,可直接导入 MySQL 数据文件
10
+
11
+ ### 2 GPT2 相关的微调和可解释实验: `2-gpt_ft_test_explain`
12
+
13
+ - `1-gpt2_ft_en_test_protein_confusion.ipynb`:GPT2 基于英文 PAWS-X 微调,测试蛋白质序列,提供混淆矩阵
14
+ - `2-gpt2_test_protein.ipynb`:GPT2 直接测试蛋白质序列,提供混淆矩阵
15
+ - `3-acc distribution.ipynb`:准确率(acc)的分布统计,包括微调和未微调模型
16
+ - `4-explain_***`:语言能力迁移的可解释性分析
17
+ - `batch_run`:批量运行代码
18
+
19
+ ### 3 LLaMA3 微调测试:`3-llama_sft_test`
20
+
21
+ - `1-llama_sft_**`:LLaMA 3.1 微调代码,使用不同的量化策略
22
+ - `2-llama_sft_test.py`:微调模型,测试蛋白质同源性
23
+ - `3-llama**`:官方预训练模型和微调模型的测试结果
24
+ - `4-*_standard_protein`:SOTA 大模型在常见同源蛋白质判定任务上的表现
25
+ - `5-*_remote_protein`:SOTA 大模型在远程同源蛋白质判定任务上的表现
26
+ - `6-qwen3_explain-`:基于思维链(Chain-of-Thought)的可解释性分析
27
+
28
+ ### 4 BioPAWS 数据集测试:`4-biopaws`
29
+
30
+ - `1-qwen3_dna`:DNA 同源判定
31
+ - `2-qwen3_dna_protein`:DNA-蛋白质编码关系判定
32
+ - `3-qwen3_dna_single`:DNA 单序列分类问题
33
+ - `4-qwen3_protein_single`:蛋白质单序列分类问题
34
+
35
+ ---
36
+
37
+ > 注:原始 notebook 中的星号(`*`)和下划线命名已按语义还原为通配符或描述性文字,便于阅读。
38
+
39
+ 如需生成完整的 `.md` 文件或添加 GitHub 风格链接/图标,也可告知!
1-data/.ipynb_checkpoints/1-get_sample_uniprot_sprot-checkpoint.ipynb ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "6f2bac2f-0be6-4734-af0d-28eb3375ca80",
6
+ "metadata": {},
7
+ "source": [
8
+ "## 获得uniprot_sprot采样蛋白质序列"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": null,
14
+ "id": "89bfb02d-57c4-4aad-bfcd-8631dc52a7ca",
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "!wget ftp://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase/complete/uniprot_sprot.fasta.gz"
19
+ ]
20
+ },
21
+ {
22
+ "cell_type": "code",
23
+ "execution_count": null,
24
+ "id": "deab856a-465b-4a67-88d8-6d3af7b261f0",
25
+ "metadata": {},
26
+ "outputs": [],
27
+ "source": [
28
+ "!gunzip uniprot_sprot.fasta.gz"
29
+ ]
30
+ },
31
+ {
32
+ "cell_type": "code",
33
+ "execution_count": 1,
34
+ "id": "6cb6aabd-f0a9-4e91-ad65-d1409fd29881",
35
+ "metadata": {},
36
+ "outputs": [
37
+ {
38
+ "name": "stdout",
39
+ "output_type": "stream",
40
+ "text": [
41
+ "采样完成:10000 条序列\n"
42
+ ]
43
+ }
44
+ ],
45
+ "source": [
46
+ "from Bio import SeqIO\n",
47
+ "import random\n",
48
+ "\n",
49
+ "input_file = \"uniprot_sprot.fasta\"\n",
50
+ "output_file = \"sampled_10000_proteins.fasta\"\n",
51
+ "\n",
52
+ "# 读取所有序列(内存足够的话)\n",
53
+ "all_sequences = list(SeqIO.parse(input_file, \"fasta\"))\n",
54
+ "\n",
55
+ "# 随机抽样\n",
56
+ "sampled = random.sample(all_sequences, 10000)\n",
57
+ "\n",
58
+ "# 保存\n",
59
+ "SeqIO.write(sampled, output_file, \"fasta\")\n",
60
+ "print(f\"采样完成:{len(sampled)} 条序列\")"
61
+ ]
62
+ },
63
+ {
64
+ "cell_type": "code",
65
+ "execution_count": 2,
66
+ "id": "60f21e14-1c49-4bd3-971b-0e6e43b77a57",
67
+ "metadata": {},
68
+ "outputs": [
69
+ {
70
+ "name": "stdout",
71
+ "output_type": "stream",
72
+ "text": [
73
+ "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
74
+ "\u001b[0m"
75
+ ]
76
+ }
77
+ ],
78
+ "source": [
79
+ "!pip install -q Bio"
80
+ ]
81
+ },
82
+ {
83
+ "cell_type": "code",
84
+ "execution_count": null,
85
+ "id": "327a2186-e116-4cee-84a5-863ef11668d6",
86
+ "metadata": {},
87
+ "outputs": [],
88
+ "source": []
89
+ }
90
+ ],
91
+ "metadata": {
92
+ "kernelspec": {
93
+ "display_name": "Python 3 (ipykernel)",
94
+ "language": "python",
95
+ "name": "python3"
96
+ },
97
+ "language_info": {
98
+ "codemirror_mode": {
99
+ "name": "ipython",
100
+ "version": 3
101
+ },
102
+ "file_extension": ".py",
103
+ "mimetype": "text/x-python",
104
+ "name": "python",
105
+ "nbconvert_exporter": "python",
106
+ "pygments_lexer": "ipython3",
107
+ "version": "3.12.3"
108
+ }
109
+ },
110
+ "nbformat": 4,
111
+ "nbformat_minor": 5
112
+ }
1-data/.ipynb_checkpoints/2-get_non_homologous_pairs-checkpoint.ipynb ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "62923fb0-c04f-4c60-9262-4e505c0c2dbd",
6
+ "metadata": {},
7
+ "source": [
8
+ "## 获得非同源蛋白质序列"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": null,
14
+ "id": "3f6635fa-4f6f-4b91-9d55-6aafb477e227",
15
+ "metadata": {
16
+ "scrolled": true
17
+ },
18
+ "outputs": [
19
+ {
20
+ "name": "stdout",
21
+ "output_type": "stream",
22
+ "text": [
23
+ "Loading query sequences...\n",
24
+ "Loading full Swiss-Prot database... (this may take a minute)\n"
25
+ ]
26
+ }
27
+ ],
28
+ "source": [
29
+ "from Bio import SeqIO\n",
30
+ "from Bio.Align import PairwiseAligner\n",
31
+ "import csv\n",
32
+ "import random\n",
33
+ "\n",
34
+ "# 文件路径\n",
35
+ "QUERY_FASTA = \"sampled_10000_proteins.fasta\" # 100条查询蛋白序列\n",
36
+ "FULL_SWISSPROT = \"uniprot_sprot.fasta\" # 完整Swiss-Prot数据库\n",
37
+ "OUTPUT_CSV = \"non_homologous_pairs.csv\" # 输出负样本对\n",
38
+ "\n",
39
+ "# 参数设置(可作为论文方法部分说明)\n",
40
+ "MAX_NEG_PER_QUERY = 5 # 每条查询序列生成5个负样本\n",
41
+ "IDENTITY_THRESHOLD = 25.0 # 身份度 < 25% 视为非同源\n",
42
+ "LENGTH_TOLERANCE = 0.3 # 序列长度差异不超过30%\n",
43
+ "MIN_LENGTH = 50 # 最小序列长度过滤\n",
44
+ "MAX_TRIALS = 300 # 每条查询最多尝试300次随机采样(足够找到5个负样本)\n",
45
+ "\n",
46
+ "# 全局比对参数(标准Needleman-Wunsch)\n",
47
+ "aligner = PairwiseAligner(mode='global')\n",
48
+ "aligner.match_score = 2\n",
49
+ "aligner.mismatch_score = -1\n",
50
+ "aligner.gap_score = -5\n",
51
+ "aligner.extend_gap_score = -1\n",
52
+ "\n",
53
+ "# 加载数据\n",
54
+ "print(\"Loading query sequences...\")\n",
55
+ "query_records = [r for r in SeqIO.parse(QUERY_FASTA, \"fasta\") if len(r.seq) >= MIN_LENGTH]\n",
56
+ "\n",
57
+ "print(\"Loading full Swiss-Prot database... (this may take a minute)\")\n",
58
+ "full_records = [r for r in SeqIO.parse(FULL_SWISSPROT, \"fasta\") if len(r.seq) >= MIN_LENGTH]\n",
59
+ "print(f\"Loaded {len(full_records)} sequences from Swiss-Prot.\")\n",
60
+ "\n",
61
+ "query_ids = {r.id for r in query_records}\n",
62
+ "\n",
63
+ "# 生成负样本\n",
64
+ "with open(OUTPUT_CSV, \"w\", newline=\"\", encoding=\"utf-8\") as f:\n",
65
+ " writer = csv.writer(f)\n",
66
+ " writer.writerow([\"sentence1\", \"sentence2\", \"label\"])\n",
67
+ "\n",
68
+ " for i, query_rec in enumerate(query_records, 1):\n",
69
+ " q_seq = str(query_rec.seq)\n",
70
+ " q_len = len(q_seq)\n",
71
+ " q_id = query_rec.id\n",
72
+ "\n",
73
+ " print(f\"[{i}/{len(query_records)}] Processing {q_id} (length={q_len})\")\n",
74
+ "\n",
75
+ " found = 0\n",
76
+ " trials = 0\n",
77
+ "\n",
78
+ " while found < MAX_NEG_PER_QUERY and trials < MAX_TRIALS:\n",
79
+ " trials += 1\n",
80
+ " cand_rec = random.choice(full_records)\n",
81
+ "\n",
82
+ " # 避免选到自身(概率极小)\n",
83
+ " if cand_rec.id == q_id:\n",
84
+ " continue\n",
85
+ "\n",
86
+ " c_seq = str(cand_rec.seq)\n",
87
+ " c_len = len(c_seq)\n",
88
+ "\n",
89
+ " # 长度相似性过滤\n",
90
+ " if abs(q_len - c_len) / ((q_len + c_len) / 2) > LENGTH_TOLERANCE:\n",
91
+ " continue\n",
92
+ "\n",
93
+ " # 全局序列比对\n",
94
+ " alignments = aligner.align(q_seq, c_seq)\n",
95
+ " if len(alignments) == 0:\n",
96
+ " continue\n",
97
+ " alignment = alignments[0]\n",
98
+ "\n",
99
+ " # 计算身份度(不计gap)\n",
100
+ " identical = sum(a == b and a != '-' and b != '-' \n",
101
+ " for a, b in zip(str(alignment[0]), str(alignment[1])))\n",
102
+ " aligned_len = len(str(alignment[0])) - str(alignment[0]).count('-') - str(alignment[1]).count('-')\n",
103
+ " identity = 100.0 * identical / aligned_len if aligned_len > 0 else 0\n",
104
+ "\n",
105
+ " if identity < IDENTITY_THRESHOLD:\n",
106
+ " writer.writerow([q_seq, c_seq, 0])\n",
107
+ " found += 1\n",
108
+ "\n",
109
+ " print(f\" Found {found} negative pairs (after {trials} trials)\")\n",
110
+ "\n",
111
+ "print(f\"\\nDone! Negative samples saved to {OUTPUT_CSV}\")"
112
+ ]
113
+ },
114
+ {
115
+ "cell_type": "code",
116
+ "execution_count": null,
117
+ "id": "8c031fd5-ecac-4d50-b8e7-944d37172221",
118
+ "metadata": {},
119
+ "outputs": [],
120
+ "source": [
121
+ "\n",
122
+ "\n",
123
+ "\n",
124
+ "\n",
125
+ " "
126
+ ]
127
+ }
128
+ ],
129
+ "metadata": {
130
+ "kernelspec": {
131
+ "display_name": "Python 3 (ipykernel)",
132
+ "language": "python",
133
+ "name": "python3"
134
+ },
135
+ "language_info": {
136
+ "codemirror_mode": {
137
+ "name": "ipython",
138
+ "version": 3
139
+ },
140
+ "file_extension": ".py",
141
+ "mimetype": "text/x-python",
142
+ "name": "python",
143
+ "nbconvert_exporter": "python",
144
+ "pygments_lexer": "ipython3",
145
+ "version": "3.12.3"
146
+ }
147
+ },
148
+ "nbformat": 4,
149
+ "nbformat_minor": 5
150
+ }
1-data/.ipynb_checkpoints/3-get_homology_pairs-checkpoint.ipynb ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "cc2589ac-f8bb-4052-9cdf-c2d00b36ec2f",
6
+ "metadata": {},
7
+ "source": [
8
+ "## 获得同源序列"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": null,
14
+ "id": "aa596805-2075-4fcf-8f34-d24eaae64f68",
15
+ "metadata": {},
16
+ "outputs": [
17
+ {
18
+ "name": "stdout",
19
+ "output_type": "stream",
20
+ "text": [
21
+ "总共 100 条序列,开始处理...\n",
22
+ "[1/100] 处理序列: sp|Q3C1K4|CLPP_NICSY (长度: 196)\n",
23
+ " 正在运行BLAST...\n",
24
+ " 找到同源: sp|P12210.1| (identity: 100.0%, e-value: 2e-144)\n",
25
+ " 找到同源: sp|A0A360.1| (identity: 97.4%, e-value: 5e-142)\n",
26
+ " 找到同源: sp|Q9M3K5.1| (identity: 96.9%, e-value: 6e-141)\n",
27
+ " 找到同源: sp|Q09FT6.1| (identity: 98.0%, e-value: 1e-140)\n",
28
+ " 找到同源: sp|Q49KX3.1| (identity: 95.4%, e-value: 3e-140)\n",
29
+ "[2/100] 处理序列: sp|Q5B3K6|RNY1_EMENI (长度: 417)\n",
30
+ " 正在运行BLAST...\n",
31
+ " 找到同源: sp|Q5B3K6.1| (identity: 100.0%, e-value: 0e+00)\n",
32
+ " 找到同源: sp|Q4WXZ5.2| (identity: 65.6%, e-value: 0e+00)\n",
33
+ " 找到同源: sp|P24657.1| (identity: 55.6%, e-value: 2e-88)\n",
34
+ " 找到同源: sp|P10281.2| (identity: 50.2%, e-value: 2e-84)\n",
35
+ " 找到同源: sp|P19791.1| (identity: 49.8%, e-value: 4e-75)\n",
36
+ "[3/100] 处理序列: sp|Q6MM37|T23O_BDEBA (长度: 359)\n",
37
+ " 正在运行BLAST...\n",
38
+ " 找到同源: sp|Q6MM37.1| (identity: 100.0%, e-value: 0e+00)\n",
39
+ " 找到同源: sp|B4RUH2.1| (identity: 48.0%, e-value: 4e-125)\n",
40
+ " 找到同源: sp|Q55DB4.1| (identity: 44.5%, e-value: 4e-113)\n",
41
+ " 找到同源: sp|Q17P71.1| (identity: 45.7%, e-value: 2e-99)\n",
42
+ " 找到同源: sp|Q5EBG2.1| (identity: 45.7%, e-value: 5e-98)\n",
43
+ "[4/100] 处理序列: sp|O07319|MRAZ_STAAU (长度: 144)\n",
44
+ " 正在运行BLAST...\n",
45
+ " 找到同源: sp|O07319.1| (identity: 100.0%, e-value: 6e-105)\n",
46
+ " 找到同源: sp|A5IS64.1| (identity: 96.5%, e-value: 1e-99)\n",
47
+ " 找到同源: sp|Q5HQ14.1| (identity: 93.8%, e-value: 2e-97)\n",
48
+ " 找到同源: sp|Q4L5M9.1| (identity: 92.4%, e-value: 2e-96)\n",
49
+ " 找到同源: sp|Q49WW0.1| (identity: 90.3%, e-value: 1e-94)\n",
50
+ "[5/100] 处理序列: sp|Q9CR48|DRAM2_MOUSE (长度: 267)\n",
51
+ " 正在运行BLAST...\n",
52
+ " 找到同源: sp|Q9CR48.1| (identity: 100.0%, e-value: 0e+00)\n",
53
+ " 找到同源: sp|Q5BK09.1| (identity: 97.4%, e-value: 0e+00)\n",
54
+ " 找到同源: sp|Q6UX65.1| (identity: 86.4%, e-value: 3e-167)\n",
55
+ " 找到同源: sp|Q3ZC48.1| (identity: 86.4%, e-value: 7e-163)\n",
56
+ " 找到同源: sp|Q8N682.1| (identity: 41.4%, e-value: 3e-62)\n",
57
+ "[6/100] 处理序列: sp|F8DIF2|URDA_STREP (长度: 803)\n",
58
+ " 正在运行BLAST...\n",
59
+ " 找到同源: sp|F8DIF2.1| (identity: 100.0%, e-value: 0e+00)\n",
60
+ " 找到同源: sp|Q8DW88.1| (identity: 76.8%, e-value: 0e+00)\n",
61
+ " 找到同源: sp|F9UNH3.1| (identity: 68.9%, e-value: 0e+00)\n",
62
+ " 找到同源: sp|A0A1R4LHH9.1| (identity: 67.1%, e-value: 0e+00)\n",
63
+ " 找到同源: sp|B2GCE0.1| (identity: 61.5%, e-value: 0e+00)\n",
64
+ "[7/100] 处理序列: sp|Q6MUD1|EX7L_MYCMS (长度: 469)\n",
65
+ " 正在运行BLAST...\n",
66
+ " 找到同源: sp|Q6MUD1.1| (identity: 100.0%, e-value: 0e+00)\n",
67
+ " 找到同源: sp|Q6F1D5.1| (identity: 56.2%, e-value: 3e-124)\n",
68
+ " 找到同源: sp|A7GSJ8.1| (identity: 33.5%, e-value: 3e-82)\n",
69
+ " 找到同源: sp|C3LJV4.1| (identity: 33.5%, e-value: 4e-82)\n",
70
+ " 找到同源: sp|A0RIH2.1| (identity: 33.5%, e-value: 4e-82)\n",
71
+ "[8/100] 处理序列: sp|P34109|MYOD_DICDI (长度: 1109)\n",
72
+ " 正在运行BLAST...\n",
73
+ " 找到同源: sp|P34109.2| (identity: 100.0%, e-value: 0e+00)\n",
74
+ " 找到同源: sp|P10569.1| (identity: 59.0%, e-value: 0e+00)\n",
75
+ " 找到同源: sp|P19706.2| (identity: 48.8%, e-value: 0e+00)\n",
76
+ " 找到同源: sp|P19706.2| (identity: 53.6%, e-value: 2e-11)\n",
77
+ " 找到同源: sp|P34092.2| (identity: 48.3%, e-value: 0e+00)\n",
78
+ "[9/100] 处理序列: sp|Q7U564|LGT_PARMW (长度: 278)\n",
79
+ " 正在运行BLAST...\n"
80
+ ]
81
+ },
82
+ {
83
+ "name": "stderr",
84
+ "output_type": "stream",
85
+ "text": [
86
+ "/root/miniconda3/lib/python3.12/site-packages/Bio/Blast/NCBIWWW.py:275: BiopythonWarning: BLAST request N1VE8FMV016 is taking longer than 10 minutes, consider re-issuing it\n",
87
+ " warnings.warn(\n"
88
+ ]
89
+ },
90
+ {
91
+ "name": "stdout",
92
+ "output_type": "stream",
93
+ "text": [
94
+ " 找到同源: sp|Q7U564.1| (identity: 100.0%, e-value: 0e+00)\n",
95
+ " 找到同源: sp|Q3AWL5.1| (identity: 79.5%, e-value: 4e-154)\n",
96
+ " 找到同源: sp|Q7V652.1| (identity: 69.1%, e-value: 6e-138)\n",
97
+ " 找到同源: sp|A2C7F4.1| (identity: 68.4%, e-value: 3e-137)\n",
98
+ " 找到同源: sp|A2C0R7.1| (identity: 62.7%, e-value: 6e-125)\n",
99
+ "[10/100] 处理序列: sp|A9N941|ERA_COXBR (长度: 295)\n",
100
+ " 正在运行BLAST...\n"
101
+ ]
102
+ },
103
+ {
104
+ "name": "stderr",
105
+ "output_type": "stream",
106
+ "text": [
107
+ "/root/miniconda3/lib/python3.12/site-packages/Bio/Blast/NCBIWWW.py:275: BiopythonWarning: BLAST request N1WAWWS3016 is taking longer than 10 minutes, consider re-issuing it\n",
108
+ " warnings.warn(\n"
109
+ ]
110
+ },
111
+ {
112
+ "name": "stdout",
113
+ "output_type": "stream",
114
+ "text": [
115
+ " 找到同源: sp|A9N941.1| (identity: 100.0%, e-value: 0e+00)\n",
116
+ " 找到同源: sp|B6IZ00.1| (identity: 99.3%, e-value: 0e+00)\n",
117
+ " 找到同源: sp|A9KFA1.1| (identity: 99.0%, e-value: 0e+00)\n",
118
+ " 找到同源: sp|A5UFI7.2| (identity: 55.5%, e-value: 3e-113)\n",
119
+ " 找到同源: sp|Q9CPH8.1| (identity: 53.1%, e-value: 1e-110)\n",
120
+ "[11/100] 处理序列: sp|P41874|FAR3_PANRE (长度: 7)\n",
121
+ " 正在运行BLAST...\n"
122
+ ]
123
+ },
124
+ {
125
+ "name": "stderr",
126
+ "output_type": "stream",
127
+ "text": [
128
+ "/root/miniconda3/lib/python3.12/site-packages/Bio/Blast/NCBIWWW.py:275: BiopythonWarning: BLAST request N1XDCAVV016 is taking longer than 10 minutes, consider re-issuing it\n",
129
+ " warnings.warn(\n"
130
+ ]
131
+ },
132
+ {
133
+ "ename": "KeyboardInterrupt",
134
+ "evalue": "",
135
+ "output_type": "error",
136
+ "traceback": [
137
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
138
+ "\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)",
139
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 34\u001b[39m\n\u001b[32m 31\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 32\u001b[39m \u001b[38;5;66;03m# 运行在线BLAST(对swissprot数据库)\u001b[39;00m\n\u001b[32m 33\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33m 正在运行BLAST...\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m---> \u001b[39m\u001b[32m34\u001b[39m result_handle = \u001b[43mNCBIWWW\u001b[49m\u001b[43m.\u001b[49m\u001b[43mqblast\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 35\u001b[39m \u001b[43m \u001b[49m\u001b[43mprogram\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mblastp\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[32m 36\u001b[39m \u001b[43m \u001b[49m\u001b[43mdatabase\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mswissprot\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[32m 37\u001b[39m \u001b[43m \u001b[49m\u001b[43msequence\u001b[49m\u001b[43m=\u001b[49m\u001b[43mquery_seq\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 38\u001b[39m \u001b[43m \u001b[49m\u001b[43mexpect\u001b[49m\u001b[43m=\u001b[49m\u001b[43me_value_thresh\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# 直接设置expect过滤\u001b[39;49;00m\n\u001b[32m 39\u001b[39m \u001b[43m \u001b[49m\u001b[43mhitlist_size\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m20\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# 先取多一点,后面再过滤identity\u001b[39;49;00m\n\u001b[32m 40\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 42\u001b[39m \u001b[38;5;66;03m# 解析结果\u001b[39;00m\n\u001b[32m 43\u001b[39m blast_records = NCBIXML.parse(result_handle)\n",
140
+ "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/lib/python3.12/site-packages/Bio/Blast/NCBIWWW.py:264\u001b[39m, in \u001b[36mqblast\u001b[39m\u001b[34m(program, database, sequence, url_base, auto_format, composition_based_statistics, db_genetic_code, endpoints, entrez_query, expect, filter, gapcosts, genetic_code, hitlist_size, i_thresh, layout, lcase_mask, matrix_name, nucl_penalty, nucl_reward, other_advanced, perc_ident, phi_pattern, query_file, query_believe_defline, query_from, query_to, searchsp_eff, service, threshold, ungapped_alignment, word_size, short_query, alignments, alignment_view, descriptions, entrez_links_new_window, expect_low, expect_high, format_entrez_query, format_object, format_type, ncbi_gi, results_file, show_overview, megablast, template_type, template_length, username, password)\u001b[39m\n\u001b[32m 262\u001b[39m wait = qblast.previous + delay - current\n\u001b[32m 263\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m wait > \u001b[32m0\u001b[39m:\n\u001b[32m--> \u001b[39m\u001b[32m264\u001b[39m \u001b[43mtime\u001b[49m\u001b[43m.\u001b[49m\u001b[43msleep\u001b[49m\u001b[43m(\u001b[49m\u001b[43mwait\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 265\u001b[39m qblast.previous = current + wait\n\u001b[32m 266\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n",
141
+ "\u001b[31mKeyboardInterrupt\u001b[39m: "
142
+ ]
143
+ },
144
+ {
145
+ "name": "stderr",
146
+ "output_type": "stream",
147
+ "text": [
148
+ "\n",
149
+ "KeyboardInterrupt\n",
150
+ "\n"
151
+ ]
152
+ }
153
+ ],
154
+ "source": [
155
+ "from Bio import SeqIO\n",
156
+ "from Bio.Blast import NCBIWWW\n",
157
+ "from Bio.Blast import NCBIXML\n",
158
+ "import csv\n",
159
+ "import time\n",
160
+ "import random\n",
161
+ "\n",
162
+ "# 输入FASTA文件\n",
163
+ "fasta_file = \"sampled_100000_proteins.fasta\"\n",
164
+ "output_csv = \"homology_pairs.csv\"\n",
165
+ "\n",
166
+ "# 参数\n",
167
+ "max_homologs_per_query = 5 # 每条取1-5条同源\n",
168
+ "e_value_thresh = 1e-10 # e-value阈值,越小越严格\n",
169
+ "identity_thresh = 30.0 # 最低身份度百分比(>30%通常视为同源)\n",
170
+ "\n",
171
+ "# 打开CSV写入\n",
172
+ "with open(output_csv, \"w\", newline=\"\", encoding=\"utf-8\") as csvfile:\n",
173
+ " writer = csv.writer(csvfile)\n",
174
+ " writer.writerow([\"sentence1\", \"sentence2\", \"label\"])\n",
175
+ "\n",
176
+ " # 读取所有序列\n",
177
+ " sequences = list(SeqIO.parse(fasta_file, \"fasta\"))\n",
178
+ " print(f\"总共 {len(sequences)} 条序列,开始处理...\")\n",
179
+ "\n",
180
+ " for idx, record in enumerate(sequences, 1):\n",
181
+ " query_seq = str(record.seq)\n",
182
+ " query_id = record.id\n",
183
+ " print(f\"[{idx}/{len(sequences)}] 处理序列: {query_id} (长度: {len(query_seq)})\")\n",
184
+ "\n",
185
+ " try:\n",
186
+ " # 运行在线BLAST(对swissprot数据库)\n",
187
+ " print(\" 正在运行BLAST...\")\n",
188
+ " result_handle = NCBIWWW.qblast(\n",
189
+ " program=\"blastp\", \n",
190
+ " database=\"swissprot\", \n",
191
+ " sequence=query_seq,\n",
192
+ " expect=e_value_thresh, # 直接设置expect过滤\n",
193
+ " hitlist_size=20 # 先取多一点,后面再过滤identity\n",
194
+ " )\n",
195
+ "\n",
196
+ " # 解析结果\n",
197
+ " blast_records = NCBIXML.parse(result_handle)\n",
198
+ " blast_record = next(blast_records, None) # 只有一个record\n",
199
+ "\n",
200
+ " homolog_count = 0\n",
201
+ " if blast_record:\n",
202
+ " for alignment in blast_record.alignments:\n",
203
+ " hit_id = alignment.hit_id # 如 sp|P12345|XXX\n",
204
+ " hit_seq = \"\" # 我们稍后从HSP取对齐部分,但这里用完整hit序列(NCBI返回的是对齐,但我们取第一个HSP的hit序列)\n",
205
+ " \n",
206
+ " for hsp in alignment.hsps:\n",
207
+ " hit_seq = hsp.sbjct # 对齐的hit部分(保守)\n",
208
+ " identity = (hsp.identities / hsp.align_length) * 100\n",
209
+ "\n",
210
+ " if identity >= identity_thresh:\n",
211
+ " # 排除自身(如果query本身在数据库中)\n",
212
+ " if hit_id.split(\"|\")[1] == query_id: # UniProt ID匹配\n",
213
+ " continue\n",
214
+ "\n",
215
+ " # 写入一对\n",
216
+ " writer.writerow([query_seq, hsp.sbjct, 1]) # 用对齐序列作为sentence2,更公平(长度一致)\n",
217
+ " # 如果想用完整hit序列:需额外请求,但复杂,这里用对齐部分\n",
218
+ " homolog_count += 1\n",
219
+ " print(f\" 找到同源: {hit_id} (identity: {identity:.1f}%, e-value: {hsp.expect:.0e})\")\n",
220
+ "\n",
221
+ " if homolog_count >= max_homologs_per_query:\n",
222
+ " break\n",
223
+ " if homolog_count >= max_homologs_per_query:\n",
224
+ " break\n",
225
+ "\n",
226
+ " if homolog_count == 0:\n",
227
+ " print(\" 无合格同源序列。\")\n",
228
+ "\n",
229
+ " except Exception as e:\n",
230
+ " print(f\" BLAST错误: {e},跳过此序列。\")\n",
231
+ "\n",
232
+ " # 礼貌等待,避免NCBI限速\n",
233
+ " time.sleep(3 + random.uniform(0, 3)) # 3-6秒随机间隔\n",
234
+ "\n",
235
+ "print(f\"完成!正样本对已保存到 {output_csv}\")"
236
+ ]
237
+ }
238
+ ],
239
+ "metadata": {
240
+ "kernelspec": {
241
+ "display_name": "Python 3 (ipykernel)",
242
+ "language": "python",
243
+ "name": "python3"
244
+ },
245
+ "language_info": {
246
+ "codemirror_mode": {
247
+ "name": "ipython",
248
+ "version": 3
249
+ },
250
+ "file_extension": ".py",
251
+ "mimetype": "text/x-python",
252
+ "name": "python",
253
+ "nbconvert_exporter": "python",
254
+ "pygments_lexer": "ipython3",
255
+ "version": "3.12.3"
256
+ }
257
+ },
258
+ "nbformat": 4,
259
+ "nbformat_minor": 5
260
+ }
1-data/.ipynb_checkpoints/4-get_distant homology_pairs-checkpoint.ipynb ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "acaa7453-aa81-483c-8a98-8d98ba29855e",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "# import os\n",
11
+ "\n",
12
+ "# # 设置环境变量\n",
13
+ "# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'\n",
14
+ "\n",
15
+ "# # 打印环境变量以确认设置成功\n",
16
+ "# print(os.environ.get('HF_ENDPOINT'))\n",
17
+ "\n",
18
+ "import subprocess\n",
19
+ "import os\n",
20
+ "\n",
21
+ "result = subprocess.run('bash -c \"source /etc/network_turbo && env | grep proxy\"', shell=True, capture_output=True, text=True)\n",
22
+ "output = result.stdout\n",
23
+ "for line in output.splitlines():\n",
24
+ " if '=' in line:\n",
25
+ " var, value = line.split('=', 1)\n",
26
+ " os.environ[var] = value"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": 3,
32
+ "id": "6bd44c1c-8414-47b6-ba89-fdc5db3c5c44",
33
+ "metadata": {},
34
+ "outputs": [
35
+ {
36
+ "name": "stdout",
37
+ "output_type": "stream",
38
+ "text": [
39
+ "正在加载 filtered-SCOPe-2.08 数据集...\n",
40
+ "总序列数: 14535\n",
41
+ "示例数据字段: dict_keys(['id', 'primary', 'protein_length', 'class', 'fold', 'super_family', 'family', 'description'])\n",
42
+ "示例数据: {'id': 'd1dlwa_', 'primary': 'slfeqlggqaavqavtaqfyaniqadatvatffngidmpnqtnktaaflcaalggpnawtgrnlkevhanmgvsnaqfttvighlrsaltgagvaaalveqtvavaetvrgdvvtv', 'protein_length': 116, 'class': 'a', 'fold': 'a.1', 'super_family': 'a.1.1', 'family': 'a.1.1.1', 'description': 'd1dlwa_ a.1.1.1 (A:) Protozoan/bacterial hemoglobin {Ciliate (Paramecium caudatum) [TaxId: 5885]}'}\n",
43
+ "有效 superfamily 数量: 1754\n",
44
+ "构建正样本(同一 superfamily,不同 family)...\n",
45
+ "正样本数量: 179702\n",
46
+ "构建负样本(不同 superfamily)...\n",
47
+ "负样本数量: 179702\n",
48
+ "最终远程同源测试集规模: 359404 条\n",
49
+ " 正样本 (同超家族不同家族): 179702\n",
50
+ " 负样本 (不同超家族): 179702\n",
51
+ "测试集已保存为 protein_pair_remote.csv\n"
52
+ ]
53
+ }
54
+ ],
55
+ "source": [
56
+ "from datasets import load_dataset\n",
57
+ "import random\n",
58
+ "from itertools import combinations\n",
59
+ "import pandas as pd\n",
60
+ "\n",
61
+ "# ==================== 步骤1:加载数据集 ====================\n",
62
+ "print(\"正在加载 filtered-SCOPe-2.08 数据集...\")\n",
63
+ "dataset = load_dataset(\"vkarthik095/filtered-SCOPe-2.08\")[\"train\"]\n",
64
+ "\n",
65
+ "print(f\"总序列数: {len(dataset)}\")\n",
66
+ "print(\"示例数据字段:\", dataset[0].keys())\n",
67
+ "print(\"示例数据:\", dataset[0])\n",
68
+ "\n",
69
+ "# ==================== 步骤2:按 superfamily 分组 ====================\n",
70
+ "superfamily_to_items = {}\n",
71
+ "\n",
72
+ "for item in dataset:\n",
73
+ " seq = item[\"primary\"] # 关键修正:用 'primary' 而不是 'sequence'\n",
74
+ " superfamily = item[\"super_family\"] # e.g., \"a.1.1\"\n",
75
+ " family = item[\"family\"] # e.g., \"a.1.1.1\"\n",
76
+ " \n",
77
+ " # 可选:长度过滤,与你的 short 数据集对齐\n",
78
+ " if not (40 <= len(seq) <= 250):\n",
79
+ " continue\n",
80
+ " \n",
81
+ " key = (superfamily, family)\n",
82
+ " if superfamily not in superfamily_to_items:\n",
83
+ " superfamily_to_items[superfamily] = {}\n",
84
+ " if key not in superfamily_to_items[superfamily]:\n",
85
+ " superfamily_to_items[superfamily][key] = []\n",
86
+ " superfamily_to_items[superfamily][key].append(seq)\n",
87
+ "\n",
88
+ "print(f\"有效 superfamily 数量: {len(superfamily_to_items)}\")\n",
89
+ "\n",
90
+ "# ==================== 步骤3:构建正样本(同一 superfamily,不同 family) ====================\n",
91
+ "pos_pairs = []\n",
92
+ "print(\"构建正样本(同一 superfamily,不同 family)...\")\n",
93
+ "for superfamily, family_dict in superfamily_to_items.items():\n",
94
+ " families = list(family_dict.keys())\n",
95
+ " if len(families) < 2:\n",
96
+ " continue # 需要至少两个不同 family\n",
97
+ " \n",
98
+ " # 不同 family 之间两两组合\n",
99
+ " for i in range(len(families)):\n",
100
+ " for j in range(i + 1, len(families)):\n",
101
+ " seqs1 = family_dict[families[i]]\n",
102
+ " seqs2 = family_dict[families[j]]\n",
103
+ " for s1 in seqs1:\n",
104
+ " for s2 in seqs2:\n",
105
+ " pos_pairs.append({\n",
106
+ " \"sentence1\": s1,\n",
107
+ " \"sentence2\": s2,\n",
108
+ " \"label\": 1\n",
109
+ " })\n",
110
+ "\n",
111
+ "print(f\"正样本数量: {len(pos_pairs)}\")\n",
112
+ "\n",
113
+ "# ==================== 步骤4:构建负样本(不同 superfamily) ====================\n",
114
+ "neg_pairs = []\n",
115
+ "print(\"构建负样本(不同 superfamily)...\")\n",
116
+ "all_seqs = [item[\"primary\"] for item in dataset if 40 <= len(item[\"primary\"]) <= 250]\n",
117
+ "\n",
118
+ "superfamilies = list(superfamily_to_items.keys())\n",
119
+ "target_neg = len(pos_pairs) # 平衡正负\n",
120
+ "\n",
121
+ "count = 0\n",
122
+ "while count < target_neg:\n",
123
+ " sf1, sf2 = random.sample(superfamilies, 2)\n",
124
+ " # 从 sf1 和 sf2 各随机取一个序列\n",
125
+ " fam1 = random.choice(list(superfamily_to_items[sf1].keys()))\n",
126
+ " fam2 = random.choice(list(superfamily_to_items[sf2].keys()))\n",
127
+ " s1 = random.choice(superfamily_to_items[sf1][fam1])\n",
128
+ " s2 = random.choice(superfamily_to_items[sf2][fam2])\n",
129
+ " neg_pairs.append({\n",
130
+ " \"sentence1\": s1,\n",
131
+ " \"sentence2\": s2,\n",
132
+ " \"label\": 0\n",
133
+ " })\n",
134
+ " count += 1\n",
135
+ "\n",
136
+ "print(f\"负样本数量: {len(neg_pairs)}\")\n",
137
+ "\n",
138
+ "# ==================== 步骤5:合并、打乱、保存 ====================\n",
139
+ "all_pairs = pos_pairs + neg_pairs\n",
140
+ "random.shuffle(all_pairs)\n",
141
+ "\n",
142
+ "df = pd.DataFrame(all_pairs)\n",
143
+ "\n",
144
+ "print(f\"最终远程同源测试集规模: {len(df)} 条\")\n",
145
+ "print(f\" 正样本 (同超家族不同家族): {len(df[df['label']==1])}\")\n",
146
+ "print(f\" 负样本 (不同超家族): {len(df[df['label']==0])}\")\n",
147
+ "\n",
148
+ "# 保存\n",
149
+ "df.to_csv(\"protein_pair_remote.csv\", index=False)\n",
150
+ "print(\"测试集已保存为 protein_pair_remote.csv\")\n",
151
+ "\n",
152
+ "# 可选:上传到你的 biopaws 数据集\n",
153
+ "# from huggingface_hub import login\n",
154
+ "# login(token=\"your_token\")\n",
155
+ "# from datasets import Dataset\n",
156
+ "# hf_ds = Dataset.from_pandas(df)\n",
157
+ "# hf_ds.push_to_hub(\"dnagpt/biopaws\", config_name=\"remote-homology-scop\")"
158
+ ]
159
+ },
160
+ {
161
+ "cell_type": "code",
162
+ "execution_count": 4,
163
+ "id": "122bd364-d920-4e59-a41c-25593fc502fa",
164
+ "metadata": {},
165
+ "outputs": [
166
+ {
167
+ "data": {
168
+ "application/vnd.jupyter.widget-view+json": {
169
+ "model_id": "4c8cded3e47941ca88819ecb3192933b",
170
+ "version_major": 2,
171
+ "version_minor": 0
172
+ },
173
+ "text/plain": [
174
+ "Uploading the dataset shards: 0%| | 0/1 [00:00<?, ? shards/s]"
175
+ ]
176
+ },
177
+ "metadata": {},
178
+ "output_type": "display_data"
179
+ },
180
+ {
181
+ "data": {
182
+ "application/vnd.jupyter.widget-view+json": {
183
+ "model_id": "694e44947a894a7e8d69e290a575c33e",
184
+ "version_major": 2,
185
+ "version_minor": 0
186
+ },
187
+ "text/plain": [
188
+ "Creating parquet from Arrow format: 0%| | 0/360 [00:00<?, ?ba/s]"
189
+ ]
190
+ },
191
+ "metadata": {},
192
+ "output_type": "display_data"
193
+ },
194
+ {
195
+ "data": {
196
+ "application/vnd.jupyter.widget-view+json": {
197
+ "model_id": "26f1c5dde84843b190f4b06e2a246c3e",
198
+ "version_major": 2,
199
+ "version_minor": 0
200
+ },
201
+ "text/plain": [
202
+ "Processing Files (0 / 0) : | | 0.00B / 0.00B "
203
+ ]
204
+ },
205
+ "metadata": {},
206
+ "output_type": "display_data"
207
+ },
208
+ {
209
+ "data": {
210
+ "application/vnd.jupyter.widget-view+json": {
211
+ "model_id": "58aca8ef2aaa47af8401eddb3e5280de",
212
+ "version_major": 2,
213
+ "version_minor": 0
214
+ },
215
+ "text/plain": [
216
+ "New Data Upload : | | 0.00B / 0.00B "
217
+ ]
218
+ },
219
+ "metadata": {},
220
+ "output_type": "display_data"
221
+ },
222
+ {
223
+ "data": {
224
+ "application/vnd.jupyter.widget-view+json": {
225
+ "model_id": "f437286eee11491b894b6dec0720ca0e",
226
+ "version_major": 2,
227
+ "version_minor": 0
228
+ },
229
+ "text/plain": [
230
+ " : 7%|7 | 6.29MB / 88.8MB "
231
+ ]
232
+ },
233
+ "metadata": {},
234
+ "output_type": "display_data"
235
+ },
236
+ {
237
+ "data": {
238
+ "text/plain": [
239
+ "CommitInfo(commit_url='https://huggingface.co/datasets/dnagpt/biopaws/commit/6404c36e42e9f7b83cadcdd70029042757eb8b57', commit_message='Upload dataset', commit_description='', oid='6404c36e42e9f7b83cadcdd70029042757eb8b57', pr_url=None, repo_url=RepoUrl('https://huggingface.co/datasets/dnagpt/biopaws', endpoint='https://huggingface.co', repo_type='dataset', repo_id='dnagpt/biopaws'), pr_revision=None, pr_num=None)"
240
+ ]
241
+ },
242
+ "execution_count": 4,
243
+ "metadata": {},
244
+ "output_type": "execute_result"
245
+ }
246
+ ],
247
+ "source": [
248
+ "# 可选:上传到你的 biopaws 数据集\n",
249
+ "from huggingface_hub import login\n",
250
+ "login(token=\"your_token\")\n",
251
+ "from datasets import Dataset\n",
252
+ "hf_dataset = Dataset.from_pandas(df)\n",
253
+ "hf_dataset.push_to_hub(\"dnagpt/biopaws\", config_name=\"protein_pair_remote\")"
254
+ ]
255
+ },
256
+ {
257
+ "cell_type": "code",
258
+ "execution_count": null,
259
+ "id": "e15916b5-8a77-4d10-8feb-a6a279197209",
260
+ "metadata": {},
261
+ "outputs": [],
262
+ "source": []
263
+ }
264
+ ],
265
+ "metadata": {
266
+ "kernelspec": {
267
+ "display_name": "Python 3 (ipykernel)",
268
+ "language": "python",
269
+ "name": "python3"
270
+ },
271
+ "language_info": {
272
+ "codemirror_mode": {
273
+ "name": "ipython",
274
+ "version": 3
275
+ },
276
+ "file_extension": ".py",
277
+ "mimetype": "text/x-python",
278
+ "name": "python",
279
+ "nbconvert_exporter": "python",
280
+ "pygments_lexer": "ipython3",
281
+ "version": "3.12.3"
282
+ }
283
+ },
284
+ "nbformat": 4,
285
+ "nbformat_minor": 5
286
+ }
1-data/1-get_sample_uniprot_sprot.ipynb ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "6f2bac2f-0be6-4734-af0d-28eb3375ca80",
6
+ "metadata": {},
7
+ "source": [
8
+ "## 获得uniprot_sprot采样蛋白质序列"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": null,
14
+ "id": "89bfb02d-57c4-4aad-bfcd-8631dc52a7ca",
15
+ "metadata": {},
16
+ "outputs": [],
17
+ "source": [
18
+ "!wget ftp://ftp.uniprot.org/pub/databases/uniprot/current_release/knowledgebase/complete/uniprot_sprot.fasta.gz"
19
+ ]
20
+ },
21
+ {
22
+ "cell_type": "code",
23
+ "execution_count": null,
24
+ "id": "deab856a-465b-4a67-88d8-6d3af7b261f0",
25
+ "metadata": {},
26
+ "outputs": [],
27
+ "source": [
28
+ "!gunzip uniprot_sprot.fasta.gz"
29
+ ]
30
+ },
31
+ {
32
+ "cell_type": "code",
33
+ "execution_count": 1,
34
+ "id": "6cb6aabd-f0a9-4e91-ad65-d1409fd29881",
35
+ "metadata": {},
36
+ "outputs": [
37
+ {
38
+ "name": "stdout",
39
+ "output_type": "stream",
40
+ "text": [
41
+ "采样完成:10000 条序列\n"
42
+ ]
43
+ }
44
+ ],
45
+ "source": [
46
+ "from Bio import SeqIO\n",
47
+ "import random\n",
48
+ "\n",
49
+ "input_file = \"uniprot_sprot.fasta\"\n",
50
+ "output_file = \"sampled_10000_proteins.fasta\"\n",
51
+ "\n",
52
+ "# 读取所有序列(内存足够的话)\n",
53
+ "all_sequences = list(SeqIO.parse(input_file, \"fasta\"))\n",
54
+ "\n",
55
+ "# 随机抽样\n",
56
+ "sampled = random.sample(all_sequences, 10000)\n",
57
+ "\n",
58
+ "# 保存\n",
59
+ "SeqIO.write(sampled, output_file, \"fasta\")\n",
60
+ "print(f\"采样完成:{len(sampled)} 条序列\")"
61
+ ]
62
+ },
63
+ {
64
+ "cell_type": "code",
65
+ "execution_count": 2,
66
+ "id": "60f21e14-1c49-4bd3-971b-0e6e43b77a57",
67
+ "metadata": {},
68
+ "outputs": [
69
+ {
70
+ "name": "stdout",
71
+ "output_type": "stream",
72
+ "text": [
73
+ "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
74
+ "\u001b[0m"
75
+ ]
76
+ }
77
+ ],
78
+ "source": [
79
+ "!pip install -q Bio"
80
+ ]
81
+ },
82
+ {
83
+ "cell_type": "code",
84
+ "execution_count": null,
85
+ "id": "327a2186-e116-4cee-84a5-863ef11668d6",
86
+ "metadata": {},
87
+ "outputs": [],
88
+ "source": []
89
+ }
90
+ ],
91
+ "metadata": {
92
+ "kernelspec": {
93
+ "display_name": "Python 3 (ipykernel)",
94
+ "language": "python",
95
+ "name": "python3"
96
+ },
97
+ "language_info": {
98
+ "codemirror_mode": {
99
+ "name": "ipython",
100
+ "version": 3
101
+ },
102
+ "file_extension": ".py",
103
+ "mimetype": "text/x-python",
104
+ "name": "python",
105
+ "nbconvert_exporter": "python",
106
+ "pygments_lexer": "ipython3",
107
+ "version": "3.12.3"
108
+ }
109
+ },
110
+ "nbformat": 4,
111
+ "nbformat_minor": 5
112
+ }
1-data/2-get_non_homologous_pairs.ipynb ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "62923fb0-c04f-4c60-9262-4e505c0c2dbd",
6
+ "metadata": {},
7
+ "source": [
8
+ "## 获得非同源蛋白质序列"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": null,
14
+ "id": "3f6635fa-4f6f-4b91-9d55-6aafb477e227",
15
+ "metadata": {
16
+ "scrolled": true
17
+ },
18
+ "outputs": [
19
+ {
20
+ "name": "stdout",
21
+ "output_type": "stream",
22
+ "text": [
23
+ "Loading query sequences...\n",
24
+ "Loading full Swiss-Prot database... (this may take a minute)\n"
25
+ ]
26
+ }
27
+ ],
28
+ "source": [
29
+ "from Bio import SeqIO\n",
30
+ "from Bio.Align import PairwiseAligner\n",
31
+ "import csv\n",
32
+ "import random\n",
33
+ "\n",
34
+ "# 文件路径\n",
35
+ "QUERY_FASTA = \"sampled_10000_proteins.fasta\" # 100条查询蛋白序列\n",
36
+ "FULL_SWISSPROT = \"uniprot_sprot.fasta\" # 完整Swiss-Prot数据库\n",
37
+ "OUTPUT_CSV = \"non_homologous_pairs.csv\" # 输出负样本对\n",
38
+ "\n",
39
+ "# 参数设置(可作为论文方法部分说明)\n",
40
+ "MAX_NEG_PER_QUERY = 5 # 每条查询序列生成5个负样本\n",
41
+ "IDENTITY_THRESHOLD = 25.0 # 身份度 < 25% 视为非同源\n",
42
+ "LENGTH_TOLERANCE = 0.3 # 序列长度差异不超过30%\n",
43
+ "MIN_LENGTH = 50 # 最小序列长度过滤\n",
44
+ "MAX_TRIALS = 300 # 每条查询最多尝试300次随机采样(足够找到5个负样本)\n",
45
+ "\n",
46
+ "# 全局比对参数(标准Needleman-Wunsch)\n",
47
+ "aligner = PairwiseAligner(mode='global')\n",
48
+ "aligner.match_score = 2\n",
49
+ "aligner.mismatch_score = -1\n",
50
+ "aligner.gap_score = -5\n",
51
+ "aligner.extend_gap_score = -1\n",
52
+ "\n",
53
+ "# 加载数据\n",
54
+ "print(\"Loading query sequences...\")\n",
55
+ "query_records = [r for r in SeqIO.parse(QUERY_FASTA, \"fasta\") if len(r.seq) >= MIN_LENGTH]\n",
56
+ "\n",
57
+ "print(\"Loading full Swiss-Prot database... (this may take a minute)\")\n",
58
+ "full_records = [r for r in SeqIO.parse(FULL_SWISSPROT, \"fasta\") if len(r.seq) >= MIN_LENGTH]\n",
59
+ "print(f\"Loaded {len(full_records)} sequences from Swiss-Prot.\")\n",
60
+ "\n",
61
+ "query_ids = {r.id for r in query_records}\n",
62
+ "\n",
63
+ "# 生成负样本\n",
64
+ "with open(OUTPUT_CSV, \"w\", newline=\"\", encoding=\"utf-8\") as f:\n",
65
+ " writer = csv.writer(f)\n",
66
+ " writer.writerow([\"sentence1\", \"sentence2\", \"label\"])\n",
67
+ "\n",
68
+ " for i, query_rec in enumerate(query_records, 1):\n",
69
+ " q_seq = str(query_rec.seq)\n",
70
+ " q_len = len(q_seq)\n",
71
+ " q_id = query_rec.id\n",
72
+ "\n",
73
+ " print(f\"[{i}/{len(query_records)}] Processing {q_id} (length={q_len})\")\n",
74
+ "\n",
75
+ " found = 0\n",
76
+ " trials = 0\n",
77
+ "\n",
78
+ " while found < MAX_NEG_PER_QUERY and trials < MAX_TRIALS:\n",
79
+ " trials += 1\n",
80
+ " cand_rec = random.choice(full_records)\n",
81
+ "\n",
82
+ " # 避免选到自身(概率极小)\n",
83
+ " if cand_rec.id == q_id:\n",
84
+ " continue\n",
85
+ "\n",
86
+ " c_seq = str(cand_rec.seq)\n",
87
+ " c_len = len(c_seq)\n",
88
+ "\n",
89
+ " # 长度相似性过滤\n",
90
+ " if abs(q_len - c_len) / ((q_len + c_len) / 2) > LENGTH_TOLERANCE:\n",
91
+ " continue\n",
92
+ "\n",
93
+ " # 全局序列比对\n",
94
+ " alignments = aligner.align(q_seq, c_seq)\n",
95
+ " if len(alignments) == 0:\n",
96
+ " continue\n",
97
+ " alignment = alignments[0]\n",
98
+ "\n",
99
+ " # 计算身份度(不计gap)\n",
100
+ " identical = sum(a == b and a != '-' and b != '-' \n",
101
+ " for a, b in zip(str(alignment[0]), str(alignment[1])))\n",
102
+ " aligned_len = len(str(alignment[0])) - str(alignment[0]).count('-') - str(alignment[1]).count('-')\n",
103
+ " identity = 100.0 * identical / aligned_len if aligned_len > 0 else 0\n",
104
+ "\n",
105
+ " if identity < IDENTITY_THRESHOLD:\n",
106
+ " writer.writerow([q_seq, c_seq, 0])\n",
107
+ " found += 1\n",
108
+ "\n",
109
+ " print(f\" Found {found} negative pairs (after {trials} trials)\")\n",
110
+ "\n",
111
+ "print(f\"\\nDone! Negative samples saved to {OUTPUT_CSV}\")"
112
+ ]
113
+ },
114
+ {
115
+ "cell_type": "code",
116
+ "execution_count": null,
117
+ "id": "8c031fd5-ecac-4d50-b8e7-944d37172221",
118
+ "metadata": {},
119
+ "outputs": [],
120
+ "source": [
121
+ "\n",
122
+ "\n",
123
+ "\n",
124
+ "\n",
125
+ " "
126
+ ]
127
+ }
128
+ ],
129
+ "metadata": {
130
+ "kernelspec": {
131
+ "display_name": "Python 3 (ipykernel)",
132
+ "language": "python",
133
+ "name": "python3"
134
+ },
135
+ "language_info": {
136
+ "codemirror_mode": {
137
+ "name": "ipython",
138
+ "version": 3
139
+ },
140
+ "file_extension": ".py",
141
+ "mimetype": "text/x-python",
142
+ "name": "python",
143
+ "nbconvert_exporter": "python",
144
+ "pygments_lexer": "ipython3",
145
+ "version": "3.12.3"
146
+ }
147
+ },
148
+ "nbformat": 4,
149
+ "nbformat_minor": 5
150
+ }
1-data/3-get_homology_pairs.ipynb ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "id": "cc2589ac-f8bb-4052-9cdf-c2d00b36ec2f",
6
+ "metadata": {},
7
+ "source": [
8
+ "## 获得同源序列"
9
+ ]
10
+ },
11
+ {
12
+ "cell_type": "code",
13
+ "execution_count": null,
14
+ "id": "aa596805-2075-4fcf-8f34-d24eaae64f68",
15
+ "metadata": {},
16
+ "outputs": [
17
+ {
18
+ "name": "stdout",
19
+ "output_type": "stream",
20
+ "text": [
21
+ "总共 100 条序列,开始处理...\n",
22
+ "[1/100] 处理序列: sp|Q3C1K4|CLPP_NICSY (长度: 196)\n",
23
+ " 正在运行BLAST...\n",
24
+ " 找到同源: sp|P12210.1| (identity: 100.0%, e-value: 2e-144)\n",
25
+ " 找到同源: sp|A0A360.1| (identity: 97.4%, e-value: 5e-142)\n",
26
+ " 找到同源: sp|Q9M3K5.1| (identity: 96.9%, e-value: 6e-141)\n",
27
+ " 找到同源: sp|Q09FT6.1| (identity: 98.0%, e-value: 1e-140)\n",
28
+ " 找到同源: sp|Q49KX3.1| (identity: 95.4%, e-value: 3e-140)\n",
29
+ "[2/100] 处理序列: sp|Q5B3K6|RNY1_EMENI (长度: 417)\n",
30
+ " 正在运行BLAST...\n",
31
+ " 找到同源: sp|Q5B3K6.1| (identity: 100.0%, e-value: 0e+00)\n",
32
+ " 找到同源: sp|Q4WXZ5.2| (identity: 65.6%, e-value: 0e+00)\n",
33
+ " 找到同源: sp|P24657.1| (identity: 55.6%, e-value: 2e-88)\n",
34
+ " 找到同源: sp|P10281.2| (identity: 50.2%, e-value: 2e-84)\n",
35
+ " 找到同源: sp|P19791.1| (identity: 49.8%, e-value: 4e-75)\n",
36
+ "[3/100] 处理序列: sp|Q6MM37|T23O_BDEBA (长度: 359)\n",
37
+ " 正在运行BLAST...\n",
38
+ " 找到同源: sp|Q6MM37.1| (identity: 100.0%, e-value: 0e+00)\n",
39
+ " 找到同源: sp|B4RUH2.1| (identity: 48.0%, e-value: 4e-125)\n",
40
+ " 找到同源: sp|Q55DB4.1| (identity: 44.5%, e-value: 4e-113)\n",
41
+ " 找到同源: sp|Q17P71.1| (identity: 45.7%, e-value: 2e-99)\n",
42
+ " 找到同源: sp|Q5EBG2.1| (identity: 45.7%, e-value: 5e-98)\n",
43
+ "[4/100] 处理序列: sp|O07319|MRAZ_STAAU (长度: 144)\n",
44
+ " 正在运行BLAST...\n",
45
+ " 找到同源: sp|O07319.1| (identity: 100.0%, e-value: 6e-105)\n",
46
+ " 找到同源: sp|A5IS64.1| (identity: 96.5%, e-value: 1e-99)\n",
47
+ " 找到同源: sp|Q5HQ14.1| (identity: 93.8%, e-value: 2e-97)\n",
48
+ " 找到同源: sp|Q4L5M9.1| (identity: 92.4%, e-value: 2e-96)\n",
49
+ " 找到同源: sp|Q49WW0.1| (identity: 90.3%, e-value: 1e-94)\n",
50
+ "[5/100] 处理序列: sp|Q9CR48|DRAM2_MOUSE (长度: 267)\n",
51
+ " 正在运行BLAST...\n",
52
+ " 找到同源: sp|Q9CR48.1| (identity: 100.0%, e-value: 0e+00)\n",
53
+ " 找到同源: sp|Q5BK09.1| (identity: 97.4%, e-value: 0e+00)\n",
54
+ " 找到同源: sp|Q6UX65.1| (identity: 86.4%, e-value: 3e-167)\n",
55
+ " 找到同源: sp|Q3ZC48.1| (identity: 86.4%, e-value: 7e-163)\n",
56
+ " 找到同源: sp|Q8N682.1| (identity: 41.4%, e-value: 3e-62)\n",
57
+ "[6/100] 处理序列: sp|F8DIF2|URDA_STREP (长度: 803)\n",
58
+ " 正在运行BLAST...\n",
59
+ " 找到同源: sp|F8DIF2.1| (identity: 100.0%, e-value: 0e+00)\n",
60
+ " 找到同源: sp|Q8DW88.1| (identity: 76.8%, e-value: 0e+00)\n",
61
+ " 找到同源: sp|F9UNH3.1| (identity: 68.9%, e-value: 0e+00)\n",
62
+ " 找到同源: sp|A0A1R4LHH9.1| (identity: 67.1%, e-value: 0e+00)\n",
63
+ " 找到同源: sp|B2GCE0.1| (identity: 61.5%, e-value: 0e+00)\n",
64
+ "[7/100] 处理序列: sp|Q6MUD1|EX7L_MYCMS (长度: 469)\n",
65
+ " 正在运行BLAST...\n",
66
+ " 找到同源: sp|Q6MUD1.1| (identity: 100.0%, e-value: 0e+00)\n",
67
+ " 找到同源: sp|Q6F1D5.1| (identity: 56.2%, e-value: 3e-124)\n",
68
+ " 找到同源: sp|A7GSJ8.1| (identity: 33.5%, e-value: 3e-82)\n",
69
+ " 找到同源: sp|C3LJV4.1| (identity: 33.5%, e-value: 4e-82)\n",
70
+ " 找到同源: sp|A0RIH2.1| (identity: 33.5%, e-value: 4e-82)\n",
71
+ "[8/100] 处理序列: sp|P34109|MYOD_DICDI (长度: 1109)\n",
72
+ " 正在运行BLAST...\n",
73
+ " 找到同源: sp|P34109.2| (identity: 100.0%, e-value: 0e+00)\n",
74
+ " 找到同源: sp|P10569.1| (identity: 59.0%, e-value: 0e+00)\n",
75
+ " 找到同源: sp|P19706.2| (identity: 48.8%, e-value: 0e+00)\n",
76
+ " 找到同源: sp|P19706.2| (identity: 53.6%, e-value: 2e-11)\n",
77
+ " 找到同源: sp|P34092.2| (identity: 48.3%, e-value: 0e+00)\n",
78
+ "[9/100] 处理序列: sp|Q7U564|LGT_PARMW (长度: 278)\n",
79
+ " 正在运行BLAST...\n"
80
+ ]
81
+ },
82
+ {
83
+ "name": "stderr",
84
+ "output_type": "stream",
85
+ "text": [
86
+ "/root/miniconda3/lib/python3.12/site-packages/Bio/Blast/NCBIWWW.py:275: BiopythonWarning: BLAST request N1VE8FMV016 is taking longer than 10 minutes, consider re-issuing it\n",
87
+ " warnings.warn(\n"
88
+ ]
89
+ },
90
+ {
91
+ "name": "stdout",
92
+ "output_type": "stream",
93
+ "text": [
94
+ " 找到同源: sp|Q7U564.1| (identity: 100.0%, e-value: 0e+00)\n",
95
+ " 找到同源: sp|Q3AWL5.1| (identity: 79.5%, e-value: 4e-154)\n",
96
+ " 找到同源: sp|Q7V652.1| (identity: 69.1%, e-value: 6e-138)\n",
97
+ " 找到同源: sp|A2C7F4.1| (identity: 68.4%, e-value: 3e-137)\n",
98
+ " 找到同源: sp|A2C0R7.1| (identity: 62.7%, e-value: 6e-125)\n",
99
+ "[10/100] 处理序列: sp|A9N941|ERA_COXBR (长度: 295)\n",
100
+ " 正在运行BLAST...\n"
101
+ ]
102
+ },
103
+ {
104
+ "name": "stderr",
105
+ "output_type": "stream",
106
+ "text": [
107
+ "/root/miniconda3/lib/python3.12/site-packages/Bio/Blast/NCBIWWW.py:275: BiopythonWarning: BLAST request N1WAWWS3016 is taking longer than 10 minutes, consider re-issuing it\n",
108
+ " warnings.warn(\n"
109
+ ]
110
+ },
111
+ {
112
+ "name": "stdout",
113
+ "output_type": "stream",
114
+ "text": [
115
+ " 找到同源: sp|A9N941.1| (identity: 100.0%, e-value: 0e+00)\n",
116
+ " 找到同源: sp|B6IZ00.1| (identity: 99.3%, e-value: 0e+00)\n",
117
+ " 找到同源: sp|A9KFA1.1| (identity: 99.0%, e-value: 0e+00)\n",
118
+ " 找到同源: sp|A5UFI7.2| (identity: 55.5%, e-value: 3e-113)\n",
119
+ " 找到同源: sp|Q9CPH8.1| (identity: 53.1%, e-value: 1e-110)\n",
120
+ "[11/100] 处理序列: sp|P41874|FAR3_PANRE (长度: 7)\n",
121
+ " 正在运行BLAST...\n"
122
+ ]
123
+ },
124
+ {
125
+ "name": "stderr",
126
+ "output_type": "stream",
127
+ "text": [
128
+ "/root/miniconda3/lib/python3.12/site-packages/Bio/Blast/NCBIWWW.py:275: BiopythonWarning: BLAST request N1XDCAVV016 is taking longer than 10 minutes, consider re-issuing it\n",
129
+ " warnings.warn(\n"
130
+ ]
131
+ },
132
+ {
133
+ "ename": "KeyboardInterrupt",
134
+ "evalue": "",
135
+ "output_type": "error",
136
+ "traceback": [
137
+ "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
138
+ "\u001b[31mKeyboardInterrupt\u001b[39m Traceback (most recent call last)",
139
+ "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 34\u001b[39m\n\u001b[32m 31\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 32\u001b[39m \u001b[38;5;66;03m# 运行在线BLAST(对swissprot数据库)\u001b[39;00m\n\u001b[32m 33\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[33m\"\u001b[39m\u001b[33m 正在运行BLAST...\u001b[39m\u001b[33m\"\u001b[39m)\n\u001b[32m---> \u001b[39m\u001b[32m34\u001b[39m result_handle = \u001b[43mNCBIWWW\u001b[49m\u001b[43m.\u001b[49m\u001b[43mqblast\u001b[49m\u001b[43m(\u001b[49m\n\u001b[32m 35\u001b[39m \u001b[43m \u001b[49m\u001b[43mprogram\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mblastp\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[32m 36\u001b[39m \u001b[43m \u001b[49m\u001b[43mdatabase\u001b[49m\u001b[43m=\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mswissprot\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\n\u001b[32m 37\u001b[39m \u001b[43m \u001b[49m\u001b[43msequence\u001b[49m\u001b[43m=\u001b[49m\u001b[43mquery_seq\u001b[49m\u001b[43m,\u001b[49m\n\u001b[32m 38\u001b[39m \u001b[43m \u001b[49m\u001b[43mexpect\u001b[49m\u001b[43m=\u001b[49m\u001b[43me_value_thresh\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# 直接设置expect过滤\u001b[39;49;00m\n\u001b[32m 39\u001b[39m \u001b[43m \u001b[49m\u001b[43mhitlist_size\u001b[49m\u001b[43m=\u001b[49m\u001b[32;43m20\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# 先取多一点,后面再过滤identity\u001b[39;49;00m\n\u001b[32m 40\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 42\u001b[39m \u001b[38;5;66;03m# 解析结果\u001b[39;00m\n\u001b[32m 43\u001b[39m blast_records = NCBIXML.parse(result_handle)\n",
140
+ "\u001b[36mFile \u001b[39m\u001b[32m~/miniconda3/lib/python3.12/site-packages/Bio/Blast/NCBIWWW.py:264\u001b[39m, in \u001b[36mqblast\u001b[39m\u001b[34m(program, database, sequence, url_base, auto_format, composition_based_statistics, db_genetic_code, endpoints, entrez_query, expect, filter, gapcosts, genetic_code, hitlist_size, i_thresh, layout, lcase_mask, matrix_name, nucl_penalty, nucl_reward, other_advanced, perc_ident, phi_pattern, query_file, query_believe_defline, query_from, query_to, searchsp_eff, service, threshold, ungapped_alignment, word_size, short_query, alignments, alignment_view, descriptions, entrez_links_new_window, expect_low, expect_high, format_entrez_query, format_object, format_type, ncbi_gi, results_file, show_overview, megablast, template_type, template_length, username, password)\u001b[39m\n\u001b[32m 262\u001b[39m wait = qblast.previous + delay - current\n\u001b[32m 263\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m wait > \u001b[32m0\u001b[39m:\n\u001b[32m--> \u001b[39m\u001b[32m264\u001b[39m \u001b[43mtime\u001b[49m\u001b[43m.\u001b[49m\u001b[43msleep\u001b[49m\u001b[43m(\u001b[49m\u001b[43mwait\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 265\u001b[39m qblast.previous = current + wait\n\u001b[32m 266\u001b[39m \u001b[38;5;28;01melse\u001b[39;00m:\n",
141
+ "\u001b[31mKeyboardInterrupt\u001b[39m: "
142
+ ]
143
+ },
144
+ {
145
+ "name": "stderr",
146
+ "output_type": "stream",
147
+ "text": [
148
+ "\n",
149
+ "KeyboardInterrupt\n",
150
+ "\n"
151
+ ]
152
+ }
153
+ ],
154
+ "source": [
155
+ "from Bio import SeqIO\n",
156
+ "from Bio.Blast import NCBIWWW\n",
157
+ "from Bio.Blast import NCBIXML\n",
158
+ "import csv\n",
159
+ "import time\n",
160
+ "import random\n",
161
+ "\n",
162
+ "# 输入FASTA文件\n",
163
+ "fasta_file = \"sampled_100000_proteins.fasta\"\n",
164
+ "output_csv = \"homology_pairs.csv\"\n",
165
+ "\n",
166
+ "# 参数\n",
167
+ "max_homologs_per_query = 5 # 每条取1-5条同源\n",
168
+ "e_value_thresh = 1e-10 # e-value阈值,越小越严格\n",
169
+ "identity_thresh = 30.0 # 最低身份度百分比(>30%通常视为同源)\n",
170
+ "\n",
171
+ "# 打开CSV写入\n",
172
+ "with open(output_csv, \"w\", newline=\"\", encoding=\"utf-8\") as csvfile:\n",
173
+ " writer = csv.writer(csvfile)\n",
174
+ " writer.writerow([\"sentence1\", \"sentence2\", \"label\"])\n",
175
+ "\n",
176
+ " # 读取所有序列\n",
177
+ " sequences = list(SeqIO.parse(fasta_file, \"fasta\"))\n",
178
+ " print(f\"总共 {len(sequences)} 条序列,开始处理...\")\n",
179
+ "\n",
180
+ " for idx, record in enumerate(sequences, 1):\n",
181
+ " query_seq = str(record.seq)\n",
182
+ " query_id = record.id\n",
183
+ " print(f\"[{idx}/{len(sequences)}] 处理序列: {query_id} (长度: {len(query_seq)})\")\n",
184
+ "\n",
185
+ " try:\n",
186
+ " # 运行在线BLAST(对swissprot数据库)\n",
187
+ " print(\" 正在运行BLAST...\")\n",
188
+ " result_handle = NCBIWWW.qblast(\n",
189
+ " program=\"blastp\", \n",
190
+ " database=\"swissprot\", \n",
191
+ " sequence=query_seq,\n",
192
+ " expect=e_value_thresh, # 直接设置expect过滤\n",
193
+ " hitlist_size=20 # 先取多一点,后面再过滤identity\n",
194
+ " )\n",
195
+ "\n",
196
+ " # 解析结果\n",
197
+ " blast_records = NCBIXML.parse(result_handle)\n",
198
+ " blast_record = next(blast_records, None) # 只有一个record\n",
199
+ "\n",
200
+ " homolog_count = 0\n",
201
+ " if blast_record:\n",
202
+ " for alignment in blast_record.alignments:\n",
203
+ " hit_id = alignment.hit_id # 如 sp|P12345|XXX\n",
204
+ " hit_seq = \"\" # 我们稍后从HSP取对齐部分,但这里用完整hit序列(NCBI返回的是对齐,但我们取第一个HSP的hit序列)\n",
205
+ " \n",
206
+ " for hsp in alignment.hsps:\n",
207
+ " hit_seq = hsp.sbjct # 对齐的hit部分(保守)\n",
208
+ " identity = (hsp.identities / hsp.align_length) * 100\n",
209
+ "\n",
210
+ " if identity >= identity_thresh:\n",
211
+ " # 排除自身(如果query本身在数据库中)\n",
212
+ " if hit_id.split(\"|\")[1] == query_id: # UniProt ID匹配\n",
213
+ " continue\n",
214
+ "\n",
215
+ " # 写入一对\n",
216
+ " writer.writerow([query_seq, hsp.sbjct, 1]) # 用对齐序列作为sentence2,更公平(长度一致)\n",
217
+ " # 如果想用完整hit序列:需额外请求,但复杂,这里用对齐部分\n",
218
+ " homolog_count += 1\n",
219
+ " print(f\" 找到同源: {hit_id} (identity: {identity:.1f}%, e-value: {hsp.expect:.0e})\")\n",
220
+ "\n",
221
+ " if homolog_count >= max_homologs_per_query:\n",
222
+ " break\n",
223
+ " if homolog_count >= max_homologs_per_query:\n",
224
+ " break\n",
225
+ "\n",
226
+ " if homolog_count == 0:\n",
227
+ " print(\" 无合格同源序列。\")\n",
228
+ "\n",
229
+ " except Exception as e:\n",
230
+ " print(f\" BLAST错误: {e},跳过此序列。\")\n",
231
+ "\n",
232
+ " # 礼貌等待,避免NCBI限速\n",
233
+ " time.sleep(3 + random.uniform(0, 3)) # 3-6秒随机间隔\n",
234
+ "\n",
235
+ "print(f\"完成!正样本对已保存到 {output_csv}\")"
236
+ ]
237
+ }
238
+ ],
239
+ "metadata": {
240
+ "kernelspec": {
241
+ "display_name": "Python 3 (ipykernel)",
242
+ "language": "python",
243
+ "name": "python3"
244
+ },
245
+ "language_info": {
246
+ "codemirror_mode": {
247
+ "name": "ipython",
248
+ "version": 3
249
+ },
250
+ "file_extension": ".py",
251
+ "mimetype": "text/x-python",
252
+ "name": "python",
253
+ "nbconvert_exporter": "python",
254
+ "pygments_lexer": "ipython3",
255
+ "version": "3.12.3"
256
+ }
257
+ },
258
+ "nbformat": 4,
259
+ "nbformat_minor": 5
260
+ }
1-data/4-get_distant homology_pairs.ipynb ADDED
@@ -0,0 +1,286 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "acaa7453-aa81-483c-8a98-8d98ba29855e",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "# import os\n",
11
+ "\n",
12
+ "# # 设置环境变量\n",
13
+ "# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'\n",
14
+ "\n",
15
+ "# # 打印环境变量以确认设置成功\n",
16
+ "# print(os.environ.get('HF_ENDPOINT'))\n",
17
+ "\n",
18
+ "import subprocess\n",
19
+ "import os\n",
20
+ "\n",
21
+ "result = subprocess.run('bash -c \"source /etc/network_turbo && env | grep proxy\"', shell=True, capture_output=True, text=True)\n",
22
+ "output = result.stdout\n",
23
+ "for line in output.splitlines():\n",
24
+ " if '=' in line:\n",
25
+ " var, value = line.split('=', 1)\n",
26
+ " os.environ[var] = value"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": 3,
32
+ "id": "6bd44c1c-8414-47b6-ba89-fdc5db3c5c44",
33
+ "metadata": {},
34
+ "outputs": [
35
+ {
36
+ "name": "stdout",
37
+ "output_type": "stream",
38
+ "text": [
39
+ "正在加载 filtered-SCOPe-2.08 数据集...\n",
40
+ "总序列数: 14535\n",
41
+ "示例数据字段: dict_keys(['id', 'primary', 'protein_length', 'class', 'fold', 'super_family', 'family', 'description'])\n",
42
+ "示例数据: {'id': 'd1dlwa_', 'primary': 'slfeqlggqaavqavtaqfyaniqadatvatffngidmpnqtnktaaflcaalggpnawtgrnlkevhanmgvsnaqfttvighlrsaltgagvaaalveqtvavaetvrgdvvtv', 'protein_length': 116, 'class': 'a', 'fold': 'a.1', 'super_family': 'a.1.1', 'family': 'a.1.1.1', 'description': 'd1dlwa_ a.1.1.1 (A:) Protozoan/bacterial hemoglobin {Ciliate (Paramecium caudatum) [TaxId: 5885]}'}\n",
43
+ "有效 superfamily 数量: 1754\n",
44
+ "构建正样本(同一 superfamily,不同 family)...\n",
45
+ "正样本数量: 179702\n",
46
+ "构建负样本(不同 superfamily)...\n",
47
+ "负样本数量: 179702\n",
48
+ "最终远程同源测试集规模: 359404 条\n",
49
+ " 正样本 (同超家族不同家族): 179702\n",
50
+ " 负样本 (不同超家族): 179702\n",
51
+ "测试集已保存为 protein_pair_remote.csv\n"
52
+ ]
53
+ }
54
+ ],
55
+ "source": [
56
+ "from datasets import load_dataset\n",
57
+ "import random\n",
58
+ "from itertools import combinations\n",
59
+ "import pandas as pd\n",
60
+ "\n",
61
+ "# ==================== 步骤1:加载数据集 ====================\n",
62
+ "print(\"正在加载 filtered-SCOPe-2.08 数据集...\")\n",
63
+ "dataset = load_dataset(\"vkarthik095/filtered-SCOPe-2.08\")[\"train\"]\n",
64
+ "\n",
65
+ "print(f\"总序列数: {len(dataset)}\")\n",
66
+ "print(\"示例数据字段:\", dataset[0].keys())\n",
67
+ "print(\"示例数据:\", dataset[0])\n",
68
+ "\n",
69
+ "# ==================== 步骤2:按 superfamily 分组 ====================\n",
70
+ "superfamily_to_items = {}\n",
71
+ "\n",
72
+ "for item in dataset:\n",
73
+ " seq = item[\"primary\"] # 关键修正:用 'primary' 而不是 'sequence'\n",
74
+ " superfamily = item[\"super_family\"] # e.g., \"a.1.1\"\n",
75
+ " family = item[\"family\"] # e.g., \"a.1.1.1\"\n",
76
+ " \n",
77
+ " # 可选:长度过滤,与你的 short 数据集对齐\n",
78
+ " if not (40 <= len(seq) <= 250):\n",
79
+ " continue\n",
80
+ " \n",
81
+ " key = (superfamily, family)\n",
82
+ " if superfamily not in superfamily_to_items:\n",
83
+ " superfamily_to_items[superfamily] = {}\n",
84
+ " if key not in superfamily_to_items[superfamily]:\n",
85
+ " superfamily_to_items[superfamily][key] = []\n",
86
+ " superfamily_to_items[superfamily][key].append(seq)\n",
87
+ "\n",
88
+ "print(f\"有效 superfamily 数量: {len(superfamily_to_items)}\")\n",
89
+ "\n",
90
+ "# ==================== 步骤3:构建正样本(同一 superfamily,不同 family) ====================\n",
91
+ "pos_pairs = []\n",
92
+ "print(\"构建正样本(同一 superfamily,不同 family)...\")\n",
93
+ "for superfamily, family_dict in superfamily_to_items.items():\n",
94
+ " families = list(family_dict.keys())\n",
95
+ " if len(families) < 2:\n",
96
+ " continue # 需要至少两个不同 family\n",
97
+ " \n",
98
+ " # 不同 family 之间两两组合\n",
99
+ " for i in range(len(families)):\n",
100
+ " for j in range(i + 1, len(families)):\n",
101
+ " seqs1 = family_dict[families[i]]\n",
102
+ " seqs2 = family_dict[families[j]]\n",
103
+ " for s1 in seqs1:\n",
104
+ " for s2 in seqs2:\n",
105
+ " pos_pairs.append({\n",
106
+ " \"sentence1\": s1,\n",
107
+ " \"sentence2\": s2,\n",
108
+ " \"label\": 1\n",
109
+ " })\n",
110
+ "\n",
111
+ "print(f\"正样本数量: {len(pos_pairs)}\")\n",
112
+ "\n",
113
+ "# ==================== 步骤4:构建负样本(不同 superfamily) ====================\n",
114
+ "neg_pairs = []\n",
115
+ "print(\"构建负样本(不同 superfamily)...\")\n",
116
+ "all_seqs = [item[\"primary\"] for item in dataset if 40 <= len(item[\"primary\"]) <= 250]\n",
117
+ "\n",
118
+ "superfamilies = list(superfamily_to_items.keys())\n",
119
+ "target_neg = len(pos_pairs) # 平衡正负\n",
120
+ "\n",
121
+ "count = 0\n",
122
+ "while count < target_neg:\n",
123
+ " sf1, sf2 = random.sample(superfamilies, 2)\n",
124
+ " # 从 sf1 和 sf2 各随机取一个序列\n",
125
+ " fam1 = random.choice(list(superfamily_to_items[sf1].keys()))\n",
126
+ " fam2 = random.choice(list(superfamily_to_items[sf2].keys()))\n",
127
+ " s1 = random.choice(superfamily_to_items[sf1][fam1])\n",
128
+ " s2 = random.choice(superfamily_to_items[sf2][fam2])\n",
129
+ " neg_pairs.append({\n",
130
+ " \"sentence1\": s1,\n",
131
+ " \"sentence2\": s2,\n",
132
+ " \"label\": 0\n",
133
+ " })\n",
134
+ " count += 1\n",
135
+ "\n",
136
+ "print(f\"负样本数量: {len(neg_pairs)}\")\n",
137
+ "\n",
138
+ "# ==================== 步骤5:合并、打乱、保存 ====================\n",
139
+ "all_pairs = pos_pairs + neg_pairs\n",
140
+ "random.shuffle(all_pairs)\n",
141
+ "\n",
142
+ "df = pd.DataFrame(all_pairs)\n",
143
+ "\n",
144
+ "print(f\"最终远程同源测试集规模: {len(df)} 条\")\n",
145
+ "print(f\" 正样本 (同超家族不同家族): {len(df[df['label']==1])}\")\n",
146
+ "print(f\" 负样本 (不同超家族): {len(df[df['label']==0])}\")\n",
147
+ "\n",
148
+ "# 保存\n",
149
+ "df.to_csv(\"protein_pair_remote.csv\", index=False)\n",
150
+ "print(\"测试集已保存为 protein_pair_remote.csv\")\n",
151
+ "\n",
152
+ "# 可选:上传到你的 biopaws 数据集\n",
153
+ "# from huggingface_hub import login\n",
154
+ "# login(token=\"your_token\")\n",
155
+ "# from datasets import Dataset\n",
156
+ "# hf_ds = Dataset.from_pandas(df)\n",
157
+ "# hf_ds.push_to_hub(\"dnagpt/biopaws\", config_name=\"remote-homology-scop\")"
158
+ ]
159
+ },
160
+ {
161
+ "cell_type": "code",
162
+ "execution_count": 4,
163
+ "id": "122bd364-d920-4e59-a41c-25593fc502fa",
164
+ "metadata": {},
165
+ "outputs": [
166
+ {
167
+ "data": {
168
+ "application/vnd.jupyter.widget-view+json": {
169
+ "model_id": "4c8cded3e47941ca88819ecb3192933b",
170
+ "version_major": 2,
171
+ "version_minor": 0
172
+ },
173
+ "text/plain": [
174
+ "Uploading the dataset shards: 0%| | 0/1 [00:00<?, ? shards/s]"
175
+ ]
176
+ },
177
+ "metadata": {},
178
+ "output_type": "display_data"
179
+ },
180
+ {
181
+ "data": {
182
+ "application/vnd.jupyter.widget-view+json": {
183
+ "model_id": "694e44947a894a7e8d69e290a575c33e",
184
+ "version_major": 2,
185
+ "version_minor": 0
186
+ },
187
+ "text/plain": [
188
+ "Creating parquet from Arrow format: 0%| | 0/360 [00:00<?, ?ba/s]"
189
+ ]
190
+ },
191
+ "metadata": {},
192
+ "output_type": "display_data"
193
+ },
194
+ {
195
+ "data": {
196
+ "application/vnd.jupyter.widget-view+json": {
197
+ "model_id": "26f1c5dde84843b190f4b06e2a246c3e",
198
+ "version_major": 2,
199
+ "version_minor": 0
200
+ },
201
+ "text/plain": [
202
+ "Processing Files (0 / 0) : | | 0.00B / 0.00B "
203
+ ]
204
+ },
205
+ "metadata": {},
206
+ "output_type": "display_data"
207
+ },
208
+ {
209
+ "data": {
210
+ "application/vnd.jupyter.widget-view+json": {
211
+ "model_id": "58aca8ef2aaa47af8401eddb3e5280de",
212
+ "version_major": 2,
213
+ "version_minor": 0
214
+ },
215
+ "text/plain": [
216
+ "New Data Upload : | | 0.00B / 0.00B "
217
+ ]
218
+ },
219
+ "metadata": {},
220
+ "output_type": "display_data"
221
+ },
222
+ {
223
+ "data": {
224
+ "application/vnd.jupyter.widget-view+json": {
225
+ "model_id": "f437286eee11491b894b6dec0720ca0e",
226
+ "version_major": 2,
227
+ "version_minor": 0
228
+ },
229
+ "text/plain": [
230
+ " : 7%|7 | 6.29MB / 88.8MB "
231
+ ]
232
+ },
233
+ "metadata": {},
234
+ "output_type": "display_data"
235
+ },
236
+ {
237
+ "data": {
238
+ "text/plain": [
239
+ "CommitInfo(commit_url='https://huggingface.co/datasets/dnagpt/biopaws/commit/6404c36e42e9f7b83cadcdd70029042757eb8b57', commit_message='Upload dataset', commit_description='', oid='6404c36e42e9f7b83cadcdd70029042757eb8b57', pr_url=None, repo_url=RepoUrl('https://huggingface.co/datasets/dnagpt/biopaws', endpoint='https://huggingface.co', repo_type='dataset', repo_id='dnagpt/biopaws'), pr_revision=None, pr_num=None)"
240
+ ]
241
+ },
242
+ "execution_count": 4,
243
+ "metadata": {},
244
+ "output_type": "execute_result"
245
+ }
246
+ ],
247
+ "source": [
248
+ "# 可选:上传到你的 biopaws 数据集\n",
249
+ "from huggingface_hub import login\n",
250
+ "login(token=\"your_token\")\n",
251
+ "from datasets import Dataset\n",
252
+ "hf_dataset = Dataset.from_pandas(df)\n",
253
+ "hf_dataset.push_to_hub(\"dnagpt/biopaws\", config_name=\"protein_pair_remote\")"
254
+ ]
255
+ },
256
+ {
257
+ "cell_type": "code",
258
+ "execution_count": null,
259
+ "id": "e15916b5-8a77-4d10-8feb-a6a279197209",
260
+ "metadata": {},
261
+ "outputs": [],
262
+ "source": []
263
+ }
264
+ ],
265
+ "metadata": {
266
+ "kernelspec": {
267
+ "display_name": "Python 3 (ipykernel)",
268
+ "language": "python",
269
+ "name": "python3"
270
+ },
271
+ "language_info": {
272
+ "codemirror_mode": {
273
+ "name": "ipython",
274
+ "version": 3
275
+ },
276
+ "file_extension": ".py",
277
+ "mimetype": "text/x-python",
278
+ "name": "python",
279
+ "nbconvert_exporter": "python",
280
+ "pygments_lexer": "ipython3",
281
+ "version": "3.12.3"
282
+ }
283
+ },
284
+ "nbformat": 4,
285
+ "nbformat_minor": 5
286
+ }
1-data/mysql_part/.ipynb_checkpoints/1-insert_protein_to_db-checkpoint.ipynb ADDED
@@ -0,0 +1,646 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "ba2cea75-a1d4-4b1e-a7f8-06abd623e49f",
7
+ "metadata": {
8
+ "scrolled": true
9
+ },
10
+ "outputs": [
11
+ {
12
+ "name": "stdout",
13
+ "output_type": "stream",
14
+ "text": [
15
+ "正在读取 sampled_10000_proteins.fasta ...\n",
16
+ "共找到 10000 条蛋白质序列,开始插入...\n",
17
+ "已插入 20/10000 条\n",
18
+ "已插入 40/10000 条\n",
19
+ "已插入 60/10000 条\n",
20
+ "已插入 80/10000 条\n",
21
+ "已插入 100/10000 条\n",
22
+ "已插入 120/10000 条\n",
23
+ "已插入 140/10000 条\n",
24
+ "已插入 160/10000 条\n",
25
+ "已插入 180/10000 条\n",
26
+ "已插入 200/10000 条\n",
27
+ "已插入 220/10000 条\n",
28
+ "已插入 240/10000 条\n",
29
+ "已插入 260/10000 条\n",
30
+ "已插入 280/10000 条\n",
31
+ "已插入 300/10000 条\n",
32
+ "已插入 320/10000 条\n",
33
+ "已插入 340/10000 条\n",
34
+ "已插入 360/10000 条\n",
35
+ "已插入 380/10000 条\n",
36
+ "已插入 400/10000 条\n",
37
+ "已插入 420/10000 条\n",
38
+ "已插入 440/10000 条\n",
39
+ "已插入 460/10000 条\n",
40
+ "已插入 480/10000 条\n",
41
+ "已插入 500/10000 条\n",
42
+ "已插入 520/10000 条\n",
43
+ "已插入 540/10000 条\n",
44
+ "已插入 560/10000 条\n",
45
+ "已插入 580/10000 条\n",
46
+ "已插入 600/10000 条\n",
47
+ "已插入 620/10000 条\n",
48
+ "已插入 640/10000 条\n",
49
+ "已插入 660/10000 条\n",
50
+ "已插入 680/10000 条\n",
51
+ "已插入 700/10000 条\n",
52
+ "已插入 720/10000 条\n",
53
+ "已插入 740/10000 条\n",
54
+ "已插入 760/10000 条\n",
55
+ "已插入 780/10000 条\n",
56
+ "已插入 800/10000 条\n",
57
+ "已插入 820/10000 条\n",
58
+ "已插入 840/10000 条\n",
59
+ "已插入 860/10000 条\n",
60
+ "已插入 880/10000 条\n",
61
+ "已插入 900/10000 条\n",
62
+ "已插入 920/10000 条\n",
63
+ "已插入 940/10000 条\n",
64
+ "已插入 960/10000 条\n",
65
+ "已插入 980/10000 条\n",
66
+ "已插入 1000/10000 条\n",
67
+ "已插入 1020/10000 条\n",
68
+ "已插入 1040/10000 条\n",
69
+ "已插入 1060/10000 条\n",
70
+ "已插入 1080/10000 条\n",
71
+ "已插入 1100/10000 条\n",
72
+ "已插入 1120/10000 条\n",
73
+ "已插入 1140/10000 条\n",
74
+ "已插入 1160/10000 条\n",
75
+ "已插入 1180/10000 条\n",
76
+ "已插入 1200/10000 条\n",
77
+ "已插入 1220/10000 条\n",
78
+ "已插入 1240/10000 条\n",
79
+ "已插入 1260/10000 条\n",
80
+ "已插入 1280/10000 条\n",
81
+ "已插入 1300/10000 条\n",
82
+ "已插入 1320/10000 条\n",
83
+ "已插入 1340/10000 条\n",
84
+ "已插入 1360/10000 条\n",
85
+ "已插入 1380/10000 条\n",
86
+ "已插入 1400/10000 条\n",
87
+ "已插入 1420/10000 条\n",
88
+ "已插入 1440/10000 条\n",
89
+ "已插入 1460/10000 条\n",
90
+ "已插入 1480/10000 条\n",
91
+ "已插入 1500/10000 条\n",
92
+ "已插入 1520/10000 条\n",
93
+ "已插入 1540/10000 条\n",
94
+ "已插入 1560/10000 条\n",
95
+ "已插入 1580/10000 条\n",
96
+ "已插入 1600/10000 条\n",
97
+ "已插入 1620/10000 条\n",
98
+ "已插入 1640/10000 条\n",
99
+ "已插入 1660/10000 条\n",
100
+ "已插入 1680/10000 条\n",
101
+ "已插入 1700/10000 条\n",
102
+ "已插入 1720/10000 条\n",
103
+ "已插入 1740/10000 条\n",
104
+ "已插入 1760/10000 条\n",
105
+ "已插入 1780/10000 条\n",
106
+ "已插入 1800/10000 条\n",
107
+ "已插入 1820/10000 条\n",
108
+ "已插入 1840/10000 条\n",
109
+ "已插入 1860/10000 条\n",
110
+ "已插入 1880/10000 条\n",
111
+ "已插入 1900/10000 条\n",
112
+ "已插入 1920/10000 条\n",
113
+ "已插入 1940/10000 条\n",
114
+ "已插入 1960/10000 条\n",
115
+ "已插入 1980/10000 条\n",
116
+ "已插入 2000/10000 条\n",
117
+ "已插入 2020/10000 条\n",
118
+ "已插入 2040/10000 条\n",
119
+ "已插入 2060/10000 条\n",
120
+ "已插入 2080/10000 条\n",
121
+ "已插入 2100/10000 条\n",
122
+ "已插入 2120/10000 条\n",
123
+ "已插入 2140/10000 条\n",
124
+ "已插入 2160/10000 条\n",
125
+ "已插入 2180/10000 条\n",
126
+ "已插入 2200/10000 条\n",
127
+ "已插入 2220/10000 条\n",
128
+ "已插入 2240/10000 条\n",
129
+ "已插入 2260/10000 条\n",
130
+ "已插入 2280/10000 条\n",
131
+ "已插入 2300/10000 条\n",
132
+ "已插入 2320/10000 条\n",
133
+ "已插入 2340/10000 条\n",
134
+ "已插入 2360/10000 条\n",
135
+ "已插入 2380/10000 条\n",
136
+ "已插入 2400/10000 条\n",
137
+ "已插入 2420/10000 条\n",
138
+ "已插入 2440/10000 条\n",
139
+ "已插入 2460/10000 条\n",
140
+ "已插入 2480/10000 条\n",
141
+ "已插入 2500/10000 条\n",
142
+ "已插入 2520/10000 条\n",
143
+ "已插入 2540/10000 条\n",
144
+ "已插入 2560/10000 条\n",
145
+ "已插入 2580/10000 条\n",
146
+ "已插入 2600/10000 条\n",
147
+ "已插入 2620/10000 条\n",
148
+ "已插入 2640/10000 条\n",
149
+ "已插入 2660/10000 条\n",
150
+ "已插入 2680/10000 条\n",
151
+ "已插入 2700/10000 条\n",
152
+ "已插入 2720/10000 条\n",
153
+ "已插入 2740/10000 条\n",
154
+ "已插入 2760/10000 条\n",
155
+ "已插入 2780/10000 条\n",
156
+ "已插入 2800/10000 条\n",
157
+ "已插入 2820/10000 条\n",
158
+ "已插入 2840/10000 条\n",
159
+ "已插入 2860/10000 条\n",
160
+ "已插入 2880/10000 条\n",
161
+ "已插入 2900/10000 条\n",
162
+ "已插入 2920/10000 条\n",
163
+ "已插入 2940/10000 条\n",
164
+ "已插入 2960/10000 条\n",
165
+ "已插入 2980/10000 条\n",
166
+ "已插入 3000/10000 条\n",
167
+ "已插入 3020/10000 条\n",
168
+ "已插入 3040/10000 条\n",
169
+ "已插入 3060/10000 条\n",
170
+ "已插入 3080/10000 条\n",
171
+ "已插入 3100/10000 条\n",
172
+ "已插入 3120/10000 条\n",
173
+ "已插入 3140/10000 条\n",
174
+ "已插入 3160/10000 条\n",
175
+ "已插入 3180/10000 条\n",
176
+ "已插入 3200/10000 条\n",
177
+ "已插入 3220/10000 条\n",
178
+ "已插入 3240/10000 条\n",
179
+ "已插入 3260/10000 条\n",
180
+ "已插入 3280/10000 条\n",
181
+ "已插入 3300/10000 条\n",
182
+ "已插入 3320/10000 条\n",
183
+ "已插入 3340/10000 条\n",
184
+ "已插入 3360/10000 条\n",
185
+ "已插入 3380/10000 条\n",
186
+ "已插入 3400/10000 条\n",
187
+ "已插入 3420/10000 条\n",
188
+ "已插入 3440/10000 条\n",
189
+ "已插入 3460/10000 条\n",
190
+ "已插入 3480/10000 条\n",
191
+ "已插入 3500/10000 条\n",
192
+ "已插入 3520/10000 条\n",
193
+ "已插入 3540/10000 条\n",
194
+ "已插入 3560/10000 条\n",
195
+ "已插入 3580/10000 条\n",
196
+ "已插入 3600/10000 条\n",
197
+ "已插入 3620/10000 条\n",
198
+ "已插入 3640/10000 条\n",
199
+ "已插入 3660/10000 条\n",
200
+ "已插入 3680/10000 条\n",
201
+ "已插入 3700/10000 条\n",
202
+ "已插入 3720/10000 条\n",
203
+ "已插入 3740/10000 条\n",
204
+ "已插入 3760/10000 条\n",
205
+ "已插入 3780/10000 条\n",
206
+ "已插入 3800/10000 条\n",
207
+ "已插入 3820/10000 条\n",
208
+ "已插入 3840/10000 条\n",
209
+ "已插入 3860/10000 条\n",
210
+ "已插入 3880/10000 条\n",
211
+ "已插入 3900/10000 条\n",
212
+ "已插入 3920/10000 条\n",
213
+ "已插入 3940/10000 条\n",
214
+ "已插入 3960/10000 条\n",
215
+ "已插入 3980/10000 条\n",
216
+ "已插入 4000/10000 条\n",
217
+ "已插入 4020/10000 条\n",
218
+ "已插入 4040/10000 条\n",
219
+ "已插入 4060/10000 条\n",
220
+ "已插入 4080/10000 条\n",
221
+ "已插入 4100/10000 条\n",
222
+ "已插入 4120/10000 条\n",
223
+ "已插入 4140/10000 条\n",
224
+ "已插入 4160/10000 条\n",
225
+ "已插入 4180/10000 条\n",
226
+ "已插入 4200/10000 条\n",
227
+ "已插入 4220/10000 条\n",
228
+ "已插入 4240/10000 条\n",
229
+ "已插入 4260/10000 条\n",
230
+ "已插入 4280/10000 条\n",
231
+ "已插入 4300/10000 条\n",
232
+ "已插入 4320/10000 条\n",
233
+ "已插入 4340/10000 条\n",
234
+ "已插入 4360/10000 条\n",
235
+ "已插入 4380/10000 条\n",
236
+ "已插入 4400/10000 条\n",
237
+ "已插入 4420/10000 条\n",
238
+ "已插入 4440/10000 条\n",
239
+ "已插入 4460/10000 条\n",
240
+ "已插入 4480/10000 条\n",
241
+ "已插入 4500/10000 条\n",
242
+ "已插入 4520/10000 条\n",
243
+ "已插入 4540/10000 条\n",
244
+ "已插入 4560/10000 条\n",
245
+ "已插入 4580/10000 条\n",
246
+ "已插入 4600/10000 条\n",
247
+ "已插入 4620/10000 条\n",
248
+ "已插入 4640/10000 条\n",
249
+ "已插入 4660/10000 条\n",
250
+ "已插入 4680/10000 条\n",
251
+ "已插入 4700/10000 条\n",
252
+ "已插入 4720/10000 条\n",
253
+ "已插入 4740/10000 条\n",
254
+ "已插入 4760/10000 条\n",
255
+ "已插入 4780/10000 条\n",
256
+ "已插入 4800/10000 条\n",
257
+ "已插入 4820/10000 条\n",
258
+ "已插入 4840/10000 条\n",
259
+ "已插入 4860/10000 条\n",
260
+ "已插入 4880/10000 条\n",
261
+ "已插入 4900/10000 条\n",
262
+ "已插入 4920/10000 条\n",
263
+ "已插入 4940/10000 条\n",
264
+ "已插入 4960/10000 条\n",
265
+ "已插入 4980/10000 条\n",
266
+ "已插入 5000/10000 条\n",
267
+ "已插入 5020/10000 条\n",
268
+ "已插入 5040/10000 条\n",
269
+ "已插入 5060/10000 条\n",
270
+ "已插入 5080/10000 条\n",
271
+ "已插入 5100/10000 条\n",
272
+ "已插入 5120/10000 条\n",
273
+ "已插入 5140/10000 条\n",
274
+ "已插入 5160/10000 条\n",
275
+ "已插入 5180/10000 条\n",
276
+ "已插入 5200/10000 条\n",
277
+ "已插入 5220/10000 条\n",
278
+ "已插入 5240/10000 条\n",
279
+ "已插入 5260/10000 条\n",
280
+ "已插入 5280/10000 条\n",
281
+ "已插入 5300/10000 条\n",
282
+ "已插入 5320/10000 条\n",
283
+ "已插入 5340/10000 条\n",
284
+ "���插入 5360/10000 条\n",
285
+ "已插入 5380/10000 条\n",
286
+ "已插入 5400/10000 条\n",
287
+ "已插入 5420/10000 条\n",
288
+ "已插入 5440/10000 条\n",
289
+ "已插入 5460/10000 条\n",
290
+ "已插入 5480/10000 条\n",
291
+ "已插入 5500/10000 条\n",
292
+ "已插入 5520/10000 条\n",
293
+ "已插入 5540/10000 条\n",
294
+ "已插入 5560/10000 条\n",
295
+ "已插入 5580/10000 条\n",
296
+ "已插入 5600/10000 条\n",
297
+ "已插入 5620/10000 条\n",
298
+ "已插入 5640/10000 条\n",
299
+ "已插入 5660/10000 条\n",
300
+ "已插入 5680/10000 条\n",
301
+ "已插入 5700/10000 条\n",
302
+ "已插入 5720/10000 条\n",
303
+ "已插入 5740/10000 条\n",
304
+ "已插入 5760/10000 条\n",
305
+ "已插入 5780/10000 条\n",
306
+ "已插入 5800/10000 条\n",
307
+ "已插入 5820/10000 条\n",
308
+ "已插入 5840/10000 条\n",
309
+ "已插入 5860/10000 条\n",
310
+ "已插入 5880/10000 条\n",
311
+ "已插入 5900/10000 条\n",
312
+ "已插入 5920/10000 条\n",
313
+ "已插入 5940/10000 条\n",
314
+ "已插入 5960/10000 条\n",
315
+ "已插入 5980/10000 条\n",
316
+ "已插入 6000/10000 条\n",
317
+ "已插入 6020/10000 条\n",
318
+ "已插入 6040/10000 条\n",
319
+ "已插入 6060/10000 条\n",
320
+ "已插入 6080/10000 条\n",
321
+ "已插入 6100/10000 条\n",
322
+ "已插入 6120/10000 条\n",
323
+ "已插入 6140/10000 条\n",
324
+ "已插入 6160/10000 条\n",
325
+ "已插入 6180/10000 条\n",
326
+ "已插入 6200/10000 条\n",
327
+ "已插入 6220/10000 条\n",
328
+ "已插入 6240/10000 条\n",
329
+ "已插入 6260/10000 条\n",
330
+ "已插入 6280/10000 条\n",
331
+ "已插入 6300/10000 条\n",
332
+ "已插入 6320/10000 条\n",
333
+ "已插入 6340/10000 条\n",
334
+ "已插入 6360/10000 条\n",
335
+ "已插入 6380/10000 条\n",
336
+ "已插入 6400/10000 条\n",
337
+ "已插入 6420/10000 条\n",
338
+ "已插入 6440/10000 条\n",
339
+ "已插入 6460/10000 条\n",
340
+ "已插入 6480/10000 条\n",
341
+ "已插入 6500/10000 条\n",
342
+ "已插入 6520/10000 条\n",
343
+ "已插入 6540/10000 条\n",
344
+ "已插入 6560/10000 条\n",
345
+ "已插入 6580/10000 条\n",
346
+ "已插入 6600/10000 条\n",
347
+ "已插入 6620/10000 条\n",
348
+ "已插入 6640/10000 条\n",
349
+ "已插入 6660/10000 条\n",
350
+ "已插入 6680/10000 条\n",
351
+ "已插入 6700/10000 条\n",
352
+ "已插入 6720/10000 条\n",
353
+ "已插入 6740/10000 条\n",
354
+ "已插入 6760/10000 条\n",
355
+ "已插入 6780/10000 条\n",
356
+ "已插入 6800/10000 条\n",
357
+ "已插入 6820/10000 条\n",
358
+ "已插入 6840/10000 条\n",
359
+ "已插入 6860/10000 条\n",
360
+ "已插入 6880/10000 条\n",
361
+ "已插入 6900/10000 条\n",
362
+ "已插入 6920/10000 条\n",
363
+ "已插入 6940/10000 条\n",
364
+ "已插入 6960/10000 条\n",
365
+ "已插入 6980/10000 条\n",
366
+ "已插入 7000/10000 条\n",
367
+ "已插入 7020/10000 条\n",
368
+ "已插入 7040/10000 条\n",
369
+ "已插入 7060/10000 条\n",
370
+ "已插入 7080/10000 条\n",
371
+ "已插入 7100/10000 条\n",
372
+ "已插入 7120/10000 条\n",
373
+ "已插入 7140/10000 条\n",
374
+ "已插入 7160/10000 条\n",
375
+ "已插入 7180/10000 条\n",
376
+ "已插入 7200/10000 条\n",
377
+ "已插入 7220/10000 条\n",
378
+ "已插入 7240/10000 条\n",
379
+ "已插入 7260/10000 条\n",
380
+ "已插入 7280/10000 条\n",
381
+ "已插入 7300/10000 条\n",
382
+ "已插入 7320/10000 条\n",
383
+ "已插入 7340/10000 条\n",
384
+ "已插入 7360/10000 条\n",
385
+ "已插入 7380/10000 条\n",
386
+ "已插入 7400/10000 条\n",
387
+ "已插入 7420/10000 条\n",
388
+ "已插入 7440/10000 条\n",
389
+ "已插入 7460/10000 条\n",
390
+ "已插入 7480/10000 条\n",
391
+ "已插入 7500/10000 条\n",
392
+ "已插入 7520/10000 条\n",
393
+ "已插入 7540/10000 条\n",
394
+ "已插入 7560/10000 条\n",
395
+ "已插入 7580/10000 条\n",
396
+ "已插入 7600/10000 条\n",
397
+ "已插入 7620/10000 条\n",
398
+ "已插入 7640/10000 条\n",
399
+ "已插入 7660/10000 条\n",
400
+ "已插入 7680/10000 条\n",
401
+ "已插入 7700/10000 条\n",
402
+ "已插入 7720/10000 条\n",
403
+ "已插入 7740/10000 条\n",
404
+ "已插入 7760/10000 条\n",
405
+ "已插入 7780/10000 条\n",
406
+ "已插入 7800/10000 条\n",
407
+ "已插入 7820/10000 条\n",
408
+ "已插入 7840/10000 条\n",
409
+ "已插入 7860/10000 条\n",
410
+ "已插入 7880/10000 条\n",
411
+ "已插入 7900/10000 条\n",
412
+ "已插入 7920/10000 条\n",
413
+ "已插入 7940/10000 条\n",
414
+ "已插入 7960/10000 条\n",
415
+ "已插入 7980/10000 条\n",
416
+ "已插入 8000/10000 条\n",
417
+ "已插入 8020/10000 条\n",
418
+ "已插入 8040/10000 条\n",
419
+ "已插入 8060/10000 条\n",
420
+ "已插入 8080/10000 条\n",
421
+ "已插入 8100/10000 条\n",
422
+ "已插入 8120/10000 条\n",
423
+ "已插入 8140/10000 条\n",
424
+ "已插入 8160/10000 条\n",
425
+ "已插入 8180/10000 条\n",
426
+ "已插入 8200/10000 条\n",
427
+ "已插入 8220/10000 条\n",
428
+ "已插入 8240/10000 条\n",
429
+ "已插入 8260/10000 条\n",
430
+ "已插入 8280/10000 条\n",
431
+ "已插入 8300/10000 条\n",
432
+ "已插入 8320/10000 条\n",
433
+ "已插入 8340/10000 条\n",
434
+ "已插入 8360/10000 条\n",
435
+ "已插入 8380/10000 条\n",
436
+ "已插入 8400/10000 条\n",
437
+ "已插入 8420/10000 条\n",
438
+ "已插入 8440/10000 条\n",
439
+ "已插入 8460/10000 条\n",
440
+ "已插入 8480/10000 条\n",
441
+ "已插入 8500/10000 条\n",
442
+ "已插入 8520/10000 条\n",
443
+ "已插入 8540/10000 条\n",
444
+ "已插入 8560/10000 条\n",
445
+ "已插入 8580/10000 条\n",
446
+ "已插入 8600/10000 条\n",
447
+ "已插入 8620/10000 条\n",
448
+ "已插入 8640/10000 条\n",
449
+ "已插入 8660/10000 条\n",
450
+ "已插入 8680/10000 条\n",
451
+ "已插入 8700/10000 条\n",
452
+ "已插入 8720/10000 条\n",
453
+ "已插入 8740/10000 条\n",
454
+ "已插入 8760/10000 条\n",
455
+ "已插入 8780/10000 条\n",
456
+ "已插入 8800/10000 条\n",
457
+ "已插入 8820/10000 条\n",
458
+ "已插入 8840/10000 条\n",
459
+ "已插入 8860/10000 条\n",
460
+ "已插入 8880/10000 条\n",
461
+ "已插入 8900/10000 条\n",
462
+ "已插入 8920/10000 条\n",
463
+ "已插入 8940/10000 条\n",
464
+ "已插入 8960/10000 条\n",
465
+ "已插入 8980/10000 条\n",
466
+ "已插入 9000/10000 条\n",
467
+ "已插入 9020/10000 条\n",
468
+ "已插入 9040/10000 条\n",
469
+ "已插入 9060/10000 条\n",
470
+ "已插入 9080/10000 条\n",
471
+ "已插入 9100/10000 条\n",
472
+ "已插入 9120/10000 条\n",
473
+ "已插入 9140/10000 条\n",
474
+ "已插入 9160/10000 条\n",
475
+ "已插入 9180/10000 条\n",
476
+ "已插入 9200/10000 条\n",
477
+ "已插入 9220/10000 条\n",
478
+ "已插入 9240/10000 条\n",
479
+ "已插入 9260/10000 条\n",
480
+ "已插入 9280/10000 条\n",
481
+ "已插入 9300/10000 条\n",
482
+ "已插入 9320/10000 条\n",
483
+ "已插入 9340/10000 条\n",
484
+ "已插入 9360/10000 条\n",
485
+ "已插入 9380/10000 条\n",
486
+ "已插入 9400/10000 条\n",
487
+ "已插入 9420/10000 条\n",
488
+ "已插入 9440/10000 条\n",
489
+ "已插入 9460/10000 条\n",
490
+ "已插入 9480/10000 条\n",
491
+ "已插入 9500/10000 条\n",
492
+ "已插入 9520/10000 条\n",
493
+ "已插入 9540/10000 条\n",
494
+ "已插入 9560/10000 条\n",
495
+ "已插入 9580/10000 条\n",
496
+ "已插入 9600/10000 条\n",
497
+ "已插入 9620/10000 条\n",
498
+ "已插入 9640/10000 条\n",
499
+ "已插入 9660/10000 条\n",
500
+ "已插入 9680/10000 条\n",
501
+ "已插入 9700/10000 条\n",
502
+ "已插入 9720/10000 条\n",
503
+ "已插入 9740/10000 条\n",
504
+ "已插入 9760/10000 条\n",
505
+ "已插入 9780/10000 条\n",
506
+ "已插入 9800/10000 条\n",
507
+ "已插入 9820/10000 条\n",
508
+ "已插入 9840/10000 条\n",
509
+ "已插入 9860/10000 条\n",
510
+ "已插入 9880/10000 条\n",
511
+ "已插入 9900/10000 条\n",
512
+ "已插入 9920/10000 条\n",
513
+ "已插入 9940/10000 条\n",
514
+ "已插入 9960/10000 条\n",
515
+ "已插入 9980/10000 条\n",
516
+ "已插入 10000/10000 条\n",
517
+ "插入完成!共插入 10000 条蛋白质序列到 protein_seq 表。\n",
518
+ "数据库连接已关闭。\n"
519
+ ]
520
+ }
521
+ ],
522
+ "source": [
523
+ "import pymysql\n",
524
+ "from Bio import SeqIO\n",
525
+ "\n",
526
+ "# ==================== 配置区 ====================\n",
527
+ "# FASTA 文件路径\n",
528
+ "FASTA_FILE = \"sampled_10000_proteins.fasta\"\n",
529
+ "\n",
530
+ "# 阿里云 RDS MySQL 连接信息\n",
531
+ "HOST = \"\"\n",
532
+ "PORT = 3306\n",
533
+ "USER = \"\"\n",
534
+ "PASSWORD = \"\"\n",
535
+ "DATABASE = \"ionet\"\n",
536
+ "CHARSET = \"utf8mb4\"\n",
537
+ "\n",
538
+ "# 插入的默认值\n",
539
+ "IS_PROCESS = 0\n",
540
+ "# ===============================================\n",
541
+ "\n",
542
+ "# 连接数据库\n",
543
+ "connection = pymysql.connect(\n",
544
+ " host=HOST,\n",
545
+ " port=PORT,\n",
546
+ " user=USER,\n",
547
+ " password=PASSWORD,\n",
548
+ " database=DATABASE,\n",
549
+ " charset=CHARSET,\n",
550
+ " autocommit=True # 自动提交,也可手动控制\n",
551
+ ")\n",
552
+ "\n",
553
+ "try:\n",
554
+ " with connection.cursor() as cursor:\n",
555
+ " # 插入 SQL\n",
556
+ " insert_sql = \"\"\"\n",
557
+ " INSERT INTO protein_seq (sentence1, is_process)\n",
558
+ " VALUES (%s, %s)\n",
559
+ " \"\"\"\n",
560
+ "\n",
561
+ " # 读取 FASTA 文件\n",
562
+ " print(f\"正在读取 {FASTA_FILE} ...\")\n",
563
+ " records = list(SeqIO.parse(FASTA_FILE, \"fasta\"))\n",
564
+ "\n",
565
+ " print(f\"共找到 {len(records)} 条蛋白质序列,开始插入...\")\n",
566
+ "\n",
567
+ " count = 0\n",
568
+ " for record in records:\n",
569
+ " sequence = str(record.seq).strip() # 转为字符串并去除首尾空白\n",
570
+ "\n",
571
+ " # 可选:如果想过滤过长序列(配合 GPT-2 512 aa 限制)\n",
572
+ " # if len(sequence) > 512:\n",
573
+ " # sequence = sequence[:512]\n",
574
+ "\n",
575
+ " cursor.execute(insert_sql, (sequence, IS_PROCESS))\n",
576
+ " count += 1\n",
577
+ "\n",
578
+ " if count % 20 == 0:\n",
579
+ " print(f\"已插入 {count}/{len(records)} 条\")\n",
580
+ "\n",
581
+ " print(f\"插入完成!共插入 {count} 条蛋白质序列到 protein_seq 表。\")\n",
582
+ "\n",
583
+ "except Exception as e:\n",
584
+ " print(f\"插入失败:{e}\")\n",
585
+ " connection.rollback() # 出错时回滚\n",
586
+ "finally:\n",
587
+ " connection.close()\n",
588
+ " print(\"数据库连接已关闭。\")"
589
+ ]
590
+ },
591
+ {
592
+ "cell_type": "code",
593
+ "execution_count": 1,
594
+ "id": "52130081-9d9a-4696-ac0b-cdcef9f6f08a",
595
+ "metadata": {},
596
+ "outputs": [
597
+ {
598
+ "name": "stdout",
599
+ "output_type": "stream",
600
+ "text": [
601
+ "Looking in indexes: http://mirrors.aliyun.com/pypi/simple\n",
602
+ "Collecting pymysql\n",
603
+ " Downloading http://mirrors.aliyun.com/pypi/packages/7c/4c/ad33b92b9864cbde84f259d5df035a6447f91891f5be77788e2a3892bce3/pymysql-1.1.2-py3-none-any.whl (45 kB)\n",
604
+ "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m45.3/45.3 kB\u001b[0m \u001b[31m1.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
605
+ "\u001b[?25hInstalling collected packages: pymysql\n",
606
+ "Successfully installed pymysql-1.1.2\n",
607
+ "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
608
+ "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n"
609
+ ]
610
+ }
611
+ ],
612
+ "source": [
613
+ "pip install pymysql"
614
+ ]
615
+ },
616
+ {
617
+ "cell_type": "code",
618
+ "execution_count": null,
619
+ "id": "34864573-6348-4196-af14-4f7ea5324f61",
620
+ "metadata": {},
621
+ "outputs": [],
622
+ "source": []
623
+ }
624
+ ],
625
+ "metadata": {
626
+ "kernelspec": {
627
+ "display_name": "Python 3 (ipykernel)",
628
+ "language": "python",
629
+ "name": "python3"
630
+ },
631
+ "language_info": {
632
+ "codemirror_mode": {
633
+ "name": "ipython",
634
+ "version": 3
635
+ },
636
+ "file_extension": ".py",
637
+ "mimetype": "text/x-python",
638
+ "name": "python",
639
+ "nbconvert_exporter": "python",
640
+ "pygments_lexer": "ipython3",
641
+ "version": "3.12.3"
642
+ }
643
+ },
644
+ "nbformat": 4,
645
+ "nbformat_minor": 5
646
+ }
1-data/mysql_part/.ipynb_checkpoints/2-get_non_homologous_pairs-checkpoint.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pymysql
2
+ import hashlib
3
+ from Bio import SeqIO
4
+ from Bio.Align import PairwiseAligner
5
+ import random
6
+ import time
7
+
8
+ # ==================== 数据库配置 ====================
9
+ HOST = ""
10
+ PORT = 3306
11
+ USER = ""
12
+ PASSWORD = ""
13
+ DATABASE = "ionet"
14
+ CHARSET = "utf8mb4"
15
+
16
+ # ==================== 负样本生成参数 ====================
17
+ MAX_NEG_PER_QUERY = 5 # 每条查询生成 5 个负样本
18
+ IDENTITY_THRESHOLD = 25.0 # identity < 25% 视为非同源
19
+ LENGTH_TOLERANCE = 0.3 # 长度差异不超过 30%
20
+ MIN_LENGTH = 50
21
+ MAX_TRIALS = 500 # 最多尝试 500 次随机采样
22
+
23
+ # 完整 Swiss-Prot 文件路径
24
+ FULL_SWISSPROT = "uniprot_sprot.fasta"
25
+
26
+ # ==================== 全局比对器 ====================
27
+ aligner = PairwiseAligner(mode='global')
28
+ aligner.match_score = 2
29
+ aligner.mismatch_score = -1
30
+ aligner.gap_score = -5
31
+ aligner.extend_gap_score = -1
32
+
33
+ # ==================== 加载完整 Swiss-Prot ====================
34
+ print("Loading full Swiss-Prot database... (this may take a minute)")
35
+ full_records = [r for r in SeqIO.parse(FULL_SWISSPROT, "fasta") if len(r.seq) >= MIN_LENGTH]
36
+ print(f"Loaded {len(full_records)} sequences from Swiss-Prot.")
37
+
38
+ # ==================== 数据库连接 ====================
39
+ connection = pymysql.connect(
40
+ host=HOST,
41
+ port=PORT,
42
+ user=USER,
43
+ password=PASSWORD,
44
+ database=DATABASE,
45
+ charset=CHARSET,
46
+ autocommit=False
47
+ )
48
+
49
+ def compute_pair_md5(seq1, seq2):
50
+ """有序拼接计算 MD5,确保去重一致"""
51
+ if seq1 < seq2:
52
+ combined = seq1 + seq2
53
+ else:
54
+ combined = seq2 + seq1
55
+ return hashlib.md5(combined.encode('utf-8')).hexdigest()
56
+
57
+ try:
58
+ with connection.cursor() as cursor:
59
+ while True:
60
+ # 1. 取一条 is_dis_process = 0 的序列(用于负样本处理)
61
+ cursor.execute("""
62
+ SELECT id, sentence1
63
+ FROM protein_seq
64
+ WHERE is_dis_process = 0
65
+ ORDER BY id
66
+ LIMIT 1
67
+ """)
68
+ row = cursor.fetchone()
69
+ if row is None:
70
+ print("所有序列的负样本处理已完成,没有剩余 is_dis_process=0 的记录。")
71
+ break
72
+
73
+ query_id_in_db, query_seq = row
74
+ q_len = len(query_seq)
75
+ print(f"\n正在处理负样本 - 数据库 ID={query_id_in_db} (长度={q_len})")
76
+
77
+ found = 0
78
+ trials = 0
79
+
80
+ while found < MAX_NEG_PER_QUERY and trials < MAX_TRIALS:
81
+ trials += 1
82
+ cand_rec = random.choice(full_records)
83
+
84
+ c_seq = str(cand_rec.seq)
85
+ if c_seq == query_seq: # 避免完全相同(极少发生)
86
+ continue
87
+
88
+ c_len = len(c_seq)
89
+
90
+ # 长度过滤
91
+ if abs(q_len - c_len) / ((q_len + c_len) / 2) > LENGTH_TOLERANCE:
92
+ continue
93
+
94
+ # 全局比对
95
+ try:
96
+ alignments = aligner.align(query_seq, c_seq)
97
+ if not alignments:
98
+ continue
99
+ alignment = alignments[0]
100
+
101
+ identical = sum(a == b and a != '-' and b != '-'
102
+ for a, b in zip(str(alignment[0]), str(alignment[1])))
103
+ aligned_len = (len(str(alignment[0])) -
104
+ str(alignment[0]).count('-') -
105
+ str(alignment[1]).count('-'))
106
+ identity = 100.0 * identical / aligned_len if aligned_len > 0 else 0
107
+
108
+ if identity < IDENTITY_THRESHOLD:
109
+ pair_md5 = compute_pair_md5(query_seq, c_seq)
110
+
111
+ # 插入负样本对,label=0
112
+ insert_sql = """
113
+ INSERT IGNORE INTO protein_pair
114
+ (sentence1, sentence2, pair_md5, label)
115
+ VALUES (%s, %s, %s, 0)
116
+ """
117
+ cursor.execute(insert_sql, (query_seq, c_seq, pair_md5))
118
+ if cursor.rowcount > 0:
119
+ found += 1
120
+ print(f" → 插入负样本对 (label=0, identity={identity:.1f}%)")
121
+
122
+ except Exception as e:
123
+ print(f" 比对异常: {e}")
124
+ continue
125
+
126
+ print(f" 本序列共插入 {found} 条负样本对(尝试 {trials} 次)")
127
+
128
+ # 2. 标记为已处理负样本(使用新字段)
129
+ cursor.execute("""
130
+ UPDATE protein_seq
131
+ SET is_dis_process = 1
132
+ WHERE id = %s
133
+ """, (query_id_in_db,))
134
+
135
+ connection.commit()
136
+ print(f" 已将数据库 ID={query_id_in_db} 的负样本处理标记为完成 (is_dis_process=1)\n")
137
+
138
+ # 短暂休眠,避免 CPU 过载
139
+ time.sleep(0.1)
140
+
141
+ except Exception as e:
142
+ print(f"数据库操作异常: {e}")
143
+ connection.rollback()
144
+ finally:
145
+ connection.close()
146
+ print("数据库连接已关闭,负样本生成程序结束。")
1-data/mysql_part/.ipynb_checkpoints/3-get_homology_pairs-checkpoint.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pymysql
2
+ import hashlib
3
+ from Bio.Blast import NCBIWWW
4
+ from Bio.Blast import NCBIXML
5
+ import time
6
+ import random
7
+
8
+ # ==================== 数据库配置 ====================
9
+ HOST = ""
10
+ PORT = 3306
11
+ USER = ""
12
+ PASSWORD = ""
13
+ DATABASE = "ionet"
14
+ CHARSET = "utf8mb4"
15
+
16
+ # ==================== BLAST 参数 ====================
17
+ MAX_HOMOLOGS_PER_QUERY = 5
18
+ E_VALUE_THRESH = 1e-10
19
+ IDENTITY_THRESH = 30.0
20
+ HITLIST_SIZE = 20
21
+
22
+ # ==================== 数据库连接 ====================
23
+ connection = pymysql.connect(
24
+ host=HOST,
25
+ port=PORT,
26
+ user=USER,
27
+ password=PASSWORD,
28
+ database=DATABASE,
29
+ charset=CHARSET,
30
+ autocommit=False, # 手动提交
31
+ cursorclass=pymysql.cursors.DictCursor # 可选:返回字典,便于阅读
32
+ )
33
+
34
+ def compute_pair_md5(seq1, seq2):
35
+ if seq1 < seq2:
36
+ combined = seq1 + seq2
37
+ else:
38
+ combined = seq2 + seq1
39
+ return hashlib.md5(combined.encode('utf-8')).hexdigest()
40
+
41
+ try:
42
+ with connection.cursor() as cursor:
43
+ while True:
44
+ # ==================== 关键修改:随机 + 锁机制 ====================
45
+ try:
46
+ # 开始事务 + 行锁:确保多实例不会同时选中同一行
47
+ cursor.execute("START TRANSACTION")
48
+ cursor.execute("""
49
+ SELECT id, sentence1
50
+ FROM protein_seq
51
+ WHERE is_process = 0
52
+ ORDER BY RAND()
53
+ LIMIT 1
54
+ FOR UPDATE -- 重要!锁住选中的行,直到事务结束
55
+ """)
56
+ row = cursor.fetchone()
57
+
58
+ if row is None:
59
+ connection.commit() # 提交空事务
60
+ print("所有序列已处理完成,没有剩余 is_process=0 的记录。")
61
+ break
62
+
63
+ query_id_in_db = row['id']
64
+ query_seq = row['sentence1']
65
+
66
+ # 立即提交锁事务,释放行锁(但还没标记处理)
67
+ connection.commit()
68
+
69
+ except Exception as e:
70
+ connection.rollback()
71
+ print(f"获取随机序列时出错: {e}")
72
+ time.sleep(5)
73
+ continue
74
+ # ==============================================================
75
+
76
+ print(f"\n正在处理数据库 ID={query_id_in_db} 的蛋白序列 (长度={len(query_seq)})")
77
+
78
+ try:
79
+ print(" 正在运行 NCBI BLAST...")
80
+ result_handle = NCBIWWW.qblast(
81
+ program="blastp",
82
+ database="swissprot",
83
+ sequence=query_seq,
84
+ expect=E_VALUE_THRESH,
85
+ hitlist_size=HITLIST_SIZE
86
+ )
87
+
88
+ blast_records = NCBIXML.parse(result_handle)
89
+ blast_record = next(blast_records, None)
90
+
91
+ inserted_count = 0
92
+ if blast_record:
93
+ for alignment in blast_record.alignments:
94
+ if inserted_count >= MAX_HOMOLOGS_PER_QUERY:
95
+ break
96
+ hit_id = alignment.hit_id
97
+
98
+ for hsp in alignment.hsps:
99
+ if inserted_count >= MAX_HOMOLOGS_PER_QUERY:
100
+ break
101
+
102
+ hit_seq_aligned = hsp.sbjct
103
+ identity = (hsp.identities / hsp.align_length) * 100
104
+
105
+ if identity < IDENTITY_THRESH:
106
+ continue
107
+
108
+ # 排除自身(粗略判断)
109
+ hit_acc = hit_id.split("|")[1] if '|' in hit_id else ""
110
+ if hit_acc and query_seq.startswith(hit_acc):
111
+ continue
112
+
113
+ pair_md5 = compute_pair_md5(query_seq, hit_seq_aligned)
114
+
115
+ insert_sql = """
116
+ INSERT IGNORE INTO protein_pair
117
+ (sentence1, sentence2, pair_md5, label)
118
+ VALUES (%s, %s, %s, 1)
119
+ """
120
+ cursor.execute(insert_sql, (query_seq, hit_seq_aligned, pair_md5))
121
+ if cursor.rowcount > 0:
122
+ inserted_count += 1
123
+ print(f" → 插入正样本对 (label=1, identity={identity:.1f}%)")
124
+
125
+ print(f" 本序列共插入 {inserted_count} 条正样本对。")
126
+
127
+ except Exception as e:
128
+ print(f" BLAST 出错: {e},仍将标记为已处理(避免重复尝试)")
129
+
130
+ # ==================== 标记为已处理 ====================
131
+ try:
132
+ cursor.execute("""
133
+ UPDATE protein_seq
134
+ SET is_process = 1
135
+ WHERE id = %s
136
+ """, (query_id_in_db,))
137
+ connection.commit()
138
+ print(f" 已将数据库 ID={query_id_in_db} 标记为已处理 (is_process=1)")
139
+ except Exception as e:
140
+ connection.rollback()
141
+ print(f" 更新 is_process 失败: {e}")
142
+
143
+ # NCBI 限速等待
144
+ sleep_time = 3 + random.uniform(0, 3)
145
+ print(f" 等待 {sleep_time:.1f} 秒避免 NCBI 限速...\n")
146
+ time.sleep(sleep_time)
147
+
148
+ except Exception as e:
149
+ print(f"程序异常: {e}")
150
+ connection.rollback()
151
+ finally:
152
+ connection.close()
153
+ print("数据库连接已关闭,程序结束。")
1-data/mysql_part/.ipynb_checkpoints/4-get_ft_protein_data_250a-checkpoint.ipynb ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "680e74ed-1fe1-455b-a599-7bc9fcb30924",
7
+ "metadata": {},
8
+ "outputs": [
9
+ {
10
+ "name": "stdout",
11
+ "output_type": "stream",
12
+ "text": [
13
+ "正在抽取正样本(label=1),目标 10000 条,长度 40–250 aa...\n"
14
+ ]
15
+ },
16
+ {
17
+ "name": "stderr",
18
+ "output_type": "stream",
19
+ "text": [
20
+ "/tmp/ipykernel_9166/785285738.py:32: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
21
+ " pos_df = pd.read_sql(\"\"\"\n"
22
+ ]
23
+ },
24
+ {
25
+ "name": "stdout",
26
+ "output_type": "stream",
27
+ "text": [
28
+ "正在抽取负样本(label=0),目标 10000 条,长度 40–250 aa...\n"
29
+ ]
30
+ },
31
+ {
32
+ "name": "stderr",
33
+ "output_type": "stream",
34
+ "text": [
35
+ "/tmp/ipykernel_9166/785285738.py:44: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
36
+ " neg_df = pd.read_sql(\"\"\"\n"
37
+ ]
38
+ },
39
+ {
40
+ "name": "stdout",
41
+ "output_type": "stream",
42
+ "text": [
43
+ "\n",
44
+ "实际抽取完成:\n",
45
+ " 正样本 (label=1): 10000 条\n",
46
+ " 负样本 (label=0): 10000 条\n",
47
+ " 总样本: 20000 条\n",
48
+ "\n",
49
+ "🎉 完成!共生成 20000 条平衡样本(长度 40–250 aa,长度差 ≤ 50 aa)\n",
50
+ "CSV 文件已保存至:protein_pair_20k_length_restricted_balanced.csv\n",
51
+ "\n",
52
+ "前5行预览:\n",
53
+ " sentence1 \\\n",
54
+ "0 MAKPAARPRKKVKKTVVDGIAHIHASFNNTIVTITDRQGNALSWAT... \n",
55
+ "1 MKIGIIGAMEPEVAHLIAAMTNATSQTIAGIEFIAGTLAGKDVVVT... \n",
56
+ "2 MSDSPRRFPTPLLLTGVMGVLILIALLTFTLMRSWDNAPDQSALLG... \n",
57
+ "3 MKWSEVFHDITTRHDFQAMHDFLEKEYTTQIVYPDKKNIYQAFDLT... \n",
58
+ "4 MSSVFEIVNQARRKNKLKRELLDNEKKVRDNRKRVDLLENLLDYIK... \n",
59
+ "\n",
60
+ " sentence2 label \n",
61
+ "0 MAALIVSRLARRGWLWKLPLATRREFWSRSRKEKEPVVAETVEEVK... 0 \n",
62
+ "1 MKIGIIGAMEPEVAHLIAAMTNATSQTIAGIEFIAGTLAGKDVVVT... 1 \n",
63
+ "2 MSDSPRRFPTPLLLTGVMGVLILIALLTFTLMRSWDNAPDQSALLG... 1 \n",
64
+ "3 MEWSQIFHDITTKHDFKAMHDFLEKEYSTAIVYPDRENIYQAFDLT... 1 \n",
65
+ "4 MSSGGLLLLLGLLTLWEVLTPVSSKDRPKFCELLPDTGSCEDFTGA... 0 \n",
66
+ "数据库连接已关闭。\n"
67
+ ]
68
+ }
69
+ ],
70
+ "source": [
71
+ "import pymysql\n",
72
+ "import pandas as pd\n",
73
+ "\n",
74
+ "# ==================== 数据库配置 ====================\n",
75
+ "HOST = \"\"\n",
76
+ "PORT = 3306\n",
77
+ "USER = \"\"\n",
78
+ "PASSWORD = \"\"\n",
79
+ "DATABASE = \"ionet\"\n",
80
+ "CHARSET = \"utf8mb4\"\n",
81
+ "\n",
82
+ "# ==================== 参数 ====================\n",
83
+ "POSITIVE_SAMPLES = 10000 # 正样本目标数量\n",
84
+ "NEGATIVE_SAMPLES = 10000 # 负样本目标数量\n",
85
+ "MIN_LENGTH = 40\n",
86
+ "MAX_LENGTH = 250\n",
87
+ "LENGTH_DIFF_MAX = 50 # 新增:一对序列长度差不超过 50 aa(推荐!)\n",
88
+ "OUTPUT_CSV = \"protein_pair_20k_length_restricted_balanced.csv\"\n",
89
+ "\n",
90
+ "# ==================== 连接数据库 ====================\n",
91
+ "connection = pymysql.connect(\n",
92
+ " host=HOST,\n",
93
+ " port=PORT,\n",
94
+ " user=USER,\n",
95
+ " password=PASSWORD,\n",
96
+ " database=DATABASE,\n",
97
+ " charset=CHARSET\n",
98
+ ")\n",
99
+ "\n",
100
+ "try:\n",
101
+ " print(f\"正在抽取正样本(label=1),目标 {POSITIVE_SAMPLES} 条,长度 40–250 aa...\")\n",
102
+ " pos_df = pd.read_sql(\"\"\"\n",
103
+ " SELECT sentence1, sentence2, label\n",
104
+ " FROM protein_pair\n",
105
+ " WHERE label = 1\n",
106
+ " AND LENGTH(sentence1) BETWEEN %s AND %s\n",
107
+ " AND LENGTH(sentence2) BETWEEN %s AND %s\n",
108
+ " AND ABS(LENGTH(sentence1) - LENGTH(sentence2)) <= %s\n",
109
+ " ORDER BY RAND()\n",
110
+ " LIMIT %s\n",
111
+ " \"\"\", connection, params=(MIN_LENGTH, MAX_LENGTH, MIN_LENGTH, MAX_LENGTH, LENGTH_DIFF_MAX, POSITIVE_SAMPLES))\n",
112
+ "\n",
113
+ " print(f\"正在抽取负样本(label=0),目标 {NEGATIVE_SAMPLES} 条,长度 40–250 aa...\")\n",
114
+ " neg_df = pd.read_sql(\"\"\"\n",
115
+ " SELECT sentence1, sentence2, label\n",
116
+ " FROM protein_pair\n",
117
+ " WHERE label = 0\n",
118
+ " AND LENGTH(sentence1) BETWEEN %s AND %s\n",
119
+ " AND LENGTH(sentence2) BETWEEN %s AND %s\n",
120
+ " AND ABS(LENGTH(sentence1) - LENGTH(sentence2)) <= %s\n",
121
+ " ORDER BY RAND()\n",
122
+ " LIMIT %s\n",
123
+ " \"\"\", connection, params=(MIN_LENGTH, MAX_LENGTH, MIN_LENGTH, MAX_LENGTH, LENGTH_DIFF_MAX, NEGATIVE_SAMPLES))\n",
124
+ "\n",
125
+ " # 检查实际抽取数量\n",
126
+ " actual_pos = len(pos_df)\n",
127
+ " actual_neg = len(neg_df)\n",
128
+ " print(f\"\\n实际抽取完成:\")\n",
129
+ " print(f\" 正样本 (label=1): {actual_pos} 条\")\n",
130
+ " print(f\" 负样本 (label=0): {actual_neg} 条\")\n",
131
+ " print(f\" 总样本: {actual_pos + actual_neg} 条\")\n",
132
+ "\n",
133
+ " if actual_pos < POSITIVE_SAMPLES:\n",
134
+ " print(f\" 警告:正样本不足,仅抽到 {actual_pos} 条(可能数据库中符合条件的样本较少)\")\n",
135
+ " if actual_neg < NEGATIVE_SAMPLES:\n",
136
+ " print(f\" 警告:负样本不足,仅抽到 {actual_neg} 条\")\n",
137
+ "\n",
138
+ " # 合并并打乱\n",
139
+ " sample_df = pd.concat([pos_df, neg_df], ignore_index=True)\n",
140
+ " sample_df = sample_df.sample(frac=1, random_state=42).reset_index(drop=True)\n",
141
+ "\n",
142
+ " # 保存 CSV\n",
143
+ " sample_df.to_csv(OUTPUT_CSV, index=False, encoding=\"utf-8\")\n",
144
+ "\n",
145
+ " print(f\"\\n🎉 完成!共生成 {len(sample_df)} 条平衡样本(长度 40–250 aa,长度差 ≤ {LENGTH_DIFF_MAX} aa)\")\n",
146
+ " print(f\"CSV 文件已保存至:{OUTPUT_CSV}\")\n",
147
+ " print(\"\\n前5行预览:\")\n",
148
+ " print(sample_df.head())\n",
149
+ "\n",
150
+ "except Exception as e:\n",
151
+ " print(f\"错误:{e}\")\n",
152
+ "finally:\n",
153
+ " connection.close()\n",
154
+ " print(\"数据库连接已关闭。\")"
155
+ ]
156
+ },
157
+ {
158
+ "cell_type": "code",
159
+ "execution_count": null,
160
+ "id": "f036071a-269e-4684-a292-46fcbce0c384",
161
+ "metadata": {},
162
+ "outputs": [],
163
+ "source": []
164
+ }
165
+ ],
166
+ "metadata": {
167
+ "kernelspec": {
168
+ "display_name": "Python 3 (ipykernel)",
169
+ "language": "python",
170
+ "name": "python3"
171
+ },
172
+ "language_info": {
173
+ "codemirror_mode": {
174
+ "name": "ipython",
175
+ "version": 3
176
+ },
177
+ "file_extension": ".py",
178
+ "mimetype": "text/x-python",
179
+ "name": "python",
180
+ "nbconvert_exporter": "python",
181
+ "pygments_lexer": "ipython3",
182
+ "version": "3.12.3"
183
+ }
184
+ },
185
+ "nbformat": 4,
186
+ "nbformat_minor": 5
187
+ }
1-data/mysql_part/.ipynb_checkpoints/4-get_sft_protein_data_full-checkpoint.ipynb ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "680e74ed-1fe1-455b-a599-7bc9fcb30924",
7
+ "metadata": {},
8
+ "outputs": [
9
+ {
10
+ "name": "stdout",
11
+ "output_type": "stream",
12
+ "text": [
13
+ "正在抽取正样本(label=1),目标 10000 条,仅限制长度差 ≤ 50 aa...\n"
14
+ ]
15
+ },
16
+ {
17
+ "name": "stderr",
18
+ "output_type": "stream",
19
+ "text": [
20
+ "/tmp/ipykernel_9297/2781235706.py:30: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
21
+ " pos_df = pd.read_sql(\"\"\"\n"
22
+ ]
23
+ },
24
+ {
25
+ "name": "stdout",
26
+ "output_type": "stream",
27
+ "text": [
28
+ "正在抽取负样本(label=0),目标 10000 条,仅限制长度差 ≤ 50 aa...\n"
29
+ ]
30
+ },
31
+ {
32
+ "name": "stderr",
33
+ "output_type": "stream",
34
+ "text": [
35
+ "/tmp/ipykernel_9297/2781235706.py:40: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
36
+ " neg_df = pd.read_sql(\"\"\"\n"
37
+ ]
38
+ },
39
+ {
40
+ "name": "stdout",
41
+ "output_type": "stream",
42
+ "text": [
43
+ "\n",
44
+ "实际抽取完成:\n",
45
+ " 正样本 (label=1): 10000 条\n",
46
+ " 负样本 (label=0): 10000 条\n",
47
+ " 总样本: 20000 条\n",
48
+ "\n",
49
+ "🎉 完成!共生成 20000 条样本(不限制绝对长度,仅限制一对长度差 ≤ 50 aa)\n",
50
+ "CSV 文件已保存至:protein_pair_20k_no_length_limit_but_diff_limited.csv\n",
51
+ "\n",
52
+ "前5行预览:\n",
53
+ " sentence1 \\\n",
54
+ "0 MSEMRLIVAGAGGRMGRALTRAISETEGVVLTGALESPNSELLGKD... \n",
55
+ "1 ADFKVYWEVPSFLCSKRFKINVTEVLTSHEILVNQGESFNGDKIVI... \n",
56
+ "2 MLQNIRIVLVETSHTGNMGSVARAMKTMGLTNLWLVNPLVKPDSQA... \n",
57
+ "3 MESLKIYNTLAREKQTFVPIEPGRVRMYVCGMTVYDYCHVGHARVM... \n",
58
+ "4 MAVKLKIKKGDSVKVITGDDKGKTGKVLAVYPKTLKVVVEGCKIAK... \n",
59
+ "\n",
60
+ " sentence2 label \n",
61
+ "0 MLQKIILATVVAFVLSLASGPLFIPYLRKLKFGQKVREDGPKSHIK... 0 \n",
62
+ "1 AEFKVYWEVPSFLCSKRFNINVTQVLTSHKILVNQGESFNGDKIVM... 1 \n",
63
+ "2 MLQNIRIVLVETSHTGNMGSVARAMKTMGLTNLWLVNPLVKPDSQA... 1 \n",
64
+ "3 MESLKIYNTLAREKQTFVPIEPGRVRMYVCGMTVYDYCHVGHARVM... 1 \n",
65
+ "4 MPKSKINSPEENFDSSAAAGVDRRTPVKLNASGTPRWYIVIMLGLM... 0 \n",
66
+ "\n",
67
+ "长度统计(供论文参考):\n",
68
+ "sentence1 长度分布:\n",
69
+ "count 20000.000000\n",
70
+ "mean 294.462200\n",
71
+ "std 242.257635\n",
72
+ "min 29.000000\n",
73
+ "25% 147.000000\n",
74
+ "50% 240.000000\n",
75
+ "75% 374.000000\n",
76
+ "max 7639.000000\n",
77
+ "Name: sentence1, dtype: float64\n",
78
+ "\n",
79
+ "sentence2 长度分布:\n",
80
+ "count 20000.000000\n",
81
+ "mean 294.066100\n",
82
+ "std 242.058872\n",
83
+ "min 29.000000\n",
84
+ "25% 148.000000\n",
85
+ "50% 240.000000\n",
86
+ "75% 372.000000\n",
87
+ "max 7639.000000\n",
88
+ "Name: sentence2, dtype: float64\n",
89
+ "\n",
90
+ "长度差分布:\n",
91
+ "count 20000.000000\n",
92
+ "mean 12.787100\n",
93
+ "std 14.561544\n",
94
+ "min 0.000000\n",
95
+ "25% 0.000000\n",
96
+ "50% 7.000000\n",
97
+ "75% 22.000000\n",
98
+ "max 50.000000\n",
99
+ "dtype: float64\n",
100
+ "数据库连接已关闭。\n"
101
+ ]
102
+ }
103
+ ],
104
+ "source": [
105
+ "import pymysql\n",
106
+ "import pandas as pd\n",
107
+ "\n",
108
+ "# ==================== 数据库配置 ====================\n",
109
+ "HOST = \"\"\n",
110
+ "PORT = 3306\n",
111
+ "USER = \"\"\n",
112
+ "PASSWORD = \"\"\n",
113
+ "DATABASE = \"ionet\"\n",
114
+ "CHARSET = \"utf8mb4\"\n",
115
+ "\n",
116
+ "# ==================== 参数 ====================\n",
117
+ "POSITIVE_SAMPLES = 10000 # 正样本目标数量\n",
118
+ "NEGATIVE_SAMPLES = 10000 # 负样本目标数量\n",
119
+ "LENGTH_DIFF_MAX = 50 # 一对序列长度差不超过 50 aa(关键限制)\n",
120
+ "OUTPUT_CSV = \"protein_pair_20k_no_length_limit_but_diff_limited.csv\"\n",
121
+ "\n",
122
+ "# ==================== 连接数据库 ====================\n",
123
+ "connection = pymysql.connect(\n",
124
+ " host=HOST,\n",
125
+ " port=PORT,\n",
126
+ " user=USER,\n",
127
+ " password=PASSWORD,\n",
128
+ " database=DATABASE,\n",
129
+ " charset=CHARSET\n",
130
+ ")\n",
131
+ "\n",
132
+ "try:\n",
133
+ " print(f\"正在抽取正样本(label=1),目标 {POSITIVE_SAMPLES} 条,仅限制长度差 ≤ {LENGTH_DIFF_MAX} aa...\")\n",
134
+ " pos_df = pd.read_sql(\"\"\"\n",
135
+ " SELECT sentence1, sentence2, label\n",
136
+ " FROM protein_pair\n",
137
+ " WHERE label = 1\n",
138
+ " AND ABS(LENGTH(sentence1) - LENGTH(sentence2)) <= %s\n",
139
+ " ORDER BY RAND()\n",
140
+ " LIMIT %s\n",
141
+ " \"\"\", connection, params=(LENGTH_DIFF_MAX, POSITIVE_SAMPLES))\n",
142
+ "\n",
143
+ " print(f\"正在抽取负样本(label=0),目标 {NEGATIVE_SAMPLES} 条,仅限制长度差 ≤ {LENGTH_DIFF_MAX} aa...\")\n",
144
+ " neg_df = pd.read_sql(\"\"\"\n",
145
+ " SELECT sentence1, sentence2, label\n",
146
+ " FROM protein_pair\n",
147
+ " WHERE label = 0\n",
148
+ " AND ABS(LENGTH(sentence1) - LENGTH(sentence2)) <= %s\n",
149
+ " ORDER BY RAND()\n",
150
+ " LIMIT %s\n",
151
+ " \"\"\", connection, params=(LENGTH_DIFF_MAX, NEGATIVE_SAMPLES))\n",
152
+ "\n",
153
+ " # 检查实际抽取数量\n",
154
+ " actual_pos = len(pos_df)\n",
155
+ " actual_neg = len(neg_df)\n",
156
+ " print(f\"\\n实际抽取完成:\")\n",
157
+ " print(f\" 正样本 (label=1): {actual_pos} 条\")\n",
158
+ " print(f\" 负样本 (label=0): {actual_neg} 条\")\n",
159
+ " print(f\" 总样本: {actual_pos + actual_neg} 条\")\n",
160
+ "\n",
161
+ " if actual_pos < POSITIVE_SAMPLES:\n",
162
+ " print(f\" 警告:正样本不足,仅抽到 {actual_pos} 条(可能数据库中长度差 ≤ {LENGTH_DIFF_MAX} 的样本较少)\")\n",
163
+ " if actual_neg < NEGATIVE_SAMPLES:\n",
164
+ " print(f\" 警告:负样本不足,仅抽到 {actual_neg} 条\")\n",
165
+ "\n",
166
+ " # 合并并打乱\n",
167
+ " sample_df = pd.concat([pos_df, neg_df], ignore_index=True)\n",
168
+ " sample_df = sample_df.sample(frac=1, random_state=42).reset_index(drop=True)\n",
169
+ "\n",
170
+ " # 保存 CSV\n",
171
+ " sample_df.to_csv(OUTPUT_CSV, index=False, encoding=\"utf-8\")\n",
172
+ "\n",
173
+ " print(f\"\\n🎉 完成!共生成 {len(sample_df)} 条样本(不限制绝对长度,仅限制一对长度差 ≤ {LENGTH_DIFF_MAX} aa)\")\n",
174
+ " print(f\"CSV 文件已保存至:{OUTPUT_CSV}\")\n",
175
+ " print(\"\\n前5行预览:\")\n",
176
+ " print(sample_df.head())\n",
177
+ "\n",
178
+ " # 可选:输出长度统计,便于论文描述数据集分布\n",
179
+ " print(\"\\n长度统计(供论文参考):\")\n",
180
+ " print(\"sentence1 长度分布:\")\n",
181
+ " print(sample_df['sentence1'].str.len().describe())\n",
182
+ " print(\"\\nsentence2 长度分布:\")\n",
183
+ " print(sample_df['sentence2'].str.len().describe())\n",
184
+ " print(\"\\n长度差分布:\")\n",
185
+ " print(abs(sample_df['sentence1'].str.len() - sample_df['sentence2'].str.len()).describe())\n",
186
+ "\n",
187
+ "except Exception as e:\n",
188
+ " print(f\"错误:{e}\")\n",
189
+ "finally:\n",
190
+ " connection.close()\n",
191
+ " print(\"数据库连接已关闭。\")"
192
+ ]
193
+ },
194
+ {
195
+ "cell_type": "code",
196
+ "execution_count": null,
197
+ "id": "f036071a-269e-4684-a292-46fcbce0c384",
198
+ "metadata": {},
199
+ "outputs": [],
200
+ "source": []
201
+ }
202
+ ],
203
+ "metadata": {
204
+ "kernelspec": {
205
+ "display_name": "Python 3 (ipykernel)",
206
+ "language": "python",
207
+ "name": "python3"
208
+ },
209
+ "language_info": {
210
+ "codemirror_mode": {
211
+ "name": "ipython",
212
+ "version": 3
213
+ },
214
+ "file_extension": ".py",
215
+ "mimetype": "text/x-python",
216
+ "name": "python",
217
+ "nbconvert_exporter": "python",
218
+ "pygments_lexer": "ipython3",
219
+ "version": "3.12.3"
220
+ }
221
+ },
222
+ "nbformat": 4,
223
+ "nbformat_minor": 5
224
+ }
1-data/mysql_part/.ipynb_checkpoints/5-upload_data_hf-checkpoint.ipynb ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 6,
6
+ "id": "9e0cfe40-9613-4e9e-9b77-c7877632d035",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "# import os\n",
11
+ "\n",
12
+ "# # 设置环境变量\n",
13
+ "# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'\n",
14
+ "\n",
15
+ "# # 打印环境变量以确认设置成功\n",
16
+ "# print(os.environ.get('HF_ENDPOINT'))\n",
17
+ "\n",
18
+ "import subprocess\n",
19
+ "import os\n",
20
+ "\n",
21
+ "result = subprocess.run('bash -c \"source /etc/network_turbo && env | grep proxy\"', shell=True, capture_output=True, text=True)\n",
22
+ "output = result.stdout\n",
23
+ "for line in output.splitlines():\n",
24
+ " if '=' in line:\n",
25
+ " var, value = line.split('=', 1)\n",
26
+ " os.environ[var] = value"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": 7,
32
+ "id": "80df554c-7bbe-4313-b9fd-ea1c61c125ba",
33
+ "metadata": {},
34
+ "outputs": [
35
+ {
36
+ "name": "stdout",
37
+ "output_type": "stream",
38
+ "text": [
39
+ "正在上传 protein_pair_short...\n"
40
+ ]
41
+ },
42
+ {
43
+ "data": {
44
+ "application/vnd.jupyter.widget-view+json": {
45
+ "model_id": "702159e131d9433d9664afa8f9c78dda",
46
+ "version_major": 2,
47
+ "version_minor": 0
48
+ },
49
+ "text/plain": [
50
+ "Uploading the dataset shards: 0%| | 0/1 [00:00<?, ? shards/s]"
51
+ ]
52
+ },
53
+ "metadata": {},
54
+ "output_type": "display_data"
55
+ },
56
+ {
57
+ "data": {
58
+ "application/vnd.jupyter.widget-view+json": {
59
+ "model_id": "7d25d3941a11494faa34add308eb7b1d",
60
+ "version_major": 2,
61
+ "version_minor": 0
62
+ },
63
+ "text/plain": [
64
+ "Creating parquet from Arrow format: 0%| | 0/20 [00:00<?, ?ba/s]"
65
+ ]
66
+ },
67
+ "metadata": {},
68
+ "output_type": "display_data"
69
+ },
70
+ {
71
+ "data": {
72
+ "application/vnd.jupyter.widget-view+json": {
73
+ "model_id": "155b6f5a30844dec891b951f67790afb",
74
+ "version_major": 2,
75
+ "version_minor": 0
76
+ },
77
+ "text/plain": [
78
+ "Processing Files (0 / 0) : | | 0.00B / 0.00B "
79
+ ]
80
+ },
81
+ "metadata": {},
82
+ "output_type": "display_data"
83
+ },
84
+ {
85
+ "data": {
86
+ "application/vnd.jupyter.widget-view+json": {
87
+ "model_id": "8fa30989e50843f5bf9453df9bd9ad87",
88
+ "version_major": 2,
89
+ "version_minor": 0
90
+ },
91
+ "text/plain": [
92
+ "New Data Upload : | | 0.00B / 0.00B "
93
+ ]
94
+ },
95
+ "metadata": {},
96
+ "output_type": "display_data"
97
+ },
98
+ {
99
+ "data": {
100
+ "application/vnd.jupyter.widget-view+json": {
101
+ "model_id": "bbc24436ed9145c69c8c59b5fed01c81",
102
+ "version_major": 2,
103
+ "version_minor": 0
104
+ },
105
+ "text/plain": [
106
+ " : 100%|##########| 5.82MB / 5.82MB "
107
+ ]
108
+ },
109
+ "metadata": {},
110
+ "output_type": "display_data"
111
+ },
112
+ {
113
+ "name": "stdout",
114
+ "output_type": "stream",
115
+ "text": [
116
+ "正在上传 protein_pair_full...\n"
117
+ ]
118
+ },
119
+ {
120
+ "data": {
121
+ "application/vnd.jupyter.widget-view+json": {
122
+ "model_id": "b1cd86dcb3fc4897821fa9c8afa0703c",
123
+ "version_major": 2,
124
+ "version_minor": 0
125
+ },
126
+ "text/plain": [
127
+ "Uploading the dataset shards: 0%| | 0/1 [00:00<?, ? shards/s]"
128
+ ]
129
+ },
130
+ "metadata": {},
131
+ "output_type": "display_data"
132
+ },
133
+ {
134
+ "data": {
135
+ "application/vnd.jupyter.widget-view+json": {
136
+ "model_id": "ca158bec1fc7458a8c864f30e12d356f",
137
+ "version_major": 2,
138
+ "version_minor": 0
139
+ },
140
+ "text/plain": [
141
+ "Creating parquet from Arrow format: 0%| | 0/20 [00:00<?, ?ba/s]"
142
+ ]
143
+ },
144
+ "metadata": {},
145
+ "output_type": "display_data"
146
+ },
147
+ {
148
+ "data": {
149
+ "application/vnd.jupyter.widget-view+json": {
150
+ "model_id": "6f86925787144f37941cdba943edb7e4",
151
+ "version_major": 2,
152
+ "version_minor": 0
153
+ },
154
+ "text/plain": [
155
+ "Processing Files (0 / 0) : | | 0.00B / 0.00B "
156
+ ]
157
+ },
158
+ "metadata": {},
159
+ "output_type": "display_data"
160
+ },
161
+ {
162
+ "data": {
163
+ "application/vnd.jupyter.widget-view+json": {
164
+ "model_id": "4475aa6f34e74d4391ca8585a6016f2f",
165
+ "version_major": 2,
166
+ "version_minor": 0
167
+ },
168
+ "text/plain": [
169
+ "New Data Upload : | | 0.00B / 0.00B "
170
+ ]
171
+ },
172
+ "metadata": {},
173
+ "output_type": "display_data"
174
+ },
175
+ {
176
+ "data": {
177
+ "application/vnd.jupyter.widget-view+json": {
178
+ "model_id": "531d28f12c5b4c7c962347e48ea05b40",
179
+ "version_major": 2,
180
+ "version_minor": 0
181
+ },
182
+ "text/plain": [
183
+ " : 100%|##########| 11.8MB / 11.8MB "
184
+ ]
185
+ },
186
+ "metadata": {},
187
+ "output_type": "display_data"
188
+ },
189
+ {
190
+ "data": {
191
+ "application/vnd.jupyter.widget-view+json": {
192
+ "model_id": "de24e59c3d2c474a84b9ede63032ff5a",
193
+ "version_major": 2,
194
+ "version_minor": 0
195
+ },
196
+ "text/plain": [
197
+ "README.md: 0%| | 0.00/410 [00:00<?, ?B/s]"
198
+ ]
199
+ },
200
+ "metadata": {},
201
+ "output_type": "display_data"
202
+ },
203
+ {
204
+ "name": "stdout",
205
+ "output_type": "stream",
206
+ "text": [
207
+ "上传完成!\n",
208
+ "现在可以直接这样加载:\n",
209
+ "load_dataset(\"dnagpt/biopaws\", \"protein_pair_short\")\n",
210
+ "load_dataset(\"dnagpt/biopaws\", \"protein_pair_full\")\n"
211
+ ]
212
+ }
213
+ ],
214
+ "source": [
215
+ "from datasets import load_dataset, DatasetDict\n",
216
+ "from huggingface_hub import login\n",
217
+ "\n",
218
+ "HF_TOKEN = \"hf_your_token\"\n",
219
+ "login(token=HF_TOKEN)\n",
220
+ "\n",
221
+ "short_csv = \"protein_pair_20k_length_restricted_balanced.csv\"\n",
222
+ "full_csv = \"protein_pair_20k_no_length_limit_but_diff_limited.csv\"\n",
223
+ "\n",
224
+ "short_ds = load_dataset(\"csv\", data_files=short_csv)[\"train\"]\n",
225
+ "full_ds = load_dataset(\"csv\", data_files=full_csv)[\"train\"]\n",
226
+ "\n",
227
+ "# 关键修改:分别上传,每个指定 config_name\n",
228
+ "print(\"正在上传 protein_pair_short...\")\n",
229
+ "short_ds.push_to_hub(\"dnagpt/biopaws\", config_name=\"protein_pair_short\", private=False)\n",
230
+ "\n",
231
+ "print(\"正在上传 protein_pair_full...\")\n",
232
+ "full_ds.push_to_hub(\"dnagpt/biopaws\", config_name=\"protein_pair_full\", private=False)\n",
233
+ "\n",
234
+ "print(\"上传完成!\")\n",
235
+ "print(\"现在可以直接这样加载:\")\n",
236
+ "print('load_dataset(\"dnagpt/biopaws\", \"protein_pair_short\")')\n",
237
+ "print('load_dataset(\"dnagpt/biopaws\", \"protein_pair_full\")')"
238
+ ]
239
+ },
240
+ {
241
+ "cell_type": "code",
242
+ "execution_count": null,
243
+ "id": "26987755-e4be-4f64-873c-22305afc5b74",
244
+ "metadata": {},
245
+ "outputs": [],
246
+ "source": []
247
+ }
248
+ ],
249
+ "metadata": {
250
+ "kernelspec": {
251
+ "display_name": "Python 3 (ipykernel)",
252
+ "language": "python",
253
+ "name": "python3"
254
+ },
255
+ "language_info": {
256
+ "codemirror_mode": {
257
+ "name": "ipython",
258
+ "version": 3
259
+ },
260
+ "file_extension": ".py",
261
+ "mimetype": "text/x-python",
262
+ "name": "python",
263
+ "nbconvert_exporter": "python",
264
+ "pygments_lexer": "ipython3",
265
+ "version": "3.12.3"
266
+ }
267
+ },
268
+ "nbformat": 4,
269
+ "nbformat_minor": 5
270
+ }
1-data/mysql_part/1-insert_protein_to_db.ipynb ADDED
@@ -0,0 +1,646 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "ba2cea75-a1d4-4b1e-a7f8-06abd623e49f",
7
+ "metadata": {
8
+ "scrolled": true
9
+ },
10
+ "outputs": [
11
+ {
12
+ "name": "stdout",
13
+ "output_type": "stream",
14
+ "text": [
15
+ "正在读取 sampled_10000_proteins.fasta ...\n",
16
+ "共找到 10000 条蛋白质序列,开始插入...\n",
17
+ "已插入 20/10000 条\n",
18
+ "已插入 40/10000 条\n",
19
+ "已插入 60/10000 条\n",
20
+ "已插入 80/10000 条\n",
21
+ "已插入 100/10000 条\n",
22
+ "已插入 120/10000 条\n",
23
+ "已插入 140/10000 条\n",
24
+ "已插入 160/10000 条\n",
25
+ "已插入 180/10000 条\n",
26
+ "已插入 200/10000 条\n",
27
+ "已插入 220/10000 条\n",
28
+ "已插入 240/10000 条\n",
29
+ "已插入 260/10000 条\n",
30
+ "已插入 280/10000 条\n",
31
+ "已插入 300/10000 条\n",
32
+ "已插入 320/10000 条\n",
33
+ "已插入 340/10000 条\n",
34
+ "已插入 360/10000 条\n",
35
+ "已插入 380/10000 条\n",
36
+ "已插入 400/10000 条\n",
37
+ "已插入 420/10000 条\n",
38
+ "已插入 440/10000 条\n",
39
+ "已插入 460/10000 条\n",
40
+ "已插入 480/10000 条\n",
41
+ "已插入 500/10000 条\n",
42
+ "已插入 520/10000 条\n",
43
+ "已插入 540/10000 条\n",
44
+ "已插入 560/10000 条\n",
45
+ "已插入 580/10000 条\n",
46
+ "已插入 600/10000 条\n",
47
+ "已插入 620/10000 条\n",
48
+ "已插入 640/10000 条\n",
49
+ "已插入 660/10000 条\n",
50
+ "已插入 680/10000 条\n",
51
+ "已插入 700/10000 条\n",
52
+ "已插入 720/10000 条\n",
53
+ "已插入 740/10000 条\n",
54
+ "已插入 760/10000 条\n",
55
+ "已插入 780/10000 条\n",
56
+ "已插入 800/10000 条\n",
57
+ "已插入 820/10000 条\n",
58
+ "已插入 840/10000 条\n",
59
+ "已插入 860/10000 条\n",
60
+ "已插入 880/10000 条\n",
61
+ "已插入 900/10000 条\n",
62
+ "已插入 920/10000 条\n",
63
+ "已插入 940/10000 条\n",
64
+ "已插入 960/10000 条\n",
65
+ "已插入 980/10000 条\n",
66
+ "已插入 1000/10000 条\n",
67
+ "已插入 1020/10000 条\n",
68
+ "已插入 1040/10000 条\n",
69
+ "已插入 1060/10000 条\n",
70
+ "已插入 1080/10000 条\n",
71
+ "已插入 1100/10000 条\n",
72
+ "已插入 1120/10000 条\n",
73
+ "已插入 1140/10000 条\n",
74
+ "已插入 1160/10000 条\n",
75
+ "已插入 1180/10000 条\n",
76
+ "已插入 1200/10000 条\n",
77
+ "已插入 1220/10000 条\n",
78
+ "已插入 1240/10000 条\n",
79
+ "已插入 1260/10000 条\n",
80
+ "已插入 1280/10000 条\n",
81
+ "已插入 1300/10000 条\n",
82
+ "已插入 1320/10000 条\n",
83
+ "已插入 1340/10000 条\n",
84
+ "已插入 1360/10000 条\n",
85
+ "已插入 1380/10000 条\n",
86
+ "已插入 1400/10000 条\n",
87
+ "已插入 1420/10000 条\n",
88
+ "已插入 1440/10000 条\n",
89
+ "已插入 1460/10000 条\n",
90
+ "已插入 1480/10000 条\n",
91
+ "已插入 1500/10000 条\n",
92
+ "已插入 1520/10000 条\n",
93
+ "已插入 1540/10000 条\n",
94
+ "已插入 1560/10000 条\n",
95
+ "已插入 1580/10000 条\n",
96
+ "已插入 1600/10000 条\n",
97
+ "已插入 1620/10000 条\n",
98
+ "已插入 1640/10000 条\n",
99
+ "已插入 1660/10000 条\n",
100
+ "已插入 1680/10000 条\n",
101
+ "已插入 1700/10000 条\n",
102
+ "已插入 1720/10000 条\n",
103
+ "已插入 1740/10000 条\n",
104
+ "已插入 1760/10000 条\n",
105
+ "已插入 1780/10000 条\n",
106
+ "已插入 1800/10000 条\n",
107
+ "已插入 1820/10000 条\n",
108
+ "已插入 1840/10000 条\n",
109
+ "已插入 1860/10000 条\n",
110
+ "已插入 1880/10000 条\n",
111
+ "已插入 1900/10000 条\n",
112
+ "已插入 1920/10000 条\n",
113
+ "已插入 1940/10000 条\n",
114
+ "已插入 1960/10000 条\n",
115
+ "已插入 1980/10000 条\n",
116
+ "已插入 2000/10000 条\n",
117
+ "已插入 2020/10000 条\n",
118
+ "已插入 2040/10000 条\n",
119
+ "已插入 2060/10000 条\n",
120
+ "已插入 2080/10000 条\n",
121
+ "已插入 2100/10000 条\n",
122
+ "已插入 2120/10000 条\n",
123
+ "已插入 2140/10000 条\n",
124
+ "已插入 2160/10000 条\n",
125
+ "已插入 2180/10000 条\n",
126
+ "已插入 2200/10000 条\n",
127
+ "已插入 2220/10000 条\n",
128
+ "已插入 2240/10000 条\n",
129
+ "已插入 2260/10000 条\n",
130
+ "已插入 2280/10000 条\n",
131
+ "已插入 2300/10000 条\n",
132
+ "已插入 2320/10000 条\n",
133
+ "已插入 2340/10000 条\n",
134
+ "已插入 2360/10000 条\n",
135
+ "已插入 2380/10000 条\n",
136
+ "已插入 2400/10000 条\n",
137
+ "已插入 2420/10000 条\n",
138
+ "已插入 2440/10000 条\n",
139
+ "已插入 2460/10000 条\n",
140
+ "已插入 2480/10000 条\n",
141
+ "已插入 2500/10000 条\n",
142
+ "已插入 2520/10000 条\n",
143
+ "已插入 2540/10000 条\n",
144
+ "已插入 2560/10000 条\n",
145
+ "已插入 2580/10000 条\n",
146
+ "已插入 2600/10000 条\n",
147
+ "已插入 2620/10000 条\n",
148
+ "已插入 2640/10000 条\n",
149
+ "已插入 2660/10000 条\n",
150
+ "已插入 2680/10000 条\n",
151
+ "已插入 2700/10000 条\n",
152
+ "已插入 2720/10000 条\n",
153
+ "已插入 2740/10000 条\n",
154
+ "已插入 2760/10000 条\n",
155
+ "已插入 2780/10000 条\n",
156
+ "已插入 2800/10000 条\n",
157
+ "已插入 2820/10000 条\n",
158
+ "已插入 2840/10000 条\n",
159
+ "已插入 2860/10000 条\n",
160
+ "已插入 2880/10000 条\n",
161
+ "已插入 2900/10000 条\n",
162
+ "已插入 2920/10000 条\n",
163
+ "已插入 2940/10000 条\n",
164
+ "已插入 2960/10000 条\n",
165
+ "已插入 2980/10000 条\n",
166
+ "已插入 3000/10000 条\n",
167
+ "已插入 3020/10000 条\n",
168
+ "已插入 3040/10000 条\n",
169
+ "已插入 3060/10000 条\n",
170
+ "已插入 3080/10000 条\n",
171
+ "已插入 3100/10000 条\n",
172
+ "已插入 3120/10000 条\n",
173
+ "已插入 3140/10000 条\n",
174
+ "已插入 3160/10000 条\n",
175
+ "已插入 3180/10000 条\n",
176
+ "已插入 3200/10000 条\n",
177
+ "已插入 3220/10000 条\n",
178
+ "已插入 3240/10000 条\n",
179
+ "已插入 3260/10000 条\n",
180
+ "已插入 3280/10000 条\n",
181
+ "已插入 3300/10000 条\n",
182
+ "已插入 3320/10000 条\n",
183
+ "已插入 3340/10000 条\n",
184
+ "已插入 3360/10000 条\n",
185
+ "已插入 3380/10000 条\n",
186
+ "已插入 3400/10000 条\n",
187
+ "已插入 3420/10000 条\n",
188
+ "已插入 3440/10000 条\n",
189
+ "已插入 3460/10000 条\n",
190
+ "已插入 3480/10000 条\n",
191
+ "已插入 3500/10000 条\n",
192
+ "已插入 3520/10000 条\n",
193
+ "已插入 3540/10000 条\n",
194
+ "已插入 3560/10000 条\n",
195
+ "已插入 3580/10000 条\n",
196
+ "已插入 3600/10000 条\n",
197
+ "已插入 3620/10000 条\n",
198
+ "已插入 3640/10000 条\n",
199
+ "已插入 3660/10000 条\n",
200
+ "已插入 3680/10000 条\n",
201
+ "已插入 3700/10000 条\n",
202
+ "已插入 3720/10000 条\n",
203
+ "已插入 3740/10000 条\n",
204
+ "已插入 3760/10000 条\n",
205
+ "已插入 3780/10000 条\n",
206
+ "已插入 3800/10000 条\n",
207
+ "已插入 3820/10000 条\n",
208
+ "已插入 3840/10000 条\n",
209
+ "已插入 3860/10000 条\n",
210
+ "已插入 3880/10000 条\n",
211
+ "已插入 3900/10000 条\n",
212
+ "已插入 3920/10000 条\n",
213
+ "已插入 3940/10000 条\n",
214
+ "已插入 3960/10000 条\n",
215
+ "已插入 3980/10000 条\n",
216
+ "已插入 4000/10000 条\n",
217
+ "已插入 4020/10000 条\n",
218
+ "已插入 4040/10000 条\n",
219
+ "已插入 4060/10000 条\n",
220
+ "已插入 4080/10000 条\n",
221
+ "已插入 4100/10000 条\n",
222
+ "已插入 4120/10000 条\n",
223
+ "已插入 4140/10000 条\n",
224
+ "已插入 4160/10000 条\n",
225
+ "已插入 4180/10000 条\n",
226
+ "已插入 4200/10000 条\n",
227
+ "已插入 4220/10000 条\n",
228
+ "已插入 4240/10000 条\n",
229
+ "已插入 4260/10000 条\n",
230
+ "已插入 4280/10000 条\n",
231
+ "已插入 4300/10000 条\n",
232
+ "已插入 4320/10000 条\n",
233
+ "已插入 4340/10000 条\n",
234
+ "已插入 4360/10000 条\n",
235
+ "已插入 4380/10000 条\n",
236
+ "已插入 4400/10000 条\n",
237
+ "已插入 4420/10000 条\n",
238
+ "已插入 4440/10000 条\n",
239
+ "已插入 4460/10000 条\n",
240
+ "已插入 4480/10000 条\n",
241
+ "已插入 4500/10000 条\n",
242
+ "已插入 4520/10000 条\n",
243
+ "已插入 4540/10000 条\n",
244
+ "已插入 4560/10000 条\n",
245
+ "已插入 4580/10000 条\n",
246
+ "已插入 4600/10000 条\n",
247
+ "已插入 4620/10000 条\n",
248
+ "已插入 4640/10000 条\n",
249
+ "已插入 4660/10000 条\n",
250
+ "已插入 4680/10000 条\n",
251
+ "已插入 4700/10000 条\n",
252
+ "已插入 4720/10000 条\n",
253
+ "已插入 4740/10000 条\n",
254
+ "已插入 4760/10000 条\n",
255
+ "已插入 4780/10000 条\n",
256
+ "已插入 4800/10000 条\n",
257
+ "已插入 4820/10000 条\n",
258
+ "已插入 4840/10000 条\n",
259
+ "已插入 4860/10000 条\n",
260
+ "已插入 4880/10000 条\n",
261
+ "已插入 4900/10000 条\n",
262
+ "已插入 4920/10000 条\n",
263
+ "已插入 4940/10000 条\n",
264
+ "已插入 4960/10000 条\n",
265
+ "已插入 4980/10000 条\n",
266
+ "已插入 5000/10000 条\n",
267
+ "已插入 5020/10000 条\n",
268
+ "已插入 5040/10000 条\n",
269
+ "已插入 5060/10000 条\n",
270
+ "已插入 5080/10000 条\n",
271
+ "已插入 5100/10000 条\n",
272
+ "已插入 5120/10000 条\n",
273
+ "已插入 5140/10000 条\n",
274
+ "已插入 5160/10000 条\n",
275
+ "已插入 5180/10000 条\n",
276
+ "已插入 5200/10000 条\n",
277
+ "已插入 5220/10000 条\n",
278
+ "已插入 5240/10000 条\n",
279
+ "已插入 5260/10000 条\n",
280
+ "已插入 5280/10000 条\n",
281
+ "已插入 5300/10000 条\n",
282
+ "已插入 5320/10000 条\n",
283
+ "已插入 5340/10000 条\n",
284
+ "���插入 5360/10000 条\n",
285
+ "已插入 5380/10000 条\n",
286
+ "已插入 5400/10000 条\n",
287
+ "已插入 5420/10000 条\n",
288
+ "已插入 5440/10000 条\n",
289
+ "已插入 5460/10000 条\n",
290
+ "已插入 5480/10000 条\n",
291
+ "已插入 5500/10000 条\n",
292
+ "已插入 5520/10000 条\n",
293
+ "已插入 5540/10000 条\n",
294
+ "已插入 5560/10000 条\n",
295
+ "已插入 5580/10000 条\n",
296
+ "已插入 5600/10000 条\n",
297
+ "已插入 5620/10000 条\n",
298
+ "已插入 5640/10000 条\n",
299
+ "已插入 5660/10000 条\n",
300
+ "已插入 5680/10000 条\n",
301
+ "已插入 5700/10000 条\n",
302
+ "已插入 5720/10000 条\n",
303
+ "已插入 5740/10000 条\n",
304
+ "已插入 5760/10000 条\n",
305
+ "已插入 5780/10000 条\n",
306
+ "已插入 5800/10000 条\n",
307
+ "已插入 5820/10000 条\n",
308
+ "已插入 5840/10000 条\n",
309
+ "已插入 5860/10000 条\n",
310
+ "已插入 5880/10000 条\n",
311
+ "已插入 5900/10000 条\n",
312
+ "已插入 5920/10000 条\n",
313
+ "已插入 5940/10000 条\n",
314
+ "已插入 5960/10000 条\n",
315
+ "已插入 5980/10000 条\n",
316
+ "已插入 6000/10000 条\n",
317
+ "已插入 6020/10000 条\n",
318
+ "已插入 6040/10000 条\n",
319
+ "已插入 6060/10000 条\n",
320
+ "已插入 6080/10000 条\n",
321
+ "已插入 6100/10000 条\n",
322
+ "已插入 6120/10000 条\n",
323
+ "已插入 6140/10000 条\n",
324
+ "已插入 6160/10000 条\n",
325
+ "已插入 6180/10000 条\n",
326
+ "已插入 6200/10000 条\n",
327
+ "已插入 6220/10000 条\n",
328
+ "已插入 6240/10000 条\n",
329
+ "已插入 6260/10000 条\n",
330
+ "已插入 6280/10000 条\n",
331
+ "已插入 6300/10000 条\n",
332
+ "已插入 6320/10000 条\n",
333
+ "已插入 6340/10000 条\n",
334
+ "已插入 6360/10000 条\n",
335
+ "已插入 6380/10000 条\n",
336
+ "已插入 6400/10000 条\n",
337
+ "已插入 6420/10000 条\n",
338
+ "已插入 6440/10000 条\n",
339
+ "已插入 6460/10000 条\n",
340
+ "已插入 6480/10000 条\n",
341
+ "已插入 6500/10000 条\n",
342
+ "已插入 6520/10000 条\n",
343
+ "已插入 6540/10000 条\n",
344
+ "已插入 6560/10000 条\n",
345
+ "已插入 6580/10000 条\n",
346
+ "已插入 6600/10000 条\n",
347
+ "已插入 6620/10000 条\n",
348
+ "已插入 6640/10000 条\n",
349
+ "已插入 6660/10000 条\n",
350
+ "已插入 6680/10000 条\n",
351
+ "已插入 6700/10000 条\n",
352
+ "已插入 6720/10000 条\n",
353
+ "已插入 6740/10000 条\n",
354
+ "已插入 6760/10000 条\n",
355
+ "已插入 6780/10000 条\n",
356
+ "已插入 6800/10000 条\n",
357
+ "已插入 6820/10000 条\n",
358
+ "已插入 6840/10000 条\n",
359
+ "已插入 6860/10000 条\n",
360
+ "已插入 6880/10000 条\n",
361
+ "已插入 6900/10000 条\n",
362
+ "已插入 6920/10000 条\n",
363
+ "已插入 6940/10000 条\n",
364
+ "已插入 6960/10000 条\n",
365
+ "已插入 6980/10000 条\n",
366
+ "已插入 7000/10000 条\n",
367
+ "已插入 7020/10000 条\n",
368
+ "已插入 7040/10000 条\n",
369
+ "已插入 7060/10000 条\n",
370
+ "已插入 7080/10000 条\n",
371
+ "已插入 7100/10000 条\n",
372
+ "已插入 7120/10000 条\n",
373
+ "已插入 7140/10000 条\n",
374
+ "已插入 7160/10000 条\n",
375
+ "已插入 7180/10000 条\n",
376
+ "已插入 7200/10000 条\n",
377
+ "已插入 7220/10000 条\n",
378
+ "已插入 7240/10000 条\n",
379
+ "已插入 7260/10000 条\n",
380
+ "已插入 7280/10000 条\n",
381
+ "已插入 7300/10000 条\n",
382
+ "已插入 7320/10000 条\n",
383
+ "已插入 7340/10000 条\n",
384
+ "已插入 7360/10000 条\n",
385
+ "已插入 7380/10000 条\n",
386
+ "已插入 7400/10000 条\n",
387
+ "已插入 7420/10000 条\n",
388
+ "已插入 7440/10000 条\n",
389
+ "已插入 7460/10000 条\n",
390
+ "已插入 7480/10000 条\n",
391
+ "已插入 7500/10000 条\n",
392
+ "已插入 7520/10000 条\n",
393
+ "已插入 7540/10000 条\n",
394
+ "已插入 7560/10000 条\n",
395
+ "已插入 7580/10000 条\n",
396
+ "已插入 7600/10000 条\n",
397
+ "已插入 7620/10000 条\n",
398
+ "已插入 7640/10000 条\n",
399
+ "已插入 7660/10000 条\n",
400
+ "已插入 7680/10000 条\n",
401
+ "已插入 7700/10000 条\n",
402
+ "已插入 7720/10000 条\n",
403
+ "已插入 7740/10000 条\n",
404
+ "已插入 7760/10000 条\n",
405
+ "已插入 7780/10000 条\n",
406
+ "已插入 7800/10000 条\n",
407
+ "已插入 7820/10000 条\n",
408
+ "已插入 7840/10000 条\n",
409
+ "已插入 7860/10000 条\n",
410
+ "已插入 7880/10000 条\n",
411
+ "已插入 7900/10000 条\n",
412
+ "已插入 7920/10000 条\n",
413
+ "已插入 7940/10000 条\n",
414
+ "已插入 7960/10000 条\n",
415
+ "已插入 7980/10000 条\n",
416
+ "已插入 8000/10000 条\n",
417
+ "已插入 8020/10000 条\n",
418
+ "已插入 8040/10000 条\n",
419
+ "已插入 8060/10000 条\n",
420
+ "已插入 8080/10000 条\n",
421
+ "已插入 8100/10000 条\n",
422
+ "已插入 8120/10000 条\n",
423
+ "已插入 8140/10000 条\n",
424
+ "已插入 8160/10000 条\n",
425
+ "已插入 8180/10000 条\n",
426
+ "已插入 8200/10000 条\n",
427
+ "已插入 8220/10000 条\n",
428
+ "已插入 8240/10000 条\n",
429
+ "已插入 8260/10000 条\n",
430
+ "已插入 8280/10000 条\n",
431
+ "已插入 8300/10000 条\n",
432
+ "已插入 8320/10000 条\n",
433
+ "已插入 8340/10000 条\n",
434
+ "已插入 8360/10000 条\n",
435
+ "已插入 8380/10000 条\n",
436
+ "已插入 8400/10000 条\n",
437
+ "已插入 8420/10000 条\n",
438
+ "已插入 8440/10000 条\n",
439
+ "已插入 8460/10000 条\n",
440
+ "已插入 8480/10000 条\n",
441
+ "已插入 8500/10000 条\n",
442
+ "已插入 8520/10000 条\n",
443
+ "已插入 8540/10000 条\n",
444
+ "已插入 8560/10000 条\n",
445
+ "已插入 8580/10000 条\n",
446
+ "已插入 8600/10000 条\n",
447
+ "已插入 8620/10000 条\n",
448
+ "已插入 8640/10000 条\n",
449
+ "已插入 8660/10000 条\n",
450
+ "已插入 8680/10000 条\n",
451
+ "已插入 8700/10000 条\n",
452
+ "已插入 8720/10000 条\n",
453
+ "已插入 8740/10000 条\n",
454
+ "已插入 8760/10000 条\n",
455
+ "已插入 8780/10000 条\n",
456
+ "已插入 8800/10000 条\n",
457
+ "已插入 8820/10000 条\n",
458
+ "已插入 8840/10000 条\n",
459
+ "已插入 8860/10000 条\n",
460
+ "已插入 8880/10000 条\n",
461
+ "已插入 8900/10000 条\n",
462
+ "已插入 8920/10000 条\n",
463
+ "已插入 8940/10000 条\n",
464
+ "已插入 8960/10000 条\n",
465
+ "已插入 8980/10000 条\n",
466
+ "已插入 9000/10000 条\n",
467
+ "已插入 9020/10000 条\n",
468
+ "已插入 9040/10000 条\n",
469
+ "已插入 9060/10000 条\n",
470
+ "已插入 9080/10000 条\n",
471
+ "已插入 9100/10000 条\n",
472
+ "已插入 9120/10000 条\n",
473
+ "已插入 9140/10000 条\n",
474
+ "已插入 9160/10000 条\n",
475
+ "已插入 9180/10000 条\n",
476
+ "已插入 9200/10000 条\n",
477
+ "已插入 9220/10000 条\n",
478
+ "已插入 9240/10000 条\n",
479
+ "已插入 9260/10000 条\n",
480
+ "已插入 9280/10000 条\n",
481
+ "已插入 9300/10000 条\n",
482
+ "已插入 9320/10000 条\n",
483
+ "已插入 9340/10000 条\n",
484
+ "已插入 9360/10000 条\n",
485
+ "已插入 9380/10000 条\n",
486
+ "已插入 9400/10000 条\n",
487
+ "已插入 9420/10000 条\n",
488
+ "已插入 9440/10000 条\n",
489
+ "已插入 9460/10000 条\n",
490
+ "已插入 9480/10000 条\n",
491
+ "已插入 9500/10000 条\n",
492
+ "已插入 9520/10000 条\n",
493
+ "已插入 9540/10000 条\n",
494
+ "已插入 9560/10000 条\n",
495
+ "已插入 9580/10000 条\n",
496
+ "已插入 9600/10000 条\n",
497
+ "已插入 9620/10000 条\n",
498
+ "已插入 9640/10000 条\n",
499
+ "已插入 9660/10000 条\n",
500
+ "已插入 9680/10000 条\n",
501
+ "已插入 9700/10000 条\n",
502
+ "已插入 9720/10000 条\n",
503
+ "已插入 9740/10000 条\n",
504
+ "已插入 9760/10000 条\n",
505
+ "已插入 9780/10000 条\n",
506
+ "已插入 9800/10000 条\n",
507
+ "已插入 9820/10000 条\n",
508
+ "已插入 9840/10000 条\n",
509
+ "已插入 9860/10000 条\n",
510
+ "已插入 9880/10000 条\n",
511
+ "已插入 9900/10000 条\n",
512
+ "已插入 9920/10000 条\n",
513
+ "已插入 9940/10000 条\n",
514
+ "已插入 9960/10000 条\n",
515
+ "已插入 9980/10000 条\n",
516
+ "已插入 10000/10000 条\n",
517
+ "插入完成!共插入 10000 条蛋白质序列到 protein_seq 表。\n",
518
+ "数据库连接已关闭。\n"
519
+ ]
520
+ }
521
+ ],
522
+ "source": [
523
+ "import pymysql\n",
524
+ "from Bio import SeqIO\n",
525
+ "\n",
526
+ "# ==================== 配置区 ====================\n",
527
+ "# FASTA 文件路径\n",
528
+ "FASTA_FILE = \"sampled_10000_proteins.fasta\"\n",
529
+ "\n",
530
+ "# 阿里云 RDS MySQL 连接信息\n",
531
+ "HOST = \"\"\n",
532
+ "PORT = 3306\n",
533
+ "USER = \"\"\n",
534
+ "PASSWORD = \"\"\n",
535
+ "DATABASE = \"ionet\"\n",
536
+ "CHARSET = \"utf8mb4\"\n",
537
+ "\n",
538
+ "# 插入的默认值\n",
539
+ "IS_PROCESS = 0\n",
540
+ "# ===============================================\n",
541
+ "\n",
542
+ "# 连接数据库\n",
543
+ "connection = pymysql.connect(\n",
544
+ " host=HOST,\n",
545
+ " port=PORT,\n",
546
+ " user=USER,\n",
547
+ " password=PASSWORD,\n",
548
+ " database=DATABASE,\n",
549
+ " charset=CHARSET,\n",
550
+ " autocommit=True # 自动提交,也可手动控制\n",
551
+ ")\n",
552
+ "\n",
553
+ "try:\n",
554
+ " with connection.cursor() as cursor:\n",
555
+ " # 插入 SQL\n",
556
+ " insert_sql = \"\"\"\n",
557
+ " INSERT INTO protein_seq (sentence1, is_process)\n",
558
+ " VALUES (%s, %s)\n",
559
+ " \"\"\"\n",
560
+ "\n",
561
+ " # 读取 FASTA 文件\n",
562
+ " print(f\"正在读取 {FASTA_FILE} ...\")\n",
563
+ " records = list(SeqIO.parse(FASTA_FILE, \"fasta\"))\n",
564
+ "\n",
565
+ " print(f\"共找到 {len(records)} 条蛋白质序列,开始插入...\")\n",
566
+ "\n",
567
+ " count = 0\n",
568
+ " for record in records:\n",
569
+ " sequence = str(record.seq).strip() # 转为字符串并去除首尾空白\n",
570
+ "\n",
571
+ " # 可选:如果想过滤过长序列(配合 GPT-2 512 aa 限制)\n",
572
+ " # if len(sequence) > 512:\n",
573
+ " # sequence = sequence[:512]\n",
574
+ "\n",
575
+ " cursor.execute(insert_sql, (sequence, IS_PROCESS))\n",
576
+ " count += 1\n",
577
+ "\n",
578
+ " if count % 20 == 0:\n",
579
+ " print(f\"已插入 {count}/{len(records)} 条\")\n",
580
+ "\n",
581
+ " print(f\"插入完成!共插入 {count} 条蛋白质序列到 protein_seq 表。\")\n",
582
+ "\n",
583
+ "except Exception as e:\n",
584
+ " print(f\"插入失败:{e}\")\n",
585
+ " connection.rollback() # 出错时回滚\n",
586
+ "finally:\n",
587
+ " connection.close()\n",
588
+ " print(\"数据库连接已关闭。\")"
589
+ ]
590
+ },
591
+ {
592
+ "cell_type": "code",
593
+ "execution_count": 1,
594
+ "id": "52130081-9d9a-4696-ac0b-cdcef9f6f08a",
595
+ "metadata": {},
596
+ "outputs": [
597
+ {
598
+ "name": "stdout",
599
+ "output_type": "stream",
600
+ "text": [
601
+ "Looking in indexes: http://mirrors.aliyun.com/pypi/simple\n",
602
+ "Collecting pymysql\n",
603
+ " Downloading http://mirrors.aliyun.com/pypi/packages/7c/4c/ad33b92b9864cbde84f259d5df035a6447f91891f5be77788e2a3892bce3/pymysql-1.1.2-py3-none-any.whl (45 kB)\n",
604
+ "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m45.3/45.3 kB\u001b[0m \u001b[31m1.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
605
+ "\u001b[?25hInstalling collected packages: pymysql\n",
606
+ "Successfully installed pymysql-1.1.2\n",
607
+ "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
608
+ "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n"
609
+ ]
610
+ }
611
+ ],
612
+ "source": [
613
+ "pip install pymysql"
614
+ ]
615
+ },
616
+ {
617
+ "cell_type": "code",
618
+ "execution_count": null,
619
+ "id": "34864573-6348-4196-af14-4f7ea5324f61",
620
+ "metadata": {},
621
+ "outputs": [],
622
+ "source": []
623
+ }
624
+ ],
625
+ "metadata": {
626
+ "kernelspec": {
627
+ "display_name": "Python 3 (ipykernel)",
628
+ "language": "python",
629
+ "name": "python3"
630
+ },
631
+ "language_info": {
632
+ "codemirror_mode": {
633
+ "name": "ipython",
634
+ "version": 3
635
+ },
636
+ "file_extension": ".py",
637
+ "mimetype": "text/x-python",
638
+ "name": "python",
639
+ "nbconvert_exporter": "python",
640
+ "pygments_lexer": "ipython3",
641
+ "version": "3.12.3"
642
+ }
643
+ },
644
+ "nbformat": 4,
645
+ "nbformat_minor": 5
646
+ }
1-data/mysql_part/2-get_non_homologous_pairs.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pymysql
2
+ import hashlib
3
+ from Bio import SeqIO
4
+ from Bio.Align import PairwiseAligner
5
+ import random
6
+ import time
7
+
8
+ # ==================== 数据库配置 ====================
9
+ HOST = ""
10
+ PORT = 3306
11
+ USER = ""
12
+ PASSWORD = ""
13
+ DATABASE = "ionet"
14
+ CHARSET = "utf8mb4"
15
+
16
+ # ==================== 负样本生成参数 ====================
17
+ MAX_NEG_PER_QUERY = 5 # 每条查询生成 5 个负样本
18
+ IDENTITY_THRESHOLD = 25.0 # identity < 25% 视为非同源
19
+ LENGTH_TOLERANCE = 0.3 # 长度差异不超过 30%
20
+ MIN_LENGTH = 50
21
+ MAX_TRIALS = 500 # 最多尝试 500 次随机采样
22
+
23
+ # 完整 Swiss-Prot 文件路径
24
+ FULL_SWISSPROT = "uniprot_sprot.fasta"
25
+
26
+ # ==================== 全局比对器 ====================
27
+ aligner = PairwiseAligner(mode='global')
28
+ aligner.match_score = 2
29
+ aligner.mismatch_score = -1
30
+ aligner.gap_score = -5
31
+ aligner.extend_gap_score = -1
32
+
33
+ # ==================== 加载完整 Swiss-Prot ====================
34
+ print("Loading full Swiss-Prot database... (this may take a minute)")
35
+ full_records = [r for r in SeqIO.parse(FULL_SWISSPROT, "fasta") if len(r.seq) >= MIN_LENGTH]
36
+ print(f"Loaded {len(full_records)} sequences from Swiss-Prot.")
37
+
38
+ # ==================== 数据库连接 ====================
39
+ connection = pymysql.connect(
40
+ host=HOST,
41
+ port=PORT,
42
+ user=USER,
43
+ password=PASSWORD,
44
+ database=DATABASE,
45
+ charset=CHARSET,
46
+ autocommit=False
47
+ )
48
+
49
+ def compute_pair_md5(seq1, seq2):
50
+ """有序拼接计算 MD5,确保去重一致"""
51
+ if seq1 < seq2:
52
+ combined = seq1 + seq2
53
+ else:
54
+ combined = seq2 + seq1
55
+ return hashlib.md5(combined.encode('utf-8')).hexdigest()
56
+
57
+ try:
58
+ with connection.cursor() as cursor:
59
+ while True:
60
+ # 1. 取一条 is_dis_process = 0 的序列(用于负样本处理)
61
+ cursor.execute("""
62
+ SELECT id, sentence1
63
+ FROM protein_seq
64
+ WHERE is_dis_process = 0
65
+ ORDER BY id
66
+ LIMIT 1
67
+ """)
68
+ row = cursor.fetchone()
69
+ if row is None:
70
+ print("所有序列的负样本处理已完成,没有剩余 is_dis_process=0 的记录。")
71
+ break
72
+
73
+ query_id_in_db, query_seq = row
74
+ q_len = len(query_seq)
75
+ print(f"\n正在处理负样本 - 数据库 ID={query_id_in_db} (长度={q_len})")
76
+
77
+ found = 0
78
+ trials = 0
79
+
80
+ while found < MAX_NEG_PER_QUERY and trials < MAX_TRIALS:
81
+ trials += 1
82
+ cand_rec = random.choice(full_records)
83
+
84
+ c_seq = str(cand_rec.seq)
85
+ if c_seq == query_seq: # 避免完全相同(极少发生)
86
+ continue
87
+
88
+ c_len = len(c_seq)
89
+
90
+ # 长度过滤
91
+ if abs(q_len - c_len) / ((q_len + c_len) / 2) > LENGTH_TOLERANCE:
92
+ continue
93
+
94
+ # 全局比对
95
+ try:
96
+ alignments = aligner.align(query_seq, c_seq)
97
+ if not alignments:
98
+ continue
99
+ alignment = alignments[0]
100
+
101
+ identical = sum(a == b and a != '-' and b != '-'
102
+ for a, b in zip(str(alignment[0]), str(alignment[1])))
103
+ aligned_len = (len(str(alignment[0])) -
104
+ str(alignment[0]).count('-') -
105
+ str(alignment[1]).count('-'))
106
+ identity = 100.0 * identical / aligned_len if aligned_len > 0 else 0
107
+
108
+ if identity < IDENTITY_THRESHOLD:
109
+ pair_md5 = compute_pair_md5(query_seq, c_seq)
110
+
111
+ # 插入负样本对,label=0
112
+ insert_sql = """
113
+ INSERT IGNORE INTO protein_pair
114
+ (sentence1, sentence2, pair_md5, label)
115
+ VALUES (%s, %s, %s, 0)
116
+ """
117
+ cursor.execute(insert_sql, (query_seq, c_seq, pair_md5))
118
+ if cursor.rowcount > 0:
119
+ found += 1
120
+ print(f" → 插入负样本对 (label=0, identity={identity:.1f}%)")
121
+
122
+ except Exception as e:
123
+ print(f" 比对异常: {e}")
124
+ continue
125
+
126
+ print(f" 本序列共插入 {found} 条负样本对(尝试 {trials} 次)")
127
+
128
+ # 2. 标记为已处理负样本(使用新字段)
129
+ cursor.execute("""
130
+ UPDATE protein_seq
131
+ SET is_dis_process = 1
132
+ WHERE id = %s
133
+ """, (query_id_in_db,))
134
+
135
+ connection.commit()
136
+ print(f" 已将数据库 ID={query_id_in_db} 的负样本处理标记为完成 (is_dis_process=1)\n")
137
+
138
+ # 短暂休眠,避免 CPU 过载
139
+ time.sleep(0.1)
140
+
141
+ except Exception as e:
142
+ print(f"数据库操作异常: {e}")
143
+ connection.rollback()
144
+ finally:
145
+ connection.close()
146
+ print("数据库连接已关闭,负样本生成程序结束。")
1-data/mysql_part/3-get_homology_pairs.py ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pymysql
2
+ import hashlib
3
+ from Bio.Blast import NCBIWWW
4
+ from Bio.Blast import NCBIXML
5
+ import time
6
+ import random
7
+
8
+ # ==================== 数据库配置 ====================
9
+ HOST = ""
10
+ PORT = 3306
11
+ USER = ""
12
+ PASSWORD = ""
13
+ DATABASE = "ionet"
14
+ CHARSET = "utf8mb4"
15
+
16
+ # ==================== BLAST 参数 ====================
17
+ MAX_HOMOLOGS_PER_QUERY = 5
18
+ E_VALUE_THRESH = 1e-10
19
+ IDENTITY_THRESH = 30.0
20
+ HITLIST_SIZE = 20
21
+
22
+ # ==================== 数据库连接 ====================
23
+ connection = pymysql.connect(
24
+ host=HOST,
25
+ port=PORT,
26
+ user=USER,
27
+ password=PASSWORD,
28
+ database=DATABASE,
29
+ charset=CHARSET,
30
+ autocommit=False, # 手动提交
31
+ cursorclass=pymysql.cursors.DictCursor # 可选:返回字典,便于阅读
32
+ )
33
+
34
+ def compute_pair_md5(seq1, seq2):
35
+ if seq1 < seq2:
36
+ combined = seq1 + seq2
37
+ else:
38
+ combined = seq2 + seq1
39
+ return hashlib.md5(combined.encode('utf-8')).hexdigest()
40
+
41
+ try:
42
+ with connection.cursor() as cursor:
43
+ while True:
44
+ # ==================== 关键修改:随机 + 锁机制 ====================
45
+ try:
46
+ # 开始事务 + 行锁:确保多实例不会同时选中同一行
47
+ cursor.execute("START TRANSACTION")
48
+ cursor.execute("""
49
+ SELECT id, sentence1
50
+ FROM protein_seq
51
+ WHERE is_process = 0
52
+ ORDER BY RAND()
53
+ LIMIT 1
54
+ FOR UPDATE -- 重要!锁住选中的行,直到事务结束
55
+ """)
56
+ row = cursor.fetchone()
57
+
58
+ if row is None:
59
+ connection.commit() # 提交空事务
60
+ print("所有序列已处理完成,没有剩余 is_process=0 的记录。")
61
+ break
62
+
63
+ query_id_in_db = row['id']
64
+ query_seq = row['sentence1']
65
+
66
+ # 立即提交锁事务,释放行锁(但还没标记处理)
67
+ connection.commit()
68
+
69
+ except Exception as e:
70
+ connection.rollback()
71
+ print(f"获取随机序列时出错: {e}")
72
+ time.sleep(5)
73
+ continue
74
+ # ==============================================================
75
+
76
+ print(f"\n正在处理数据库 ID={query_id_in_db} 的蛋白序列 (长度={len(query_seq)})")
77
+
78
+ try:
79
+ print(" 正在运行 NCBI BLAST...")
80
+ result_handle = NCBIWWW.qblast(
81
+ program="blastp",
82
+ database="swissprot",
83
+ sequence=query_seq,
84
+ expect=E_VALUE_THRESH,
85
+ hitlist_size=HITLIST_SIZE
86
+ )
87
+
88
+ blast_records = NCBIXML.parse(result_handle)
89
+ blast_record = next(blast_records, None)
90
+
91
+ inserted_count = 0
92
+ if blast_record:
93
+ for alignment in blast_record.alignments:
94
+ if inserted_count >= MAX_HOMOLOGS_PER_QUERY:
95
+ break
96
+ hit_id = alignment.hit_id
97
+
98
+ for hsp in alignment.hsps:
99
+ if inserted_count >= MAX_HOMOLOGS_PER_QUERY:
100
+ break
101
+
102
+ hit_seq_aligned = hsp.sbjct
103
+ identity = (hsp.identities / hsp.align_length) * 100
104
+
105
+ if identity < IDENTITY_THRESH:
106
+ continue
107
+
108
+ # 排除自身(粗略判断)
109
+ hit_acc = hit_id.split("|")[1] if '|' in hit_id else ""
110
+ if hit_acc and query_seq.startswith(hit_acc):
111
+ continue
112
+
113
+ pair_md5 = compute_pair_md5(query_seq, hit_seq_aligned)
114
+
115
+ insert_sql = """
116
+ INSERT IGNORE INTO protein_pair
117
+ (sentence1, sentence2, pair_md5, label)
118
+ VALUES (%s, %s, %s, 1)
119
+ """
120
+ cursor.execute(insert_sql, (query_seq, hit_seq_aligned, pair_md5))
121
+ if cursor.rowcount > 0:
122
+ inserted_count += 1
123
+ print(f" → 插入正样本对 (label=1, identity={identity:.1f}%)")
124
+
125
+ print(f" 本序列共插入 {inserted_count} 条正样本对。")
126
+
127
+ except Exception as e:
128
+ print(f" BLAST 出错: {e},仍将标记为已处理(避免重复尝试)")
129
+
130
+ # ==================== 标记为已处理 ====================
131
+ try:
132
+ cursor.execute("""
133
+ UPDATE protein_seq
134
+ SET is_process = 1
135
+ WHERE id = %s
136
+ """, (query_id_in_db,))
137
+ connection.commit()
138
+ print(f" 已将数据库 ID={query_id_in_db} 标记为已处理 (is_process=1)")
139
+ except Exception as e:
140
+ connection.rollback()
141
+ print(f" 更新 is_process 失败: {e}")
142
+
143
+ # NCBI 限速等待
144
+ sleep_time = 3 + random.uniform(0, 3)
145
+ print(f" 等待 {sleep_time:.1f} 秒避免 NCBI 限速...\n")
146
+ time.sleep(sleep_time)
147
+
148
+ except Exception as e:
149
+ print(f"程序异常: {e}")
150
+ connection.rollback()
151
+ finally:
152
+ connection.close()
153
+ print("数据库连接已关闭,程序结束。")
1-data/mysql_part/4-get_ft_protein_data_250a.ipynb ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "680e74ed-1fe1-455b-a599-7bc9fcb30924",
7
+ "metadata": {},
8
+ "outputs": [
9
+ {
10
+ "name": "stdout",
11
+ "output_type": "stream",
12
+ "text": [
13
+ "正在抽取正样本(label=1),目标 10000 条,长度 40–250 aa...\n"
14
+ ]
15
+ },
16
+ {
17
+ "name": "stderr",
18
+ "output_type": "stream",
19
+ "text": [
20
+ "/tmp/ipykernel_9166/785285738.py:32: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
21
+ " pos_df = pd.read_sql(\"\"\"\n"
22
+ ]
23
+ },
24
+ {
25
+ "name": "stdout",
26
+ "output_type": "stream",
27
+ "text": [
28
+ "正在抽取负样本(label=0),目标 10000 条,长度 40–250 aa...\n"
29
+ ]
30
+ },
31
+ {
32
+ "name": "stderr",
33
+ "output_type": "stream",
34
+ "text": [
35
+ "/tmp/ipykernel_9166/785285738.py:44: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
36
+ " neg_df = pd.read_sql(\"\"\"\n"
37
+ ]
38
+ },
39
+ {
40
+ "name": "stdout",
41
+ "output_type": "stream",
42
+ "text": [
43
+ "\n",
44
+ "实际抽取完成:\n",
45
+ " 正样本 (label=1): 10000 条\n",
46
+ " 负样本 (label=0): 10000 条\n",
47
+ " 总样本: 20000 条\n",
48
+ "\n",
49
+ "🎉 完成!共生成 20000 条平衡样本(长度 40–250 aa,长度差 ≤ 50 aa)\n",
50
+ "CSV 文件已保存至:protein_pair_20k_length_restricted_balanced.csv\n",
51
+ "\n",
52
+ "前5行预览:\n",
53
+ " sentence1 \\\n",
54
+ "0 MAKPAARPRKKVKKTVVDGIAHIHASFNNTIVTITDRQGNALSWAT... \n",
55
+ "1 MKIGIIGAMEPEVAHLIAAMTNATSQTIAGIEFIAGTLAGKDVVVT... \n",
56
+ "2 MSDSPRRFPTPLLLTGVMGVLILIALLTFTLMRSWDNAPDQSALLG... \n",
57
+ "3 MKWSEVFHDITTRHDFQAMHDFLEKEYTTQIVYPDKKNIYQAFDLT... \n",
58
+ "4 MSSVFEIVNQARRKNKLKRELLDNEKKVRDNRKRVDLLENLLDYIK... \n",
59
+ "\n",
60
+ " sentence2 label \n",
61
+ "0 MAALIVSRLARRGWLWKLPLATRREFWSRSRKEKEPVVAETVEEVK... 0 \n",
62
+ "1 MKIGIIGAMEPEVAHLIAAMTNATSQTIAGIEFIAGTLAGKDVVVT... 1 \n",
63
+ "2 MSDSPRRFPTPLLLTGVMGVLILIALLTFTLMRSWDNAPDQSALLG... 1 \n",
64
+ "3 MEWSQIFHDITTKHDFKAMHDFLEKEYSTAIVYPDRENIYQAFDLT... 1 \n",
65
+ "4 MSSGGLLLLLGLLTLWEVLTPVSSKDRPKFCELLPDTGSCEDFTGA... 0 \n",
66
+ "数据库连接已关闭。\n"
67
+ ]
68
+ }
69
+ ],
70
+ "source": [
71
+ "import pymysql\n",
72
+ "import pandas as pd\n",
73
+ "\n",
74
+ "# ==================== 数据库配置 ====================\n",
75
+ "HOST = \"\"\n",
76
+ "PORT = 3306\n",
77
+ "USER = \"\"\n",
78
+ "PASSWORD = \"\"\n",
79
+ "DATABASE = \"ionet\"\n",
80
+ "CHARSET = \"utf8mb4\"\n",
81
+ "\n",
82
+ "# ==================== 参数 ====================\n",
83
+ "POSITIVE_SAMPLES = 10000 # 正样本目标数量\n",
84
+ "NEGATIVE_SAMPLES = 10000 # 负样本目标数量\n",
85
+ "MIN_LENGTH = 40\n",
86
+ "MAX_LENGTH = 250\n",
87
+ "LENGTH_DIFF_MAX = 50 # 新增:一对序列长度差不超过 50 aa(推荐!)\n",
88
+ "OUTPUT_CSV = \"protein_pair_20k_length_restricted_balanced.csv\"\n",
89
+ "\n",
90
+ "# ==================== 连接数据库 ====================\n",
91
+ "connection = pymysql.connect(\n",
92
+ " host=HOST,\n",
93
+ " port=PORT,\n",
94
+ " user=USER,\n",
95
+ " password=PASSWORD,\n",
96
+ " database=DATABASE,\n",
97
+ " charset=CHARSET\n",
98
+ ")\n",
99
+ "\n",
100
+ "try:\n",
101
+ " print(f\"正在抽取正样本(label=1),目标 {POSITIVE_SAMPLES} 条,长度 40–250 aa...\")\n",
102
+ " pos_df = pd.read_sql(\"\"\"\n",
103
+ " SELECT sentence1, sentence2, label\n",
104
+ " FROM protein_pair\n",
105
+ " WHERE label = 1\n",
106
+ " AND LENGTH(sentence1) BETWEEN %s AND %s\n",
107
+ " AND LENGTH(sentence2) BETWEEN %s AND %s\n",
108
+ " AND ABS(LENGTH(sentence1) - LENGTH(sentence2)) <= %s\n",
109
+ " ORDER BY RAND()\n",
110
+ " LIMIT %s\n",
111
+ " \"\"\", connection, params=(MIN_LENGTH, MAX_LENGTH, MIN_LENGTH, MAX_LENGTH, LENGTH_DIFF_MAX, POSITIVE_SAMPLES))\n",
112
+ "\n",
113
+ " print(f\"正在抽取负样本(label=0),目标 {NEGATIVE_SAMPLES} 条,长度 40–250 aa...\")\n",
114
+ " neg_df = pd.read_sql(\"\"\"\n",
115
+ " SELECT sentence1, sentence2, label\n",
116
+ " FROM protein_pair\n",
117
+ " WHERE label = 0\n",
118
+ " AND LENGTH(sentence1) BETWEEN %s AND %s\n",
119
+ " AND LENGTH(sentence2) BETWEEN %s AND %s\n",
120
+ " AND ABS(LENGTH(sentence1) - LENGTH(sentence2)) <= %s\n",
121
+ " ORDER BY RAND()\n",
122
+ " LIMIT %s\n",
123
+ " \"\"\", connection, params=(MIN_LENGTH, MAX_LENGTH, MIN_LENGTH, MAX_LENGTH, LENGTH_DIFF_MAX, NEGATIVE_SAMPLES))\n",
124
+ "\n",
125
+ " # 检查实际抽取数量\n",
126
+ " actual_pos = len(pos_df)\n",
127
+ " actual_neg = len(neg_df)\n",
128
+ " print(f\"\\n实际抽取完成:\")\n",
129
+ " print(f\" 正样本 (label=1): {actual_pos} 条\")\n",
130
+ " print(f\" 负样本 (label=0): {actual_neg} 条\")\n",
131
+ " print(f\" 总样本: {actual_pos + actual_neg} 条\")\n",
132
+ "\n",
133
+ " if actual_pos < POSITIVE_SAMPLES:\n",
134
+ " print(f\" 警告:正样本不足,仅抽到 {actual_pos} 条(可能数据库中符合条件的样本较少)\")\n",
135
+ " if actual_neg < NEGATIVE_SAMPLES:\n",
136
+ " print(f\" 警告:负样本不足,仅抽到 {actual_neg} 条\")\n",
137
+ "\n",
138
+ " # 合并并打乱\n",
139
+ " sample_df = pd.concat([pos_df, neg_df], ignore_index=True)\n",
140
+ " sample_df = sample_df.sample(frac=1, random_state=42).reset_index(drop=True)\n",
141
+ "\n",
142
+ " # 保存 CSV\n",
143
+ " sample_df.to_csv(OUTPUT_CSV, index=False, encoding=\"utf-8\")\n",
144
+ "\n",
145
+ " print(f\"\\n🎉 完成!共生成 {len(sample_df)} 条平衡样本(长度 40–250 aa,长度差 ≤ {LENGTH_DIFF_MAX} aa)\")\n",
146
+ " print(f\"CSV 文件已保存至:{OUTPUT_CSV}\")\n",
147
+ " print(\"\\n前5行预览:\")\n",
148
+ " print(sample_df.head())\n",
149
+ "\n",
150
+ "except Exception as e:\n",
151
+ " print(f\"错误:{e}\")\n",
152
+ "finally:\n",
153
+ " connection.close()\n",
154
+ " print(\"数据库连接已关闭。\")"
155
+ ]
156
+ },
157
+ {
158
+ "cell_type": "code",
159
+ "execution_count": null,
160
+ "id": "f036071a-269e-4684-a292-46fcbce0c384",
161
+ "metadata": {},
162
+ "outputs": [],
163
+ "source": []
164
+ }
165
+ ],
166
+ "metadata": {
167
+ "kernelspec": {
168
+ "display_name": "Python 3 (ipykernel)",
169
+ "language": "python",
170
+ "name": "python3"
171
+ },
172
+ "language_info": {
173
+ "codemirror_mode": {
174
+ "name": "ipython",
175
+ "version": 3
176
+ },
177
+ "file_extension": ".py",
178
+ "mimetype": "text/x-python",
179
+ "name": "python",
180
+ "nbconvert_exporter": "python",
181
+ "pygments_lexer": "ipython3",
182
+ "version": "3.12.3"
183
+ }
184
+ },
185
+ "nbformat": 4,
186
+ "nbformat_minor": 5
187
+ }
1-data/mysql_part/4-get_sft_protein_data_full.ipynb ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "680e74ed-1fe1-455b-a599-7bc9fcb30924",
7
+ "metadata": {},
8
+ "outputs": [
9
+ {
10
+ "name": "stdout",
11
+ "output_type": "stream",
12
+ "text": [
13
+ "正在抽取正样本(label=1),目标 10000 条,仅限制长度差 ≤ 50 aa...\n"
14
+ ]
15
+ },
16
+ {
17
+ "name": "stderr",
18
+ "output_type": "stream",
19
+ "text": [
20
+ "/tmp/ipykernel_9297/2781235706.py:30: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
21
+ " pos_df = pd.read_sql(\"\"\"\n"
22
+ ]
23
+ },
24
+ {
25
+ "name": "stdout",
26
+ "output_type": "stream",
27
+ "text": [
28
+ "正在抽取负样本(label=0),目标 10000 条,仅限制长度差 ≤ 50 aa...\n"
29
+ ]
30
+ },
31
+ {
32
+ "name": "stderr",
33
+ "output_type": "stream",
34
+ "text": [
35
+ "/tmp/ipykernel_9297/2781235706.py:40: UserWarning: pandas only supports SQLAlchemy connectable (engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested. Please consider using SQLAlchemy.\n",
36
+ " neg_df = pd.read_sql(\"\"\"\n"
37
+ ]
38
+ },
39
+ {
40
+ "name": "stdout",
41
+ "output_type": "stream",
42
+ "text": [
43
+ "\n",
44
+ "实际抽取完成:\n",
45
+ " 正样本 (label=1): 10000 条\n",
46
+ " 负样本 (label=0): 10000 条\n",
47
+ " 总样本: 20000 条\n",
48
+ "\n",
49
+ "🎉 完成!共生成 20000 条样本(不限制绝对长度,仅限制一对长度差 ≤ 50 aa)\n",
50
+ "CSV 文件已保存至:protein_pair_20k_no_length_limit_but_diff_limited.csv\n",
51
+ "\n",
52
+ "前5行预览:\n",
53
+ " sentence1 \\\n",
54
+ "0 MSEMRLIVAGAGGRMGRALTRAISETEGVVLTGALESPNSELLGKD... \n",
55
+ "1 ADFKVYWEVPSFLCSKRFKINVTEVLTSHEILVNQGESFNGDKIVI... \n",
56
+ "2 MLQNIRIVLVETSHTGNMGSVARAMKTMGLTNLWLVNPLVKPDSQA... \n",
57
+ "3 MESLKIYNTLAREKQTFVPIEPGRVRMYVCGMTVYDYCHVGHARVM... \n",
58
+ "4 MAVKLKIKKGDSVKVITGDDKGKTGKVLAVYPKTLKVVVEGCKIAK... \n",
59
+ "\n",
60
+ " sentence2 label \n",
61
+ "0 MLQKIILATVVAFVLSLASGPLFIPYLRKLKFGQKVREDGPKSHIK... 0 \n",
62
+ "1 AEFKVYWEVPSFLCSKRFNINVTQVLTSHKILVNQGESFNGDKIVM... 1 \n",
63
+ "2 MLQNIRIVLVETSHTGNMGSVARAMKTMGLTNLWLVNPLVKPDSQA... 1 \n",
64
+ "3 MESLKIYNTLAREKQTFVPIEPGRVRMYVCGMTVYDYCHVGHARVM... 1 \n",
65
+ "4 MPKSKINSPEENFDSSAAAGVDRRTPVKLNASGTPRWYIVIMLGLM... 0 \n",
66
+ "\n",
67
+ "长度统计(供论文参考):\n",
68
+ "sentence1 长度分布:\n",
69
+ "count 20000.000000\n",
70
+ "mean 294.462200\n",
71
+ "std 242.257635\n",
72
+ "min 29.000000\n",
73
+ "25% 147.000000\n",
74
+ "50% 240.000000\n",
75
+ "75% 374.000000\n",
76
+ "max 7639.000000\n",
77
+ "Name: sentence1, dtype: float64\n",
78
+ "\n",
79
+ "sentence2 长度分布:\n",
80
+ "count 20000.000000\n",
81
+ "mean 294.066100\n",
82
+ "std 242.058872\n",
83
+ "min 29.000000\n",
84
+ "25% 148.000000\n",
85
+ "50% 240.000000\n",
86
+ "75% 372.000000\n",
87
+ "max 7639.000000\n",
88
+ "Name: sentence2, dtype: float64\n",
89
+ "\n",
90
+ "长度差分布:\n",
91
+ "count 20000.000000\n",
92
+ "mean 12.787100\n",
93
+ "std 14.561544\n",
94
+ "min 0.000000\n",
95
+ "25% 0.000000\n",
96
+ "50% 7.000000\n",
97
+ "75% 22.000000\n",
98
+ "max 50.000000\n",
99
+ "dtype: float64\n",
100
+ "数据库连接已关闭。\n"
101
+ ]
102
+ }
103
+ ],
104
+ "source": [
105
+ "import pymysql\n",
106
+ "import pandas as pd\n",
107
+ "\n",
108
+ "# ==================== 数据库配置 ====================\n",
109
+ "HOST = \"\"\n",
110
+ "PORT = 3306\n",
111
+ "USER = \"\"\n",
112
+ "PASSWORD = \"\"\n",
113
+ "DATABASE = \"ionet\"\n",
114
+ "CHARSET = \"utf8mb4\"\n",
115
+ "\n",
116
+ "# ==================== 参数 ====================\n",
117
+ "POSITIVE_SAMPLES = 10000 # 正样本目标数量\n",
118
+ "NEGATIVE_SAMPLES = 10000 # 负样本目标数量\n",
119
+ "LENGTH_DIFF_MAX = 50 # 一对序列长度差不超过 50 aa(关键限制)\n",
120
+ "OUTPUT_CSV = \"protein_pair_20k_no_length_limit_but_diff_limited.csv\"\n",
121
+ "\n",
122
+ "# ==================== 连接数据库 ====================\n",
123
+ "connection = pymysql.connect(\n",
124
+ " host=HOST,\n",
125
+ " port=PORT,\n",
126
+ " user=USER,\n",
127
+ " password=PASSWORD,\n",
128
+ " database=DATABASE,\n",
129
+ " charset=CHARSET\n",
130
+ ")\n",
131
+ "\n",
132
+ "try:\n",
133
+ " print(f\"正在抽取正样本(label=1),目标 {POSITIVE_SAMPLES} 条,仅限制长度差 ≤ {LENGTH_DIFF_MAX} aa...\")\n",
134
+ " pos_df = pd.read_sql(\"\"\"\n",
135
+ " SELECT sentence1, sentence2, label\n",
136
+ " FROM protein_pair\n",
137
+ " WHERE label = 1\n",
138
+ " AND ABS(LENGTH(sentence1) - LENGTH(sentence2)) <= %s\n",
139
+ " ORDER BY RAND()\n",
140
+ " LIMIT %s\n",
141
+ " \"\"\", connection, params=(LENGTH_DIFF_MAX, POSITIVE_SAMPLES))\n",
142
+ "\n",
143
+ " print(f\"正在抽取负样本(label=0),目标 {NEGATIVE_SAMPLES} 条,仅限制长度差 ≤ {LENGTH_DIFF_MAX} aa...\")\n",
144
+ " neg_df = pd.read_sql(\"\"\"\n",
145
+ " SELECT sentence1, sentence2, label\n",
146
+ " FROM protein_pair\n",
147
+ " WHERE label = 0\n",
148
+ " AND ABS(LENGTH(sentence1) - LENGTH(sentence2)) <= %s\n",
149
+ " ORDER BY RAND()\n",
150
+ " LIMIT %s\n",
151
+ " \"\"\", connection, params=(LENGTH_DIFF_MAX, NEGATIVE_SAMPLES))\n",
152
+ "\n",
153
+ " # 检查实际抽取数量\n",
154
+ " actual_pos = len(pos_df)\n",
155
+ " actual_neg = len(neg_df)\n",
156
+ " print(f\"\\n实际抽取完成:\")\n",
157
+ " print(f\" 正样本 (label=1): {actual_pos} 条\")\n",
158
+ " print(f\" 负样本 (label=0): {actual_neg} 条\")\n",
159
+ " print(f\" 总样本: {actual_pos + actual_neg} 条\")\n",
160
+ "\n",
161
+ " if actual_pos < POSITIVE_SAMPLES:\n",
162
+ " print(f\" 警告:正样本不足,仅抽到 {actual_pos} 条(可能数据库中长度差 ≤ {LENGTH_DIFF_MAX} 的样本较少)\")\n",
163
+ " if actual_neg < NEGATIVE_SAMPLES:\n",
164
+ " print(f\" 警告:负样本不足,仅抽到 {actual_neg} 条\")\n",
165
+ "\n",
166
+ " # 合并并打乱\n",
167
+ " sample_df = pd.concat([pos_df, neg_df], ignore_index=True)\n",
168
+ " sample_df = sample_df.sample(frac=1, random_state=42).reset_index(drop=True)\n",
169
+ "\n",
170
+ " # 保存 CSV\n",
171
+ " sample_df.to_csv(OUTPUT_CSV, index=False, encoding=\"utf-8\")\n",
172
+ "\n",
173
+ " print(f\"\\n🎉 完成!共生成 {len(sample_df)} 条样本(不限制绝对长度,仅限制一对长度差 ≤ {LENGTH_DIFF_MAX} aa)\")\n",
174
+ " print(f\"CSV 文件已保存至:{OUTPUT_CSV}\")\n",
175
+ " print(\"\\n前5行预览:\")\n",
176
+ " print(sample_df.head())\n",
177
+ "\n",
178
+ " # 可选:输出长度统计,便于论文描述数据集分布\n",
179
+ " print(\"\\n长度统计(供论文参考):\")\n",
180
+ " print(\"sentence1 长度分布:\")\n",
181
+ " print(sample_df['sentence1'].str.len().describe())\n",
182
+ " print(\"\\nsentence2 长度分布:\")\n",
183
+ " print(sample_df['sentence2'].str.len().describe())\n",
184
+ " print(\"\\n长度差分布:\")\n",
185
+ " print(abs(sample_df['sentence1'].str.len() - sample_df['sentence2'].str.len()).describe())\n",
186
+ "\n",
187
+ "except Exception as e:\n",
188
+ " print(f\"错误:{e}\")\n",
189
+ "finally:\n",
190
+ " connection.close()\n",
191
+ " print(\"数据库连接已关闭。\")"
192
+ ]
193
+ },
194
+ {
195
+ "cell_type": "code",
196
+ "execution_count": null,
197
+ "id": "f036071a-269e-4684-a292-46fcbce0c384",
198
+ "metadata": {},
199
+ "outputs": [],
200
+ "source": []
201
+ }
202
+ ],
203
+ "metadata": {
204
+ "kernelspec": {
205
+ "display_name": "Python 3 (ipykernel)",
206
+ "language": "python",
207
+ "name": "python3"
208
+ },
209
+ "language_info": {
210
+ "codemirror_mode": {
211
+ "name": "ipython",
212
+ "version": 3
213
+ },
214
+ "file_extension": ".py",
215
+ "mimetype": "text/x-python",
216
+ "name": "python",
217
+ "nbconvert_exporter": "python",
218
+ "pygments_lexer": "ipython3",
219
+ "version": "3.12.3"
220
+ }
221
+ },
222
+ "nbformat": 4,
223
+ "nbformat_minor": 5
224
+ }
1-data/mysql_part/5-upload_data_hf.ipynb ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 6,
6
+ "id": "9e0cfe40-9613-4e9e-9b77-c7877632d035",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "# import os\n",
11
+ "\n",
12
+ "# # 设置环境变量\n",
13
+ "# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'\n",
14
+ "\n",
15
+ "# # 打印环境变量以确认设置成功\n",
16
+ "# print(os.environ.get('HF_ENDPOINT'))\n",
17
+ "\n",
18
+ "import subprocess\n",
19
+ "import os\n",
20
+ "\n",
21
+ "result = subprocess.run('bash -c \"source /etc/network_turbo && env | grep proxy\"', shell=True, capture_output=True, text=True)\n",
22
+ "output = result.stdout\n",
23
+ "for line in output.splitlines():\n",
24
+ " if '=' in line:\n",
25
+ " var, value = line.split('=', 1)\n",
26
+ " os.environ[var] = value"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": 7,
32
+ "id": "80df554c-7bbe-4313-b9fd-ea1c61c125ba",
33
+ "metadata": {},
34
+ "outputs": [
35
+ {
36
+ "name": "stdout",
37
+ "output_type": "stream",
38
+ "text": [
39
+ "正在上传 protein_pair_short...\n"
40
+ ]
41
+ },
42
+ {
43
+ "data": {
44
+ "application/vnd.jupyter.widget-view+json": {
45
+ "model_id": "702159e131d9433d9664afa8f9c78dda",
46
+ "version_major": 2,
47
+ "version_minor": 0
48
+ },
49
+ "text/plain": [
50
+ "Uploading the dataset shards: 0%| | 0/1 [00:00<?, ? shards/s]"
51
+ ]
52
+ },
53
+ "metadata": {},
54
+ "output_type": "display_data"
55
+ },
56
+ {
57
+ "data": {
58
+ "application/vnd.jupyter.widget-view+json": {
59
+ "model_id": "7d25d3941a11494faa34add308eb7b1d",
60
+ "version_major": 2,
61
+ "version_minor": 0
62
+ },
63
+ "text/plain": [
64
+ "Creating parquet from Arrow format: 0%| | 0/20 [00:00<?, ?ba/s]"
65
+ ]
66
+ },
67
+ "metadata": {},
68
+ "output_type": "display_data"
69
+ },
70
+ {
71
+ "data": {
72
+ "application/vnd.jupyter.widget-view+json": {
73
+ "model_id": "155b6f5a30844dec891b951f67790afb",
74
+ "version_major": 2,
75
+ "version_minor": 0
76
+ },
77
+ "text/plain": [
78
+ "Processing Files (0 / 0) : | | 0.00B / 0.00B "
79
+ ]
80
+ },
81
+ "metadata": {},
82
+ "output_type": "display_data"
83
+ },
84
+ {
85
+ "data": {
86
+ "application/vnd.jupyter.widget-view+json": {
87
+ "model_id": "8fa30989e50843f5bf9453df9bd9ad87",
88
+ "version_major": 2,
89
+ "version_minor": 0
90
+ },
91
+ "text/plain": [
92
+ "New Data Upload : | | 0.00B / 0.00B "
93
+ ]
94
+ },
95
+ "metadata": {},
96
+ "output_type": "display_data"
97
+ },
98
+ {
99
+ "data": {
100
+ "application/vnd.jupyter.widget-view+json": {
101
+ "model_id": "bbc24436ed9145c69c8c59b5fed01c81",
102
+ "version_major": 2,
103
+ "version_minor": 0
104
+ },
105
+ "text/plain": [
106
+ " : 100%|##########| 5.82MB / 5.82MB "
107
+ ]
108
+ },
109
+ "metadata": {},
110
+ "output_type": "display_data"
111
+ },
112
+ {
113
+ "name": "stdout",
114
+ "output_type": "stream",
115
+ "text": [
116
+ "正在上传 protein_pair_full...\n"
117
+ ]
118
+ },
119
+ {
120
+ "data": {
121
+ "application/vnd.jupyter.widget-view+json": {
122
+ "model_id": "b1cd86dcb3fc4897821fa9c8afa0703c",
123
+ "version_major": 2,
124
+ "version_minor": 0
125
+ },
126
+ "text/plain": [
127
+ "Uploading the dataset shards: 0%| | 0/1 [00:00<?, ? shards/s]"
128
+ ]
129
+ },
130
+ "metadata": {},
131
+ "output_type": "display_data"
132
+ },
133
+ {
134
+ "data": {
135
+ "application/vnd.jupyter.widget-view+json": {
136
+ "model_id": "ca158bec1fc7458a8c864f30e12d356f",
137
+ "version_major": 2,
138
+ "version_minor": 0
139
+ },
140
+ "text/plain": [
141
+ "Creating parquet from Arrow format: 0%| | 0/20 [00:00<?, ?ba/s]"
142
+ ]
143
+ },
144
+ "metadata": {},
145
+ "output_type": "display_data"
146
+ },
147
+ {
148
+ "data": {
149
+ "application/vnd.jupyter.widget-view+json": {
150
+ "model_id": "6f86925787144f37941cdba943edb7e4",
151
+ "version_major": 2,
152
+ "version_minor": 0
153
+ },
154
+ "text/plain": [
155
+ "Processing Files (0 / 0) : | | 0.00B / 0.00B "
156
+ ]
157
+ },
158
+ "metadata": {},
159
+ "output_type": "display_data"
160
+ },
161
+ {
162
+ "data": {
163
+ "application/vnd.jupyter.widget-view+json": {
164
+ "model_id": "4475aa6f34e74d4391ca8585a6016f2f",
165
+ "version_major": 2,
166
+ "version_minor": 0
167
+ },
168
+ "text/plain": [
169
+ "New Data Upload : | | 0.00B / 0.00B "
170
+ ]
171
+ },
172
+ "metadata": {},
173
+ "output_type": "display_data"
174
+ },
175
+ {
176
+ "data": {
177
+ "application/vnd.jupyter.widget-view+json": {
178
+ "model_id": "531d28f12c5b4c7c962347e48ea05b40",
179
+ "version_major": 2,
180
+ "version_minor": 0
181
+ },
182
+ "text/plain": [
183
+ " : 100%|##########| 11.8MB / 11.8MB "
184
+ ]
185
+ },
186
+ "metadata": {},
187
+ "output_type": "display_data"
188
+ },
189
+ {
190
+ "data": {
191
+ "application/vnd.jupyter.widget-view+json": {
192
+ "model_id": "de24e59c3d2c474a84b9ede63032ff5a",
193
+ "version_major": 2,
194
+ "version_minor": 0
195
+ },
196
+ "text/plain": [
197
+ "README.md: 0%| | 0.00/410 [00:00<?, ?B/s]"
198
+ ]
199
+ },
200
+ "metadata": {},
201
+ "output_type": "display_data"
202
+ },
203
+ {
204
+ "name": "stdout",
205
+ "output_type": "stream",
206
+ "text": [
207
+ "上传完成!\n",
208
+ "现在可以直接这样加载:\n",
209
+ "load_dataset(\"dnagpt/biopaws\", \"protein_pair_short\")\n",
210
+ "load_dataset(\"dnagpt/biopaws\", \"protein_pair_full\")\n"
211
+ ]
212
+ }
213
+ ],
214
+ "source": [
215
+ "from datasets import load_dataset, DatasetDict\n",
216
+ "from huggingface_hub import login\n",
217
+ "\n",
218
+ "HF_TOKEN = \"hf_your_token\"\n",
219
+ "login(token=HF_TOKEN)\n",
220
+ "\n",
221
+ "short_csv = \"protein_pair_20k_length_restricted_balanced.csv\"\n",
222
+ "full_csv = \"protein_pair_20k_no_length_limit_but_diff_limited.csv\"\n",
223
+ "\n",
224
+ "short_ds = load_dataset(\"csv\", data_files=short_csv)[\"train\"]\n",
225
+ "full_ds = load_dataset(\"csv\", data_files=full_csv)[\"train\"]\n",
226
+ "\n",
227
+ "# 关键修改:分别上传,每个指定 config_name\n",
228
+ "print(\"正在上传 protein_pair_short...\")\n",
229
+ "short_ds.push_to_hub(\"dnagpt/biopaws\", config_name=\"protein_pair_short\", private=False)\n",
230
+ "\n",
231
+ "print(\"正在上传 protein_pair_full...\")\n",
232
+ "full_ds.push_to_hub(\"dnagpt/biopaws\", config_name=\"protein_pair_full\", private=False)\n",
233
+ "\n",
234
+ "print(\"上传完成!\")\n",
235
+ "print(\"现在可以直接这样加载:\")\n",
236
+ "print('load_dataset(\"dnagpt/biopaws\", \"protein_pair_short\")')\n",
237
+ "print('load_dataset(\"dnagpt/biopaws\", \"protein_pair_full\")')"
238
+ ]
239
+ },
240
+ {
241
+ "cell_type": "code",
242
+ "execution_count": null,
243
+ "id": "26987755-e4be-4f64-873c-22305afc5b74",
244
+ "metadata": {},
245
+ "outputs": [],
246
+ "source": []
247
+ }
248
+ ],
249
+ "metadata": {
250
+ "kernelspec": {
251
+ "display_name": "Python 3 (ipykernel)",
252
+ "language": "python",
253
+ "name": "python3"
254
+ },
255
+ "language_info": {
256
+ "codemirror_mode": {
257
+ "name": "ipython",
258
+ "version": 3
259
+ },
260
+ "file_extension": ".py",
261
+ "mimetype": "text/x-python",
262
+ "name": "python",
263
+ "nbconvert_exporter": "python",
264
+ "pygments_lexer": "ipython3",
265
+ "version": "3.12.3"
266
+ }
267
+ },
268
+ "nbformat": 4,
269
+ "nbformat_minor": 5
270
+ }
1-data/mysql_part/protein_pair.sql ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dcd0a8467a1088ea7a51190d1aa3aababdfb14e995c537edd5b7068802aabfa7
3
+ size 77062385
1-data/mysql_part/protein_seq.sql ADDED
The diff for this file is too large to render. See raw diff
 
1-data/protein_pair_20k_length_restricted_balanced.csv ADDED
The diff for this file is too large to render. See raw diff
 
1-data/protein_pair_20k_no_length_limit_but_diff_limited.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:29b367b7442b55a69d51b0ec56d96630e51e9b81dd4aaf316aa08c4588dd6989
3
+ size 11850592
1-data/protein_pair_remote.csv ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:05c7949858f607ddbd712354dc02d7348cf3c2944728754883147759c871eb9e
3
+ size 94852549
1-data/sampled_10000_proteins.fasta ADDED
The diff for this file is too large to render. See raw diff
 
2-gpt_ft_test_explain/.ipynb_checkpoints/1-gpt2_ft_en_test_protein_confusion-checkpoint.ipynb ADDED
@@ -0,0 +1,534 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "b87144f0-f446-4280-9387-e306169549a4",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "# import os\n",
11
+ "\n",
12
+ "# # 设置环境变量\n",
13
+ "# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'\n",
14
+ "\n",
15
+ "# # 打印环境变量以确认设置成功\n",
16
+ "# print(os.environ.get('HF_ENDPOINT'))\n",
17
+ "\n",
18
+ "import subprocess\n",
19
+ "import os\n",
20
+ "\n",
21
+ "result = subprocess.run('bash -c \"source /etc/network_turbo && env | grep proxy\"', shell=True, capture_output=True, text=True)\n",
22
+ "output = result.stdout\n",
23
+ "for line in output.splitlines():\n",
24
+ " if '=' in line:\n",
25
+ " var, value = line.split('=', 1)\n",
26
+ " os.environ[var] = value"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": 2,
32
+ "id": "bc7e500f-4920-4bec-9ff5-ffdcbf3c10d6",
33
+ "metadata": {},
34
+ "outputs": [
35
+ {
36
+ "name": "stdout",
37
+ "output_type": "stream",
38
+ "text": [
39
+ "Initializing model: gpt2...\n"
40
+ ]
41
+ },
42
+ {
43
+ "name": "stderr",
44
+ "output_type": "stream",
45
+ "text": [
46
+ "Some weights of GPT2ForSequenceClassification were not initialized from the model checkpoint at gpt2 and are newly initialized: ['score.weight']\n",
47
+ "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n"
48
+ ]
49
+ },
50
+ {
51
+ "name": "stdout",
52
+ "output_type": "stream",
53
+ "text": [
54
+ "Loading PAWS-X (en) dataset...\n"
55
+ ]
56
+ },
57
+ {
58
+ "name": "stderr",
59
+ "output_type": "stream",
60
+ "text": [
61
+ "/tmp/ipykernel_46687/771796766.py:97: FutureWarning: `tokenizer` is deprecated and will be removed in version 5.0.0 for `Trainer.__init__`. Use `processing_class` instead.\n",
62
+ " trainer = Trainer(\n"
63
+ ]
64
+ },
65
+ {
66
+ "name": "stdout",
67
+ "output_type": "stream",
68
+ "text": [
69
+ "\n",
70
+ ">>> Starting training with Seed 56...\n"
71
+ ]
72
+ },
73
+ {
74
+ "data": {
75
+ "text/html": [
76
+ "\n",
77
+ " <div>\n",
78
+ " \n",
79
+ " <progress value='3088' max='3088' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
80
+ " [3088/3088 14:33, Epoch 4/4]\n",
81
+ " </div>\n",
82
+ " <table border=\"1\" class=\"dataframe\">\n",
83
+ " <thead>\n",
84
+ " <tr style=\"text-align: left;\">\n",
85
+ " <th>Epoch</th>\n",
86
+ " <th>Training Loss</th>\n",
87
+ " <th>Validation Loss</th>\n",
88
+ " <th>Accuracy</th>\n",
89
+ " </tr>\n",
90
+ " </thead>\n",
91
+ " <tbody>\n",
92
+ " <tr>\n",
93
+ " <td>1</td>\n",
94
+ " <td>0.651800</td>\n",
95
+ " <td>0.507618</td>\n",
96
+ " <td>0.774000</td>\n",
97
+ " </tr>\n",
98
+ " <tr>\n",
99
+ " <td>2</td>\n",
100
+ " <td>0.391900</td>\n",
101
+ " <td>0.337644</td>\n",
102
+ " <td>0.861500</td>\n",
103
+ " </tr>\n",
104
+ " <tr>\n",
105
+ " <td>3</td>\n",
106
+ " <td>0.299300</td>\n",
107
+ " <td>0.287754</td>\n",
108
+ " <td>0.887000</td>\n",
109
+ " </tr>\n",
110
+ " <tr>\n",
111
+ " <td>4</td>\n",
112
+ " <td>0.242700</td>\n",
113
+ " <td>0.276862</td>\n",
114
+ " <td>0.890500</td>\n",
115
+ " </tr>\n",
116
+ " </tbody>\n",
117
+ "</table><p>"
118
+ ],
119
+ "text/plain": [
120
+ "<IPython.core.display.HTML object>"
121
+ ]
122
+ },
123
+ "metadata": {},
124
+ "output_type": "display_data"
125
+ },
126
+ {
127
+ "name": "stdout",
128
+ "output_type": "stream",
129
+ "text": [
130
+ "\n",
131
+ ">>> Saving best model to: ./best_model_seed_56 ...\n",
132
+ "\n",
133
+ ">>> Loading BioPAWS (Short)...\n"
134
+ ]
135
+ },
136
+ {
137
+ "data": {
138
+ "application/vnd.jupyter.widget-view+json": {
139
+ "model_id": "e1c11d6121b34ba4908c3d161e13d700",
140
+ "version_major": 2,
141
+ "version_minor": 0
142
+ },
143
+ "text/plain": [
144
+ "Map (num_proc=4): 0%| | 0/14000 [00:00<?, ? examples/s]"
145
+ ]
146
+ },
147
+ "metadata": {},
148
+ "output_type": "display_data"
149
+ },
150
+ {
151
+ "data": {
152
+ "application/vnd.jupyter.widget-view+json": {
153
+ "model_id": "964967575b764b59b585aa0030354558",
154
+ "version_major": 2,
155
+ "version_minor": 0
156
+ },
157
+ "text/plain": [
158
+ "Map (num_proc=4): 0%| | 0/6000 [00:00<?, ? examples/s]"
159
+ ]
160
+ },
161
+ "metadata": {},
162
+ "output_type": "display_data"
163
+ },
164
+ {
165
+ "name": "stdout",
166
+ "output_type": "stream",
167
+ "text": [
168
+ ">>> Running inference on Protein Pair Short...\n",
169
+ "Executing inference...\n",
170
+ "[Protein Pair Short] Raw Accuracy: 0.8403\n",
171
+ "\n",
172
+ ">>> Classification Report for Protein Pair Short:\n",
173
+ " precision recall f1-score support\n",
174
+ "\n",
175
+ "Non-Homologous 0.7629 0.9793 0.8577 2947\n",
176
+ " Homologous 0.9725 0.7062 0.8182 3053\n",
177
+ "\n",
178
+ " accuracy 0.8403 6000\n",
179
+ " macro avg 0.8677 0.8427 0.8379 6000\n",
180
+ " weighted avg 0.8695 0.8403 0.8376 6000\n",
181
+ "\n",
182
+ "========================================\n",
183
+ ">>> Confusion Matrix saved to: confusion_matrix_Protein_Pair_Short_seed56.png\n",
184
+ "\n",
185
+ ">>> Loading BioPAWS (Full)...\n"
186
+ ]
187
+ },
188
+ {
189
+ "data": {
190
+ "application/vnd.jupyter.widget-view+json": {
191
+ "model_id": "0248f801ac6e4324961606006431dde0",
192
+ "version_major": 2,
193
+ "version_minor": 0
194
+ },
195
+ "text/plain": [
196
+ "Map (num_proc=4): 0%| | 0/14000 [00:00<?, ? examples/s]"
197
+ ]
198
+ },
199
+ "metadata": {},
200
+ "output_type": "display_data"
201
+ },
202
+ {
203
+ "data": {
204
+ "application/vnd.jupyter.widget-view+json": {
205
+ "model_id": "e9d2e03920694a92b76a591ff9540b95",
206
+ "version_major": 2,
207
+ "version_minor": 0
208
+ },
209
+ "text/plain": [
210
+ "Map (num_proc=4): 0%| | 0/6000 [00:00<?, ? examples/s]"
211
+ ]
212
+ },
213
+ "metadata": {},
214
+ "output_type": "display_data"
215
+ },
216
+ {
217
+ "name": "stdout",
218
+ "output_type": "stream",
219
+ "text": [
220
+ ">>> Running inference on Protein Pair Full...\n",
221
+ "Executing inference...\n",
222
+ "\n",
223
+ "==============================\n",
224
+ "Final Results:\n",
225
+ "{\n",
226
+ " \"seed\": 56,\n",
227
+ " \"lang\": \"en\",\n",
228
+ " \"protein_pair_short\": {\n",
229
+ " \"accuracy\": 0.8403333333333334\n",
230
+ " },\n",
231
+ " \"protein_pair_full\": {\n",
232
+ " \"accuracy\": 0.7765\n",
233
+ " }\n",
234
+ "}\n",
235
+ "==============================\n"
236
+ ]
237
+ },
238
+ {
239
+ "data": {
240
+ "image/png": "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AtLS0gAAZcqUKemwiIiICGqWVMTExCgcUpp/REifPn1KMiQiIiL6f2qVVACKh5QaGRnBxsYGAwYMQL9+/VQQFREREalVUhEaGqrqEIiIiKgAapNUpKamYt68eZBIJHB1dVXYv+JTun79OhISEtCyZUsYGhqW6LGJiIjUgdokFfr6+jh69CgyMjLQpUuXT3acdevWwd/fH5s2bRLWTZw4EadPnwYAVKpUCbt27YK5ufkni4GIiEgdqdWQ0po1awIAEhMTP9kxTpw4gSpVqgjLV65cgZ+fH7p3747p06cjISEBGzdu/GTHJyIiUldqlVR8//330NHRwcqVK/H06dNPcozY2FhUrVpVWPbz80P58uWxePFijB49GgMGDMD58+c/ybGJiIjUmdo0fwDAihUrYGJigqdPn6Jr166oUqUKypUrJxpmKpFI8M8//3zwMVJSUmBgYCAs37hxAy1btoSGRm7+Vb16dezevfvDT4KIiOgzpVZJRUBAACQSCSQSCbKzs/HkyRM8efJE2C6VSt97a/T3qVChgrDPV69e4eHDhxg8eLCwPTk5GVpaavW0ERERlYhSf3XMm367Vq1aAMTzVHyK26C3atUKO3bsQNmyZeHv7w8NDQ04OzsL2588eYJKlSop/bhERETqrtQnFUOHDoWGhga2bdsmjMD4lCZOnIjAwEAsXbpUWK5cuTIAICsrC6dOnULnzp0/eRxERETqptQnFcB/NRKWlpaf/Fjm5uY4cuQIwsLCYGRkBAsLC2FbWloafvnlF2EUChEREf1HLZKKkqapqQkHBwe59YaGhnBzc1NBRERERKWf2iQVISEhyM7OLlLZpk2bfvBxYmNji1Qufw0GERERqVFSMX/+/CKVk0gkuH///gcfp3379kUaQRISEvLBxyAiIvocqU1S8SlGeigyYcIEuaQiKysLUVFR8PPzQ82aNeHk5FQisRAREakTtUkqypcvDx0dnU9+nEmTJhW4LSIiAgMGDMD48eM/eRxERETqRm2SihUrVqBRo0YqjcHW1hYDBw7EqlWr4OLiotJYiIiIShu1uvdHaWBhYYFHjx6pOgwiIqJSh0lFMV27dk10bxAiIiLKVeqbP/KGburq6pbI8Q4dOqRwfUJCAq5evYoLFy5g4MCBJRILERGROin1ScWZM2dK9HheXl6QSCQKR5toaWmhf//+mDFjRonG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",
241
+ "text/plain": [
242
+ "<Figure size 600x500 with 1 Axes>"
243
+ ]
244
+ },
245
+ "metadata": {},
246
+ "output_type": "display_data"
247
+ }
248
+ ],
249
+ "source": [
250
+ "import os\n",
251
+ "import json\n",
252
+ "import torch\n",
253
+ "import numpy as np\n",
254
+ "import evaluate\n",
255
+ "import matplotlib.pyplot as plt\n",
256
+ "import seaborn as sns\n",
257
+ "from sklearn.metrics import confusion_matrix, accuracy_score\n",
258
+ "from transformers import (\n",
259
+ " AutoTokenizer, \n",
260
+ " AutoModelForSequenceClassification, \n",
261
+ " DataCollatorWithPadding, \n",
262
+ " Trainer, \n",
263
+ " TrainingArguments, \n",
264
+ " set_seed\n",
265
+ ")\n",
266
+ "from datasets import load_dataset\n",
267
+ "from tqdm import tqdm\n",
268
+ "from sklearn.metrics import classification_report\n",
269
+ "\n",
270
+ "# ==========================================\n",
271
+ "# 1. 全局配置与参数\n",
272
+ "# ==========================================\n",
273
+ "seed = 56 # 最佳种子\n",
274
+ "lang = \"en\" # 语言设置\n",
275
+ "model_checkpoint = \"gpt2\"\n",
276
+ "\n",
277
+ "# 路径设置\n",
278
+ "final_model_path = f\"./best_model_seed_{seed}\"\n",
279
+ "checkpoint_dir = f\"./checkpoints_seed_{seed}\"\n",
280
+ "\n",
281
+ "# 设置随机种子\n",
282
+ "set_seed(seed)\n",
283
+ "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
284
+ "\n",
285
+ "# ==========================================\n",
286
+ "# 2. 模型与分词器初始化\n",
287
+ "# ==========================================\n",
288
+ "print(f\"Initializing model: {model_checkpoint}...\")\n",
289
+ "tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)\n",
290
+ "tokenizer.pad_token = tokenizer.eos_token\n",
291
+ "\n",
292
+ "model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=2)\n",
293
+ "model.config.pad_token_id = model.config.eos_token_id\n",
294
+ "\n",
295
+ "# 定义分词函数\n",
296
+ "def tokenize_short_function(example):\n",
297
+ " return tokenizer(\n",
298
+ " example[\"sentence1\"],\n",
299
+ " example[\"sentence2\"],\n",
300
+ " truncation=True,\n",
301
+ " max_length=256, # Short 子集无截断\n",
302
+ " padding=\"max_length\"\n",
303
+ " )\n",
304
+ "\n",
305
+ "def tokenize_full_function(example):\n",
306
+ " return tokenizer(\n",
307
+ " example[\"sentence1\"],\n",
308
+ " example[\"sentence2\"],\n",
309
+ " truncation=True,\n",
310
+ " max_length=512, # Full 子集覆盖 97%\n",
311
+ " padding=\"max_length\"\n",
312
+ " )\n",
313
+ "\n",
314
+ "# ==========================================\n",
315
+ "# 3. 训练阶段 (PAWS-X)\n",
316
+ "# ==========================================\n",
317
+ "print(f\"Loading PAWS-X ({lang}) dataset...\")\n",
318
+ "raw_datasets = load_dataset('paws-x', lang)\n",
319
+ "tokenized_datasets = raw_datasets.map(tokenize_short_function, batched=True)\n",
320
+ "data_collator = DataCollatorWithPadding(tokenizer=tokenizer)\n",
321
+ "\n",
322
+ "def compute_metrics(eval_pred):\n",
323
+ " predictions, labels = eval_pred\n",
324
+ " predictions = np.argmax(predictions, axis=1)\n",
325
+ " return {'accuracy': (predictions == labels).sum() / len(labels)}\n",
326
+ "\n",
327
+ "training_args = TrainingArguments(\n",
328
+ " output_dir=checkpoint_dir,\n",
329
+ " learning_rate=1e-5,\n",
330
+ " lr_scheduler_type=\"constant_with_warmup\",\n",
331
+ " warmup_ratio=0.1,\n",
332
+ " optim='adamw_torch',\n",
333
+ " weight_decay=0.0,\n",
334
+ " seed=seed,\n",
335
+ " per_device_train_batch_size=64,\n",
336
+ " per_device_eval_batch_size=64,\n",
337
+ " num_train_epochs=4,\n",
338
+ " eval_strategy=\"epoch\", # 新版transformers使用eval_strategy\n",
339
+ " save_strategy=\"epoch\",\n",
340
+ " logging_strategy=\"epoch\",\n",
341
+ " load_best_model_at_end=True,\n",
342
+ " save_total_limit=1,\n",
343
+ " metric_for_best_model=\"accuracy\"\n",
344
+ ")\n",
345
+ "\n",
346
+ "trainer = Trainer(\n",
347
+ " model=model,\n",
348
+ " args=training_args,\n",
349
+ " train_dataset=tokenized_datasets[\"train\"],\n",
350
+ " eval_dataset=tokenized_datasets[\"validation\"],\n",
351
+ " data_collator=data_collator,\n",
352
+ " tokenizer=tokenizer,\n",
353
+ " compute_metrics=compute_metrics,\n",
354
+ ")\n",
355
+ "\n",
356
+ "# --- 开始训练 ---\n",
357
+ "print(f\"\\n>>> Starting training with Seed {seed}...\")\n",
358
+ "trainer.train()\n",
359
+ "\n",
360
+ "# --- 保存最佳模型 ---\n",
361
+ "print(f\"\\n>>> Saving best model to: {final_model_path} ...\")\n",
362
+ "trainer.save_model(final_model_path)\n",
363
+ "tokenizer.save_pretrained(final_model_path)\n",
364
+ "\n",
365
+ "# ==========================================\n",
366
+ "# 4. 推理与绘图函数\n",
367
+ "# ==========================================\n",
368
+ "\n",
369
+ "def run_inference_detailed(test_dataset, batch_size=64):\n",
370
+ " \"\"\"\n",
371
+ " 运行推理并返回原始的 (预测值列表, 真实标签列表)\n",
372
+ " \"\"\"\n",
373
+ " preds = []\n",
374
+ " labels = []\n",
375
+ " \n",
376
+ " # 确保模型在GPU上并处于评估模式\n",
377
+ " model.to(device)\n",
378
+ " model.eval()\n",
379
+ " \n",
380
+ " print(\"Executing inference...\")\n",
381
+ " # 禁用 tqdm 防止日志混乱\n",
382
+ " for i in tqdm(range(0, len(test_dataset), batch_size), desc=\"Predicting\", disable=True):\n",
383
+ " batch = test_dataset[i : i + batch_size]\n",
384
+ " \n",
385
+ " inputs = {\n",
386
+ " \"input_ids\": torch.tensor(batch[\"input_ids\"]).to(device),\n",
387
+ " \"attention_mask\": torch.tensor(batch[\"attention_mask\"]).to(device),\n",
388
+ " }\n",
389
+ " batch_labels = batch[\"label\"] \n",
390
+ "\n",
391
+ " with torch.no_grad():\n",
392
+ " outputs = model(**inputs)\n",
393
+ " # 获取 logits 的 argmax\n",
394
+ " batch_preds = torch.argmax(outputs.logits, axis=-1).cpu().numpy() \n",
395
+ "\n",
396
+ " preds.extend(batch_preds)\n",
397
+ " labels.extend(batch_labels)\n",
398
+ " \n",
399
+ " return np.array(preds), np.array(labels)\n",
400
+ "\n",
401
+ "def plot_and_save_confusion_matrix(preds, labels, dataset_name=\"Protein Short\"):\n",
402
+ " \"\"\"\n",
403
+ " 绘制混淆矩阵,并打印分类报告\n",
404
+ " \"\"\"\n",
405
+ " # 1. 计算准确率\n",
406
+ " acc = accuracy_score(labels, preds)\n",
407
+ " print(f\"[{dataset_name}] Raw Accuracy: {acc:.4f}\")\n",
408
+ " \n",
409
+ " # 2. 检查翻转\n",
410
+ " is_flipped = False\n",
411
+ " if acc < 0.5:\n",
412
+ " print(f\">>> Detected Label Inversion (Acc < 0.5). Rectifying...\")\n",
413
+ " preds = 1 - preds\n",
414
+ " acc = accuracy_score(labels, preds)\n",
415
+ " print(f\"[{dataset_name}] Rectified Accuracy: {acc:.4f}\")\n",
416
+ " is_flipped = True\n",
417
+ " \n",
418
+ " # ================= [新增] 打印详细分类报告 =================\n",
419
+ " print(f\"\\n>>> Classification Report for {dataset_name}:\")\n",
420
+ " # target_names 对应 0 和 1 的含义\n",
421
+ " report = classification_report(labels, preds, target_names=['Non-Homologous', 'Homologous'], digits=4)\n",
422
+ " print(report)\n",
423
+ " print(\"=\"*40)\n",
424
+ " # ========================================================\n",
425
+ "\n",
426
+ " # 3. 计算混淆矩阵\n",
427
+ " cm = confusion_matrix(labels, preds)\n",
428
+ " \n",
429
+ " # 4. 绘图\n",
430
+ " sns.set_theme(style=\"white\", font_scale=1.2)\n",
431
+ " plt.figure(figsize=(6, 5))\n",
432
+ " \n",
433
+ " class_names = ['Non-Homologous', 'Homologous']\n",
434
+ " \n",
435
+ " sns.heatmap(\n",
436
+ " cm, \n",
437
+ " annot=True, \n",
438
+ " fmt='d',\n",
439
+ " cmap='Blues',\n",
440
+ " cbar=False, \n",
441
+ " xticklabels=class_names,\n",
442
+ " yticklabels=class_names,\n",
443
+ " linewidths=1.5,\n",
444
+ " linecolor='black',\n",
445
+ " square=True\n",
446
+ " )\n",
447
+ " \n",
448
+ " plt.ylabel('True Label', fontsize=12, fontweight='bold')\n",
449
+ " plt.xlabel('Predicted Label', fontsize=12, fontweight='bold')\n",
450
+ " \n",
451
+ " plt.title(f'Confusion Matrix: Protein Homology Detection\\nAccuracy: {acc:.2%}', \n",
452
+ " fontsize=14, pad=15, fontweight='bold')\n",
453
+ " \n",
454
+ " plt.tight_layout()\n",
455
+ " \n",
456
+ " filename = f\"confusion_matrix_{dataset_name.replace(' ', '_')}_seed{seed}.png\"\n",
457
+ " plt.savefig(filename, dpi=300)\n",
458
+ " print(f\">>> Confusion Matrix saved to: {filename}\")\n",
459
+ " \n",
460
+ " return acc\n",
461
+ "\n",
462
+ "# ==========================================\n",
463
+ "# 5. 测试阶段 (BioPAWS)\n",
464
+ "# ==========================================\n",
465
+ "result = {\"seed\": seed, \"lang\": lang}\n",
466
+ "\n",
467
+ "# --- 测试集 1: Protein Pair Short (主要关注) ---\n",
468
+ "print(\"\\n>>> Loading BioPAWS (Short)...\")\n",
469
+ "raw_datasets_short = load_dataset('dnagpt/biopaws', 'protein_pair_short')['train'].train_test_split(test_size=0.3, seed=seed)\n",
470
+ "tokenized_short = raw_datasets_short.map(tokenize_short_function, batched=True, num_proc=4)\n",
471
+ "\n",
472
+ "print(\">>> Running inference on Protein Pair Short...\")\n",
473
+ "preds_short, labels_short = run_inference_detailed(tokenized_short[\"test\"])\n",
474
+ "\n",
475
+ "# 绘制混淆矩阵并获取准确率\n",
476
+ "acc_short = plot_and_save_confusion_matrix(preds_short, labels_short, dataset_name=\"Protein Pair Short\")\n",
477
+ "result[\"protein_pair_short\"] = {\"accuracy\": acc_short}\n",
478
+ "\n",
479
+ "\n",
480
+ "# --- 测试集 2: Protein Pair Full (可选,仅计算指标) ---\n",
481
+ "print(\"\\n>>> Loading BioPAWS (Full)...\")\n",
482
+ "raw_datasets_full = load_dataset('dnagpt/biopaws', 'protein_pair_full')['train'].train_test_split(test_size=0.3, seed=seed)\n",
483
+ "tokenized_full = raw_datasets_full.map(tokenize_full_function, batched=True, num_proc=4)\n",
484
+ "\n",
485
+ "print(\">>> Running inference on Protein Pair Full...\")\n",
486
+ "preds_full, labels_full = run_inference_detailed(tokenized_full[\"test\"])\n",
487
+ "\n",
488
+ "# 这里我们只计算翻转后的准确率,不画图了\n",
489
+ "acc_full = accuracy_score(labels_full, preds_full)\n",
490
+ "if acc_full < 0.5:\n",
491
+ " acc_full = 1 - acc_full\n",
492
+ "\n",
493
+ "result[\"protein_pair_full\"] = {\"accuracy\": acc_full}\n",
494
+ "\n",
495
+ "# ==========================================\n",
496
+ "# 6. 最终结果输出\n",
497
+ "# ==========================================\n",
498
+ "print(\"\\n\" + \"=\"*30)\n",
499
+ "print(\"Final Results:\")\n",
500
+ "print(json.dumps(result, indent=2))\n",
501
+ "print(\"=\"*30)"
502
+ ]
503
+ },
504
+ {
505
+ "cell_type": "code",
506
+ "execution_count": null,
507
+ "id": "d330ffb3-6bcd-419a-b916-98fd12a8b922",
508
+ "metadata": {},
509
+ "outputs": [],
510
+ "source": []
511
+ }
512
+ ],
513
+ "metadata": {
514
+ "kernelspec": {
515
+ "display_name": "Python 3 (ipykernel)",
516
+ "language": "python",
517
+ "name": "python3"
518
+ },
519
+ "language_info": {
520
+ "codemirror_mode": {
521
+ "name": "ipython",
522
+ "version": 3
523
+ },
524
+ "file_extension": ".py",
525
+ "mimetype": "text/x-python",
526
+ "name": "python",
527
+ "nbconvert_exporter": "python",
528
+ "pygments_lexer": "ipython3",
529
+ "version": "3.12.3"
530
+ }
531
+ },
532
+ "nbformat": 4,
533
+ "nbformat_minor": 5
534
+ }
2-gpt_ft_test_explain/.ipynb_checkpoints/2-gpt2_test_protein-checkpoint.ipynb ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "f050d46f-b696-4175-b337-9e20363d780d",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "# import os\n",
11
+ "\n",
12
+ "# # 设置环境变量\n",
13
+ "# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'\n",
14
+ "\n",
15
+ "# # 打印环境变量以确认设置成功\n",
16
+ "# print(os.environ.get('HF_ENDPOINT'))\n",
17
+ "\n",
18
+ "import subprocess\n",
19
+ "import os\n",
20
+ "\n",
21
+ "result = subprocess.run('bash -c \"source /etc/network_turbo && env | grep proxy\"', shell=True, capture_output=True, text=True)\n",
22
+ "output = result.stdout\n",
23
+ "for line in output.splitlines():\n",
24
+ " if '=' in line:\n",
25
+ " var, value = line.split('=', 1)\n",
26
+ " os.environ[var] = value"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": 2,
32
+ "id": "572acc50-6138-4c50-aa11-85c949a34a86",
33
+ "metadata": {},
34
+ "outputs": [],
35
+ "source": [
36
+ "import os\n",
37
+ "import sys\n",
38
+ "import json\n",
39
+ "import torch\n",
40
+ "import numpy as np\n",
41
+ "import evaluate\n",
42
+ "from transformers import AutoTokenizer, AutoModelForSequenceClassification, set_seed\n",
43
+ "from datasets import load_dataset\n",
44
+ "from tqdm import tqdm"
45
+ ]
46
+ },
47
+ {
48
+ "cell_type": "code",
49
+ "execution_count": 3,
50
+ "id": "af862809-f2df-4a8f-be23-741ad5ceb1ae",
51
+ "metadata": {},
52
+ "outputs": [],
53
+ "source": [
54
+ "seed = 42\n",
55
+ "lang = \"en\"\n",
56
+ "# 设置随机种子\n",
57
+ "set_seed(seed)\n",
58
+ "\n",
59
+ "result = {}\n",
60
+ "result[\"seed\"] = seed\n",
61
+ "result[\"type\"] = \"no_finetune_baseline\""
62
+ ]
63
+ },
64
+ {
65
+ "cell_type": "code",
66
+ "execution_count": null,
67
+ "id": "d04e80cc-9157-4b43-aaa1-f2e4691c0e4f",
68
+ "metadata": {},
69
+ "outputs": [],
70
+ "source": [
71
+ "# 初始化模型和分词器\n",
72
+ "model_checkpoint = \"gpt2\"\n",
73
+ "tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)\n",
74
+ "tokenizer.pad_token = tokenizer.eos_token\n",
75
+ "\n",
76
+ "# 加载模型 (预训练权重 + 随机分类头)\n",
77
+ "model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=2)\n",
78
+ "model.config.pad_token_id = model.config.eos_token_id\n",
79
+ "\n",
80
+ "# 移动到 GPU\n",
81
+ "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
82
+ "model.to(device)\n",
83
+ "model.eval()"
84
+ ]
85
+ },
86
+ {
87
+ "cell_type": "code",
88
+ "execution_count": null,
89
+ "id": "8b7dd096-85aa-401d-b59b-fb52e0165508",
90
+ "metadata": {},
91
+ "outputs": [],
92
+ "source": [
93
+ "# 定义两个专用的分词函数\n",
94
+ "def tokenize_short_function(example):\n",
95
+ " return tokenizer(\n",
96
+ " example[\"sentence1\"],\n",
97
+ " example[\"sentence2\"],\n",
98
+ " truncation=True,\n",
99
+ " max_length=256, # short 子集:完全无截断\n",
100
+ " padding=\"max_length\"\n",
101
+ " )\n",
102
+ "\n",
103
+ "def tokenize_full_function(example):\n",
104
+ " return tokenizer(\n",
105
+ " example[\"sentence1\"],\n",
106
+ " example[\"sentence2\"],\n",
107
+ " truncation=True,\n",
108
+ " max_length=512, # full 子集:覆盖 ~97%,最佳平衡\n",
109
+ " padding=\"max_length\"\n",
110
+ " )\n",
111
+ "\n",
112
+ "def plot_and_save_confusion_matrix(preds, labels, dataset_name=\"Protein Short\"):\n",
113
+ " \"\"\"\n",
114
+ " 绘制混淆矩阵,并打印分类报告\n",
115
+ " \"\"\"\n",
116
+ " # 1. 计算准确率\n",
117
+ " acc = accuracy_score(labels, preds)\n",
118
+ " print(f\"[{dataset_name}] Raw Accuracy: {acc:.4f}\")\n",
119
+ " \n",
120
+ " # 2. 检查翻转\n",
121
+ " is_flipped = False\n",
122
+ " if acc < 0.5:\n",
123
+ " print(f\">>> Detected Label Inversion (Acc < 0.5). Rectifying...\")\n",
124
+ " preds = 1 - preds\n",
125
+ " acc = accuracy_score(labels, preds)\n",
126
+ " print(f\"[{dataset_name}] Rectified Accuracy: {acc:.4f}\")\n",
127
+ " is_flipped = True\n",
128
+ " \n",
129
+ " # ================= [新增] 打印详细分类报告 =================\n",
130
+ " print(f\"\\n>>> Classification Report for {dataset_name}:\")\n",
131
+ " # target_names 对应 0 和 1 的含义\n",
132
+ " report = classification_report(labels, preds, target_names=['Non-Homologous', 'Homologous'], digits=4)\n",
133
+ " print(report)\n",
134
+ " print(\"=\"*40)\n",
135
+ " # ========================================================\n",
136
+ "\n",
137
+ " # 3. 计算混淆矩阵\n",
138
+ " cm = confusion_matrix(labels, preds)\n",
139
+ " \n",
140
+ " # 4. 绘图\n",
141
+ " sns.set_theme(style=\"white\", font_scale=1.2)\n",
142
+ " plt.figure(figsize=(6, 5))\n",
143
+ " \n",
144
+ " class_names = ['Non-Homologous', 'Homologous']\n",
145
+ " \n",
146
+ " sns.heatmap(\n",
147
+ " cm, \n",
148
+ " annot=True, \n",
149
+ " fmt='d',\n",
150
+ " cmap='Blues',\n",
151
+ " cbar=False, \n",
152
+ " xticklabels=class_names,\n",
153
+ " yticklabels=class_names,\n",
154
+ " linewidths=1.5,\n",
155
+ " linecolor='black',\n",
156
+ " square=True\n",
157
+ " )\n",
158
+ " \n",
159
+ " plt.ylabel('True Label', fontsize=12, fontweight='bold')\n",
160
+ " plt.xlabel('Predicted Label', fontsize=12, fontweight='bold')\n",
161
+ " \n",
162
+ " plt.title(f'Confusion Matrix: Protein Homology Detection\\nAccuracy: {acc:.2%}', \n",
163
+ " fontsize=14, pad=15, fontweight='bold')\n",
164
+ " \n",
165
+ " plt.tight_layout()\n",
166
+ " \n",
167
+ " filename = f\"confusion_matrix_{dataset_name.replace(' ', '_')}_seed{seed}.png\"\n",
168
+ " plt.savefig(filename, dpi=300)\n",
169
+ " print(f\">>> Confusion Matrix saved to: {filename}\")\n",
170
+ " \n",
171
+ " return acc\n",
172
+ "\n",
173
+ "# 定义推理函数\n",
174
+ "def run_inference(test_dataset, batch_size=64):\n",
175
+ " preds = []\n",
176
+ " labels = []\n",
177
+ " \n",
178
+ " # disable=True 禁用进度条以保持输出纯净\n",
179
+ " for i in tqdm(range(0, len(test_dataset), batch_size), desc=\"Predicting\", disable=True):\n",
180
+ " batch = test_dataset[i : i + batch_size]\n",
181
+ " \n",
182
+ " inputs = {\n",
183
+ " \"input_ids\": torch.tensor(batch[\"input_ids\"]).to(device),\n",
184
+ " \"attention_mask\": torch.tensor(batch[\"attention_mask\"]).to(device),\n",
185
+ " }\n",
186
+ " batch_labels = batch[\"label\"] \n",
187
+ "\n",
188
+ " with torch.no_grad():\n",
189
+ " outputs = model(**inputs)\n",
190
+ " batch_preds = torch.argmax(outputs.logits, axis=-1).cpu().numpy() \n",
191
+ "\n",
192
+ " preds.extend(batch_preds)\n",
193
+ " labels.extend(batch_labels)\n",
194
+ " \n",
195
+ " metric = evaluate.load(\"glue\", \"mrpc\")\n",
196
+ " plot_and_save_confusion_matrix(preds, labels)\n",
197
+ " return metric.compute(predictions=preds, references=labels)"
198
+ ]
199
+ },
200
+ {
201
+ "cell_type": "code",
202
+ "execution_count": null,
203
+ "id": "d75bb781-321f-4642-ae1e-e7a0d4c7406b",
204
+ "metadata": {},
205
+ "outputs": [],
206
+ "source": [
207
+ "# ==========================================\n",
208
+ "# 测试集 1: protein_pair_short\n",
209
+ "# ==========================================\n",
210
+ "raw_datasets_short = load_dataset('dnagpt/biopaws', 'protein_pair_short')['train'].train_test_split(test_size=0.3, seed=seed)\n",
211
+ "\n",
212
+ "# 直接分词\n",
213
+ "tokenized_raw_datasets_short = raw_datasets_short.map(tokenize_short_function, batched=True, num_proc=4)\n",
214
+ "ret_1 = run_inference(tokenized_raw_datasets_short[\"test\"])\n",
215
+ "result[\"protein_pair_short\"] = ret_1\n"
216
+ ]
217
+ },
218
+ {
219
+ "cell_type": "code",
220
+ "execution_count": null,
221
+ "id": "ca8ce39e-12b3-440a-9c86-b6213a431079",
222
+ "metadata": {},
223
+ "outputs": [],
224
+ "source": [
225
+ "# ==========================================\n",
226
+ "# 测试集 2: protein_pair_full (\n",
227
+ "# ==========================================\n",
228
+ "raw_datasets_full = load_dataset('dnagpt/biopaws', 'protein_pair_full')['train'].train_test_split(test_size=0.3, seed=seed)\n",
229
+ "\n",
230
+ "# 直接分词 (去除了 flip_labels 以保持与基线脚本一致)\n",
231
+ "tokenized_raw_datasets_full = raw_datasets_full.map(tokenize_full_function, batched=True, num_proc=4)\n",
232
+ "ret_2 = run_inference(tokenized_raw_datasets_full[\"test\"])\n",
233
+ "result[\"protein_pair_full\"] = ret_2"
234
+ ]
235
+ },
236
+ {
237
+ "cell_type": "code",
238
+ "execution_count": null,
239
+ "id": "541c63c8-d06f-4426-9476-458a48ee5748",
240
+ "metadata": {},
241
+ "outputs": [],
242
+ "source": [
243
+ "# ==========================================\n",
244
+ "# 输出结果\n",
245
+ "# ==========================================\n",
246
+ "print(json.dumps(result))"
247
+ ]
248
+ },
249
+ {
250
+ "cell_type": "code",
251
+ "execution_count": null,
252
+ "id": "59826e1a-722c-4690-a343-927e6dbfebfd",
253
+ "metadata": {},
254
+ "outputs": [],
255
+ "source": []
256
+ },
257
+ {
258
+ "cell_type": "code",
259
+ "execution_count": null,
260
+ "id": "de0d00a8-d15e-4d24-81a7-22ad564eeaaf",
261
+ "metadata": {},
262
+ "outputs": [],
263
+ "source": []
264
+ }
265
+ ],
266
+ "metadata": {
267
+ "kernelspec": {
268
+ "display_name": "Python 3 (ipykernel)",
269
+ "language": "python",
270
+ "name": "python3"
271
+ },
272
+ "language_info": {
273
+ "codemirror_mode": {
274
+ "name": "ipython",
275
+ "version": 3
276
+ },
277
+ "file_extension": ".py",
278
+ "mimetype": "text/x-python",
279
+ "name": "python",
280
+ "nbconvert_exporter": "python",
281
+ "pygments_lexer": "ipython3",
282
+ "version": "3.12.3"
283
+ }
284
+ },
285
+ "nbformat": 4,
286
+ "nbformat_minor": 5
287
+ }
2-gpt_ft_test_explain/.ipynb_checkpoints/3-acc distribution-checkpoint.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
2-gpt_ft_test_explain/.ipynb_checkpoints/4-explain_layer_wise-checkpoint.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
2-gpt_ft_test_explain/.ipynb_checkpoints/4-explain_mutation awareness-checkpoint.ipynb ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "347af636-8135-484c-8a1a-3bad7e2ba82e",
7
+ "metadata": {},
8
+ "outputs": [
9
+ {
10
+ "name": "stdout",
11
+ "output_type": "stream",
12
+ "text": [
13
+ ">>> Loading model from ./best_model_seed_56...\n",
14
+ ">>> Running inference to get attention maps...\n",
15
+ "✅ Attention maps extracted successfully!\n",
16
+ "Seq Length: 12, Split at: 6\n",
17
+ "\n",
18
+ ">>> 🏆 Top Difference Detector Candidate: Layer 5, Head 9\n",
19
+ "Top 5 Candidates: [(5, 9, 1.2959465980529785), (5, 11, 1.2529085874557495), (4, 7, 1.064322590827942), (6, 2, 1.0418472290039062), (4, 10, 0.9984771609306335)]\n"
20
+ ]
21
+ }
22
+ ],
23
+ "source": [
24
+ "import torch\n",
25
+ "import numpy as np\n",
26
+ "import matplotlib.pyplot as plt\n",
27
+ "import seaborn as sns\n",
28
+ "from transformers import AutoTokenizer, AutoModelForSequenceClassification\n",
29
+ "\n",
30
+ "# ================= 配置 =================\n",
31
+ "MODEL_PATH = \"./best_model_seed_56\" # 确保路径正确\n",
32
+ "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
33
+ "\n",
34
+ "# 1. 加载模型(关键修改点)\n",
35
+ "print(f\">>> Loading model from {MODEL_PATH}...\")\n",
36
+ "tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)\n",
37
+ "\n",
38
+ "# 修改点:在加载时直接指定 output_attentions=True\n",
39
+ "model = AutoModelForSequenceClassification.from_pretrained(\n",
40
+ " MODEL_PATH, \n",
41
+ " num_labels=2, \n",
42
+ " output_attentions=True \n",
43
+ ")\n",
44
+ "\n",
45
+ "# 双重保险:强制修改 config\n",
46
+ "model.config.output_attentions = True \n",
47
+ "\n",
48
+ "model.to(device)\n",
49
+ "model.eval()\n",
50
+ "\n",
51
+ "# 2. 定义“找茬”探针输入\n",
52
+ "text_en_1 = \"The cat sat on the mat\"\n",
53
+ "text_en_2 = \"The mat sat on the cat\"\n",
54
+ "\n",
55
+ "# 示例序列 (假设之前的 demo 序列)\n",
56
+ "seq_bio_1 = \"MKLVINGFGRIGRLVTRAAF\" \n",
57
+ "seq_bio_2 = \"MKLVINLFGRIGRLVTRAAF\" \n",
58
+ "\n",
59
+ "def get_attention_maps(text1, text2):\n",
60
+ " # GPT-2 没有 token_type_ids,所以我们只取 input_ids 和 attention_mask\n",
61
+ " inputs = tokenizer(text1, text2, return_tensors='pt')\n",
62
+ " inputs = {k: v.to(device) for k, v in inputs.items()}\n",
63
+ " \n",
64
+ " with torch.no_grad():\n",
65
+ " # 这里虽然 config 改了,但为了保险再传一次参数\n",
66
+ " outputs = model(**inputs, output_attentions=True)\n",
67
+ " \n",
68
+ " # 调试检查:如果这里报错,说明模型真的没吐出 attention\n",
69
+ " if outputs.attentions is None:\n",
70
+ " raise ValueError(\"Model did not return attentions! Check config.\")\n",
71
+ " \n",
72
+ " # outputs.attentions 是一个 tuple,包含每一层的 attention tensor\n",
73
+ " # 形状: (batch, num_heads, seq_len, seq_len)\n",
74
+ " # stack 后形状: (num_layers, batch, num_heads, seq_len, seq_len)\n",
75
+ " # squeeze(1) 去掉 batch 维度 -> (num_layers, num_heads, seq_len, seq_len)\n",
76
+ " attentions = torch.stack(outputs.attentions).squeeze(1).cpu() \n",
77
+ " \n",
78
+ " return attentions, inputs['input_ids'][0]\n",
79
+ "\n",
80
+ "# 3. 获取注意力权重 (现在应该不会报错了)\n",
81
+ "print(\">>> Running inference to get attention maps...\")\n",
82
+ "attns_en, ids_en = get_attention_maps(text_en_1, text_en_2)\n",
83
+ "tokens_en = tokenizer.convert_ids_to_tokens(ids_en)\n",
84
+ "\n",
85
+ "print(\"✅ Attention maps extracted successfully!\")\n",
86
+ "\n",
87
+ "# 4. 自动筛选“找茬 Head”\n",
88
+ "# 找到 Sent1 和 Sent2 的分界线\n",
89
+ "# 注意:GPT-2 Tokenizer 对空格敏感,单独 encode text1 可能会少一个前导空格,导致长度差1\n",
90
+ "# 最稳妥的方法是看 inputs_ids\n",
91
+ "len_1 = len(tokenizer(text_en_1)['input_ids'])\n",
92
+ "len_total = len(ids_en)\n",
93
+ "\n",
94
+ "print(f\"Seq Length: {len_total}, Split at: {len_1}\")\n",
95
+ "\n",
96
+ "scores = []\n",
97
+ "\n",
98
+ "# 遍历中间层 (Layer 4-9)\n",
99
+ "for layer in range(4, 10): \n",
100
+ " for head in range(12):\n",
101
+ " attn_mat = attns_en[layer, head]\n",
102
+ " \n",
103
+ " # 提取 Cross Attention 区域: Sent2 关注 Sent1\n",
104
+ " cross_attn = attn_mat[len_1:, :len_1]\n",
105
+ " \n",
106
+ " # 计算对角线强度\n",
107
+ " min_len = min(cross_attn.shape)\n",
108
+ " if min_len > 0:\n",
109
+ " diag_score = cross_attn.diagonal(offset=0)[:min_len].sum().item()\n",
110
+ " scores.append((layer, head, diag_score))\n",
111
+ "\n",
112
+ "# 排序\n",
113
+ "scores.sort(key=lambda x: x[2], reverse=True)\n",
114
+ "if len(scores) > 0:\n",
115
+ " top_layer, top_head, top_score = scores[0]\n",
116
+ " print(f\"\\n>>> 🏆 Top Difference Detector Candidate: Layer {top_layer}, Head {top_head}\")\n",
117
+ " print(f\"Top 5 Candidates: {scores[:5]}\")\n",
118
+ " \n",
119
+ " # ================= 绘图代码准备 =================\n",
120
+ " # 把刚才跑出来的 top_layer 和 top_head 传给变量,方便下一个单元格绘图\n",
121
+ " TARGET_LAYER = top_layer\n",
122
+ " TARGET_HEAD = top_head\n",
123
+ "else:\n",
124
+ " print(\"❌ No valid attention scores found. Check sequence lengths.\")"
125
+ ]
126
+ },
127
+ {
128
+ "cell_type": "code",
129
+ "execution_count": 2,
130
+ "id": "db76380f-3d15-4d12-b267-2265a0a5ee23",
131
+ "metadata": {},
132
+ "outputs": [
133
+ {
134
+ "name": "stdout",
135
+ "output_type": "stream",
136
+ "text": [
137
+ ">>> Visualizing Layer 5, Head 9...\n"
138
+ ]
139
+ },
140
+ {
141
+ "data": {
142
+ "image/png": "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XhUqVEjDhw/XjRs3NGvWLMXGxsowDE2ZMkVt2rRJU3up8dRTT2nTpk26ePGipk+friFDhmjOnDm6evWqpDv38s0330y386XlXnt4eKhly5aqXbu2PD095e7uroiICK1atUoHDhxQTEyMXnrpJe3fv1+SNGXKFB05ckQzZ8402xo7dqwKFSokSamafz+t352kbNq0SQ899JD8/f21YcMGc6TL4cOH9f3336tXr16pvl/z5s0zXz/yyCN6+OGHVb16dR04cEDSnREjnTt3NuvYbDa1bNlSy5cvl3Tnb6RPnz6Kjo7Wf/7zH7Perl27dO3aNeXNm1cbN240y5s3b27+HTk4OKh69epq1KiRvL29VbBgQd28eVO7du3SypUrZRiG5syZoxdeeEGNGjVKMv7k/kaHDx+uX3/9VZLk4uKiXr16qXLlygoNDdWyZctkGIamTp2qBg0aqE+fPsne5zZt2qhp06b6/vvvzZEjf/zxh7Zu3aomTZpIkhYvXqx3333XPM7f318PP/ywzp07p3nz5unatWvauHGjhg8fri+++MK879evX5cklSpVSn379pWHh4dOnjypffv2aevWrWZ7165d04IFC8z3jz/+uOrXr6/IyEgdO3bMblQMgGzA0pQEAAAAAAA51N09fnv27GlMmTLFeOedd4zOnTvb7fPy8jJ7V999XJMmTRL1vL548aLh6Oho1unRo4fd/h49epj7HB0djYsXL5r7kurZntCePXvszv/bb7/Z7X/kkUfMfd26dTPLUztSwGazGX/++ae575VXXjH3eXp62h2XXiMFVq5cadeLes2aNUalSpUMSUbRokWNmzdvputIgdTsSyg2NtbYtm2bMXfuXGPatGnGlClTjMDAQLvzHj9+PNnrS+q+JFfnfr87d9+HRo0aGTExMYZhGEZMTIxRrFixZHu/p2Tbtm127S5btswwDMOYNGmSWebi4mL3HTYM+5E2NWvWNAzDMP744w9DkuHq6mp4eHgYkoy1a9cahmHYjXz58MMPE8Vx7Ngx49tvvzWmT59ufPDBB8aUKVOMkiVLmsdMmjQp2Xub1N9oRESE4eTkZNaZPXu23f4hQ4aY++rVq5ds2926dTPi4uLMNhN+dp988ol5XL169czyfv362Z1r6dKl5j4nJycjIiLCMAzDeOmll8zyoKCgRPfk0qVLxqVLl8zX8XXz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vXo1xeuSZLRt29a4ffu2XXujR49Osm6jRo0MLy+vJM9lGIbx2WefGU5OTsmeq0aNGsaZM2fsjpkzZ06K15aclD7nu/c1a9YsyXhatGhhHvPPP//Y7QsJCbE737hx48x9VapUMcuvX79utG3bNsV7HBgYmGLszZs3t3v/2GOPGYZhGH5+fim227NnT7PNsLAwu30bNmww992+fdvo0aNHim1Vr17dOH36tF2cCfc//PDDhs1mS3Rc4cKFjfPnzz/wZxZv+/btdnXat29vtz8t37G7z5fUFh9DbGys8fTTT6dY98knn0z0t7Jq1SojX758yR7z8ssvG4Zh/zea1NayZcts8zkCAAAAAIDsi+mDgP8qVqyYOnfurIoVK8rT01OOjo46deqUlixZooiICJ06dUpvv/22PvvssySP3759u0qVKqWnnnpKJ06c0DfffCNJio6O1scff6yZM2dKutOjePjw4eZxHh4eeuaZZ+Tg4KCvvvpKV65cSdfrstlsqlChgpo0aaKSJUuqUKFCunXrlg4ePKhly5bp9u3b+vXXX/Xdd9+pR48ekqT//Oc/ev/99+3uTf/+/XX16lXNnj1bMTExSZ4rJCREQ4cOVVxcnCSpSZMm6tChg65evap58+bp4sWL2r9/v/r166dffvklXa/zXjZt2qQ2bdqoadOm+v777xUaGipJ+uOPP7R161Y1adJEFStWVIsWLfTHH39Ikr755hv5+vqabSxatMh8HRAQYL4ePny4fv31V0mSi4uLevXqpcqVKys0NFTLli2TYRiaOnWqGjRooD59+iQZ38aNG1WzZk117txZhmHI0dFRBw4cMHuJOzg4qF+/fqpSpYouXryosLCwNC1S++6772rp0qXm+yZNmqh9+/Y6cOCAli1bJkk6cOCAnnrqKf32229JtrF582ZVq1ZN3bt31+7du80REBEREfrqq680evToVMeTkoceekh169bVnj17JN35jGJjY+Xo6Jjm71jFihU1ZcoU/fLLL2aP/UKFCmns2LF255OkyZMna/78+ZLu/N08/vjjqlu3rsLCwjR//nzdunVLy5Ytk4+Pj3n8sWPH9OSTT5q98202m7p06SIfHx9duHDB7l6+/vrrCg8P17vvvmuWvfDCC+aaAaVLl77nvclOnyMAAAAAAMiirM5KABnhfkYKGIZhREVFGb/++qvxxRdf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L774otzd3RUYGKjXX39dkrR06VIdPXpU7dq1U758+XT27Fnt2LFD27ZtU7NmzdStWzdJ0ksvvaTPP//cHLnTp08fLVmyRD4+Pvr3338VHBwsPz8/TZs2TdKddUScnZ3NeUlff/117dmzR87OzvLz8zMTP0nJyp8jAADAvfzyyy/m6/TqvPD+++8nmp4zMjJS//nPf/Sf//xHS5YsUUhIiPLmzathw4aZSYEVK1bo33//tRtZmfDfUr179zYTAocPH1b79u0Tdd46ceKE5s2bp8WLF2v+/Pl68sknk4wxKipKzZo108GDB+/7Ovv376/z58/LZrOpTJky8vf316uvvmp2PMkKfvjhB/Xp0ydRp5cDBw7owIEDWrBggdatW6fq1aub+3766SfNnj07UVtHjhzRkSNH9M0332jVqlVq27atuS88PFy+vr52I+w3btyo9u3bq2PHjmmO++jRo+ZrDw8PuxEZderUMV/funVL69atU9++fe2Oj08KSHcSCiQFAGQXJAWQK4SHhyfZAz9+GpyXX35Z8+bNMx+4L168WNKd+fOfeuopLVy4MN1iKVmypD766CMNHjxY0p1eJx999JGkO71ISpYsqQMHDkiynzLIxcVFL7/8srmYVHR0tFasWCHpTs/+48ePJzkd0euvv67evXtLutP7Of7hZPHixdWuXTszGZLQQw89pFGjRpnzIp45c0bvv/++JKl+/fo6deqUOVVJwhibNWum6dOn6+WXX9bt27d14sQJffzxx/d5p6zVr18/vfHGG+YIAOnOtSf1oHXatGkKCwvTr7/+Kkn67bffzAWK08Nff/2lv/76K8l9jRo1UsuWLe/ZxpgxY7R3715zuqitW7cmSnhVr15dCxYsePCA02DJkiV2U1zFc3Jy0oQJEzR27Fi78vv9jnXo0EF58uQxf6QkXDxtwIABcnd31+jRo3Xw4EHNnz9f0p1RMgkX3U5K2bJl9e2336pXr166evWqDMPQ999/b45mkGTXq8vFxUWPPvqo+be7e/du7d69W9Kdnm4pJQWkrPs5AgAApOT48eM6fvy4+f5e/+ZJrVKlSqlVq1YqW7asChUqJMMwFBYWpiVLligqKkqhoaH67LPPNHLkSHXt2lWlSpXSyZMndfPmTc2fP18vvfSSpDsjUDdt2mS2G98RKDY2Vt26dTN/SxYtWlR9+vSRp6en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ABKIH51sJZvNJm9vb3Xu3FnvvvuuHByyf39JR0dHRUZGWh1GrvHkk09m6JSoAJDRsv9/+QA8sDlz5pivH3/88XRpc8qUKeratauqVKkiT09POTs7q2DBgmrUqJHeeecdu97dLVu2NKe+SWqBrhkzZpj7PT09dfPmTXPflStXFBQUpMaNG6tAgQJycXFRmTJlNGDAAHO+0oTefPNNs61y5copIiJCL774okqVKiVHR0d99dVXabrOnj17asSIEYm2Jk2apKmdjHb06FG99NJLql69ujw8POTu7q4aNWpo9OjRunjxYqL6wcHBGjRokOrXr6/ixYvL1dVVefLkUaVKlRQQEGAmke527Ngx9e7dW56envLw8FCLFi3066+/3lfMixYtsvus161bp0mTJmnGjBl2UyTNmjVL586dszu2ZMmS8vX1lXRnmPjy5cvvKwYAAADkHH5+fjIMQ3FxcTp9+rT+7//+T4ULF7Y6LAAAMh0jBQDol19+MV83bdo0Xdp8//33FRERYVcWGRmp//znP/rPf/6jJUuWKCQkRHnz5tWwYcP0xx9/SLqztsG///6rQoUKmcctXbrUfN27d2+5ublJkg4fPqz27dsrPDzc7jwnTpzQvHnztHjxYs2fP19PPvlkkjFGRUWpWbNmOnjw4H1fZ//+/XX+/HnZbDaVKVNG/v7+evXVV1WmTJn7bjO9/fDDD+rTp4+uX79uV37gwAEdOHBACxYs0Lp161S9enVz308//aTZs2cnauvIkSM6cuSIvvnmG61atUpt27Y194WHh8vX11dnz541yzZu3Kj27durY8eOaY47fki3JHl4eNiNyKhTp475+tatW1q3bp369u1rd7yvr69CQkIk3UkoPPXUU2mOAQAAAAAAIKchKQDkcsePH9fx48fN9w0bNkyXdkuVKqVWrVqpbNmyKlSokAzDUFhYmJYsWaKoqCiFhobqs88+08iRI9W1a1eVKlVKJ0+e1M2bNzV//ny99NJLkqSzZ8+ac2NKUkBAgKQ7c2Z269bNTAgULVpUffr0kaenp9auXauQkBBFR0erX79+atCggSpUqJAoxosXL+rixYtq27atHn74YV24cEFeXl5pus6E9+7QoUM6dOiQ5s2bp59//jndEiwJrVmzJlHP/n///TfZ+mFhYerdu7du3LghSapZs6a6deumuLg4LVy4UMeOHdOpU6f0+OOPKzQ0VI6OjpLuPIRv2bKlateuLU9PT7m7uysiIkKrVq3SgQMHFBMTo5deekn79+83zzV06FC7hEDnzp1Vr149rV69Wj///HOar7VAgQLm66ioKIWFhal8+fKSlGikwr59+xId/9BDD5mvN27cmObzAwAAAAAA5EQkBYBcLn5xJElycXFJ80Px5OzevVuRkZEKCQnR8ePHFRUVperVq6tBgwbmqIC1a9dq5MiRcnJy0uDBg/X6669Lkr788kszKfDtt98qLi5OklS7dm0zabFq1SpzeiBHR0dt3rxZlStXliS9/vrrqlevnkJDQ3Xz5k1Nnz5dU6dOTTLOV155RR999FGar69w4cJq166dKlasqLi4OP3yyy/auXOnpDsjInr27KnDhw+boxrSy5IlS7RkyZJU1//000/NhECVKlW0Y8cOM6ahQ4eqdOnSio2N1YEDB7Rq1Sp16dJFkjRx4kTFxcVpx44dOnDggC5fviwvLy917NhRBw4ckHRnpMGJEydUunRpnTlzxu7Bf9++fTV//nxJ0htvvKF69eolOZ1TSjp37qwpU6aY79u1a6c+ffro33//TTTNU1KJkVKlSpmvw8PDFRcXlyPmiwVyqxds+S0790zjimXnRs7j/ey3VoeQ4f7+tLvVIWSo2LicP4/41Zu3rQ4hw52PjLY6hAxXoZiH1SFkqJu3Yq0OIcPdis35/39z5cYtq0PIcH+eTb4jX07Qv2Fpq0NIknu9oZad+8au6ZadO7VICgC53IULF8zXCafseRBxcXEaPXq0Pv74Y3OR2KScPHnSfP3ss89q0qRJio6OVmhoqLZt26bGjRtr2bJlZp34UQKStHnzZvN1bGysqlSpkux54qeQScobb7xxz+u527hx4zRr1iw5OzubZe+8846efvppLVy4UNKda/v111/16KOPprn99JTwPv39998pLrYbEhJiJgXWrVunZ555xm4kRFJOnjyp0qVLa+fOnXYLbSWcqsfZ2Vk9evTQhAkT0hR78+bNNWTIEH322WeS7iSw3nrrrSTruri4JCpLOD9sXFycIiIiVLRo0TTFAAAAAAAAkNPQZRJAuvvkk080ZcqUFBMCkhQd/b/eOUWLFlXv3r3N919++aXOnDljTh3k7OxsN2f8pUuXUh1PwsRHQkWKFLmvhcUqVqxolxCQJJvNpmHDhtmV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hnJwcFDZsmXVrl075c+fX0FBQerUqVNaQwQAAEBOk02G4cI6hmGoTZs2+uuvv1S5cuUHaiupod/ZZSg4AAAA+PlwL2lKCty6dUvh4eEqUqRIivVsNpvKli2bqjajoqJUrFgxSVKhQoV04cIFValSRbVr19aff/6ZlvAAAAAA5FIODg6qXLmyIiIiHjgpYBiGBgwYYPbyv3nzpl544QV5eHjY1Vu+fPkDnQcAAACwQpqSAs7Ozum++G/VqlV16NAhlStXTnXr1tX//d//qVy5cpo5c6aKFy+erucCAABANsWCv0iF9957T6+99po+//xz1apV677buXt9tL59+z5oaAAAAMhEDgwVSFGapw/q27evvvrqK7333nvpEsDLL7+sM2fOSJImTJigDh06aOHChXJxcdHcuXPveXx0dLSio6Ptypxux8rVyTFd4gMAAACQPfTr10/Xr19X3bp15eLikmhtgUuXLqWqnTlz5mREeAAAAECWkOakwO3btzV79mz9+uuvatCgQaIhtFOnTk1Tewl73TRo0EDHjh3TwYMHVaZMmXtOUyRJQUFBmjhxol3ZuHYPaUL7RmmKAwAAAED2Nm3aNKtDAAAAALK8NCcF9u3bp/r160uS/v77b7t96bH4lqurqxwcHOTomLqe/mPGjFFgYKBdmdNnox44DgAAAGQdLPKK1Lh72h8AAADkTvx8SFmakwIbNmxI1wBeeeUV1a5dW4MGDVJsbKxatGihLVu2KE+ePPrpp5/k5+eX4vGurq7mAmDxYpk6CAAAAMiVjhw5ojlz5ujIkSP6+OOPVaxYMa1evVplypRRzZo1rQ4PAAAAsJzD/R74zz//aO3atbpx44YkyTCM+2rn22+/Vd26dSVJK1euVHh4uA4ePKjhw4fr9ddfv9/wAAAAkJM42KzbkG38/vvvql27trZt26bly5fr2rVrkqQ9e/ZowoQJFkcHAACAzGKz2SzbsoM0JwUiIiLUpk0bValSRY888oi5SPCgQYP06quvpjmAixcvytvbW5L0888/68knn1SVKlU0cOBAhYaGprk9AAAAALnT6NGj9fbbb2vdunVycXExy1u3bq2tW7daGBkAAACQdaQ5KTB8+HA5Ozvr+PHjypMnj1nes2dPrVmzJs0BeHl5af/+/YqNjdWaNWvUrl07SdL169dTva4AAAAAAISGhqpbt26JyosVK6aLFy9aEBEAAACQ9aR5TYFffvlFa9euValSpezKK1eurGPHjqU5gICAAPXo0UPFixeXzWZT27ZtJUnbtm1TtWrV0tweAAAAcqBsMgwX1ipYsKDOnDmj8uXL25Xv2rVLJUuWtCgqAAAAZDZ+PqQszUmBqKgouxEC8S5dupRowd/UePPNN1WrVi2dOHFCPXr0MNtwdHTU6NGj09weAAAAgNypV69eGjVqlJYtWyabzaa4uDht3rxZI0aMUL9+/awODwAAAMgS0jx9UPPmzfX111+b7+P/sT158mS1atUq1e3cuHFDP/30kyTpiSee0Pnz5/Xhhx8qMDBQgYGB2rdvn/z9/dMaHgAAAHIgm4N1G7KPd999V9WqVVPp0qV17do11ahRQy1atFDTpk31xhtvWB0eAAAAMomDzWbZlh2keaTA5MmT1aZNG+3YsUMxMTEaOXKk/vrrL126dEmbN29OdTvz5s3TqlWr9Oijj0qSpk+frpo1a8rd3V2SdOjQIZUoUULDhw9Pa4gAAAAAciEXFxfNmjVL48aN0759+3Tt2jXVq1dPlStXtjo0AAAAIMtIc1KgVq1a+vvvvzV9+nTly5dP165dU/fu3fXiiy+qePHiqW5n4cKFGjlypF3ZN998owoVKkiSFixYoBkzZpAUAAAAAJAmZcqUUZkyZawOAwAAAMiS0pwUkKQCBQro9ddfT/NxM2fO1FNPPaV8+fLpn3/+Ue3atZOt26hRI7344ov3Ex4AAABymmwyDBfWGjhwYIr7Z8+enUmRAAAAwEr8ekhZmpMCf/zxR4r7W7Rokey+6dOnq1OnTsqXL58uX76s6Ohoc19ERIRcXFzM93FxcXb7AQAAACAl//77r937W7duad++fbp8+bJat25tUVQAAABA1pLmpICfn1+iMluCnluxsbHJHrtv3z7zdalSpbRv3z5VrVpVkuwSApK0d+9elSpVKq3hAQAAIAeyOdDXB/e2YsWKRGVxcXEaPHiwKlasaEFEAAAAsIKNkcYpckjrAf/++6/ddv78ea1Zs0YPPfSQfvnll1S388gjj2j8+PG6efNmon03btzQxIkT1alTp7SGBwAAAAAmBwcHBQYG6qOPPrI6FAAAACBLSPNIgQIFCiQqa9eunVxcXBQYGKidO3emqp2xY8dq6dKlqlq1qoYOHaoqVapIkg4dOqTp06fr9u3bGjt2bFrDAwAAQE5ETx88gCNHjuj27dtWhwEAAIBMwkDjlN3XQsNJ8fLy0qFDh9JUPyQkRIMHD9bo0aNlGIakO0M72rVrp88++0xeXl7pFR4AAACAHC4wMNDuvWEYOnPmjFatWqX+/ftbFBUAAACQtaQ5KbB371679/H/0H7vvffk4+OTprbKly+vNWvW6NKlS/rnn38kSZUqVZKnp2dawwIAAACQy+3atcvuvYODg4oWLaoPP/xQAwcOtCgqAAAAIGtJc1LAx8dHNpvN7Nkfr0mTJpo9e/Z9BeHp6alGjRrd17EAAADIBbLR+N8ZM2ZoypQpOnv2rOrWratPP/002X/rzpo1S19//bX27dsnSWrQoIHeffddu/oDBgzQvHnz7I7z9/fXmjVrMu4isqkNGzZYHQIAAACyABYaTlmakwJhYWF27+N737i5uaVbUAAAAEB2tGTJEgUGBmrmzJlq3Lixpk2bJn9/fx06dEjFihVLVD84OFi9e/dW06ZN5ebmpvfff1/t27fXX3/9pZIlS5r1OnTooDlz5pjvXV1dM+V6sqO9e/fq77//louLi6pWraqqVataHRIAAACQpaQ5KVC2bNmMiAMAAABIlpU9faKjoxUdHW1X5urqmuSD+alTp+rZZ59VQECAJGnmzJlatWqVZs+erdGjRyeqv3DhQrv3X375pb777jutX79e/fr1szuft7d3elxOjrV9+3YNGjRI+/fvt1uv7KGHHtK8efPM5MClS5eYrhQAACCHY6BAytKUFLh9+7Y++ugjLVq0yOx9U6VKFQUEBOi5555jWAYAAABynKCgIE2cONGubMKECXrzzTftymJiYrRz506NGTPGLHNwcFDbtm21ZcuWVJ3r+vXrunXrVqKH1sHBwSpWrJgKFSqk1q1b6+2331bhwoXv74JyoP3796tNmzaqXr26FixYoOrVq5vlH330kXx9fbVv3z59//33unTpkt544w3LYq1fPucnJFydHa0OIUPlhp+9cXdNF5wTOTnm/A8yp3+OueEzzOOSs///VMpWM0Tet50n/7U6hAxV2cvd6hBwH1KdFLhx44batWunLVu2qG3btmrRooUk6cCBAxoyZIhWrlypH3/8UWFhYdq4caMGDBiQUTEDAAAAmWbMmDEKDAy0K0tqlMDFixcVGxsrLy8vu3IvLy8dPHgwVecaNWqUSpQoobZt25plHTp0UPfu3VW+fHkdOXJEY8eOVceOHbVlyxY5Oub8hwWp8eabb6pdu3b67rvv7Doq+fj4qHfv3urevbtatWqlEydOaPXq1RZGCgAAAFgv1UmB9957TydOnNCuXbtUp04du3179uxRly5dNHz4cH333XcaNWpUugcKAACAXMzCbmTJTRWU3t577z0tXrxYwcHBdut19erVy3xdu3Zt1alTRxUrVlRwcLDatGmT4XFlBxs2bNDq1auTHLlss9k0duxYNW7cWKtXr1bLli0tiBAAAACZiRltUuaQ2oqLFy/W1KlTEyUEJKlu3br64IMP9Omnn8rf31/Dhg1L1yABAACArK5IkSJydHTUuXPn7MrPnTt3z/UAPvjgA7333nv65Zdfkvz3dkIVKlRQkSJF9M8//zxwzDnF1atXE43QSMjb21vOzs7y9/fPxKgAAACArCnVSYFjx46pUaNGye5v0qSJbDabvvrqq3QJDAAAADDZbNZtqeTi4qIGDRpo/fr1ZllcXJzWr18vX1/fZI+bPHmy3nrrLa1Zs0YNGza853lOnjypiIgIFS9ePNWx5XRly5bV9u3bk92/bds2lS1bNhMjAgAAgJUcbNZt2UGqkwL58+fX+fPnk91/9uzZRAuiAQAAALlJYGCgZs2apXnz5unAgQMaPHiwoqKi9P/t3Xl8TGf7x/HvJJEEIZoidgmxL7WVoqUtGjut2rpYivbRKpUqonatrRSPtVX7Urqo9qHWFLWV2kVQsTRtiT2IJSE5vz/U/EwT6YRJzkzyefd1XjX3OXOf656TTObMdS+dO3eWJHXo0MFmIeIxY8Zo0KBBmj17tgICAhQdHa3o6GjFxsZKkmJjY/XBBx/ol19+0alTpxQWFqYWLVooKCiIXu/3adeunUJCQhQeHp5k38GDB9WnTx+baZgAAACAzMzuNQWee+45jRw5Ut9++22y+0ePHq3nnnvOYYEBAAAArqZt27Y6f/68Bg8erOjoaFWqVEmrV6+2Tm0TFRUlN7f/75czffp0xcfH6+WXX7apZ8iQIRo6dKjc3d114MABzZs3TzExMSpQoIBeeOEFjRgxIl3WOXAVoaGhWr9+vSpVqqQGDRqoTJkyMgxDhw8f1vr161W9enWbZAwAAACQmdmdFBgyZIhq1Kihp556SiEhISpdurT1g/aECRMUERGhX375JS1jBQAAQCblSguF9ejRQz169Eh238aNG20enzp1KsW6smbNqjVr1jgosozL29tbGzZs0IQJE/Tll19q06ZNkqQSJUroo48+Uu/evUmiAAAAZCKudP9gBruTAmXLltW6devUpUsXtWvXzvrCGoah0qVLa82aNSpXrlyaBQoAAAAAD+Lp6al+/fqpX79+ZocCAAAAODW7kwLS3cWEDx06pH379um3336TdLf3TeXKldMkOAAAAECS66zYhXRnGAY9wQAAAGCDT4cpS1VS4J5KlSqpUqVKDg4FAAAAAFKnXLlyGjx4sF566SV5eno+8Lhjx47p008/VdGiRdW/f/90jBAAAABwLnYlBUaPHq1evXopa9as/3rsjh07dOHCBTVp0uSRgwMAAAAk5gTFg02ePFn9+vXT22+/rQYNGqhatWoqUKCAvL29dfnyZUVERGjLli06dOiQevTooe7du5sdMgAAANKYG/cPKbIrKRAREaEiRYqodevWatasmapVq6Y8efJIku7cuWP9oL1w4UKdPn1a8+fPT9OgAQAAAECS6tWrp127dmnLli1aunSpFi1apN9//103b95U7ty5VblyZXXo0EGvvvqqHnvsMbPDBQAAAExnV1Jg/vz52r9/v6ZMmaJXXnlFV69elbu7u7y8vHTjxg1JUuXKldW1a1d16tRJ3t7eaRo0AAAAANzv6aef1tNPP212GAAAAIDTs3tNgSeeeEIzZ87UZ599pgMHDtj0vqlUqZJy586dlnECAAAgM2OhYQAAAAB2YvaglKV6oWE3NzcWGgYAAAAAAAAAwAWlOikAAAAApDu6+gAAAACwk4X7hxS5mR0AAAAAAAAAAABIHyQFAAAAAAAAAADIJJg+CAAAAE7PwkLDAAAAAOzE7EEpY6QAAAAAAJd2+/Zt9e3bV0FBQapevbpmz55ts//s2bNyd3c3KToAAADAuaQqKbB//3599NFHmjZtmi5cuGCz7+rVq3rjjTccGhwAAAAg6W5XH7M2OL2PP/5Y8+fP13/+8x+98MILCgkJ0VtvvWVzjGEYJkUHAACA9OZmsZi2uQK7kwJr165V9erVtWTJEo0ZM0alS5fWhg0brPtv3rypefPmpUmQAAAAAPAgixYt0hdffKE+ffroo48+0q5du/TTTz+pc+fO1mSAxYE3aHv27FHTpk0dVh8AAACQnuxOCgwdOlR9+vRReHi4Tp06pb59+6p58+ZavXp1WsYHAAAAACn666+/VL58eevjoKAgbdy4Udu2bdPrr7+uhISEVNe5Zs0a9enTRwMGDNCJEyckSUeOHFHLli315JNPKjEx0WHxAwAAAOnJ7oWGDx06pAULFki628umb9++KlSokF5++WUtWbJETz75ZJoFCQAAgEyOhYaRgnz58un48eMKCAiwlhUsWFAbNmzQc889p06dOqWqvlmzZqlbt27y8/PT5cuX9cUXX+jTTz/Vu+++q7Zt2yo8PFxlypRxbCMAAADgMC4yi49p7B4p4OXlpZiYGJuyV155RV988YXatm2r7777ztGxAQAAAMC/ev7557V48eIk5QUKFNBPP/2kkydPpqq+SZMmacyYMbpw4YK++uorXbhwQdOmTdPBgwc1Y8YMEgIAAABwaXaPFKhUqZI2bNigqlWr2pS3a9dOhmGoY8eODg8OAAAAkBw7HzwynkGDBunIkSPJ7itYsKA2bdqkdevW2V3f8ePH1bp1a0nSSy+9JA8PD33yyScqVKiQQ+IFAABA2uL+IWV2JwW6d++un3/+Odl97du3l2EYmjlzpsMCAwAAAAB7FC1aVEWLFn3g/gIFCqSqE9PNmzeVLVs2SXdvKL28vJQ/f/5HjhMAAABwBnYnBV588UW9+OKLD9z/yiuv6JVXXnFIUAAAAABgpi+++EI+Pj6SpDt37mju3LnKnTu3zTE9e/ZMsY64uDjFxcXZlN25HS+PLJ6ODRYAAABIBbuTAq7k+o9bzQ4Bf8sxfbrZIeBvxq3rZoeAe9yzmB0B/macCDc7BNyT1cfsCODsWGgY6ahIkSI2o6Dz5cunBQsW2BxjsVj+NSkwatQoDRs2zKas9is99PSr7zouWAAAACRh90K6mVSGTAoAAAAAwMM6deqUQ+oJDQ1VSEiITdnwDVEOqRsAAAB4WCQFAAAA4PxYKAzp6NatW1q/fr2aNm0q6e6X+/dPA+Th4aHhw4fL29s7xXq8vLzk5eVlU8bUQQAAAGmPhYZTRlIAAAAAQIYRExOjb775RsePH9cHH3wgPz8/7dmzR/7+/ipYsKBddcydO1crV660JgWmTJmicuXKKWvWrJKkI0eOKF++fElGAQAAAACu4KGTAvHx8Tp58qSKFy8uDw9yCwAAAEhD9PSBHQ4cOKD69evL19dXp06dUrdu3eTn56dly5YpKipK8+fPt6ueRYsWqW/fvjZlixcvVrFixSRJCxcu1NSpU0kKAAAAOCmWJEtZqtdcuHHjhrp06aJs2bKpXLlyioq6Oyfmu+++q9GjRzs8QAAAAACwR0hIiDp16qRjx47ZTO3TuHFj/fzzz3bXExkZqQoVKlgfe3t7y83t/2+dqlevroiICMcEDQAAAKSzVCcFQkNDtX//fm3cuNHmg3b9+vW1dOlShwYHAAAAAPb69ddf9dZbbyUpL1iwoKKjo+2uJyYmxmYNgfPnzysgIMD6ODEx0WY/AAAA4EpSPe/P8uXLtXTpUj311FM2CzaUK1dOx48fd2hwAAAAgCSmD4JdvLy8dPXq1STlv/32m/LkyWN3PYUKFVJ4eLhKlSqV7P4DBw6oUKFCDx0nAAAA0hbTB6Us1SMFzp8/r7x58yYpv379Oqs6AwAAADBN8+bNNXz4cN2+fVuSZLFYFBUVpX79+qlVq1Z219O4cWMNHjxYt27dSrLv5s2bGjZsmJo0aeKwuAEAAID0lOqkQLVq1bRy5Urr43uJgC+++EI1a9Z0XGQAAADAPW5u5m1wGePHj1dsbKzy5s2rmzdvqm7dugoKClKOHDn08ccf213PgAEDdOnSJZUqVUqffPKJvv/+e33//fcaO3asSpUqpcuXL2vAgAFp2BIAAAA8CovFYtrmClI9fdDIkSPVqFEjRURE6M6dO5o0aZIiIiK0bds2bdq0KS1iBAAAAIB/5evrq3Xr1mnr1q3av3+/YmNjVaVKFdWvXz9V9fj7+2vbtm3q3r27+vfvL8MwJN29uWzQoIGmTZsmf3//tGgCAAAAkOZSnRR4+umntW/fPo0ePVoVKlTQ2rVrVaVKFW3fvl0VKlRIixgBAAAAwG61a9dW7dq1H6mOwMBArV69WpcuXVJkZKQkKSgoSH5+fo4IEQAAADBNqpMCklS8eHHNnDnT0bEAAAAAyXORYbgwV8+ePRUUFKSePXvalE+ZMkWRkZGaOHFiquv08/NT9erVHRQhAAAA0gMLDacs1ZOk/vjjj1qzZk2S8jVr1mjVqlUOCQoAAAAAUuvbb79NdoRArVq19M0335gQEQAAAOB8Up0U6N+/vxISEpKUG4ah/v37OyQoAAAAwIbFYt4Gl3Hx4kX5+vomKc+ZM6cuXLhgQkQAAAAwA7cPKUt1UuDYsWMqW7ZskvLSpUtb59oEAAAAgPQWFBSk1atXJylftWqVihUrZkJEAAAAgPNJ9ZoCvr6+OnHihAICAmzKIyMjlT17dkfFBQAAAACpEhISoh49euj8+fN6/vnnJUlhYWEaP378Q60nAAAAAGREqU4KtGjRQu+9956+++47FS9eXNLdhMD777+v5s2bOzxAAAAAwGXG4cJUb7zxhuLi4vTxxx9rxIgRkqSAgABNnz5dHTp0MDk6AAAApBc37h9SlOrpg8aOHavs2bOrdOnSCgwMVGBgoMqUKaPHH39c48aNS4sYAQAAAMAu3bt3159//qmzZ8/q6tWrOnHiBAkBAAAA4D4PNX3Qtm3btG7dOu3fv19Zs2ZVxYoVVadOnbSIDwAAAJDcUt2XBZlcnjx5zA4BAAAAJuHuIWWpTgpIksVi0QsvvKAXXnjB0fEAAAAAwEM5e/as+vTpo7CwMJ07d06GYdjsT0hIMCkyAAAAwHk8VFIgLCzM+kE7MTHRZt/s2bMdEhgAAABgxZygsEOnTp0UFRWlQYMGKX/+/LLwcwMAAJAp8TEwZalOCgwbNkzDhw9XtWrV+KANAAAAwGls2bJFmzdvVqVKlcwOBQAAAHBaqU4KzJgxQ3PnztXrr7+eFvEAAAAAwEMpXLhwkimDAAAAANhK9ZoL8fHxqlWrVlrEAgAAACTPYjFvg8uYOHGi+vfvr1OnTpkdCgAAAEzkZrGYtrmCVCcFunbtqsWLF6dFLAAAAADw0Nq2bauNGzeqePHiypEjh/z8/Gw2AAAAAA8xfdCtW7f0+eefa/369apYsaKyZMlis//TTz91WHAAAACAJHrswy4TJ040OwQAAAA4AW4fUpbqpMCBAwesC3eFh4fb7GPRYQAAAABm6dixo9khAAAAAE4v1UmBDRs2pEUcAAAAAPDIjh8/rjlz5uj48eOaNGmS8ubNq1WrVqlIkSIqV66c2eGpWsEcZoeQ5uJuJ5gdQpry9kj1LLwu504iC3ZnBK4yr/XD8nDL+L+LGfwSSpKu3bxjdghpLsc/ZlkBnMFDv4NGRkZqzZo1unnzpiTJMPjQAAAAgDTi5mbeBpexadMmVahQQTt27NCyZcsUGxsrSdq/f7+GDBlicnQAAABIL24W8zZXkOq7nIsXL6pevXoqWbKkGjdurDNnzkiSunTpovfff9/hAQIAAACAPfr376+PPvpI69atk6enp7X8+eef1y+//GJiZAAAAIDzSHVSoHfv3sqSJYuioqKULVs2a3nbtm21evVqhwYHAAAASLo7ft6sDS7j4MGDevHFF5OU582bVxcuXDAhIgAAAJjBzWIxbXMFqV5TYO3atVqzZo0KFSpkU16iRAn9/vvvDgsMAAAAAFIjV65cOnPmjAIDA23K9+7dq4IFC5oUFQAAAOBcUj1S4Pr16zYjBO65dOmSvLy8HBIUAAAAAKRWu3bt1K9fP0VHR8tisSgxMVFbt25Vnz591KFDB7PDAwAAAJxCqpMCzzzzjObPn299fO/D9tixY/Xcc885NDgAAABAEtMHwS4jR45U6dKlVbhwYcXGxqps2bKqU6eOatWqpYEDB5odHgAAANIJtw8pS/X0QWPHjlW9evW0a9cuxcfHq2/fvjp06JAuXbqkrVu3pkWMAAAAAPCvPD09NXPmTA0aNEjh4eGKjY1V5cqVVaJECbNDAwAAAJxGqpMC5cuX12+//aYpU6YoR44cio2N1UsvvaR33nlH+fPnT4sYAQAAkNm5SpcbOIUiRYqoSJEiZocBAAAAk7hx+5CiVCcFoqKiVLhwYX344YfJ7uPDNwAAAAAzvPHGGynunz17djpFAgAAADivVCcFAgMDdebMGeXNm9em/OLFiwoMDFRCQoLDggMAAAAAe12+fNnm8e3btxUeHq6YmBg9//zzJkUFAAAAOJdUJwUMw5AlmeHbsbGx8vb2TnUA169f1+jRoxUWFqZz584pMTHRZv+JEydSXScAAAAyFoubm9kh2G3q1Kn65JNPFB0drSeeeEKTJ09W9erVkz125syZmj9/vsLDwyVJVatW1ciRI22ONwxDQ4YM0cyZMxUTE6PatWtr+vTpzJOfjO+++y5JWWJiorp3767ixYubEBEAAADMYBHzB6XE7qRASEiIJMlisWjQoEHKli2bdV9CQoJ27NihSpUqpTqArl27atOmTXr99deVP3/+ZBMOAAAAgCtYunSpQkJCNGPGDNWoUUMTJ05UcHCwjh49mmSkrSRt3LhR7du3V61ateTt7a0xY8bohRde0KFDh1SwYEFJ0tixY/Xf//5X8+bNU2BgoAYNGqTg4GBFREQ8VKeczMbNzU0hISF69tln1bdvX7PDAQAAAExnd1Jg7969ku72VDp48KA8PT2t+zw9PfXEE0+oT58+qQ5g1apVWrlypWrXrp3q5wIAACCTcJGOI59++qm6deumzp07S5JmzJihlStXavbs2erfv3+S4xctWmTz+IsvvtC3336rsLAwdejQQYZhaOLEiRo4cKBatGghSZo/f778/f21fPlytWvXLu0blQEcP35cd+7cMTsMAAAApBMWGk6Z3UmBDRs2SJI6d+6sSZMmKWfOnA4J4LHHHpOfn59D6gIAAAAcLS4uTnFxcTZlXl5e8vLysimLj4/X7t27FRoaai1zc3NT/fr1tX37drvOdePGDd2+fdv6+fjkyZOKjo5W/fr1rcf4+vqqRo0a2r59O0mBf7g3uvkewzB05swZrVy5Uh07djQpKgAAAMC5pHpy1jlz5jgsISBJI0aM0ODBg3Xjxg2H1QkAAIAMxmIxbRs1apR8fX1ttlGjRiUJ8cKFC0pISJC/v79Nub+/v6Kjo+1qZr9+/VSgQAFrEuDe8x6lzsxk7969NtuBAwckSePHj9fEiRPNDQ4AAADpxs1i3uYKUr3QsKMXBh4/fryOHz8uf39/BQQEKEuWLDb79+zZk9oQAQAAAIcJDQ1N0gP9n6MEHGH06NFasmSJNm7cyFoBD+ne6GYAAAAAD5bqpICjFwZu2bLlIz0fAAAASEvJTRWUnNy5c8vd3V1nz561KT979qzy5cuX4nPHjRun0aNHa/369apYsaK1/N7zzp49q/z589vUWalSpVS0AgAAAADuSnVSwNELAw8ZMsQh9QAAACADc4GFhj09PVW1alWFhYVZO74kJiYqLCxMPXr0eODzxo4dq48//lhr1qxRtWrVbPYFBgYqX758CgsLsyYBrl69qh07dqh79+5p1RSXVblyZbs7LTEiGQAAION61I7sGV2qkwJptTDw7t27dfjwYUlSuXLlVLlyZYefAwAAAEhLISEh6tixo6pVq6bq1atr4sSJun79ujp37ixJ6tChgwoWLGhdk2DMmDEaPHiwFi9erICAAOs6AT4+PvLx8ZHFYtF7772njz76SCVKlFBgYKAGDRqkAgUKMOI2GQ0bNtS0adNUtmxZ1axZU5L0yy+/6NChQ+revbuyZs1qcoQAAACA+VKdFLi3MPC8efOULVu2Rw7g3LlzateunTZu3KhcuXJJkmJiYvTcc89pyZIlypMnT4rPj4uLU1xcnG1ZYqK83FK9hjIAAACclYt8tmvbtq3Onz+vwYMHKzo6WpUqVdLq1autCwVHRUXJ7b62TJ8+XfHx8Xr55Zdt6hkyZIiGDh0qSerbt6+uX7+uN998UzExMXr66ae1evVq1h1Ixvnz59WzZ0+NGDHCpnzIkCH6448/NHv2bJMiAwAAQHpylQV/zWIxDMNIzRMqV66s48ePyzAMhywM3LZtW504cULz589XmTJlJEkRERHq2LGjgoKC9OWXX6b4/KFDh2rYsGE2Zf0D/BUamP8Bz0B6yjF9utkh4B4/fiecxpULZkeAvyUe22t2CLgnq4/ZEeA+7s+2MzuEJBI+7mbaud0/nGnauZE6vr6+2rVrl0qUKGFTfuzYMVWrVk1XrlwxKbL/983+M2aHkOaeKuL4keXOxDdrln8/yMUlpupbAtfk4Z7xvy1yy+BTZ5y7GvfvB7k4H293s0NIc+euZPzreObqLbNDSFPPl37c7BCSNX7TCdPO/X7dYqad216pHing6GHKq1ev1vr1660JAUkqW7aspk6dqhdeeOFfnx8aGqqQkBCbsrjmzzg0RgAAAADOL2vWrNq6dWuSpMDWrVsZWQEAAACnNHXqVH3yySeKjo7WE088ocmTJ6t69erJHjtz5kzNnz9f4eHhkqSqVatq5MiRDzz+QVKdFHD0wsCJiYlJRhtIUpYsWZSYmPivz/fy8pKXl5dN2VUXGV4OAAAAO2Xw3o5wjPfee0/du3fXnj17rDdGO3bs0OzZszVo0CCTowMAAEB6cZXbh6VLlyokJEQzZsxQjRo1NHHiRAUHB+vo0aPKmzdvkuM3btyo9u3bq1atWvL29taYMWP0wgsv6NChQypYsKDd532ob89jYmL0xRdfKDQ0VJcuXZJ0d9qgv/76K9V1Pf/88+rVq5dOnz5tLfvrr7/Uu3dv1atX72HCAwAAAJAJ9e/fX/PmzdPu3bvVs2dP9ezZU3v27NGcOXPUv39/s8MDAAAAbHz66afq1q2bOnfurLJly2rGjBnKli3bA9fCWrRokd5++21VqlRJpUuX1hdffKHExESFhYWl6rypHilw4MAB1a9fX76+vjp16pS6desmPz8/LVu2TFFRUZo/f36q6psyZYqaN2+ugIAAFS5cWNLdBdgqVKighQsXpjY8AAAAZESu0tUHpmvTpo3atGljdhgAAAAwkZnrqsTFxSkuzna9jORmu4mPj9fu3bsVGhpqLXNzc1P9+vW1fft2u85148YN3b59W35+qVvTKdVJgZCQEHXq1Eljx45Vjhw5rOWNGzfWK6+8ktrqVLhwYe3Zs0dhYWE6fPiwJKlMmTKqX79+qusCAAAAkLnFxMTom2++0YkTJ9SnTx/5+flpz5498vf3t3tI9c2bNxUWFqamTZtKuruO2f03du7u7hoxYgTrFAAAACCJUaNGadiwYTZlQ4YM0dChQ23KLly4oISEBPn7+9uU+/v768iRI3adq1+/fipQoECqv0tPdVLg119/1WeffZakvGDBgoqOjra7nvs/aFssFoWFhVk/aJ88eVJr167V8OHD+aANAAAAwC7/HNXctWvXhxrVPG/ePK1cudKaFJgyZYrKlSunrFmzSpKOHDmiAgUKqHfv3inWk1wvsdvxccri6fWAZwAAAMDVhYaGKiQkxKbsn6MEHGH06NFasmSJNm7cmOrv0FO9poCXl5euXr2apPy3335Tnjx57K5n3rx5NsmFKVOmaNu2bdq7d6/27t2rBQsWaPr06akNDwAAABmRxWLeBpdxb1TzsWPHbG6MGjdurJ9//tnuehYtWqQ333zTpmzx4sXasGGDNmzYoE8++URfffXVv9YzatQo+fr62mzfzZpsf4MAAADwUNws5m1eXl7KmTOnzZZcUiB37txyd3fX2bNnbcrPnj2rfPnypdi+cePGafTo0Vq7dq0qVqyY+tcntU9o3ry5hg8frtu3b0uSLBaLoqKi1K9fP7Vq1cruehz1QRsAAAAApLujmt96660k5akd1RwZGakKFSpYH3t7e8vN7f9vnapXr66IiIh/rSc0NFRXrlyx2V7s8q7dcQAAACDj8vT0VNWqVW0WCb63aHDNmjUf+LyxY8dqxIgRWr16tapVq/ZQ5051UmD8+PGKjY1V3rx5dfPmTdWtW1dBQUHKkSOHPv744xSfO2PGDF27dk1S0g/a/2TvB20AAABkAm5u5m1wGY4a1RwTE2Mz7c/58+cVEBBgfZyYmJhkWqAHxfPPXmJMHQQAAJD2XGWgcUhIiGbOnKl58+bp8OHD6t69u65fv67OnTtLkjp06GCzEPGYMWM0aNAgzZ49WwEBAYqOjlZ0dLRiY2NTdd5Uryng6+urdevWaevWrdq/f79iY2NVpUoVuxYzmDJlipo0aaIcOXIk+aB98eJFeXp6Wh/b+0EbAAAAAKT/H9V8b8Txw45qLlSokMLDw1WqVKlk9x84cECFChVySMwAAADIvNq2bavz589r8ODBio6OVqVKlbR69Wrr4sNRUVE2I1anT5+u+Ph4vfzyyzb1JLeQcUpSnRS4p3bt2qpdu3aqnhMeHm799z8/aN+fEJD4oA0AAAAgdcaPH6+XX37ZZlRzdHS0atas+a+jmu/XuHFjDR48WE2aNEmyaNvNmzc1bNgwNWnSxNHhAwAAIBPq0aOHevTokey+jRs32jw+deqUQ85pd1Jg+/btunjxopo2bWotmz9/voYMGaLr16+rZcuWmjx5st0rKfNBGwAAAHZjwV/Y4VFGNd9vwIAB+uqrr1SqVCn16NFDJUuWlCQdPXpUU6ZM0Z07dzRgwIC0aAIAAAAcwE3cP6TE7qTA8OHD9eyzz1qTAgcPHlSXLl3UqVMnlSlTRp988okKFChg9zAFPmgDAAAASAsPM6r5fv7+/tq6davefvtt9e/fX4ZhSLo7HVGDBg00bdo065BuAAAAwNXYnRTYt2+fRowYYX28ZMkS1ahRQzNnzpQkFS5cOFVzF/n7+2vbtm3q3r07H7QBAACQMkYKIAWOHtUsScWKFdPq1at16dIlRUZGSpKCgoLk5+fn8PgBAADgWNw+pMzupMDly5dtvqTftGmTGjVqZH385JNP6o8//kjVyQMDA/mgDQAAAOCROHpU80svvWTXccuWLXvYkAEAAADT2J0U8Pf318mTJ1W4cGHFx8drz549GjZsmHX/tWvXlCVLlocKws/PT9WrV3+o5wIAACATcHMzOwI4MUePavb19U2LMAEAAJBO3BgpkCK7kwKNGzdW//79NWbMGC1fvlzZsmXTM888Y91/4MABFS9ePE2CBAAAAIAHcfSo5jlz5jg0PgAAAMCZ2N3lasSIEfLw8FDdunU1c+ZMzZw5U56entb9s2fP1gsvvJAmQQIAAADAg9wb1SzJOqr5qaeesu5/lFHNAAAAQEZj90iB3Llz6+eff9aVK1fk4+Mjd3d3m/1ff/21fHx8HB4gAAAAwEphSAmjmgEAAHA/N+4fUmR3UuCeB82vyeLAAAAAAMwwYsQIvfTSS6pbt658fHw0b948RjUDAAAAD5DqpAAAAACQ7ujpgxQwqhkAAAD34/YhZSQFAAAAAGQIjGoGAAAA/p3dCw0DAAAAAAAAAADXxkgBAAAAOD/G/wIAAACwEwsNp4yRAgAAAAAAAAAAZBKMFAAAAIDzc6MvCwAAAAD7MFAgZdxdAQAAAAAAAACQSZAUAAAAAAAAAAAgk2D6IAAAADg/xv8CAAAAsBM94VPG6wMAAAAAAAAAQCbBSAEAAAA4P0YKAAAAALCThfuHFDFSAAAAAAAAAACATIKRAgAAAHB+FvqyAAAAALAP4wRSxt0VAAAAAAAAAACZBEkBAAAAAAAAAAAyCaYPAgAAgPNzYwAwMoaZW6PMDiHNNSydz+wQ0tSdxESzQ0hzcbczfht12+wA0p53FnezQ0hTfj5ZzA4hzd1JMMwOIc1l8cj4/ZXP37xldgiZkhsLDaco4//mAQAAAAAAAAAASYwUAAAAgCtgoWEAAAAAdmKcQMq4uwIAAAAAAAAAIJMgKQAAAAAAAAAAQCbB9EEAAABwfiwUBgAAAMBO3D6kjJECAAAAAAAAAABkEowUAAAAgPNzoy8LAAAAAPtYGCqQIu6uAAAAAAAAAADIJEgKAAAAAAAAAACQSTB9EAAAAJwfw38BAAAA2Ime8Cnj9QEAAAAAAAAAIJNgpAAAAACcn4W+LAAAAADsw0LDKePuCgAAAAAAAACATCJDjhS4eumm2SHgbzl885gdAv5m8c5udgj4m5Fwx+wQ8DdL4ZJmh4C/GZEHzA4BAAAAAIBMIUMmBQAAAJDBMPwXAAAAgJ24e0gZ0wcBAAAAAAAAAJBJMFIAAAAAzs+NviwAAAAA7MNCwynj7goAAAAAAAAAgEyCkQIAAABwfvT0AQAAAGAnesKnjNcHAAAAAAAAAIBMgqQAAAAAAAAAAACZBNMHAQAAwPlZ6MsCAAAAwD4sNJwy7q4AAAAAIBVu3Lihbdu2mR0GAAAA8FAYKQAAAADn50ZPHziPY8eO6ZlnnlFCQoLZoQAAACAZ3D2kjJECAAAAgANNnTpVAQEB8vb2Vo0aNbRz584HHnvo0CG1atVKAQEBslgsmjhxYpJjhg4dKovFYrOVLl06DVsAAAAAICMjKQAAAAA4yNKlSxUSEqIhQ4Zoz549euKJJxQcHKxz584le/yNGzdUrFgxjR49Wvny5XtgveXKldOZM2es25YtW9KqCQAAAAAyOKYPAgAAgPNzkYWGP/30U3Xr1k2dO3eWJM2YMUMrV67U7Nmz1b9//yTHP/nkk3ryySclKdn993h4eKSYNIBziouLU1xcnE1Z4u14uWXxNCkiAACAzIF1hlNGUgAAAABIQXJf7Hp5ecnLy8umLD4+Xrt371ZoaKi1zM3NTfXr19f27dsfKYZjx46pQIEC8vb2Vs2aNTVq1CgVKVLkkerEg/3www8p7j958qRd9YwaNUrDhg2zKSvWuIuCmnR76NgAAACAR0VSAAAAAM7PxK4+yX2xO2TIEA0dOtSm7MKFC0pISJC/v79Nub+/v44cOfLQ569Ro4bmzp2rUqVK6cyZMxo2bJieeeYZhYeHK0eOHA9dLx6sZcuWDqknNDRUISEhNmWt5ux3SN0AAAB4MDeWGk4RSQEAAAAgBcl9sfvPUQJpqVGjRtZ/V6xYUTVq1FDRokX11VdfqUuXLukWR2aSmJj4r8fcuHHjX49JbkQJUwcBAADAbK4xOSsAAABgEi8vL+XMmdNmSy4pkDt3brm7u+vs2bM25WfPnnXoegC5cuVSyZIlFRkZ6bA6Yb+4uDh9+umnKlasmNmhAAAAAA+FpAAAAACcn8XNvM1Onp6eqlq1qsLCwqxliYmJCgsLU82aNR32UsTGxur48ePKnz+/w+qErbi4OIWGhqpatWqqVauWli9fLkmaPXu2AgMDNWHCBPXu3dvcIAEAAPBAFot5mytg+iAAAADAQUJCQtSxY0dVq1ZN1atX18SJE3X9+nV17txZktShQwcVLFhQo0aNknR3ceKIiAjrv//66y/t27dPPj4+CgoKkiT16dNHzZo1U9GiRXX69GkNGTJE7u7uat++vTmNzAQGDx6szz77TPXr19e2bdvUunVrde7cWb/88os+/fRTtW7dWu7u7maHCQAAADwUkgIAAABwfm6u0eWmbdu2On/+vAYPHqzo6GhVqlRJq1evti4+HBUVJTe3/x99cPr0aVWuXNn6eNy4cRo3bpzq1q2rjRs3SpL+/PNPtW/fXhcvXlSePHn09NNP65dfflGePHnStW2Zyddff6358+erefPmCg8PV8WKFXXnzh3t379fFlfp/gUAAJCJWVhoOEUkBQAAAAAH6tGjh3r06JHsvntf9N8TEBAgwzBSrG/JkiWOCg12+vPPP1W1alVJUvny5eXl5aXevXuTEAAAAECGQFIAAAAAzo8vY5GOEhIS5OnpaX3s4eEhHx8fEyMCAABAanD7kDKSAgAAAABwH8Mw1KlTJ3l5eUmSbt26pf/85z/Knj27zXHLli0zIzwAAADgkZAUAAAAAID7dOzY0ebxa6+9ZlIkAAAAgOORFAAAAIDzs7j9+zGAg8yZM8fsEAAAAPAI3FhoOEXcXQEAAAAAAAAAkEkwUgAAAADOz42ePgAAAADsw0LDKWOkAAAAAAAAAAAAmQRJAQAAAAAAAAAAMgmmDwIAAIDzY6FhAAAAAHZi+qCUcXcFAAAAAAAAAEAmwUgBAAAAOD+6+gAAAACwk0XcP6SEkQIAAAAAAAAAAGQSJAUAAAAAAAAAAMgkmD4IAAAAzo+FhgEAAADYyY3Zg1LkFHdXmzdv1muvvaaaNWvqr7/+kiQtWLBAW7ZsMTkyAAAAAAAAAAAyDtOTAt9++62Cg4OVNWtW7d27V3FxcZKkK1euaOTIkSZHBwAAAKfgZjFvAwAAAOBSLCb+5wpMTwp89NFHmjFjhmbOnKksWbJYy2vXrq09e/aYGBkAAAAAAAAAABmL6UmBo0ePqk6dOknKfX19FRMTk/4BAQAAAAAAAACQQZm+0HC+fPkUGRmpgIAAm/ItW7aoWLFi5gQFAAAA58JCwwAAAADsZHGNWXxMY/rdVbdu3dSrVy/t2LFDFotFp0+f1qJFi9SnTx91797d7PAAAAAAAAAAAMgwTB8p0L9/fyUmJqpevXq6ceOG6tSpIy8vL/Xp00fvvvuu2eEBAADAGdDVBxlE96eLmh1CmnMzvetZ2vLMBCOX3DLBe27Gb6Hk7paxW3k9LsHsENJcjqymf22X5vx9vcwOIc19tSLa7BDSVNvKBc0OIVmusuCvWUx/d7FYLPrwww/1wQcfKDIyUrGxsSpbtqx8fHzMDg0AAAAAAAAAgAzF9KTAPZ6enipbtqzZYQAAAMAZZfSuxwAAAAAcJoMPlnpkpicFrl+/rtGjRyssLEznzp1TYmKizf4TJ06YFBkAAAAAAAAAABmL6UmBrl27atOmTXr99deVP39+WTLB3IUAAAAAAAAAAJjB9KTAqlWrtHLlStWuXdvsUAAAAOCs6DgCAAAAwE4sNJwy05MCjz32mPz8/B76+XFxcYqLi7MtS0yUF/POAgAAAAAAAABgw/RvzkeMGKHBgwfrxo0bD/X8UaNGydfX12abeu6Sg6MEAACAqSxu5m0AAAAAXIrFYt7mCkwZKVC5cmWbtQMiIyPl7++vgIAAZcmSxebYPXv2pFhXaGioQkJCbMrOP13NccECAAAAAAAAAJBBmJIUaNmypcPq8vLykpeXl03ZVaYOAgAAAAAAAAAgCVOSAkOGDDHjtAAAAHBVrjIOFwAAAIDpuHtImeld6osVK6aLFy8mKY+JiVGxYsVMiAgAAAAAAAAAgIzJlJEC9zt16pQSEhKSlMfFxenPP/80ISIAAAA4HaaHBAAAAGAnN0Yap8i0pMAPP/xg/feaNWvk6+trfZyQkKCwsDAFBgaaERoAAAAAAAAAABmSaUmBe4sNWywWdezY0WZflixZFBAQoPHjx5sQGQAAAAAAAAAAGZNpSYHExERJUmBgoH799Vflzp3brFAAAADg7Bj+CwAAAMBO3D2kzLSkwK1bt7R+/XqdPHlSkhQaGqq4uLj/D8zDQ8OHD5e3t7dZIQIAAAAAAAAAkKGYlhSYO3euVq5cqaZNm0qSpkyZonLlyilr1qySpCNHjih//vzq3bu3WSECAADAWVhYaBgAAACAnRgqkCLT7q4WLVqkN99806Zs8eLF2rBhgzZs2KBPPvlEX331lUnRAQAAAAAAAACQ8aRrUmDGjBm6du2aJCkyMlIVKlR44LHVq1dXREREeoUGAAAAAAAAAECGl65JgSlTpigmJkaSFBMTY7OGwMWLF1WsWDHr48TERJv9AAAAyMQsFvM2AAAAAC7FYuJ/riBdkwLh4eEqXLiwJKlQoUIKDw+37vP09LQ59sCBAypUqFB6hgcAAAAAAAAAQIZm2poCjRs31uDBg3Xr1q0k+27evKlhw4apSZMmJkQGAAAAp2NxM28DAAAA4FIYaJwyD7NOPGDAAH311VcqVaqUevTooZIlS0qSjh49qilTpujOnTsaMGCAWeEBAAAAAAAAAJDhmJYU8Pf317Zt29S9e3f1799fhmFIkiwWixo0aKBp06bJ39/frPAAAADgTNxcpMsNAAAAANNx95Ay05ICkhQYGKjVq1fr0qVLioyMlCQFBQXJz8/PzLAAAAAAAAAAAMiQTE0K3OPn56fq1aubHQYAAACATC48PFzly5c3OwwAAAAgzbByGgAAAJwfCw0jnVSsWFE1atTQzJkzde3aNbPDAQAAwMOwmLi5AO5yAAAAAOBvmzZtUrly5fT+++8rf/786tixozZv3vxQdcXFxenq1as22+34OAdHDAAAAKQOSQEAAAA4P4vFvA2ZyjPPPKPZs2frzJkzmjx5sk6dOqW6deuqZMmSGjNmjKKjo+2ua9SoUfL19bXZvp01OQ2jBwAAgCRZTPzPFZAUAAAAAIB/yJ49uzp37qxNmzbpt99+U+vWrTV16lQVKVJEzZs3t6uO0NBQXblyxWZr1eXdNI4cAAAASJlTLDQMAAAAAM4qKChIAwYMUNGiRRUaGqqVK1fa9TwvLy95eXnZlGXxvJEWIQIAAAB2IykAAAAA58eCvzDJzz//rNmzZ+vbb7+Vm5ub2rRpoy5dupgdFgAAAFLALKApIykAAAAAAPc5ffq05s6dq7lz5yoyMlK1atXSf//7X7Vp00bZs2c3OzwAAADgkZAUAAAAgNOz0NUH6aRRo0Zav369cufOrQ4dOuiNN95QqVKlzA4LAAAAqcDdQ8pICgAAAADA37JkyaJvvvlGTZs2lbu7u9nhAAAAAA5HUgAAAAAA/vbDDz+YHQIAAACQpkgKAAAAwPmx0DAAAAAAezF/UIq4uwIAAAAAAAAAIJNgpAAAAACcHyMFAAAAANjJwlCBFHF3BQAAADjQ1KlTFRAQIG9vb9WoUUM7d+584LGHDh1Sq1atFBAQIIvFookTJz5ynQAAAABcR1rcP/wbkgIAAABwfm4W87ZUWLp0qUJCQjRkyBDt2bNHTzzxhIKDg3Xu3Llkj79x44aKFSum0aNHK1++fA6pEwAAAMjsLBbzttRIi/sHe5AUAAAAABzk008/Vbdu3dS5c2eVLVtWM2bMULZs2TR79uxkj3/yySf1ySefqF27dvLy8nJInQAAAADMExcXp6tXr9pscXFxyR6bFvcP9iApAAAAAKTA3g/18fHx2r17t+rXr28tc3NzU/369bV9+/aHOnda1AkAAAAg7YwaNUq+vr4226hRo5IcZ+ZnfZICAAAAcH4WN9M2ez/UX7hwQQkJCfL397cp9/f3V3R09EM1Oy3qBAAAADI6i4lbaGiorly5YrOFhoYmidHMz/oeaVo7AAAA4OJCQ0MVEhJiU/YoQ3UBAAAAZFxeXl5Of79AUgAAAADOL7UrdjmQvR/qc+fOLXd3d509e9am/OzZsw+9CFha1AkAAABkeObdPtjNzM/6TB8EAAAAOICnp6eqVq2qsLAwa1liYqLCwsJUs2ZNp6kTAAAAgPnM/KzPSAEAAADAQUJCQtSxY0dVq1ZN1atX18SJE3X9+nV17txZktShQwcVLFjQuiZBfHy8IiIirP/+66+/tG/fPvn4+CgoKMiuOgEAAAC4prS4f7AHSQEAAAA4P4trDHBt27atzp8/r8GDBys6OlqVKlXS6tWrrYuHRUVFyc3t/9ty+vRpVa5c2fp43LhxGjdunOrWrauNGzfaVScAAAAAWxZXmD9IaXP/YA+LYRiGw1rhJP6sVNrsEPC3gqtXmh0C/mbJ7mt2CPibcf2K2SHgb8aFv8wOAX8zIg+YHQLu496yh9khJJG4e41p53arGmzauZHxLD8QbXYIae6FMhk8YZXh7qCTupOY8RvpGl8VPRp3t4zdyutxCWaHkOZyZM34fXlvJySaHUKa67Ror9khpKlv36hqdgjJOvBHrGnnrljYx7Rz2yvjv7sAAADA9Zm40DAAAAAA18LtQ8pcYxw2AAAAAAAAAAB4ZCQFAAAAAAAAAADIJJg+CAAAAM7PRRYaBgAAAGA+Zg9KGXdXAAAAAAAAAABkEowUAAAAgPNzo68PAAAAADtx+5CiDJkUKLBghtkh4G+WnLnNDgH3GIbZEeBvFq/sZoeAv1kKBJkdAv526c23zQ4B9/Fr2cPsEIAM6+C5WLNDSHP1S/mbHUKaygw5yjsJGf/ewcM941/IhAx+D2goY7dPkhISM34bPdyYxAQwA795AAAAAAAAAABkEhlypAAAAAAyGBYaBgAAAGAnC/MHpYi7KwAAAAAAAAAAMglGCgAAAMD5WejpAwAAAMA+3D6kjJECAAAAAAAAAABkEowUAAAAgPNjTQEAAAAAdmKgQMq4uwIAAAAAAAAAIJMgKQAAAAAAAAAAQCbB9EEAAABwfqwUBgAAAMBe3D6kiJECAAAAAAAAAABkEowUAAAAgPNjoWEAAAAAdrIwVCBF3F0BAAAAAAAAAJBJkBQAAAAAAAAAACCTYPogAAAAOD83+rIAAAAAsI+F2YNSxN0VAAAAAAAAAACZBCMFAAAA4PQsdPUBAAAAYCfuHlLGSAEAAAAAAAAAADIJkgIAAAAAAAAAAGQSTB8EAAAA52ehLwsAAAAAOzF/UIq4uwIAAAAAAAAAIJNgpAAAAACcHwsNAwAAALCThaECKWKkAAAAAADY6datWxo3bpzZYQAAAAAPjaQAAAAAnJ/FzbwNmc758+e1YsUKrV27VgkJCZKk27dva9KkSQoICNDo0aNNjhAAAAApsVjM21wB0wcBAAAAwN+2bNmipk2b6urVq7JYLKpWrZrmzJmjli1bysPDQ0OHDlXHjh3NDhMAAAB4aHR9AgAAAIC/DRw4UI0bN9aBAwcUEhKiX3/9VS+++KJGjhypiIgI/ec//1HWrFnNDhMAAAB4aCQFAAAA4PwY/4t0cvDgQQ0cOFDly5fX8OHDZbFYNHbsWL388stmhwYAAAA7WUzcXAFJAQAAAAD42+XLl5U7d25JUtasWZUtWzaVL1/e5KgAAAAAx2FNAQAAADg/N/qyIP1EREQoOjpakmQYho4eParr16/bHFOxYsV/rScuLk5xcXE2ZXfi4+Th6eW4YAEAAJCUq3TZNwlJAQAAAAC4T7169WQYhvVx06ZNJUkWi0WGYchisSghIeFf6xk1apSGDRtmU/bs6+/q+Q49HRswAAAAkAokBQAAAADgbydPnnRYXaGhoQoJCbEpG7/lD4fVDwAAADwMkgIAAABwfiz4i3Qyb9489enTR9myZXvkury8vOTlZTtVEFMHAQAApD0L8weliMlZAQAAAOBvw4YNU2xsrNlhAAAAAGmGkQIAAABwfhb6siB93L+WAAAAAFwTA41Txt0VAAAAANzHwl0kAAAAMjDTRwpERUWpcOHCST54G4ahP/74Q0WKFDEpMgAAAACZUcmSJf81MXDp0qV0igYAAABwLNOTAoGBgTpz5ozy5s1rU37p0iUFBgYqISHBpMgAAADgNOi5jXQ0bNgw+fr6mh0GAAAAHhJ3DykzPSlgGEayvXBiY2Pl7e1tQkQAAAAAMrN27dol6bQEAAAAZBSmJQVCQkIk3Z2vc9CgQcqWLZt1X0JCgnbs2KFKlSqZFB0AAACcC319kD5YTwAAACAD4CNdikxLCuzdu1fS3ZECBw8elKenp3Wfp6ennnjiCfXp08es8AAAAABkQoZhmB0CAAAAkKZMSwps2LBBktS5c2dNmjRJOXPmNCsUAAAAAJAkJSYmmh0CAAAAkKZMX1Ngzpw5ZocAAAAAZ8eULgAAAADsZGH+oBSZnhSQpF27dumrr75SVFSU4uPjbfYtW7bMpKgAAAAAAAAAAMhY3MwOYMmSJapVq5YOHz6s7777Trdv39ahQ4f0008/ydfX1+zwAAAA4AwsFvM2AAAAAC6F24eUmZ4UGDlypCZMmKD//e9/8vT01KRJk3TkyBG1adNGRYoUMTs8AAAAAAAAAAAyDNOTAsePH1eTJk0kSZ6enrp+/bosFot69+6tzz//3OToAAAA4BwsJm4AAAAAXAl3DykzPSnw2GOP6dq1a5KkggULKjw8XJIUExOjGzdumBkaAAAAAAAAAAAZiukLDdepU0fr1q1ThQoV1Lp1a/Xq1Us//fST1q1bp3r16pkdHgAAAAAAAAAAGYbpSYEpU6bo1q1bkqQPP/xQWbJk0bZt29SqVSsNHDjwX58fFxenuLg4m7Is8fHy8vRMk3gBAABgAldZsQsAAACA6bh9SJnpSQE/Pz/rv93c3NS/f/9UPX/UqFEaNmyYTdng/3TUkLc7OSI8AAAAAAAAAAAyDNOTAj/++KPc3d0VHBxsU7527VolJCSoUaNGKT4/NDRUISEhNmVZjv3i8DgBAABgInr6AAAAALAbNxApMX2h4f79+yshISFJeWJiol2jBry8vJQzZ06bjamDAAAAAAAAAABIyvSkwLFjx1S2bNkk5aVLl1ZkZKQJEQEAAAAAAAAAkDGZnhTw9fXViRMnkpRHRkYqe/bsJkQEAAAA52MxcQMAAADgSiwW8zZXYHpSoEWLFnrvvfd0/Phxa1lkZKTef/99NW/e3MTIAAAAAAAAAADIWExPCowdO1bZs2dX6dKlFRgYqMDAQJUuXVqPP/64xo0bZ3Z4AAAAcAYu1NVn6tSpCggIkLe3t2rUqKGdO3emePzXX3+t0qVLy9vbWxUqVNCPP/5os79Tp06yWCw2W8OGDVMdFwAAAJBZMM44ZR5mB+Dr66tt27Zp/fr12rdvn7JmzaqKFSuqTp06ZocGAAAApMrSpUsVEhKiGTNmqEaNGpo4caKCg4N19OhR5c2bN8nx27ZtU/v27TVq1Cg1bdpUixcvVsuWLbVnzx6VL1/eelzDhg01Z84c62MvL690aQ8AAACAjMdiGIZhxolv3rypsLAwNW3aVJIUGhqquLg4634PDw8NHz5c3t7eqa478eBGB0WJR+VWvLLZIeAec37VkZz4W2ZHgHuMRLMjwN8uNX3B7BBwH79t4WaHkIRx5php5473K2LzOVW6+6V8cl/M16hRQ08++aSmTJkiSUpMTFThwoX17rvvqn///kmOb9u2ra5fv64VK1ZYy5566ilVqlRJM2bMkHR3pEBMTIyWL1/uwFbBLBM3nzQ7hDTXtmJBs0NIU9k8Te9bl+bc3Vyln+PDi7+T8T8HemUxfXKINBVz47bZIaQ5d1eZnPwRXI+/Y3YIac4/Z+q/23QlPl7O+XN6OibetHMXyOVp2rntZdpfiHnz5umzzz6zPp4yZYq2bdumvXv3au/evVqwYIGmT59uVngAAABwJiZOHzRq1Cj5+vrabKNGjUoSYnx8vHbv3q369etby9zc3FS/fn1t37492WZt377d5nhJCg4OTnL8xo0blTdvXpUqVUrdu3fXxYsXHfCiAgAAABmTC80+agrTujgsWrRIffv2tSlbvHixihUrJklauHChpk6dqt69e5sRHgAAACDp7ojWkJAQm7LkRglcuHBBCQkJ8vf3tyn39/fXkSNHkq07Ojo62eOjo6Otjxs2bKiXXnpJgYGBOn78uAYMGKBGjRpp+/btcnd3f9hmAQAAAMik0jUpMGPGDL366qvKkSOHIiMjVaFChQceW716db3zzjvpGB0AAACcl3ldbh40VVB6adeunfXfFSpUUMWKFVW8eHFt3LhR9erVMy0uAAAAwFlZXGbJX3Ok6/RBU6ZMUUxMjCQpJibGZm7WixcvWkcJSHfnX/3n3K0AAACAs8qdO7fc3d119uxZm/KzZ88qX758yT4nX758qTpekooVK6bcuXMrMjLy0YMGAAAAkOmka1IgPDxchQsXliQVKlRI4eH/v4idp6ftAgwHDhxQoUKF0jM8AAAA4KF5enqqatWqCgsLs5YlJiYqLCxMNWvWTPY5NWvWtDlektatW/fA4yXpzz//1MWLF5U/f37HBA4AAAAgUzFtoeHGjRtr8ODBunXrVpJ9N2/e1LBhw9SkSRMTIgMAAIDTcZGVwkJCQjRz5kzNmzdPhw8fVvfu3XX9+nV17txZktShQweFhoZaj+/Vq5dWr16t8ePH68iRIxo6dKh27dqlHj16SJJiY2P1wQcf6JdfftGpU6cUFhamFi1aKCgoSMHBwY57fQEAAICMxGLi5gJMW2h4wIAB+uqrr1SqVCn16NFDJUuWlCQdPXpUU6ZM0Z07dzRgwACzwgMAAABSrW3btjp//rwGDx6s6OhoVapUSatXr7YuJhwVFSU3t//vl1OrVi0tXrxYAwcO1IABA1SiRAktX75c5cuXlyS5u7vrwIEDmjdvnmJiYlSgQAG98MILGjFihKnrHAAAAABwXRbDMAyzTn7y5El1795d69at070wLBaLGjRooGnTptmsMZAaiQc3Oi5IPBK34pXNDgH3mPerjn+KTzpCCiYxEs2OAH+71PQFs0PAffy2hf/7QenMOHvStHNb/ANNOzcynombzftZTi9tKxY0O4Q0lc3TtL516cbdzUW6OT6C+DsZ/3OgVxbTJodIFzE3bpsdQppzT+WIRVd0Pf6O2SGkOf+c3maHkKZ8vJzz5/TsVfPeI/xzZjHt3PYy9dNMYGCgVq9erUuXLlkXSgsKCpKfn5+ZYQEAAAAAAAAAkCE5RRcHPz8/Va9e3ewwAAAA4KwyQU85AAAAAI7B7UPKMvZYMgAAAAAAAAAAYEVSAAAAAAAAAACATMIppg8CAAAAUsT4XwAAAAB2soj7h5QwUgAAAAAAAAAAgEyCkQIAAABwAfT0AQAAAGAnbh9SxEgBAAAAAAAAAAAyCZICAAAAAAAAAABkEiQFAAAA4PQsFotpGzKXwYMH68aNG9bHly9fNjEaAAAAPAyLiZsrICkAAAAAAH/7+OOPFRsba31ctGhRnThxwsSIAAAAAMdioWEAAAA4P3rsI50YhpHiYwAAADg/bh9SxkgBAAAAAAAAAAAyCUYKAAAAAMDfLBaLrl27Jm9vbxmGIYvFotjYWF29etXmuJw5c5oUIQAAAPBoSAoAAADABTD+F+nDMAyVLFnS5nHlypVtHlssFiUkJJgRHgAAAOxg4f4hRSQFAAAAAOBvGzZsMDsEAAAAIE2RFAAAAIDzY6UwpJO6dev+6zGXLl1Kh0gAAADwsLh9SBkLDQMAAACAHdauXas2bdqoYMGCZocCAAAAPDRGCgAAAMD50dUHJvn99981e/ZszZs3T5cvX1ajRo00f/58u54bFxenuLg4m7I78XHy8PRKi1ABAAAAuzBSAAAAAADuEx8fryVLlqh+/foqXbq09uzZoz///FNbtmzRkiVL1Lp1a7vqGTVqlHx9fW229Qunp3H0AAAAQMpICgAAAADA3959910VKFBAkyZN0osvvqg///xT//vf/2SxWOTu7p6qukJDQ3XlyhWbrf5r3dMocgAAAMA+TB8EAAAAF8D0QUgf06dPV79+/dS/f3/lyJHjkery8vKSl5ftVEEenhcfqU4AAAD8O2YfTRkjBQAAAADgbwsWLNDOnTuVP39+tW3bVitWrFBCQoLZYQEAAAAOQ1IAAAAAzs9iMW9DptK+fXutW7dOBw8eVOnSpfXOO+8oX758SkxMVEREhNnhAQAAwA4WE/9zBSQFAAAAAOAfAgMDNWzYMJ06dUoLFy5Uq1at9Nprr6lQoULq2bOn2eEBAAAAD42kAAAAAAA8gMViUXBwsL766iudPn1aH3zwgaZNm2Z2WAAAAMBDY6FhAAAAOD/XGIWLDOzatWv65ptvtGjRIhmGYXY4AAAASAGzgKaMkQIAAAAA8AA///yzOnbsqPz582vcuHF6/vnn9csvv5gdFgAAAPDQGCkAAAAAF0BXH6Sf6OhozZ07V7NmzdLVq1fVpk0bxcXFafny5SpbtqzZ4QEAAOBfcPeQMkYKAAAAAMDfmjVrplKlSunAgQOaOHGiTp8+rcmTJ5sdFgAAAOAwjBQAAAAAgL+tWrVKPXv2VPfu3VWiRAmzwwEAAAAcjpECAAAAcH4Wi3kbMpUtW7bo2rVrqlq1qmrUqKEpU6bowoULZocFAACA1LCYuLkAkgIAAAAA8LennnpKM2fO1JkzZ/TWW29pyZIlKlCggBITE7Vu3Tpdu3bN7BABAACAR2IxDMMwOwgkFRcXp1GjRik0NFReXl5mh5OpcS2cB9fCeXAtnAfXwnlwLdLYNRN7aufIbd654RSOHj2qWbNmacGCBYqJiVGDBg30ww8/PFRdEzefdHB0zqdtxYJmh5Cmsnlm/Fl43d1cpJvjI4i/k2h2CGnOK0vG7gcac+O22SGkOfdMMGLxevwds0NIc/45vc0OIU35eDnnz2lsnHlfeTvra3I/kgJO6urVq/L19dWVK1eUM2dOs8PJ1LgWzoNr4Ty4Fs6Da+E8uBZpjKQAnEBCQoL+97//afbs2SQFUkBSwPWRFMgYSAq4PpICGQNJAXOQFEhZxv4LAQAAAAAO4u7urpYtWz50QgAAAABwBhm/iwMAAAAyAOfvbQMAAADAOWSCgTaPhJECAAAAAAAAAABkEowUcFJeXl4aMmQICxU6Aa6F8+BaOA+uhfPgWjgPrkUao6sPAAAAADtx95AyFhoGAACA84u9ZN65ffzMOzcyHBYadn0sNJwxsNCw62Oh4YyBhYZdn7Muqnsj3ryvvLN5Oudrcr+M/2kGAAAAri8T3BQDAAAAcBBuH1KUsdPGQArKlSunadOmmR0G/sb1cD5cE3Px+puPawAAAAAAyIgYKYBM68cff1SuXLnMDgN/43o4H66JuXj9zcc1AAAAAABkRIwUcCIbN26UxWJRTEyM2aFkCkWLFpWvr6/ZYeBvXA/nwzUxV2pe/6FDh6pSpUppG1AmxO+As7GYuAEAAABwJRYT/3MFJAXSicViSXEbOnSo2SFmCtHR0erVq5eCgoLk7e0tf39/1a5dW9OnT9eNGzccco5nn31W7733nkPqyujS+nqQaEu99PgdwYPZ8/pbLBYtX77c3EAzMDN/BwICAjRx4sQ0PQcAAAAAAEwflE7OnDlj/ffSpUs1ePBgHT161Frm4+OjXbt2mRFapnHixAnVrl1buXLl0siRI1WhQgV5eXnp4MGD+vzzz1WwYEE1b97c7DAzDa6H8+GamIvX33xcAyfHQsMAAAAA7MTtQ8oYKZBO8uXLZ918fX1lsVhsynx8fKzH7t69W9WqVVO2bNlUq1Ytm+SBJH3//feqUqWKvL29VaxYMQ0bNkx37txJ7ya5nLffflseHh7atWuX2rRpozJlyqhYsWJq0aKFVq5cqWbNmkmSYmJi9NZbb8nf31/e3t4qX768VqxYIUm6ePGi2rdvr4IFCypbtmyqUKGCvvzyS+s5OnXqpE2bNmnSpEnWUSCnTp0yo7lOz57rYRiGhg4dqiJFisjLy0sFChRQz549rXUsWLBA1apVU44cOZQvXz698sorOnfunCTp1KlTeu655yRJjz32mCwWizp16mRGU12Gvb8jUVFRatGihXx8fJQzZ061adNGZ8+etdZzbyqbBQsWKCAgQL6+vmrXrp2uXbtmVtNcgj2vf0BAgCTpxRdflMVisT6+J6XXPDExUaNGjVJgYKCyZs2qJ554Qt988006ttD52fs7YLFY9Nlnn6lp06bKli2bypQpo+3btysyMlLPPvussmfPrlq1aun48ePWuo8fP64WLVrI399fPj4+evLJJ7V+/Xrr/meffVa///67evfubf37AQAAAABAWiAp4IQ+/PBDjR8/Xrt27ZKHh4feeOMN677NmzerQ4cO6tWrlyIiIvTZZ59p7ty5+vjjj02M2PldvHhRa9eu1TvvvKPs2bMne4zFYlFiYqIaNWqkrVu3auHChYqIiNDo0aPl7u4uSbp165aqVq2qlStXKjw8XG+++aZef/117dy5U5I0adIk1axZU926ddOZM2d05swZFS5cON3a6SrsvR7ffvutJkyYoM8++0zHjh3T8uXLVaFCBesxt2/f1ogRI7R//34tX75cp06dsn7xX7hwYX377beSpKNHj+rMmTOaNGlSmrfNVaXmd6RFixa6dOmSNm3apHXr1unEiRNq27atzbHHjx/X8uXLtWLFCq1YsUKbNm3S6NGj06MpLsne1//XX3+VJM2ZM0dnzpyxPpb+/TUfNWqU5s+frxkzZujQoUPq3bu3XnvtNW3atCltG+ci7L0G94wYMUIdOnTQvn37VLp0ab3yyit66623FBoaql27dskwDPXo0cN6fGxsrBo3bqywsDDt3btXDRs2VLNmzRQVFSVJWrZsmQoVKqThw4db/34AAAAAAJAmDKS7OXPmGL6+vknKN2zYYEgy1q9fby1buXKlIcm4efOmYRiGUa9ePWPkyJE2z1uwYIGRP3/+NI3Z1f3yyy+GJGPZsmXWslu3bhnZs2e3bn379jXWrFljuLm5GUePHrW77iZNmhjvv/++9XHdunWNXr16OTL8DMfe6zF+/HijZMmSRnx8vF31/vrrr4Yk49q1a4Zh/P/v1OXLl9OiGRmKvddk7dq1hru7uxEVFWU97tChQ4YkY+fOnYZhGMaQIUOMbNmyGVevXrUe88EHHxg1atRIvwa5GHtff8MwDEnGd999Z/P8f3vNb926ZWTLls3Ytm2bzfO6dOlitG/fPo1a5VpSew0GDhxoPW779u2GJGPWrFnWsi+//NLw9vZO8ZzlypUzJk+ebH1ctGhRY8KECQ5qEQDcfR8bMmSIcevWLbNDSTO00fVl9PYZBm3MKGij68vo7TOMzNFGOAYjBZxQxYoVrf/Onz+/JFmnRNm/f7+GDx8uHx8f63avVzqLgKaOp6en9u3bp3379qlcuXKKi4vTvn37VKhQIZUsWTLZ5yQkJGjEiBGqUKGC/Pz85OPjozVr1lh7euLhJXc9WrdurZs3b6pYsWLq1q2bvvvuO5upsnbv3q1mzZqpSJEiypEjh+rWrStJXA8HSe6aHD58WIULF7YZAVO2bFnlypVLhw8ftpYFBAQoR44c1sf58+e3vo/BPsm9/ilJ6TWPjIzUjRs31KBBA5u/H/Pnz7eZ4ga2UroG9/+t9vf3lySbkUz+/v66deuWrl69KunuSIE+ffqoTJkyypUrl3x8fHT48GHerwCkqbi4OA0bNuxf/4a4Mtro+jJ6+yTamFHQRteX0dsnZY42wjFYaNgJZcmSxfrve1MVJCYmSrr7pcKwYcP00ksvJXmet7d3+gTogoKCgmSxWGzWZ7BYLAoKCpIkZc2a1eb/D/LJJ59o0qRJmjhxoipUqKDs2bPrvffeU3x8fNoFnwHZez0KFy6so0ePav369Vq3bp3efvttffLJJ9q0aZPi4+MVHBys4OBgLVq0SHny5FFUVJSCg4O5Hg/B3mtir/vfx+7Vde99DEk54vVP6TWPjY2VJK1cuVIFCxa0Oc7Ly+uRYs8oUnsNkvtbndLf7z59+mjdunUaN26cgoKClDVrVr388su8XwEAAAAA0h0jBVxMlSpVdPToUQUFBSXZ3Ny4nA/y+OOPq0GDBpoyZYquX7/+wOMqVqyoP//8U7/99luy+7du3aoWLVrotdde0xNPPKFixYolOdbT01MJCQkOjT+jsfd6SHe/iGvWrJn++9//auPGjdq+fbsOHjyoI0eO6OLFixo9erSeeeYZlS5dOklPdE9PT0nietjB3mtSpkwZ/fHHH/rjjz+sZREREYqJiVHZsmXTI9QMKTW/E1myZEn1z3TZsmXl5eWlqKioJH87WPfkrtRcg4exdetWderUSS+++KIqVKigfPnyJVmInr8fAAAAAID0wLfILmbw4MGaP3++hg0bpkOHDunw4cNasmSJBg4caHZoTm/atGm6c+eOqlWrpqVLl+rw4cM6evSoFi5cqCNHjsjd3V1169ZVnTp11KpVK61bt04nT57UqlWrtHr1aklSiRIltG7dOm3btk2HDx/WW2+9pbNnz9qcJyAgQDt27NCpU6d04cIFekc/gD3XY+7cuZo1a5bCw8N14sQJLVy4UFmzZlXRokVVpEgReXp6avLkyTpx4oR++OEHjRgxwuYcRYsWlcVi0YoVK3T+/Hlrb2kkz55rUr9+fVWoUEGvvvqq9uzZo507d6pDhw6qW7euqlWrZnYTXJo9r7909z0mLCxM0dHRunz5sl1158iRQ3369FHv3r01b948HT9+XHv27NHkyZM1b968tGyWS7H3GjyMEiVKaNmyZdq3b5/279+vV155Jcnfh4CAAP3888/666+/dOHChUdtDgAAAAAAyTN7UYPM6N8WGr5/UdS9e/cakoyTJ09ay1avXm3UqlXLyJo1q5EzZ06jevXqxueff572gWcAp0+fNnr06GEEBgYaWbJkMXx8fIzq1asbn3zyiXH9+nXDMAzj4sWLRufOnY3HH3/c8Pb2NsqXL2+sWLHCuq9FixaGj4+PkTdvXmPgwIFGhw4djBYtWljPcfToUeOpp54ysmbNmuTawda/XY/vvvvOqFGjhpEzZ04je/bsxlNPPWWzEPfixYuNgIAAw8vLy6hZs6bxww8/GJKMvXv3Wo8ZPny4kS9fPsNisRgdO3ZM/0a6GHt+R37//XejefPmRvbs2Y0cOXIYrVu3NqKjo611DBkyxHjiiSds6p0wYYJRtGjRdGyJa7Ln9f/hhx+MoKAgw8PDw/qa2vOaJyYmGhMnTjRKlSplZMmSxciTJ48RHBxsbNq0KZ1a5xrsuQb6x2LPJ0+eTPLe88+/6SdPnjSee+45I2vWrEbhwoWNKVOmJFmYfvv27UbFihUNLy8vg49oABwhMyw2SBtdX0Zvn2HQxoyCNrq+jN4+w8gcbYRjWAzDMMxLSQAAAAAAAAAAgPTC9EEAAAAAAAAAAGQSJAUAAAAAAAAAAMgkSAoAAAAAAAAAAJBJkBQAAAAAAAAAACCTICkAAAAAAC6oU6dOslgs+s9//pNk3zvvvCOLxaJOnTpZj23ZsqXNMd988428vb01fvz4dIj20fwz/vHjx+uxxx7TrVu3khx748YN5cyZU//973/TMcLUu79N967l6NGjbY5Zvny5LBaLTZlhGJo5c6Zq1qypnDlzysfHR+XKlVOvXr0UGRmZXuE7xL12/3NztXbcEx0drV69eikoKEje3t7y9/dX7dq1NX36dN24cUOSFBAQYG1ntmzZVKFCBX3xxRcmR546//x9tKfdruL+n8ksWbIoMDBQffv2tXmvsVgsWr58uXlBOlCzZs3UsGHDZPdt3rxZFotFBw4cSOeoHl5y7yf3b0OHDtWpU6dsyvz8/FS3bl1t3rzZ7PBTtH37drm7u6tJkyYPPObLL7+Uu7u73nnnnST7Nm7cmOxrMnDgwLQMG06MpAAAAAAAuKjChQtryZIlunnzprXs1q1bWrx4sYoUKfLA533xxRd69dVXNX36dL3//vvpEapDvf7667p+/bqWLVuWZN8333yj+Ph4vfbaayZE9vC8vb01ZswYXb58+YHHGIahV155RT179lTjxo21du1aRUREaNasWfL29tZHH32UjhE7RsOGDXXmzBmbLTAw0OywUu3EiROqXLmy1q5dq5EjR2rv3r3avn27+vbtqxUrVmj9+vXWY4cPH64zZ84oPDxcr732mrp166ZVq1aZGP3DS027XcW9n8kTJ05owoQJ+uyzzzRkyBCzw0oTXbp00bp16/Tnn38m2TdnzhxVq1ZNFStWNCGyh3P/+8jEiROVM2dOm7I+ffpYj12/fr3OnDmjn3/+WQUKFFDTpk119uxZE6NP2axZs/Tuu+/q559/1unTpx94TN++ffXll18mmzSXpKNHj9q8Jv3790/LsOHEPMwOAAAAAADwcKpUqaLjx49r2bJlevXVVyVJy5YtU5EiRR74xerYsWM1ZMgQLVmyRC+++GJ6huswefPmVbNmzTR79my98sorNvtmz56tli1bys/Pz6ToHk79+vUVGRmpUaNGaezYsckes3TpUi1ZskTff/+9mjdvbi0vUqSInnrqKRmGkV7hOoyXl5fy5ctndhiP7O2335aHh4d27dql7NmzW8uLFSumFi1a2FybHDlyWNvcr18/jR07VuvWrVOjRo3SPe5HlZp2u4r7fyYLFy6s+vXra926dRozZozJkTle06ZNlSdPHs2dO9emx3hsbKy+/vprffLJJyZGl3r3v5f4+vrKYrEkeX+5cOGCJOnxxx9Xvnz5lC9fPg0YMEBLlizRjh07bN5bnUVsbKyWLl2qXbt2KTo6WnPnztWAAQNsjjl58qS2bdumb7/9Vhs2bNCyZcuS/H2U7v79zJUrVzpFDmfGSAEAAAAAcGFvvPGG5syZY308e/Zsde7cOdlj+/XrpxEjRmjFihUumxC4p0uXLvrpp5/0+++/W8tOnDihn3/+WV26dDExsofj7u6ukSNHavLkycn22pXuTg1RqlSpB35p9c+phpA+Ll68qLVr1+qdd96x+WL8fsldm8TERH377be6fPmyPD090zpMh3vYdruS8PBwbdu2zSWvjz08PDzUoUMHzZ071yaB8/XXXyshIUHt27c3Mbr0cfPmTc2fP1+SnPY6f/XVVypdurRKlSql1157TbNnz06ScJszZ46aNGkiX19fvfbaa5o1a5ZJ0cJVkBQAgAxq0KBBevPNN9P1nO3atXOJeYkBAMhIXnvtNW3ZskW///67fv/9d23dujXZqXNWrVqlsWPH6vvvv1e9evVMiNSxgoODVaBAAZuEyNy5c1W4cGGXbd+LL76oSpUqPXCqkt9++02lSpWyKXvvvffk4+MjHx8fFSpUKD3CdKgVK1ZY4/fx8VHr1q3NDinVIiMjZRhGkmuTO3dua7v69etnLe/Xr598fHzk5eWll19+WY899pi6du2a3mE/stS221Xc+5n09vZWhQoVdO7cOX3wwQdmh5Vm3njjDR0/flybNm2yls2ZM0etWrWSr6+viZGlrVq1asnHx0fZs2fXuHHjVLVqVaf92zFr1izr3/WGDRvqypUrNtcrMTFRc+fOtR7Trl07bdmyRSdPnkxSV6FChWzecy9evJg+jYDTISkAwGmcP39e3bt3V5EiRaxDNoODg7V161aHnufZZ5/Ve++959A608qZM2f0yiuvqGTJknJzc7M77ujoaE2aNEkffvihtSw9FhgcOHCgPv74Y125csUh9QEAgH+XJ08eNWnSRHPnzrX2FMydO3eS4ypWrKiAgAANGTJEsbGxJkTqWO7u7urYsaO1h2tiYqLmzZunzp07y83NdW91x4wZo3nz5unw4cN2Hf/hhx9q3759Gjx4sEte1+eee0779u2zbs6+QHRq7Ny5U/v27VO5cuUUFxdnLf/ggw+0b98+/fTTT6pRo4YmTJigoKAgEyN1rAe121Xc+5ncsWOHOnbsqM6dO6tVq1Zmh5VmSpcurVq1amn27NmS7iZ7Nm/e7JIjrlJj6dKl2rt3r7799lsFBQVp7ty5ypIli9lhJXH06FHt3LnTOmrDw8NDbdu2tRkJsG7dOl2/fl2NGzeWdDcx16BBA+s1vd/mzZtt3nMfe+yx9GkInA5rCgBwGq1atVJ8fLzmzZunYsWK6ezZswoLC8vUmeu4uDjlyZNHAwcO1IQJE+x+3hdffKFatWqpaNGiKR7zzjvvaMaMGQ+cYiC1ypcvr+LFi2vhwoV65513HFInAAD4d2+88YZ69OghSZo6dWqyxxQsWFDffPONnnvuOTVs2FCrVq1Sjhw50jNMh3vjjTc0atQo/fTTT0pMTNQff/zhsM81ZqlTp46Cg4MVGhqqTp062ewrUaKEjh49alOWJ08e5cmTR3nz5k3HKB0ne/bsLv+FeFBQkCwWS5JrU6xYMUlS1qxZbcpz586toKAgBQUF6euvv1aFChVUrVo1lS1bNt1idoTUtttV3P8zOXv2bD3xxBOaNWtWhv6SvEuXLnr33Xc1depUzZkzR8WLF1fdunXNDitNFS5cWCVKlFCJEiV0584dvfjiiwoPD5eXl5fZodmYNWuW7ty5owIFCljLDMOQl5eXpkyZIl9fX82aNUuXLl2y+Z1LTEzUgQMHNGzYMJtEeWBgIGsKQBIjBQA4iZiYGG3evFljxozRc889p6JFi6p69eoKDQ21mTM1JiZGXbt2VZ48eZQzZ049//zz2r9/v3X/0KFDValSJS1YsEABAQHy9fVVu3btdO3aNUl3e8tv2rRJkyZNksVikcVi0alTpyTdnS+yUaNG8vHxkb+/v15//XXrIkTS3REGPXv2VN++feXn56d8+fJp6NChSdrx1ltvyd/fX97e3ipfvrxWrFhh3b9lyxY988wzypo1qwoXLqyePXvq+vXrD3xdAgICNGnSJHXo0CFVQzeXLFmiZs2aPXD/2LFj9e6772rJkiU2N87ff/+9qlSpIm9vbxUrVkzDhg3TnTt3JN296W7atKlNPbdv31bevHlteik0a9ZMS5YssTtWAADw6Bo2bKj4+Hjdvn1bwcHBDzyuaNGi2rRpk6Kjo9WwYUPrZyRXde+Lq9mzZ2vOnDmqX79+ip0iXMXo0aP1v//9T9u3b7cpb9++vY4eParvv//epMiQnMcff1wNGjTQlClTUvxsn5zChQurbdu2Cg0NTaPo0s6jtNtVuLm5acCAARo4cKBu3rxpdjhppk2bNnJzc9PixYs1f/58vfHGGy6/HkRqvPzyy/Lw8NC0adPMDsXGnTt3NH/+fI0fP96md//+/ftVoEABffnll7p48aK+//57LVmyxOaYvXv36vLly1q7dq3ZzYCTIikAwCncm89u+fLlKQ4xbd26tc6dO6dVq1Zp9+7dqlKliurVq6dLly5Zjzl+/LiWL1+uFStWaMWKFdq0aZNGjx4tSZo0aZJq1qypbt266cyZMzpz5owKFy6smJgYPf/886pcubJ27dql1atX6+zZs2rTpo3N+efNm6fs2bNrx44dGjt2rIYPH65169ZJupuJb9SokbZu3aqFCxcqIiJCo0ePlru7uzWuhg0bqlWrVjpw4ICWLl2qLVu2WHv1OcqlS5cUERGhatWqJbv/QQsMbt68WR06dFCvXr0UERGhzz77THPnztXHH38sSeratatWr16tM2fOWJ+zYsUK3bhxQ23btrWWVa9eXTt37nTJocIAALgqd3d3HT58WBEREdbPHg9SuHBhbdy4UefOnVNwcLCuXr2aTlE+mitXrth84bFv3z798ccf6tKli5YtW6bvvvsuw/TkrVChgl599dUkU+m0a9dOL7/8stq1a6fhw4drx44dOnXqlDZt2qSlS5f+67VH2pk2bZru3LmjatWqaenSpTp8+LCOHj2qhQsX6siRIylem169eul///ufdu3alY4RO8ajtNtVtG7dWu7u7jajsE6ePJnk/ciVEyM+Pj7W5NSZM2eSjFLK6CwWi3r27KnRo0frxo0bZodjtWLFCl2+fFldunRR+fLlbbZWrVpp1qxZWrBggR5//HG1adPGZv8TTzyhxo0bs+AwHswAACfxzTffGI899pjh7e1t1KpVywgNDTX2799v3b9582YjZ86cxq1bt2yeV7x4ceOzzz4zDMMwhgwZYmTLls24evWqdf8HH3xg1KhRw/q4bt26Rq9evWzqGDFihPHCCy/YlP3xxx+GJOPo0aPW5z399NM2xzz55JNGv379DMMwjDVr1hhubm7W4/+pS5cuxptvvmlTtnnzZsPNzc24efPmA1+XlOJOzt69ew1JRlRUlE15x44dDU9PT0OSERYWluR59erVM0aOHGlTtmDBAiN//vzWx2XLljXGjBljfdysWTOjU6dONs/Zv3+/Ick4derUv8YKAAAeXseOHY0WLVo8cH+LFi2Mjh07PvDYP//80yhRooTx1FNPGVeuXEm7QB2gY8eOhqQkW5cuXYwbN24Yvr6+hp+fX5LPic7s/muS3PU5efKk9bPb/RISEowZM2YYNWrUMLJnz254enoaxYoVM7p162ZERESkU/SO8W8/w67m9OnTRo8ePYzAwEAjS5Ysho+Pj1G9enXjk08+Ma5fv24YhmEULVrUmDBhQpLnBgcHG40aNUrniB/OP6+bPe12FQ/6mRw1apSRJ08eIzY2Ntn3IknG5s2b0z9gB9q2bZshyWjcuLHZoTjEnDlzDF9f3yTlJ0+eNCQZe/futSm/fv268dhjj9nc75qtadOmD7weO3bsMCQZFovFePvtt5M9ZunSpYanp6dx/vx5Y8OGDYYk4/Lly2kYMVyJxTAMI33TEADwYLdu3dLmzZv1yy+/aNWqVdq5c6e++OILderUSVOnTlXPnj2TzE158+ZN9enTR2PGjNHQoUP19ddf69ChQ9b9EyZM0OTJk3XixAlJd6cBqlSpkiZOnGg9pnXr1vr+++/l6elpU/f169f1448/qlGjRnr22WdVrlw5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",
143
+ "text/plain": [
144
+ "<Figure size 1600x700 with 4 Axes>"
145
+ ]
146
+ },
147
+ "metadata": {},
148
+ "output_type": "display_data"
149
+ }
150
+ ],
151
+ "source": [
152
+ "import seaborn as sns\n",
153
+ "\n",
154
+ "# ================= 绘图参数 =================\n",
155
+ "TARGET_LAYER = top_layer # 使用刚才筛出来的层\n",
156
+ "TARGET_HEAD = top_head # 使用刚才筛出来的头\n",
157
+ "# 或者手动指定,比如您之前提到的:\n",
158
+ "# TARGET_LAYER = 7\n",
159
+ "# TARGET_HEAD = 4 \n",
160
+ "# ===========================================\n",
161
+ "\n",
162
+ "def plot_cross_modal_heatmap(layer, head):\n",
163
+ " # 1. 获取数据\n",
164
+ " attn_en, ids_en = get_attention_maps(text_en_1, text_en_2)\n",
165
+ " attn_bio, ids_bio = get_attention_maps(seq_bio_1, seq_bio_2)\n",
166
+ " \n",
167
+ " tokens_en = tokenizer.convert_ids_to_tokens(ids_en)\n",
168
+ " # 生物序列分词后可能是字符级,也可能合并,为了展示清晰,我们只看前20个token\n",
169
+ " tokens_bio = tokenizer.convert_ids_to_tokens(ids_bio)\n",
170
+ " \n",
171
+ " # 2. 提取特定 Head 的矩阵\n",
172
+ " # 英语\n",
173
+ " mat_en = attn_en[layer, head].numpy()\n",
174
+ " # 为了视觉美观,只取 Sent2 关注 Sent1 的部分 (左下角)\n",
175
+ " len_en_1 = len(tokenizer(text_en_1)['input_ids'])\n",
176
+ " # 切片: Rows=Sent2, Cols=Sent1\n",
177
+ " viz_en = mat_en[len_en_1:, :len_en_1] \n",
178
+ " x_labels_en = tokens_en[:len_en_1]\n",
179
+ " y_labels_en = tokens_en[len_en_1:]\n",
180
+ "\n",
181
+ " # 蛋白质\n",
182
+ " mat_bio = attn_bio[layer, head].numpy()\n",
183
+ " len_bio_1 = len(tokenizer(seq_bio_1)['input_ids'])\n",
184
+ " viz_bio = mat_bio[len_bio_1:, :len_bio_1]\n",
185
+ " x_labels_bio = tokens_bio[:len_bio_1]\n",
186
+ " y_labels_bio = tokens_bio[len_bio_1:]\n",
187
+ "\n",
188
+ " # 3. 绘图\n",
189
+ " fig, axes = plt.subplots(1, 2, figsize=(16, 7))\n",
190
+ " \n",
191
+ " # 左图:Language\n",
192
+ " sns.heatmap(viz_en, xticklabels=x_labels_en, yticklabels=y_labels_en, \n",
193
+ " cmap=\"Reds\", square=True, cbar=True, ax=axes[0])\n",
194
+ " axes[0].set_title(f\"Language: Inversion Detection\\n(Layer {layer}, Head {head})\", fontsize=14, fontweight='bold')\n",
195
+ " axes[0].set_xlabel(\"Sentence 1 (Key)\")\n",
196
+ " axes[0].set_ylabel(\"Sentence 2 (Query)\")\n",
197
+ " \n",
198
+ " # 右图:Protein\n",
199
+ " # 如果序列太长,只截取前 15x15 展示细节\n",
200
+ " limit = 15\n",
201
+ " sns.heatmap(viz_bio[:limit, :limit], \n",
202
+ " xticklabels=x_labels_bio[:limit], yticklabels=y_labels_bio[:limit], \n",
203
+ " cmap=\"Blues\", square=True, cbar=True, ax=axes[1])\n",
204
+ " axes[1].set_title(f\"Protein: Mutation Awareness\\n(Layer {layer}, Head {head})\", fontsize=14, fontweight='bold')\n",
205
+ " axes[1].set_xlabel(\"Sequence 1 (Key)\")\n",
206
+ " axes[1].set_ylabel(\"Sequence 2 (Query)\")\n",
207
+ "\n",
208
+ " plt.tight_layout()\n",
209
+ " plt.savefig(\"explain2.png\")\n",
210
+ "\n",
211
+ " plt.show() # plt.savefig(\"cross_modal_attention.pdf\")\n",
212
+ "\n",
213
+ "print(f\">>> Visualizing Layer {TARGET_LAYER}, Head {TARGET_HEAD}...\")\n",
214
+ "plot_cross_modal_heatmap(TARGET_LAYER, TARGET_HEAD)"
215
+ ]
216
+ },
217
+ {
218
+ "cell_type": "code",
219
+ "execution_count": null,
220
+ "id": "0fd1ffcb-c506-4bce-b2f4-74757359e2a0",
221
+ "metadata": {},
222
+ "outputs": [],
223
+ "source": []
224
+ }
225
+ ],
226
+ "metadata": {
227
+ "kernelspec": {
228
+ "display_name": "Python 3 (ipykernel)",
229
+ "language": "python",
230
+ "name": "python3"
231
+ },
232
+ "language_info": {
233
+ "codemirror_mode": {
234
+ "name": "ipython",
235
+ "version": 3
236
+ },
237
+ "file_extension": ".py",
238
+ "mimetype": "text/x-python",
239
+ "name": "python",
240
+ "nbconvert_exporter": "python",
241
+ "pygments_lexer": "ipython3",
242
+ "version": "3.12.3"
243
+ }
244
+ },
245
+ "nbformat": 4,
246
+ "nbformat_minor": 5
247
+ }
2-gpt_ft_test_explain/.ipynb_checkpoints/4-explain_t_sne-checkpoint.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
2-gpt_ft_test_explain/.ipynb_checkpoints/gpt2_ft_en_test_protein_en-checkpoint.jsonl ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"seed": 0, "lang": "en", "protein_pair_short": {"accuracy": 0.7746666666666666, "f1": 0.7146475306036302}, "protein_pair_full": {"accuracy": 0.751, "f1": 0.674934725848564}}
2
+ {"seed": 1, "lang": "en", "protein_pair_short": {"accuracy": 0.5105, "f1": 0.56}, "protein_pair_full": {"accuracy": 0.386, "f1": 0.39705400981996725}}
3
+ {"seed": 2, "lang": "en", "protein_pair_short": {"accuracy": 0.6345, "f1": 0.6914309835373575}, "protein_pair_full": {"accuracy": 0.5386666666666666, "f1": 0.5754601226993865}}
4
+ {"seed": 3, "lang": "en", "protein_pair_short": {"accuracy": 0.5373333333333333, "f1": 0.5924838520258368}, "protein_pair_full": {"accuracy": 0.4165, "f1": 0.44944173612203175}}
5
+ {"seed": 4, "lang": "en", "protein_pair_short": {"accuracy": 0.5491666666666667, "f1": 0.6423376966812112}, "protein_pair_full": {"accuracy": 0.38266666666666665, "f1": 0.4790436005625879}}
6
+ {"seed": 5, "lang": "en", "protein_pair_short": {"accuracy": 0.548, "f1": 0.6266519823788547}, "protein_pair_full": {"accuracy": 0.44483333333333336, "f1": 0.5462471052990056}}
7
+ {"seed": 6, "lang": "en", "protein_pair_short": {"accuracy": 0.5866666666666667, "f1": 0.5766473199044042}, "protein_pair_full": {"accuracy": 0.43833333333333335, "f1": 0.40269408011343494}}
8
+ {"seed": 7, "lang": "en", "protein_pair_short": {"accuracy": 0.7748333333333334, "f1": 0.6990421029182446}, "protein_pair_full": {"accuracy": 0.7526666666666667, "f1": 0.6590073529411765}}
9
+ {"seed": 8, "lang": "en", "protein_pair_short": {"accuracy": 0.4835, "f1": 0.6092548228470559}, "protein_pair_full": {"accuracy": 0.37666666666666665, "f1": 0.5030560722827532}}
10
+ {"seed": 9, "lang": "en", "protein_pair_short": {"accuracy": 0.7771666666666667, "f1": 0.7102925243770314}, "protein_pair_full": {"accuracy": 0.7543333333333333, "f1": 0.6709821428571429}}
11
+ {"seed": 10, "lang": "en", "protein_pair_short": {"accuracy": 0.7455, "f1": 0.6611936986909253}, "protein_pair_full": {"accuracy": 0.7186666666666667, "f1": 0.6117755289788408}}
12
+ {"seed": 11, "lang": "en", "protein_pair_short": {"accuracy": 0.7505, "f1": 0.6672593909757724}, "protein_pair_full": {"accuracy": 0.7311666666666666, "f1": 0.6319872233629934}}
13
+ {"seed": 12, "lang": "en", "protein_pair_short": {"accuracy": 0.7673333333333333, "f1": 0.6988783433994823}, "protein_pair_full": {"accuracy": 0.7365, "f1": 0.6449584549741747}}
14
+ {"seed": 13, "lang": "en", "protein_pair_short": {"accuracy": 0.7983333333333333, "f1": 0.7433177768349597}, "protein_pair_full": {"accuracy": 0.7748333333333334, "f1": 0.7048284902774743}}
15
+ {"seed": 14, "lang": "en", "protein_pair_short": {"accuracy": 0.641, "f1": 0.6420737786640079}, "protein_pair_full": {"accuracy": 0.5526666666666666, "f1": 0.54631507775524}}
16
+ {"seed": 15, "lang": "en", "protein_pair_short": {"accuracy": 0.7635, "f1": 0.6910516002612671}, "protein_pair_full": {"accuracy": 0.7408333333333333, "f1": 0.6511106125196321}}
17
+ {"seed": 16, "lang": "en", "protein_pair_short": {"accuracy": 0.5053333333333333, "f1": 0.6475896461648064}, "protein_pair_full": {"accuracy": 0.44383333333333336, "f1": 0.596346921495101}}
18
+ {"seed": 17, "lang": "en", "protein_pair_short": {"accuracy": 0.7461666666666666, "f1": 0.6573678290213724}, "protein_pair_full": {"accuracy": 0.7288333333333333, "f1": 0.6252015664593412}}
19
+ {"seed": 18, "lang": "en", "protein_pair_short": {"accuracy": 0.7861666666666667, "f1": 0.7263808914480699}, "protein_pair_full": {"accuracy": 0.7483333333333333, "f1": 0.6611310592459605}}
20
+ {"seed": 19, "lang": "en", "protein_pair_short": {"accuracy": 0.774, "f1": 0.699468085106383}, "protein_pair_full": {"accuracy": 0.7293333333333333, "f1": 0.6169811320754717}}
21
+ {"seed": 20, "lang": "en", "protein_pair_short": {"accuracy": 0.8013333333333333, "f1": 0.7570322054626988}, "protein_pair_full": {"accuracy": 0.7626666666666667, "f1": 0.6942893945899528}}
22
+ {"seed": 21, "lang": "en", "protein_pair_short": {"accuracy": 0.6788333333333333, "f1": 0.5335269910433309}, "protein_pair_full": {"accuracy": 0.6718333333333333, "f1": 0.5182285294837289}}
23
+ {"seed": 22, "lang": "en", "protein_pair_short": {"accuracy": 0.6921666666666667, "f1": 0.5385960529602798}, "protein_pair_full": {"accuracy": 0.6493333333333333, "f1": 0.43803418803418803}}
24
+ {"seed": 23, "lang": "en", "protein_pair_short": {"accuracy": 0.6153333333333333, "f1": 0.6772930648769575}, "protein_pair_full": {"accuracy": 0.5303333333333333, "f1": 0.6255647090087696}}
25
+ {"seed": 24, "lang": "en", "protein_pair_short": {"accuracy": 0.7265, "f1": 0.6196987253765933}, "protein_pair_full": {"accuracy": 0.7265, "f1": 0.6196987253765933}}
26
+ {"seed": 25, "lang": "en", "protein_pair_short": {"accuracy": 0.7646666666666667, "f1": 0.687333923826395}, "protein_pair_full": {"accuracy": 0.7605, "f1": 0.6798841612831366}}
27
+ {"seed": 26, "lang": "en", "protein_pair_short": {"accuracy": 0.7895, "f1": 0.7356081222524598}, "protein_pair_full": {"accuracy": 0.7618333333333334, "f1": 0.6900889178052483}}
28
+ {"seed": 27, "lang": "en", "protein_pair_short": {"accuracy": 0.7425, "f1": 0.6596166556510245}, "protein_pair_full": {"accuracy": 0.718, "f1": 0.6149294492489759}}
29
+ {"seed": 28, "lang": "en", "protein_pair_short": {"accuracy": 0.526, "f1": 0.6501845018450184}, "protein_pair_full": {"accuracy": 0.4091666666666667, "f1": 0.5471963213692681}}
30
+ {"seed": 29, "lang": "en", "protein_pair_short": {"accuracy": 0.728, "f1": 0.6243093922651933}, "protein_pair_full": {"accuracy": 0.6775, "f1": 0.5211581291759465}}
31
+ {"seed": 30, "lang": "en", "protein_pair_short": {"accuracy": 0.6446666666666667, "f1": 0.7004776622646811}, "protein_pair_full": {"accuracy": 0.48733333333333334, "f1": 0.5493700556694989}}
32
+ {"seed": 31, "lang": "en", "protein_pair_short": {"accuracy": 0.752, "f1": 0.6683013820775747}, "protein_pair_full": {"accuracy": 0.7351666666666666, "f1": 0.6374629249372575}}
33
+ {"seed": 32, "lang": "en", "protein_pair_short": {"accuracy": 0.7293333333333333, "f1": 0.6343989194056732}, "protein_pair_full": {"accuracy": 0.6761666666666667, "f1": 0.5285124969667556}}
34
+ {"seed": 33, "lang": "en", "protein_pair_short": {"accuracy": 0.5528333333333333, "f1": 0.5940384324406113}, "protein_pair_full": {"accuracy": 0.396, "f1": 0.43691733996270976}}
35
+ {"seed": 34, "lang": "en", "protein_pair_short": {"accuracy": 0.7863333333333333, "f1": 0.729535864978903}, "protein_pair_full": {"accuracy": 0.7441666666666666, "f1": 0.6574425351484043}}
36
+ {"seed": 35, "lang": "en", "protein_pair_short": {"accuracy": 0.707, "f1": 0.5843971631205673}, "protein_pair_full": {"accuracy": 0.6763333333333333, "f1": 0.5200197726149284}}
37
+ {"seed": 36, "lang": "en", "protein_pair_short": {"accuracy": 0.7396666666666667, "f1": 0.7456854444806252}, "protein_pair_full": {"accuracy": 0.6278333333333334, "f1": 0.6084516920918814}}
38
+ {"seed": 37, "lang": "en", "protein_pair_short": {"accuracy": 0.7511666666666666, "f1": 0.6770495349340255}, "protein_pair_full": {"accuracy": 0.743, "f1": 0.6624343257443083}}
39
+ {"seed": 38, "lang": "en", "protein_pair_short": {"accuracy": 0.7161666666666666, "f1": 0.6103866392129947}, "protein_pair_full": {"accuracy": 0.692, "f1": 0.5627070515854236}}
40
+ {"seed": 39, "lang": "en", "protein_pair_short": {"accuracy": 0.505, "f1": 0.6319702602230484}, "protein_pair_full": {"accuracy": 0.3988333333333333, "f1": 0.5363157218151433}}
41
+ {"seed": 40, "lang": "en", "protein_pair_short": {"accuracy": 0.6445, "f1": 0.6139366515837104}, "protein_pair_full": {"accuracy": 0.507, "f1": 0.4004864207539522}}
42
+ {"seed": 41, "lang": "en", "protein_pair_short": {"accuracy": 0.7505, "f1": 0.6697551290536069}, "protein_pair_full": {"accuracy": 0.7443333333333333, "f1": 0.6585040071237755}}
43
+ {"seed": 42, "lang": "en", "protein_pair_short": {"accuracy": 0.7566666666666667, "f1": 0.6764184397163121}, "protein_pair_full": {"accuracy": 0.7298333333333333, "f1": 0.6274419673638244}}
44
+ {"seed": 43, "lang": "en", "protein_pair_short": {"accuracy": 0.7683333333333333, "f1": 0.7045068027210885}, "protein_pair_full": {"accuracy": 0.7298333333333333, "f1": 0.6371166330870831}}
45
+ {"seed": 44, "lang": "en", "protein_pair_short": {"accuracy": 0.752, "f1": 0.6745406824146981}, "protein_pair_full": {"accuracy": 0.7321666666666666, "f1": 0.6389575376319928}}
46
+ {"seed": 45, "lang": "en", "protein_pair_short": {"accuracy": 0.78, "f1": 0.7172236503856041}, "protein_pair_full": {"accuracy": 0.7466666666666667, "f1": 0.6599552572706935}}
47
+ {"seed": 46, "lang": "en", "protein_pair_short": {"accuracy": 0.7301666666666666, "f1": 0.619863817797605}, "protein_pair_full": {"accuracy": 0.7123333333333334, "f1": 0.583695127834057}}
48
+ {"seed": 47, "lang": "en", "protein_pair_short": {"accuracy": 0.48583333333333334, "f1": 0.631289590056173}, "protein_pair_full": {"accuracy": 0.38033333333333336, "f1": 0.5263694267515924}}
49
+ {"seed": 48, "lang": "en", "protein_pair_short": {"accuracy": 0.7503333333333333, "f1": 0.6763180639585133}, "protein_pair_full": {"accuracy": 0.7036666666666667, "f1": 0.5908881730326737}}
50
+ {"seed": 49, "lang": "en", "protein_pair_short": {"accuracy": 0.539, "f1": 0.6577579806978471}, "protein_pair_full": {"accuracy": 0.43216666666666664, "f1": 0.5642665302468346}}
51
+ {"seed": 50, "lang": "en", "protein_pair_short": {"accuracy": 0.7391666666666666, "f1": 0.6472842010367366}, "protein_pair_full": {"accuracy": 0.6705, "f1": 0.5088198757763975}}
52
+ {"seed": 51, "lang": "en", "protein_pair_short": {"accuracy": 0.7815, "f1": 0.719572192513369}, "protein_pair_full": {"accuracy": 0.7563333333333333, "f1": 0.6769774635439681}}
53
+ {"seed": 52, "lang": "en", "protein_pair_short": {"accuracy": 0.7666666666666667, "f1": 0.6987951807228916}, "protein_pair_full": {"accuracy": 0.7543333333333333, "f1": 0.6773204903677759}}
54
+ {"seed": 53, "lang": "en", "protein_pair_short": {"accuracy": 0.6168333333333333, "f1": 0.6230529595015576}, "protein_pair_full": {"accuracy": 0.4545, "f1": 0.4162653825575174}}
55
+ {"seed": 54, "lang": "en", "protein_pair_short": {"accuracy": 0.5821666666666667, "f1": 0.6085870413739266}, "protein_pair_full": {"accuracy": 0.4176666666666667, "f1": 0.4037542662116041}}
56
+ {"seed": 55, "lang": "en", "protein_pair_short": {"accuracy": 0.717, "f1": 0.6052998605299861}, "protein_pair_full": {"accuracy": 0.721, "f1": 0.6130374479889042}}
57
+ {"seed": 56, "lang": "en", "protein_pair_short": {"accuracy": 0.8403333333333334, "f1": 0.8182163187855788}, "protein_pair_full": {"accuracy": 0.7765, "f1": 0.7250358827147837}}
58
+ {"seed": 57, "lang": "en", "protein_pair_short": {"accuracy": 0.7395, "f1": 0.6539738764666815}, "protein_pair_full": {"accuracy": 0.7315, "f1": 0.6391937290033595}}
59
+ {"seed": 58, "lang": "en", "protein_pair_short": {"accuracy": 0.506, "f1": 0.5806451612903226}, "protein_pair_full": {"accuracy": 0.3665, "f1": 0.40525739320920046}}
60
+ {"seed": 59, "lang": "en", "protein_pair_short": {"accuracy": 0.7146666666666667, "f1": 0.599250936329588}, "protein_pair_full": {"accuracy": 0.668, "f1": 0.5007518796992482}}
61
+ {"seed": 60, "lang": "en", "protein_pair_short": {"accuracy": 0.502, "f1": 0.6556016597510373}, "protein_pair_full": {"accuracy": 0.47683333333333333, "f1": 0.637738026543566}}
62
+ {"seed": 61, "lang": "en", "protein_pair_short": {"accuracy": 0.768, "f1": 0.7005163511187608}, "protein_pair_full": {"accuracy": 0.7326666666666667, "f1": 0.6380866425992779}}
63
+ {"seed": 62, "lang": "en", "protein_pair_short": {"accuracy": 0.5438333333333333, "f1": 0.6735837805605247}, "protein_pair_full": {"accuracy": 0.4745, "f1": 0.6207145434861061}}
64
+ {"seed": 63, "lang": "en", "protein_pair_short": {"accuracy": 0.7405, "f1": 0.6466984343090538}, "protein_pair_full": {"accuracy": 0.6436666666666667, "f1": 0.4408995815899582}}
65
+ {"seed": 64, "lang": "en", "protein_pair_short": {"accuracy": 0.7463333333333333, "f1": 0.6671041119860017}, "protein_pair_full": {"accuracy": 0.7316666666666667, "f1": 0.6409455842997324}}
66
+ {"seed": 65, "lang": "en", "protein_pair_short": {"accuracy": 0.7391666666666666, "f1": 0.6494960806270996}, "protein_pair_full": {"accuracy": 0.7251666666666666, "f1": 0.6232579392277816}}
67
+ {"seed": 66, "lang": "en", "protein_pair_short": {"accuracy": 0.721, "f1": 0.6158788435061955}, "protein_pair_full": {"accuracy": 0.636, "f1": 0.43213728549141966}}
68
+ {"seed": 67, "lang": "en", "protein_pair_short": {"accuracy": 0.797, "f1": 0.7381771281169389}, "protein_pair_full": {"accuracy": 0.7703333333333333, "f1": 0.6932324131789849}}
69
+ {"seed": 68, "lang": "en", "protein_pair_short": {"accuracy": 0.768, "f1": 0.7045840407470289}, "protein_pair_full": {"accuracy": 0.7558333333333334, "f1": 0.6836536385229972}}
70
+ {"seed": 69, "lang": "en", "protein_pair_short": {"accuracy": 0.5016666666666667, "f1": 0.5535980889817856}, "protein_pair_full": {"accuracy": 0.36766666666666664, "f1": 0.40588787973692453}}
71
+ {"seed": 70, "lang": "en", "protein_pair_short": {"accuracy": 0.7738333333333334, "f1": 0.7009036808463742}, "protein_pair_full": {"accuracy": 0.7505, "f1": 0.6596953853148443}}
72
+ {"seed": 71, "lang": "en", "protein_pair_short": {"accuracy": 0.7498333333333334, "f1": 0.6644310306282137}, "protein_pair_full": {"accuracy": 0.7241666666666666, "f1": 0.6166319203150336}}
73
+ {"seed": 72, "lang": "en", "protein_pair_short": {"accuracy": 0.7773333333333333, "f1": 0.7157446808510638}, "protein_pair_full": {"accuracy": 0.7636666666666667, "f1": 0.6929406669553919}}
74
+ {"seed": 73, "lang": "en", "protein_pair_short": {"accuracy": 0.7735, "f1": 0.7093048128342246}, "protein_pair_full": {"accuracy": 0.7621666666666667, "f1": 0.6897151554685801}}
75
+ {"seed": 74, "lang": "en", "protein_pair_short": {"accuracy": 0.7371666666666666, "f1": 0.6485402273233787}, "protein_pair_full": {"accuracy": 0.7195, "f1": 0.6156656770952272}}
76
+ {"seed": 75, "lang": "en", "protein_pair_short": {"accuracy": 0.7701666666666667, "f1": 0.6948439920336358}, "protein_pair_full": {"accuracy": 0.7753333333333333, "f1": 0.7033450704225352}}
77
+ {"seed": 76, "lang": "en", "protein_pair_short": {"accuracy": 0.6243333333333333, "f1": 0.6441427218187559}, "protein_pair_full": {"accuracy": 0.48633333333333334, "f1": 0.47886371322286103}}
78
+ {"seed": 77, "lang": "en", "protein_pair_short": {"accuracy": 0.8108333333333333, "f1": 0.766508948775972}, "protein_pair_full": {"accuracy": 0.7625, "f1": 0.6879789796365229}}
79
+ {"seed": 78, "lang": "en", "protein_pair_short": {"accuracy": 0.7628333333333334, "f1": 0.6909880564603692}, "protein_pair_full": {"accuracy": 0.7501666666666666, "f1": 0.6687292817679558}}
80
+ {"seed": 79, "lang": "en", "protein_pair_short": {"accuracy": 0.7205, "f1": 0.60790273556231}, "protein_pair_full": {"accuracy": 0.708, "f1": 0.5832540437678402}}
81
+ {"seed": 80, "lang": "en", "protein_pair_short": {"accuracy": 0.7361666666666666, "f1": 0.6479875472537248}, "protein_pair_full": {"accuracy": 0.6493333333333333, "f1": 0.47055863110216406}}
82
+ {"seed": 81, "lang": "en", "protein_pair_short": {"accuracy": 0.665, "f1": 0.5533333333333333}, "protein_pair_full": {"accuracy": 0.6141666666666666, "f1": 0.42797133679268595}}
83
+ {"seed": 82, "lang": "en", "protein_pair_short": {"accuracy": 0.6908333333333333, "f1": 0.718470177568675}, "protein_pair_full": {"accuracy": 0.5576666666666666, "f1": 0.5797973400886637}}
84
+ {"seed": 83, "lang": "en", "protein_pair_short": {"accuracy": 0.5973333333333334, "f1": 0.6112005149662053}, "protein_pair_full": {"accuracy": 0.4295, "f1": 0.4220834036805673}}
85
+ {"seed": 84, "lang": "en", "protein_pair_short": {"accuracy": 0.5018333333333334, "f1": 0.6592955659409552}, "protein_pair_full": {"accuracy": 0.44383333333333336, "f1": 0.6072731552312581}}
86
+ {"seed": 85, "lang": "en", "protein_pair_short": {"accuracy": 0.751, "f1": 0.6691762621789193}, "protein_pair_full": {"accuracy": 0.7066666666666667, "f1": 0.5856873822975518}}
87
+ {"seed": 86, "lang": "en", "protein_pair_short": {"accuracy": 0.7391666666666666, "f1": 0.6504355595264686}, "protein_pair_full": {"accuracy": 0.7313333333333333, "f1": 0.6351290176550475}}
88
+ {"seed": 87, "lang": "en", "protein_pair_short": {"accuracy": 0.5976666666666667, "f1": 0.6369924812030076}, "protein_pair_full": {"accuracy": 0.4746666666666667, "f1": 0.5500999143591208}}
89
+ {"seed": 88, "lang": "en", "protein_pair_short": {"accuracy": 0.565, "f1": 0.6360847741215839}, "protein_pair_full": {"accuracy": 0.4055, "f1": 0.4909376337947767}}
90
+ {"seed": 89, "lang": "en", "protein_pair_short": {"accuracy": 0.7351666666666666, "f1": 0.6472807991120977}, "protein_pair_full": {"accuracy": 0.726, "f1": 0.6302294197031039}}
91
+ {"seed": 90, "lang": "en", "protein_pair_short": {"accuracy": 0.7563333333333333, "f1": 0.6786813186813186}, "protein_pair_full": {"accuracy": 0.7393333333333333, "f1": 0.648064806480648}}
92
+ {"seed": 91, "lang": "en", "protein_pair_short": {"accuracy": 0.5415, "f1": 0.6568541848571785}, "protein_pair_full": {"accuracy": 0.43066666666666664, "f1": 0.5611510791366906}}
93
+ {"seed": 92, "lang": "en", "protein_pair_short": {"accuracy": 0.7908333333333334, "f1": 0.7332624867162593}, "protein_pair_full": {"accuracy": 0.7671666666666667, "f1": 0.6939759036144578}}
94
+ {"seed": 93, "lang": "en", "protein_pair_short": {"accuracy": 0.7833333333333333, "f1": 0.7218656397090286}, "protein_pair_full": {"accuracy": 0.7473333333333333, "f1": 0.6599371915657245}}
95
+ {"seed": 94, "lang": "en", "protein_pair_short": {"accuracy": 0.488, "f1": 0.5774415405777167}, "protein_pair_full": {"accuracy": 0.37416666666666665, "f1": 0.45978995827938424}}
96
+ {"seed": 95, "lang": "en", "protein_pair_short": {"accuracy": 0.7558333333333334, "f1": 0.667875765132623}, "protein_pair_full": {"accuracy": 0.6956666666666667, "f1": 0.5486900642609985}}
97
+ {"seed": 96, "lang": "en", "protein_pair_short": {"accuracy": 0.6685, "f1": 0.7077149155033063}, "protein_pair_full": {"accuracy": 0.49683333333333335, "f1": 0.5463561232156273}}
98
+ {"seed": 97, "lang": "en", "protein_pair_short": {"accuracy": 0.7888333333333334, "f1": 0.7336556653352954}, "protein_pair_full": {"accuracy": 0.764, "f1": 0.6925749023013461}}
99
+ {"seed": 98, "lang": "en", "protein_pair_short": {"accuracy": 0.788, "f1": 0.7329974811083123}, "protein_pair_full": {"accuracy": 0.7458333333333333, "f1": 0.6614872364039955}}
100
+ {"seed": 99, "lang": "en", "protein_pair_short": {"accuracy": 0.7558333333333334, "f1": 0.6714509979816102}, "protein_pair_full": {"accuracy": 0.7333333333333333, "f1": 0.6299722479185939}}
2-gpt_ft_test_explain/1-gpt2_ft_en_test_protein_confusion.ipynb ADDED
@@ -0,0 +1,534 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "b87144f0-f446-4280-9387-e306169549a4",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "# import os\n",
11
+ "\n",
12
+ "# # 设置环境变量\n",
13
+ "# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'\n",
14
+ "\n",
15
+ "# # 打印环境变量以确认设置成功\n",
16
+ "# print(os.environ.get('HF_ENDPOINT'))\n",
17
+ "\n",
18
+ "import subprocess\n",
19
+ "import os\n",
20
+ "\n",
21
+ "result = subprocess.run('bash -c \"source /etc/network_turbo && env | grep proxy\"', shell=True, capture_output=True, text=True)\n",
22
+ "output = result.stdout\n",
23
+ "for line in output.splitlines():\n",
24
+ " if '=' in line:\n",
25
+ " var, value = line.split('=', 1)\n",
26
+ " os.environ[var] = value"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": 2,
32
+ "id": "bc7e500f-4920-4bec-9ff5-ffdcbf3c10d6",
33
+ "metadata": {},
34
+ "outputs": [
35
+ {
36
+ "name": "stdout",
37
+ "output_type": "stream",
38
+ "text": [
39
+ "Initializing model: gpt2...\n"
40
+ ]
41
+ },
42
+ {
43
+ "name": "stderr",
44
+ "output_type": "stream",
45
+ "text": [
46
+ "Some weights of GPT2ForSequenceClassification were not initialized from the model checkpoint at gpt2 and are newly initialized: ['score.weight']\n",
47
+ "You should probably TRAIN this model on a down-stream task to be able to use it for predictions and inference.\n"
48
+ ]
49
+ },
50
+ {
51
+ "name": "stdout",
52
+ "output_type": "stream",
53
+ "text": [
54
+ "Loading PAWS-X (en) dataset...\n"
55
+ ]
56
+ },
57
+ {
58
+ "name": "stderr",
59
+ "output_type": "stream",
60
+ "text": [
61
+ "/tmp/ipykernel_46687/771796766.py:97: FutureWarning: `tokenizer` is deprecated and will be removed in version 5.0.0 for `Trainer.__init__`. Use `processing_class` instead.\n",
62
+ " trainer = Trainer(\n"
63
+ ]
64
+ },
65
+ {
66
+ "name": "stdout",
67
+ "output_type": "stream",
68
+ "text": [
69
+ "\n",
70
+ ">>> Starting training with Seed 56...\n"
71
+ ]
72
+ },
73
+ {
74
+ "data": {
75
+ "text/html": [
76
+ "\n",
77
+ " <div>\n",
78
+ " \n",
79
+ " <progress value='3088' max='3088' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
80
+ " [3088/3088 14:33, Epoch 4/4]\n",
81
+ " </div>\n",
82
+ " <table border=\"1\" class=\"dataframe\">\n",
83
+ " <thead>\n",
84
+ " <tr style=\"text-align: left;\">\n",
85
+ " <th>Epoch</th>\n",
86
+ " <th>Training Loss</th>\n",
87
+ " <th>Validation Loss</th>\n",
88
+ " <th>Accuracy</th>\n",
89
+ " </tr>\n",
90
+ " </thead>\n",
91
+ " <tbody>\n",
92
+ " <tr>\n",
93
+ " <td>1</td>\n",
94
+ " <td>0.651800</td>\n",
95
+ " <td>0.507618</td>\n",
96
+ " <td>0.774000</td>\n",
97
+ " </tr>\n",
98
+ " <tr>\n",
99
+ " <td>2</td>\n",
100
+ " <td>0.391900</td>\n",
101
+ " <td>0.337644</td>\n",
102
+ " <td>0.861500</td>\n",
103
+ " </tr>\n",
104
+ " <tr>\n",
105
+ " <td>3</td>\n",
106
+ " <td>0.299300</td>\n",
107
+ " <td>0.287754</td>\n",
108
+ " <td>0.887000</td>\n",
109
+ " </tr>\n",
110
+ " <tr>\n",
111
+ " <td>4</td>\n",
112
+ " <td>0.242700</td>\n",
113
+ " <td>0.276862</td>\n",
114
+ " <td>0.890500</td>\n",
115
+ " </tr>\n",
116
+ " </tbody>\n",
117
+ "</table><p>"
118
+ ],
119
+ "text/plain": [
120
+ "<IPython.core.display.HTML object>"
121
+ ]
122
+ },
123
+ "metadata": {},
124
+ "output_type": "display_data"
125
+ },
126
+ {
127
+ "name": "stdout",
128
+ "output_type": "stream",
129
+ "text": [
130
+ "\n",
131
+ ">>> Saving best model to: ./best_model_seed_56 ...\n",
132
+ "\n",
133
+ ">>> Loading BioPAWS (Short)...\n"
134
+ ]
135
+ },
136
+ {
137
+ "data": {
138
+ "application/vnd.jupyter.widget-view+json": {
139
+ "model_id": "e1c11d6121b34ba4908c3d161e13d700",
140
+ "version_major": 2,
141
+ "version_minor": 0
142
+ },
143
+ "text/plain": [
144
+ "Map (num_proc=4): 0%| | 0/14000 [00:00<?, ? examples/s]"
145
+ ]
146
+ },
147
+ "metadata": {},
148
+ "output_type": "display_data"
149
+ },
150
+ {
151
+ "data": {
152
+ "application/vnd.jupyter.widget-view+json": {
153
+ "model_id": "964967575b764b59b585aa0030354558",
154
+ "version_major": 2,
155
+ "version_minor": 0
156
+ },
157
+ "text/plain": [
158
+ "Map (num_proc=4): 0%| | 0/6000 [00:00<?, ? examples/s]"
159
+ ]
160
+ },
161
+ "metadata": {},
162
+ "output_type": "display_data"
163
+ },
164
+ {
165
+ "name": "stdout",
166
+ "output_type": "stream",
167
+ "text": [
168
+ ">>> Running inference on Protein Pair Short...\n",
169
+ "Executing inference...\n",
170
+ "[Protein Pair Short] Raw Accuracy: 0.8403\n",
171
+ "\n",
172
+ ">>> Classification Report for Protein Pair Short:\n",
173
+ " precision recall f1-score support\n",
174
+ "\n",
175
+ "Non-Homologous 0.7629 0.9793 0.8577 2947\n",
176
+ " Homologous 0.9725 0.7062 0.8182 3053\n",
177
+ "\n",
178
+ " accuracy 0.8403 6000\n",
179
+ " macro avg 0.8677 0.8427 0.8379 6000\n",
180
+ " weighted avg 0.8695 0.8403 0.8376 6000\n",
181
+ "\n",
182
+ "========================================\n",
183
+ ">>> Confusion Matrix saved to: confusion_matrix_Protein_Pair_Short_seed56.png\n",
184
+ "\n",
185
+ ">>> Loading BioPAWS (Full)...\n"
186
+ ]
187
+ },
188
+ {
189
+ "data": {
190
+ "application/vnd.jupyter.widget-view+json": {
191
+ "model_id": "0248f801ac6e4324961606006431dde0",
192
+ "version_major": 2,
193
+ "version_minor": 0
194
+ },
195
+ "text/plain": [
196
+ "Map (num_proc=4): 0%| | 0/14000 [00:00<?, ? examples/s]"
197
+ ]
198
+ },
199
+ "metadata": {},
200
+ "output_type": "display_data"
201
+ },
202
+ {
203
+ "data": {
204
+ "application/vnd.jupyter.widget-view+json": {
205
+ "model_id": "e9d2e03920694a92b76a591ff9540b95",
206
+ "version_major": 2,
207
+ "version_minor": 0
208
+ },
209
+ "text/plain": [
210
+ "Map (num_proc=4): 0%| | 0/6000 [00:00<?, ? examples/s]"
211
+ ]
212
+ },
213
+ "metadata": {},
214
+ "output_type": "display_data"
215
+ },
216
+ {
217
+ "name": "stdout",
218
+ "output_type": "stream",
219
+ "text": [
220
+ ">>> Running inference on Protein Pair Full...\n",
221
+ "Executing inference...\n",
222
+ "\n",
223
+ "==============================\n",
224
+ "Final Results:\n",
225
+ "{\n",
226
+ " \"seed\": 56,\n",
227
+ " \"lang\": \"en\",\n",
228
+ " \"protein_pair_short\": {\n",
229
+ " \"accuracy\": 0.8403333333333334\n",
230
+ " },\n",
231
+ " \"protein_pair_full\": {\n",
232
+ " \"accuracy\": 0.7765\n",
233
+ " }\n",
234
+ "}\n",
235
+ "==============================\n"
236
+ ]
237
+ },
238
+ {
239
+ "data": {
240
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAAhUAAAHcCAYAAABh61ifAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjUsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvWftoOwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAgwdJREFUeJzt3XdUFGfbBvBr6SBFsKA0wQI27F0BFewFsSvWaNTYo4nB6JuoMbZoTGzR2Ii9K2Ks2CuIilhABUWaYkGK0mG/P/iYMLsLgq4sq9fvHI/MzLMz92ybe582EqlUKgURERHRR9JQdQBERET0eWBSQURERErBpIKIiIiUgkkFERERKQWTCiIiIlIKJhVERESkFEwqiIiISCmYVBAREZFSMKkgIiIipfhsk4q3b99i/vz5aN++PerWrQsHBwc4ODjA29u7xGIYOnSocFwvL68SO+6XauXKlcLz3b59e1WH88Xg+7zk5D3PDg4OOHDggKrDoRLi7+8veu2jo6NVHVKBtD70ga9evcLu3btx9epVPH78GElJSdDS0oKlpSXq16+PTp06wdnZGRKJRJnxFtnPP/+MI0eOqOTY6qR9+/aIiYkRlrW1tXH27FlUqFBBVC4rKwuurq54/vy5aP2DBw8+6vjR0dFwdXUVlrds2YLmzZt/1D5VzcvLCwcPHpRbr6GhAWNjY1SvXh2dO3fGgAEDoKOjUyIxfS7P84EDBzBz5kxhWdF5+Pv7Y9iwYcLywoUL0bt37xKLkYpH9jUFcr+H9PX1YWpqiipVqqBFixbw8PCAmZmZ0o6b/3ParFkzbN26VWn7Lg7Z9+vp06dhZWWlkliU4YOSiu3bt2Px4sVIT08Xrc/MzERYWBjCwsKwf/9+lT05mZmZOHHihLDcuHFjtG3bFpqammjatGmJxTFo0CC0bdsWAFCjRo0SO+7HyMzMxK5duzBp0iTR+pMnT8olFKVN69atYWBgAAAwMjJScTTycnJykJCQgMDAQAQGBuLQoUPw9vYulbEWhzq+z6l0y8zMRGZmJpKSkvD06VNcuHABK1aswMyZMzFw4EBVh1fibGxsMGPGDGG5bNmyqgvmPYqdVKxfvx5Lly4VljU1NeHi4oI6depAIpEgMjISly5dwqtXr5QaaHG8fPkSmZmZwvKkSZPQsmXLEo+ja9euJX5MZdi9ezfGjh0r+hWtqiy+KN6+fQtDQ0M0atQIjRo1UnU4cvK+DJKSknD06FFERkYCAO7evYuVK1fixx9/fO8+srOzkZGRAX19/U8a64dQ1/c5lU4DBw6EtbU1EhMTcfv2bQQEBEAqlSItLQ0///wzkpKSMGbMGFWHWaIqV66MUaNGqTqMIilWUhEWFobly5cLy+XKlcOGDRtQu3ZtUbnMzEwcPHhQ7gswLi4O3t7euHTpEqKjo5GVlYUKFSqgUaNGGDZsGOrVqycqv3LlSqxatQoAYGlpCR8fH6xZswbHjx/Hy5cvYW5ujn79+mHs2LFCM4tsdT4AjBgxQvj79OnTiImJKbS6Kf8+Jk6cKPrVfvr0aezYsQMhISFITEyErq4uzMzMYG9vj/r16+Prr7+GhkZuV5WhQ4ciICAAAODh4YFFixaJ4nry5Am8vb1x7do1oRagUqVKaN68OYYPH45q1aqJystW1y1btgwrV67E2bNnkZCQAGtra4wcORL9+/fHh9DQ0EBOTg5evnyJY8eOwd3dHQBw79493Lx5E0BuEpmdna3w8SEhIdi9ezfu3buH58+fIzExEVKpFOXLl0f9+vUxZMgQNGnSROHznCf/65JXJamo6j4yMhI7duxAeHg47Ozs4OPjI/d+OXPmDADg22+/xdGjR4X1hw8fhqGhIQDA19cX3333nXD+W7ZsEWqz8r9+H1M9mv/LYMSIEXBzc8Pbt28B5NYA5SUVsq/v4sWLsXz5cly+fBnx8fFYtWoV3NzcABTvs1TU5zlPVFQU/vnnH1y+fBnPnj1DTk4OrKys0L59e3z11VdyVdAFvc8VvW6vXr2Ct7c3Hj58CB0dHbRs2RIzZ85E5cqVP+i5/VB3797Fli1bEBgYiJcvXwpNt23atMGIESNQqVIlUXnZcxw8eDB+//133L59G/r6+ujUqRO+++47lClTBkePHsWGDRsQFhYGExMTdOvWDdOmTVPY1HXixAns378f9+7dQ2JiIvT19VGtWjV07NgRgwYNKnYSefXqVezcuRNBQUGIj4+Hjo4OqlSpgnbt2mHYsGEKf+EGBgbizz//xJ07d4TX5LvvvsPq1avlmgciIyPRqVMn5OTkAAA2btyINm3aiPbXp08f3L17F0BugjB37txinUPXrl1FTVo3btzA+PHjkZCQAABYvnw52rVrJ1crFhgYiO3bt+PWrVt49eoVdHR0UKNGDfTs2RP9+/eHtrY2AMXNLQEBAXBwcBCW8zer5eTk4PDhwzh8+DBCQkKQnJwMQ0ND1KtXD56ennBxcVF4HuHh4di2bRv8/f2Fz1H58uVRr149fPXVV3B0dBQdM0/+z0ze5+l9TSTZ2dk4ePAgfH19ERoaKvzQsre3R48ePdC7d29oaf13uf+Un81iJRVbt24VXVDmzJkjl1AAue1hshe269evY8KECUhMTBStj4mJQUxMDP7991/MmDEDI0eOVHjsd+/eYcCAAQgPDxfWRUdHY/ny5UhPT8eUKVOKcyofRNGbMSsrC+/evUNUVBROnz6NESNGQFdX9737OnbsGH744Qe5JqSIiAhERETg4MGDWLRoEbp166bw8c+ePUPv3r3x8uVLYd3jx4/xv//9DxoaGujbt2+xz69FixYICgpCSkoKtm3bJiQVW7ZsEcq0a9cOfn5+Ch9/48YN7Ny5U259bGwsYmNjcfz4cSxYsOCj27dXrFiBwMDAIpefO3cugoKCEBsbi5iYGCxZsgTz5s3DixcvMH/+fKHcmDFjPnnzmKmpKWxtbYUv3YJq9F68eIH+/fuLXt88H/tZKoyfnx++++47pKamitaHh4cjPDwchw8fxubNm+US3qL4888/cePGDWE5LS0NJ06cwIMHD3D48OEifW6UwdvbG4sXLxYujACQkZGBR48e4dGjR9i3bx9Wr15dYJ+Tu3fvwtPTExkZGQCAlJQU7NixA2FhYWjXrh0WL14slH3x4gU2b96M+Ph4LFmyRFifnZ2N6dOn49ixY6J9Z2Zm4tatW7h16xb27dsHb29vVKxYsUjntWjRImzevFluf/fv38f9+/exb98+bNy4UXQxPnv2LCZOnIisrCwAQGpqKo4fP45r167Bzs5O7hg2NjZwdnbGuXPnAAB79+4VJRVRUVHCexvITTA+VuPGjTF37lzhOz4nJwdbt27FvHnzhDLLly/H2rVr5c49KCgIQUFBOHr0KNavXy80jxZVWloavvnmG1y5ckW0/s2bNzh//jzOnz+PkSNHynVQ3rt3L+bOnSuqMQdyr1nR0dGoX78+HB0dixVLQVJSUjBmzBhcv35dtD4hIQEBAQEICAjAoUOHsH79epQpU0bhPpT52SxWUnHt2jXhbxMTE+FX0/skJSVh4sSJwpegnp4eevfuDUNDQ/z777+IiYlBTk4OFi9ejDp16qBZs2Zy+0hISEBSUhJ69eqFihUrYu/evXjz5g2A3IveN998Ax0dHYwbNw4xMTGiN9jAgQNhY2MDILctSvZXW1Hlv2A6Ojqibdu2yM7OxvPnz3H79m1RwlOYp0+fYsaMGcKXUtmyZeHh4QGJRIKDBw/izZs3yMjIwA8//IA6derA1tZWbh9RUVHQ1dXFoEGDoKenh507dyItLQ0AsGHDhg9KKoyMjODh4YHt27cjODgYQUFBsLa2Fn7lN2vWDDVr1iwwqdDR0UGDBg1Qs2ZNlC1bFmXKlEFycjKuXr2KO3fuQCqVYvHixejatSv09PTe+1oVlCEHBgbC0tISHTt2hJ6eHuLj4ws9L2NjY/z2228YNmwYsrOzsXv3bnTq1AlbtmwRfv3Ur19frh/Jp/DmzRtEREQIy+XLl1dYLq9Mx44d4eDggNjYWBgaGn7QZ6moz3NUVBSmT58uvI9q1KgBNzc3SKVS+Pr6IiYmBnFxcZg0aRJ8fX2hqalZrHO/ceMGHB0d0aZNG/j7+wu1XxEREfDz8yswgS7M0aNHRRcxAELzkiLXr1/HokWLIJVKAQAWFhbo1q0bUlJScODAAaSmpiI5ORmTJ0/GyZMnYWJiIrePR48ewdLSEj169EBwcLBwwcn7Aq9SpQq6dOmCS5cuCbH5+vpi+vTpMDc3BwCsXbtWlFA0aNAArVu3Rnh4OI4fPw4gN5H77rvvREl9QQ4dOiRKKPJeuxcvXuDQoUPIzs5GXFwcJk6ciH///RdaWlpITU3FrFmzhIRCS0sLvXv3homJCQ4dOoRbt24pPNaQIUOEpOL06dOIj48Xaq/yYs+LQbb2+UN17NgRJiYmwvve399f2Pbvv/+K3ttt2rRBo0aN8Pr1axw8eBApKSkIDAzEwoUL8csvv8DR0REzZswQvXesra0xaNAgYR95n40FCxYIr6+2tja6deuGKlWq4OHDhzh+/DikUik2b96MOnXqoEePHgCAoKAg/PTTT0LSqqWlhc6dO8POzg5xcXG4ePGicJwZM2YgMjISu3btEtaNGzcOxsbGwnP4PvPnzxclFG3atEGDBg0QFBSES5cuAcj97M2fPx8LFy5UuA9lfjaLlVTExcUJf9va2grV/O9z4MAB4csbyP2lmVdllFcdnJKSAqlUCm9vb4VJBZBbPTx8+HAAuReBCRMmAMhtU3/y5AkcHBzQv39/REdHi95kstVpHyp/rcLs2bPRoEED0fbo6Gihiq0w27ZtExIKDQ0NbN26Ffb29gByq7vc3d2Rk5ODzMxMbN++HbNmzVK4n99//11I7CpXrowFCxYAyG1Wyav+Kq4hQ4Zgx44dkEql2Lp1K+zs7IRYhw4dWuhoj/79+6N///4IDQ3Fw4cPkZCQAE1NTbi6uuLOnTsAcpPDu3fvokmTJh/8WllZWeHgwYPCB68omjRpgrFjx2LNmjUAcpu1UlJSAABlypTBsmXLRNWDyrRx40YA//WpyGv6AIAOHToU+Lgff/xReL/n8fb2LvZnqajP87Zt24SEwtbWFvv37xd+oXh6egpJdHh4OM6dOyeqPi2KevXqYceOHdDW1kZmZiZcXFzw+vVrAMCdO3c+KKnI/2VcFJs3bxYSijJlymDfvn0oV64cAMDFxUVoq09ISMDBgwdFTad5tLW1sWXLFlhZWSE1NRVNmjQRLsza2trYunUrzM3N4e7uji5dugDI/XV97949mJubIycnR5QoNGzYENu3bxeStN9++w0bNmwAkHvxDAkJQa1atd57XnksLS2xb98+6OnpAQDq1q0rNEFERETg3LlzcHNzw5kzZ4TnH8j9Tsu7sPbr1w9du3YVziu/Nm3awNbWFhEREcjMzISPj49QK5Y/UVJGLUUeDQ0NVKlSBcHBwQDE16K85woAevXqJaopatq0KaZOnQog9zo0ffp01KhRAzVq1MCjR4+EpEJRn4WEhATs379fWJ47d67onObOnYsdO3YAADZt2iQkFRs3bhQSCg0NDfzzzz+iZt+MjAzhh9CoUaPg7+8veh/369evyAMc3rx5g0OHDgnLXbp0wR9//CEsT506VXhNfHx8MGPGDJiamsrtR5mfzU/zLSojKChI+NvMzEzUBlWuXDk4OzsLGW7+svlpamqKev3KVs0lJSUpL+ACNGnSRLiojhw5Eg0bNkSVKlVQvXp1NGnSRGH7mCL5z7FOnTpCQgEA9vb2qFOnjnARLuj5qFixoqimSNHz8SFJRdWqVeHk5IQLFy7gxIkTwsgES0tLuLq6FppU3Lt3Dz/88AMePXpU6DE+dhSJp6dnsRKKPBMnTsTVq1dx69YtIaEAgJ9++gnW1tZy5ZXVOTV/tXd+tWvXLrB2xMTEBJ6ennLrlfFZKkjerxMg9+JT2K/MW7duFTup6Nevn5B0a2trw8rKSvjikm3K+VTyPydOTk5CQgHkJhVmZmbCF35Bz1/Dhg2FL/28YY95zVSNGjUSaiPyfu3myfuOevLkiSgx7NGjh6jWx8PDQ3ShvHXrVqFJRWpqquhz2blzZyGhAHIvtPn7Ndy6dQtubm5yNTx5zZ0AUKVKFTRq1EjoR5KfRCLBkCFDhKbDvXv3YuTIkYiKisK9e/cA5L6+PXv2LDDmD5GXDOaXmpqKkJAQYfnQoUOii2x+WVlZCA4OhrOzc5GOd/v2bVFS9eOPPxbYqTokJASpqanQ19cXNSO0adNGlFAAuTW6sn12PlRwcLCoS4KHh4dou4eHh5BUZGdnIzg4WGEfEGV+NouVVJibmwvVshEREZBKpUWahyJ/UIqqe/OvKyg5KFeunKhdR7bTU/720eKSfbPm/TKXNW3aNERFReHChQtISUnB5cuXcfnyZWF7s2bNsG7duve22ynj+bC0tBQtK/P5GDp0KC5cuIDMzEzhC3bw4MGFVnenpaVh7NixCvsAyCro+S2qqlWrftDj8hLT/NW65cqVK7HRCxoaGjAyMkL16tXRsWNHDB48uMB5KqytrRXWnCjjvVOQ4nx5vK/JSZHC3rOKLhhFUZR5KvIryvOXd24FPX+yfRzyn0f+bbKvX95nMn9CoSiO/IlOYXHk357/+ZPdn4GBAQwMDIREOm9/+fdbpkwZue8t2blq8vPw8MDy5cvx7t07hIeH48aNG6KLqYuLi9x5fIycnBxRs1Ze4iZ77u9TnPdtcT4PUqkUCQkJ0NfXFz3uU0+pIBuj7HNe1PeSMj+bxUoqWrRoISQViYmJOH36dJH6VeRvl1TUMS3/uoJ+gco2K3zMpFqyzTb5mzXevn1bYOc5Q0NDrF+/Hs+fP0dQUBAiIiIQFhYGPz8/pKamIiAgABs2bMDkyZMLPX5pez5kOTk5wc7ODk+ePAGQ+2usX79+hT7m+vXrooTiq6++wtdffw0zMzOkpqbKNRV9jA8dVhkfHy8aDg0Ar1+/xm+//VZgE5MyfMgEYQUlpsp47xQk/75r1Kgh96snvw+Zj0L2IquKifFMTEyEX2DK+uzlV5QmNNkRGLJx5G+SKCyO/NslEonw5S+7v5SUFFHNXN7+8u/33bt3SEtLE9VwFPYDwdDQEL179xZq8/bt2yd6nyt7srFTp06JLqB5iaTsHC/t27eXqxnIr06dOkU+pmx/mhEjRhTaaTYvlvzvsU8986VsjLLvnaK+l5T52SzWNN1DhgwR/VqdM2cOQkND5cplZmZi7969wgk1bNhQ2BYfH4/z588Ly69fv8aFCxeE5fxlPxXZN2L+as5169YVmJk9fPgQmZmZqFSpEjp37oxx48Zh6dKlok6R9+/ff+/x85/jvXv3RM0FDx8+FKoQZcuWFIlEgqFDhwrLPXv2VNhhLT/ZX189evQQOm/J9nDPT/YLOq9N/1P48ccfhS/K/H2Ctm7dKnoP5sk//XT+50OVPvSzVJTnOX/5ly9fonv37hg1apTo3/Dhw2FjY4P69esr5XxKWv5zvHjxouhL9/z586Jfsp/qs2dnZydKLHx9fUVV2LKzsb5v7hV9fX3UrFlTWD5+/Ljo9ZVtDsg7r7p164rW//vvv8LfT58+FTWHKeLp6SlcfI4cOSJ8b5UvX77AYZYfIigoCD///LOwrKGhIdREGRgYiJqGEhISMGzYMLn3bf/+/VGpUiVRMpz/Qio72gnI7beX/3qnpaUlt99Ro0ahU6dOsLe3F5qbGzduLDzm8uXLohocILcZJn+fENkLenG+A+vVqyeKUfa9k39ZU1NTaR1nC1OsmooaNWpgypQp+P333wHkfvH06dMHbdu2Ra1ateQmvzp9+jSA3KqyNWvWCBeeyZMno0+fPjA0NMSRI0eELFoikch1TPsUqlatijJlyuDdu3cAcjvcnDt3Dq9evSqwxzMALF68GHfu3EGLFi1QuXJlmJmZ4cWLF6I5+IsyO6Knpyd27tyJjIwM5OTkYMiQIaLRH3nVpNra2grb1UtC7969hSrGorwRZft0fP/99+jSpQtiYmJw+PDhAh9namoqdA4CcoeGhYaGQktLC82aNVPasKvt27fj7NmzAHK/hNetW4fdu3dj06ZNkEqlmDlzJnx9fZU6DfCn8KGfpaI8z0OHDsWuXbuQnp6OhIQEuLu7o3PnzqhcuTJSUlIQFhaGgIAAJCUl4fTp0+9NNEujESNG4PTp05BKpXj37h369u2L7t27IyUlRdQpL29E1qegoaGB4cOH488//wSQ28dh8ODBaN26NR4/fixKwps3by5KGAoycuRIYZK1mJgY9O3bVzT6I4+tra0w+6mrqyvKlSsnJFY///wzgoODYWRkhEOHDinspJmfnZ0dWrdujUuXLomaNHv27PlRnZ6PHj2KO3fuICkpCbdv34a/v7/oh953332H6tWrC8ujRo0S5pq5efMmevbsiXbt2sHExAQJCQm4f/8+bty4gYoVK4o6HOZ9vwG5P+7mz5+PypUrQ1tbW5jTo0+fPtizZw+A3A6hd+/eRcOGDaGrq4u4uDjcvn0b9+/fh4eHB5ycnIR4/Pz8kJOTg+zsbAwfPlwY/fHq1StcunQJnp6eQifg/HEAudcjJycnaGpqon379gqH9uYxNTWFh4cH9u3bByD3B1xycrLc6A8gt8+Mok6aylbsV37s2LHQ19fHb7/9hoyMDGRlZcHPz6/AYYZAbpXLqlWrMH78eCQlJSEtLQ3bt28XldHQ0MD3339f4MgPZdLR0cGwYcPw119/Acht4z916hSA3Oz92bNnctVGeRITE0VTgOenq6tbpF+0VapUwZIlS4R5KhISEuTGl+vo6GDRokWoUqVKcU5NafT19Ys8ZBjIfd6cnJyE4VJhYWFYuXIlgNwLoaJ7YQC559m2bVvh+Q8JCRE6Xs2YMUMpScWjR49EPcKnT58OW1tbfPvtt7h48SIePXqEV69eYebMmVi3bt1HH+9T+tDPUlGeZ2tra/z+++/4/vvvkZKSgjdv3iicd0SdNW3aFF5eXsI8FbGxsfj7779FZYyMjLBixYoP6gxcVGPHjsWDBw9EnWplO4ZWq1YNv/32W5H25+7ujpCQEOF7JG/OjfwqVqyIVatWCRd8PT09/Prrr8I8FXlT9AO51ep5Fyag4OrwoUOHii5cwMeP+ihoRI++vj5mzZol1xTbo0cPPHr0SPjsPn78GI8fP37vcdzc3LBmzRrk5OQIc18AubUfeTUhP/74I6Kjo4VhpdeuXRNNraBIgwYNMG/ePGGeiszMTPj6+hZY3srKCrVr1xZqufOGJgO5fR0KSyoAYNasWXj69KkwrPTSpUtyr0mjRo0we/bsQvejLB+UTg4bNgydO3fGnj17cOXKFTx58gRJSUnQ1taGhYUFmjVrhq5du4o6fzRt2hRHjhyBt7c3Ll68KJoFsHHjxhg6dGiJVqlOmTIF+vr62LNnD+Li4lCxYkV0794d33zzTYHDZ0aPHo2qVasiODgYz549Q3x8PCQSCczNzdGkSROMHDmyyCNAunTpAgcHB/zzzz+4evWqMCLC3NwcLVq0wIgRIz5ogiFVWrlyJZYvX46jR48iISEBFhYW6NOnD0aPHl1gUgEAv/zyCwwNDXHx4kXEx8d/VCdTWRkZGZg+fbrQb6ZFixYYMmQIgNwL7ZIlS9C/f39kZmbi3Llz2LZtm7C9tPrQz1JRnmc3Nzf4+vpi27ZtuHz5MqKjo5Geng4jIyPY2dmhUaNGcHNzU+sbHo0YMQKNGzfG1q1bERgYiBcvXkBTUxOWlpZwcnLCiBEjPvkMn5qamvjzzz9x/PhxHDhwAHfv3hVm1KxatSo6deqEQYMGFWuyJi8vLzg7O2PXrl24desW3rx5A21tbdGMmrK/VNu1awdvb2+sWLECwcHB0NHRQdOmTfH999+LJoYrKMFycXFBlSpV8PTpUwC5TQb5axE+lJaWFvT19WFmZoYqVaqgVatW6NWrV4G/tKdNm4a2bdti586duHnzJl68eAGpVAozMzPUqFEDzZo1E4b35qlVqxaWLVsmzH4qOxEhkJvIbNy4EUePHsXhw4dx7949JCQkQEtLCxUrVkStWrXQpk0bdOzYUfS4fv36oVGjRti6daswo2Z2djbKlSuHevXqiZpIgNzvzkWLFuH69evCTMRFZWBgAG9vb2FGzQcPHuDt27coU6YMHBwc0L17d/Tp0+eTDZmXJZF+aLdrIiJSa+np6QpnS4yLi0PXrl2FOVW+/fZbjBs3TuE+Ro0aJfwynjt37hd5wy/6T8mkLkREVOpcvHgRS5cuRffu3WFrawt9fX1ERERg27ZtQkJhYGAg16QRHh6OFy9eICgoSBhWb2xsLEwARV8uJhVERF+wJ0+eCP2fZJUpUwbLly+Xm7Ni/fr1ck2aU6dOLfDeEvTlYFJBRPSFcnBwwKBBg4S+JW/fvoW+vj6qVKmC1q1bw9PTs9DZH/Pugjp8+PD3zmVDXwb2qSAiIiKlKNbkV0REREQFYVJBRERESsGkgoiIiJSCHTVJLXXv3l00Y2CFChVw7ty5EpvghT7cu3fvsGPHDpw+fRpPnjxBcnIytLW1UbFiRdSrVw+DBg0q9KZQQO4tnwcOHCi6Z4aiO5a+T3Z2Nvbu3QsfHx9hAqRKlSrB2dkZY8aMkbuB1OvXr4XpmqOiopCQkICsrCwYGxujWrVq6NChAwYMGCA398PRo0exceNGPHnyBGXKlEHDhg0xbdo02NraisplZmaid+/eePjwIdatWydMqU2kLthRk9ROcHCwwp7ma9euRbt27VQQERXV27dvMWDAAISFhRVYRiKRYM6cOQVOopSRkQEPDw+5fRQ3qUhPT8f48ePlpjTOU7ZsWWzYsEE0VfydO3dENxBUpEGDBti6datw++itW7cKs1OWK1dOuCOosbExfH19RaMr1q1bh99//x1dunTBH3/8UeRzISot2PxBaqegKb8Lmwpc3eRNPPS52bVrlygZaN68OaZMmYJBgwYJF2GpVCrcbEuRP//8s9CkpKiWL18uJBSampro378/JkyYAAsLCwC5d7ycMmWK6LbhEokE1tbW6NGjB77++mtMmzYNnp6eohvRBQUFCfdYAYB//vkHANC5c2dcvnwZJ0+ehKGhIZKSkkQ3MYuMjMSaNWtgbGyMWbNmffT5EakC64pJrWRkZIhu0Wxra4uIiAgAwJkzZ/DmzZsC7w8QHh6Obdu2CXPx5+TkoHz58qhXrx6++uor0S9SqVSKEydO4NChQ7h37x7evHkDAwMDWFhYoHnz5pg+fTp0dHQQHR0NV1dX4XGyv5aHDh0q3BzIw8MDixYtAgCFj4uMjMSOHTsQHh4OOzs7+Pj4ICoqClu2bMG9e/cQExODxMREZGVlwdTUFHXq1EH//v3Rvn17hecbHByMnTt3CnMQaGhooGLFimjUqBG++eYbWFlZoUOHDoiOjgaQe5OradOmifaxePFibNq0CUDuDa6OHj0KIPdeBatWrRLKPXjwQGEMsiIjI4W/jYyMsHnzZuHWzZqamti2bRsAICkpCdnZ2aLbOgPA7du3hZtmubm5FXojw8IkJCSIbsT29ddf49tvvwWQ27TWtWtXSKVSxMTE4NChQxg8eDCA3BvnKTpmt27dhDJA7p1C8zx79gxA7n1n8u4VVK1aNdy+fVu45w+Qe5fQtLQ0/Pjjj3KTTRGpC9ZUkFrx8/NDYmKisLx48WJoa2sDyG2PPnLkiMLH7d27F+7u7sJFOyUlBWlpaYiOjsbRo0dx48YNoWx6ejrGjRuHKVOm4OzZs3jx4gUyMzORmJiIkJAQeHt7Iy0tTanntWLFCsyePRv3798X3dgoLCwMW7ZswY0bN/D8+XOkpqYiMzMTL168wNmzZ/HNN9+ILu55Vq1ahf79++PAgQOIjIxEWloaUlJSEBERgQMHDuDhw4fQ0NAQXQgPHjwo6qMAQHRH3t69e3/0eea/2dS7d+9w7tw5ZGRkICoqCjdv3hS2tWzZUi6hSE9Ph5eXF7Kzs9GsWbMi3RG4IJcvXxbdrjv/DaGqVq2KGjVqCMtnzpwpcD+ZmZmIioqCj4+PaH3+x+fdnOzatWuQSqWIi4tDeHi4aJuPjw+uXLmCxo0bo3///h98XkSqxpoKUiv5mzjq1KmDBg0aoGXLlrhw4YKwXfZiExQUhJ9++km4K6eWlhY6d+4MOzs7xMXFCbdrz7No0SKcO3dOWK5cuTLc3NxgZGSEsLAwnD17VunnFRgYCEtLS3Ts2BF6enqIj48HkPvrvVatWqhbty7MzMxgaGiIlJQU3Lx5E/7+/gCAv/76C/369YO5uTkA4NixY6Jpl/X19dG1a1dYWFggJiZGdJHs27cvVq5cidTUVLx48QLnzp0TalCCg4OFX9xaWlpwd3f/6PPs168fjh49ilu3biEnJwfjx48XbdfU1ISrqyvmzp0r99g//vgDjx8/hoGBARYsWIDY2NgPjkO2ZsXa2lpu+eHDhwrLArlJakG3ku7SpYuog+WIESPwyy+/4Pjx42jdurWoT0Xv3r2RkJCARYsWQVtbG7/88kuBtxknUgdMKkhtvHjxQrh5EQDhFvXdunUTkop79+7hwYMHolvQb9y4UUgoNDQ08M8//4hGF2RkZAgX8cTEROzZs0fYVrt2bWzbtk10T4Nnz55BX19fqedmZWWFgwcPyt1i2tnZGc7Oznjy5AlCQkIQHx8PLS0tuLi4IDg4GKmpqcjKysLVq1fRq1cvALn3ZchjYGCAAwcOwM7OTliXkpKC1NRUAICJiQl69OghnPPevXuFpOLYsWPCY5ycnJRSJa+vr48tW7Zg7ty52Ldvn9z2atWqwd3dXdRHAQBu3boFb29vAMB3330Ha2vrj0oqEhISRMuGhoai5fyvt2zZwowcORLTp08XJQZDhgyBqakpNm3ahMePH8PIyAguLi6YNm0aKlWqhJkzZyI+Ph7jx49HtWrVEBoailOnTuH169eoVKkSunbtChsbmw86T6KSxqSC1IaPj49QPS+RSNC1a1cAuW3rurq6QrPBgQMHMHPmTOFx+Zs22rRpIzdcUUdHR+iBHxQUhKysLGHb119/LXeTpLwqa2Xy9PSUSyiA3L4X3333HW7dulXo4+Pi4gAAqampuH//vrDe3d1dlFAAuYmGgYGBsDxkyBAhqbhw4QLi4uJgbm4uavqQvUvlpEmTMGnSpCKe3X/evn2LCRMm4Nq1awByR0o4OTkhNjYWPj4+ePjwISZMmAAvLy+MHDkSAJCWlgYvLy/k5OSgZcuWoiYbZZEdBPe+QXH16tXDjBkzkJqaisjISJw6dQopKSnYvHkzgoKCsG7dOpiYmAjlu3XrJiTB+QUEBODgwYOwtbXFN998g127dmHu3LlCEgwAa9aswR9//FFg3xmi0oR9Kkht5G/6aNiwoXBxNzQ0FFU3+/r6ihKD/H0wrKysCj1G/rJFKS9L9mKUv92+MFWrVlW4fsKECe9NKPIfJykpSRRDUeJ3cHBAs2bNAOTO23DgwAHcvn1baPowMzNT2nwJq1evFhIKW1tbbN++HRMnTsSCBQvwzTffCOX++OMPYQTMP//8g4iICJQpUwa//vqrUpoHZDvzvnv3rsBlRR1/HRwcMGrUKEycOBFLlizB/v37oaenByC3VmX16tXvjSEjIwM//fQTpFIp5s2bh7dv32LBggXIycnBhAkTcOPGDfTp0wfp6en43//+J3pPE5VWTCpILdy+fVvo3AYAN2/ehIODg/Av/6/q169f4/z588Jy/l+MeSMdCpK/bFHKa2iIP0L5O1nm5OSIRjsURlFzyuPHjxEaGiosd+/eHRcuXEBoaCgePHgg10QAAMbGxqKL7vviz5O/H8r+/fuFUR4A0LNnT6Ez7Me6evWq8HetWrVEk5XlH32TlpaGJ0+eAABevXoFIPdC3759e+E1HzZsmGjfw4YNg4ODg9DXpDD5m8cAICoqqsBle3v79+6vatWqosQwb8RPYdatW4cnT56gd+/eaN68OW7evCm8f4YPHw5DQ0MMGTIEQO5zoIxhtESfGpMKUgsHDhwoVvn8tRqNGzcW/r58+bKoOQQAsrKyhOaDBg0aiC50GzZsEPof5ImLi0NmZiYAyDVZBAUFCX/v2bNH6KvxIWTb8jt37gxzc3NIJBL4+/sr3Le+vj5q164tLPv4+ODp06eiMmlpaXj9+rVonaurqzA/Q1RUFHbu3Clsk236AHKHlOZP6ooq/+iSkJAQ0a/vO3fuiMrm/fL/GPmTkPydV1u3bi2a9fLkyZPC32FhYaILeP6hvxcvXlRY+xQZGSkkQQDeW5sSHh6OdevWwczMDD/88AMAiEYU5SVx+ZM5ZY84IvoU2KeCSr309HTRL2crKyvUq1dPrtzDhw+Fi8G5c+cQHx8PMzMzjBo1Cn5+fsjJyUF2djaGDx8ujP549eoVLl26BE9PT4wYMQImJibo378/duzYASC342e3bt3g6uoKY2NjRERE4NSpU7h06RK0tbVhaGgomitj7dq1CAkJQVpamlDN/6GqVKkCDQ0NoX39119/RUhICBISEgpNsr7++mtMnToVQG6nzF69egmjP54/f46zZ89izpw5cHNzEx6jqamJQYMGYdmyZQD+q3GpW7dukX6pF1Xz5s2FURURERHw9PQU9anIU7lyZVSrVg1Abo1Gp06d5PYVHx+P69evC8tNmzaFmZmZwhocWSYmJvD09BTm4Fi/fj3evHmDChUqYP/+/UITkqWlpWjUy9KlSxETE4NWrVqhevXq0NbWRnR0NE6cOCFKPgtrLpJKpfj555+RmZmJmTNnomzZsgDEw21PnjyJXr16CTVwWlpacn1jiEojJhVU6vn5+SEpKUlYnjJlCnr27ClX7urVqxgxYgSA3PkDfH19MXz4cDRo0ADz5s3D3LlzkZmZKWwriJeXF2JiYoQmlJiYGGzZsqXA8qNHjxaGF+bk5AhDTq2traGtrY3Hjx8X+5yB3Cmd+/fvj127dgHIHXWS11bfsmVLPH78WKhhya9Lly4IDw/HqlWrIJVKkZKSonCkhax+/fph1apVoiYcZcxNkd8333yDixcvCklYUFCQqHYHyO04O3/+fKFpqXfv3grj8Pf3FzWBTJo0qVjTdE+dOhUPHjzA5cuXkZ2djd27d4u2m5iY4M8//xR1agWA5ORknDhxQtTkll+rVq0wduzYAo+7b98+XL9+HW3atBG9j2vWrAkXFxecP38eXl5eWLZsGV68eAEAGDBggFzTHFFpxOYPKvXy/yo3MjISTVSUX4sWLWBpaSks528C6devH3x8fDBo0CBUrVoV+vr60NHRQeXKldGpUydRE4muri7WrVuHP/74A+3atUOFChWEWgl7e3sMGzZMVDXfr18/zJ8/H9WqVYO2tjYqVKiAQYMGYe/evShfvvxHnfv//vc/TJ48GZaWltDW1oaFhQVGjRqFtWvXFnrztIkTJ2LPnj3w8PCAtbU1dHV1oa+vD2tra7i7u4smZ8pjamqK7t27i56HHj16fFT8ssqVK4cDBw5g+vTpaNiwIUxMTKCpqQl9fX3Y2dlh4MCB8PHxQZs2bZR6XEV0dXWxfv16zJkzBw0bNoShoSF0dHRgY2ODoUOHwtfXV9TPAwDGjRsHDw8P1KhRA6amptDU1ISenh5sbGzQuXNnrFixAps2bSqw6SY+Ph5Lly6Fnp4e5syZI7f9zz//xPDhw1GhQgXEx8fD0tISkydPxo8//vgpngIipeMNxYhI8PfffwtNIN26dcPvv/+u4oiISJ2w+YPoC/fy5UuEh4cjNjYWGzduFNZ7enqqMCoiUkdMKoi+cBcvXhRNFgbkjjTJ3yRERFQUTCqICEDunBuVKlVCt27dMHHiRFWHQ0RqiH0qiIiISCk4+oOIiIiUgkkFERERKQWTCiIiIlIKJhVERESkFEwqiIiISCmYVBAREZFSMKkgIiIipWBSQURERErBpIKIiIiUgkkFERERKQWTCiIiIlIKJhVERESkFEwqiIiISCmYVBAREZFSMKkgIiIipdBSdQBfKolEouoQiIg+W1KpVNUhfJFYU0FERERKwZoKFdNrMEHVIRAVKi1otWg5NZO/AKn00tdmLbAqsaaCiIiIlIJJBRERESkFkwoiIiJSCiYVREREpBRMKoiIiEgpmFQQERGRUjCpICIiIqVgUkFERERKwaSCiIiIlIIzahZBRkYGjh49ioSEBHTo0AGWlpaqDomIiKjUYVIhY8GCBbh27RoOHz4MAMjJycHQoUMRHBwMqVSK1atXY8+ePbCzs1NxpERERKULmz9kXL16Fa1atRKWT58+jdu3b+Prr7/G8uXLoampiQ0bNqgwQiIiotKJNRUy4uLiYG1tLSyfP38elpaWmDZtGgAgNDQUR44cUVV4REREpRZrKmSkp6dDR0dHWA4MDESLFi2EZRsbG7x69UoVoREREZVqTCpkVKpUCaGhoQCAqKgoREREoGnTpsL2+Ph46OnpqSo8IiKiUovNHzLat2+PrVu3IicnB7dv34auri6cnZ2F7WFhYRz9QUREpACTChnffPMNQkNDsXPnTujq6mL27NkwMzMDAKSlpcHPzw/9+/dXcZRERESlj0QqlUpVHURp9PbtW+jq6kJbW1tYl5aWhoiICFSqVAlly5b9qP1LJBIAgF6DCR+1H6JPLS1otWg5NZNfGVR66Wvnfrfy0qYarKkogKGhodw6PT091KxZUwXREBERlX5MKmRcv369SOXyd94kIiIiJhVyhg4dKjRNFCYkJKQEoiEiIlIfTCpkLFy4UG5dVlYWoqKicODAAVhZWWHAgAEqiIyIiKh0Y1Ihw8PDo8BtI0eOhIeHR5FqMoiIiL40nPyqGExNTdG3b19s3LhR1aEQERGVOkwqiqlcuXJ4+vSpqsMgIiIqdZhUFJOfnx9MTU1VHQYREVGpwz4VMlatWqVwfWJiIq5du4ZHjx5h3LhxJRwVERFR6cekQkZBSQUAVKhQAdOmTcPo0aNLMCIiIiL1wKRCxunTp+XWSSQSmJiYoEyZMiqIiIiISD0wqZDBO5ASERF9GCYVhXjw4AGioqIAANbW1nBwcFBxRERERKUXkwoFAgIC8NNPP8kNHbW1tcW8efN43w8iIiIFmFTIuHv3rtAR08PDA/b29gCAhw8f4t9//8Xo0aOxY8cO1KlTR5VhEhERlTpMKmSsWrUKBgYG2LVrF2xtbUXbxo4diwEDBmD16tVYs2aNagIkIiIqpTj5lYxbt25h0KBBcgkFAFSpUgUDBw7EjRs3Sj4wIiKiUo5JhYzU1FSUL1++wO0VKlRAampqCUZERESkHphUyLC0tMTFixcL3H7p0iUOOyUiIlKASYWMbt264dy5c/j555/x6tUrYX18fDzmz5+Pc+fOoVu3biqMkIiIqHSSSKVSqaqDKE0yMjIwevRoBAQEQCKRoGzZsgCAhIQESKVSNG/eHOvXr4eOjs5HHUcikQAA9BpM+NiQiT6ptKDVouXUTH5lUOmlr5373cpLm2pw9IcMHR0deHt748CBAzh16pQw+VX9+vXRoUMH9OrVC5qamiqOkoiIqPRhTYWKsKaC1AVrKkidsKZCtdingoiIiJSCzR8yCrv1OZBbw6CnpwcLCws0b94cZmZmJRQZERFR6cakQsaqVauEpgnZ6jPZ9dra2hg7diwmTpxYskESERGVQkwqZPz777/w8vICAIwYMQLVqlUDAISFhcHb2xuampqYNWsWYmJisHHjRqxevRpWVlbo1auXCqMmIiJSPXbUlPHbb7/B398fO3fuhLa2tmhbRkYGBg8ejBYtWuC7775DRkYGevXqBSMjI+zevbtYx2FHTVIX7KhJ6oQdNVWLHTVlHDlyBN27d5dLKIDc4abdu3eHr6+vsNy1a1c8evSopMMkIiIqdZhUyIiPj0dmZmaB2zMyMvDmzRth2dzcHNnZ2SURGhERUanGpEKGnZ0dDh06pPCmYSkpKTh06BCqVKkirIuOjuYIECIiIrCjppyvvvoKXl5e6NWrFwYNGgQ7OzsAwJMnT7Bjxw5ERUVh4cKFQvkTJ07A0dFRVeESERGVGuyoqcD27duxdOlSpKamioaR6uvrY9q0aRg6dCiA3KaQGzduwMrKCtbW1sU6BjtqkrpgR01SJ+yoqVpMKgqQnJyMS5cuCff+sLa2Rps2bWBkZKSU/TOpIHXBpILUCZMK1WLzRwGMjIzQpUsXVYdBRESkNphUFCA1NRVXr15FZGQkAMDGxgYtW7aEvr6+iiMjIiIqnZhUKODr64v58+cjKSlJqEKTSCQwNjbG//73P3Tv3l3FERIREZU+TCpkXLlyBTNmzICZmRkmTpwIBwcHAMCDBw+wY8cOzJgxA+XKlUPLli1VHCkREVHpwo6aMoYPH46oqCjs378fpqamom3x8fHo27cvqlSpgs2bN3/UcdhRk9QFO2qSOmFHTdXi5Fcy7t69i379+sklFABgZmaGvn37Ijg4WAWRERERlW5MKmRkZWXBwMCgwO1lypRBVlZWCUZERESkHphUyLC1tcWpU6cUVp1JpVL4+fnB1ta25AMjIiIq5ZhUyOjTpw8CAwPxzTffIDg4GKmpqUhNTUVwcDAmTpyIwMBA9O3bV9VhEhERlToc/SFj6NChuHv3Lg4fPozz58+LtkmlUri7uwvTdBMREdF/OPqjANeuXcPJkyeFabptbGzQoUMHtGjRQin75+gPUhcc/UHqhKM/VIs1FQVo0aKF0hIIIiKiLwH7VBAREZFSfPE1FatWrSr2YyQSCSZMYLMFERFRfl98n4qaNWsW+zESiQQhISEfdVz2qSB1wT4VpE7Yp0K1vviaitOnT6s6BCIios/CF59UWFpaqjoEIiKizwI7ar5HUlISkpKSVB0GERFRqffF11QoEh8fj+XLl+PkyZNCQmFsbIxOnTph6tSpMDMzU3GEREREpQ+TChkvX77EgAEDEBsbCxsbGzRu3BgAEBYWhj179uDSpUvYs2cPypcvr+JIiYiIShcmFTJWrlyJ58+fY9GiRejVq5dom4+PD3788UesXLkSc+fOVU2AREREpRSTChnnz59H//795RIKAHB3d8fNmzdx7ty5Eo+LiIiotGNHTRmvX78udO6KWrVq4fXr1yUYERERkXpgTYUMMzMzPHjwoMDtDx8+ZEfNj2RmUgY929dDlzZ1Uae6BSwqmiAjMxv3wmKx5fA1bPG5JjdxjY62FkZ6tMKQHs1ga1keerraiH7+Bmf8Q/Hn1tOIfPZG7jgVTA0xdZgrOrWpA5vKZsjIzMbT2NfYd+IG1u+7hLcp6Qrjq2pdHtNHdED75g6oVN4Eb1PS8TjqJQ743cKfW898kueEPk/+165i545tCA4KQlJSIsqWLYvqNRzgOXQYnJxdAACZmZnYs2sHQkND8SDkPsLDw5GVlYmf585H7779VHwGRMXDpEKGk5MT9u7di6ZNm6Jr166ibSdPnsSePXvg4eGhoug+D707NMTKWQPx7GUizl9/iKjnb1DRzAjurvWx9mdPdGpdG4O/3yiU19TUwLF1k9CqYTWEPn6OvSduID0jC43r2GD8oLYY3L0Z2o34HaGPnwuPsalshgtbv4N5OWOcv/4QJy/fh56uNlxb1MSCbz0wsFtTuAxbhrT0TFFs7u3rw3vBCGRmZePYxbuIiHkNY0N92NtWhHv7+kwqqMiWL10C780bYV6pEtq2a4+ypqZ4Ex+P+/fvITDAX0gqUlNTsWTRAgBAuXLlUb58eTx//kyVoRN9MCYVMiZPnozz589j+vTpWLVqFWrUqAEgd/TH48ePUb58eUycOFHFUaq3R09foM+UtTh28Z6oRuLnVYdxcev38HBriF6uDXDodBAAwL1dfbRqWA1n/EPR/ZvVosfMHtcVs8Z2xdShrhg3d7uw/tvhbjAvZ4xf/voXC/4+JqzX0JDgyJqJaNfcAb07NMSOIwHCttrVKsN7wQiEPH4Oj0lrEPc6WRS3lhZbC6lo9u/dA+/NG9HT3QM/zZkHbR0d0fbMzP+SWX09Paxe+zccatZChQoV8dfqlVi7pvj3JCIqDfgtKcPc3Bz79+9Hjx49EBcXhxMnTuDEiRN4/vw5evbsiX379sHc3FzVYaq189cf4uiFu3JNHHGvk7F+3yUAgHOTGsJ6O6tyAIDjMkkIABw5FwwAKG9qKFqf95h/z98Rrc/JkeL4pXsAcptH8ps7qSd0tDUxcpa3XEIBAFlZOUU7QfqiZWRkYOWK5ahc2UJhQgEA2tra//2to4M2Ti6oUKFiSYZJ9EmwpkIBc3NzLFmyBFKpFPHx8QBy+1rk3QSMPp2srGzR/wBwPzy3WaNj69pYteOcKLHo4lwXAHDWX9wPJiT8OTq1roPOTnVw+0G0sF4ikaBj69rIzs7BuYCHwnqjMnro0qYOgh/G4MGTODSpUwWtGlaDpoYEoU/i4Hc1BJn5YiIqyNUrl/EmPh5Dhg6HREMDF86fQ9ijh9DV1UVdx3qo36ChqkMk+mSYVBRCIpGgXLlyqg7ji6GpqYHB3ZsDAE5e+e8usMcu3sWh00Ho5doAgXt/xFn/UGRkZqNhLWu0algNa3aew9o9F0T7+t37FLo41cGcCT3g0sQeQaFR0NHWhGuLWjAvb4xv5u0QJRsNa1lDU1MDkbHx2Lb4K/Tp2Ei0v8hn8Rj83QbcuB/5CZ8B+hzcu5tbO6ajq4sBfT0Q9uihaHvjJk2xdPkKdvimzxKTigKkpqYiJiYGCQkJCm+h27RpUxVE9XmbP9kddWtY4NjFu/C7Kr61/KDvNmDW2K7wGt0JtatVFtaf8Q/F7mOByM4WN028fPMWLsOWYd0cT7i7NkC75g4AgJycHGw6cAVn/UNF5SuaGQEAujrXReLbNAyfuRknL9+HsaEexvZ3xrQRHXBw1Tdo2Hs+Xie8+xSnT5+J+PjcIef/bN6IqtWqYfOW7ahZsxZiYqKx7LcluHrlEr6fNgUbvbeqOFIi5WNSISM1NRWLFi3CgQMHkJWVJbddKpVCIpEgJCREwaPpQ40f5IKpw1wR+vg5Rs3eItqmq6OFjb8MQ8fWtTF10R4cOReMlLRMtGxQFctm9MWpjVPhOWMjjpz7r/+ETWUz7PtjLPT1tOE+cQ2uBj2GgZ42ureth0XTeqN723poO3wZnsbmXgA0NHKbtrS0NPHtoj3Ye+IGACAhORWz/vRBVesK6OXaACN7t8bSTSdL6FkhdZSTk/sjRFNTE3+u+guWllYAgBr2Dli+YhXcu3dG4PUA3A66xaYQ+uwwqZAxb948HDx4EK6urmjatClMTExUHdJnb9wAZyyb0Q/3w5+h69gVeJOUItr+3ciO6NOxEaYv2YuN+y8L609evo/B329EwO6ZWPp9X1FSsX7eUDjaW6Jp/wW4+ygWAJD8Lg0b91+Gno42ls7oi1lju2DMz9sA5CYPQG5Nhu//d/7Mz+fMbfRybYCmdaoo/fzp82JklFvrVbNWbSGhyKOvr49Wrdvg4P59uHsnmEkFfXaYVMjw8/ODh4cHFi5cqOpQvggTB7fFb9/3xd1Hseg6dgVevnkrVyavM+b564/ktt15GIP4xHeoYlEOZiZlEJ/4DoYGunBuUgOvE94JCUV+5wNz27gb1rIR1j2MiAMApKVnyc1dAQAJ/5/o6Olqy20jys/Wzg7Af8mFLGNjYwBAWpriydeI1BmHlMqQSqVo2JC/HkrC9BFu+O37vggKjULnMX8qTCgAQFc7N/eVHTYK5M60aWSgBwDIyMwS1gGAcRk9aGtpyj0mbz955QEgIuY1Hke9hIG+Duys5O9AW6d6bj+OiFhO0U6Fa96iJSQSCR6HhyMnR34Yctij3OTY0spKbhuRumNSIaNJkyaFTtNNyuH1dWfMn9ILN+5HouvYlYV2frx8KwwAMGNURyFhyDN7XFdoa2si8G6EMO12fOI7hDx+Bm1tTcwc01lUXldHC16jc9edCxC/zmt3544g+XWKOzQ1//toWFYsi4me7QFA6GtBVBALC0u4tG2HZ89isX2ruH/QlcuXcOXyJRgZG6N1GycVRUj06UikioY2fMHCwsIwdOhQzJkzB506dfpkx8mb80KvwYRPdozSyrNHc2yYNxRZWdn4a9d5JL5NkyvzNPY1tvn6AwAsKpjg/JbvYFXJFBExr3DySgjS0jPRsn5VNHW0RUpqBrqOWwn/4CfC49s1d8DBFeOgq6ONgOAnuBb8BHq62ujUujaqWJRDWOQLuAxbhvjE/5IZTU0NHFzxDTq0qoV7YbE4F/AAhgZ66NGuHsxMyuDPrafh9fvBT/8ElTJpQatFy6mZ/Mp4n7jnzzHMcyCeP3+G5i1aomatWoiJjsHZM36QSCRY/NvvcOv43/fLxvV/I+LJYwDAg9AQPHgQigYNGsKmii0AoGGjxrwPSBHpa+d+t/LSphpMKhQ4c+YMJk6ciAoVKsDa2hoaGuIKHYlEgn/++eejjvElJxWzxnbF7HFdCy1zIfAROn39p7Bc3tQQ00d0QOc2dWBrWQ4aGhI8f5WEcwEPscz7lNAnIr+6NSzw7TA3ODWuDvPyxsjOzsGTmNc4ci4Yv3v7IfFtqtxjtLU0MXFwWwzu3hzVrMsjKzsHdx7GYN2eC9hz/MuspWBS8WHi4+Ox7q/VOH/2DF6+fAlDwzJo2LgJRo0eC8d69URlR40YisDrAQXsCejp7oFfFiz61CF/FphUqBaTChkXLlzAhAkTkJmZCUNDQ6FTlawzZz7uxlJfclJB6oVJBakTJhWqxdEfMpYtW4YKFSpgzZo1qFmzpqrDISIiUhvsqCnjyZMnGDZsGBMKIiKiYmJSIaNixYoKh4ERERFR4ZhUyOjbty8OHz6scIpuIiIiKhj7VMioX78+Tp8+jX79+sHT0xNWVlbQ1JSfQIk3FCMiIhLj6A8Zsn0p8kZp5FHWDcU4+oPUBUd/kDrh6A/VYk2FDN7zg4iI6MMwqZDh4eGh6hCIiIjUEjtqEhERkVKwpkKB9PR0bN68GSdPnkRkZCQAwMbGBp06dcKIESOgq6ur4giJiIhKH3bUlPH27VsMGzYM9+/fh76+PmxsbAAAUVFRSElJQe3atbFlyxYYGsrfhrs42FGT1AU7apI6YUdN1WLzh4zVq1fj/v37mDRpEq5evQofHx/4+Pjg6tWrmDx5Mu7fv481a9aoOkwiIqJSh0mFjFOnTqF79+6YMGEC9PT0hPW6uroYP348unXrhhMnTqgwQiIiotKJSYWMuLg4NG7cuMDtTZo0wYsXL0owIiIiIvXApEKGsbExoqOjC9weFRUFIyOjEoyIiIhIPTCpkNGsWTPs2LEDQUFBctvu3LmDnTt3okWLFiUfGBERUSnH0R8yHj9+jL59+yItLQ2tW7dGjRo1AABhYWG4dOkSDAwMsGfPHlStWvWjjsPRH6QuOPqD1AlHf6gWkwoFQkJCMH/+fNy4cUO0vkmTJpg9e7bc/UE+BJMKUhdMKkidMKlQLSYVhYiPjxf6V1hZWcHMzExp+2ZSQeqCSQWpEyYVqsUZNQthZmam1ESCiIjoc8aOmkRERKQUrKkAUKdOHaE5oqju3r37iaIhIiJST0wqADRq1Ei0nJWVhVu3bsHBwQHGxsYqioqIiEi9MKkAsHXrVtFyfHw8WrVqBS8vL7Rs2VJFUREREakX9qlQoLhNIURERMSkgoiIiJSESQUREREpBZMKIiIiUgomFURERKQUHP0B4H//+59oOSMjAxKJBN7e3jh69KhceYlEgnnz5pVUeERERGqB9/4Ain2DMIlEgpCQkI86Ju/9QeqC9/4gdcJ7f6gWayoAnD59WtUhEBERqT0mFQAsLS1VHQIREZHaY0fN98jMzMT169eRnJys6lCIiIhKtVJdU+Hq6lrsx0gkEvj5+SkthsTERAwbNgybNm3ilN1ERESFKNVJRUxMTLGmzJZKpZ9kim12+CEiInq/Up1UALygExERqYtSnVSEhoaqOgQiIiIqInbUfA89PT14eHigYsWKqg6FiIioVFPLya+Cg4Nx+PBhPH78GKmpqfD29saxY8cAAG5ubjA0NFRxhO/Hya9IXXDyK1InnPxKtUp184ciy5Ytw4YNGwD81zFTV1cXGzduRFhYGKRSKTw8PFQcJRER0ZdHrZKKw4cPY/369Qq3tW/fHo8ePcKJEyc+OqkIDg7Gli1bEBERgYSEBLmMV9nDVomIiD4HapVUbNu2DQBQtWpVdO/eHStWrBC2VatWDQAQHh7+Ucfw9fXFjBkzoKmpCTs7O1SuXPmj9kdERPSlUKuk4tGjR5BIJPj2229hZmYm2lahQgUAwMuXLz/qGH/99RdsbGzwzz//oFKlSh+1LyIioi+JWo7+0NCQDzsuLg4AoKX1cXlSVFQUBg8ezISCiIiomNQqqbCzswMArF+/Hq9evRLWx8TEYMOGDZBIJEIzyIeqUKECew0TERF9ALVKKnr06AGpVIrbt29j6tSpwrBMNzc3oS9Fz549P+oY7u7uOHHixEfHSkRE9KVRq6Ri6NChaNGiBaRSqag2IW+5ZcuWGDRo0Ecdw93dHVKpFOPGjcPVq1cRFRWF2NhYuX9EREQkpnaTX2VlZeGff/6Br68vIiIiAAC2trbo0aMHhg0bBm1t7Y/af82aNSGRSN57c7KQkJCPOg4nvyJ1wcmvSJ1w8ivVUqvRH0BuR8xRo0Zh1KhRn2T/EyZM+CR3OiUiIvrcqV1SAQAJCQm4cOECoqKiAADW1tZwcnKCqanpR+970qRJH70PIiKiL5HaJRXr16/HqlWrkJGRIVqvo6ODCRMmYMyYMSqKjIiI6MumVknFP//8g2XLlinclp6ejuXLl0NXVxfDhw//6GP5+vri5MmTiIyMBADY2NigU6dO6N69+0fvm4iI6HOkVh01XV1dERMTAwBo1KgR6tWrB4lEgtu3b+PmzZsAAEtLS5w+ffqDj5GZmYkJEybg4sWLkEqlMDQ0hEQiQXJyMiQSCZycnLBmzZqPnmSLHTVJXbCjJqkTdtRULbWqqXj58iUkEglGjBiBH374QbRt8eLF2Lx5s2hSrA+xfv16XLhwAb1798akSZOEe388f/4cq1atwr59+7BhwwaMGzfuo45DRET0uVGreSrs7e0BAC1btpTblrfuY2fU9PX1hYuLCxYsWCC6mVilSpUwf/58ODs7w8fH56OOQURE9DlSq6Tiu+++g6amJo4cOSJXtXXkyBFoampiypQpH3WMmJgYODs7F7jdxcVFaIIhIiKi/5Tq5o+ZM2fKratSpQp8fX0RGBiIunXrAgDu3buH2NhYWFtb48SJE3BxcfngY+rr6+P169cFbn/9+jX09fU/eP9ERESfq1KdVBw8eLDAiaiePXuGZ8+eidZFRUUhKioKCxYs+OBjNmjQADt37oSHhwesra1F22JjY7Fr1y40bNjwg/dPRET0uSrVSQVQ/B68Hzsb5vjx4+Hp6YmePXvC3d0dNWrUAACEhYXBx8cHmZmZGD9+/Ecdg4iI6HNUqoeUBgQEfNDjmjVr9lHHvXDhAubMmSN34zBLS0vMmTMHTk5OH7V/gENKSX1wSCmpEw4pVa1SnVSoUk5ODu7duydMBW5jY4PatWtDQ0M5fVuZVJC6YFJB6oRJhWqV+uaPgrx79w7JycnIycmR22ZhYfHR+9fQ0ICjoyMcHR0/el9ERERfArVLKnx8fPDXX3/h6dOnCrdLJBLcv3+/WPt0dXUtVnmJRAI/P79iPYaIiOhzp1ZJhZ+fH3744QdIJBKlVm1paGgUqYNnWloaXrx4wVujExERKaBWScXWrVsBAKampoiPj4dEIkGNGjUQFxeHxMRE2NnZoXz58sXe76lTpwrdLpVKceDAAaxYsQIAULNmzeIHT0RE9JlTqxk1Q0NDIZFIMGPGDGHdnDlzcO7cObRu3RqJiYn46aeflHrMixcvwt3dHbNnz4ZEIsGiRYtw4MABpR6DiIjoc6BWScW7d+8A5A7tzGuCyMzMhL6+PoYNG4b4+Hj8+uuvSjlWSEgIvvrqK4wZMwaxsbH49ttvcfLkSfTq1YvNH0RERAqoVfOHoaEhEhMTkZ2dDSMjIyQnJ+Py5cto3rw5Hjx4AAC4ffv2Rx3j2bNnWL58OY4cOQINDQ0MGTIE48ePh6mpqTJOgYiI6LOlVkmFubk5EhMT8fbtW9jb2yMwMBDr16/H3r17kZCQAIlEAjMzsw/a99u3b/HXX39h27ZtSE9PR+fOnTF9+nS5qbqJiIhIMbVKKmrXro0HDx4gIiICffv2RWBgIAAgISFBGA3Sv3//Yu/X29sba9euRWJiIho1aoQffvgB9erVU2rsREREnzu1mlEzLi4Oz58/R/ny5WFpaQlvb29s27YNcXFxsLCwwIABAzBixIhiz3pZs2ZNSCQSODo6on379u8tL5FIMHbs2A89DWEfAGfUpNKPM2qSOuGMmqqlVknFp1LcIaISiQQhISEfdUwmFaQumFSQOmFSoVpq1fxRmL1798LX1xcSiQT//PNPsR67ZcuWTxQVERHRl+OzSSoiIyMREBDwQcM9P/aupkRERKRm81QQERFR6cWkgoiIiJTis2n+UFeyneCISru8jnBERLJKfVJx/fr1IpWLjY39xJEQERFRYUr9kNK8OSSKQiqVKmW4Z0ng/UOIiD6dUn5p+2yV+pqKPO97g/AiTUREpFqlPqmwsLBQdQif1PF7L1QdAlGhOtepKFquNv2oiiIher/wZV1VHcIXrdQnFWfOnFF1CERERFQEHFJKRERESsGkgoiIiJSCSQUREREpBZMKIiIiUgomFURERKQUTCqIiIhIKdQ+qcjMzFR1CERERAQ1mKdCVlZWFry9vXH48GE8fvwYOTk5uHXrFubOnQupVIrJkyejcuXKqg6TiIjoi6NWSUV6ejpGjx6NwMBAAP/d60NXVxexsbHw9/dH9erVMWrUKBVHSkRE9OVRq+aP9evX4/r165BKpXL3AmnVqhWkUinOnj2rouiIiIi+bGqVVBw5cgQSiQRt27bF2rVrRduqVKkCAIiOjlZFaERERF88tWr+iImJAQAMHToUenp6om3GxsYAgNevX5d4XERERKRmNRX6+voAgBcv5O/s+eDBAwCAoaFhicZEREREudQqqahTpw6kUimWL1+OixcvCusPHTqENWvWQCKRwNHRUYUREhERfbnUKqnw9PQEALx8+RLr1q2DRCIBAMycORNJSUmiMkRERFSy1CqpcHNzwzfffCOM/sj/DwDGjx8PFxcXFUdJRET0ZVKrjpoAMGXKFLRv3x6+vr6IiIgAANja2qJ79+6oV6+eaoMjIiL6gqldUgEAjo6O7DtBRERUyqhVUhEbG1ukchYWFp84EiIiIpKlVklF+/bthc6ZBZFIJLh//34JRURERER51CqpACA3PTcRERGVDmqVVDRt2lRuXUJCgnC30kqVKsHa2loFkREREZFaJRVbt25VuD46OhpjxoxBXFwcfvzxxxKOioiIiAA1m6eiIFZWVhg8eDDevXuHJUuWqDocIiKiL9JnkVRkZ2fj+vXrAIBbt26pOBoiIqIvk1o1f7i6usqty8nJQUJCAtLS0gAAZcqUKemwiIiICGqWVMTExCgcUpp/REifPn1KMiQiIiL6f2qVVACKh5QaGRnBxsYGAwYMQL9+/VQQFREREalVUhEaGqrqEIiIiKgAapNUpKamYt68eZBIJHB1dVXYv+JTun79OhISEtCyZUsYGhqW6LGJiIjUgdokFfr6+jh69CgyMjLQpUuXT3acdevWwd/fH5s2bRLWTZw4EadPnwYAVKpUCbt27YK5ufkni4GIiEgdqdWQ0po1awIAEhMTP9kxTpw4gSpVqgjLV65cgZ+fH7p3747p06cjISEBGzdu/GTHJyIiUldqlVR8//330NHRwcqVK/H06dNPcozY2FhUrVpVWPbz80P58uWxePFijB49GgMGDMD58+c/ybGJiIjUmdo0fwDAihUrYGJigqdPn6Jr166oUqUKypUrJxpmKpFI8M8//3zwMVJSUmBgYCAs37hxAy1btoSGRm7+Vb16dezevfvDT4KIiOgzpVZJRUBAACQSCSQSCbKzs/HkyRM8efJE2C6VSt97a/T3qVChgrDPV69e4eHDhxg8eLCwPTk5GVpaavW0ERERlYhSf3XMm367Vq1aAMTzVHyK26C3atUKO3bsQNmyZeHv7w8NDQ04OzsL2588eYJKlSop/bhERETqrtQnFUOHDoWGhga2bdsmjMD4lCZOnIjAwEAsXbpUWK5cuTIAICsrC6dOnULnzp0/eRxERETqptQnFcB/NRKWlpaf/Fjm5uY4cuQIwsLCYGRkBAsLC2FbWloafvnlF2EUChEREf1HLZKKkqapqQkHBwe59YaGhnBzc1NBRERERKWf2iQVISEhyM7OLlLZpk2bfvBxYmNji1Qufw0GERERqVFSMX/+/CKVk0gkuH///gcfp3379kUaQRISEvLBxyAiIvocqU1S8SlGeigyYcIEuaQiKysLUVFR8PPzQ82aNeHk5FQisRAREakTtUkqypcvDx0dnU9+nEmTJhW4LSIiAgMGDMD48eM/eRxERETqRm2SihUrVqBRo0YqjcHW1hYDBw7EqlWr4OLiotJYiIiIShu1uvdHaWBhYYFHjx6pOgwiIqJSh0lFMV27dk10bxAiIiLKVeqbP/KGburq6pbI8Q4dOqRwfUJCAq5evYoLFy5g4MCBJRILERGROin1ScWZM2dK9HheXl6QSCQKR5toaWmhf//+mDFjRonG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",
241
+ "text/plain": [
242
+ "<Figure size 600x500 with 1 Axes>"
243
+ ]
244
+ },
245
+ "metadata": {},
246
+ "output_type": "display_data"
247
+ }
248
+ ],
249
+ "source": [
250
+ "import os\n",
251
+ "import json\n",
252
+ "import torch\n",
253
+ "import numpy as np\n",
254
+ "import evaluate\n",
255
+ "import matplotlib.pyplot as plt\n",
256
+ "import seaborn as sns\n",
257
+ "from sklearn.metrics import confusion_matrix, accuracy_score\n",
258
+ "from transformers import (\n",
259
+ " AutoTokenizer, \n",
260
+ " AutoModelForSequenceClassification, \n",
261
+ " DataCollatorWithPadding, \n",
262
+ " Trainer, \n",
263
+ " TrainingArguments, \n",
264
+ " set_seed\n",
265
+ ")\n",
266
+ "from datasets import load_dataset\n",
267
+ "from tqdm import tqdm\n",
268
+ "from sklearn.metrics import classification_report\n",
269
+ "\n",
270
+ "# ==========================================\n",
271
+ "# 1. 全局配置与参数\n",
272
+ "# ==========================================\n",
273
+ "seed = 56 # 最佳种子\n",
274
+ "lang = \"en\" # 语言设置\n",
275
+ "model_checkpoint = \"gpt2\"\n",
276
+ "\n",
277
+ "# 路径设置\n",
278
+ "final_model_path = f\"./best_model_seed_{seed}\"\n",
279
+ "checkpoint_dir = f\"./checkpoints_seed_{seed}\"\n",
280
+ "\n",
281
+ "# 设置随机种子\n",
282
+ "set_seed(seed)\n",
283
+ "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
284
+ "\n",
285
+ "# ==========================================\n",
286
+ "# 2. 模型与分词器初始化\n",
287
+ "# ==========================================\n",
288
+ "print(f\"Initializing model: {model_checkpoint}...\")\n",
289
+ "tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)\n",
290
+ "tokenizer.pad_token = tokenizer.eos_token\n",
291
+ "\n",
292
+ "model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=2)\n",
293
+ "model.config.pad_token_id = model.config.eos_token_id\n",
294
+ "\n",
295
+ "# 定义分词函数\n",
296
+ "def tokenize_short_function(example):\n",
297
+ " return tokenizer(\n",
298
+ " example[\"sentence1\"],\n",
299
+ " example[\"sentence2\"],\n",
300
+ " truncation=True,\n",
301
+ " max_length=256, # Short 子集无截断\n",
302
+ " padding=\"max_length\"\n",
303
+ " )\n",
304
+ "\n",
305
+ "def tokenize_full_function(example):\n",
306
+ " return tokenizer(\n",
307
+ " example[\"sentence1\"],\n",
308
+ " example[\"sentence2\"],\n",
309
+ " truncation=True,\n",
310
+ " max_length=512, # Full 子集覆盖 97%\n",
311
+ " padding=\"max_length\"\n",
312
+ " )\n",
313
+ "\n",
314
+ "# ==========================================\n",
315
+ "# 3. 训练阶段 (PAWS-X)\n",
316
+ "# ==========================================\n",
317
+ "print(f\"Loading PAWS-X ({lang}) dataset...\")\n",
318
+ "raw_datasets = load_dataset('paws-x', lang)\n",
319
+ "tokenized_datasets = raw_datasets.map(tokenize_short_function, batched=True)\n",
320
+ "data_collator = DataCollatorWithPadding(tokenizer=tokenizer)\n",
321
+ "\n",
322
+ "def compute_metrics(eval_pred):\n",
323
+ " predictions, labels = eval_pred\n",
324
+ " predictions = np.argmax(predictions, axis=1)\n",
325
+ " return {'accuracy': (predictions == labels).sum() / len(labels)}\n",
326
+ "\n",
327
+ "training_args = TrainingArguments(\n",
328
+ " output_dir=checkpoint_dir,\n",
329
+ " learning_rate=1e-5,\n",
330
+ " lr_scheduler_type=\"constant_with_warmup\",\n",
331
+ " warmup_ratio=0.1,\n",
332
+ " optim='adamw_torch',\n",
333
+ " weight_decay=0.0,\n",
334
+ " seed=seed,\n",
335
+ " per_device_train_batch_size=64,\n",
336
+ " per_device_eval_batch_size=64,\n",
337
+ " num_train_epochs=4,\n",
338
+ " eval_strategy=\"epoch\", # 新版transformers使用eval_strategy\n",
339
+ " save_strategy=\"epoch\",\n",
340
+ " logging_strategy=\"epoch\",\n",
341
+ " load_best_model_at_end=True,\n",
342
+ " save_total_limit=1,\n",
343
+ " metric_for_best_model=\"accuracy\"\n",
344
+ ")\n",
345
+ "\n",
346
+ "trainer = Trainer(\n",
347
+ " model=model,\n",
348
+ " args=training_args,\n",
349
+ " train_dataset=tokenized_datasets[\"train\"],\n",
350
+ " eval_dataset=tokenized_datasets[\"validation\"],\n",
351
+ " data_collator=data_collator,\n",
352
+ " tokenizer=tokenizer,\n",
353
+ " compute_metrics=compute_metrics,\n",
354
+ ")\n",
355
+ "\n",
356
+ "# --- 开始训练 ---\n",
357
+ "print(f\"\\n>>> Starting training with Seed {seed}...\")\n",
358
+ "trainer.train()\n",
359
+ "\n",
360
+ "# --- 保存最佳模型 ---\n",
361
+ "print(f\"\\n>>> Saving best model to: {final_model_path} ...\")\n",
362
+ "trainer.save_model(final_model_path)\n",
363
+ "tokenizer.save_pretrained(final_model_path)\n",
364
+ "\n",
365
+ "# ==========================================\n",
366
+ "# 4. 推理与绘图函数\n",
367
+ "# ==========================================\n",
368
+ "\n",
369
+ "def run_inference_detailed(test_dataset, batch_size=64):\n",
370
+ " \"\"\"\n",
371
+ " 运行推理并返回原始的 (预测值列表, 真实标签列表)\n",
372
+ " \"\"\"\n",
373
+ " preds = []\n",
374
+ " labels = []\n",
375
+ " \n",
376
+ " # 确保模型在GPU上并处于评估模式\n",
377
+ " model.to(device)\n",
378
+ " model.eval()\n",
379
+ " \n",
380
+ " print(\"Executing inference...\")\n",
381
+ " # 禁用 tqdm 防止日志混乱\n",
382
+ " for i in tqdm(range(0, len(test_dataset), batch_size), desc=\"Predicting\", disable=True):\n",
383
+ " batch = test_dataset[i : i + batch_size]\n",
384
+ " \n",
385
+ " inputs = {\n",
386
+ " \"input_ids\": torch.tensor(batch[\"input_ids\"]).to(device),\n",
387
+ " \"attention_mask\": torch.tensor(batch[\"attention_mask\"]).to(device),\n",
388
+ " }\n",
389
+ " batch_labels = batch[\"label\"] \n",
390
+ "\n",
391
+ " with torch.no_grad():\n",
392
+ " outputs = model(**inputs)\n",
393
+ " # 获取 logits 的 argmax\n",
394
+ " batch_preds = torch.argmax(outputs.logits, axis=-1).cpu().numpy() \n",
395
+ "\n",
396
+ " preds.extend(batch_preds)\n",
397
+ " labels.extend(batch_labels)\n",
398
+ " \n",
399
+ " return np.array(preds), np.array(labels)\n",
400
+ "\n",
401
+ "def plot_and_save_confusion_matrix(preds, labels, dataset_name=\"Protein Short\"):\n",
402
+ " \"\"\"\n",
403
+ " 绘制混淆矩阵,并打印分类报告\n",
404
+ " \"\"\"\n",
405
+ " # 1. 计算准确率\n",
406
+ " acc = accuracy_score(labels, preds)\n",
407
+ " print(f\"[{dataset_name}] Raw Accuracy: {acc:.4f}\")\n",
408
+ " \n",
409
+ " # 2. 检查翻转\n",
410
+ " is_flipped = False\n",
411
+ " if acc < 0.5:\n",
412
+ " print(f\">>> Detected Label Inversion (Acc < 0.5). Rectifying...\")\n",
413
+ " preds = 1 - preds\n",
414
+ " acc = accuracy_score(labels, preds)\n",
415
+ " print(f\"[{dataset_name}] Rectified Accuracy: {acc:.4f}\")\n",
416
+ " is_flipped = True\n",
417
+ " \n",
418
+ " # ================= [新增] 打印详细分类报告 =================\n",
419
+ " print(f\"\\n>>> Classification Report for {dataset_name}:\")\n",
420
+ " # target_names 对应 0 和 1 的含义\n",
421
+ " report = classification_report(labels, preds, target_names=['Non-Homologous', 'Homologous'], digits=4)\n",
422
+ " print(report)\n",
423
+ " print(\"=\"*40)\n",
424
+ " # ========================================================\n",
425
+ "\n",
426
+ " # 3. 计算混淆矩阵\n",
427
+ " cm = confusion_matrix(labels, preds)\n",
428
+ " \n",
429
+ " # 4. 绘图\n",
430
+ " sns.set_theme(style=\"white\", font_scale=1.2)\n",
431
+ " plt.figure(figsize=(6, 5))\n",
432
+ " \n",
433
+ " class_names = ['Non-Homologous', 'Homologous']\n",
434
+ " \n",
435
+ " sns.heatmap(\n",
436
+ " cm, \n",
437
+ " annot=True, \n",
438
+ " fmt='d',\n",
439
+ " cmap='Blues',\n",
440
+ " cbar=False, \n",
441
+ " xticklabels=class_names,\n",
442
+ " yticklabels=class_names,\n",
443
+ " linewidths=1.5,\n",
444
+ " linecolor='black',\n",
445
+ " square=True\n",
446
+ " )\n",
447
+ " \n",
448
+ " plt.ylabel('True Label', fontsize=12, fontweight='bold')\n",
449
+ " plt.xlabel('Predicted Label', fontsize=12, fontweight='bold')\n",
450
+ " \n",
451
+ " plt.title(f'Confusion Matrix: Protein Homology Detection\\nAccuracy: {acc:.2%}', \n",
452
+ " fontsize=14, pad=15, fontweight='bold')\n",
453
+ " \n",
454
+ " plt.tight_layout()\n",
455
+ " \n",
456
+ " filename = f\"confusion_matrix_{dataset_name.replace(' ', '_')}_seed{seed}.png\"\n",
457
+ " plt.savefig(filename, dpi=300)\n",
458
+ " print(f\">>> Confusion Matrix saved to: {filename}\")\n",
459
+ " \n",
460
+ " return acc\n",
461
+ "\n",
462
+ "# ==========================================\n",
463
+ "# 5. 测试阶段 (BioPAWS)\n",
464
+ "# ==========================================\n",
465
+ "result = {\"seed\": seed, \"lang\": lang}\n",
466
+ "\n",
467
+ "# --- 测试集 1: Protein Pair Short (主要关注) ---\n",
468
+ "print(\"\\n>>> Loading BioPAWS (Short)...\")\n",
469
+ "raw_datasets_short = load_dataset('dnagpt/biopaws', 'protein_pair_short')['train'].train_test_split(test_size=0.3, seed=seed)\n",
470
+ "tokenized_short = raw_datasets_short.map(tokenize_short_function, batched=True, num_proc=4)\n",
471
+ "\n",
472
+ "print(\">>> Running inference on Protein Pair Short...\")\n",
473
+ "preds_short, labels_short = run_inference_detailed(tokenized_short[\"test\"])\n",
474
+ "\n",
475
+ "# 绘制混淆矩阵并获取准确率\n",
476
+ "acc_short = plot_and_save_confusion_matrix(preds_short, labels_short, dataset_name=\"Protein Pair Short\")\n",
477
+ "result[\"protein_pair_short\"] = {\"accuracy\": acc_short}\n",
478
+ "\n",
479
+ "\n",
480
+ "# --- 测试集 2: Protein Pair Full (可选,仅计算指标) ---\n",
481
+ "print(\"\\n>>> Loading BioPAWS (Full)...\")\n",
482
+ "raw_datasets_full = load_dataset('dnagpt/biopaws', 'protein_pair_full')['train'].train_test_split(test_size=0.3, seed=seed)\n",
483
+ "tokenized_full = raw_datasets_full.map(tokenize_full_function, batched=True, num_proc=4)\n",
484
+ "\n",
485
+ "print(\">>> Running inference on Protein Pair Full...\")\n",
486
+ "preds_full, labels_full = run_inference_detailed(tokenized_full[\"test\"])\n",
487
+ "\n",
488
+ "# 这里我们只计算翻转后的准确率,不画图了\n",
489
+ "acc_full = accuracy_score(labels_full, preds_full)\n",
490
+ "if acc_full < 0.5:\n",
491
+ " acc_full = 1 - acc_full\n",
492
+ "\n",
493
+ "result[\"protein_pair_full\"] = {\"accuracy\": acc_full}\n",
494
+ "\n",
495
+ "# ==========================================\n",
496
+ "# 6. 最终结果输出\n",
497
+ "# ==========================================\n",
498
+ "print(\"\\n\" + \"=\"*30)\n",
499
+ "print(\"Final Results:\")\n",
500
+ "print(json.dumps(result, indent=2))\n",
501
+ "print(\"=\"*30)"
502
+ ]
503
+ },
504
+ {
505
+ "cell_type": "code",
506
+ "execution_count": null,
507
+ "id": "d330ffb3-6bcd-419a-b916-98fd12a8b922",
508
+ "metadata": {},
509
+ "outputs": [],
510
+ "source": []
511
+ }
512
+ ],
513
+ "metadata": {
514
+ "kernelspec": {
515
+ "display_name": "Python 3 (ipykernel)",
516
+ "language": "python",
517
+ "name": "python3"
518
+ },
519
+ "language_info": {
520
+ "codemirror_mode": {
521
+ "name": "ipython",
522
+ "version": 3
523
+ },
524
+ "file_extension": ".py",
525
+ "mimetype": "text/x-python",
526
+ "name": "python",
527
+ "nbconvert_exporter": "python",
528
+ "pygments_lexer": "ipython3",
529
+ "version": "3.12.3"
530
+ }
531
+ },
532
+ "nbformat": 4,
533
+ "nbformat_minor": 5
534
+ }
2-gpt_ft_test_explain/2-gpt2_test_protein.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
2-gpt_ft_test_explain/3-acc distribution.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
2-gpt_ft_test_explain/4-explain_layer_wise.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
2-gpt_ft_test_explain/4-explain_mutation awareness.ipynb ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "347af636-8135-484c-8a1a-3bad7e2ba82e",
7
+ "metadata": {},
8
+ "outputs": [
9
+ {
10
+ "name": "stdout",
11
+ "output_type": "stream",
12
+ "text": [
13
+ ">>> Loading model from ./best_model_seed_56...\n",
14
+ ">>> Running inference to get attention maps...\n",
15
+ "✅ Attention maps extracted successfully!\n",
16
+ "Seq Length: 12, Split at: 6\n",
17
+ "\n",
18
+ ">>> 🏆 Top Difference Detector Candidate: Layer 5, Head 9\n",
19
+ "Top 5 Candidates: [(5, 9, 1.2959465980529785), (5, 11, 1.2529085874557495), (4, 7, 1.064322590827942), (6, 2, 1.0418472290039062), (4, 10, 0.9984771609306335)]\n"
20
+ ]
21
+ }
22
+ ],
23
+ "source": [
24
+ "import torch\n",
25
+ "import numpy as np\n",
26
+ "import matplotlib.pyplot as plt\n",
27
+ "import seaborn as sns\n",
28
+ "from transformers import AutoTokenizer, AutoModelForSequenceClassification\n",
29
+ "\n",
30
+ "# ================= 配置 =================\n",
31
+ "MODEL_PATH = \"./best_model_seed_56\" # 确保路径正确\n",
32
+ "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
33
+ "\n",
34
+ "# 1. 加载模型(关键修改点)\n",
35
+ "print(f\">>> Loading model from {MODEL_PATH}...\")\n",
36
+ "tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)\n",
37
+ "\n",
38
+ "# 修改点:在加载时直接指定 output_attentions=True\n",
39
+ "model = AutoModelForSequenceClassification.from_pretrained(\n",
40
+ " MODEL_PATH, \n",
41
+ " num_labels=2, \n",
42
+ " output_attentions=True \n",
43
+ ")\n",
44
+ "\n",
45
+ "# 双重保险:强制修改 config\n",
46
+ "model.config.output_attentions = True \n",
47
+ "\n",
48
+ "model.to(device)\n",
49
+ "model.eval()\n",
50
+ "\n",
51
+ "# 2. 定义“找茬”探针输入\n",
52
+ "text_en_1 = \"The cat sat on the mat\"\n",
53
+ "text_en_2 = \"The mat sat on the cat\"\n",
54
+ "\n",
55
+ "# 示例序列 (假设之前的 demo 序列)\n",
56
+ "seq_bio_1 = \"MKLVINGFGRIGRLVTRAAF\" \n",
57
+ "seq_bio_2 = \"MKLVINLFGRIGRLVTRAAF\" \n",
58
+ "\n",
59
+ "def get_attention_maps(text1, text2):\n",
60
+ " # GPT-2 没有 token_type_ids,所以我们只取 input_ids 和 attention_mask\n",
61
+ " inputs = tokenizer(text1, text2, return_tensors='pt')\n",
62
+ " inputs = {k: v.to(device) for k, v in inputs.items()}\n",
63
+ " \n",
64
+ " with torch.no_grad():\n",
65
+ " # 这里虽然 config 改了,但为了保险再传一次参数\n",
66
+ " outputs = model(**inputs, output_attentions=True)\n",
67
+ " \n",
68
+ " # 调试检查:如果这里报错,说明模型真的没吐出 attention\n",
69
+ " if outputs.attentions is None:\n",
70
+ " raise ValueError(\"Model did not return attentions! Check config.\")\n",
71
+ " \n",
72
+ " # outputs.attentions 是一个 tuple,包含每一层的 attention tensor\n",
73
+ " # 形状: (batch, num_heads, seq_len, seq_len)\n",
74
+ " # stack 后形状: (num_layers, batch, num_heads, seq_len, seq_len)\n",
75
+ " # squeeze(1) 去掉 batch 维度 -> (num_layers, num_heads, seq_len, seq_len)\n",
76
+ " attentions = torch.stack(outputs.attentions).squeeze(1).cpu() \n",
77
+ " \n",
78
+ " return attentions, inputs['input_ids'][0]\n",
79
+ "\n",
80
+ "# 3. 获取注意力权重 (现在应该不会报错了)\n",
81
+ "print(\">>> Running inference to get attention maps...\")\n",
82
+ "attns_en, ids_en = get_attention_maps(text_en_1, text_en_2)\n",
83
+ "tokens_en = tokenizer.convert_ids_to_tokens(ids_en)\n",
84
+ "\n",
85
+ "print(\"✅ Attention maps extracted successfully!\")\n",
86
+ "\n",
87
+ "# 4. 自动筛选“找茬 Head”\n",
88
+ "# 找到 Sent1 和 Sent2 的分界线\n",
89
+ "# 注意:GPT-2 Tokenizer 对空格敏感,单独 encode text1 可能会少一个前导空格,导致长度差1\n",
90
+ "# 最稳妥的方法是看 inputs_ids\n",
91
+ "len_1 = len(tokenizer(text_en_1)['input_ids'])\n",
92
+ "len_total = len(ids_en)\n",
93
+ "\n",
94
+ "print(f\"Seq Length: {len_total}, Split at: {len_1}\")\n",
95
+ "\n",
96
+ "scores = []\n",
97
+ "\n",
98
+ "# 遍历中间层 (Layer 4-9)\n",
99
+ "for layer in range(4, 10): \n",
100
+ " for head in range(12):\n",
101
+ " attn_mat = attns_en[layer, head]\n",
102
+ " \n",
103
+ " # 提取 Cross Attention 区域: Sent2 关注 Sent1\n",
104
+ " cross_attn = attn_mat[len_1:, :len_1]\n",
105
+ " \n",
106
+ " # 计算对角线强度\n",
107
+ " min_len = min(cross_attn.shape)\n",
108
+ " if min_len > 0:\n",
109
+ " diag_score = cross_attn.diagonal(offset=0)[:min_len].sum().item()\n",
110
+ " scores.append((layer, head, diag_score))\n",
111
+ "\n",
112
+ "# 排序\n",
113
+ "scores.sort(key=lambda x: x[2], reverse=True)\n",
114
+ "if len(scores) > 0:\n",
115
+ " top_layer, top_head, top_score = scores[0]\n",
116
+ " print(f\"\\n>>> 🏆 Top Difference Detector Candidate: Layer {top_layer}, Head {top_head}\")\n",
117
+ " print(f\"Top 5 Candidates: {scores[:5]}\")\n",
118
+ " \n",
119
+ " # ================= 绘图代码准备 =================\n",
120
+ " # 把刚才跑出来的 top_layer 和 top_head 传给变量,方便下一个单元格绘图\n",
121
+ " TARGET_LAYER = top_layer\n",
122
+ " TARGET_HEAD = top_head\n",
123
+ "else:\n",
124
+ " print(\"❌ No valid attention scores found. Check sequence lengths.\")"
125
+ ]
126
+ },
127
+ {
128
+ "cell_type": "code",
129
+ "execution_count": 2,
130
+ "id": "db76380f-3d15-4d12-b267-2265a0a5ee23",
131
+ "metadata": {},
132
+ "outputs": [
133
+ {
134
+ "name": "stdout",
135
+ "output_type": "stream",
136
+ "text": [
137
+ ">>> Visualizing Layer 5, Head 9...\n"
138
+ ]
139
+ },
140
+ {
141
+ "data": {
142
+ "image/png": "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XhUqVEjDhw/XjRs3NGvWLMXGxsowDE2ZMkVt2rRJU3up8dRTT2nTpk26ePGipk+friFDhmjOnDm6evWqpDv38s0330y386XlXnt4eKhly5aqXbu2PD095e7uroiICK1atUoHDhxQTEyMXnrpJe3fv1+SNGXKFB05ckQzZ8402xo7dqwKFSokSamafz+t352kbNq0SQ899JD8/f21YcMGc6TL4cOH9f3336tXr16pvl/z5s0zXz/yyCN6+OGHVb16dR04cEDSnREjnTt3NuvYbDa1bNlSy5cvl3Tnb6RPnz6Kjo7Wf/7zH7Perl27dO3aNeXNm1cbN240y5s3b27+HTk4OKh69epq1KiRvL29VbBgQd28eVO7du3SypUrZRiG5syZoxdeeEGNGjVKMv7k/kaHDx+uX3/9VZLk4uKiXr16qXLlygoNDdWyZctkGIamTp2qBg0aqE+fPsne5zZt2qhp06b6/vvvzZEjf/zxh7Zu3aomTZpIkhYvXqx3333XPM7f318PP/ywzp07p3nz5unatWvauHGjhg8fri+++MK879evX5cklSpVSn379pWHh4dOnjypffv2aevWrWZ7165d04IFC8z3jz/+uOrXr6/IyEgdO3bMblQMgGzA0pQEAAAAAAA51N09fnv27GlMmTLFeOedd4zOnTvb7fPy8jJ7V999XJMmTRL1vL548aLh6Oho1unRo4fd/h49epj7HB0djYsXL5r7kurZntCePXvszv/bb7/Z7X/kkUfMfd26dTPLUztSwGazGX/++ae575VXXjH3eXp62h2XXiMFVq5cadeLes2aNUalSpUMSUbRokWNmzdvputIgdTsSyg2NtbYtm2bMXfuXGPatGnGlClTjMDAQLvzHj9+PNnrS+q+JFfnfr87d9+HRo0aGTExMYZhGEZMTIxRrFixZHu/p2Tbtm127S5btswwDMOYNGmSWebi4mL3HTYM+5E2NWvWNAzDMP744w9DkuHq6mp4eHgYkoy1a9cahmHYjXz58MMPE8Vx7Ngx49tvvzWmT59ufPDBB8aUKVOMkiVLmsdMmjQp2Xub1N9oRESE4eTkZNaZPXu23f4hQ4aY++rVq5ds2926dTPi4uLMNhN+dp988ol5XL169czyfv362Z1r6dKl5j4nJycjIiLCMAzDeOmll8zyoKCgRPfk0qVLxqVLl8zX8XXz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vXo1xeuSZLRt29a4ffu2XXujR49Osm6jRo0MLy+vJM9lGIbx2WefGU5OTsmeq0aNGsaZM2fsjpkzZ06K15aclD7nu/c1a9YsyXhatGhhHvPPP//Y7QsJCbE737hx48x9VapUMcuvX79utG3bNsV7HBgYmGLszZs3t3v/2GOPGYZhGH5+fim227NnT7PNsLAwu30bNmww992+fdvo0aNHim1Vr17dOH36tF2cCfc//PDDhs1mS3Rc4cKFjfPnzz/wZxZv+/btdnXat29vtz8t37G7z5fUFh9DbGys8fTTT6dY98knn0z0t7Jq1SojX758yR7z8ssvG4Zh/zea1NayZcts8zkCAAAAAIDsi+mDgP8qVqyYOnfurIoVK8rT01OOjo46deqUlixZooiICJ06dUpvv/22PvvssySP3759u0qVKqWnnnpKJ06c0DfffCNJio6O1scff6yZM2dKutOjePjw4eZxHh4eeuaZZ+Tg4KCvvvpKV65cSdfrstlsqlChgpo0aaKSJUuqUKFCunXrlg4ePKhly5bp9u3b+vXXX/Xdd9+pR48ekqT//Oc/ev/99+3uTf/+/XX16lXNnj1bMTExSZ4rJCREQ4cOVVxcnCSpSZMm6tChg65evap58+bp4sWL2r9/v/r166dffvklXa/zXjZt2qQ2bdqoadOm+v777xUaGipJ+uOPP7R161Y1adJEFStWVIsWLfTHH39Ikr755hv5+vqabSxatMh8HRAQYL4ePny4fv31V0mSi4uLevXqpcqVKys0NFTLli2TYRiaOnWqGjRooD59+iQZ38aNG1WzZk117txZhmHI0dFRBw4cMHuJOzg4qF+/fqpSpYouXryosLCwNC1S++6772rp0qXm+yZNmqh9+/Y6cOCAli1bJkk6cOCAnnrqKf32229JtrF582ZVq1ZN3bt31+7du80REBEREfrqq680evToVMeTkoceekh169bVnj17JN35jGJjY+Xo6Jjm71jFihU1ZcoU/fLLL2aP/UKFCmns2LF255OkyZMna/78+ZLu/N08/vjjqlu3rsLCwjR//nzdunVLy5Ytk4+Pj3n8sWPH9OSTT5q98202m7p06SIfHx9duHDB7l6+/vrrCg8P17vvvmuWvfDCC+aaAaVLl77nvclOnyMAAAAAAMiirM5KABnhfkYKGIZhREVFGb/++qvxxRdf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L774otzd3RUYGKjXX39dkrR06VIdPXpU7dq1U758+XT27Fnt2LFD27ZtU7NmzdStWzdJ0ksvvaTPP//cHLnTp08fLVmyRD4+Pvr3338VHBwsPz8/TZs2TdKddUScnZ3NeUlff/117dmzR87OzvLz8zMTP0nJyp8jAADAvfzyyy/m6/TqvPD+++8nmp4zMjJS//nPf/Sf//xHS5YsUUhIiPLmzathw4aZSYEVK1bo33//tRtZmfDfUr179zYTAocPH1b79u0Tdd46ceKE5s2bp8WLF2v+/Pl68sknk4wxKipKzZo108GDB+/7Ovv376/z58/LZrOpTJky8vf316uvvmp2PMkKfvjhB/Xp0ydRp5cDBw7owIEDWrBggdatW6fq1aub+3766SfNnj07UVtHjhzRkSNH9M0332jVqlVq27atuS88PFy+vr52I+w3btyo9u3bq2PHjmmO++jRo+ZrDw8PuxEZderUMV/funVL69atU9++fe2Oj08KSHcSCiQFAGQXJAWQK4SHhyfZAz9+GpyXX35Z8+bNMx+4L168WNKd+fOfeuopLVy4MN1iKVmypD766CMNHjxY0p1eJx999JGkO71ISpYsqQMHDkiynzLIxcVFL7/8srmYVHR0tFasWCHpTs/+48ePJzkd0euvv67evXtLutP7Of7hZPHixdWuXTszGZLQQw89pFGjRpnzIp45c0bvv/++JKl+/fo6deqUOVVJwhibNWum6dOn6+WXX9bt27d14sQJffzxx/d5p6zVr18/vfHGG+YIAOnOtSf1oHXatGkKCwvTr7/+Kkn67bffzAWK08Nff/2lv/76K8l9jRo1UsuWLe/ZxpgxY7R3715zuqitW7cmSnhVr15dCxYsePCA02DJkiV2U1zFc3Jy0oQJEzR27Fi78vv9jnXo0EF58uQxf6QkXDxtwIABcnd31+jRo3Xw4EHNnz9f0p1RMgkX3U5K2bJl9e2336pXr166evWqDMPQ999/b45mkGTXq8vFxUWPPvqo+be7e/du7d69W9Kdnm4pJQWkrPs5AgAApOT48eM6fvy4+f5e/+ZJrVKlSqlVq1YqW7asChUqJMMwFBYWpiVLligqKkqhoaH67LPPNHLkSHXt2lWlSpXSyZMndfPmTc2fP18vvfSSpDsjUDdt2mS2G98RKDY2Vt26dTN/SxYtWlR9+vSRp6en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ABKIH51sJZvNJm9vb3Xu3FnvvvuuHByyf39JR0dHRUZGWh1GrvHkk09m6JSoAJDRsv9/+QA8sDlz5pivH3/88XRpc8qUKeratauqVKkiT09POTs7q2DBgmrUqJHeeecdu97dLVu2NKe+SWqBrhkzZpj7PT09dfPmTXPflStXFBQUpMaNG6tAgQJycXFRmTJlNGDAAHO+0oTefPNNs61y5copIiJCL774okqVKiVHR0d99dVXabrOnj17asSIEYm2Jk2apKmdjHb06FG99NJLql69ujw8POTu7q4aNWpo9OjRunjxYqL6wcHBGjRokOrXr6/ixYvL1dVVefLkUaVKlRQQEGAmke527Ngx9e7dW56envLw8FCLFi3066+/3lfMixYtsvus161bp0mTJmnGjBl2UyTNmjVL586dszu2ZMmS8vX1lXRnmPjy5cvvKwYAAADkHH5+fjIMQ3FxcTp9+rT+7//+T4ULF7Y6LAAAMh0jBQDol19+MV83bdo0Xdp8//33FRERYVcWGRmp//znP/rPf/6jJUuWKCQkRHnz5tWwYcP0xx9/SLqztsG///6rQoUKmcctXbrUfN27d2+5ublJkg4fPqz27dsrPDzc7jwnTpzQvHnztHjxYs2fP19PPvlkkjFGRUWpWbNmOnjw4H1fZ//+/XX+/HnZbDaVKVNG/v7+evXVV1WmTJn7bjO9/fDDD+rTp4+uX79uV37gwAEdOHBACxYs0Lp161S9enVz308//aTZs2cnauvIkSM6cuSIvvnmG61atUpt27Y194WHh8vX11dnz541yzZu3Kj27durY8eOaY47fki3JHl4eNiNyKhTp475+tatW1q3bp369u1rd7yvr69CQkIk3UkoPPXUU2mOAQAAAAAAIKchKQDkcsePH9fx48fN9w0bNkyXdkuVKqVWrVqpbNmyKlSokAzDUFhYmJYsWaKoqCiFhobqs88+08iRI9W1a1eVKlVKJ0+e1M2bNzV//ny99NJLkqSzZ8+ac2NKUkBAgKQ7c2Z269bNTAgULVpUffr0kaenp9auXauQkBBFR0erX79+atCggSpUqJAoxosXL+rixYtq27atHn74YV24cEFeXl5pus6E9+7QoUM6dOiQ5s2bp59//jndEiwJrVmzJlHP/n///TfZ+mFhYerdu7du3LghSapZs6a6deumuLg4LVy4UMeOHdOpU6f0+OOPKzQ0VI6OjpLuPIRv2bKlateuLU9PT7m7uysiIkKrVq3SgQMHFBMTo5deekn79+83zzV06FC7hEDnzp1Vr149rV69Wj///HOar7VAgQLm66ioKIWFhal8+fKSlGikwr59+xId/9BDD5mvN27cmObzAwAAAAAA5EQkBYBcLn5xJElycXFJ80Px5OzevVuRkZEKCQnR8ePHFRUVperVq6tBgwbmqIC1a9dq5MiRcnJy0uDBg/X6669Lkr788kszKfDtt98qLi5OklS7dm0zabFq1SpzeiBHR0dt3rxZlStXliS9/vrrqlevnkJDQ3Xz5k1Nnz5dU6dOTTLOV155RR999FGar69w4cJq166dKlasqLi4OP3yyy/auXOnpDsjInr27KnDhw+boxrSy5IlS7RkyZJU1//000/NhECVKlW0Y8cOM6ahQ4eqdOnSio2N1YEDB7Rq1Sp16dJFkjRx4kTFxcVpx44dOnDggC5fviwvLy917NhRBw4ckHRnpMGJEydUunRpnTlzxu7Bf9++fTV//nxJ0htvvKF69eolOZ1TSjp37qwpU6aY79u1a6c+ffro33//TTTNU1KJkVKlSpmvw8PDFRcXlyPmiwVyqxds+S0790zjimXnRs7j/ey3VoeQ4f7+tLvVIWSo2LicP4/41Zu3rQ4hw52PjLY6hAxXoZiH1SFkqJu3Yq0OIcPdis35/39z5cYtq0PIcH+eTb4jX07Qv2Fpq0NIknu9oZad+8au6ZadO7VICgC53IULF8zXCafseRBxcXEaPXq0Pv74Y3OR2KScPHnSfP3ss89q0qRJio6OVmhoqLZt26bGjRtr2bJlZp34UQKStHnzZvN1bGysqlSpkux54qeQScobb7xxz+u527hx4zRr1iw5OzubZe+8846efvppLVy4UNKda/v111/16KOPprn99JTwPv39998pLrYbEhJiJgXWrVunZ555xm4kRFJOnjyp0qVLa+fOnXYLbSWcqsfZ2Vk9evTQhAkT0hR78+bNNWTIEH322WeS7iSw3nrrrSTruri4JCpLOD9sXFycIiIiVLRo0TTFAAAAAAAAkNPQZRJAuvvkk080ZcqUFBMCkhQd/b/eOUWLFlXv3r3N919++aXOnDljTh3k7OxsN2f8pUuXUh1PwsRHQkWKFLmvhcUqVqxolxCQJJvNpmHDhtmV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hnJwcFDZsmXVrl075c+fX0FBQerUqVNaQwQAAEBOk02G4cI6hmGoTZs2+uuvv1S5cuUHaiupod/ZZSg4AAAA+PlwL2lKCty6dUvh4eEqUqRIivVsNpvKli2bqjajoqJUrFgxSVKhQoV04cIFValSRbVr19aff/6ZlvAAAAAA5FIODg6qXLmyIiIiHjgpYBiGBgwYYPbyv3nzpl544QV5eHjY1Vu+fPkDnQcAAACwQpqSAs7Ozum++G/VqlV16NAhlStXTnXr1tX//d//qVy5cpo5c6aKFy+erucCAABANsWCv0iF9957T6+99po+//xz1apV677buXt9tL59+z5oaAAAAMhEDgwVSFGapw/q27evvvrqK7333nvpEsDLL7+sM2fOSJImTJigDh06aOHChXJxcdHcuXPveXx0dLSio6Ptypxux8rVyTFd4gMAAACQPfTr10/Xr19X3bp15eLikmhtgUuXLqWqnTlz5mREeAAAAECWkOakwO3btzV79mz9+uuvatCgQaIhtFOnTk1Tewl73TRo0EDHjh3TwYMHVaZMmXtOUyRJQUFBmjhxol3ZuHYPaUL7RmmKAwAAAED2Nm3aNKtDAAAAALK8NCcF9u3bp/r160uS/v77b7t96bH4lqurqxwcHOTomLqe/mPGjFFgYKBdmdNnox44DgAAAGQdLPKK1Lh72h8AAADkTvx8SFmakwIbNmxI1wBeeeUV1a5dW4MGDVJsbKxatGihLVu2KE+ePPrpp5/k5+eX4vGurq7mAmDxYpk6CAAAAMiVjhw5ojlz5ujIkSP6+OOPVaxYMa1evVplypRRzZo1rQ4PAAAAsJzD/R74zz//aO3atbpx44YkyTCM+2rn22+/Vd26dSVJK1euVHh4uA4ePKjhw4fr9ddfv9/wAAAAkJM42KzbkG38/vvvql27trZt26bly5fr2rVrkqQ9e/ZowoQJFkcHAACAzGKz2SzbsoM0JwUiIiLUpk0bValSRY888oi5SPCgQYP06quvpjmAixcvytvbW5L0888/68knn1SVKlU0cOBAhYaGprk9AAAAALnT6NGj9fbbb2vdunVycXExy1u3bq2tW7daGBkAAACQdaQ5KTB8+HA5Ozvr+PHjypMnj1nes2dPrVmzJs0BeHl5af/+/YqNjdWaNWvUrl07SdL169dTva4AAAAAAISGhqpbt26JyosVK6aLFy9aEBEAAACQ9aR5TYFffvlFa9euValSpezKK1eurGPHjqU5gICAAPXo0UPFixeXzWZT27ZtJUnbtm1TtWrV0tweAAAAcqBsMgwX1ipYsKDOnDmj8uXL25Xv2rVLJUuWtCgqAAAAZDZ+PqQszUmBqKgouxEC8S5dupRowd/UePPNN1WrVi2dOHFCPXr0MNtwdHTU6NGj09weAAAAgNypV69eGjVqlJYtWyabzaa4uDht3rxZI0aMUL9+/awODwAAAMgS0jx9UPPmzfX111+b7+P/sT158mS1atUq1e3cuHFDP/30kyTpiSee0Pnz5/Xhhx8qMDBQgYGB2rdvn/z9/dMaHgAAAHIgm4N1G7KPd999V9WqVVPp0qV17do11ahRQy1atFDTpk31xhtvWB0eAAAAMomDzWbZlh2keaTA5MmT1aZNG+3YsUMxMTEaOXKk/vrrL126dEmbN29OdTvz5s3TqlWr9Oijj0qSpk+frpo1a8rd3V2SdOjQIZUoUULDhw9Pa4gAAAAAciEXFxfNmjVL48aN0759+3Tt2jXVq1dPlStXtjo0AAAAIMtIc1KgVq1a+vvvvzV9+nTly5dP165dU/fu3fXiiy+qePHiqW5n4cKFGjlypF3ZN998owoVKkiSFixYoBkzZpAUAAAAAJAmZcqUUZkyZawOAwAAAMiS0pwUkKQCBQro9ddfT/NxM2fO1FNPPaV8+fLpn3/+Ue3atZOt26hRI7344ov3Ex4AAABymmwyDBfWGjhwYIr7Z8+enUmRAAAAwEr8ekhZmpMCf/zxR4r7W7Rokey+6dOnq1OnTsqXL58uX76s6Ohoc19ERIRcXFzM93FxcXb7AQAAACAl//77r937W7duad++fbp8+bJat25tUVQAAABA1pLmpICfn1+iMluCnluxsbHJHrtv3z7zdalSpbRv3z5VrVpVkuwSApK0d+9elSpVKq3hAQAAIAeyOdDXB/e2YsWKRGVxcXEaPHiwKlasaEFEAAAAsIKNkcYpckjrAf/++6/ddv78ea1Zs0YPPfSQfvnll1S388gjj2j8+PG6efNmon03btzQxIkT1alTp7SGBwAAAAAmBwcHBQYG6qOPPrI6FAAAACBLSPNIgQIFCiQqa9eunVxcXBQYGKidO3emqp2xY8dq6dKlqlq1qoYOHaoqVapIkg4dOqTp06fr9u3bGjt2bFrDAwAAQE5ETx88gCNHjuj27dtWhwEAAIBMwkDjlN3XQsNJ8fLy0qFDh9JUPyQkRIMHD9bo0aNlGIakO0M72rVrp88++0xeXl7pFR4AAACAHC4wMNDuvWEYOnPmjFatWqX+/ftbFBUAAACQtaQ5KbB371679/H/0H7vvffk4+OTprbKly+vNWvW6NKlS/rnn38kSZUqVZKnp2dawwIAAACQy+3atcvuvYODg4oWLaoPP/xQAwcOtCgqAAAAIGtJc1LAx8dHNpvN7Nkfr0mTJpo9e/Z9BeHp6alGjRrd17EAAADIBbLR+N8ZM2ZoypQpOnv2rOrWratPP/002X/rzpo1S19//bX27dsnSWrQoIHeffddu/oDBgzQvHnz7I7z9/fXmjVrMu4isqkNGzZYHQIAAACyABYaTlmakwJhYWF27+N737i5uaVbUAAAAEB2tGTJEgUGBmrmzJlq3Lixpk2bJn9/fx06dEjFihVLVD84OFi9e/dW06ZN5ebmpvfff1/t27fXX3/9pZIlS5r1OnTooDlz5pjvXV1dM+V6sqO9e/fq77//louLi6pWraqqVataHRIAAACQpaQ5KVC2bNmMiAMAAABIlpU9faKjoxUdHW1X5urqmuSD+alTp+rZZ59VQECAJGnmzJlatWqVZs+erdGjRyeqv3DhQrv3X375pb777jutX79e/fr1szuft7d3elxOjrV9+3YNGjRI+/fvt1uv7KGHHtK8efPM5MClS5eYrhQAACCHY6BAytKUFLh9+7Y++ugjLVq0yOx9U6VKFQUEBOi5555jWAYAAABynKCgIE2cONGubMKECXrzzTftymJiYrRz506NGTPGLHNwcFDbtm21ZcuWVJ3r+vXrunXrVqKH1sHBwSpWrJgKFSqk1q1b6+2331bhwoXv74JyoP3796tNmzaqXr26FixYoOrVq5vlH330kXx9fbVv3z59//33unTpkt544w3LYq1fPucnJFydHa0OIUPlhp+9cXdNF5wTOTnm/A8yp3+OueEzzOOSs///VMpWM0Tet50n/7U6hAxV2cvd6hBwH1KdFLhx44batWunLVu2qG3btmrRooUk6cCBAxoyZIhWrlypH3/8UWFhYdq4caMGDBiQUTEDAAAAmWbMmDEKDAy0K0tqlMDFixcVGxsrLy8vu3IvLy8dPHgwVecaNWqUSpQoobZt25plHTp0UPfu3VW+fHkdOXJEY8eOVceOHbVlyxY5Oub8hwWp8eabb6pdu3b67rvv7Doq+fj4qHfv3urevbtatWqlEydOaPXq1RZGCgAAAFgv1UmB9957TydOnNCuXbtUp04du3179uxRly5dNHz4cH333XcaNWpUugcKAACAXMzCbmTJTRWU3t577z0tXrxYwcHBdut19erVy3xdu3Zt1alTRxUrVlRwcLDatGmT4XFlBxs2bNDq1auTHLlss9k0duxYNW7cWKtXr1bLli0tiBAAAACZiRltUuaQ2oqLFy/W1KlTEyUEJKlu3br64IMP9Omnn8rf31/Dhg1L1yABAACArK5IkSJydHTUuXPn7MrPnTt3z/UAPvjgA7333nv65Zdfkvz3dkIVKlRQkSJF9M8//zxwzDnF1atXE43QSMjb21vOzs7y9/fPxKgAAACArCnVSYFjx46pUaNGye5v0qSJbDabvvrqq3QJDAAAADDZbNZtqeTi4qIGDRpo/fr1ZllcXJzWr18vX1/fZI+bPHmy3nrrLa1Zs0YNGza853lOnjypiIgIFS9ePNWx5XRly5bV9u3bk92/bds2lS1bNhMjAgAAgJUcbNZt2UGqkwL58+fX+fPnk91/9uzZRAuiAQAAALlJYGCgZs2apXnz5unAgQMaPHiwoqKi9P/t3Xl8TGf7x/HvJJEEIZoidgmxL7WVoqUtGjut2rpYivbRKpUqonatrRSPtVX7Urqo9qHWFLWV2kVQsTRtiT2IJSE5vz/U/EwT6YRJzkzyefd1XjX3OXOf656TTObMdS+dO3eWJHXo0MFmIeIxY8Zo0KBBmj17tgICAhQdHa3o6GjFxsZKkmJjY/XBBx/ol19+0alTpxQWFqYWLVooKCiIXu/3adeunUJCQhQeHp5k38GDB9WnTx+baZgAAACAzMzuNQWee+45jRw5Ut9++22y+0ePHq3nnnvOYYEBAAAArqZt27Y6f/68Bg8erOjoaFWqVEmrV6+2Tm0TFRUlN7f/75czffp0xcfH6+WXX7apZ8iQIRo6dKjc3d114MABzZs3TzExMSpQoIBeeOEFjRgxIl3WOXAVoaGhWr9+vSpVqqQGDRqoTJkyMgxDhw8f1vr161W9enWbZAwAAACQmdmdFBgyZIhq1Kihp556SiEhISpdurT1g/aECRMUERGhX375JS1jBQAAQCblSguF9ejRQz169Eh238aNG20enzp1KsW6smbNqjVr1jgosozL29tbGzZs0IQJE/Tll19q06ZNkqQSJUroo48+Uu/evUmiAAAAZCKudP9gBruTAmXLltW6devUpUsXtWvXzvrCGoah0qVLa82aNSpXrlyaBQoAAAAAD+Lp6al+/fqpX79+ZocCAAAAODW7kwLS3cWEDx06pH379um3336TdLf3TeXKldMkOAAAAECS66zYhXRnGAY9wQAAAGCDT4cpS1VS4J5KlSqpUqVKDg4FAAAAAFKnXLlyGjx4sF566SV5eno+8Lhjx47p008/VdGiRdW/f/90jBAAAABwLnYlBUaPHq1evXopa9as/3rsjh07dOHCBTVp0uSRgwMAAAAk5gTFg02ePFn9+vXT22+/rQYNGqhatWoqUKCAvL29dfnyZUVERGjLli06dOiQevTooe7du5sdMgAAANKYG/cPKbIrKRAREaEiRYqodevWatasmapVq6Y8efJIku7cuWP9oL1w4UKdPn1a8+fPT9OgAQAAAECS6tWrp127dmnLli1aunSpFi1apN9//103b95U7ty5VblyZXXo0EGvvvqqHnvsMbPDBQAAAExnV1Jg/vz52r9/v6ZMmaJXXnlFV69elbu7u7y8vHTjxg1JUuXKldW1a1d16tRJ3t7eaRo0AAAAANzv6aef1tNPP212GAAAAIDTs3tNgSeeeEIzZ87UZ599pgMHDtj0vqlUqZJy586dlnECAAAgM2OhYQAAAAB2YvaglKV6oWE3NzcWGgYAAAAAAAAAwAWlOikAAAAApDu6+gAAAACwk4X7hxS5mR0AAAAAAAAAAABIHyQFAAAAAAAAAADIJJg+CAAAAE7PwkLDAAAAAOzE7EEpY6QAAAAAAJd2+/Zt9e3bV0FBQapevbpmz55ts//s2bNyd3c3KToAAADAuaQqKbB//3599NFHmjZtmi5cuGCz7+rVq3rjjTccGhwAAAAg6W5XH7M2OL2PP/5Y8+fP13/+8x+98MILCgkJ0VtvvWVzjGEYJkUHAACA9OZmsZi2uQK7kwJr165V9erVtWTJEo0ZM0alS5fWhg0brPtv3rypefPmpUmQAAAAAPAgixYt0hdffKE+ffroo48+0q5du/TTTz+pc+fO1mSAxYE3aHv27FHTpk0dVh8AAACQnuxOCgwd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6tKli5YtW6bvvvsuw/TkrVChgl599dUkU+m0a9dOL7/8stq1a6fhw4drx44dOnXqlDZt2qSlS5f+67VH2pk2bZru3LmjatWqaenSpTp8+LCOHj2qhQsX6siRIylem169eul///ufdu3alY4RO8ajtNtVtG7dWu7u7jajsE6ePJnk/ciVEyM+Pj7W5NSZM2eSjFLK6CwWi3r27KnRo0frxo0bZodjtWLFCl2+fFldunRR+fLlbbZWrVpp1qxZWrBggR5//HG1adPGZv8TTzyhxo0bs+AwHswAACfxzTffGI899pjh7e1t1KpVywgNDTX2799v3b9582YjZ86cxq1bt2yeV7x4ceOzzz4zDMMwhgwZYmTLls24evWqdf8HH3xg1KhRw/q4bt26Rq9evWzqGDFihPHCCy/YlP3xxx+GJOPo0aPW5z399NM2xzz55JNGv379DMMwjDVr1hhubm7W4/+pS5cuxptvvmlTtnnzZsPNzc24efPmA1+XlOJOzt69ew1JRlRUlE15x44dDU9PT0OSERYWluR59erVM0aOHGlTtmDBAiN//vzWx2XLljXGjBljfdysWTOjU6dONs/Zv3+/Ick4derUv8YKAAAeXseOHY0WLVo8cH+LFi2Mjh07PvDYP//80yhRooTx1FNPGVeuXEm7QB2gY8eOhqQkW5cuXYwbN24Yvr6+hp+fX5LPic7s/muS3PU5efKk9bPb/RISEowZM2YYNWrUMLJnz254enoaxYoVM7p162ZERESkU/SO8W8/w67m9OnTRo8ePYzAwEAjS5Ysho+Pj1G9enXjk08+Ma5fv24YhmEULVrUmDBhQpLnBgcHG40aNUrniB/OP6+bPe12FQ/6mRw1apSRJ08eIzY2Ntn3IknG5s2b0z9gB9q2bZshyWjcuLHZoTjEnDlzDF9f3yTlJ0+eNCQZe/futSm/fv268dhjj9nc75qtadOmD7weO3bsMCQZFovFePvtt5M9ZunSpYanp6dx/vx5Y8OGDYYk4/Lly2kYMVyJxTAMI33TEADwYLdu3dLmzZv1yy+/aNWqVdq5c6e++OILderUSVOnTlXPnj2TzE158+ZN9enTR2PGjNHQoUP19ddf69ChQ9b9EyZM0OTJk3XixAlJd6cBqlSpkiZOnGg9pnXr1vr+++/l6elpU/f169f1448/qlGjRnr22WdVrlw5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",
143
+ "text/plain": [
144
+ "<Figure size 1600x700 with 4 Axes>"
145
+ ]
146
+ },
147
+ "metadata": {},
148
+ "output_type": "display_data"
149
+ }
150
+ ],
151
+ "source": [
152
+ "import seaborn as sns\n",
153
+ "\n",
154
+ "# ================= 绘图参数 =================\n",
155
+ "TARGET_LAYER = top_layer # 使用刚才筛出来的层\n",
156
+ "TARGET_HEAD = top_head # 使用刚才筛出来的头\n",
157
+ "# 或者手动指定,比如您之前提到的:\n",
158
+ "# TARGET_LAYER = 7\n",
159
+ "# TARGET_HEAD = 4 \n",
160
+ "# ===========================================\n",
161
+ "\n",
162
+ "def plot_cross_modal_heatmap(layer, head):\n",
163
+ " # 1. 获取数据\n",
164
+ " attn_en, ids_en = get_attention_maps(text_en_1, text_en_2)\n",
165
+ " attn_bio, ids_bio = get_attention_maps(seq_bio_1, seq_bio_2)\n",
166
+ " \n",
167
+ " tokens_en = tokenizer.convert_ids_to_tokens(ids_en)\n",
168
+ " # 生物序列分词后可能是字符级,也可能合并,为了展示清晰,我们只看前20个token\n",
169
+ " tokens_bio = tokenizer.convert_ids_to_tokens(ids_bio)\n",
170
+ " \n",
171
+ " # 2. 提取特定 Head 的矩阵\n",
172
+ " # 英语\n",
173
+ " mat_en = attn_en[layer, head].numpy()\n",
174
+ " # 为了视觉美观,只取 Sent2 关注 Sent1 的部分 (左下角)\n",
175
+ " len_en_1 = len(tokenizer(text_en_1)['input_ids'])\n",
176
+ " # 切片: Rows=Sent2, Cols=Sent1\n",
177
+ " viz_en = mat_en[len_en_1:, :len_en_1] \n",
178
+ " x_labels_en = tokens_en[:len_en_1]\n",
179
+ " y_labels_en = tokens_en[len_en_1:]\n",
180
+ "\n",
181
+ " # 蛋白质\n",
182
+ " mat_bio = attn_bio[layer, head].numpy()\n",
183
+ " len_bio_1 = len(tokenizer(seq_bio_1)['input_ids'])\n",
184
+ " viz_bio = mat_bio[len_bio_1:, :len_bio_1]\n",
185
+ " x_labels_bio = tokens_bio[:len_bio_1]\n",
186
+ " y_labels_bio = tokens_bio[len_bio_1:]\n",
187
+ "\n",
188
+ " # 3. 绘图\n",
189
+ " fig, axes = plt.subplots(1, 2, figsize=(16, 7))\n",
190
+ " \n",
191
+ " # 左图:Language\n",
192
+ " sns.heatmap(viz_en, xticklabels=x_labels_en, yticklabels=y_labels_en, \n",
193
+ " cmap=\"Reds\", square=True, cbar=True, ax=axes[0])\n",
194
+ " axes[0].set_title(f\"Language: Inversion Detection\\n(Layer {layer}, Head {head})\", fontsize=14, fontweight='bold')\n",
195
+ " axes[0].set_xlabel(\"Sentence 1 (Key)\")\n",
196
+ " axes[0].set_ylabel(\"Sentence 2 (Query)\")\n",
197
+ " \n",
198
+ " # 右图:Protein\n",
199
+ " # 如果序列太长,只截取前 15x15 展示细节\n",
200
+ " limit = 15\n",
201
+ " sns.heatmap(viz_bio[:limit, :limit], \n",
202
+ " xticklabels=x_labels_bio[:limit], yticklabels=y_labels_bio[:limit], \n",
203
+ " cmap=\"Blues\", square=True, cbar=True, ax=axes[1])\n",
204
+ " axes[1].set_title(f\"Protein: Mutation Awareness\\n(Layer {layer}, Head {head})\", fontsize=14, fontweight='bold')\n",
205
+ " axes[1].set_xlabel(\"Sequence 1 (Key)\")\n",
206
+ " axes[1].set_ylabel(\"Sequence 2 (Query)\")\n",
207
+ "\n",
208
+ " plt.tight_layout()\n",
209
+ " plt.savefig(\"explain2.png\")\n",
210
+ "\n",
211
+ " plt.show() # plt.savefig(\"cross_modal_attention.pdf\")\n",
212
+ "\n",
213
+ "print(f\">>> Visualizing Layer {TARGET_LAYER}, Head {TARGET_HEAD}...\")\n",
214
+ "plot_cross_modal_heatmap(TARGET_LAYER, TARGET_HEAD)"
215
+ ]
216
+ },
217
+ {
218
+ "cell_type": "code",
219
+ "execution_count": null,
220
+ "id": "0fd1ffcb-c506-4bce-b2f4-74757359e2a0",
221
+ "metadata": {},
222
+ "outputs": [],
223
+ "source": []
224
+ }
225
+ ],
226
+ "metadata": {
227
+ "kernelspec": {
228
+ "display_name": "Python 3 (ipykernel)",
229
+ "language": "python",
230
+ "name": "python3"
231
+ },
232
+ "language_info": {
233
+ "codemirror_mode": {
234
+ "name": "ipython",
235
+ "version": 3
236
+ },
237
+ "file_extension": ".py",
238
+ "mimetype": "text/x-python",
239
+ "name": "python",
240
+ "nbconvert_exporter": "python",
241
+ "pygments_lexer": "ipython3",
242
+ "version": "3.12.3"
243
+ }
244
+ },
245
+ "nbformat": 4,
246
+ "nbformat_minor": 5
247
+ }
2-gpt_ft_test_explain/4-explain_t_sne.ipynb ADDED
The diff for this file is too large to render. See raw diff
 
2-gpt_ft_test_explain/batch_run/.ipynb_checkpoints/gpt2_ft_en_test_protein-checkpoint.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import json
4
+ import torch
5
+ import numpy as np
6
+ import evaluate
7
+ from transformers import (
8
+ AutoTokenizer,
9
+ AutoModelForSequenceClassification,
10
+ DataCollatorWithPadding,
11
+ Trainer,
12
+ TrainingArguments,
13
+ set_seed
14
+ )
15
+ from datasets import load_dataset
16
+ from tqdm import tqdm
17
+
18
+ # 设置环境变量
19
+ os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
20
+
21
+ # 1. 获取命令行参数
22
+ if len(sys.argv) > 1:
23
+ seed = int(sys.argv[1])
24
+ try:
25
+ lang = sys.argv[2]
26
+ except IndexError:
27
+ lang = "en"
28
+ else:
29
+ seed = 42
30
+ lang = "en"
31
+
32
+ # 2. 设置随机种子
33
+ set_seed(seed)
34
+ result = {}
35
+ result["seed"] = seed
36
+ result["lang"] = lang
37
+
38
+ # 3. 初始化分词器和模型
39
+ model_checkpoint = "gpt2"
40
+ tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
41
+ tokenizer.pad_token = tokenizer.eos_token
42
+
43
+ model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=2)
44
+ model.config.pad_token_id = model.config.eos_token_id
45
+
46
+ # 定义两个专用的分词函数
47
+ def tokenize_short_function(example):
48
+ return tokenizer(
49
+ example["sentence1"],
50
+ example["sentence2"],
51
+ truncation=True,
52
+ max_length=256, # short 子集:完全无截断
53
+ padding="max_length"
54
+ )
55
+
56
+ def tokenize_full_function(example):
57
+ return tokenizer(
58
+ example["sentence1"],
59
+ example["sentence2"],
60
+ truncation=True,
61
+ max_length=512, # full 子集:覆盖 ~97%,最佳平衡
62
+ padding="max_length"
63
+ )
64
+
65
+ # 5. 加载并处理训练数据 (PAWS-X)
66
+ print(f"Loading PAWS-X ({lang}) for training...")
67
+ raw_datasets = load_dataset('paws-x', lang)
68
+ tokenized_datasets = raw_datasets.map(tokenize_short_function, batched=True)
69
+ data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
70
+
71
+ # 6. 定义训练参数和指标
72
+ def compute_metrics(eval_pred):
73
+ predictions, labels = eval_pred
74
+ predictions = np.argmax(predictions, axis=1)
75
+ return {'accuracy': (predictions == labels).sum() / len(labels)}
76
+
77
+ training_args = TrainingArguments(
78
+ #output_dir=f"ds_job_dna_{seed}", # 动态输出目录避免冲突
79
+ output_dir="ds_job_dna_my", # 存一个也行
80
+ learning_rate=1e-5,
81
+ lr_scheduler_type="constant_with_warmup",
82
+ warmup_ratio=0.1,
83
+ optim='adamw_torch',
84
+ weight_decay=0.0,
85
+ seed=seed,
86
+ per_device_train_batch_size=64,
87
+ per_device_eval_batch_size=64,
88
+ num_train_epochs=4,
89
+ eval_strategy="epoch", # 旧版本是 evaluation_strategy,新版本必须用 eval_strategy
90
+ save_strategy="epoch",
91
+ logging_strategy="epoch",
92
+ load_best_model_at_end=True,
93
+ save_total_limit=1 # 只保留最好的模型,节省空间
94
+ )
95
+
96
+ trainer = Trainer(
97
+ model=model,
98
+ args=training_args,
99
+ train_dataset=tokenized_datasets["train"],
100
+ eval_dataset=tokenized_datasets["validation"],
101
+ data_collator=data_collator,
102
+ tokenizer=tokenizer,
103
+ compute_metrics=compute_metrics,
104
+ )
105
+
106
+ # 7. 开始训练
107
+ trainer.train()
108
+
109
+ # 8. 定义通用推理函数 (复用逻辑)
110
+ # 确保模型在 GPU 上并处于评估模式
111
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
112
+ model.to(device)
113
+ model.eval()
114
+
115
+ def run_inference(test_dataset, batch_size=64):
116
+ preds = []
117
+ labels = []
118
+
119
+ # disable=True 禁用进度条以保持输出日志纯净
120
+ for i in tqdm(range(0, len(test_dataset), batch_size), desc="Predicting", disable=True):
121
+ batch = test_dataset[i : i + batch_size]
122
+
123
+ inputs = {
124
+ "input_ids": torch.tensor(batch["input_ids"]).to(device),
125
+ "attention_mask": torch.tensor(batch["attention_mask"]).to(device),
126
+ }
127
+ batch_labels = batch["label"]
128
+
129
+ with torch.no_grad():
130
+ outputs = model(**inputs)
131
+ batch_preds = torch.argmax(outputs.logits, axis=-1).cpu().numpy()
132
+
133
+ preds.extend(batch_preds)
134
+ labels.extend(batch_labels)
135
+
136
+ metric = evaluate.load("glue", "mrpc")
137
+ return metric.compute(predictions=preds, references=labels)
138
+
139
+ # ==========================================
140
+ # 测试集 1: protein_pair_short
141
+ # ==========================================
142
+ raw_datasets_short = load_dataset('dnagpt/biopaws', 'protein_pair_short')['train'].train_test_split(test_size=0.3, seed=seed)
143
+
144
+ # 直接分词
145
+ tokenized_raw_datasets_short = raw_datasets_short.map(tokenize_short_function, batched=True, num_proc=4)
146
+
147
+ print("Running inference on protein_pair_short set...")
148
+ ret_1 = run_inference(tokenized_raw_datasets_short["test"])
149
+ result["protein_pair_short"] = ret_1
150
+
151
+
152
+ # ==========================================
153
+ # 测试集 2: protein_pair_full (
154
+ # ==========================================
155
+ raw_datasets_full = load_dataset('dnagpt/biopaws', 'protein_pair_full')['train'].train_test_split(test_size=0.3, seed=seed)
156
+
157
+ # 直接分词 (去除了 flip_labels 以保持与基线脚本一致)
158
+ tokenized_raw_datasets_full = raw_datasets_full.map(tokenize_full_function, batched=True, num_proc=4)
159
+
160
+ print("Running inference on protein_pair_full set...")
161
+ ret_2 = run_inference(tokenized_raw_datasets_full["test"])
162
+ result["protein_pair_full"] = ret_2
163
+
164
+
165
+ # ==========================================
166
+ # 输出结果
167
+ # ==========================================
168
+ print(json.dumps(result))
2-gpt_ft_test_explain/batch_run/.ipynb_checkpoints/gpt2_test_protein-checkpoint.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import json
4
+ import torch
5
+ import numpy as np
6
+ import evaluate
7
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification, set_seed
8
+ from datasets import load_dataset
9
+ from tqdm import tqdm
10
+
11
+ # 设置环境变量
12
+ #os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
13
+
14
+ # 获取命令行参数
15
+ if len(sys.argv) > 1:
16
+ seed = int(sys.argv[1])
17
+ try:
18
+ lang = sys.argv[2]
19
+ except IndexError:
20
+ lang = "en"
21
+ else:
22
+ seed = 42
23
+ lang = "en"
24
+
25
+ # 设置随机种子
26
+ set_seed(seed)
27
+
28
+ result = {}
29
+ result["seed"] = seed
30
+ result["type"] = "no_finetune_baseline"
31
+
32
+ # 初始化模型和分词器
33
+ model_checkpoint = "gpt2"
34
+ tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
35
+ tokenizer.pad_token = tokenizer.eos_token
36
+
37
+ # 加载模型 (预训练权重 + 随机分类头)
38
+ model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=2)
39
+ model.config.pad_token_id = model.config.eos_token_id
40
+
41
+ # 移动到 GPU
42
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
43
+ model.to(device)
44
+ model.eval()
45
+
46
+ # 定义两个专用的分词函数
47
+ def tokenize_short_function(example):
48
+ return tokenizer(
49
+ example["sentence1"],
50
+ example["sentence2"],
51
+ truncation=True,
52
+ max_length=256, # short 子集:完全无截断
53
+ padding="max_length"
54
+ )
55
+
56
+ def tokenize_full_function(example):
57
+ return tokenizer(
58
+ example["sentence1"],
59
+ example["sentence2"],
60
+ truncation=True,
61
+ max_length=512, # full 子集:覆盖 ~97%,最佳平衡
62
+ padding="max_length"
63
+ )
64
+
65
+ # 定义推理函数
66
+ def run_inference(test_dataset, batch_size=64):
67
+ preds = []
68
+ labels = []
69
+
70
+ # disable=True 禁用进度条以保持输出纯净
71
+ for i in tqdm(range(0, len(test_dataset), batch_size), desc="Predicting", disable=True):
72
+ batch = test_dataset[i : i + batch_size]
73
+
74
+ inputs = {
75
+ "input_ids": torch.tensor(batch["input_ids"]).to(device),
76
+ "attention_mask": torch.tensor(batch["attention_mask"]).to(device),
77
+ }
78
+ batch_labels = batch["label"]
79
+
80
+ with torch.no_grad():
81
+ outputs = model(**inputs)
82
+ batch_preds = torch.argmax(outputs.logits, axis=-1).cpu().numpy()
83
+
84
+ preds.extend(batch_preds)
85
+ labels.extend(batch_labels)
86
+
87
+ metric = evaluate.load("glue", "mrpc")
88
+ return metric.compute(predictions=preds, references=labels)
89
+
90
+ # ==========================================
91
+ # 测试集 1: protein_pair_short
92
+ # ==========================================
93
+ raw_datasets_short = load_dataset('dnagpt/biopaws', 'protein_pair_short')['train'].train_test_split(test_size=0.3, seed=seed)
94
+
95
+ # 直接分词
96
+ tokenized_raw_datasets_short = raw_datasets_short.map(tokenize_short_function, batched=True, num_proc=4)
97
+ ret_1 = run_inference(tokenized_raw_datasets_short["test"])
98
+ result["protein_pair_short"] = ret_1
99
+
100
+
101
+ # ==========================================
102
+ # 测试集 2: protein_pair_full (
103
+ # ==========================================
104
+ raw_datasets_full = load_dataset('dnagpt/biopaws', 'protein_pair_full')['train'].train_test_split(test_size=0.3, seed=seed)
105
+
106
+ # 直接分词 (去除了 flip_labels 以保持与基线脚本一致)
107
+ tokenized_raw_datasets_full = raw_datasets_full.map(tokenize_full_function, batched=True, num_proc=4)
108
+ ret_2 = run_inference(tokenized_raw_datasets_full["test"])
109
+ result["protein_pair_full"] = ret_2
110
+
111
+
112
+ # ==========================================
113
+ # 输出结果
114
+ # ==========================================
115
+ print(json.dumps(result))
2-gpt_ft_test_explain/batch_run/.ipynb_checkpoints/run_ft_test-checkpoint.sh ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ export HF_ENDPOINT=https://hf-mirror.com
3
+ lang=$1
4
+ for ((i=0;i<100;i++))
5
+ do
6
+ echo "----------------------------------------------"$i
7
+ python gpt2_ft_en_test_protein.py $i $lang >> gpt2_ft_en_test_protein_${lang}.json
8
+ done
9
+
2-gpt_ft_test_explain/batch_run/.ipynb_checkpoints/run_test-checkpoint.sh ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ export HF_ENDPOINT=https://hf-mirror.com
3
+ lang=$1
4
+ for ((i=0;i<100;i++))
5
+ do
6
+ echo "----------------------------------------------"$i
7
+ python gpt2_test_protein.py $i $lang >> gpt2_test_protein_${lang}.json
8
+ done
9
+
2-gpt_ft_test_explain/batch_run/.ipynb_checkpoints/stat_token_len-checkpoint.ipynb ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "4f9561c7-9f13-4f35-bc4c-6751cdbcb5e9",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "# import os\n",
11
+ "\n",
12
+ "# # 设置环境变量\n",
13
+ "# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'\n",
14
+ "\n",
15
+ "# # 打印环境变量以确认设置成功\n",
16
+ "# print(os.environ.get('HF_ENDPOINT'))\n",
17
+ "\n",
18
+ "import subprocess\n",
19
+ "import os\n",
20
+ "\n",
21
+ "result = subprocess.run('bash -c \"source /etc/network_turbo && env | grep proxy\"', shell=True, capture_output=True, text=True)\n",
22
+ "output = result.stdout\n",
23
+ "for line in output.splitlines():\n",
24
+ " if '=' in line:\n",
25
+ " var, value = line.split('=', 1)\n",
26
+ " os.environ[var] = value"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "code",
31
+ "execution_count": 2,
32
+ "id": "039873dc-8712-49e5-9d46-3cb546875860",
33
+ "metadata": {},
34
+ "outputs": [
35
+ {
36
+ "name": "stdout",
37
+ "output_type": "stream",
38
+ "text": [
39
+ "\n",
40
+ "=== 分析子集: protein_pair_short ===\n",
41
+ "总序列数: 40000\n",
42
+ "氨基酸长度统计(近似,token数 ≈ aa数 × 1.5):\n",
43
+ " Min: 21\n",
44
+ " 25%: 63\n",
45
+ " 50% (中位数): 88\n",
46
+ " 75%: 114\n",
47
+ " 90%: 132\n",
48
+ " 95%: 140\n",
49
+ " 99%: 148\n",
50
+ " Max: 169\n",
51
+ " 保留 90% 序列完整 → 建议 max_length = 142\n",
52
+ " 保留 95% 序列完整 → 建议 max_length = 150\n",
53
+ " 保留 99% 序列完整 → 建议 max_length = 158\n",
54
+ "\n",
55
+ "=== 分析子集: protein_pair_full ===\n"
56
+ ]
57
+ },
58
+ {
59
+ "name": "stderr",
60
+ "output_type": "stream",
61
+ "text": [
62
+ "Using the latest cached version of the dataset since dnagpt/biopaws couldn't be found on the Hugging Face Hub\n",
63
+ "Found the latest cached dataset configuration 'protein_pair_full' at /root/.cache/huggingface/datasets/dnagpt___biopaws/protein_pair_full/0.0.0/04f23788fc267a3ab45cd27f39e8d6566a412a41 (last modified on Tue Dec 30 20:40:55 2025).\n",
64
+ "Token indices sequence length is longer than the specified maximum sequence length for this model (1283 > 1024). Running this sequence through the model will result in indexing errors\n"
65
+ ]
66
+ },
67
+ {
68
+ "name": "stdout",
69
+ "output_type": "stream",
70
+ "text": [
71
+ "总序列数: 40000\n",
72
+ "氨基酸长度统计(近似,token数 ≈ aa数 × 1.5):\n",
73
+ " Min: 15\n",
74
+ " 25%: 87\n",
75
+ " 50% (中位数): 142\n",
76
+ " 75%: 219\n",
77
+ " 90%: 310\n",
78
+ " 95%: 400\n",
79
+ " 99%: 675\n",
80
+ " Max: 4516\n",
81
+ " 保留 90% 序列完整 → 建议 max_length = 320\n",
82
+ " 保留 95% 序列完整 → 建议 max_length = 410\n",
83
+ " 保留 99% 序列完整 → 建议 max_length = 685\n"
84
+ ]
85
+ }
86
+ ],
87
+ "source": [
88
+ "from datasets import load_dataset\n",
89
+ "from transformers import AutoTokenizer\n",
90
+ "import numpy as np\n",
91
+ "\n",
92
+ "# ==================== 配置 ====================\n",
93
+ "MODEL_NAME = \"gpt2\" # 你用的 tokenizer\n",
94
+ "DATASET_NAME = \"dnagpt/biopaws\"\n",
95
+ "\n",
96
+ "SUBSETS = [\"protein_pair_short\", \"protein_pair_full\"]\n",
97
+ "\n",
98
+ "tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)\n",
99
+ "\n",
100
+ "def analyze_length(subset_name):\n",
101
+ " print(f\"\\n=== 分析子集: {subset_name} ===\")\n",
102
+ " dataset = load_dataset(DATASET_NAME, subset_name)[\"train\"]\n",
103
+ " \n",
104
+ " # 计算所有序列的 token 长度\n",
105
+ " lengths1 = []\n",
106
+ " lengths2 = []\n",
107
+ " for item in dataset:\n",
108
+ " len1 = len(tokenizer.encode(item[\"sentence1\"], add_special_tokens=False))\n",
109
+ " len2 = len(tokenizer.encode(item[\"sentence2\"], add_special_tokens=False))\n",
110
+ " lengths1.append(len1)\n",
111
+ " lengths2.append(len2)\n",
112
+ " \n",
113
+ " all_lengths = lengths1 + lengths2\n",
114
+ " all_lengths = np.array(all_lengths)\n",
115
+ " \n",
116
+ " print(f\"总序列数: {len(all_lengths)}\")\n",
117
+ " print(f\"氨基酸长度统计(近似,token数 ≈ aa数 × 1.5):\")\n",
118
+ " print(f\" Min: {all_lengths.min()}\")\n",
119
+ " print(f\" 25%: {np.percentile(all_lengths, 25):.0f}\")\n",
120
+ " print(f\" 50% (中位数): {np.percentile(all_lengths, 50):.0f}\")\n",
121
+ " print(f\" 75%: {np.percentile(all_lengths, 75):.0f}\")\n",
122
+ " print(f\" 90%: {np.percentile(all_lengths, 90):.0f}\")\n",
123
+ " print(f\" 95%: {np.percentile(all_lengths, 95):.0f}\")\n",
124
+ " print(f\" 99%: {np.percentile(all_lengths, 99):.0f}\")\n",
125
+ " print(f\" Max: {all_lengths.max()}\")\n",
126
+ " \n",
127
+ " # 推荐 max_length(保留 95% 或 99% 序列不截断)\n",
128
+ " for perc in [90, 95, 99]:\n",
129
+ " val = np.percentile(all_lengths, perc)\n",
130
+ " print(f\" 保留 {perc}% 序列完整 → 建议 max_length = {int(val) + 10}\") # +10 留余量\n",
131
+ " \n",
132
+ " return all_lengths\n",
133
+ "\n",
134
+ "# 运行统计\n",
135
+ "for subset in SUBSETS:\n",
136
+ " analyze_length(subset)"
137
+ ]
138
+ },
139
+ {
140
+ "cell_type": "code",
141
+ "execution_count": null,
142
+ "id": "5f77b16f-b571-4684-8da5-365c3bb8d34b",
143
+ "metadata": {},
144
+ "outputs": [],
145
+ "source": []
146
+ }
147
+ ],
148
+ "metadata": {
149
+ "kernelspec": {
150
+ "display_name": "Python 3 (ipykernel)",
151
+ "language": "python",
152
+ "name": "python3"
153
+ },
154
+ "language_info": {
155
+ "codemirror_mode": {
156
+ "name": "ipython",
157
+ "version": 3
158
+ },
159
+ "file_extension": ".py",
160
+ "mimetype": "text/x-python",
161
+ "name": "python",
162
+ "nbconvert_exporter": "python",
163
+ "pygments_lexer": "ipython3",
164
+ "version": "3.12.3"
165
+ }
166
+ },
167
+ "nbformat": 4,
168
+ "nbformat_minor": 5
169
+ }
2-gpt_ft_test_explain/batch_run/gpt2_ft_en_test_protein.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import json
4
+ import torch
5
+ import numpy as np
6
+ import evaluate
7
+ from transformers import (
8
+ AutoTokenizer,
9
+ AutoModelForSequenceClassification,
10
+ DataCollatorWithPadding,
11
+ Trainer,
12
+ TrainingArguments,
13
+ set_seed
14
+ )
15
+ from datasets import load_dataset
16
+ from tqdm import tqdm
17
+
18
+ # 设置环境变量
19
+ os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
20
+
21
+ # 1. 获取命令行参数
22
+ if len(sys.argv) > 1:
23
+ seed = int(sys.argv[1])
24
+ try:
25
+ lang = sys.argv[2]
26
+ except IndexError:
27
+ lang = "en"
28
+ else:
29
+ seed = 42
30
+ lang = "en"
31
+
32
+ # 2. 设置随机种子
33
+ set_seed(seed)
34
+ result = {}
35
+ result["seed"] = seed
36
+ result["lang"] = lang
37
+
38
+ # 3. 初始化分词器和模型
39
+ model_checkpoint = "gpt2"
40
+ tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
41
+ tokenizer.pad_token = tokenizer.eos_token
42
+
43
+ model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=2)
44
+ model.config.pad_token_id = model.config.eos_token_id
45
+
46
+ # 定义两个专用的分词函数
47
+ def tokenize_short_function(example):
48
+ return tokenizer(
49
+ example["sentence1"],
50
+ example["sentence2"],
51
+ truncation=True,
52
+ max_length=256, # short 子集:完全无截断
53
+ padding="max_length"
54
+ )
55
+
56
+ def tokenize_full_function(example):
57
+ return tokenizer(
58
+ example["sentence1"],
59
+ example["sentence2"],
60
+ truncation=True,
61
+ max_length=512, # full 子集:覆盖 ~97%,最佳平衡
62
+ padding="max_length"
63
+ )
64
+
65
+ # 5. 加载并处理训练数据 (PAWS-X)
66
+ print(f"Loading PAWS-X ({lang}) for training...")
67
+ raw_datasets = load_dataset('paws-x', lang)
68
+ tokenized_datasets = raw_datasets.map(tokenize_short_function, batched=True)
69
+ data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
70
+
71
+ # 6. 定义训练参数和指标
72
+ def compute_metrics(eval_pred):
73
+ predictions, labels = eval_pred
74
+ predictions = np.argmax(predictions, axis=1)
75
+ return {'accuracy': (predictions == labels).sum() / len(labels)}
76
+
77
+ training_args = TrainingArguments(
78
+ #output_dir=f"ds_job_dna_{seed}", # 动态输出目录避免冲突
79
+ output_dir="ds_job_dna_my", # 存一个也行
80
+ learning_rate=1e-5,
81
+ lr_scheduler_type="constant_with_warmup",
82
+ warmup_ratio=0.1,
83
+ optim='adamw_torch',
84
+ weight_decay=0.0,
85
+ seed=seed,
86
+ per_device_train_batch_size=64,
87
+ per_device_eval_batch_size=64,
88
+ num_train_epochs=4,
89
+ eval_strategy="epoch", # 旧版本是 evaluation_strategy,新版本必须用 eval_strategy
90
+ save_strategy="epoch",
91
+ logging_strategy="epoch",
92
+ load_best_model_at_end=True,
93
+ save_total_limit=1 # 只保留最好的模型,节省空间
94
+ )
95
+
96
+ trainer = Trainer(
97
+ model=model,
98
+ args=training_args,
99
+ train_dataset=tokenized_datasets["train"],
100
+ eval_dataset=tokenized_datasets["validation"],
101
+ data_collator=data_collator,
102
+ tokenizer=tokenizer,
103
+ compute_metrics=compute_metrics,
104
+ )
105
+
106
+ # 7. 开始训练
107
+ trainer.train()
108
+
109
+ # 8. 定义通用推理函数 (复用逻辑)
110
+ # 确保模型在 GPU 上并处于评估模式
111
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
112
+ model.to(device)
113
+ model.eval()
114
+
115
+ def run_inference(test_dataset, batch_size=64):
116
+ preds = []
117
+ labels = []
118
+
119
+ # disable=True 禁用进度条以保持输出日志纯净
120
+ for i in tqdm(range(0, len(test_dataset), batch_size), desc="Predicting", disable=True):
121
+ batch = test_dataset[i : i + batch_size]
122
+
123
+ inputs = {
124
+ "input_ids": torch.tensor(batch["input_ids"]).to(device),
125
+ "attention_mask": torch.tensor(batch["attention_mask"]).to(device),
126
+ }
127
+ batch_labels = batch["label"]
128
+
129
+ with torch.no_grad():
130
+ outputs = model(**inputs)
131
+ batch_preds = torch.argmax(outputs.logits, axis=-1).cpu().numpy()
132
+
133
+ preds.extend(batch_preds)
134
+ labels.extend(batch_labels)
135
+
136
+ metric = evaluate.load("glue", "mrpc")
137
+ return metric.compute(predictions=preds, references=labels)
138
+
139
+ # ==========================================
140
+ # 测试集 1: protein_pair_short
141
+ # ==========================================
142
+ raw_datasets_short = load_dataset('dnagpt/biopaws', 'protein_pair_short')['train'].train_test_split(test_size=0.3, seed=seed)
143
+
144
+ # 直接分词
145
+ tokenized_raw_datasets_short = raw_datasets_short.map(tokenize_short_function, batched=True, num_proc=4)
146
+
147
+ print("Running inference on protein_pair_short set...")
148
+ ret_1 = run_inference(tokenized_raw_datasets_short["test"])
149
+ result["protein_pair_short"] = ret_1
150
+
151
+
152
+ # ==========================================
153
+ # 测试集 2: protein_pair_full (
154
+ # ==========================================
155
+ raw_datasets_full = load_dataset('dnagpt/biopaws', 'protein_pair_full')['train'].train_test_split(test_size=0.3, seed=seed)
156
+
157
+ # 直接分词 (去除了 flip_labels 以保持与基线脚本一致)
158
+ tokenized_raw_datasets_full = raw_datasets_full.map(tokenize_full_function, batched=True, num_proc=4)
159
+
160
+ print("Running inference on protein_pair_full set...")
161
+ ret_2 = run_inference(tokenized_raw_datasets_full["test"])
162
+ result["protein_pair_full"] = ret_2
163
+
164
+
165
+ # ==========================================
166
+ # 输出结果
167
+ # ==========================================
168
+ print(json.dumps(result))
2-gpt_ft_test_explain/batch_run/gpt2_test_protein.py ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import json
4
+ import torch
5
+ import numpy as np
6
+ import evaluate
7
+ from transformers import AutoTokenizer, AutoModelForSequenceClassification, set_seed
8
+ from datasets import load_dataset
9
+ from tqdm import tqdm
10
+
11
+ # 设置环境变量
12
+ #os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
13
+
14
+ # 获取命令行参数
15
+ if len(sys.argv) > 1:
16
+ seed = int(sys.argv[1])
17
+ try:
18
+ lang = sys.argv[2]
19
+ except IndexError:
20
+ lang = "en"
21
+ else:
22
+ seed = 42
23
+ lang = "en"
24
+
25
+ # 设置随机种子
26
+ set_seed(seed)
27
+
28
+ result = {}
29
+ result["seed"] = seed
30
+ result["type"] = "no_finetune_baseline"
31
+
32
+ # 初始化模型和分词器
33
+ model_checkpoint = "gpt2"
34
+ tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)
35
+ tokenizer.pad_token = tokenizer.eos_token
36
+
37
+ # 加载模型 (预训练权重 + 随机分类头)
38
+ model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=2)
39
+ model.config.pad_token_id = model.config.eos_token_id
40
+
41
+ # 移动到 GPU
42
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
43
+ model.to(device)
44
+ model.eval()
45
+
46
+ # 定义两个专用的分词函数
47
+ def tokenize_short_function(example):
48
+ return tokenizer(
49
+ example["sentence1"],
50
+ example["sentence2"],
51
+ truncation=True,
52
+ max_length=256, # short 子集:完全无截断
53
+ padding="max_length"
54
+ )
55
+
56
+ def tokenize_full_function(example):
57
+ return tokenizer(
58
+ example["sentence1"],
59
+ example["sentence2"],
60
+ truncation=True,
61
+ max_length=512, # full 子集:覆盖 ~97%,最佳平衡
62
+ padding="max_length"
63
+ )
64
+
65
+ # 定义推理函数
66
+ def run_inference(test_dataset, batch_size=64):
67
+ preds = []
68
+ labels = []
69
+
70
+ # disable=True 禁用进度条以保持输出纯净
71
+ for i in tqdm(range(0, len(test_dataset), batch_size), desc="Predicting", disable=True):
72
+ batch = test_dataset[i : i + batch_size]
73
+
74
+ inputs = {
75
+ "input_ids": torch.tensor(batch["input_ids"]).to(device),
76
+ "attention_mask": torch.tensor(batch["attention_mask"]).to(device),
77
+ }
78
+ batch_labels = batch["label"]
79
+
80
+ with torch.no_grad():
81
+ outputs = model(**inputs)
82
+ batch_preds = torch.argmax(outputs.logits, axis=-1).cpu().numpy()
83
+
84
+ preds.extend(batch_preds)
85
+ labels.extend(batch_labels)
86
+
87
+ metric = evaluate.load("glue", "mrpc")
88
+ return metric.compute(predictions=preds, references=labels)
89
+
90
+ # ==========================================
91
+ # 测试集 1: protein_pair_short
92
+ # ==========================================
93
+ raw_datasets_short = load_dataset('dnagpt/biopaws', 'protein_pair_short')['train'].train_test_split(test_size=0.3, seed=seed)
94
+
95
+ # 直接分词
96
+ tokenized_raw_datasets_short = raw_datasets_short.map(tokenize_short_function, batched=True, num_proc=4)
97
+ ret_1 = run_inference(tokenized_raw_datasets_short["test"])
98
+ result["protein_pair_short"] = ret_1
99
+
100
+
101
+ # ==========================================
102
+ # 测试集 2: protein_pair_full (
103
+ # ==========================================
104
+ raw_datasets_full = load_dataset('dnagpt/biopaws', 'protein_pair_full')['train'].train_test_split(test_size=0.3, seed=seed)
105
+
106
+ # 直接分词 (去除了 flip_labels 以保持与基线脚本一致)
107
+ tokenized_raw_datasets_full = raw_datasets_full.map(tokenize_full_function, batched=True, num_proc=4)
108
+ ret_2 = run_inference(tokenized_raw_datasets_full["test"])
109
+ result["protein_pair_full"] = ret_2
110
+
111
+
112
+ # ==========================================
113
+ # 输出结果
114
+ # ==========================================
115
+ print(json.dumps(result))