最近想添加封面图,想尝试使用StableDiffusion的AIGC自动生成封面图。但是,国内chatGPT很容易被封,像DALL·E这种又没法注册,国内百度的文言一格等未开放API,所以只有使用开源大模型了。
类似github管理代码仓库一样,比较有名的管理开源大模型的网站主要有2个:
考虑到第一次使用,学习曲线需要比较平缓才好入门,我使用国内的ModelScope来做大模型HelloWorld。最终决定使用阿里达摩院提供的中文StableDiffusion-通用领域 这个大模型进行生成。
M2芯片本地安装环境 官方提供了一个环境配置教程 ,但是没有看到任何有关M2芯片的提示,于是我转而采用了CSDN上的教程Mac M1 安装 modelscope 深度学习库 避免踩坑。
安装ANACONDA anaconda有点像是python界的docker,可以安装很多个版本的python环境不打架。
创建modelscope python环境 安装完成后可能还不能正常使用conda命令,需要先source一下:
1 source {安装的路径}/anaconda3/bin/activate
然后就可以创建modelscope自己的python环境了,使用3.8版本:
1 2 conda create -n modelscope python=3.8 conda activate modelscope
官方文档是3.7,但是CSDN上说python3.7和python3.9有问题,所以用3.8。
执行完activate之后,你会发现命令行前面会多一个(modelscope)
这样的前缀,说明你进入了modelscope这个环境。
在modelscope环境下,下载Pytorch框架包 可以使用清华源加速:
1 pip3 install torch torchvision torchaudio -i https://pypi.tuna.tsinghua.edu.cn/simple
在modelscope环境下,下载tensorflow框架包 tensorflow似乎就会有mac芯片兼容性问题,因此直接采用CSDN上的解法:
1 GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=1 GRPC_PYTHON_BUILD_SYSTEM_ZLIB=1 python -m pip install tensorflow-macos -i https://pypi.tuna.tsinghua.edu.cn/simple
安装ModelScope常用的包 1 pip install "modelscope[cv,nlp,multi-modal,science]" -f https://modelscope.oss-cn-beijing.aliyuncs.com/releases/repo.html
验证环境正确安装 使用分词模型进行分词:
1 python -c "from modelscope.pipelines import pipeline;print(pipeline('word-segmentation')('今天天气不错,适合 出去游玩'))"
如果环境正确,就能成功对这句话进行分词:
1 {'output' : ['今天' , '天气' , '不错' , ',' , '适合' , '出去' , '游玩' ]}
测试大模型 根据中文StableDiffusion-文本生成图像-通用领域 ,我们可以进行模型测试:
1 2 3 4 5 6 7 8 9 10 import torchimport cv2from modelscope.pipelines import pipelinefrom modelscope.utils.constant import Taskstask = Tasks.text_to_image_synthesis model_id = 'damo/multi-modal_chinese_stable_diffusion_v1.0' pipe = pipeline(task=task, model=model_id, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32) output = pipe({'text' : '明亮插画 人工智能 chatGPT' , 'num_inference_steps' : 50 , 'guidance_scale' : 7.5 , 'negative_prompt' :'模糊的' }) cv2.imwrite('result.png' , output['output_imgs' ][0 ])
1 2 conda activate modelscope python test.py
期间会下载很久大模型(而且不能中断,中断就得重新来,这点我还没去仔细研究),下载完成后,就会根据你的提示词生成图片,我这里的提示词是明亮插画 人工智能 chatGPT
。这里即使没有GPU也可以的,它会使用CPU去跑,生成一张图片大约需要4分钟左右。
然后你就可以得到一张图片:
这张图默认是512x512像素的,我查了一下,似乎并不能生成非正方形的图像,不过即使是正方形,hexo的cover也能自动调整显示尺寸。另外有一个遗留问题,就是图片比较模糊,暂时没有解决方法。大概率是因为使用了贫穷的CPU进行跑图的关系。
使用ChatGPT帮我在博客里注入cover标签 生成基本逻辑代码 提示 1 2 3 4 5 6 7 8 9 10 POST https: { "model" : "gpt-3.5-turbo" , "messages" : [ { "role" : "user" , "content" : "用python3完成如下功能,注意是macOS操作系统:我有一堆hexo博客文件,格式是markdown。如果文件内容头部有类似'cover: /image/123.jpg'的注解,说明有封面图了,跳过;如果文件内容头部没有类似注解,则调用test.py为其生成一张图片到image目录下,并插入注解到内容中" } ] }
回复 以下是一种实现方式,其中假设markdown文件都存放在post文件夹下,test.py脚本用来生成封面图并返回文件名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 import osimport subprocessdef has_cover (path ): with open (path, 'r' ) as f: lines = f.readlines() for line in lines: if line.startswith('cover:' ): return True return False def generate_cover (path ): cmd = f'python3 test.py {path} ' filename = subprocess.check_output(cmd, shell=True ).decode('utf-8' ).strip() with open (path, 'r+' ) as f: lines = f.readlines() lines.insert(2 , f'cover: /image/{filename} \\n' ) f.seek(0 ) f.writelines(lines) return filename for filename in os.listdir('post' ): if filename.endswith('.md' ): path = os.path.join('post' , filename) if not has_cover(path): generate_cover(path)
需要注意的是,test.py脚本需要自己编写,用于生成封面图并返回文件名。另外,上述代码中默认在博客根目录下建立image文件夹,用于存放生成的封面图。如果需要修改文件夹名或路径,可以修改代码中对应的部分。
分析 近乎完美,逻辑清晰,语法正确,而且对于不熟悉python的我来说,很多地方我都想不到的,比如:
{filename}
字符串竟然可以直接这样注入
subprocess竟然可以运行command命令
我想的是完全解析整个文件,但它的思路是line.startswith('cover:')
,以及lines.insert
不过有个缺憾,也怪我没有提示清楚,就是hexo的markdown头部,是用yaml写的,我想利用tags来作为提示词,但是yaml有两种写法:
1 2 3 4 5 6 7 tags: [标签1 , 标签2 ]tags: - 标签1 - 标签2
我的习惯是第1种写法,但是abbr这个hexo插件在注入abbrlink时会自动改为第2种写法,这就导致两种写法都存在,都得解析。如果直接用line.startswith就不太好搞。
解决提取标签的问题 于是我又问了一下chatGPT思路,如何提取标签?
提示 1 2 3 4 5 6 7 8 9 { "model" : "gpt-3.5-turbo" , "messages" : [ { "role" : "user" , "content" : "用python3完成如下功能,注意是macOS操作系统:有一个markdown文件,需要提取tags内容,提取为python数组。其格式可能为'tags:\n- 平行宇宙\n- 量子力学',也可能为'tags:['平行宇宙', '量子力学']'" } ] }
回复 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { "id" : "chatcmpl-7Gkdb9UAEWK0sdP8TQGzeYNlEolyn" , "object" : "chat.completion" , "created" : 1684226879 , "model" : "gpt-3.5-turbo-0301" , "usage" : { "prompt_tokens" : 84 , "completion_tokens" : 367 , "total_tokens" : 451 } , "choices" : [ { "message" : { "role" : "assistant" , "content" : "可以使用正则表达式来提取tags内容,代码如下:\n\n```python\nimport re\n\nwith open('example.md', 'r') as f:\n content = f.read()\n tags_match = re.search(r'^tags:([\\s\\S]*?)(?=^\\w)', content, re.M)\n if tags_match:\n tags_str = tags_match.group(1)\n tags = re.findall(r'[\\w\\s]+', tags_str)\n tags = [tag.strip() for tag in tags]\n print(tags)\n```\n\n这里的正则表达式`r'^tags:([\\s\\S]*?)(?=^\\w)'`的含义是:从文本开头匹配'tags:',然后匹配任意非空白字符和空白字符0到无限次,非贪婪模式,直到遇到一个以字母或数字开头的行为止。\n\n如果查找成功,则会得到一个含有tags内容的字符串,例如' - 平行宇宙\\n - 量子力学\\n'或\"['平行宇宙', '量子力学']\"。接下来,使用正则表达式`r'[\\w\\s]+'`提取出所有的单词和空格,再通过循环去除空格并添加到一个数组中即可。最后将数组打印出来即可。\n\n注:假设markdown文件中只含有一组tags内容。如果含有多组tags内容,只提取第一组。如果不含有tags内容,则不会打印任何东西。" } , "finish_reason" : "stop" , "index" : 0 } ] }
分析 正则的思路很好,不过我还是觉得不太可靠,毕竟文本中也可能出现tags:xxx
这种被匹配出来,虽然也有方法去除,但可靠性不太高,不想给自己挖坑。
最终脚本 最终,我还是自己采用了yaml解析的方式解决这个问题。不过仍然参考了大量chatGPT提供的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import osimport torchimport cv2from modelscope.pipelines import pipelinefrom modelscope.utils.constant import Tasksimport yamldef parse_blog (blog_md_file ): with open (blog_md_file, 'r' , encoding='utf-8' ) as f: raw_data = f.read() pattern = '---' blog_data = {} search_result = list (pattern_search(raw_data, pattern)) if len (search_result) >= 2 : blog_info_str = raw_data[search_result[0 ] + len (pattern): search_result[1 ]] blog_data = yaml.load(blog_info_str, Loader=yaml.SafeLoader) blog_data['content' ] = raw_data[search_result[1 ] + len (pattern):] return blog_data def pattern_search (string, pattern ): index = 0 while index < len (string)-len (pattern): index = string.find(pattern, index, len (string)) if index == -1 : break yield index index += len (pattern)-1 def generate_cover (path, blog_data ): task = Tasks.text_to_image_synthesis model_id = 'damo/multi-modal_chinese_stable_diffusion_v1.0' pipe = pipeline(task=task, model=model_id, torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32) text = "明亮插画 " + " " .join(blog_data.get("tags" )) cover_path = '/cover/' + str (blog_data.get("abbrlink" )) + ".png" print ("generating cover for " + path + ", prompt is " + text + ", image name is: " + cover_path) output = pipe({'text' : text, 'num_inference_steps' : 50 , 'guidance_scale' : 7.5 , 'negative_prompt' : '模糊的' }) cv2.imwrite("source" + cover_path, output['output_imgs' ][0 ]) with open (path, 'r+' ) as f: lines = f.readlines() lines.insert(2 , f"cover: {cover_path} \n" ) f.seek(0 ) f.writelines(lines) return filename if __name__ == '__main__' : for filename in os.listdir('source/_posts' ): if filename.endswith('.md' ): path = os.path.join('source/_posts' , filename) blog_data = parse_blog(path) if not ('cover' in blog_data.keys()): filename = generate_cover(path, blog_data)
总结 这次尝试让我熟悉了大模型和chatGPT的使用。我之前一直觉得chatGPT不太能辅助写代码,但是这次深刻体会到的一点是,chatGPT不太能辅助你写你擅长语言的熟悉的代码 。如果让chatGPT写Java,我能挑出一百个毛病。但是如果让它写python,我会惊叹原来还能这样写、原来还有这个库、原来还有这个函数!它会加速你实现目标的过程,无需担心编程语法。另外,就算是Java,我也不熟悉文件操作之类的日常不会使用的库,相比于网上搜索,直接问chatGPT来得更快,而且更符合需求。
注意:2024.08.05 为了优化网站加载速度,去除了封面图展示