如何让ChatGPT帮你写(生信)代码?

现在ChatGPT有多火、多好用,无需赘言。由于生成式AI(Generative AI)现在过于强大,很多人或者舆论开始担心,未来人类的工作,甚至包括很多“高级”工作,例如码农、高校老师、工程师、画师等职业都会被取代。咱也没法说这个一定不会发生,但是,与其担心失业,不如学习某球星“打不过就加入”的精神。换句话说,与其恐惧被AI取代,不如更好地学会如何用AI加持,助力自己的工作,做一个“AI-enhanced human”。

要做到这一点,最关键的就是学会与AI“沟通”。以我自己为例,今天在数据分析过程中,我遇到一个需求:就是要通过一个列表,批量替换很多系统发育树的label的名字(忽略什么是系统发育树,什么文件其实都一样)。于是尝试使用免费的GPT3.5来解决。这是一个相对比较复杂的需求。按照以往的经验,如果对ChatGPT一下提太多要求,反而会返回乱七八糟、不符合期望的答案。反过来说,就是我们使用它的时候一定要提非常具体(specific)的要求(某名校蒋姓AI大牛的原话)。

因此这就需要利用好现在生成式AI的一大优点:可以理解上下文(context)。这就意味着我们可以首先通过简单的问题,基于给出的结果,一步步增加功能,同步进行测试和debug,最后得到满足我们需求的代码。因此第一步,我试探性地问了这个具体的问题:

“Write python code to replace tip labels of a phylogenetic tree with a list, in which the first colomn with the label to be replace and the second colomn with the new label”

这个问题真的特别具体,我明确告诉它,我需要处理的文件是phylogenetic tree,对象是label,并且用于替换的列表第一列是原本的名字,第二列是用于替换的名字。本来也是试探性地,想看看它能不能理解什么是系统发育树,以及是否知道biopython等生信相关的包。没想到ChatGPT哥秒懂:

可以看见给出的结果十分强大,既包括了程序每个部分所实现的功能,也包括了一些对应文件、函数的详细解释和所需要依赖包的安装提示。

由于第一次返回的结果只相当于一个“示例”,ChatGPT返回的是所需要的功能最基本的函数结构,还对数据进行读取和批量处理,因此可以进一步“提要求”:

“Modify the code to read replacement_labels from a list file;” 

“Modify the code to apply the replacement to multiple phylogenetic tree in a folder;”

经过我的强烈要求,新的代码已经可以读取文件以及目录了。但是经常做分析的朋友,肯定希望一个代码能够通过各种输入一直使用,因此还需要让它能够根据我们输入的argument(args)来操作各种文件:

“revise the code to read folder_path from args;”“revise the code to read “list_file.txt” from the second args;”“modify the code to put all new phylogenetic tree into a new single folder;”“revise the code to read the output_folder from new args;”

至此,这个代码已经完全实现了我想要的功能,可以输入列表、包含系统发育树的文件夹,中间还增加了一个要求,就是将结果全部输出到一个输出文件夹。另外,对于有输入输出文件的程序,ChatGPT还会很贴心地标注好命令使用的方法:

经过测试,我发现代码已经能够良好运行,返回我需要的结果。并且相比我这种原本学生物、半路出家、“有生无信”的生信分析菜鸡写的半吊子代码,ChatGPT哥的代码还有非常工整、专业的过程和结果提示:

到这个时候,一切都太顺利,非常兴奋

。于是又打起主意:“好像程序运行得太慢了,能不能给我快点?” 刚好我不会写python多线程/多核运行的脚本,干脆让ChatGPT哥教教我:

“The code running speed is very low, can you make it faster?”

显然这个要求比之前的难不少,因此花费了比较长的时间,并且产生的代码一直报错。但是没关系,有困难找ChatGPT哥,直接告诉他:你写错了!

态度太好了,而且飞快生成了修改后的代码,让我非常不好意思

但是后续还有一些问题,不知道要怎么描述,所以尝试了一下直接丢报错信息给他:

就像一位五星客服,在一边道歉的过程中,他已经解决了我所有的问题。这时候已经不能叫他ChatGPT哥了,应该叫G神!!G神对不起,以后一定好好跟你说话!!

最后说一下使用体验和感想。

首先,ChatGPT对于我们这种半路出家的,“边查边学”的菜鸡是绝对的福音。因为这种学习模式,对于编程并没有有一个系统的了解。因此在遇到问题的时候,很多情况可能连怎么问问题,怎么查阅相关的资料都想不到。这种情况下,ChatGPT可以帮助我们“寻找灵感”,通过模糊的问题找到具体的答案,再一步步深入解决问题。同时在启发我们灵感的过程中,帮助我们补齐自己缺少的知识碎片。

另外,G神虽然神,但是也不是万能的。就效率而言,明显最好还是对编程、生信各方面的背景都有一定的了解,才能够尽快问到比较具体的问题,高效率地一步一步得到想要的答案。一个具体的点就是我需要程序通过终端的输入找到对应的目录和文件,直接跟他说“read from args”,肯定比“从我输入的文件。。文件夹找xxx”理解得更快。

还有一些使用技巧,例如如果产生的答案不太满意的时候,其实可以修改问题,重新生成答案(注意右边的编辑按钮):

最后,目前使用体验来说,3.5对于大部分人应该是足够了,每次想充钱的时候,我都告诉自己“你的水平还不配用4.0!” 另外,如果英文比较好,直接用英文问问题应该是比中文的效率更高,毕竟大模型训练还是以英文为基础的,而且与代码可以无缝切换。

Final code:

import osimport sysimport multiprocessingfrom Bio import Phylo# Check if the folder path, list file, output folder, and number of processes are provided as command-line argumentsif len(sys.argv) < 5: print(“Please provide the folder path, list file, output folder, and number of processes as command-line arguments.”) sys.exit(1)# Read the folder path from the command-line argumentfolder_path = sys.argv[1]# Read the replacement labels from the list filelist_file_path = sys.argv[2]replacement_labels = []with open(list_file_path, “r”) as file: for line in file: original_label, new_label = line.strip().split(“\t”) replacement_labels.append((original_label, new_label))# Read the output folder path from the command-line argumentoutput_folder = sys.argv[3]os.makedirs(output_folder, exist_ok=True)# Read the number of processes from the command-line argumentnum_processes = int(sys.argv[4])# Function to modify a treedef modify_tree(tree): # Iterate over each replacement label tuple for original_label, new_label in replacement_labels: # Find the tip with the original label and replace it with the new label for tip in tree.get_terminals(): if tip.name == original_label: tip.name = new_label return tree# Function to process a single filedef process_file(filename): file_path = os.path.join(folder_path, filename) # Load the phylogenetic tree from the file tree = Phylo.read(file_path, “newick”) # Modify the tree modified_tree = modify_tree(tree) # Save the modified tree to a new file in the output folder with “.modified.treefile” suffix output_filename = filename.split(“.”)[0] + “.modified.treefile” output_file_path = os.path.join(output_folder, output_filename) Phylo.write(modified_tree, output_file_path, “newick”) # Print a message for each processed file print(f”Processed: {filename})# Main function to ensure proper forking on Windowsdef main(): # Create a process pool pool = multiprocessing.Pool(processes=num_processes) # Iterate over the files in the folder and apply the process_file function using the pool file_list = [filename for filename in os.listdir(folder_path) if filename.endswith(“.phy.treefile”)] pool.map(process_file, file_list) # Close the pool and wait for all processes to finish pool.close() pool.join() print(f”All files processed. Modified trees are saved in the {output_folder} folder.”)if __name__ == __main__: multiprocessing.freeze_support() main()

Leave a Reply