单细胞组学大模型(7)--- GenePT,一个可以在本地部署和使用的单细胞转录组大模型
–https://doi.org/10.1038/s41551-024-01284-6
代码来源:https://github.com/yiqunchen/GenePT
Simple and effective embedding model for single-cell biology built from ChatGPT
研究作者和单位
James Zou–Department of Biomedical Data Science, Stanford University
Yiqun Chen–Department of Biomedical Data Science, Stanford University
目录:
- 1.研究简介
- 2.基因文本获取以及转成embedding
- 3.模型架构
- 4.研究结果展示
1.研究简介
单细胞测序领域正在兴起一股开发“基础模型”的热潮,目的是学习基因和细胞的embedding表示,促进各种下游分析。
1.1 现有方法(scBERT、Geneformer、scGPT)
采用深度学习架构,通常是基于 Transformer。
预训练: 使用大规模单细胞基因表达数据集,以自监督方式(例如,通过填补masked的表达值)进行预训练。训练后的编码器将基因和细胞映射到高维embedding向量,其中包含潜在的生物学信息。
微调: 对于下游任务,可以选择使用少量特定于任务的数据进行微调,以提高预测能力。
以下是之前我发布的相关推文:
1.2 局限性:
-
仅从基因表达数据集生成embedding表示,没有利用关于基因的文献和现有知识。
-
大规模单细胞转录组学数据的收集、处理和训练需要大量工作。
-
提取的c表示信号很大程度上依赖于使用的基因表达数据,没有利用总结基因功能的庞大研究和文献,可能导致某些应用中样本效率低下和次优结果。
1.3 GenePT
利用 OpenAI 的 ChatGPT 文本嵌入模型来表示基因和细胞,GenePT把文本知识embedding和单细胞转录组数据embedding结合,研究利用自然语言对基因和细胞的生物学进行编码的可行性,弥补现有方法在利用现有生物学知识方面的不足。
因为大型语言模型已经利用大量计算资源在广泛的文本语料库(包括生物医学文献)上进行训练,并且在理解、推理甚至生成生物医学文本方面表现出卓越的能力。
因此,作者假设 LLM 衍生的基因summary和功能embedding表示(通常是从广泛的实验和研究中策划出来的)可以更直接地捕获底层的生物学信息。
GenePT 在各种下游任务上的表现可与 Geneformer 等专门设计的模型相当,有时甚至超越它们:
(i)它在生物学任务上表现的更好;
(ii)它不需要对基因组数据进行大量数据集整理处理或额外的预训练(因为它只使用基因文本的embedding);
(iii)它非常易于使用并生成基因和细胞embedding表示。具体而言,GenePT 使用基于 LLM 的嵌入表示作为与基于表达的数据和表示的正交信息源
2.基因文本获取以及转成embedding
作者提到,基因的文本可以是:
- 1.symbol的名称,比如CD24;
- 2.NCBI基因数据库提供的基因summary,如:This gene encodes a sialoglycoprotein that is expressed on mature granulocytes and B cells and modulates growth and differentiation signals to these cells…
- 3.ChatGPT生成对基因的描述
2.1 词库:
作者统一了 Geneformer 和 scGPT 中提供的基因名称列表。在 Geneformer 的例子中,基因以 Ensembl ID 而非基因symbol名称表示,使用 mygene 包进行转换。
作者提供了词库文件,在github的input_data/gene_info_table.csv中。
2.2 NCBI基因数据库基因summary文本
https://www.ncbi.nlm.nih.gov/gene
作者提供了从NCBI基因数据库获取基因summary的代码(实际上是BeautifulSoup爬取检索到的网页源代码),代码的位置在request_ncbi_text_for_genes.ipynb,很像ChatGPT写的代码哈哈
import requests
from bs4 import BeautifulSoup
import html2text
import mygene
import json
import pickle
mg = mygene.MyGeneInfo()
parts_to_remove = [
"## Summary\n",
"NEW",
'Try the newGene table',
'Try the newTranscript table',
'**',
"\nGo to the top of the page Help\n"
]
def rough_text_from_gene_name(gene_number):
# 获取网页
url = f"https://www.ncbi.nlm.nih.gov/gene/{gene_number}"
summary_text = ''
soup = None
try:
response = requests.get(url, timeout=30)
except requests.exceptions.Timeout:
print('time out')
return((summary_text,soup))
# 确保检索网页正确
if response.status_code == 200:
soup = BeautifulSoup(response.content, 'html.parser')
# 查找"summary" tab 目录
summary_tab = soup.find('div', {'class': 'rprt-section gene-summary'})
if summary_tab:
html_to_text = html2text.HTML2Text()
html_to_text.ignore_links = True # Ignore hyperlinks
# 提取对应部分的tab
summary_text = html_to_text.handle(str(summary_tab))
# 去除不需要的文本部分
for part in parts_to_remove:
summary_text = summary_text.replace(part, ' ')
# 去掉多余的换行符号
summary_text = summary_text.replace('\n', ' ')
# 去掉多个空格
summary_text = ' '.join(summary_text.split())
else:
print("Summary tab not found on the page.")
else:
print(f"Failed to retrieve the webpage. Status code: {response.status_code}")
return((summary_text,soup))
用CD24基因作为一个例子:
cd_24_name = mg.querymany('CD24', scopes='symbol', species='human')
gene_name_to_tax_id = {}
for result in cd_24_name:
if "_id" in result and "query" in result:
gene_name_to_tax_id[result['symbol']] = result['_id']
# 可以看到是本地运行的
with open('/Users/yiquntchen/Downloads/human/vocab.json', 'rb') as handle:
vocab_gene = json.load(handle)
vocab_gene_list = list(vocab_gene.keys())
gene_name_to_summary_page = {}
for gene_name, page_id in sorted(gene_name_to_tax_id.items()):
if gene_name not in gene_name_to_summary_page:
print('gene_name',gene_name)
parsed_text, unparsed_html = rough_text_from_gene_name(page_id)
gene_name_to_summary_page[gene_name] = parsed_text
需要的部分就是红框中的:
以下代码就是批量获取基因symbol的summary了,是依照词库来获取的:
# 导入scGPT的词库
with open(f"{data_dir}/vocab.json', 'rb') as handle:
vocab_gene = json.load(handle)
vocab_gene_list = list(vocab_gene.keys())
# 导入Geneformer的词库
with open(f"{data_dir}/token_dictionary.pkl", 'rb') as handle:
token_dictionary = pickle.load(handle)
vocab_gene_list_results = mg.querymany(sorted(vocab_gene_list), scopes='symbol', species='human')
token_dictionary_results = mg.querymany(sorted(token_dictionary.keys()), fields="symbol")
2.3 用ChatGPT来获取基因的summary文本
该代码作者提供了,在gene_embeddings_examples.ipynb代码中:
gene_name_to_GPT_response = {}
gene_completion_test = ['ALPP']
for gene in sorted(gene_completion_test):
if gene not in gene_name_to_GPT_response:
print('gene name',gene)
completion = openai.ChatCompletion.create(model="gpt-3.5-turbo",
messages=[{"role": "user", "content": f"Tell me about gene {gene}"}])
gene_name_to_GPT_response[gene] = completion.choices[0].message.content
gene_name_to_GPT_response['ALPP']
2.4 用ChatGPT3.5模型生成文本embedding
该代码在gene_embeddings_examples.ipynb中,模型使用text-embedding-ada-002,文本的embedding维度是1536(作者自定义),需要自备Open AI的API:
import json
import numpy as np
import openai
# remember to set your OpenAI api key
openai.api_key = ''
def get_gpt_embedding(text, model="text-embedding-ada-002"):
text = text.replace("\n", " ")
return np.array(openai.Embedding.create(input = [text], model=model)['data'][0]['embedding'])
with open(f"{data_dir}NCBI_cleaned_summary_of_genes.json", 'r') as file:
NCBI_cleaned_summary_of_genes = json.load(file)
gpt_gene_name_to_embedding_clean_text = {}
GPT_DIM = 1536 # 自定义embedding的维度1536
for key, text in sorted(NCBI_cleaned_summary_of_genes.items()):
if key not in gpt_gene_name_to_embedding_clean_text:
print('key',key)
if NCBI_cleaned_summary_of_genes[key] == '':
# if the dictionary does not have information about a gene
NCBI_cleaned_summary_of_genes[key] = np.zeros(GPT_DIM) # it's hard coded to be 0
else:
NCBI_cleaned_summary_of_genes[key] = get_gpt_embedding(text)
with open(f"{data_dir}GPT_3_5_gene_embeddings.pickle", "wb") as fp:
pickle.dump(gpt_gene_name_to_embedding_clean_text, fp)
其实后续这里可以用其它免费的API,比如DeepSeek V3、千问、Hugging face上也有很多免费开源的把文本转换成embedding的模型。
3.模型架构
GenePT有2种模型,分别为GenePT-w和GenePT-s
3.1 GenePT-w
这个模型是需要对每个基因的文本embedding进行加权运算。
-
1.首先对原本的基因表达矩阵做个预处理(scanpy标准流程)
-
2.然后对 GenePT 基因文本embedding进行加权平均,权重由每个基因的归一化表达量决定(就是上面的scanpy处理之后的结果)
-
3.对于一个特定的细胞,获取该细胞中所有基因的归一化表达量;将每个基因的嵌入向量乘以其对应的归一化表达量;将所有加权后的基因嵌入向量相加;对得到的向量进行归一化,使其具有单位ℓ2范数。这意味着将向量的每个分量除以向量的长度(欧几里得范数),使得向量的长度变为1。
举个例子,假设一个细胞有3个基因A、B、C,它们归一化之后的表达量是0.5、0.3、0.2,对应的基因文本embedding向量是 E a E_a Ea、 E b E_b Eb、 E c E_c Ec
- 1.加权:基因A(0.5* E a E_a Ea),基因B(0.3* E b E_b Eb),基因C(0.2* E c E_c Ec)
- 2.求和:ALL = 0.5* E a E_a Ea+0.3* E b E_b Eb+0.2* E c E_c Ec
- 3.归一化:(0.5* E a E_a Ea)/ALL,(0.3* E b E_b Eb)/ALL,(0.2* E c E_c Ec)/ALL
这样就得到了GenePT-w的embedding,所以该模型只关注有表达的那部分基因。
最后用已有的聚类方法做细胞聚类,用逻辑回归和随即森林做细胞注释和疾病状态分类等下游任务。
3.2 GenePT-S
不用计算权重,在对scanpy预处理之后的基因表达矩阵的每个细胞中,基因表达值从大到小排序,然后每个细胞提取前N个基因(自定义),这样就组成了cell句子。
然后用基因名称,或NCBI基因summary,或ChatGPT生成的基因描述,替换每个细胞中对应基因的位置,送入到GPT的文本生成embedding模型中获取GenePT-s embedding,本质上就是cell embedding。
最后也是用已有的聚类方法做细胞聚类,用逻辑回归和随即森林做细胞注释和疾病状态分类等下游任务。
4.研究结果
GenePT embedding在结果上效果很好,有时甚至超越 Geneformer,尽管Geneformer是从大型预训练数据集和更复杂的分类头。而且,仅包含基因名称的 GPT-3.5 embedding在某些任务中也表现出很高的准确率。
在细胞类型、癌症类型和捐赠者年龄分类任务中的AMI 和 ARI 指标方面, GenePT-w 和 scGPT 的潜在表示远远优于 GenePT-s 和 Geneformer 的embedding:
在疾病表型预测的分类任务中,GenePT有更好的表现:
细胞聚类和细胞注释效果展示:
批次效应去除的效果展示:
总的来说,文章的思路很巧妙,借助已有大语言模型的方法,能在本地上进行模型的训练,很酷很机智,因为自己从头训练获得cell embedding是很需要算力支撑的,大部分实验室/课题组无法满足。而这篇文章仅2个人就完成了。赞!