🔛 引

八月初的时候,博导跟我介绍了这个项目,大概内容就是对某市供水管网中的泵站与清水池进行优化调度。第一次博导跟我探讨这个项目的时候,我还是有点忐忑,觉得万一自己干不好岂不是砸了老师的招牌。但是我导说了句特别有安全感的话:没事,只管干吧,搞不出来有我给你兜着!安全感直接拉满,奥里给,干!还得是我导儿。

历时4个多月,十几次的每周PPT工作汇报,终于完成了模型的算法设计、实际运维逻辑匹配、数据库输入输出对接、线上部署等工作。在这个项目过程中,也经历过很多次的绝望、失落、喜悦,以及无数次的DEBUG,感觉自己还是学到了很多东西的。这篇博客就来记录下整个项目下来我自己的一些学习与心得体会。

在上次的组会过程中,我其实已经进行了一个大概的总结,所以说接下来就按照组会PPT中的内容进行一个记录吧~

组会汇报
组会汇报

⏱️ 考虑时间成本

Quote / 参考

面对未知的事物,试错过程需要花费大量时间成本,凡事一定要提前准备,预备好抵抗未知错误的缓冲区。

对于任何一件未知的事(这个未知是指没有别人做过类似的事可以给你参考),那么就一定要提前考虑到遇到未知错误所花费的时间成本。

在项目开始前,博导说预备时间是两个半月,然后我们就先按两个月的来,给最后留半个月的时间缓冲。我当时觉得时间还是很充裕的。

然而实际情况做下来,我们花费了将近4个多月,几乎是原来时间的两倍了。

首先是第一个问题:项目开始前一定要做充分的调研,大方向不能错,或者说尽量少错。

在项目开始前,与甲方多次沟通交流,确定了的方案是“基于历史数据驱动的智能调度模型研究”。从八月到国庆节前,我们也是一直按照这个方向来做的。结果到了国庆节前发现,我们所走的路是存在致命问题的,不得已又改变路线,选择了“基于区间流量平衡与遗传算法的智能调度模型研究”,这也就意味着我们前期浪费了巨多时间做了无用功。

其实选择的这个新方案,我导在第一次跟我讲这个项目的时候就提到了这种方案。因为对于一个泵站来说,其水泵开关的组合非常适合使用遗传算法(Genetic Algorithm, GA)进行求解。只是我当时根本不知道遗传算法是个什么东西,所以没有GET到我导的意思。

现在看来,我导对大方向的把握真是有点东西,如果一开始就按照我导的思路做,也许我们能减少很多无用功了。

第二个感受比较深的过程是在做项目前后端数据库对接的时候。简单来说,我负责模型的构建,模型输入输出的数据需要从数据库中读取或写入,另外一个团队负责WEB前端的开发,前端显示的内容就是从数据库中读取的。

这里,犯了一个很严重的逻辑问题。直接与数据库对接的是前端和模型,那么前端就应该在与模型商议数据库结构之前,先确定了用户的需求。但是实际上前端团队并不是这样做的,而是边与用户沟通加入新的需求边与模型端沟通确定写入数据格式。这就造成了一个问题,前端一边与模型一起对接数据库,一边与顾客商议需求,这就给我的模型带来了很大困扰,我对我的模型输出进行了多次很大结构上的调整,花费了很多时间。所以说,选择与靠谱的团队进行合作还是很重要的😑。

🤌 嘴上说,心里想, 不如手上做

Quote / 参考

不知道什么行不行,那就先动手试试,不动手永远不知道结果。动手了还真可能一个个慢慢就解决了。

上面也提到了,在国庆前后我们准备更换新的工作路线。我导跟我提到了GA,说GA非常适合我们当时所遇到的问题,但是当时的我对于GA一窍不通。

对于未知的事物,人们的本能总是恐惧,所以总是想逃避。

当时我的一听到“算法”两个字,似乎就感觉一座大山压在我的身上,要让我迅速学习一个算法并应用到实际项目,这对我来说太难了。所以,那天我还是想看看以前的路到底还有没有办法能够继续走得通。

一直到最后,发现老路实在是走不下去了,我才“逼上梁山”开始学习GA。但是当我真的开始学习GA,发现它其实并没有想象中的那么难。从初识到应用到自己的项目里,前后也就是三五天的时间。

通过这件事就说明两点:

  1. 要对自己有信心,面对未知事物,肯定会遇到困难。但是要相信自己——只用功夫深,铁棒磨成针!!!
  2. 凡事要一点点来,不用心急,每天进步一点点——水滴石穿!!!

🐛 先谋大略,后顾细微

Quote / 参考

技术应用于实际工程,需要考虑太多太多太多的未知情况,前期尽量去考虑照顾到,大方向不要出错误,后期预留一定的时间慢慢调试实际运行中的小问题。

如果一项技术的科学研究已经发展到了98%,那么能将其30%成功应用到实际就很不错了, 科学与技术之间还是有很多隔阂的。这个隔阂,其实指的就是将科学应用到实际过程中需要协调的未知情况,因为科学实验大部分是在较为理想的环境中进行的。

可能并没有什么好的办法能够消除这种隔阂的,只能说是在技术应用的过程中就尽量多的去考虑实际中可能遇到的情况。然后,还要在实际部署后,预留大量的时间去DEBUG。

💡 君子生非异也,善假于物也

Quote / 参考

擅用ChatGPT!!!特别是数据库相关内容(猜测可以推广到Numpy、Pandas的数据分析),把问题描述清楚(可以采用分步提问的方式)。

从我小学接触互联网以来,互联网第一次给我的震撼是CS Online(一款在线射击游戏),第二次就是今年的ChatGPT。

这个项目的全过程中,涉及到数据库的操作全部是ChatGPT帮助我完成的,对于数据库这种本身其结构性就很强的东西,ChatGPT的回答相当完美,几乎没有出现过任何错误。

ChatGPT另一个比较好的应用场景是基础知识的学习,最近在学习图论的过程中,我没有看过任何视频教程,全部都是跟着ChatGPT进行学习的。这让我的学习效率得到了极大得提高。

前几天毫不犹豫下单了ChatGPT 4.0,这个价格能够提供给我的帮助让我觉得非常实惠。

⌨️ Python程序设计指南(非正式版)

Quote / 参考

项目程序设计一定要最起码有一套“自己的规范”。

结构化设计

这个项目核心文件将近3000行代码,算是我目前写过最大的项目了。在项目开发的过程中,越发觉得程序设计规范化的重要性。当你写10行代码时,没有什么需要注意的;但是当你写了1000行代码时,回过头来可能你自己都忘了刚开始写的是什么。这个时候就凸显了注释和结构的重要性。

这里所说的结构化其实并不一定要按照Python编程规范或者说某一种固定的格式来编写代码。我个人觉得是最起码你自己要有一套自己的规范(如果你是单打独斗的话)。

例如,我就喜欢对每个类设置一个run()函数来作为类对象的入口函数:

if __name__ == '__main__':
    optimizer = Optimizer(
        log_level=logging.INFO  # logging.INFO, logging.DEBUG
    )
    optimizer.run()

虚拟环境

在大项目开发过程中,虚拟环境真的太重要了,主要体现在以下三点:

  1. 有可能项目需要部署到其他电脑来测试,如果没有虚拟环境,那么就需要在新电脑重新配置一个与原电脑一样的环境,非常麻烦;
  2. 避免了其他项目环境对本项目的污染,也避免了本项目对系统环境的污染;
  3. 如果程序需要打包为EXE或者其他可执行文件,那么虚拟环境可以避免无用的包被打包进程序里,从而减小程序的体积。

如何导出当前环境中的所有包及其版本号?

使用命令: pip freeze > .\requirements.txt即可。会在根目录生成一个名为requirements.txt的文件。

将该文件复制到需要配置新环境的电脑中,使用命令:

pip install -r requirements.txt

即可一键复制安装源环境中的所有包。

EXE程序的打包

PyInstaller 是最常用和最方便的打包工具,尤其是由于其对不同操作系统的广泛支持和对各种Python版本和库的兼容性。使用PyInstaller打包Python程序相对简单,社区支持也很强大。PyInstaller可以打包Python程序为独立的可执行文件,适用于Windows、macOS和Linux。它支持Python 2.7和Python 3.5及以上版本,能够打包大多数Python库,并且有很多选项来自定义打包过程,下面记录几个打包过程踩的坑。

打包程序

(venv) PS D:\Code\Optimizer> pyinstaller -w -i favicon.ico --add-data="data;data" main.py

参数解释如下:

  • -w:让程序以无命令行的形式运行;
  • -i favicon.ico:将根文件夹中的favicon.ico作为程序的LOGO;
  • --add-data="data;data"data文件夹打包为程序下的data文件夹。

异常捕获

Python程序在经过打包后,如果有异常就会直接闪退,非常不利于调试错误。这个时候我们可以使用:

try:  # 尝试捕获异常
    ...
except KeyError as e:  # 成功捕获异常
    print(e)
else:  # 未发现异常
    ...
finally:  # 最终要执行的
    ...

来捕获并处理异常。

路径处理

Python程序如果需要涉及读写文件的操作,那么就必须使用下面的函数来兼容EXE运行时的路径问题:

@staticmethod
def resource_path(*args):
    """
    获取文件路径, 兼容EXE打包问题
    :param args: 任意多个参数, 文件路径. 例如访问 './data/Setting_CQ.json'文件, 需要输入两个参数'data', 'Setting_CQ.json'
    :return: 组合后的绝对路径
    """
    if hasattr(sys, '_MEIPASS'):
        # 在PyInstaller打包后的可执行文件中
        return os.path.join(sys._MEIPASS, *args)
    else:
        # 在原始Python脚本中
        return os.path.join(os.path.abspath("."), *args)

💨 结

总体上来说,这个过程还是挺锻炼人的,我在这个过程中学习到了很多东西,也成长了很多,也算是自己的一笔财富吧。加油!(不过我还是希望后面甲方反馈的BUG能少一点~🙏)

🙏🙏🙏佛祖保佑🙏🙏🙏

print("                            _ooOoo_  ")
print("                           o8888888o  ")
print("                           88  .  88  ")
print("                           (| -_- |)  ")
print("                            O\\ = /O  ")
print("                        ____/`---'\\____  ")
print("                      .   ' \\| |// `.  ")
print("                       / \\||| : |||// \\  ")
print("                     / _||||| -:- |||||- \\  ")
print("                       | | \\\\\\ - /// | |  ")
print("                     | \\_| ''\\---/'' | |  ")
print("                      \\ .-\\__ `-` ___/-. /  ")
print("                   ___`. .' /--.--\\ `. . __  ")
print("                ."" '< `.___\\_<|>_/___.' >'"".  ")
print("               | | : `- \\`.;`\\ _ /`;.`/ - ` : | |  ")
print("                 \\ \\ `-. \\_ __\\ /__ _/ .-` / /  ")
print("         =======`-.____`-.___\\_____/___.-`____.-'=======  ")
print("                            `=------='  ")
print("  ")
print("         .............................................  ")
print("                  佛祖镇楼                  BUG消失  ")
print("          佛曰:  ")
print("                  写字楼里写字间,写字间里程序员;  ")
print("                  程序人员写程序,又拿程序换酒钱。  ")
print("                  酒醒只在网上坐,酒醉还来网下眠;  ")
print("                  酒醉酒醒日复日,网上网下年复年。  ")
print("                  但愿老死电脑间,不愿鞠躬老板前;  ")
print("                  奔驰宝马贵者趣,公交自行程序员。  ")
print("                  别人笑我忒疯癫,我笑自己命太贱;  ")
print("                  不见满街漂亮妹,哪个归得程序员?")