就如当年学习搜索引擎的技巧,我们也需要学习一些和AI沟通的技巧,给出合理且充分的限定条件,高效的获取需要的答案。

如果你换个角度呢,当前的AI属于一个记忆力很好的小孩子,它拥有过目不忘的能力,有抄作业的能力。我们需要做的是学会如何正确、有效的和AI沟通,精准的描述需求,帮助AI生成预期的结果。

科普

火出天际的AI确切的说Generative Pre-Training,直译过来就是生成式的预训练。它是一种基于互联网可用数据训练的文本生成深度学习模型,用于问答、文本摘要生成、机器翻译、分类、代码生成和对话 AI 等任务。目前已经有 GPT-1、GPT-2、GPT-3 和 GPT-4 等不同版本的模型,每个版本都比前一个版本更大、更强大。

到底有没有智能

  • 相似度足够高,准确率也就越高
  • 基础的、重复性的工作,进过特定训练,不再需要人工的介入

生成式AI是一种利用现有的文本、音频、图像等数据来创建新内容的技术。它可以用于文本生成、语音合成、图像生成、对话系统等多种任务。生成式AI的逻辑性取决于它的训练数据和模型结构。一般来说,生成式AI可以在一定程度上遵循语法、逻辑和常识,但也可能出现错误、偏差或不真实的内容。因此,生成式AI的输出需要人类的判断和验证,不能盲目相信或使用。

Prompt 工程师

时间河流不会逆流,人需要学会适应潮流。你可以认为AI不智能,没有逻辑性,它经常写出来不能用的代码。

如果你换个角度呢,当前的AI属于一个记忆力很好的小孩子,它拥有过目不忘的能力,有抄作业的能力。我们需要做的是学会如何正确、有效的和AI沟通,精准的描述需求,帮助AI生成预期的结果。

对话模式

两年前Github Copilot发布,谁也想不到两年后,OpenAI横空出世,让人类认知到了大语言模型的能力。

基于基于注释的编程基于对话的编程,交互逻辑上完全不同,对话的模式,小白用户的交互体验友好,此处必须提到NewBing在每个问题之后,给出的追问提示,微软在尝试引导用户,获取AI知识库里面,更多的内容。

举个栗子

场景:高铁旅途时间漫漫,编码是个不错的选择。流量有限的情况下,处理几个图片,获取了图片素材,下载软件处理图片稍微有点奢侈,网络条件也不允许。

需求:有一批图片素材,带透明背景图,用来作为小程序的导航栏,选中的状态是彩色的,没选中的状态是黑白的,素材都是彩色的,需要预处理一下。

Prompt:编写一段 python 代码,从一个文件夹中读取所有的图片,将图片转换成黑白,维持透明背景,保存图片的时候,优化图片的质量,减少文件体积,同时在控制台展示,优化前后的文件体积大小对比

# 导入必要的库
import os
import PIL
from PIL import Image

# 定义一个函数,将图片转换成黑白,维持透明背景,保存图片,并返回文件体积
def convert_and_save(image_file):
    # 打开图片
    image = Image.open(image_file)
    # 获取图片的模式,如果是 RGBA 模式,说明有透明背景
    mode = image.mode
    if mode == "RGBA":
        # 创建一个和图片大小一致的白色背景图片
        background = Image.new("RGB", image.size, (255, 255, 255))
        # 将原图片粘贴到白色背景上,忽略透明像素
        background.paste(image, mask=image.split()[3])
        # 将合成的图片转换成灰度模式
        gray_image = background.convert("L")
        # 将灰度图片再转换成 RGBA 模式,以便保留透明背景
        final_image = gray_image.convert("RGBA")
    else:
        # 如果不是 RGBA 模式,直接将图片转换成灰度模式
        final_image = image.convert("L")
    # 获取原图片的文件名和扩展名
    file_name, file_ext = os.path.splitext(image_file)
    # 定义新图片的文件名,添加 _bw 后缀表示黑白
    new_file_name = file_name + "_bw" + file_ext
    # 保存新图片,并优化质量,减少文件体积
    final_image.save(new_file_name, optimize=True)
    # 获取原图片和新图片的文件体积,并返回
    old_size = os.path.getsize(image_file)
    new_size = os.path.getsize(new_file_name)
    return old_size, new_size

# 定义一个文件夹路径,存放所有的图片
folder_path = "images"
# 遍历文件夹中的所有文件
for file in os.listdir(folder_path):
    # 拼接完整的文件路径
    file_path = os.path.join(folder_path, file)
    # 判断是否是图片文件,根据扩展名判断,可以根据需要添加或修改扩展名
    if file_path.endswith((".png", ".jpg", ".jpeg", ".gif")):
        # 调用函数,转换并保存图片,并获取文件体积
        old_size, new_size = convert_and_save(file_path)
        # 在控制台展示优化前后的文件体积大小对比
        print(f"{file}: {old_size} bytes -> {new_size} bytes")

AI迅速给出了答案,看着很漂亮,用起来也没问题,经验丰富的程序源,会觉得下面的代码不够通用,也没法上生产环境:

  • 参数固定了
  • 没有日志文件,信息都打印在控制台
  • 图片处理的任务,适合多线程模型

NewBing登场,依据提示,选择了一个问题:这段代码有什么可以改进的地方吗

这次给的答案,实话,完全超出了预期,一般程序员能想到的优化措施,AI都给出对应的建议

这段代码可能还有一些可以改进的地方,比如:

可以使用 argparse 库,让用户可以通过命令行参数指定文件夹路径和扩展名,而不是写死在代码里。
可以使用 logging 库,记录程序的运行情况,而不是直接打印到控制台。
可以使用 try-except 语句,捕获可能出现的异常,比如文件打开失败,图片转换失败等,并做相应的处理。
可以使用 multiprocessing 库,利用多核处理器,加速图片的转换和保存过程。

很友好的应答,考虑到部署的环境存在差异,允许不支持多进程

当然,我很乐意帮你修改一下代码。我尝试添加了 argparse, logging 和 try-except 语句,但没有添加 multiprocessing 库,因为我不确定你的电脑是否支持多核处理。修改后的代码如下:
# 导入必要的库
import argparse
import logging
import multiprocessing
import os

from PIL import Image


# 定义一个函数,将图片转换成黑白,维持透明背景,保存图片,并返回文件体积
def convert_and_save(image_file):
    # 打开图片
    try:
        image = Image.open(image_file)
    except Exception as e:
        logging.error(f"Failed to open {image_file}: {e}")
        return None, None
    # 获取图片的模式,如果是 RGBA 模式,说明有透明背景
    mode = image.mode
    if mode == "RGBA":
        # 创建一个和图片大小一致的白色背景图片
        background = Image.new("RGB", image.size, (255, 255, 255))
        # 将原图片粘贴到白色背景上,忽略透明像素
        background.paste(image, mask=image.split()[3])
        # 将合成的图片转换成灰度模式
        gray_image = background.convert("L")
        # 将灰度图片再转换成 RGBA 模式,以便保留透明背景
        final_image = gray_image.convert("RGBA")
    else:
        # 如果不是 RGBA 模式,直接将图片转换成灰度模式
        final_image = image.convert("L")
    # 获取原图片的文件名和扩展名
    file_name, file_ext = os.path.splitext(image_file)
    # 定义新图片的文件名,添加 _bw 后缀表示黑白
    new_file_name = file_name + "_bw" + file_ext
    # 保存新图片,并优化质量,减少文件体积
    try:
        final_image.save(new_file_name, optimize=True)
    except Exception as e:
        logging.error(f"Failed to save {new_file_name}: {e}")
        return None, None
    # 获取原图片和新图片的文件体积,并返回
    old_size = os.path.getsize(image_file)
    new_size = os.path.getsize(new_file_name)
    return file_name, old_size, new_size

# 定义一个函数,解析命令行参数,并返回文件夹路径和扩展名列表
def parse_args():
    # 创建一个解析器对象
    parser = argparse.ArgumentParser(description="Convert images to black and white and optimize quality.")
    # 添加一个必选的位置参数,表示文件夹路径
    parser.add_argument("folder_path", help="The path of the folder that contains the images.")
    # 添加一个可选的参数,表示扩展名列表,默认为 png, jpg, jpeg 和 gif
    parser.add_argument("-e", "--extensions", nargs="+", default=[".png", ".jpg", ".jpeg", ".gif"], help="The extensions of the image files.")
    # 解析命令行参数,并返回结果对象
    args = parser.parse_args()
    return args.folder_path, args.extensions

# 定义一个函数,打印优化前后的文件体积大小对比
def print_result(result):
    # 如果结果不为空,说明转换和保存成功
    if result:
        # 解包结果为文件名和文件体积元组
        if len(result) == 3:
            file, old_size, new_size = result
            # 在控制台展示优化前后的文件体积大小对比
            logging.info(f"{file}: {old_size} bytes -> {new_size} bytes")
        else:
            logging.info(f"{result}")

# 配置日志记录器,将日志输出到控制台和文件中,设置日志等级为 INFO
logging.basicConfig(level=logging.INFO,
                    format="%(asctime)s %(levelname)s %(message)s",
                    handlers=[logging.StreamHandler(),
                              logging.FileHandler("log.txt")])

# 调用函数,获取文件夹路径和扩展名列表
folder_path, extensions = parse_args()

if __name__ == "__main__":
    # Windows 需要这个函数的原因是 Windows 缺乏 fork() 函数(这不完全正确)。
    # 因此,在 Windows 上,fork() 是通过创建一个新的进程来模拟的,这个新的进程中运行的代码,在 Linux 上是在子进程中运行的。
    # 由于这些代码要在技术上无关的进程中运行,所以它必须在运行之前被传递到那里。
    # 传递的方式是先将它们序列化,然后通过管道从原始进程发送到新的进程。
    # 另外,这个新的进程被通知它必须运行通过管道传递的代码,通过向它传递 --multiprocessing-fork 命令行参数。
    # 如果你看一下 freeze_support() 函数的实现,它的任务是检查它运行在哪个进程中,是否应该运行通过管道传递的代码
    multiprocessing.freeze_support()

    # 创建一个进程池,根据电脑的核心数自动分配进程
    pool = multiprocessing.Pool()
    # 创建一个空列表,用于存放异步任务的结果对象
    results = []
    # 遍历文件夹中的所有文件
    for file in os.listdir(folder_path):
        # 拼接完整的文件路径
        file_path = os.path.join(folder_path, file)
        # 判断是否是图片文件,根据扩展名判断,可以根据需要修改扩展名列表
        if any(file_path.endswith(ext) for ext in extensions):
            # 调用函数,转换并保存图片,并获取文件体积,使用异步方式,不阻塞主进程
            result = pool.apply_async(convert_and_save, args=(file_path,), callback=print_result)
            # 将结果对象添加到列表中
            results.append((file, result))
    # 关闭进程池,不再接受新的任务
    pool.close()
    # 等待所有的任务完成
    pool.join()

后记

由于本地开发是windows系统,AI第一次给出的答案,并没有加上main函数,也没有multiprocessing.freeze_support。通过追问发生的报错,修复了代码。

就如当年学习搜索引擎的技巧,我们也需要学习一些和AI沟通的技巧,给出合理且充分的限定条件,高效的获取需要的答案。

注意:如果你是个编程新手,基于给出的注释,还有看不懂的地方,可以继续追问相关代码