结论先行

  • 熬夜学习 AI,脱发却依然摸不着门道?
    • 花了一周时间,AI 还是帮不了你写文案?
  • 有人用 AI 高效工作,他们的秘诀是什么?
    • 是 AI 真那么强,还是我们没找对方法?
  • 程序员的 AI 日记曝光:
    • 他用 AI 重塑了工作流程,我们却在纠结 AI 会不会骗人。
  • 看完这篇文章,你可能会恍然大悟:
    • 被忽视的 AI 能力,才是真正的效率杀手锏。
    • 想像高手一样驾驭 AI,不再为脱发烦恼?继续阅读!

原文地址 : How I Use AI
我如何使用“AI”
作者:Nicholas Carlini 2024-08-01

我并不认为“AI”模型 (人工智能模型) 这个词有多好,我甚至讨厌它。(我指的是:大语言模型)。我认为现在对它的宣传有点过火了。

它根本不是真正的 AI (人工智能)。但我希望,无论是否喜欢这个词的人,都能看到这篇文章。所以为了营销、SEO 和吸引眼球,我不得不使用它。

的确,任何新技术都会吸引投机者。许多公司喜欢宣称他们在“使用 AI (人工智能)”,就像他们之前说他们使用了“区块链”技术一样。(类似的情况我们已经见过 很多, 很多, 很多, 很多)。我们可能正处于一个泡沫之中。互联网曾在 2000 年经历泡沫破裂,但我们现在所使用的互联网应用,在过去可是科幻小说中的情节。

但我认为,我们最近取得的进步并非只是炒作。在过去的一年里,我每周都会花几个小时与各种大语言模型互动,它们解决我提出的各种难题的能力给我留下了深刻的印象。因此,我的编程速度至少提高了 50%,无论是在我的研究项目还是个人项目中。

我发现网上大多数讨论大语言模型的人,要么过于乐观,认为所有工作都将在三年内自动化;要么过于悲观,认为它们毫无用处。

所以在这篇文章中,我想尝试让讨论更务实一些。我不会预测未来,只想列举 50 个我(一名研究机器学习的程序员和研究科学家)与不同的大语言模型进行的对话,这些对话显著提升了我的研究能力,并帮助我完成各种编程项目。其中包括:

  • 使用我从未接触过的技术构建完整的网络应用。
  • 学习如何使用各种我之前不熟悉的框架。
  • 将许多程序转换为 C 语言或 Rust 语言,从而将性能提升 10 到 100 倍。
  • 精简大型代码库,从而大大简化项目。
  • 为我去年撰写的几乎每一篇研究论文编写初始实验代码。
  • 自动化几乎所有重复性任务或一次性脚本。
  • 几乎完全取代了网络搜索,在设置和配置新的软件包或项目时为我提供帮助。
  • 在调试错误信息时,大约 50% 的情况下取代了网络搜索。

如果要将这些例子归类,可以分为“帮助我学习”和“自动化繁琐的任务”。帮助我学习显然很重要,因为它让我能够做到以前觉得困难的事情;而自动化繁琐的任务(对我来说)同样重要,因为它让我能够专注于自己最擅长的领域,解决那些真正棘手的问题。

最重要的是,这些例子都是我如何利用大语言模型来帮助我的真实案例。它们的目的不是为了炫耀某些令人印象深刻的功能,而是源于我完成实际工作的需要。这意味着这些例子可能并不光鲜亮丽,但我每天的大部分工作也并不光鲜,而现在的大语言模型能够帮我自动完成几乎所有这些工作。

我希望通过这篇文章,用大量的例子来展示在过去的一年中,我如何利用大语言模型来提高我的工作效率。请记住,当你读完这些例子的时候,你看到的只是我使用大语言模型的案例中不到 2% 的部分。

所以,当你读到感到疲倦时——你很可能会——请随时使用左侧的导航菜单,这是我(或者说,一个大语言模型)专门为这篇文章创建的,因为它的内容实在是太长了。

细微差别

如果说互联网有什么事情做得不好,那就是把握细微差别。我不会说现在的大语言模型会统治世界,也不会谈论未来的模型能做什么或不能做什么。我只想讨论现在的模型是否对我有用。

你可能会想——怎么会有人专门写一篇文章来论证语言模型的实用性呢?这难道不是显而易见的吗?但似乎有很多人——无论是在学术界、软件工程领域还是媒体领域——都声称大语言模型毫无贡献,只是一时的炒作,几年后就会销声匿迹,对世界没有任何影响。我认为他们是错的,因为现在的大语言模型已经非常有用了。

但是我觉得有必要说明一下,因为还有另一群人持有相反的观点:他们认为现在的大语言模型可以取代所有的程序员,人们不应该学习编程,因为明年他们都会失业。我不会直接反驳他们的观点(这不是本文的重点),但我想明确表示,我并不是在支持他们的观点。

我也不会试图论证“为了目的可以不择手段”,说我们应该不顾大语言模型的负面影响(其中有很多)而去训练它们。

我完全理解,这些模型会带来负面影响,甚至是极其负面的影响。我指的是所有方面,从虚假信息到滥用,从监控到失业。(或者,如果你相信某些人的说法,甚至是人类灭绝?)我很快会专门写一篇文章来阐述我对大语言模型的负面影响的看法。链接会放在这里。但这与语言模型是否有用的问题是分开的——正如我所说,这正是我想在这里讨论的内容。

我进一步理解,你可能因为大语言模型容易产生幻觉、重复事实以及由于缺乏鲁棒性而可能出现的严重错误等局限性而不愿意使用它们—— 我可能比你更了解这些局限性。但这篇文章不会讨论这些,因为我认为即使存在这些缺陷,模型仍然是有用的。

我更进一步地理解,训练这些模型的伦理问题非常值得商榷。也许你反感它们未经授权就使用人们的数据进行训练(我可能比你更了解这一点)。或者,你可能会想到那些拿着微薄的工资来专门训练这些模型的人。我同意,这些都是问题。但这篇文章也不会讨论这些。

正如我已经反复强调的那样:我在这里只讨论一个问题,那就是这些模型,以它们现在的形式,是否有用。

我的一些背景

一般来说,我不是一个轻易相信事物的人。例如:尽管十年前经历了安全社区的加密炒作,但我完全避免写任何关于区块链的论文。我从未拥有过比特币。它们基本上没有任何用途——除了赌博和欺诈。日复一日,我对所有的说法都持怀疑态度。每当有人告诉我“新技术将改变世界”时,我的一般反应是漠不关心。

所以,当我告诉你,当我第一次听到有人告诉我,人工智能将非常有用,并将显著改变我处理日常工作的方式时,我的反应基本上是一样的,那就是:“眼见为实”。

除此之外,我还是一名安全研究员。近十年来,我的日常工作就是展示人工智能模型在面对任何未经训练的环境时是如何彻底失败的。我已经证明,稍微扰动机器学习模型的输入,使其产生极其错误的输出,这是微不足道的;或者大多数机器学习模型会记住其训练数据集中特定的例子,并在使用时重复它们。我完全理解这些系统的局限性。

然而,我在这里要说的是,我认为当前的大语言模型为我的工作效率提供了自互联网诞生以来最大的提升。老实说,今天,如果你让我在完成一项从我的工作中随机选择的编程任务时,可以选择使用互联网或使用最先进的语言模型,我可能会在超过一半的时间里选择语言模型。

我如何使用大型语言模型

我之所以强调“帮助我”,

是因为我的工作方式可能与你截然不同。以下是我在过去一年里与多个大型语言模型(LLM)交互时总结的一些使用案例,仅供参考。

你可能会觉得这些案例很奇怪或不实用,甚至与你的需求无关。我对此表示理解,毕竟每个人对 AI 工具的使用方式都不同。这些案例只是我个人的经验分享。

帮助我构建完整的应用程序

去年,我制作了一个测验,让人们测试自己对 GPT-4 解决任务能力的预测水平。这个测验非常受欢迎,获得了超过一千万次的页面浏览量。而且你知道吗?我几乎完全依靠 GPT-4 编写了这个应用程序的初始版本。整个过程是通过一系列的提问来完成的,我从询问应用程序的基本结构开始,逐步扩展功能。整个对话记录超过了 30,000 字,这极大地考验了(当时最先进的)原版 GPT-4 模型的能力。

ChatGPT 对话记录 - 编写应用程序的初始版本

编写应用程序的初始版本

🧑‍🦰User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
我计划使用 Flask 构建一个类似问答游戏的应用程序。每个页面将展示一个问题和一个预期的答案。用户可以通过滑动条来预测他们认为 GPT-4 以该预期答案作答的概率。每个页面都会有一个提交按钮。当用户点击提交后,页面会显示 GPT-4 是否成功回答了这个问题,并提供模型的实际答案作为参考。页面还会有一个“下一个问题”的按钮。

我需要以下页面:

- GET /home:介绍比赛内容并解释规则。提供一个链接以开始第一个问题,同时为用户创建一个会话变量。
- GET /question? Id=[uuid]:显示对应 [uuid] 的问题,页面内容如前述。
- POST /score:接收 POST 参数 id=[uuid]、session=[uuid] 和 guess=[0 到 1 之间的浮点数]。
- 首先,将用户的预测值添加到该问题的所有预测列表中。
- 接着,计算用户的对数损失,并返回结果。
- 最后,告知用户其表现相对于其他人如何。

你需要设置以下全局变量:

- scores:一个字典,将问题 UUID 映射到一个预测列表,例如 {question 1: [.5, .1, .9]}。
- entries:一个字典,将用户 UUID 映射到包含问题 UUID 和预测值的字典,例如 {person 1: {question 1: .5, question 2: .8}}。
- questions:一个包含问题和答案对的元组列表,例如 [("1+1 等于多少?", "2")]。
- answers:一个由整数构成的列表,用于记录模型是否回答正确,例如 [0, 1, 0 ...]。

🤖 Assistant

你可以首先使用 pip 安装 Flask 和其他必要的库,如果尚未安装这些库的话:

1
pip install flask numpy scikit-learn

现在,我们来使用 Flask 开发你的游戏:

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
from flask import Flask, request, session, redirect, url_for, render_template
from sklearn. Metrics import log_loss
from uuid import uuid 4
import numpy as np

App = Flask (__name__)
App. Config['SECRET_KEY'] = 'a very secret key' # replace with your secret key

# Global variables
scores = {}
entries = {}
questions = [("What is 1+1?", "2"), ("What is the capital of France?", "Paris")]
answers = [1, 0] # Suppose for these questions GPT-4 got the first one right and the second one wrong

@app.Route ('/home', methods=['GET'])
def home ():
session['id'] = str (uuid 4 ())
return render_template ('home. Html') # render a template that explains the game

@app.Route ('/question', methods=['GET'])
def question ():
qid = request.Args.Get ('id', default=0, type=int)
if 'id' in session:
question, expected_answer = questions[qid]
return render_template ('question. Html', question=question, expected_answer=expected_answer, id=qid)
else:
return redirect (url_for ('home'))

@app.Route ('/score', methods=['POST'])
def score ():
qid = request.Form.Get ('id', type=int)
guess = request.Form.Get ('guess', type=float)
user_id = session.Get ('id')

if user_id and qid is not None and guess is not None:
# Add to the list of all guesses for that question
scores.setdefault(qid, []).append(guess)

# Add to the person's guesses
entries.setdefault(user_id, {})[qid] = guess

# Compute the person's log loss
user_log_loss = log_loss([answers[qid]], [guess], labels=[0, 1])

# Compare with others
other_scores = scores[qid]
other_log_losses = log_loss([answers[qid]] \* len(other_scores), other_scores, labels=[0, 1])

comparison = 'better' if user_log_loss < other_log_losses else 'worse'

return render_template('score.html', user_log_loss=user_log_loss, comparison=comparison)
else:
return redirect(url_for('home'))

If __name__ == "__main__":
App.Run (debug=True)

这段代码作为初始代码,实现了三个端点:/home/question/score,并管理全局变量来记录用户预测和计算对数损失得分。

不过,要让代码正常运行,你还需要创建用于渲染的模板文件,包括 home.htmlquestion.htmlscore.html。这些模板文件应该包含你的 HTML 代码,并预留出动态内容的占位符。

建议你阅读 Flask 文档中关于会话和模板的部分,以便更好地理解它们的使用。

需要注意的是,使用全局变量来存储信息适用于用户较少且应用运行时间较短的小型应用或快速原型。但如果是在生产环境或有更多用户的应用中,你可能需要使用数据库或其他数据存储系统来管理这些信息。

如果你查看这段对话,你会发现各种类型的例子,从我用文字描述我想要的功能并要求模型提供完整实现的情况,到我请求具体更改的场景(如“能否将‘与平均分的比较’改为‘用 KDE 表示的百分位数’?”),再到我仅仅复制并粘贴错误信息,提出完全未指定的问题(如“绘图时:numpy. Linalg. LinAlgError: 奇异矩阵”),甚至还有一些我只是简单询问一次性问题的例子(如“如何用 JavaScript 添加一个内容从字符串加载的 iframe?”)。

总的来说,这能奏效是因为语言模型非常擅长解决那些已经被人们解决过的问题。事实上,这个测验的 99% 只是由一些基本的 HTML 和一个 python 网页服务器后端组成的,任何人都能轻松编写。这个测验之所以引人入胜,受到大家的喜爱,并不是因为背后的技术有多么先进,而是因为测验的内容本身有趣。因此,通过自动化处理所有重复性的工作,我可以很轻松地完成这个测验。

事实上,我非常有信心地说,如果没有语言模型的帮助,我可能根本不会去制作这个测验——因为我并不愿意花时间从头开始编写整个 Web 应用程序。要知道,我可是会编程的! 我相信,即使是现有的模型,也已经足够让绝大多数人通过简单的提问来解决他们以前从未能解决的复杂问题。

我还有一些类似的例子,我让模型为我编写了完整的应用程序,等它们上线时,我会明确说明这些应用程序是借助语言模型完成的。

作为新技术的导师

曾经我一直密切关注各种新框架的发展。但一个人的时间有限,由于工作原因,我主要专注于最新的研究进展,而不是 JavaScript 框架的最新动态。

这意味着,当我需要开展一个超出我研究领域的新项目时,通常有两个选择。首先,我可以使用已有的知识。虽然这些知识可能已经过时了十年或二十年,但对于小项目来说通常已经足够。或者,我可以尝试学习新的(通常更优越的)方法。

这时,语言模型就派上用场了。像 Docker、Flexbox 或 React 这样的框架或工具,虽然对我来说是新鲜的,但对其他许多人来说却非常熟悉。世界上可能有成千上万的人精通这些技术,而现有的语言模型也可以同样了解这些技术。

这意味着我不必去读那些假定读者有特定知识并想实现特定目标的静态教程。相反,我可以通过与语言模型互动,学习我完成任务所需的任何知识。
例如,今年早些时候,我在构建一个用于评估大语言模型的框架,并希望能够在受限环境中运行 LLM 生成的代码,以防止其意外删除我电脑上的文件或执行其他不必要的操作。Docker 是完成这一任务的理想工具,但我之前从未使用过它。

需要注意的是,_这个项目的目标并不是为了使用 Docker_。Docker 只是我实现目标所需的工具而已。我只需掌握 Docker 的 10% 核心知识,以确保我能够在最基本的操作下安全地使用它。

如果这是 90 年代,我可能需要买一本入门 Docker 的书籍,阅读前几章,然后翻阅找到我需要的部分,来了解如何实现我的需求。此后,情况有所好转;如果这是过去十年,我可能会在线搜索一些 Docker 教程,跟着步骤操作,然后查找出现的错误信息,看看是否有其他人遇到类似问题。

但如今,我只需请语言模型教我如何使用 Docker。 这就是我所做的。

ChatGPT 对话记录 - 教我如何使用Docker

教我如何使用 Docker

🧑‍🦰User

首先,启动一个新的 Docker 虚拟机。接着,在 Python 中运行一个当前位于 /tmp/a.sh 的 Bash 脚本,以在新的环境中进行配置。最后,在 Docker 环境中执行 ls /tmp/ 命令来验证设置是否成功。

🤖 Assistant

1. 启动新的 Docker 容器

我们将使用 Python 的 subprocess 模块来启动一个新的 Docker 容器。你可以根据需要选择一个合适的 Docker 镜像。以下是使用 ubuntu 镜像的示例。

1
2
3
4
5
6
7
8
import subprocess

# 第一步:启动新的 Docker 容器
container_name = "my_new_container"
image_name = "ubuntu" # 将此处替换为你要使用的镜像名称

# 以分离模式运行一个新的容器
subprocess.run(["docker", "run", "-d", "--name", container_name, image_name, "sleep", "infinity"])

2. 将 Bash 脚本复制到 Docker 容器中

接下来,我们将主机上的 Bash 脚本 /tmp/a.sh 复制到 Docker 容器的 /tmp/ 目录中。

1
2
# 第二步:将脚本复制到 Docker 容器中
subprocess.run(["docker", "cp", "/tmp/a.sh", f"{container_name}:/tmp/a.sh"])

3. 在 Docker 容器中执行 Bash 脚本

现在,我们将在 Docker 容器中执行已复制的脚本,以设置环境。

1
2
# 第三步:在 Docker 容器中执行 Bash 脚本
subprocess.run(["docker", "exec", container_name, "bash", "/tmp/a.sh"])

4. 通过在 Docker 容器中列出 /tmp/ 目录来测试

最后,让我们通过在 Docker 容器中列出 /tmp/ 目录的内容来验证一切是否已正确设置。

1
2
# 第四步:在 Docker 容器中运行 'ls /tmp/' 以测试
subprocess.run(["docker", "exec", container_name, "ls", "/tmp/"])

5. 清理(可选)

如果你在测试后希望删除 Docker 容器,可以使用以下命令:

1
2
3
# 可选步骤:停止并删除 Docker 容器
subprocess.run(["docker", "stop", container_name])
subprocess.run(["docker", "rm", container_name])

总结

  • 该 Python 脚本启动一个新的 Docker 容器,将脚本复制到其中,执行该脚本,并通过列出 /tmp/ 目录来验证设置。
  • 这个过程假设系统上已安装并运行 Docker。
  • Docker 镜像和路径应根据你的具体使用场景进行调整。

当我成功设置并运行 Docker 后,发现它在 Linux 上运行时存在一些权限问题。我希望能解决这些权限问题,于是请模型帮助我处理这些问题。

ChatGPT 对话记录 - 如何解决 Linux 上运行时权限问题

如何解决 Linux 上运行时权限问题

🧑‍🦰User

1
2
3
4
5
我正在编写一个程序,该程序通过为每个测试启动一个 Docker 环境来运行一些测试用例,在环境中运行测试,然后删除该环境。

在我的 Mac 上,我可以直接运行 `do_test.py`,因为 Docker 不需要 root 权限。

但是在 Ubuntu 上,现在我必须使用 `sudo` 运行 `do_test.py`,因为创建新的 Docker 环境需要 `sudo`。我怎样才能设置不需要使用 `sudo` 来运行?

🤖 Assistant

如果你想在 Ubuntu 上运行 Docker 命令时不使用 sudo,你需要将你的用户添加到 Docker 组中。Docker 组在 Docker 安装时自动创建。将用户添加到 Docker 组后,组内的用户就可以无须超级用户权限执行 Docker 命令。以下是实现这一设置的步骤:

  1. 将你的用户添加到 Docker 组

    打开终端,输入以下命令,将 your-username 替换为你当前的用户名:

    1
    2

    sudo usermod -aG docker your-username

    这条命令会将你的用户添加到 Docker 组。-aG 参数确保用户会被追加到指定组中。

  2. 应用组设置

    为了使组设置生效,你需要注销并重新登录。或者,你也可以通过运行以下命令立即应用新的组设置:

    1
    newgrp docker

    这条命令会在当前会话中激活新的组设置,从而让你在不使用 sudo 的情况下执行 Docker 命令。

  3. 验证 Docker 命令是否可在无 sudo 情况下运行

    重新登录或执行 newgrp docker 之后,你可以通过运行以下命令验证 Docker 命令是否可在不使用 sudo 的情况下执行:

    Bash

    1
    2

    docker run hello-world

    这条命令会下载一个测试镜像并在容器中运行。如果运行成功,说明你的设置已正确生效。

重要注意事项

  • 安全性考虑:请注意,将用户添加到 Docker 组会授予该用户在 Docker 操作中的权限,相当于 root 用户的权限。这种设置在多用户系统中可能会带来安全隐患。请确保你理解这一更改的安全影响。

  • Docker 服务:确保你的系统上 Docker 服务正在运行。你可以使用 sudo systemctl start docker 启动 Docker 服务,并使用 sudo systemctl enable docker 设置其在启动时自动运行。

通过遵循这些步骤,你应该可以在 Ubuntu 上无需使用 sudo 就能运行 Docker 命令,从而简化在 Docker 环境中运行测试用例的过程。

通过这个过程,我了解到了 Podman,于是请模型将我所有特定于 Docker 的代码转换为对应的 Podman 版本。

ChatGPT 对话记录 - 所有特定于 Docker 的代码转换为对应的 Podman 版本

对话记录

🧑‍🦰User

1
From python spin up a podman container

🤖 Assistant

To spin up a Podman container using Python, you would typically use the subprocess module to execute Podman commands from within a Python script. This approach allows you to programmatically manage containers, including starting them, from your Python code.

Here’s a basic example to demonstrate how you can use Python to start a Podman container:

  1. Ensure Podman is Installed: First, make sure Podman is installed on your system. You can check this by running podman --version from your terminal.

  2. Choose Your Container Image: Decide which container image you want to use. For this example, let’s use the alpine image, which is a minimal Docker image based on Alpine Linux. If you want to use a different image, replace alpine with the name of the image you wish to use.

  3. Write the Python Script: Use the subprocess.run function to call Podman commands.

Here’s a simple Python script that starts an Alpine container and runs the echo "Hello from Podman!" command inside it:

python

复制代码

import subprocess def start_podman_container(): # Define the Podman command to run a container command = ["podman", "run", "alpine", "echo", "Hello from Podman!"] # Execute the command result = subprocess.run(command, capture_output=True, text=True) # Check if the command was successful if result.returncode == 0: print("Container ran successfully.") print("Output:", result.stdout) else: print("Error running container.") print("Error:", result.stderr) # Run the function start_podman_container()

This script runs a container using the alpine image and executes a simple echo command inside it. The output of the command is captured and displayed by the Python script.

Important Notes:

  • Permissions: Running Podman might require superuser privileges, depending on your setup. However, one of the advantages of Podman is its ability to run containers as a non-root user. Ensure your environment is configured appropriately.
  • Python Environment: This script uses Python’s built-in subprocess module, which is included in the standard library and does not require any additional packages.
  • Podman Installation: This example assumes that Podman is already installed and configured on your system. If not, you will need to install Podman first.

Remember, this script is a basic example. Podman’s capabilities allow for much more complex container management, including building images, managing container networks, volumes, and more. For advanced scenarios, refer to the Podman documentation and the Python subprocess module documentation to tailor the script to your needs.

And then when I wanted to figure out how to pass a host’s GPUs through in a docker container, I ask for that too.

ChatGPT 对话记录 - 所有特定于 Docker 的代码转换为对应的 Podman 版本

对话记录

🧑‍🦰User

1
How do I pass nvidia gpus through to a docker instance?

🤖 Assistant

你可以首先使用 pip 安装 Flask 和其他必要的库,如果尚未安装这些库的话:

1
pip install flask numpy scikit-learn

现在,我们来使用 Flask 开发你的游戏:

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
from flask import Flask, request, session, redirect, url_for, render_template
from sklearn. Metrics import log_loss
from uuid import uuid 4
import numpy as np

App = Flask (__name__)
App. Config['SECRET_KEY'] = 'a very secret key' # replace with your secret key

# Global variables
scores = {}
entries = {}
questions = [("What is 1+1?", "2"), ("What is the capital of France?", "Paris")]
answers = [1, 0] # Suppose for these questions GPT-4 got the first one right and the second one wrong

@app.Route ('/home', methods=['GET'])
def home ():
session['id'] = str (uuid 4 ())
return render_template ('home. Html') # render a template that explains the game

@app.Route ('/question', methods=['GET'])
def question ():
qid = request.Args.Get ('id', default=0, type=int)
if 'id' in session:
question, expected_answer = questions[qid]
return render_template ('question. Html', question=question, expected_answer=expected_answer, id=qid)
else:
return redirect (url_for ('home'))

@app.Route ('/score', methods=['POST'])
def score ():
qid = request.Form.Get ('id', type=int)
guess = request.Form.Get ('guess', type=float)
user_id = session.Get ('id')

if user_id and qid is not None and guess is not None:
# Add to the list of all guesses for that question
scores.setdefault(qid, []).append(guess)

# Add to the person's guesses
entries.setdefault(user_id, {})[qid] = guess

# Compute the person's log loss
user_log_loss = log_loss([answers[qid]], [guess], labels=[0, 1])

# Compare with others
other_scores = scores[qid]
other_log_losses = log_loss([answers[qid]] \* len(other_scores), other_scores, labels=[0, 1])

comparison = 'better' if user_log_loss < other_log_losses else 'worse'

return render_template('score.html', user_log_loss=user_log_loss, comparison=comparison)
else:
return redirect(url_for('home'))

If __name__ == "__main__":
App.Run (debug=True)

这段代码作为初始代码,实现了三个端点:/home/question/score,并管理全局变量来记录用户预测和计算对数损失得分。

不过,要让代码正常运行,你还需要创建用于渲染的模板文件,包括 home.htmlquestion.htmlscore.html。这些模板文件应该包含你的 HTML 代码,并预留出动态内容的占位符。

建议你阅读 Flask 文档中关于会话和模板的部分,以便更好地理解它们的使用。

需要注意的是,使用全局变量来存储信息适用于用户较少且应用运行时间较短的小型应用或快速原型。但如果是在生产环境或有更多用户的应用中,你可能需要使用数据库或其他数据存储系统来管理这些信息。

页面受限,原始英文版本查看,中文版本查看

如何开始新项目

年轻时,我的第一门编程语言是 Java。(这或许是个错误。)

甚至可以说整个 Java 语言都是个错误。但这另当别论。

我非常喜欢编程,但有一件事让我特别头疼,那就是面对新项目时那片空白的屏幕。尤其是在使用 Java 的时候!即便是编译一个简单的 hello world 程序,我也会困惑:“public static void main string args”究竟是什么意思?括号该放在哪里?哪些字母要大写?为什么这里用花括号而那里用方括号?

于是,我做了任何孩子都会做的事情——我请我父亲帮我完成。

二十年后的今天,我依然不喜欢用那些我不太熟悉的框架来启动新项目。光是处理模板代码就需要花费大量时间,而且我对自己在做什么也没有头绪。

举个例子,我最近想尝试写一些 CUDA 代码,来基准测试在 GPU 上运行的一种朴素贪婪搜索算法的性能,并与某个高效优化的 CPU 实现进行比较。

但问题是,我不会写 CUDA 程序。我会写 C 语言,也理解 GPU 的工作原理、内核的作用以及内存布局等方面的内容。但让我实际编写发送任务到 GPU 的代码?完全不知道从何入手。所以我就请模型为我写了一个 CUDA 程序的初步版本。它完美吗?当然不是!但这已经是一个不错的开始。而这正是我所需要的。

ChatGPT 对话记录 - 请模型为我写了一个 CUDA 程序的初步版本

对话记录

🧑‍🦰User

1
2
3
4
5
6
7
8
9
10
11
12
13
编写一个简单的 CUDA 程序,该程序接受一个 32 位的命令行参数,并将其拆分为 8 个 4 位的独立命令。接着,对这些命令逐一调用 proc() 函数。

proc 函数将接收一组 4 位整数命令,并计算表达式
(a [op1] b) op2 (c [op3] d)

其中,a、op1、b、op2、c、op3、d 都是 4 位整数,操作符 op 由以下映射定义:
0: 加法(+)
1: 减法(-)
2: 乘法(*)
3: 除法(/)
4: 异或(^)

接下来,枚举所有可能的 32 位程序,并打印出计算结果最接近 1729 的那个程序。

🤖 Assistant

下面是一段 CUDA 程序代码,它将一个 32 位的命令行参数拆分为 8 个 4 位的独立命令,并按照前面描述的方式进行处理。该程序会枚举所有可能的 32 位程序,并输出结果最接近 1729 的那个程序。

要运行此程序,你需要一台支持 CUDA 的 GPU 并安装好 CUDA 工具包。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <iostream>
#include <cuda_runtime.h>
#include <limits>

__device__ int proc(int command) {
int a = (command >> 28) &amp; 0xF;
int op1 = (command >> 24) &amp; 0xF;
int b = (command >> 20) &amp; 0xF;
int op2 = (command >> 16) &amp; 0xF;
int c = (command >> 12) &amp; 0xF;
int op3 = (command >> 8) &amp; 0xF;
int d = (command >> 4) &amp; 0xF;

int intermediate1, intermediate2, result;

switch (op1) {
case 0: intermediate1 = a + b; break;
case 1: intermediate1 = a - b; break;
case 2: intermediate1 = a * b; break;
case 3: intermediate1 = (b != 0) ? a / b : 0; break;
case 4: intermediate1 = a ^ b; break;
default: intermediate1 = 0; break;
}

switch (op3) {
case 0: intermediate2 = c + d; break;
case 1: intermediate2 = c - d; break;
case 2: intermediate2 = c * d; break;
case 3: intermediate2 = (d != 0) ? c / d : 0; break;
case 4: intermediate2 = c ^ d; break;
default: intermediate2 = 0; break;
}

switch (op2) {
case 0: result = intermediate1 + intermediate2; break;
case 1: result = intermediate1 - intermediate2; break;
case 2: result = intermediate1 * intermediate2; break;
case 3: result = (intermediate2 != 0) ? intermediate1 / intermediate2 : 0; break;
case 4: result = intermediate1 ^ intermediate2; break;
default: result = 0; break;
}

return result;
}

__global__ void findClosest(int* closestCommand, int* closestValue, int target) {
int idx = blockDim.x * blockIdx.x + threadIdx.x;
int command = idx;
int result = proc(command);

int diff = abs(result - target);

atomicMin(closestValue, diff);

if (diff == *closestValue) {
*closestCommand = command;
}
}

int main(int argc, char** argv) {
int target = 1729;
int closestCommand = 0;
int closestValue = std::numeric_limits<int>::max();

int* d_closestCommand;
int* d_closestValue;

cudaMalloc((void**)&amp;d_closestCommand, sizeof(int));
cudaMalloc((void**)&amp;d_closestValue, sizeof(int));

cudaMemcpy(d_closestCommand, &amp;closestCommand, sizeof(int), cudaMemcpyHostToDevice);
cudaMemcpy(d_closestValue, &amp;closestValue, sizeof(int), cudaMemcpyHostToDevice);

int threadsPerBlock = 256;
int numBlocks = (1 << 24) / threadsPerBlock;

findClosest<<<numBlocks, threadsPerBlock>>>(d_closestCommand, d_closestValue, target);

cudaMemcpy(&amp;closestCommand, d_closestCommand, sizeof(int), cudaMemcpyDeviceToHost);
cudaMemcpy(&amp;closestValue, d_closestValue, sizeof(int), cudaMemcpyDeviceToHost);

std::cout << "Closest command: " << closestCommand << std::endl;
std::cout << "Closest value: " << (1729 - closestValue) << std::endl;

cudaFree(d_closestCommand);
cudaFree(d_closestValue);

return 0;
}

本程序将执行以下步骤:

  1. 定义一个名为 proc 的函数,用来计算给定命令序列的结果。
  2. 启动 CUDA 内核,寻找一个能返回最接近 1729 的 32 位数值的程序。
  3. 输出最接近的命令及其对应的值。

请确保您的 CUDA 开发环境已经正确配置,这样才能顺利编译和运行程序。您可以使用 nvcc 进行编译:

1
nvcc -o cuda_program cuda_program.cu

And then run it:

1
./cuda_program

你可能会发现,这里的代码有时会出错!但这对我来说完全可以接受。我并不追求一个完美的解决方案,我需要的只是一个起点,然后我可以从那里开始。如果未来的模型更强大,那当然非常了不起。但即便是现在的模型,已经对我有很大帮助了。

或者,换个话题,谈谈我在家里进行的另一个完全不同的个人项目。我正在使用 Raspberry Pi Pico W。这是我第一次使用它。我希望它能帮我实现一些功能,尤其是在网络方面。同样,我相信可以在网上找到一些好的教程,教我如何实现这些功能。但是你有没有注意到,最近的网络搜索结果通常前 5 个都是一些低质量的内容农场,里面充斥着从 2008 年更新过来的、为了 SEO 优化而更新却依然存在问题的代码。

因此,我选择让语言模型直接教我如何完成这些任务。我之前使用过微控制器,了解它们的大致工作原理,但从未使用过 Pico W。我需要的只是一个起点,帮我解决依赖项的问题,然后我可以自己摸索其余的部分。

对于新的微控制器,我总是先写一个 LED 闪烁程序作为“hello world”。这样可以测试我是否能成功编译并上传代码,确认所有引脚设置是否正确,并基本了解我在做什么。所以,让我们请模型生成一个 LED 闪烁程序。(这在互联网上找得到吗?几乎可以肯定是能找到的。但还得费劲去搜。)

ChatGPT 对话记录 - 请模型生成一个 LED 闪烁程序

对话记录

🧑‍🦰User

1
编写一个用于 Raspberry Pi Pico W Zero 的 blink 示例代码,让引脚 26 上的 LED 闪烁,使用 MicroPython。

🤖 Assistant

以下是一个简单的 Micropython 示例代码,能够让 Raspberry Pi Pico W Zero 的引脚 26 上的 LED 闪烁。该代码假设你已经在 Pico W 上刷入了 MicroPython。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from machine import Pin
import utime

# Setup pin 26 as output
led = Pin (26, Pin. OUT)

# Function to toggle the LED
def blink_led ():
while True:
led.Value (1) # Turn LED on
utime.Sleep (1) # Sleep for 1 second
led.Value (0) # Turn LED off
utime.Sleep (1) # Sleep for 1 second

# Run the blink function
blink_led ()

该脚本将引脚 26 设置为输出引脚,然后连续不断地在开关状态之间每秒切换一次 LED。当你想要停止 LED 闪烁时,请中断脚本或重置你的开发板,因为它运行在一个无限循环中。

一旦我将这段代码运行起来,接下来我就知道如何继续了。我知道 python 是如何工作的(或许你会觉得惊讶!)。因此,我可以直接从这里开始编辑代码,因为已经处理好了 Micro python 的特殊部分。

当我遇到需要特殊处理的问题时,我可以直接请模型帮助我解决。比如在这里,我继续请模型帮我写了一个连接 wifi 的脚本

ChatGPT 对话记录 - 一个连接 wifi 的脚本

对话记录

🧑‍🦰User

1
Write me a simple script that connects a pico w to the wifi and sends an http request with python curl

🤖 Assistant

你可以首先使用 pip 安装 Flask 和其他必要的库,如果尚未安装这些库的话:

1
pip install flask numpy scikit-learn

现在,我们来使用 Flask 开发你的游戏:

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
from flask import Flask, request, session, redirect, url_for, render_template
from sklearn. Metrics import log_loss
from uuid import uuid 4
import numpy as np

App = Flask (__name__)
App. Config['SECRET_KEY'] = 'a very secret key' # replace with your secret key

# Global variables
scores = {}
entries = {}
questions = [("What is 1+1?", "2"), ("What is the capital of France?", "Paris")]
answers = [1, 0] # Suppose for these questions GPT-4 got the first one right and the second one wrong

@app.Route ('/home', methods=['GET'])
def home ():
session['id'] = str (uuid 4 ())
return render_template ('home. Html') # render a template that explains the game

@app.Route ('/question', methods=['GET'])
def question ():
qid = request.Args.Get ('id', default=0, type=int)
if 'id' in session:
question, expected_answer = questions[qid]
return render_template ('question. Html', question=question, expected_answer=expected_answer, id=qid)
else:
return redirect (url_for ('home'))

@app.Route ('/score', methods=['POST'])
def score ():
qid = request.Form.Get ('id', type=int)
guess = request.Form.Get ('guess', type=float)
user_id = session.Get ('id')

if user_id and qid is not None and guess is not None:
# Add to the list of all guesses for that question
scores.setdefault(qid, []).append(guess)

# Add to the person's guesses
entries.setdefault(user_id, {})[qid] = guess

# Compute the person's log loss
user_log_loss = log_loss([answers[qid]], [guess], labels=[0, 1])

# Compare with others
other_scores = scores[qid]
other_log_losses = log_loss([answers[qid]] \* len(other_scores), other_scores, labels=[0, 1])

comparison = 'better' if user_log_loss < other_log_losses else 'worse'

return render_template('score.html', user_log_loss=user_log_loss, comparison=comparison)
else:
return redirect(url_for('home'))

If __name__ == "__main__":
App.Run (debug=True)

这段代码作为初始代码,实现了三个端点:/home/question/score,并管理全局变量来记录用户预测和计算对数损失得分。

不过,要让代码正常运行,你还需要创建用于渲染的模板文件,包括 home.htmlquestion.htmlscore.html。这些模板文件应该包含你的 HTML 代码,并预留出动态内容的占位符。

建议你阅读 Flask 文档中关于会话和模板的部分,以便更好地理解它们的使用。

需要注意的是,使用全局变量来存储信息适用于用户较少且应用运行时间较短的小型应用或快速原型。但如果是在生产环境或有更多用户的应用中,你可能需要使用数据库或其他数据存储系统来管理这些信息。

页面受限,原始英文版本查看,中文版本查看

然后,当我再次遇到困难,这次是需要连接到 MQTT 服务器时,我再次请模型帮助我解决

ChatGPT 对话记录 - 连接到 MQTT 服务器

对话记录

🧑‍🦰User

1
为 Raspberry Pi Pico W Zero 配置 HTTP 请求的端口

🤖 Assistant

要为 Raspberry Pi Pico W Zero 配置一个用于接收 HTTP 请求的端口,您需要按照以下步骤进行:

  1. 连接 Pico W Zero 到您的网络:

    • 确保您的 Pico W Zero 具备 WiFi 功能并已连接至本地网络。
  2. 设置 Pico W Zero 的 Web 服务器:

    • 您需要编写代码,让 Pico W Zero 能够处理 HTTP 请求。Micropython 是非常适合 Pico W Zero 这类微控制器的编程环境。
    • 如果还未安装 MicroPython,请先将其安装到您的 Pico W Zero 上。
    • 编写一个 Micropython 脚本以启动基础的 Web 服务器。以下是一个示例:
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
import network
import socket

ssid = 'YOUR_SSID'
password = 'YOUR_PASSWORD'

station = network.WLAN(network.STA_IF)
station.active(True)
station.connect(ssid, password)

while station.isconnected() == False:
pass

print('Connection successful')
print(station.ifconfig())

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)

while True:
conn, addr = s.accept()
print('Got a connection from %s' % str(addr))
request = conn.recv(1024)
print('Content = %s' % str(request))
response = 'HTTP/1.1 200 OK\n\nHello World!'
conn.send(response)
conn.close()
  1. 在路由器上设置端口转发:

    • 如果您希望从外部网络访问 Pico W Zero,需要将外部端口转发到 Pico W Zero 的内部 IP 地址及端口。
    • 登录到路由器的管理界面,找到端口转发设置。
    • 创建一条规则,将外部端口(如 8080)转发到 Pico W Zero 的内部 IP 地址的 80 端口。
    • 确保您的 ISP 不会阻止您所使用的端口。
  2. 测试设置:

    • 在同一网络中的设备上访问 http://<Pico W IP 地址>/ 来测试服务器。
    • 从外部网络访问时,使用您的公共 IP 地址并添加转发的端口,例如 http://<公共 IP 地址>:8080/
  3. 安全性建议:

    • 在将设备直接暴露在互联网上时要格外小心。确保只开放必要的端口,并考虑使用防火墙或 VPN 增强安全性。

如果您需要更详细的指导,请告知我!

我现在经常这样做。甚至这个部分开头的例子也不是虚构的——这是我在请教如何使用 Flexbox,因为我上次学习 HTML 布局方式时还在用 div 代替表格。

ChatGPT 对话记录 - 请教如何使用 Flexbox

对话记录

🧑‍🦰User

1
使用 Flexbox 实现一个页面布局,其中左侧有一个 ID 为 "a" 的元素,右侧有两个 ID 分别为 "b" 和 "c" 的元素

🤖 Assistant

以下是如何使用 Flexbox 实现这一布局的示例:
HTML:

1
2
3
4
5
6
7
<div class="container">
<div id="a">Element A</div>
<div class="right">
<div id="b">Element B</div>
<div id="c">Element C</div>
</div>
</div>

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.container {
Display: flex;
Justify-content: space-between;
Align-items: center;
}

#a {
Flex: 0 0 auto;
Margin-right: 20 px;
}

.right {
Display: flex;
Justify-content: flex-end;
Align-items: center;
}

#b , #c {
Margin-left: 20 px;
}

以下是 CSS 代码的主要功能解释:

  • .container 元素设置为 display: flex,使其子元素能够使用 Flexbox 布局。
  • justify-content: space-between 用于在左侧和右侧元素之间留出空间。
  • align-items: center 用于将元素垂直居中。
  • #aflex: 0 0 auto 设置了它的宽度只占用所需的空间,并通过 margin-right 属性在 #a 和右侧元素之间创建间距。
  • 如果希望 #b#c 垂直堆叠,可以将它们放在一个 flex 容器内,并使用 flex-direction: column 实现堆叠。

🧑‍🦰User

1
Can you do this and keep the three divs for a, b, and c all siblings? That is, without the "right" div?

🤖 Assistant

Sure, you can achieve the same layout without using the “right” container. Here’s an example:

HTML:

1
2
3
4
5
<div class="container">
<div id="a">Element A</div>
<div id="b">Element B</div>
<div id="c">Element C</div>
</div>

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.container {
Display: flex;
Justify-content: space-between;
Align-items: center;
}

#a {
Flex: 0 0 auto;
Margin-right: 20 px;
}

#b , #c {
Flex: 0 0 auto;
}

Here’s a breakdown of what’s happening in the CSS:

  • The . Container element is set to display: flex so that its child elements can be positioned using flexbox.
  • The justify-content: space-between property is used to create space between the left and right elements.
  • The align-items: center property is used to vertically center the elements.
  • The flex: 0 0 auto property on #a, #b, and #c sets their flex basis to auto, which means they will take up only the necessary space.
  • The margin-right: 20 px property on #a creates a gap between #a and #b.
  • Since #b and #c are both siblings, they will automatically be positioned next to each other without the need for an additional container.

🧑‍🦰User

1
I want b and c to stack vertically please

🤖 Assistant

Sure, to stack #b and #c vertically while keeping #a on the left, you can nest another flex container inside the main . Container. Here’s an example:

HTML:

1
2
3
4
5
6
7
<div class="container">
<div id="a">Element A</div>
<div class="right">
<div id="b">Element B</div>
<div id="c">Element C</div>
</div>
</div>

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.container {
Display: flex;
Justify-content: space-between;
Align-items: center;
}

#a {
Flex: 0 0 auto;
Margin-right: 20 px;
}

.right {
Display: flex;
Flex-direction: column;
}

#b , #c {
Margin-top: 20 px;
}

Here’s a breakdown of what’s happening in the CSS:

  • The . Container element is set to display: flex so that its child elements can be positioned using flexbox.
  • The justify-content: space-between property is used to create space between the left and right elements.
  • The align-items: center property is used to vertically center the elements.
  • The flex: 0 0 auto property on #a sets its flex basis to auto, which means it will take up only the necessary space, and the margin-right property creates a gap between #a and the right elements.
  • The . Right element is set to display: flex and flex-direction: column to create a vertical stack of #b and #c.
  • The margin-top: 20 px property on #b and #c creates a gap between the stacked elements.

🧑‍🦰User

1
I want b and c to stack vertically without the other div.

🤖 Assistant

Sure, you can achieve this by nesting another flex container inside the . Container element and then using the flex-direction: column property on the nested container to stack #b and #c vertically. Here’s an example:

HTML:

1
2
3
4
5
6
7
<div class="container">
<div id="a">Element A</div>
<div class="stacked">
<div id="b">Element B</div>
<div id="c">Element C</div>
</div>
</div>

CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.container {
Display: flex;
Justify-content: space-between;
Align-items: center;
}

#a {
Flex: 0 0 auto;
Margin-right: 20 px;
}

.stacked {
Display: flex;
Flex-direction: column;
}

#b , #c {
Flex: 0 0 auto;
Margin-top: 20 px;
}

Here’s a breakdown of what’s happening in the CSS:

  • The . Container element is set to display: flex so that its child elements can be positioned using flexbox.
  • The justify-content: space-between property is used to create space between the left and right elements.
  • The align-items: center property is used to vertically center the elements.
  • The flex: 0 0 auto property on #a, #b, and #c sets their flex basis to auto, which means they will take up only the necessary space.
  • The margin-right: 20 px property on #a creates a gap between #a and the stacked elements.
  • The . Stacked element is set to display: flex and flex-direction: column to create a vertical stack of #b and #c.
  • The margin-top: 20 px property on #b and #c creates a gap between the stacked elements.

简化代码

作为一名安全研究员,我经常面对这样一种情况:需要分析一个包含几千行代码的研究项目代码仓库,以便了解其工作原理,然后找出可攻击的点。听起来似乎不难,如果所有人都写干净的代码的话,确实如此,但事实并非如此。研究人员通常不会因为代码的整洁而受到激励,因此很多人只会发布那些能运行的代码,无论其质量如何。(我也不例外。)

虽然我不能分享任何与研究相关的例子,但可以分享一个我个人项目中的例子。有人说我对康威的生命游戏有着不健康的偏爱。最近,我在尝试通过 python 快速评估一些生命游戏的模式。有一个叫 golly 的 C++ 工具非常适合这个任务,但我并不想把我的 python 代码重写成 C++。

golly 有一个可以满足我需求的命令行工具——我只需要找到正确调用它的方法。第一步是从支持大约 50 个不同命令行选项的 C++ 代码中,只让它执行我需要的那个功能。所以我将 500 行的 C++ 代码输入到 LLM 中,请求生成一个更简洁的文件来完成同样的任务

结果非常完美。随后,我请求模型生成了一个围绕这段 C++ 代码的 python 包装器,也同样顺利完成。

ChatGPT 对话记录 - 请求生成一个更简洁的文件来完成同样的任务

对话记录

🧑‍🦰User

Given this cpp file

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
// See docs/License.html for the copyright notice.

#include "qlifealgo.h"
#include "hlifealgo.h"
#include "generationsalgo.h"
#include "ltlalgo.h"
#include "jvnalgo.h"
#include "superalgo.h"
#include "ruleloaderalgo.h"
#include "readpattern.h"
#include "util.h"
#include "viewport.h"
#include "liferender.h"
#include "writepattern.h"
#include <stdlib.h>
#include <iostream>
#include <cstdio>
#include <string.h>
#include <cstdlib>

using namespace std ;

double start ;
int maxtime = 0 ;
double timestamp() {
double now = gollySecondCount() ;
double r = now - start ;
if (start == 0)
start = now ;
else if (maxtime &amp;&amp; r > maxtime)
exit(0) ;
return r ;
}

viewport viewport(1000, 1000) ;
lifealgo *imp = 0 ;

/*
* This is a "renderer" that is just stubs, for performance testing.
*/
class nullrender : public liferender {
public:
nullrender() {}
virtual ~nullrender() {}
virtual void pixblit(int, int, int, int, unsigned char*, int) {}
virtual void getcolors(unsigned char** r, unsigned char** g, unsigned char** b,
unsigned char* dead_alpha, unsigned char* live_alpha) {
static unsigned char dummy[256];
*r = *g = *b = dummy;
*dead_alpha = *live_alpha = 255;
}
} ;
nullrender renderer ;

// the RuleLoader algo looks for .rule files in the user_rules directory
// then in the supplied_rules directory
char* user_rules = (char *)""; // can be changed by -s or --search
char* supplied_rules = (char *)"Rules/";

int benchmark ; // show timing?
/*
* This lifeerrors is used to check rendering during a progress dialog.
*/
class progerrors : public lifeerrors {
public:
progerrors() {}
virtual void fatal(const char *s) { cout << "Fatal error: " << s << endl ; exit(10) ; }
virtual void warning(const char *s) { cout << "Warning: " << s << endl ; }
virtual void status(const char *s) {
if (benchmark)
cout << timestamp() << " " << s << endl ;
else {
timestamp() ;
cout << s << endl ;
}
}
virtual void beginprogress(const char *s) { abortprogress(0, s) ; }
virtual bool abortprogress(double, const char *) ;
virtual void endprogress() { abortprogress(1, "") ; }
virtual const char* getuserrules() { return user_rules ; }
virtual const char* getrulesdir() { return supplied_rules ; }
} ;
progerrors progerrors_instance ;

bool progerrors::abortprogress(double, const char *) {
imp->draw(viewport, renderer) ;
return 0 ;
}

/*
* This is our standard lifeerrors.
*/
class stderrors : public lifeerrors {
public:
stderrors() {}
virtual void fatal(const char *s) { cout << "Fatal error: " << s << endl ; exit(10) ; }
virtual void warning(const char *s) { cout << "Warning: " << s << endl ; }
virtual void status(const char *s) {
if (benchmark)
cout << timestamp() << " " << s << endl ;
else {
timestamp() ;
cout << s << endl ;
}
}
virtual void beginprogress(const char *) {}
virtual bool abortprogress(double, const char *) { return 0 ; }
virtual void endprogress() {}
virtual const char* getuserrules() { return user_rules ; }
virtual const char* getrulesdir() { return supplied_rules ; }
} ;
stderrors stderrors_instance ;

char *filename ;
struct options {
const char *shortopt ;
const char *longopt ;
const char *desc ;
char opttype ;
void *data ;
} ;
bigint maxgen = -1, inc = 0 ;
int maxmem = 256 ;
int hyperxxx ; // renamed hyper to avoid conflict with windows.h
int render, autofit, quiet, popcount, progress ;
int hashlife ;
char *algoName = 0 ;
int verbose ;
int timeline ;
int stepthresh, stepfactor ;
char *liferule = 0 ;
char *outfilename = 0 ;
char *renderscale = (char *)"1" ;
char *testscript = 0 ;
int outputgzip, outputismc ;
int numberoffset ; // where to insert file name numbers
options options[] = {
{ "-m", "--generation", "How far to run", 'I', &amp;maxgen },
{ "-i", "--stepsize", "Step size", 'I', &amp;inc },
{ "-M", "--maxmemory", "Max memory to use in megabytes", 'i', &amp;maxmem },
{ "-T", "--maxtime", "Max duration", 'i', &amp;maxtime },
{ "-b", "--benchmark", "Show timestamps", 'b', &amp;benchmark },
{ "-2", "--exponential", "Use exponentially increasing steps", 'b', &amp;hyperxxx },
{ "-q", "--quiet", "Don't show population; twice, don't show anything", 'b', &amp;quiet },
{ "-r", "--rule", "Life rule to use", 's', &amp;liferule },
{ "-s", "--search", "Search directory for .rule files", 's', &amp;user_rules },
{ "-h", "--hashlife", "Use Hashlife algorithm", 'b', &amp;hashlife },
{ "-a", "--algorithm", "Select algorithm by name", 's', &amp;algoName },
{ "-o", "--output", "Output file (*.rle, *.mc, *.rle.gz, *.mc.gz)", 's',
&amp;outfilename },
{ "-v", "--verbose", "Verbose", 'b', &amp;verbose },
{ "-t", "--timeline", "Use timeline", 'b', &amp;timeline },
{ "", "--render", "Render (benchmarking)", 'b', &amp;render },
{ "", "--progress", "Render during progress dialog (debugging)", 'b', &amp;progress },
{ "", "--popcount", "Popcount (benchmarking)", 'b', &amp;popcount },
{ "", "--scale", "Rendering scale", 's', &amp;renderscale },
//{ "", "--stepthreshold", "Stepsize >= gencount/this (default 1)",
// 'i', &amp;stepthresh },
//{ "", "--stepfactor", "How much to scale step by (default 2)",
// 'i', &amp;stepfactor },
{ "", "--autofit", "Autofit before each render", 'b', &amp;autofit },
{ "", "--exec", "Run testing script", 's', &amp;testscript },
{ 0, 0, 0, 0, 0 }
} ;

int endswith(const char *s, const char *suff) {
int off = (int)(strlen(s) - strlen(suff)) ;
if (off <= 0)
return 0 ;
s += off ;
while (*s)
if (tolower(*s++) != tolower(*suff++))
return 0 ;
numberoffset = off ;
return 1 ;
}

void usage(const char *s) {
fprintf(stderr, "Usage: bgolly [options] patternfile\n") ;
for (int i=0; options[i].shortopt; i++)
fprintf(stderr, "%3s %-15s %s\n", options[i].shortopt, options[i].longopt,
options[i].desc) ;
if (s)
lifefatal(s) ;
exit(0) ;
}

#define STRINGIFY(ARG) STR2(ARG)
#define STR2(ARG) #ARG
#define MAXRLE 1000000000
void writepat(int fc) {
char *thisfilename = outfilename ;
char tmpfilename[256] ;
if (fc >= 0) {
strcpy(tmpfilename, outfilename) ;
char *p = tmpfilename + numberoffset ;
*p++ = '-' ;
sprintf(p, "%d", fc) ;
p += strlen(p) ;
strcpy(p, outfilename + numberoffset) ;
thisfilename = tmpfilename ;
}
cerr << "(->" << thisfilename << flush ;
bigint t, l, b, r ;
imp->findedges(&amp;t, &amp;l, &amp;b, &amp;r) ;
if (!outputismc &amp;&amp; (t < -MAXRLE || l < -MAXRLE || b > MAXRLE || r > MAXRLE))
lifefatal("Pattern too large to write in RLE format") ;
const char *err = writepattern(thisfilename, *imp,
outputismc ? MC_format : RLE_format,
outputgzip ? gzip_compression : no_compression,
t.toint(), l.toint(), b.toint(), r.toint()) ;
if (err != 0)
lifewarning(err) ;
cerr << ")" << flush ;
}

const int MAXCMDLENGTH = 2048 ;
struct cmdbase {
cmdbase(const char *cmdarg, const char *argsarg) {
verb = cmdarg ;
args = argsarg ;
next = list ;
list = this ;
}
const char *verb ;
const char *args ;
int iargs[4] ;
char *sarg ;
bigint barg ;
virtual void doit() {}
// for convenience, we put the generic loop here that takes a
// 4x bounding box and runs getnext on all y values until
// they are done. Input is assumed to be a bounding box in the
// form minx miny maxx maxy
void runnextloop() {
int minx = iargs[0] ;
int miny = iargs[1] ;
int maxx = iargs[2] ;
int maxy = iargs[3] ;
int v ;
for (int y=miny; y<=maxy; y++) {
for (int x=minx; x<=maxx; x++) {
int dx = imp->nextcell(x, y, v) ;
if (dx < 0)
break ;
if (x > 0 &amp;&amp; (x + dx) < 0)
break ;
x += dx ;
if (x > maxx)
break ;
nextloopinner(x, y) ;
}
}
}
virtual void nextloopinner(int, int) {}
int parseargs(const char *cmdargs) {
int iargn = 0 ;
char sbuf[MAXCMDLENGTH+2] ;
for (const char *rargs = args; *rargs; rargs++) {
while (*cmdargs &amp;&amp; *cmdargs <= ' ')
cmdargs++ ;
if (*cmdargs == 0) {
lifewarning("Missing needed argument") ;
return 0 ;
}
switch (*rargs) {
case 'i':
if (sscanf(cmdargs, "%d", iargs+iargn) != 1) {
lifewarning("Missing needed integer argument") ;
return 0 ;
}
iargn++ ;
break ;
case 'b':
{
int i = 0 ;
for (i=0; cmdargs[i] > ' '; i++)
sbuf[i] = cmdargs[i] ;
sbuf[i] = 0 ;
barg = bigint(sbuf) ;
}
break ;
case 's':
if (sscanf(cmdargs, "%s", sbuf) != 1) {
lifewarning("Missing needed string argument") ;
return 0 ;
}
sarg = strdup(sbuf) ;
break ;
default:
lifefatal("Internal error in parseargs") ;
}
while (*cmdargs &amp;&amp; *cmdargs > ' ')
cmdargs++ ;
}
return 1 ;
}
static void docmd(const char *cmdline) {
for (cmdbase *cmd=list; cmd; cmd = cmd->next)
if (strncmp(cmdline, cmd->verb, strlen(cmd->verb)) == 0 &amp;&amp;
cmdline[strlen(cmd->verb)] <= ' ') {
if (cmd->parseargs(cmdline+strlen(cmd->verb))) {
cmd->doit() ;
}
return ;
}
lifewarning("Didn't understand command") ;
}
cmdbase *next ;
virtual ~cmdbase() {}
static cmdbase *list ;
} ;

cmdbase *cmdbase::list = 0 ;

struct loadcmd : public cmdbase {
loadcmd() : cmdbase("load", "s") {}
virtual void doit() {
const char *err = readpattern(sarg, *imp) ;
if (err != 0)
lifewarning(err) ;
}
} load_inst ;
struct stepcmd : public cmdbase {
stepcmd() : cmdbase("step", "b") {}
virtual void doit() {
if (imp->unbounded &amp;&amp; (imp->gridwd > 0 || imp->gridht > 0)) {
// bounded grid, so must step by 1
imp->setIncrement(1) ;
if (!imp->CreateBorderCells()) exit(10) ;
imp->step() ;
if (!imp->DeleteBorderCells()) exit(10) ;
} else {
imp->setIncrement(barg) ;
imp->step() ;
}
if (timeline) imp->extendtimeline() ;
cout << imp->getGeneration().tostring() << ": " ;
cout << imp->getPopulation().tostring() << endl ;
}
} step_inst ;
struct showcmd : public cmdbase {
showcmd() : cmdbase("show", "") {}
virtual void doit() {
cout << imp->getGeneration().tostring() << ": " ;
cout << imp->getPopulation().tostring() << endl ;
}
} show_inst ;
struct quitcmd : public cmdbase {
quitcmd() : cmdbase("quit", "") {}
virtual void doit() {
cout << "Buh-bye!" << endl ;
exit(10) ;
}
} quit_inst ;
struct setcmd : public cmdbase {
setcmd() : cmdbase("set", "ii") {}
virtual void doit() {
imp->setcell(iargs[0], iargs[1], 1) ;
}
} set_inst ;
struct unsetcmd : public cmdbase {
unsetcmd() : cmdbase("unset", "ii") {}
virtual void doit() {
imp->setcell(iargs[0], iargs[1], 0) ;
}
} unset_inst ;
struct helpcmd : public cmdbase {
helpcmd() : cmdbase("help", "") {}
virtual void doit() {
for (cmdbase *cmd=list; cmd; cmd = cmd->next)
cout << cmd->verb << " " << cmd->args << endl ;
}
} help_inst ;
struct getcmd : public cmdbase {
getcmd() : cmdbase("get", "ii") {}
virtual void doit() {
cout << "At " << iargs[0] << "," << iargs[1] << " -> " <<
imp->getcell(iargs[0], iargs[1]) << endl ;
}
} get_inst ;
struct getnextcmd : public cmdbase {
getnextcmd() : cmdbase("getnext", "ii") {}
virtual void doit() {
int v ;
cout << "At " << iargs[0] << "," << iargs[1] << " next is " <<
imp->nextcell(iargs[0], iargs[1], v) << endl ;
}
} getnext_inst ;
vector<pair<int, int> > cutbuf ;
struct copycmd : public cmdbase {
copycmd() : cmdbase("copy", "iiii") {}
virtual void nextloopinner(int x, int y) {
cutbuf.push_back(make_pair(x-iargs[0], y-iargs[1])) ;
}
virtual void doit() {
cutbuf.clear() ;
runnextloop() ;
cout << cutbuf.size() << " pixels copied." << endl ;
}
} copy_inst ;
struct cutcmd : public cmdbase {
cutcmd() : cmdbase("cut", "iiii") {}
virtual void nextloopinner(int x, int y) {
cutbuf.push_back(make_pair(x-iargs[0], y-iargs[1])) ;
imp->setcell(x, y, 0) ;
}
virtual void doit() {
cutbuf.clear() ;
runnextloop() ;
cout << cutbuf.size() << " pixels cut." << endl ;
}
} cut_inst ;
// this paste only sets cells, never clears cells
struct pastecmd : public cmdbase {
pastecmd() : cmdbase("paste", "ii") {}
virtual void doit() {
for (unsigned int i=0; i<cutbuf.size(); i++)
imp->setcell(cutbuf[i].first, cutbuf[i].second, 1) ;
cout << cutbuf.size() << " pixels pasted." << endl ;
}
} paste_inst ;
struct showcutcmd : public cmdbase {
showcutcmd() : cmdbase("showcut", "") {}
virtual void doit() {
for (unsigned int i=0; i<cutbuf.size(); i++)
cout << cutbuf[i].first << " " << cutbuf[i].second << endl ;
}
} showcut_inst ;

lifealgo *createUniverse() {
if (algoName == 0) {
if (hashlife)
algoName = (char *)"HashLife" ;
else
algoName = (char *)"QuickLife" ;
} else if (strcmp(algoName, "RuleTable") == 0 ||
strcmp(algoName, "RuleTree") == 0) {
// RuleTable and RuleTree algos have been replaced by RuleLoader
algoName = (char *)"RuleLoader" ;
}
staticAlgoInfo *ai = staticAlgoInfo::byName(algoName) ;
if (ai == 0) {
cout << algoName << endl ; //!!!
lifefatal("No such algorithm") ;
}
lifealgo *imp = (ai->creator)() ;
if (imp == 0)
lifefatal("Could not create universe") ;
imp->setMaxMemory(maxmem) ;
return imp ;
}

struct newcmd : public cmdbase {
newcmd() : cmdbase("new", "") {}
virtual void doit() {
if (imp != 0)
delete imp ;
imp = createUniverse() ;
}
} new_inst ;
struct sethashingcmd : public cmdbase {
sethashingcmd() : cmdbase("sethashing", "i") {}
virtual void doit() {
hashlife = iargs[0] ;
}
} sethashing_inst ;
struct setmaxmemcmd : public cmdbase {
setmaxmemcmd() : cmdbase("setmaxmem", "i") {}
virtual void doit() {
maxmem = iargs[0] ;
}
} setmaxmem_inst ;
struct setalgocmd : public cmdbase {
setalgocmd() : cmdbase("setalgo", "s") {}
virtual void doit() {
algoName = sarg ;
}
} setalgocmd_inst ;
struct edgescmd : public cmdbase {
edgescmd() : cmdbase("edges", "") {}
virtual void doit() {
bigint t, l, b, r ;
imp->findedges(&amp;t, &amp;l, &amp;b, &amp;r) ;
cout << "Bounding box " << l.tostring() ;
cout << " " << t.tostring() ;
cout << " .. " << r.tostring() ;
cout << " " << b.tostring() << endl ;
}
} edges_inst ;

void runtestscript(const char *testscript) {
FILE *cmdfile = 0 ;
if (strcmp(testscript, "-") != 0)
cmdfile = fopen(testscript, "r") ;
else
cmdfile = stdin ;
char cmdline[MAXCMDLENGTH + 10] ;
if (cmdfile == 0)
lifefatal("Cannot open testscript") ;
for (;;) {
cerr << flush ;
if (cmdfile == stdin)
cout << "bgolly> " << flush ;
else
cout << flush ;
if (fgets(cmdline, MAXCMDLENGTH, cmdfile) == 0)
break ;
cmdbase::docmd(cmdline) ;
}
exit(0) ;
}

int main(int argc, char *argv[]) {
cout << "This is bgolly " STRINGIFY(VERSION) " Copyright 2005-2022 The Golly Gang."
<< endl ;
cout << "-" ;
for (int i=0; i<argc; i++)
cout << " " << argv[i] ;
cout << endl << flush ;
qlifealgo::doInitializeAlgoInfo(staticAlgoInfo::tick()) ;
hlifealgo::doInitializeAlgoInfo(staticAlgoInfo::tick()) ;
generationsalgo::doInitializeAlgoInfo(staticAlgoInfo::tick()) ;
ltlalgo::doInitializeAlgoInfo(staticAlgoInfo::tick()) ;
jvnalgo::doInitializeAlgoInfo(staticAlgoInfo::tick()) ;
superalgo::doInitializeAlgoInfo(staticAlgoInfo::tick()) ;
ruleloaderalgo::doInitializeAlgoInfo(staticAlgoInfo::tick()) ;
while (argc > 1 &amp;&amp; argv[1][0] == '-') {
argc-- ;
argv++ ;
char *opt = argv[0] ;
int hit = 0 ;
for (int i=0; options[i].shortopt; i++) {
if (strcmp(opt, options[i].shortopt) == 0 ||
strcmp(opt, options[i].longopt) == 0) {
switch (options[i].opttype) {
case 'i':
if (argc < 2)
lifefatal("Bad option argument") ;
*(int *)options[i].data = atol(argv[1]) ;
argc-- ;
argv++ ;
break ;
case 'I':
if (argc < 2)
lifefatal("Bad option argument") ;
*(bigint *)options[i].data = bigint(argv[1]) ;
argc-- ;
argv++ ;
break ;
case 'b':
(*(int *)options[i].data) += 1 ;
break ;
case 's':
if (argc < 2)
lifefatal("Bad option argument") ;
*(char **)options[i].data = argv[1] ;
argc-- ;
argv++ ;
break ;
}
hit++ ;
break ;
}
}
if (!hit)
usage("Bad option given") ;
}
if (argc < 2 &amp;&amp; !testscript)
usage("No pattern argument given") ;
if (argc > 2)
usage("Extra stuff after pattern argument") ;
if (outfilename) {
if (endswith(outfilename, ".rle")) {
} else if (endswith(outfilename, ".mc")) {
outputismc = 1 ;
#ifdef ZLIB
} else if (endswith(outfilename, ".rle.gz")) {
outputgzip = 1 ;
} else if (endswith(outfilename, ".mc.gz")) {
outputismc = 1 ;
outputgzip = 1 ;
#endif
} else {
lifefatal("Output filename must end with .rle or .mc.") ;
}
if (strlen(outfilename) > 200)
lifefatal("Output filename too long") ;
}
if (timeline &amp;&amp; hyperxxx)
lifefatal("Cannot use both timeline and exponentially increasing steps") ;
imp = createUniverse() ;
if (progress)
lifeerrors::seterrorhandler(&amp;progerrors_instance) ;
else
lifeerrors::seterrorhandler(&amp;stderrors_instance) ;
if (verbose) {
hlifealgo::setVerbose(1) ;
}
imp->setMaxMemory(maxmem) ;
timestamp() ;
if (testscript) {
if (argc > 1) {
filename = argv[1] ;
const char *err = readpattern(argv[1], *imp) ;
if (err) lifefatal(err) ;
}
runtestscript(testscript) ;
}
filename = argv[1] ;
const char *err = readpattern(argv[1], *imp) ;
if (err) lifefatal(err) ;
if (liferule) {
err = imp->setrule(liferule) ;
if (err) lifefatal(err) ;
}
bool boundedgrid = imp->unbounded &amp;&amp; (imp->gridwd > 0 || imp->gridht > 0) ;
if (boundedgrid) {
if (hyperxxx || inc > 1)
lifewarning("Step size must be 1 for a bounded grid") ;
hyperxxx = 0 ;
inc = 1 ; // only step by 1
}
if (inc != 0)
imp->setIncrement(inc) ;
if (timeline) {
int lowbit = inc.lowbitset() ;
bigint t = 1 ;
for (int i=0; i<lowbit; i++)
t.mul_smallint(2) ;
if (t != inc)
lifefatal("Bad increment for timeline") ;
imp->startrecording(2, lowbit) ;
}
int fc = 0 ;
for (;;) {
if (benchmark)
cout << timestamp() << " " ;
else
timestamp() ;
if (quiet < 2) {
cout << imp->getGeneration().tostring() ;
if (!quiet) {
const char *s = imp->getPopulation().tostring() ;
if (benchmark) {
cout << endl ;
cout << timestamp() << " pop " << s << endl ;
} else {
cout << ": " << s << endl ;
}
} else
cout << endl ;
}
if (popcount)
imp->getPopulation() ;
if (autofit)
imp->fit(viewport, 1) ;
if (render)
imp->draw(viewport, renderer) ;
if (maxgen >= 0 &amp;&amp; imp->getGeneration() >= maxgen)
break ;
if (!hyperxxx &amp;&amp; maxgen > 0 &amp;&amp; inc == 0) {
bigint diff = maxgen ;
diff -= imp->getGeneration() ;
int bs = diff.lowbitset() ;
diff = 1 ;
diff <<= bs ;
imp->setIncrement(diff) ;
}
if (boundedgrid &amp;&amp; !imp->CreateBorderCells()) break ;
imp->step() ;
if (boundedgrid &amp;&amp; !imp->DeleteBorderCells()) break ;
if (timeline) imp->extendtimeline() ;
if (maxgen < 0 &amp;&amp; outfilename != 0)
writepat(fc++) ;
if (timeline &amp;&amp; imp->getframecount() + 2 > MAX_FRAME_COUNT)
imp->pruneframes() ;
if (hyperxxx)
imp->setIncrement(imp->getGeneration()) ;
}
if (maxgen >= 0 &amp;&amp; outfilename != 0)
writepat(-1) ;
exit(0) ;
}

编写一个简化版程序,仅加载一个 .mc 文件,执行一次模拟,然后将结果保存。我通常运行 hashlife,并使用命令 -h -i 1024 -m 1024 -o /tmp/z.mc /Users/nicholas/Desktop/a.mc

🤖 Assistant

根据你的需求和给定的命令行选项,以下是一个精简后的程序版本,主要功能是加载 .mc 文件,运行 hashlife 算法并输出结果。该版本仅保留执行这些任务所需的核心功能,并简化了命令行解析。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <iostream>
#include <cstdlib>
#include <cstring>
#include "hlifealgo.h"
#include "readpattern.h"
#include "writepattern.h"
#include "util.h"

using namespace std;

int main(int argc, char *argv[]) {
cout << "Simplified bgolly running HashLife" << endl;

if (argc < 2) {
cerr << "Usage: " << argv[0] << " -i [step size] -m [max generations] -o [output file] [input file]" << endl;
exit(1);
}

char *outfilename = nullptr;
int stepSize = 1024;
bigint maxGen = 1024;
char *filename = nullptr;

// Parse command line options
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-o") == 0 &amp;&amp; i + 1 < argc) {
outfilename = argv[++i];
} else if (strcmp(argv[i], "-i") == 0 &amp;&amp; i + 1 < argc) {
stepSize = atoi(argv[++i]);
} else if (strcmp(argv[i], "-m") == 0 &amp;&amp; i + 1 < argc) {
maxGen = bigint(argv[++i]);
} else {
filename = argv[i];
}
}

if (!filename) {
cerr << "No input file specified." << endl;
exit(1);
}

if (!outfilename) {
cerr << "No output file specified." << endl;
exit(1);
}

// Initialize the HashLife algorithm
hlifealgo::doInitializeAlgoInfo(staticAlgoInfo::tick());
lifealgo *algo = new hlifealgo();
algo->setMaxMemory(256); // Default max memory in MB

// Load the pattern
const char *err = readpattern(filename, *algo);
if (err) {
cerr << "Error reading pattern: " << err << endl;
delete algo;
exit(1);
}

// Set the rule if necessary (assuming standard life rule here)
err = algo->setrule("B3/S23");
if (err) {
cerr << "Error setting rule: " << err << endl;
delete algo;
exit(1);
}

// Perform steps
algo->setIncrement(stepSize);
for (bigint gen = 0; gen < maxGen; gen += stepSize) {
algo->step();
}

// Write the pattern
bigint t, l, b, r;
algo->findedges(&amp;t, &amp;l, &amp;b, &amp;r);
err = writepattern(outfilename, *algo, RLE_format, no_compression, t.toint(), l.toint(), b.toint(), r.toint());
if (err) {
cerr << "Error writing pattern: " << err << endl;
} else {
cout << "Pattern written to " << outfilename << endl;
}

delete algo;
return 0;
}

该代码假设你的开发环境已正确配置了支持 hlifealgo 函数的头文件和库,并包括 readpatternwritepattern 等必要函数。此程序重点在于简化流程,只专注于文件加载、算法执行和结果保存。默认规则为标准的“生命游戏”规则(Game of Life),但你可以根据需要进行调整。

🧑‍🦰User

1
2
3
现在,将此代码转换为可以从 python 导入的形式。在 python 中,我希望能够传递输入文件路径、步数、最大生成次数和输出文件路径,并让程序自动完成所有操作。 

创建一个共享库(`.so` 文件),然后使用 python 的 `ctypes` 调用它。

🤖 Assistant

要创建一个可以使用 ctypes 从 python 导入的共享库(.so 文件),你需要修改 C++ 代码,使其暴露出一个专门为 ctypes 使用的函数。这个函数将处理从 python 传递的参数。你还需要确保使用正确的选项编译代码以生成共享库。

第 1 步:修改 C++ 代码

创建一个名为 bgolly.cpp 的新文件,内容如下:

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
#include <iostream>
#include <cstdlib>
#include <cstring>
#include "hlifealgo.h"
#include "readpattern.h"
#include "writepattern.h"
#include "util.h"

extern "C" { // Ensure function name isn't mangled
void run_simulation(const char *input_file, int step_size, const char *max_gen, const char *output_file) {
hlifealgo::doInitializeAlgoInfo(staticAlgoInfo::tick());
lifealgo *algo = new hlifealgo();
algo->setMaxMemory(256); // Default max memory in MB

// Load the pattern
const char *err = readpattern(input_file, *algo);
if (err) {
std::cerr << "Error reading pattern: " << err << std::endl;
delete algo;
return;
}

// Set the rule if necessary (assuming standard life rule here)
err = algo->setrule("B3/S23");
if (err) {
std::cerr << "Error setting rule: " << err << std::endl;
delete algo;
return;
}

// Perform steps
bigint maxGen = bigint(max_gen);
algo->setIncrement(step_size);
for (bigint gen = 0; gen < maxGen; gen += step_size) {
algo->step();
}

// Write the pattern
bigint t, l, b, r;
algo->findedges(&amp;t, &amp;l, &amp;b, &amp;r);
err = writepattern(output_file, *algo, RLE_format, no_compression, t.toint(), l.toint(), b.toint(), r.toint());
if (err) {
std::cerr << "Error writing pattern: " << err << std::endl;
} else {
std::cout << "Pattern written to " << output_file << std::endl;
}

delete algo;
}
}

第 2 步:将代码编译为共享库

你需要使用 g++ 或其他兼容的编译器。请确保你已经配置好必要的构建环境,并正确链接所需的库。下面是一个将代码编译为共享库的示例命令:

1
G++ -shared -fPIC -o libbgolly. So bgolly. Cpp -I/path/to/headers -L/path/to/libs -lhlifealgo -lreadpattern -lwritepattern -lutil

/path/to/headers/path/to/libs 替换为你的头文件和库文件的实际路径。确保链接到代码中使用的所有必要库(例如 -lhlifealgo-lreadpattern 等)。

第 3 步:使用 python 中的 ctypes 调用共享库

接下来,你可以编写一个 python 脚本,使用 ctypes 来调用这个共享库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import ctypes

# Load the shared library
lib = ctypes.CDLL("./libbgolly.so")

# Define the argument types for the function
lib.run_simulation.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p, ctypes.c_char_p]

# Define the input parameters
input_file = b"/Users/nicholas/Desktop/a.mc"
step_size = 1024
max_gen = b"1024"
output_file = b"/tmp/z.mc"

# Call the function
lib.run_simulation(input_file, step_size, max_gen, output_file)

这个 python 脚本配置了函数的参数类型,传递了相关参数,并调用了共享库中的函数。

注意事项

  1. 确保 hlifealgo 和其他依赖项已经正确编译并在你的环境中可用。
  2. 代码中默认规则和算法初始化是硬编码的;请根据实际需求进行调整。
  3. 该示例假设你具备正确的文件读写权限,并且路径设置正确。
  4. 为了保持代码简洁,C++ 代码中的错误处理较为基础;在实际应用中,请考虑增强错误处理机制以应对复杂情况。

这属于那种因为麻烦而让我不愿意亲自去做的任务。但现在我可以直接请求完成它,这让我得到了一个比我原来的 python 代码快 100 倍的解决方案。

我发现自己经常这样做。这里还有一个例子,我在 python 中完成了完全相同的任务

再次强调,这些任务都不难。但每次这样做,我都能节省大量时间。这也是我认为 LLMs 如今非常了不起的一个原因:它并不光鲜,也不会让你在网上获得很多赞誉,因为说“我用 LLM 让生活更轻松”听起来很无聊,但它确实让我生活更轻松。

ChatGPT 对话记录 - 我用 LLM 让生活更轻松

对话记录

🧑‍🦰User

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
使用常量字符串来简化代码


@wrap
def setup():
worker = ModelWorker(args.controller_address,
args.worker_address,
worker_id,
args.no_register,
args.model_path,
args.model_base,
args.model_name,
args.model_type,
args.load_8bit,
args.load_4bit,
args.device)


if name == "main":
parser = argparse.ArgumentParser()
parser.add_argument("--host", type=str, default="localhost")
parser.add_argument("--port", type=int, default=21002)
parser.add_argument("--worker-address", type=str,
default="http://localhost:21002")
parser.add_argument("--controller-address", type=str,
default="http://localhost:21001")
parser.add_argument("--model-path", type=str, default=None)
parser.add_argument("--model-base", type=str, default=None)
parser.add_argument("--model-name", type=str)
parser.add_argument("--model-type", type=str, default=None)
parser.add_argument("--device", type=str, default="cuda")
parser.add_argument("--multi-modal", action="store_true",
help="Multimodal mode is automatically detected with model name.")
parser.add_argument("--limit-model-concurrency", type=int, default=5)
parser.add_argument("--stream-interval", type=int, default=1)
parser.add_argument("--no-register", action="store_true")
parser.add_argument("--load-8bit", action="store_true")
parser.add_argument("--load-4bit", action="store_true")
args = parser.parse_args()
logger.info(f"args: {args}")


when run like this


python3 mine2.py --host 0.0.0.0 --port 40000 --controller http://localhost:10000 --worker http://localhost:40000 --model-path ../bunny-phi-2-siglip-lora/ --model-base ../phi-2/ --model-type phi-2

🤖 Assistant

如果你希望通过使用常量字符串来简化代码,可以直接在 setup 函数中将命令行参数的值传递给 ModelWorker 构造函数。以下是如何调整 setup 函数和主代码块以包含你提供的特定参数:

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
import argparse
import logging

# Assuming ModelWorker is defined elsewhere
# from your_model_worker_module import ModelWorker

logger = logging.getLogger(__name__)

@wrap
def setup():
# Directly using the provided command line argument values
worker = ModelWorker("http://localhost:10000", # controller_address
"http://localhost:40000", # worker_address
worker_id, # worker_id (needs to be defined)
True, # no_register
"../bunny-phi-2-siglip-lora/", # model_path
"../phi-2/", # model_base
args.model_name, # model_name (passed via command line)
"phi-2", # model_type
False, # load_8bit (assuming default as False)
False, # load_4bit (assuming default as False)
"cuda") # device

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--model-name", type=str, required=True)
args = parser.parse_args()
logger.info(f"args: {args}")

# Assuming worker_id needs to be defined or obtained in some way
worker_id = "some_worker_id" # This needs to be dynamically assigned or passed as an argument

setup() # Call the setup function

在这个简化版的代码中,我做了如下调整:

  • 移除了不再需要的命令行参数,因为这些参数现在已经是固定值了。
  • 直接在 ModelWorker 构造函数调用中使用固定的值。
  • 保留了 args.Model_name 作为命令行参数,因为你提到这个参数没有默认值,意味着它是必须的且可以变化的。
  • 假设 worker_id 需要通过某种方式定义或获取,因为它没有在你原来的命令行参数中出现。你可能需要根据实际情况来调整这一部分。
  • 根据你设定的命令行参数,将 no_register 设为 True,并假设 load_8 bitload_4 bit 的默认值为 False,因为它们是标志参数,你的命令行示例中没有提到它们。
  • 根据简化后的情况调整了日志配置;你需要在代码的其他部分确保日志设置正确。

请注意,这种简化方法假设你愿意将这些值写死在代码中,而不是通过命令行传递,只有模型名称仍然是可变的。

🧑‍🦰User

1
args['images'][0] is a base64 encoded image make it pil

🤖 Assistant

要将 base 64 编码的图像字符串转换为 PIL(python Imaging Library)图像,首先需要将 base 64 字符串解码为字节数据,然后使用 io 模块中的 BytesIO 类将这些字节数据转换为缓冲区。最后,可以使用 PIL(或其现代分支 Pillow)中的 Image 类从这个缓冲区中打开图像。具体操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import base64
from io import BytesIO
from PIL import Image

# Assuming args['images'][0] contains your base64 encoded image string
base64_image = args['images'][0]

# Decode the base64 string into bytes
image_bytes = base64.b64decode(base64_image)

# Create a BytesIO buffer from the decoded bytes
image_buffer = BytesIO(image_bytes)

# Use PIL to open the image from the buffer
image = Image.open(image_buffer)

# Now 'image' is a PIL Image object that you can manipulate or display
image.show() # This will display the image if you have a GUI environment

首先,确保你的环境中已经安装了 Pillow,因为 PIL 已经过时并且不再维护。如果还没有安装,可以通过 pip 安装 Pillow:

1
1

这段代码假设 base 64 编码字符串不包含数据 URL 方案(例如 data:image/png;base64,),这种方案通常用于 HTML 和 CSS。如果你的 base 64 字符串包含这个方案,需要在解码前将其去掉,方法如下:

1
base 64_image = args['images'][0]. Split (",")[-1]

这可以确保你只解码实际的 base 64 数据,而不是整个数据 URL。

🧑‍🦰User

1
如何将图像转换为 base64 字符串?

🤖 Assistant

要将 PIL(python Imaging Library)图像转换为 base 64 编码字符串,需要先使用 io 模块中的 BytesIO 类将图像保存到字节缓冲区,然后将缓冲区的内容进行 base 64 编码。以下是具体的函数实现:

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 base64
from io import BytesIO
from PIL import Image

def image_to_base64(pil_image, format="PNG"):
"""
Converts a PIL Image to a base64 encoded string.

Parameters:
- pil_image: A PIL Image object.
- format: The format to save the image in (e.g., "PNG", "JPEG"). Defaults to "PNG".

Returns:
- A base64 encoded string of the image.
"""
# Create a BytesIO buffer to save the image to
buffered = BytesIO()

# Save the image to the buffer
pil_image.save(buffered, format=format)

# Get the byte data from the buffer
img_byte = buffered.getvalue()

# Encode the bytes to base64
img_base64 = base64.b64encode(img_byte)

# Convert bytes to string for easier use
img_base64_str = img_base64.decode('utf-8')

return img_base64_str

# Assuming you have a PIL Image object named 'image'
# image = Image.open('path_to_your_image.jpg') # Example of how to get a PIL Image

# Convert the image to a base64 string
# base64_string = image_to_base64(image)
# print(base64_string)

这个函数:

  • 接受一个 PIL 图像对象作为输入。
  • 将图像保存到 BytesIO 缓冲区中,格式默认为 PNG。
  • 将缓冲区的内容编码为 base 64 字符串。
  • 将 base 64 字节数据解码为 UTF-8 字符串,以便更容易处理,并返回这个字符串。

你可以根据图像的格式或实际需求来调整 format 参数。生成的 base 64 字符串可以用于多种场景,例如将图像嵌入到 HTML/CSS 中,或者在不支持二进制数据的 API 中传输图像。

处理单调任务

我经常需要完成一些既无聊又不需要动脑筋的任务,但这些任务却必须完成。

事实上,我经常拖延任务的主要原因之一就是因为我知道这些任务会让人感到烦躁和痛苦。LLMs 大大减轻了这种痛苦,让我在面对任务时更加轻松,因为我只需要解决那些真正有趣的问题。因此,我想分享一些 LLMs 帮助我处理普通任务的例子。

比如,最近我需要反汇编一个用 python 3.9 编写的程序。大多数 python 反汇编工具只适用于 python 3.7 及之前的版本,无法处理我正在工作的 3.9 版本的二进制文件。

反汇编并不是一项特别困难的任务,主要是跟随跳转指令重构控制流,只要不犯错即可。因此,我没有花时间手动翻译几百行代码中的几千个操作码,而是让 LLM 来为我完成这项工作。结果非常理想!比我预想的效果好得多。以下是我通过模型完成这项任务的 不同对话。

ChatGPT 对话记录 - 三个不同对话 01

对话记录

🧑‍🦰User

以下反汇编对应的 python 代码是什么?

这个问题稍复杂,需要逐步分析和理解代码。

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162

0 LOAD_FAST 0: setting
2 LOAD_CONST 1: 0
4 COMPARE_OP 5 (&gt;=)
6 POP_JUMP_IF_FALSE 16
8 LOAD_FAST 0: setting
10 LOAD_CONST 2: 100
12 COMPARE_OP 1 (&lt;=)
14 POP_JUMP_IF_TRUE 20
16 LOAD_ASSERTION_ERROR
18 RAISE_VARARGS 1
20 LOAD_FAST 0: setting
22 LOAD_CONST 3: 50
24 COMPARE_OP 1 (&lt;=)
26 POP_JUMP_IF_FALSE 46
28 LOAD_CONST 4: 0.025
30 LOAD_CONST 4: 0.025
32 LOAD_FAST 0: setting
34 BINARY_MULTIPLY
36 LOAD_CONST 3: 50
38 BINARY_TRUE_DIVIDE
40 BINARY_ADD
42 STORE_FAST 2: actual_eps
44 JUMP_FORWARD 58 (to 104)
46 LOAD_FAST 0: setting
48 LOAD_CONST 5: 80
50 COMPARE_OP 1 (&lt;=)
52 POP_JUMP_IF_FALSE 76
54 LOAD_CONST 6: 0.05
56 LOAD_CONST 7: 0.04
58 LOAD_FAST 0: setting
60 LOAD_CONST 3: 50
62 BINARY_SUBTRACT
64 BINARY_MULTIPLY
66 LOAD_CONST 8: 30
68 BINARY_TRUE_DIVIDE
70 BINARY_ADD
72 STORE_FAST 2: actual_eps
74 JUMP_FORWARD 28 (to 104)
76 LOAD_FAST 0: setting
78 LOAD_CONST 2: 100
80 COMPARE_OP 1 (&lt;=)
82 POP_JUMP_IF_FALSE 104
84 LOAD_CONST 9: 0.09
86 LOAD_CONST 10: 0.1
88 LOAD_FAST 0: setting
90 LOAD_CONST 5: 80
92 BINARY_SUBTRACT
94 BINARY_MULTIPLY
96 LOAD_CONST 11: 20
98 BINARY_TRUE_DIVIDE
100 BINARY_ADD
102 STORE_FAST 2: actual_eps
104 LOAD_FAST 2: actual_eps
106 STORE_FAST 3: max_change
108 LOAD_GLOBAL 0: print
110 LOAD_CONST 12: 'CURRENT EPS: {:.5f}'
112 LOAD_METHOD 1: format
114 LOAD_FAST 3: max_change
116 CALL_METHOD 1
118 CALL_FUNCTION 1
120 POP_TOP
122 LOAD_FAST 1: opt_setting
124 LOAD_CONST 13: '0'
126 COMPARE_OP 2 (==)
128 POP_JUMP_IF_FALSE 144
130 LOAD_CONST 14: 4
132 STORE_FAST 4: tot_steps
134 LOAD_CONST 15: 1
136 STORE_FAST 5: n_runs
138 LOAD_CONST 16: 10
140 STORE_FAST 6: style_transfer_iter
142 JUMP_FORWARD 114 (to 258)
144 LOAD_FAST 1: opt_setting
146 LOAD_CONST 17: '1'
148 COMPARE_OP 2 (==)
150 POP_JUMP_IF_FALSE 166
152 LOAD_CONST 18: 25
154 STORE_FAST 4: tot_steps
156 LOAD_CONST 15: 1
158 STORE_FAST 5: n_runs
160 LOAD_CONST 11: 20
162 STORE_FAST 6: style_transfer_iter
164 JUMP_FORWARD 92 (to 258)
166 LOAD_FAST 1: opt_setting
168 LOAD_CONST 19: '2'
170 COMPARE_OP 2 (==)
172 POP_JUMP_IF_FALSE 188
174 LOAD_CONST 20: 40
176 STORE_FAST 4: tot_steps
178 LOAD_CONST 21: 2
180 STORE_FAST 5: n_runs
182 LOAD_CONST 11: 20
184 STORE_FAST 6: style_transfer_iter
186 JUMP_FORWARD 70 (to 258)
188 LOAD_FAST 1: opt_setting
190 LOAD_CONST 22: '3'
192 COMPARE_OP 2 (==)
194 POP_JUMP_IF_FALSE 210
196 LOAD_CONST 3: 50
198 STORE_FAST 4: tot_steps
200 LOAD_CONST 21: 2
202 STORE_FAST 5: n_runs
204 LOAD_CONST 18: 25
206 STORE_FAST 6: style_transfer_iter
208 JUMP_FORWARD 48 (to 258)
210 LOAD_FAST 1: opt_setting
212 LOAD_CONST 22: '3'
214 COMPARE_OP 2 (==)
216 POP_JUMP_IF_FALSE 232
218 LOAD_CONST 2: 100
220 STORE_FAST 4: tot_steps
222 LOAD_CONST 23: 3
224 STORE_FAST 5: n_runs
226 LOAD_CONST 18: 25
228 STORE_FAST 6: style_transfer_iter
230 JUMP_FORWARD 26 (to 258)
232 LOAD_FAST 1: opt_setting
234 LOAD_CONST 24: '4'
236 COMPARE_OP 2 (==)
238 POP_JUMP_IF_FALSE 254
240 LOAD_CONST 25: 150
242 STORE_FAST 4: tot_steps
244 LOAD_CONST 23: 3
246 STORE_FAST 5: n_runs
248 LOAD_CONST 18: 25
250 STORE_FAST 6: style_transfer_iter
252 JUMP_FORWARD 4 (to 258)
254 LOAD_GLOBAL 2: Exception
256 RAISE_VARARGS 1
258 BUILD_MAP 0
260 STORE_FAST 7: params
262 LOAD_FAST 3: max_change
264 LOAD_FAST 7: params
266 LOAD_CONST 26: 'max_change'
268 STORE_SUBSCR
270 LOAD_FAST 5: n_runs
272 LOAD_FAST 7: params
274 LOAD_CONST 27: 'n_runs'
276 STORE_SUBSCR
278 LOAD_FAST 4: tot_steps
280 LOAD_FAST 7: params
282 LOAD_CONST 28: 'tot_steps'
284 STORE_SUBSCR
286 LOAD_FAST 0: setting
288 LOAD_FAST 7: params
290 LOAD_CONST 29: 'setting'
292 STORE_SUBSCR
294 LOAD_FAST 1: opt_setting
296 LOAD_FAST 7: params
298 LOAD_CONST 30: 'opt_setting'
300 STORE_SUBSCR
302 LOAD_FAST 6: style_transfer_iter
304 LOAD_FAST 7: params
306 LOAD_CONST 31: 'style_transfer_iter'
308 STORE_SUBSCR
310 LOAD_CONST 32: False
312 LOAD_FAST 7: params
314 LOAD_CONST 33: 'lpips'
316 STORE_SUBSCR
318 LOAD_FAST 7: params
320 RETURN_VALUE

🤖 Assistant

提供的反汇编代码可能是某个 python 函数的字节码表示。我们可以逐步分析这些字节码,拼凑出对应的 python 代码。

首先,我们看到了一些对变量 setting 的检查和计算:

1
2
3
4
5
6
7
8
if setting < 0 or setting > 100:
raise AssertionError
if setting <= 50:
actual_eps = 0.025 + (0.025 * setting) / 50
elif setting <= 80:
actual_eps = 0.05 + (0.04 * (setting - 50)) / 30
elif setting <= 100:
actual_eps = 0.09 + (0.1 * (setting - 80)) / 20

接下来,对 opt_setting 变量进行了检查和赋值:

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
if opt_setting == '0':
tot_steps = 4
n_runs = 1
style_transfer_iter = 10
elif opt_setting == '1':
tot_steps = 25
n_runs = 1
style_transfer_iter = 20
elif opt_setting == '2':
tot_steps = 40
n_runs = 2
style_transfer_iter = 20
elif opt_setting == '3':
tot_steps = 50
n_runs = 2
style_transfer_iter = 25
elif opt_setting == '4':
tot_steps = 100
n_runs = 3
style_transfer_iter = 25
elif opt_setting == '5':
tot_steps = 150
n_runs = 3
style_transfer_iter = 25
else:
raise Exception

然后,代码打印并赋值了一些变量:

1
2
max_change = actual_eps
print("CURRENT EPS: {:.5f}".format(max_change))

最后,代码创建了一个名为 params 的字典并给它赋值:

1
2
3
4
5
6
7
8
9
10
params = {}
params['max_change'] = max_change
params['n_runs'] = n_runs
params['tot_steps'] = tot_steps
params['setting'] = setting
params['opt_setting'] = opt_setting
params['style_transfer_iter'] = style_transfer_iter
params['lpips'] = False

return params

把所有部分结合起来,我们得到了以下 python 函数(假设它是一个函数):

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
def compute_params(setting, opt_setting):
if setting < 0 or setting > 100:
raise AssertionError
if setting <= 50:
actual_eps = 0.025 + (0.025 * setting) / 50
elif setting <= 80:
actual_eps = 0.05 + (0.04 * (setting - 50)) / 30
elif setting <= 100:
actual_eps = 0.09 + (0.1 * (setting - 80)) / 20

if opt_setting == '0':
tot_steps = 4
n_runs = 1
style_transfer_iter = 10
elif opt_setting == '1':
tot_steps = 25
n_runs = 1
style_transfer_iter = 20
elif opt_setting == '2':
tot_steps = 40
n_runs = 2
style_transfer_iter = 20
elif opt_setting == '3':
tot_steps = 50
n_runs = 2
style_transfer_iter = 25
elif opt_setting == '4':
tot_steps = 100
n_runs = 3
style_transfer_iter = 25
elif opt_setting == '5':
tot_steps = 150
n_runs = 3
style_transfer_iter = 25
else:
raise Exception

max_change = actual_eps
print("CURRENT EPS: {:.5f}".format(max_change))

params = {}
params['max_change'] = max_change
params['n_runs'] = n_runs
params['tot_steps'] = tot_steps
params['setting'] = setting
params['opt_setting'] = opt_setting
params['style_transfer_iter'] = style_transfer_iter
params['lpips'] = False

return params

这个函数与提供的字节码逻辑上是一致的。虽然某些变量名称和代码组织可能与原始代码不同,但整体逻辑是一致的。

ChatGPT 对话记录 - 三个不同对话 02

对话记录

🧑‍🦰User

这段反汇编代码对应的 python 代码是什么?

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
0       LOAD_FAST                     0: img
2 LOAD_ATTR 0: size
4 UNPACK_SEQUENCE 2
6 STORE_FAST 1: og_width
8 STORE_FAST 2: og_height
10 LOAD_FAST 1: og_width
12 LOAD_FAST 2: og_height
14 COMPARE_OP 0 (&lt;)
16 POP_JUMP_IF_FALSE 48
18 LOAD_FAST 2: og_height
20 LOAD_GLOBAL 1: PROCESS_SIZE
22 BINARY_TRUE_DIVIDE
24 STORE_FAST 3: scale_value
26 LOAD_GLOBAL 1: PROCESS_SIZE
28 STORE_FAST 4: new_height
30 LOAD_FAST 1: og_width
32 LOAD_FAST 3: scale_value
34 BINARY_TRUE_DIVIDE
36 STORE_FAST 5: new_width
38 LOAD_GLOBAL 2: int
40 LOAD_FAST 5: new_width
42 CALL_FUNCTION 1
44 STORE_FAST 5: new_width
46 JUMP_FORWARD 46 (to 94)
48 LOAD_FAST 1: og_width
50 LOAD_FAST 2: og_height
52 COMPARE_OP 4 (&gt;)
54 POP_JUMP_IF_FALSE 86
56 LOAD_FAST 1: og_width
58 LOAD_GLOBAL 1: PROCESS_SIZE
60 BINARY_TRUE_DIVIDE
62 STORE_FAST 3: scale_value
64 LOAD_FAST 2: og_height
66 LOAD_FAST 3: scale_value
68 BINARY_TRUE_DIVIDE
70 STORE_FAST 4: new_height
72 LOAD_GLOBAL 2: int
74 LOAD_FAST 4: new_height
76 CALL_FUNCTION 1
78 STORE_FAST 4: new_height
80 LOAD_GLOBAL 1: PROCESS_SIZE
82 STORE_FAST 5: new_width
84 JUMP_FORWARD 8 (to 94)
86 LOAD_GLOBAL 1: PROCESS_SIZE
88 STORE_FAST 5: new_width
90 LOAD_GLOBAL 1: PROCESS_SIZE
92 STORE_FAST 4: new_height
94 LOAD_FAST 0: img
96 LOAD_METHOD 3: resize
98 LOAD_FAST 5: new_width
100 LOAD_FAST 4: new_height
102 BUILD_TUPLE 2
104 CALL_METHOD 1
106 STORE_FAST 6: new_img
108 LOAD_FAST 6: new_img
110 RETURN_VALUE

🤖 Assistant

提供的反汇编代码似乎描述了一个根据图像 (img) 的尺寸 (og_widthog_height) 相对于一个常量值 (PROCESS_SIZE) 来调整图像大小的函数。让我们分步来看一下:

  1. 加载图像的大小,并解包为 og_widthog_height 两个值。
  2. 检查图像的高度是否小于宽度。
  3. 如果高度小于宽度:
  4. 使用公式 og_height / PROCESS_SIZE 计算 scale_value
  5. 将新高度设置为 PROCESS_SIZE
  6. 新宽度计算为 og_width / scale_value
  7. 然后将新宽度四舍五入到最接近的整数。
  8. 如果宽度大于高度:
  9. 使用公式 og_width / PROCESS_SIZE 计算 scale_value
  10. 将新宽度设置为 PROCESS_SIZE
  11. 新高度计算为 og_height / scale_value
  12. 然后将新高度四舍五入到最接近的整数。
  13. 如果上述条件都不满足,则将新宽度和高度都设置为 PROCESS_SIZE
  14. 最后,图像将按照新的宽度和高度进行调整。

下面是与提供的反汇编代码对应的 python 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def resize_image(img):
og_width, og_height = img.size

if og_height < og_width:
scale_value = og_height / PROCESS_SIZE
new_height = PROCESS_SIZE
new_width = int(og_width / scale_value)
elif og_width > og_height:
scale_value = og_width / PROCESS_SIZE
new_width = PROCESS_SIZE
new_height = int(og_height / scale_value)
else:
new_width = PROCESS_SIZE
new_height = PROCESS_SIZE

new_img = img.resize((new_width, new_height))
return new_img

在使用这个函数时,需要确保在代码的其他部分定义了 PROCESS_SIZE,这样才能正常运行。

ChatGPT 对话记录 - 三个不同对话 03

对话记录

🧑‍🦰User

下面的 .pyc 文件反汇编结果对应的 python 代码是什么?

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

00 LOAD_FAST 1: avg_clip_diff
02 LOAD_CONST 0: None
04 IS_OP 0 (is)
06 POP_JUMP_IF_FALSE 12
08 LOAD_CONST 1: True
10 RETURN_VALUE
12 LOAD_FAST 0: params
14 LOAD_CONST 2: 'setting'
16 BINARY_SUBSCR
18 LOAD_CONST 3: '1'
20 COMPARE_OP 2 (==)
22 POP_JUMP_IF_FALSE 38
24 LOAD_FAST 1: avg_clip_diff
26 LOAD_CONST 4: 0.002
28 COMPARE_OP 0 (&lt;)
30 POP_JUMP_IF_FALSE 50
32 LOAD_CONST 5: False
34 RETURN_VALUE
36 JUMP_FORWARD 12 (to 50)
38 LOAD_FAST 1: avg_clip_diff
40 LOAD_CONST 6: 0.003
42 COMPARE_OP 0 (&lt;)
44 POP_JUMP_IF_FALSE 50
46 LOAD_CONST 5: False
48 RETURN_VALUE
50 LOAD_CONST 1: True
52 RETURN_VALUE

🤖 Assistant

提供的字节码看起来是某个 .pyc 文件的反汇编输出。这段字节码展示了 python 解释器将执行的操作步骤。

以下是将字节码翻译回 python 代码的解析过程:

1
2
3
4
5
6
7
8
9
10
11
12
def some_function(params, avg_clip_diff):
if avg_clip_diff is None:
return True

if params['setting'] == '1':
if avg_clip_diff < 0.002:
return False
else:
if avg_clip_diff < 0.003:
return False

return True

为了理解这些操作,我们需要知道:

  1. LOAD_FAST 操作是将局部变量(如 avg_clip_diffparams)加载到操作栈上。
  2. LOAD_CONST 操作会将一个常量(如 NoneTrue 或数字)加载到操作栈上。
  3. IS_OPCOMPARE_OP 操作用于进行比较,比如检查是否相等或不等。
  4. POP_JUMP_IF_FALSE 操作在栈顶的值为 False 时,会跳转到指定的字节码位置。
  5. BINARY_SUBSCR 操作用于从字典(或列表)中获取一个元素。
  6. RETURN_VALUE 操作用于返回栈顶的值。

通过这些操作,我们可以推导出原始 python 函数的执行逻辑。

另一个例子是,当我需要将非结构化数据转换为结构化格式时。比如,我在处理某个项目时,需要获取一份包含作者姓名的书名列表。我在网上找到了一些非结构化的数据,并请 LLM 帮我格式化这些数据

ChatGPT 对话记录 - 请 LLM 帮我格式化这些数据

对话记录

🧑‍🦰User

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
Extract the book name and author as a JSON:

1

To Kill a Mockingbird To Kill a Mockingbird
by Harper Lee
4.26 avg rating — 6,088,504 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
2

1984 1984
by George Orwell
4.19 avg rating — 4,565,319 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
3

The Lord of the Rings The Lord of the Rings
by J.R.R. Tolkien
4.53 avg rating — 673,468 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
4

The Catcher in the Rye The Catcher in the Rye
by J.D. Salinger
3.80 avg rating — 3,507,482 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
5

The Great Gatsby The Great Gatsby
by F. Scott Fitzgerald
3.93 avg rating — 5,205,572 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
6

The Lion, the Witch and the... The Lion, the Witch and the Wardrobe (Chronicles of Narnia, #1)
by C.S. Lewis
4.24 avg rating — 2,783,610 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
7

Lord of the Flies Lord of the Flies
by William Golding
3.69 avg rating — 2,891,608 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
8

Animal Farm Animal Farm
by George Orwell
3.99 avg rating — 3,858,494 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
9

Catch-22 Catch-22
by Joseph Heller
3.99 avg rating — 829,250 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
10

The Grapes of Wrath The Grapes of Wrath
by John Steinbeck
4.01 avg rating — 903,092 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
11

Gone with the Wind Gone with the Wind
by Margaret Mitchell
4.31 avg rating — 1,205,620 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
12

Slaughterhouse-Five Slaughterhouse-Five
by Kurt Vonnegut Jr.
4.10 avg rating — 1,365,298 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
13

One Flew Over the Cuckoo’s ... One Flew Over the Cuckoo’s Nest
by Ken Kesey
4.20 avg rating — 730,303 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
14

A Clockwork Orange A Clockwork Orange
by Anthony Burgess
really liked it 4.00 avg rating — 709,514 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
15

Lolita Lolita
by Vladimir Nabokov
3.88 avg rating — 846,130 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
16

Are You There God? It’s Me,... Are You There God? It’s Me, Margaret
by Judy Blume (Goodreads Author)
3.94 avg rating — 238,479 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
17

Watchmen Watchmen
by Alan Moore (Goodreads Author)
4.38 avg rating — 556,331 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
18

Atonement Atonement
by Ian McEwan (Goodreads Author)
3.94 avg rating — 517,957 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars
19

Things Fall Apart (The Afri... Things Fall Apart (The African Trilogy, #1)
by Chinua Achebe
3.73 avg rating — 369,857 ratings
Want to Read
Rate this book
1 of 5 stars2 of 5 stars3 of 5 stars4 of 5 stars5 of 5 stars

🤖 Assistant

这里是以 JSON 格式提取的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[
{"book_name": "To Kill a Mockingbird", "author": "Harper Lee"},
{"book_name": "1984", "author": "George Orwell"},
{"book_name": "The Lord of the Rings", "author": "J.R.R. Tolkien"},
{"book_name": "The Catcher in the Rye", "author": "J.D. Salinger"},
{"book_name": "The Great Gatsby", "author": "F. Scott Fitzgerald"},
{"book_name": "The Lion, the Witch and the Wardrobe (Chronicles of Narnia, #1)", "author": "C.S. Lewis"},
{"book_name": "Lord of the Flies", "author": "William Golding"},
{"book_name": "Animal Farm", "author": "George Orwell"},
{"book_name": "Catch-22", "author": "Joseph Heller"},
{"book_name": "The Grapes of Wrath", "author": "John Steinbeck"},
{"book_name": "Gone with the Wind", "author": "Margaret Mitchell"},
{"book_name": "Slaughterhouse-Five", "author": "Kurt Vonnegut Jr."},
{"book_name": "One Flew Over the Cuckoo’s Nest", "author": "Ken Kesey"},
{"book_name": "A Clockwork Orange", "author": "Anthony Burgess"},
{"book_name": "Lolita", "author": "Vladimir Nabokov"},
{"book_name": "Are You There God? It’s Me, Margaret", "author": "Judy Blume"},
{"book_name": "Watchmen", "author": "Alan Moore"},
{"book_name": "Atonement", "author": "Ian McEwan"},
{"book_name": "Things Fall Apart (The African Trilogy, #1)", "author": "Chinua Achebe"}
]

又比如,最近我在写一篇博客文章,讨论我如何破解了一种防御机制,并希望展示我更改的代码的完整 diff。所以我粘贴了(1)diff,(2)之前 HTML 化一个 diff 的示例,并请 LLM 以相同的格式生成该 diff

ChatGPT 对话记录 - 请 LLM 以相同的格式生成该 diff

对话记录

🧑‍🦰User

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
以下是我希望你对比数据时使用的格式:

<div style="background: #222; margin: 1em; padding: .5em">
<p class="p1"><span class="s1"><b>diff --git a/model.py b/model.py</b></span></p>
<p class="p1"><span class="s1"><b>index b8ae9f8..243edd6 100644</b></span></p>
<p class="p1"><span class="s1"><b>--- a/model.py</b></span></p>
<p class="p1"><span class="s1"><b>+++ b/model.py</b></span></p>
<p class="p1"><span class="s2">@@ -7,7 +7,7 @@</span><span class="s1"> class DefenseWrapper(nn.Module):</span></p>
<p class="p1"><span class="s1"><span class="Apple-converted-space"> </span>model = Defense(eps=eps, wave=wave, use_rand=use_rand, n_variants=n_variants)</span></p>
<p class="p1"><span class="s1"><span class="Apple-converted-space"> </span>self.core = model</span></p>
<p class="p1"><span class="s1"><span class="Apple-converted-space"> </span>self.base_model = base_model</span></p>
<p class="p2"><span class="s1">-<span class="Apple-converted-space"> </span>self.transform = BPDAWrapper(lambda x, lambda_r: model.transform(x, lambda_r).float())</span></p>
<p class="p3"><span class="s1">+<span class="Apple-converted-space"> </span>self.transform = &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(lambda x, lambda_r: model\
.transform(x, lambda_r).float())</span></p>
<p class="p4"><span class="s1"><span class="Apple-converted-space"> </span></span></p>
</div>


format this diff:
diff --git a/core/defenses/sabre.py b/core/defenses/sabre.py
index 319aebd..69fec01 100644
--- a/core/defenses/sabre.py
+++ b/core/defenses/sabre.py
@@ -61,7 +61,7 @@ class Sabre(nn.Module):
def precision_blend(self, x):
if self.eps > 0:
precision = max(min(-int(math.floor(math.log10(abs(1.25 * self.eps)))) - 1, 1), 0)
- x = self.diff_round(x, decimals=precision)
+ x = BPDAWrapper(lambda x: self.diff_round(x, decimals=precision))(x)
return x

🤖 Assistant

这是按照要求格式化的对比内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
<div style="background: #222; margin: 1em; padding: .5em">
<p class="p1"><span class="s1"><b>diff --git a/core/defenses/sabre.py b/core/defenses/sabre.py</b></span></p>
<p class="p1"><span class="s1"><b>index 319aebd..69fec01 100644</b></span></p>
<p class="p1"><span class="s1"><b>--- a/core/defenses/sabre.py</b></span></p>
<p class="p1"><span class="s1"><b>+++ b/core/defenses/sabre.py</b></span></p>
<p class="p1"><span class="s2">@@ -61,7 +61,7 @@</span><span class="s1"> class Sabre(nn.Module):</span></p>
<p class="p1"><span class="s1"><span class="Apple-converted-space">        </span>def precision_blend(self, x):</span></p>
<p class="p1"><span class="s1"><span class="Apple-converted-space">        </span>if self.eps > 0:</span></p>
<p class="p1"><span class="s1"><span class="Apple-converted-space">        </span>precision = max(min(-int(math.floor(math.log10(abs(1.25 * self.eps)))) - 1, 1), 0)</span></p>
<p class="p2"><span class="s1">-<span class="Apple-converted-space">        </span>x = self.diff_round(x, decimals=precision)</span></p>
<p class="p3"><span class="s1">+<span class="Apple-converted-space">        </span>x = BPDAWrapper(lambda x: self.diff_round(x, decimals=precision))(x)</span></p>
<p class="p4"><span class="s1"><span class="Apple-converted-space"> </span></span></p>
</div>

按照你设置的样式,这些内容应该能正确显示。

再举一个例子,作为我工作的一部分,我经常需要为我使用的资源生成引用。Google Scholar 对于论文引用生成非常方便,我可以直接复制粘贴引用信息。但引用网页时会稍显繁琐。最近,我开始请 LLM 帮我生成引用。(当然,我会检查这些引用是否正确!)

ChatGPT 对话记录 - 请 LLM 帮我生成引用

对话记录

🧑‍🦰User

1
2
3
在 LaTeX 中引用此博客文章:

《On the Sizes of OpenAI API Models》 - 2021年5月24日,作者:Leo Gao,https://blog.eleuther.ai/gpt3-model-sizes/

🤖 Assistant

如果你使用的是 BibTeX,你可以按以下方式引用 Leo Gao 撰写的这篇博客文章《On the Sizes of OpenAI API Models》:

1
2
3
4
5
6
7
@misc{gao2021sizes,
title={On the Sizes of OpenAI API Models},
author={Gao, Leo},
year={2021},
howpublished={\url{https://blog.eleuther.ai/gpt3-model-sizes/}},
note={Accessed: [insert current date here]}
}

在这个 BibTeX 引用中:

  • 使用 @misc 因为这是一个在线资源,不属于传统的文献类别如 @article@book
  • title 字段包含博客文章的标题。
  • author 字段列出作者的姓名。
  • year 字段表示出版年份。
  • howpublished 字段用于提供博客文章的 URL。
  • note 字段可以用来注明你访问该博客文章的日期。

请将 [insert current date here] 替换为你实际访问博客文章的日期。

我可以举出许多类似的例子。但我想你已经明白了这一点。

我完全理解,这类任务可能会让人觉得简单过头了。但请记住,五年前的模型连拼凑一个连贯段落都很困难,更不用说帮你解决整个问题了。

让每个用户成为“高级用户”

当你看到别人使用某个工具,而他们的熟练程度不及你时,这可能会让你感到无奈。你会发现,他们在完成一项任务时,可能花费几分钟甚至几个小时,而这项任务本可以通过某种宏或并行应用来自动化处理。

然而,学习如何执行这些操作需要时间和挑战。

举个例子,最近我在尝试编写一个 python 程序,用来处理来自 Apple Lisa 键盘的键盘输入。我在网上找到了一些用 C 语言编写的代码,其中包含许多类似 #define KEYNAME key_code 的语句,我希望将这些语句转换为一个将整数代码映射到对应字符串的 python 字典。

我是一名 Emacs 用户,知道如何在 Emacs 中解决这个问题。其实,这并不算太难。以下是我刚刚记录下来的按键操作,它将产生这一效果:

C-h C-s #def [enter] M-f [delete] C-d M-f
C-[space] M-f C-w C-a C-y : “ M-f “, C-g C-] } C-[ {

而对于我来说,这几乎是自然而然的事情,因为到现在为止,我已经花了人生的一半时间来熟练掌握 Emacs,以至于这对我来说是自然的。你知道吗?即使现在我在编辑器中集成了 LLM,我仍然会输入什么?

C-h C-h rewrite these #defines to a dictionary of {keycode: string, …}

然后突然间,文本就在我眼前被重写了!

在这样的情况下,我认为 LLMs 对非专家的潜在实用性甚至比对专家更高。模型提高了所有人的起点,如果你以前完全不能做什么,现在突然间你可以做得更多。

作为 API 参考

真正的程序员在想知道一个工具如何工作时,会去阅读参考手册。但我是个懒惰的程序员;我只想要有人直接告诉我答案。所以现在我会问语言模型。

当我向一些人展示这些例子时,他们有些防御性地说:“LLM 并没有做什么是你用现有工具做不到的!”你知道吗?他们说得对。但你用搜索引擎能做到的事情,完全可以用一本实体书做到,同样你用实体书能做到的事情,也可以通过阅读源码做到。

但每一个方法都比前一个更容易。当事情变得更容易时,你会更频繁地做它,并且以不同的方式去做。

所以这就是我问“什么 $thing 给出所有剩余参数”的情景,并得到了答案。(紧接着是另一个“如何使用这个东西”的问题!)

ChatGPT 对话记录 - 什么 $thing 给出所有剩余参数

对话记录

🧑‍🦰User

1
在 bash 中,哪个 `$` 符号代表所有剩余的参数?

🤖 Assistant

在 Bash 中,特殊参数 $@ 会扩展为传递给脚本或函数的所有位置参数,从第一个开始。这包括所有未被分配给其他变量的参数,因此 $@ 实际上给出了所有剩余的参数。

下面是一个示例来说明这一点:

1
2
3
4
5
#!/bin/bash

# This script prints all the arguments passed to it.
echo "The script was called with $# arguments."
echo "The remaining arguments are: $@"

如果您使用命令 ./script.sh foo bar baz 运行此脚本,它将输出:

1
The remaining arguments are: foo bar baz

🧑‍🦰User

1
如何使用 `iperf`?

🤖 Assistant

iperf 是一个命令行工具,它允许您通过在网络上传输和接收数据来测量网络性能。以下是使用 iperf 的基本步骤:

  1. 在您的系统上安装 iperf:您通常可以使用系统的包管理器安装 iperf。例如,在 Ubuntu 上,您可以运行 sudo apt-get install iperf 来安装 iperf

  2. 选择一个服务器:您需要选择一个服务器来测试网络性能。服务器应安装了 iperf 并且可以通过网络访问。您需要知道服务器的 IP 地址或主机名。

  3. 启动服务器:在服务器上,运行命令 iperf -s 启动 iperf 服务器。它将监听来自 iperf 客户端的连接。

  4. 运行客户端:在客户端上,运行命令 iperf -c <server-ip> 连接到 iperf 服务器并开始网络测试。将 <server-ip> 替换为服务器的 IP 地址或主机名。

  5. 查看结果:测试完成后,iperf 将显示测试结果,包括吞吐量或每秒传输的数据量。

以下是运行 iperf 的示例:

在服务器上:

1
2
3
4
------------------------------------------------------------
Server listening on TCP port 5001
TCP window size: 85.3 KByte (default)
------------------------------------------------------------

在客户端上:

1
2
3
4
5
6
7
------------------------------------------------------------
Client connecting to 192.168.1.100, TCP port 5001
TCP window size: 85.3 KByte (default)
------------------------------------------------------------
[ 3] local 192.168.1.101 port 56916 connected with 192.168.1.100 port 5001
[ ID] Interval Transfer Bandwidth
[ 3] 0.0-10.0 sec 1.09 GBytes 937 Mbits/sec

在此示例中,iperf 报告网络吞吐量在测试期间(10 秒)为 937 兆比特每秒(Mbps)。

或者,当我想知道如何在 LaTeX 中将文本变红时,我不再搜索或阅读文档,而是直接问模型。

当我想知道各种 GDB 命令在 LLDB 中的等价命令时,我会做同样的事情

ChatGPT 对话记录 - 各种 GDB 命令在 LLDB 中的等价命令

对话记录

🧑‍🦰User

1
lldb 中 "i r" 的等效命令

🤖 Assistant

在 LLDB 中,i r 命令是 info registers 的缩写,用于显示 CPU 寄存器的当前值。该命令在调试时常用于检查程序在某一特定执行点的寄存器状态。

在 LLDB 中,等效的命令是 register read 或简写为 reg read。这个命令同样会显示当前线程和帧上下文中的 CPU 寄存器值。

以下是使用该命令的方法:

  • 显示所有寄存器:register readreg read
  • 显示特定寄存器(如 rax):register read raxreg read rax

这些命令在调试过程中非常关键,尤其是在需要理解程序在特定断点或崩溃后的底层状态时。

🧑‍🦰User

1
在 lldb 中打印变量的地址

🤖 Assistant

在 LLDB 中打印变量地址

在 LLDB 中,您可以通过几种方式来打印变量的地址。最简单的方法是使用带有 -L 标志的 frame variable 命令,它可以显示变量数据所在的内存地址。具体操作如下:

  1. 使用带 -L 标志的 frame variable 命令:

此命令将显示指定变量的内存地址。

  1. **使用取地址操作符 (&)**:

或者

这些命令使用 C/C++ 语言的取地址操作符 (&) 来获取变量的地址。pprint 的缩写,exprexpression 的缩写。这两个命令都会在当前帧上下文中计算表达式。

请记住,只有在变量处于当前程序执行上下文中的作用域内时,您才能获取它的地址。如果变量超出了作用域,LLDB 将无法找到它的地址。

或者当我想知道某个 find 命令是如何工作的

ChatGPT 对话记录 - 某个 find 命令是如何工作的

对话记录

🧑‍🦰User

1
2
3
下面这个命令的原理:

find . -name '*txt' -exec bash -c ' mv $0 ${0/\ZZ_/}' {} \;

🤖 Assistant

这个命令用于从当前目录及其子目录中的所有 .txt 文件名中移除 “ZZ_” 前缀。我们来逐步分析这个命令的每个部分:

  1. find . -name '*txt'find 命令用于在目录中根据条件搜索文件。在这里,它会搜索当前目录(.)及其子目录中所有文件名以 ‘txt’ 结尾的文件(使用通配符模式 *txt)。

  2. -exec:这个选项指定 find 命令在找到每个文件后,应该执行接下来的命令。

  3. bash -c '...':这个部分启动一个新的 Bash shell 并执行引号中的命令(传递给 -c 的参数)。引号内的命令会针对 find 命令找到的每个文件运行。

  4. mv $0 ${0/\ZZ_/}:这是新 Bash shell 针对每个文件执行的命令。mv 是用于移动(或重命名)文件的命令。$0 是对当前文件的引用,而 ${0/\ZZ_/} 通过参数扩展将文件名中的 “ZZ_” 替换为空字符串,这样就移除了 “ZZ_” 前缀。

  5. {}:这是一个占位符,find 会将其替换为找到的每个文件的路径,并将其作为参数传递给 Bash shell,Bash shell 会用作 $0

  6. \;:这表示 -exec 命令的结束,反斜杠用于转义分号。

总结一下,该命令会查找当前目录及其子目录中的所有 .txt 文件,并移除它们文件名中的 “ZZ_” 前缀。

🧑‍🦰User

1
我运行了这个命令,但它没有改变文件的修改日期。为什么?

🤖 Assistant

当您运行这个命令时,它只是通过移除 “ZZ_” 前缀来重命名文件,但并不会改变文件的内容。在大多数文件系统中,只有当文件内容被更改时,文件的修改日期(mtime)才会更新,而重命名文件不会触发这一更新。

因为文件内容没有变化,所以它的修改日期也不会更新。如果您希望重命名后更新文件的修改日期,可以使用 touch 命令。您可以将命令修改为包含 touch,如下所示:

1
find . -name '*txt' -exec bash -c 'mv $0 ${0/\ZZ_/}; touch ${0/\ZZ_/}' {} \;

这样不仅会重命名文件,还会将重命名后的文件的修改日期更新为当前日期和时间。

或者如何使用 lpr

ChatGPT 对话记录 - 如何使用 lpr

对话记录

🧑‍🦰User

1
在 macOS 13 上如何直接将一个 `.ps` 文件发送到打印机,而不打印出 PostScript 命令?

🤖 Assistant

在 macOS 13 上使用 lpr 命令将 .ps(PostScript)文件直接发送到打印机而不是打印 PostScript 命令,可以按以下步骤进行:

  1. 确保打印机支持 PostScript:并非所有打印机都能直接处理 PostScript 文件。您需要检查打印机的规格以确保它支持 PostScript。

  2. 打开终端:您可以在 /Applications/Utilities/ 文件夹中找到终端应用程序,或使用 Spotlight 搜索。

  3. 导航到文件所在的目录:使用 cd 命令切换到包含 .ps 文件的目录。例如,如果文件在桌面上,可以使用:

1
cd ~/Desktop
  1. 使用 lpr 打印文件:用 lpr 命令将 PostScript 文件发送到打印机。例如,如果文件名为 document.ps,可以使用:
1
lpr -P printer_name document. Ps

printer_name 替换为打印机的实际名称。您可以在系统偏好设置的“打印机与扫描仪”中找到打印机名称。

  1. 检查打印任务:确保文档在打印机上正常打印。您可以查看打印队列,确认任务已接收并正在打印。

如果没有使用 -P printer_name 指定打印机,lpr 命令会将任务发送到默认打印机。确保默认打印机是您希望使用的那一台。

通过这种方式,您指示系统直接将 PostScript 文件发送到打印机,打印机会正确解释并打印文件内容,而不是输出文件的文本内容。

🧑‍🦰User

1
如果打印机只是输出 `.ps` 文件的文本内容而不是渲染后的文档,如何告诉打印机它是一个 PostScript 文件?

🤖 Assistant

如果您的打印机输出的是 .ps 文件的文本内容而不是渲染后的 PostScript 文档,那么可能是因为打印机将输入当作普通文本。要确保打印机将文件识别为 PostScript 文件,您需要指定正确的内容类型。以下是如何指定内容类型来确保 .ps 文件被识别为 PostScript 文件的方法:

  1. 打开终端:进入 /Applications/Utilities/ 并打开终端。

  2. 导航到文件所在的目录:使用 cd 命令切换到包含 .ps 文件的目录。

  3. 使用 lpr-o 选项:通过 -o 选项指定文件类型为 PostScript。例如,如果文件名为 document.ps,并且您希望将其打印到默认打印机,可以使用:

1
lpr -o document-format=application/postscript document. Ps

如果需要指定特定的打印机,可以使用 -P 选项和打印机名称:

1
2
Lpr -P printer_name -o document-format=application/postscript document. Ps

printer_name 替换为打印机的实际名称。

使用 -o document-format=application/postscript 选项明确告诉打印系统文件是 PostScript 文件,这样有助于确保文件正确打印,而不是仅输出其文本内容。

确保您的打印机支持 PostScript。如果不支持,打印机可能无法正确解释 PostScript 命令,导致问题。在这种情况下,您需要将 .ps 文件转换为打印机支持的格式(如 PDF)后再进行打印。

🧑‍🦰User

1
使用 `-o raw` 选项解决了问题,为什么?

🤖 Assistant

使用 lpr 命令中的 -o raw 选项解决问题是因为该选项告诉打印机将文件作为原始数据处理,绕过任何由打印驱动程序或计算机打印系统进行的处理或解释。通过使用 -o raw,实际上您是在指示系统直接将 .ps(PostScript)文件发送到打印机,而不尝试进行解释或转换。

以下是 -o raw 有效的原因:

  • 由打印机直接解释:如果打印机支持 PostScript,它可以直接处理 PostScript 命令。通过以原始模式发送数据,打印机能够直接接收并处理 PostScript 命令,从而正确渲染和打印文档。

  • 绕过驱动程序处理:通常,打印系统可能会根据文件类型或驱动程序设置尝试解释或转换文件,如果系统未能正确识别或处理 PostScript 数据,则可能导致问题。以原始模式发送数据可以绕过这些处理机制,避免错误解释或转换。

  • 保留文件内容:由于文件按原样发送,没有任何内容被打印系统修改或错误格式化的风险。这对于 PostScript 文件尤其重要,因为内容的精确渲染至关重要。

使用 -o raw 选项特别适合处理打印机能够直接识别的文件类型,如 PostScript。这确保了文件按原样打印,没有不必要的修改或转换。

Or how to rebind some latex commands.

ChatGPT 对话记录 - how to rebind some latex commands

对话记录

🧑‍🦰User

1
将 `\citep` 命令设为调用 `\cite`

🤖 Assistant

要让 LaTeX 中的 \citep 命令像 \cite 一样工作,您可以重新定义 \citep 命令,使其调用 \cite 命令。可以在 LaTeX 文档的导言部分添加以下代码行来实现,在导入必要的引用包(如 natbib 或其他文献管理包)后:

1
\let\citep=\cite

这实际上是我最常用 LLM 的方式之一。我不能提供更多类似的示例链接,是因为我已经在 Emacs 和 Shell 中内置了查询 LLM 的工具。因此,90% 的情况下,我想执行这些任务时,根本不需要离开编辑器。

搜索难以找到的信息

过去,搜索互联网内容需要一定的技巧。你需要仔细选择查询中包含的词语,是用复数、单数还是过去式?哪些词需要排除?我需要 X AND Y,还是 X OR Y?

但这种情况已经不再常见。我不记得上次在 Google 中使用 OR 是什么时候了,也不记得上次使用减号 (-) 来排除结果是什么时候了。大多数情况下,现在你只需输入想找到的内容,搜索引擎就能找到。

然而,搜索引擎仍未完全支持自然语言查询。使用搜索引擎时,仍然感觉像是在玩“反向 Jeopardy”游戏,试图使用答案中的关键词而非问题。这是一种我们几乎忘记曾经学习过的技能。

对于如今的一些简单任务(并且随着时间的推移,越来越多的任务),语言模型显得更加出色。我只需输入 “我知道 + 对应于 __add__,那么 ~ 是什么”,模型就会告诉我答案是 __inv__。

ChatGPT 对话记录 - 我知道 + 对应于 __add__,那么 ~ 是什么

对话记录

🧑‍🦰User

1
我知道 `+` 对应的是 `add` 方法,那 `~` 是什么?

🤖 Assistant

在 python 中,~ 运算符对应的是 __invert__ 方法,用于进行按位取反操作。这意味着它会将整数的每个位翻转,即将所有 0 变为 1,将所有 1 变为 0

举个简单的例子来说明 __invert__ 是如何工作的:

1
2
3
4
5
6
7
8
9
10
11
12
class InvertExample:
def __init__(self, value):
self.value = value

def __invert__(self):
return ~self.value

# Create an instance of InvertExample with a value of 10
example = InvertExample(10)

# Use the ~ operator, which calls the __invert__ method
print(~example) # Output: -11

在上面的例子中,~ 运算符调用了 example 对象上的 __invert__ 方法,并返回了整数 10 的按位取反结果。

我们来看看具体的二进制表示:

  • 10 的二进制表示是 00001010
  • 00001010 的按位取反结果是 11110101

在 python 使用的补码表示法中,11110101 对应的十进制值是 -11

这种问题在标准搜索引擎中很难找到答案。虽然我知道有办法通过搜索找到答案,可能通过输入“python documentation metaclass “add“”然后在页面中搜索 ~ 可以得到结果。但你知道还有什么更简单吗?直接向 LLM 提问。

每次这样做可能只节省几十秒,但当你在解决编码任务时,需要同时记住许多事情,能够随时提出问题并获得连贯的答案,真是令人惊喜。

当然,这并不是说现在的模型在这方面已经完美。语言模型只有在网上某些内容被足够多次提及时才会掌握。“足够多次”的标准取决于模型,因此我仍然需要考虑是问模型还是查找互联网。但模型只会越来越好。

或者,当我遇到随机崩溃时,我会将错误信息输入模型并请求解释,就像我在这里做的那样,当我输入 zsh no matches found “Remote wildcard transfer issue” 时。

ChatGPT 对话记录 - zsh no matches found

对话记录

🧑‍🦰User

1
Zsh no matches found "Remote wildcard transfer issue"

🤖 Assistant

当你在 Zsh 中使用通配符(如 *)时,如果没有找到匹配的文件或目录,Zsh 会显示 “No matches found” 错误信息。

在处理远程文件传输(例如使用 scprsync)时,Zsh 可能会在本地解析通配符,而不是将其发送到远程服务器。这就导致了找不到匹配项的错误。

你可以通过以下方法来解决这个问题:

  1. 使用引号:在远程通配符周围加上单引号,防止 Zsh 在本地进行解析。

    1
    scp ' user@remote_host :/path/to/files/*. Txt' /local/destination/
  2. 使用反斜杠:在通配符前加反斜杠以转义它,这样 Zsh 就不会在本地解析它。

1
scp user@remote_host :/path/to/files/\*. Txt /local/destination/
  1. 临时关闭通配符扩展:可以用 setopt 命令临时禁用通配符扩展,执行命令后再重新启用。
1
2
3
Setopt NO_GLOB
scp user@remote_host :/path/to/files/*. Txt /local/destination/
Unsetopt NO_GLOB
  1. 切换到其他 shell:如果你熟悉其他 shell,可以暂时切换到 Bash 等,可能在这种情况下会更好处理通配符。
1
bash -c 'scp user@remote_host :/path/to/files/*. Txt /local/destination/'

使用 rsync 的例子

如果你使用的是 rsync,确保通配符加上引号:

1
rsync -avz ' user@remote_host :/path/to/files/*. Txt' /local/destination/

这些步骤应该能帮助解决 Zsh 在进行远程文件传输时找不到匹配项的问题。

又例如,去年我在写一篇博客文章时,我想让第一个词的首字母放大,并让其余的文字环绕在它周围,像我刚刚在这句话中做的那样。这种效果被称为 _drop cap_。但当时我并不知道这个术语。我只是知道我想要的效果,所以我问语言模型“我希望文本像一本精美的书一样环绕在 O 字母周围”,模型给了我正是我想要的答案

ChatGPT 对话记录 - 文本像一本精美的书一样环绕在 O 字母周围

对话记录

🧑‍🦰User

1
在 HTML 中,如何让 "Once upon a time" 的 "O" 变大?

🤖 Assistant

你可以通过使用 HTML 的 style 属性并结合 font-size 来调整 “O” 的大小。下面是一个简单的例子:

1
2
<p><span style="font-size: 60px;">O</span>nce upon a time</p>

在上面的 HTML 代码中,”O” 将以 60 像素的字体大小显示,而其余文字将使用默认字体大小。

如果你想用相对大小而不是绝对大小,可以使用 em 单位,它是相对于当前字体大小的:

1
<p><span style="font-size: 2em;">O</span>nce upon a time</p>

在这种情况下,”O” 的大小将是当前字体大小的两倍。

根据你的设计需求,调整字体大小,使其达到理想的效果。如果 60 px 或 2 em 不合适,你可以继续调整,直到满意为止。

🧑‍🦰User

1
我希望它看起来像一本书中的首字母大写,文字环绕在 "O" 周围

🤖 Assistant

要实现文字环绕首字母 “O” 的效果,你可以使用 CSS 的 float 属性,再加上一些内边距来达到这个效果。下面是具体的做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html>
<html>
<head>
<style>
.dropcap {
Float: left;
Font-size: 5 em;
Line-height: 0.65 em;
Padding: 0.1 em 0.2 em 0.1 em 0;
}
</style>
</head>
<body>

<p><span class="dropcap">O</span>n a sunny day in the middle of summer, a king decided to take a walk around his kingdom...</p>

</body>
</html>

在这段代码中,我们为 “O” 设置了 dropcap 类,并定义了以下样式:

  • float: left; 让大 “O” 靠左显示,其他文字环绕它。
  • font-size: 5em; 将 “O” 的字体大小设为正常文本的五倍。
  • line-height: 0.65em; 控制行间距,让效果更自然。
  • padding: 0.1em 0.2em 0.1em 0; 为 “O” 添加了一些空白,以便与其他文字分开。

你可以根据需要调整这些数值,以实现理想的视觉效果。

这个任务属于“我之所以做它完全是因为 LLMs”的另一类任务——如果让我自己花时间研究如何实现它,我根本不会觉得值得。但因为我可以直接问模型,所以我就去做了,这让我的帖子更漂亮了一些。

解决临时任务

程序通常分为两类。第一类是需要做得_尽善尽美_的程序;它们会存在较长时间,因此代码的整洁性非常重要,因为你可能需要维护它们好几年。另一类是只存在短短 25 秒的程序;它们帮助你完成某个任务,然后马上被丢弃。

我明白,第二类程序常常会演变成第一类程序。

在这种情况下,当我完全不在意代码质量时,而且这些程序是完全独立的,我现在几乎完全使用 LLMs 来编写它们。

需要注意的是:这些任务大多属于你看到后会说“就这样?”的那种。但正如我之前所说,我每天可以花在一个项目上的时间是有限的。如果我能够节省时间和精力,去编写一个我不会再用的程序,我会毫不犹豫地这样做。

最常见的例子之一是帮助我生成可视化某些数据的图表,这些数据是我研究实验的结果。我有许多类似的例子,可能有几十个。它们看起来基本相同,所以这里展示其中一个:

ChatGPT 对话记录 - 帮助我生成可视化某些数据的图表

对话记录

🧑‍🦰User

1
2
3
4
5
6
7
8
9
10
11
我有一个名为 "ct" 的文件,内容如下:

B'7' 142505778
B'6' 143132100
B'5' 143145577
B'1' 143155168
B'4' 143159985
B'0' 143163386
B'2' 143184765

请写一个程序,用 matplotlib 绘制频率图表,按频率排序,x 轴采用对数刻度,图表大小设为 (4,3),x 轴标签为 "Token",y 轴标签为 "Frequency"。

🤖 Assistant

在 python 中使用 matplotlib 绘制这些数据,需要先解析数据,计算频率,然后进行绘图。以下是实现这一过程的 python 代码。假设这些数据已保存到一个名为 “ct.txt” 的文件中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# Load the data
data = pd.read_csv('ct.txt', sep=' ', names=['Token', 'Frequency'])

# Sort the data based on the frequency
data = data.sort_values('Frequency')

# Log-scale the frequency
data['Frequency'] = np.log10(data['Frequency'])

# Plot the data
plt.figure(figsize=(4,3))
plt.bar(data['Token'], data['Frequency'])
plt.xlabel('Token')
plt.ylabel('Frequency')
plt.xscale('log')
plt.title('Token Frequency')
plt.show()

这个脚本会将文件 ct.txt 读取到一个 pandas DataFrame 中,并按照频率对数据进行排序。接着,它计算频率的 log 10 以实现对数刻度。最后,它用 matplotlib 来绘制数据。通过 xscale 函数,x 轴会设置为对数刻度。

请注意,文件中的 ‘Token’ 列似乎包含字节格式的内容。如果您想在绘图前去除 ‘Token’ 值中的 b' ' 字符,可以通过调整代码来实现。

🧑‍🦰User

1
No pandas

🤖 Assistant

当然!如果您不想使用 pandas,也可以用 python 内置库来实现。以下是对应的 python 代码:

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

import matplotlib.pyplot as plt
import numpy as np

# Load the data
with open("ct.txt", "r") as file:
data = file.readlines()

tokens = []
frequencies = []

for line in data:
token, freq = line.split()
# Remove b' ' from the token
token = token[2:-1]
tokens.append(token)
frequencies.append(int(freq))

# Sort the data based on the frequency
sorted_indices = np.argsort(frequencies)
tokens = [tokens[i] for i in sorted_indices]
frequencies = [frequencies[i] for i in sorted_indices]

# Log-scale the frequency
log_frequencies = np.log10(frequencies)

# Plot the data
plt.figure(figsize=(4,3))
plt.bar(tokens, log_frequencies)
plt.xlabel('Token')
plt.ylabel('Frequency (log scale)')
plt.xscale('log')
plt.title('Token Frequency')
plt.show()

这个脚本读取文件 ct.txt,逐行解析数据,提取 tokens 及其频率。然后按频率对数据进行排序,再计算频率的 log 10 以实现对数刻度。最后,使用 matplotlib 绘制图表,并用 xscale 函数将 x 轴设为对数刻度。

同样地,这个脚本会在绘图前去除 ‘Token’ 值中的 b' ',如果这些内容不是字节格式,或您想保留它们作为 token 的一部分,可以调整脚本。

另一个类似的例子是,当我有一种格式的数据,而想将其转换为另一种格式的数据。通常这只是一个一次性的任务,完成后,我就会删除生成的脚本。

ChatGPT 对话记录 - 转换为另一种格式的数据

对话记录

🧑‍🦰User

1
在 "data/" 目录中查找所有的 CSV 文件,并将它们合并为一个字典。这些文件有标题,请确保所有标题一致,并移除这些标题。

🤖 Assistant

抱歉,可能有些误会,我无法直接访问您的文件系统。不过,我可以教您如何用 python 来实现这个操作。

要将 “data/“ 目录中的多个 CSV 文件合并到一个字典中,您可以使用 os 模块列出目录中的文件,并通过 pandas 来读取和处理 CSV 文件。具体步骤如下:

  1. 首先,确保您已经安装了 pandas
1
pip install pandas
  1. 使用以下 python 代码,将 “data/“ 目录中的所有 CSV 文件合并为一个字典:
    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
    import os
    import pandas as pd

    path = 'data/'
    csv_files = [f for f in os.listdir(path) if f.endswith('.csv')]

    data_dict = {}

    for file in csv_files:
    file_path = os.path.join(path, file)

    # Assuming all CSVs have a header
    df = pd.read_csv(file_path)

    # Check if data_dict is empty; if yes, add the CSV's header as key
    if not data_dict:
    data_dict = {column: [] for column in df.columns}

    # Check if headers match
    if set(df.columns) != set(data_dict.keys()):
    raise ValueError(f"Headers in {file} don't match with the previous files.")

    # Append the data from the current CSV to the data_dict
    for column in df.columns:
    data_dict[column].extend(df[column].tolist())

    print(data_dict)

如果 CSV 文件的标题不匹配,代码会报错。如果所有操作正常,data_dict 字典将包含所有 CSV 文件的合并数据。

注意:请根据您的具体需求处理可能出现的问题,例如文件丢失、标题不一致等。

🧑‍🦰User

1
哦,原来这些文件没有列标题

🤖 Assistant

如果您的 CSV 文件没有列标题,您可以逐行读取每个文件,并将内容添加到一个列表中。假设这些 CSV 文件没有列标题,我会假定每个 CSV 文件中的列顺序一致。

以下是一个简单的 python 脚本,用于将 “data/“ 目录中所有 CSV 文件的内容合并为一个列表的列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import os

path = 'data/'
csv_files = [f for f in os.listdir(path) if f.endswith('.csv')]

all_data = []

for file in csv_files:
file_path = os.path.join(path, file)

with open(file_path, 'r') as f:
# Read the file line by line
for line in f:
# Assuming the data is comma-separated
# You can strip off any whitespace or newline characters and split by comma
row = line.strip().split(',')
all_data.append(row)

print(all_data)

这个脚本会生成一个列表的列表 (all_data),每个子列表代表 CSV 文件中的一行。如果文件中的数据并不是用逗号分隔的,可以调整 split() 方法来适应。

🧑‍🦰User

1
不要只用 `strip` 和 `split`,请使用 `csv` 模块

🤖 Assistant

当然,使用 python 的 csv 模块处理 CSV 文件更为稳妥,特别是当数据中可能包含逗号或换行符时。以下是如何使用 csv 模块读取并合并文件的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import os
import csv

path = 'data/'
csv_files = [f for f in os.listdir(path) if f.endswith('.csv')]

all_data = []

for file in csv_files:
file_path = os.path.join(path, file)

with open(file_path, 'r') as f:
reader = csv.reader(f)
for row in reader:
all_data.append(row)

print(all_data)

这样可以确保即使数据值中包含特殊字符,或者 CSV 文件中有引用的值,每一行数据也能被正确读取。

但这样的例子还有很多。通常,当我想编写一个非常简单的脚本时,我会直接让 LLM 为我全程编写。例如,这里我让 LLM 为我编写了一个将我的论文朗读出来的脚本,以确保没有愚蠢的语法错误。

ChatGPT 对话记录 - 编写了一个将我的论文朗读出来的脚本

对话记录

🧑‍🦰User

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
以下是如何为文本生成音频的方法

from pathlib import Path
import openai


speech_file_path = Path(file).parent / "speech.mp3"
response = openai.audio.speech.create(
model="tts-1",
voice="alloy",
input="The quick brown fox jumped over the lazy dog."
)
response.stream_to_file(speech_file_path)


取一个文件 a.txt 并将其朗读出来。但有一个限制:一次只能传递 5000 个字符。因此,您可以按以下步骤操作:
(1) 按段落拆分文本(使用 "\n\n" 作为分隔符)
(2) 将段落连接在一起,确保总字符数小于 5000 个
(3) 并行地朗读每个合并后的部分,生成的文件命名为 audio_%04d.mp3

🤖 Assistant

下面是一个脚本,它会读取文件 a.txt,按段落拆分,确保每部分少于 5000 个字符,然后使用 OpenAI API 生成对应的语音文件。这些音频文件将依次命名为 audio_0000.mp3audio_0001.mp3 等。

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

from pathlib import Path
import openai
import concurrent.futures

def read_file(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
return file.read()

def split_text_into_sections(text, max_length=5000):
paragraphs = text.split("\n\n")
sections = []
current_section = []

for paragraph in paragraphs:
if sum(len(p) for p in current_section) + len(paragraph) < max_length:
current_section.append(paragraph)
else:
sections.append("\n\n".join(current_section))
current_section = [paragraph]

if current_section:
sections.append("\n\n".join(current_section))

return sections

def generate_speech(text, output_path):
response = openai.audio.speech.create(
model="tts-1",
voice="alloy",
input=text
)
response.stream_to_file(output_path)

def main():
input_file_path = Path("a.txt")
output_dir = Path(".")
text = read_file(input_file_path)
sections = split_text_into_sections(text)

with concurrent.futures.ThreadPoolExecutor() as executor:
futures = []
for idx, section in enumerate(sections):
output_path = output_dir / f"audio_{idx:04d}.mp3"
futures.append(executor.submit(generate_speech, section, output_path))

concurrent.futures.wait(futures)

if __name__ == "__main__":
main()

解释:

  1. 读取文件read_file 函数会读取 a.txt 文件的内容。
  2. 拆分文本split_text_into_sections 函数将文本按段落拆分,并确保每部分的字符数不超过 5000。
  3. 生成语音generate_speech 函数调用 OpenAI API,将文本转换为语音并保存到指定路径。
  4. 并行执行main 函数会读取文本,将其拆分为多部分,并通过 concurrent.futures.ThreadPoolExecutor 并行生成各部分的语音。

请注意,需要将 "tts-1""alloy" 替换为您实际使用的 OpenAI API 模型和语音设置。确保已安装 openai 包,并正确配置了 API 密钥。

很多时候,当我不太确定自己想要什么时,我会先让模型生成一些初始代码,然后再进行调整。例如,这是一个一次性的任务,我只需要快速处理一些数据。如果是在 2022 年,我可能会花两分钟用 python 写个程序,然后等上几个小时让它跑完,因为只需要运行一次——优化它花的时间比程序跑的时间还长。但现在?我肯定会花同样的两分钟让模型生成 Rust 代码来帮我处理数据

ChatGPT 对话记录 - 让模型生成 Rust 代码来帮我处理数据

对话记录

🧑‍🦰User

1
我有一个文件,其中每一行都是一个 JSON 对象(类似于 jsonl 格式)。每个 JSON 对象都有一个名为 "text" 的字段,以及一些我不关心的其他内容。能否编写一个 python 函数,提取每行的 "text" 字段?

🤖 Assistant

当然!以下是一个 python 函数,它读取一个 JSONL 文件,并从每行中提取 “text” 字段的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import json

def extract_text_from_jsonl(file_path):
extracted_texts = []

with open(file_path, 'r') as file:
for line in file:
json_object = json.loads(line)
if "text" in json_object:
extracted_texts.append(json_object["text"])

return extracted_texts

# Example usage:
file_path = 'your_file.jsonl'
texts = extract_text_from_jsonl(file_path)
print(texts)

'your_file.jsonl' 替换为 JSONL 文件的路径。该函数将返回一个包含每行 “text” 字段值的字符串列表。

或者另一个例子,我请模型为我下载一个数据集,并进行初步处理。这件事对我来说简单吗?是的,可能很简单。但这不是我想要花时间去思考的任务;我更想专注于如何利用这个数据集进行研究。消除这些分心的事情,比仅仅节省几分钟时间更有价值。

ChatGPT 对话记录 - 请模型为我下载一个数据集

对话记录

🧑‍🦰User

1
Can you download and extract 0 to 23098 files at https://storage.googleapis.com/ai2-jackh-mmc4-public/data_v1.1/docs_no_face_shard_{$SHARD}_v 2. Jsonl. Zip in bash

🤖 Assistant

如果要在 Bash 中下载并解压编号从 0 到 23098 的文件,可以编写一个脚本,将 URL 中的 $SHARD 替换为 0 到 23098 之间的数字,然后依次下载并解压这些文件。

以下是 Bash 中的示例脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/bash

for SHARD in {0..23098}
do
# Construct the download URL
url="https://storage.googleapis.com/ai2-jackh-mmc4-public/data_v1.1/docs_no_face_shard_${SHARD}_v2.jsonl.zip"

# Download the zip file
wget $url -O temp.zip

# Check if the file has been downloaded successfully
if [ $? -eq 0 ]
then
# Unzip the file
unzip -o temp.zip -d directory_to_extract_to
echo "Downloaded and extracted shard number: $SHARD"
else
echo "Failed to download shard number: $SHARD"
fi
done

# Remove the temporary zip file
rm temp.zip

directory_to_extract_to 替换为您希望保存解压文件的目录。

注意:这个脚本使用了 wgetunzip,如果您的系统中没有安装这些工具,可以使用包管理器来安装它们。

例如,在 Ubuntu 系统上,可以运行:

1
2
sudo apt update
sudo apt install wget unzip

该脚本假定服务器在文件不存在时会返回错误码。如果服务器返回的是 200 OK 状态并显示错误页面,您需要检查下载的文件内容以确保下载成功。

该脚本将尝试下载并解压 23000 多个文件,这可能需要很长时间,并占用大量带宽和存储空间。请确保您有足够的资源,并且不会违反服务条款。

还有一次,我在编写一个程序,希望用小方块 3 D 打印一些像素化的图像。为此,我需要将 PNG 转换为 STL 文件。这不是项目的重点,只是项目中必须完成的任务之一。所以我请 LLM 帮我完成这个任务。

ChatGPT 对话记录 - 将 PNG 转换为 STL 文件

对话记录

🧑‍🦰User

1
2
3
4
5
6
7
I want to generate an STL file with the following type

Header: solid name

Body: facet normal ni nj nk outer loop vertex v 1 x v 1 y v 1 z vertex v 2 x v 2 y v 2 z vertex v 3 x v 3 y v 3 z endloop endfacet

我需要一个 STL 文件,包含一个六面正方体。请按照以下格式生成这个文件。

🤖 Assistant

一个立方体的 STL 文件包含 12 个三角形(每个面由 2 个三角形组成)。

在生成 STL 文件之前,我们需要确定立方体的尺寸和位置。为了简化,我选择一个中心在原点、边长为 2 个单位的立方体。

以下是 STL 文件的内容:

1
2
3
4
5
6
7
B: ( 1, -1, -1)
C: ( 1, 1, -1)
D: (-1, 1, -1)
E: (-1, -1, 1)
F: ( 1, -1, 1)
G: ( 1, 1, 1)
H: (-1, 1, 1)

Here’s the STL file:

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// Bottom Face
facet normal 0 0 -1
outer loop
vertex -1 -1 -1
vertex 1 -1 -1
vertex 1 1 -1
endloop
endfacet

facet normal 0 0 -1
outer loop
vertex -1 -1 -1
vertex 1 1 -1
vertex -1 1 -1
endloop
endfacet

// Top Face
facet normal 0 0 1
outer loop
vertex -1 -1 1
vertex 1 1 1
vertex 1 -1 1
endloop
endfacet

facet normal 0 0 1
outer loop
vertex -1 -1 1
vertex -1 1 1
vertex 1 1 1
endloop
endfacet

// Left Face
facet normal -1 0 0
outer loop
vertex -1 -1 -1
vertex -1 1 1
vertex -1 -1 1
endloop
endfacet

facet normal -1 0 0
outer loop
vertex -1 -1 -1
vertex -1 1 -1
vertex -1 1 1
endloop
endfacet

// Right Face
facet normal 1 0 0
outer loop
vertex 1 -1 -1
vertex 1 -1 1
vertex 1 1 1
endloop
endfacet

facet normal 1 0 0
outer loop
vertex 1 -1 -1
vertex 1 1 1
vertex 1 1 -1
endloop
endfacet

// Front Face
facet normal 0 -1 0
outer loop
vertex -1 -1 -1
vertex -1 -1 1
vertex 1 -1 1
endloop
endfacet

facet normal 0 -1 0
outer loop
vertex -1 -1 -1
vertex 1 -1 1
vertex 1 -1 -1
endloop
endfacet

// Back Face
facet normal 0 1 0
outer loop
vertex -1 1 -1
vertex 1 1 1
vertex -1 1 1
endloop
endfacet

facet normal 0 1 0
outer loop
vertex -1 1 -1
vertex 1 1 -1
vertex 1 1 1
endloop
endfacet

endsolid cube

每个面的法线方向取决于该面的朝向。每个面由两个三角形构成。这种 STL 文件使用 ASCII STL 格式,可以兼容大多数 STL 查看器和 3D 打印机。如果需要更紧凑的表示,可以使用二进制 STL 格式。

再举一个例子,最近我想用 Docker Compose 来设置一个新项目。我遇到了一些问题,只是想让它尽快运行起来,之后再弄清楚问题所在。所以我反复操作,多次复制错误信息,直到最终得到一个可行的解决方案。查看这个过程

ChatGPT 对话记录 - 多次复制错误信息

对话记录

🧑‍🦰User

I have this docker-compose

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
Version: '2.4'
Services:
Frigate:
Container_name: frigate
Image: madsciencetist/frigate-jetson: jetpack 4.6.1 # this docker image
Privileged: true
Shm_size: "164 mb"
Volumes:
- /etc/localtime:/etc/localtime:ro
- /home/beppe/frigate/:/config
- /home/beppe/frigate/storage:/media/frigate
- /home/beppe/frigate/trt-models:/trt-models
- Type: tmpfs # Optional: 1 GB of memory, reduces SSD/SD Card wear
Target: /tmp/cache
Tmpfs:
Size: 500000000
Ports:
- "50 00:5000"
- "185 54:8554" # RTSP feeds
- "185 55:8555/tcp" # WebRTC over tcp
- "185 55:8555/udp" # WebRTC over udp
Environment:
FRIGATE_RTSP_PASSWORD: "xxx"
Runtime: nvidia
Environment:
YOLO_MODELS: "yolov 7-320"


🤖 Assistant

It seems like there’s a small issue with your docker-compose configuration related to the environment key being duplicated, which might cause some configurations not to be applied as expected. Docker Compose only allows one environment key per service, and you should combine all environment variables under a single environment block. Here’s a corrected version of your docker-compose. Yml:

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
Version: '2.4'
Services:
Frigate:
Container_name: frigate
Image: madsciencetist/frigate-jetson: jetpack 4.6.1
Privileged: true
Shm_size: "164 mb"
Volumes:
- /etc/localtime:/etc/localtime:ro
- /home/beppe/frigate/:/config
- /home/beppe/frigate/storage:/media/frigate
- /home/beppe/frigate/trt-models:/trt-models
- Type: tmpfs
Target: /tmp/cache
Tmpfs:
Size: 500000000
Ports:
- "50 00:5000"
- "185 54:8554" # RTSP feeds
- "185 55:8555/tcp" # WebRTC over tcp
- "185 55:8555/udp" # WebRTC over udp
Environment:
FRIGATE_RTSP_PASSWORD: "xxx"
YOLO_MODELS: "yolov 7-320"
Runtime: nvidia

Here are a few things you might want to check or update:

  1. Volume Configuration: Ensure that the paths you are mapping exist on your host machine and that you have proper permissions set up to allow Docker to access them.

  2. shm_size: You’ve set shm_size to “164 mb”. Make sure this is sufficient for your application’s needs; insufficient shared memory can lead to crashes or malfunctioning of containerized applications, especially those that handle video processing.

  3. Ports Configuration: Your mapping includes standard and specialized ports for web services and streaming protocols. Verify that these ports are open on your host firewall and are not being used by another service.

  4. Runtime: You’re using the nvidia runtime, which is necessary for leveraging GPU resources in Docker containers. Ensure that you have the NVIDIA Docker toolkit properly installed and configured on your host.

  5. Environment Variables: Make sure all necessary environment variables are properly configured for your application’s needs. Combining them into one block as shown above is crucial for Docker Compose to read them correctly.

  6. Privileged Mode: Running containers in privileged mode grants them extensive permissions which could pose a security risk. If possible, it’s advisable to limit the capabilities to what’s strictly necessary.

If you face any specific errors when running this configuration, it would help to provide those details for more targeted troubleshooting!

我还经常先请求一个完整的解决方案,然后再请求改进提示。在这次对话中,我首先请求了一个解析 HTML 的程序,然后进一步请求了有关 API 参考或改进方法的提示。

ChatGPT 对话记录 - 先请求一个完整的解决方案,然后再请求改进提示

对话记录

🧑‍🦰User

1
Write me some beautifulsoup code to find all <li> tags under a <ul> and then extract the text under the first <a> tag after that. So if I have <ul><li><a href="2344.html">Hello There</a>sdfsdf <a>another</a></li></ul> you should return ["Hello there"]

🤖 Assistant

Here’s a Python script using the BeautifulSoup library to achieve your desired functionality:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from bs4 import BeautifulSoup

html = '''
<ul>
<li>
<a href="2344.html">Hello There</a>sdfsdf <a>another</a>
</li>
</ul>
'''

# Initialize BeautifulSoup object with the HTML content
soup = BeautifulSoup(html, 'html.parser')

# Find all the <li> tags under the <ul> tag
li_tags = soup.find('ul').find_all('li')

# Extract the text under the first <a> tag for each <li> and store in a list
result = [li.find('a').text for li in li_tags]

print(result)

Running this script will output:

1
['Hello There']

This code snippet will find all the <li> tags under the <ul> tag, and then extract the text under the first <a> tag for each <li> found, storing the extracted text in the result list.

🧑‍🦰User

1
Can you group the answers by <ul> tag?

🤖 Assistant

Certainly! To group the extracted text by <ul> tag, you can modify the code as follows:

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
from bs4 import BeautifulSoup

html = '''
<ul>
<li><a href="2344.html">Hello There</a>sdfsdf <a>another</a></li>
<li><a href="1234.html">General Kenobi</a></li>
</ul>
<ul>
<li><a href="5678.html">Welcome</a>to the <a>Club</a></li>
<li><a href="9012.html">Nice to Meet</a> You!</li>
</ul>
'''

# Initialize BeautifulSoup object with the HTML content
soup = BeautifulSoup(html, 'html.parser')

# Find all the <ul> tags
ul_tags = soup.find_all('ul')

# Extract the text under the first <a> tag for each <li> within each <ul> and store in a list of lists
result = [[li.find('a').text for li in ul.find_all('li')] for ul in ul_tags]

print(result)


Running this script will output:

1
2
[['Hello There', 'General Kenobi'], ['Welcome', 'Nice to Meet']]

This code snippet will find all the <ul> tags, and then for each <ul> tag, it will extract the text under the first <a> tag for each <li> found within that <ul>. The extracted texts are stored in a list of lists, where each sublist corresponds to a <ul> tag.

🧑‍🦰User

1
How do I find the number of a elements within a specific li?

🤖 Assistant

You can find the number of <a> elements within a specific <li> by using the find_all() method from BeautifulSoup. Here’s an example:

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
from bs4 import BeautifulSoup

html = '''
<ul>
<li>
<a href="2344.html">Hello There</a>sdfsdf <a>another</a>
</li>
<li>
<a href="1234.html">General Kenobi</a>
</li>
</ul>
'''

# Initialize BeautifulSoup object with the HTML content
soup = BeautifulSoup(html, 'html.parser')

# Find the <li> tags under the <ul> tag
li_tags = soup.find('ul').find_all('li')

# Choose a specific <li> tag by its index (e.g., first <li> has index 0)
specific_li = li_tags[0]

# Find the number of <a> elements within the specific <li> tag
num_a_elements = len(specific_li.find_all('a'))

print("Number of <a> elements in the specific <li>:", num_a_elements)

Running this script will output:

1
2
Number of <a> elements in the specific <li>: 2

In this code snippet, we first find all the <li> tags under the <ul> tag. We then choose a specific <li> tag by its index (in this case, the first <li> tag has an index of 0). Next, we find all the <a> elements within the specific <li> tag and use the len() function to count the number of <a> elements.

🧑‍🦰User

1
How do I find a tag by id?

🤖 Assistant

To find an HTML tag by its id attribute using BeautifulSoup, you can use the find() method with the id parameter. Here’s an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from bs4 import BeautifulSoup

html = '''
<div>
<p id="introduction">This is the introduction paragraph.</p>
<p id="body">This is the body paragraph.</p>
<p id="conclusion">This is the conclusion paragraph.</p>
</div>
'''

# Initialize BeautifulSoup object with the HTML content
soup = BeautifulSoup(html, 'html.parser')

# Find the tag with a specific id (e.g., 'body')
tag_with_specific_id = soup.find(id='body')

print(tag_with_specific_id)

Running this script will output:

1
2
<p id="body">This is the body paragraph.</p>

In this code snippet, we initialize a BeautifulSoup object with the given HTML content. Then, we use the find() method with the id parameter to find the tag with a specific id value (in this case, the tag with the id “body”).

🧑‍🦰User

1
How do I filter ul_tags to only those where the previous element was an h3?

🤖 Assistant

To filter ul_tags to only those where the previous element was an <h3> tag, you can use a list comprehension with a conditional statement that checks if the previous element’s tag name is ‘h 3’. Here’s an example:

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
from bs4 import BeautifulSoup

html = '''
<h3>Section 1</h3>
<ul>
<li>Item 1</li>
<li>Item 2</li>
</ul>
<p>Some text</p>
<ul>
<li>Item 3</li>
<li>Item 4</li>
</ul>
<h3>Section 2</h3>
<ul>
<li>Item 5</li>
<li>Item 6</li>
</ul>
'''

# Initialize BeautifulSoup object with the HTML content
soup = BeautifulSoup(html, 'html.parser')

# Find all the <ul> tags
ul_tags = soup.find_all('ul')

# Filter <ul> tags to only those where the previous element was an <h3> tag
filtered_ul_tags = [ul for ul in ul_tags if ul.previous_sibling and ul.previous_sibling.name == 'h3']

for ul in filtered_ul_tags:
print(ul.prettify())

Running this script will output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<ul>
<li>
Item 1
</li>
<li>
Item 2
</li>
</ul>

<ul>
<li>
Item 5
</li>
<li>
Item 6
</li>
</ul>

In this code snippet, we first find all the <ul> tags. Then, we use a list comprehension to filter the <ul> tags to only those where the previous element was an <h3> tag. The conditional statement checks if the previous_sibling exists and if its tag name is ‘h 3’. Finally, we print the filtered <ul> tags.

还有一次,我想跟踪我的电脑在一段时间内的内存和 CPU 使用情况。我可以花几分钟时间找出合适的命令并将它们串联成一个脚本来完成我的需求……或者我可以直接请语言模型为我生成。

ChatGPT 对话记录 - 跟踪我的电脑在一段时间内的内存和 CPU 使用情况

对话记录

🧑‍🦰User

1
在 bash 中每秒将内存和平均 CPU 使用情况写入文件

🤖 Assistant

当然可以!如果你希望在 bash 脚本中每秒记录内存和 CPU 使用情况,可以使用 freevmstat 工具。以下是一个实现该操作的脚本:

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
#!/bin/bash

# Name of the output file
OUTPUT_FILE="usage_data.txt"

# Write headers to the output file
echo "Timestamp, Memory Usage (MB), Average CPU Usage (%)" > $OUTPUT_FILE

# Infinite loop to gather data every second
while true; do
# Get the current timestamp
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')

# Use 'free' command to get memory usage in MB
MEMORY_USAGE=$(free -m | awk 'NR==2{print $3}')

# Use 'vmstat' to get average CPU usage.
# We take a sample for 2 seconds to get the most recent average usage.
CPU_USAGE=$(vmstat 1 2 | tail -1 | awk '{print $15}')

# Log the data to the output file
echo "$TIMESTAMP, $MEMORY_USAGE, $CPU_USAGE" >> $OUTPUT_FILE

# Sleep for a second (in fact, we're sampling for 2 seconds due to vmstat,
# so this will approximately log data every 3 seconds)
sleep 1
done

以下是对该脚本的解释:

  • free 命令可以查看内存使用情况。使用 -m 参数可以以 MB 为单位输出。我们使用 awk 从结果中提取已使用的内存。
  • vmstat 1 2 命令会在一秒间隔内获取两个样本。我们关注的是第二个样本,它反映了最新的 CPU 使用情况。输出中的 $15 字段表示 CPU 的空闲时间。实际的 CPU 使用率通过从 100 中减去这个值计算而得,但在此脚本中,我们直接记录了空闲时间,它可以很好地反映 CPU 负载。如果你更愿意记录实际使用的百分比(即使用时间),可以将 awk 命令修改为 awk '{print 100 - $15}'
  • 结果会带有时间戳记录到 usage_data.txt 文件中。
  • 我们使用 while true 循环不断记录这些数据。

注意:运行上述命令可能需要足够的权限,并且由于命令本身的开销和 vmstat 命令的固有延迟,该脚本可能无法精确到秒。可以根据具体的精度需求进行调整。

最近,我在做一些电子项目。我有一个在 Arduino 上运行的 C 程序,但我想让它在 Raspberry Pi Pico 上用 Micropython 运行。这个转换过程没有什么有趣的地方,它只是必须完成。所以我请语言模型为我解决。

ChatGPT 对话记录 - 让它在 Raspberry Pi Pico 上用 Micropython 运行

对话记录

🧑‍🦰User

为 Micropython 编写适用于 pico w 的版本

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
#define MOTOR2_BASE_PIN 10

int Steps1 = 0;
int Steps2 = 0;

boolean Direction1 = true;
boolean Direction2 = true;

void setupPins(int basePin) {
for (int i = 0; i < 4; i++) {
pinMode(basePin + i, OUTPUT);
}
}

void stepper(int basePin, int &amp;Steps, boolean &amp;Direction, int xw) {
int pins[4];
for (int i = 0; i < 4; i++) {
pins[i] = basePin + i;
}

for (int x = 0; x < xw; x++) {
switch (Steps) {
case 0:
digitalWrite(pins[0], LOW);
digitalWrite(pins[1], LOW);
digitalWrite(pins[2], LOW);
digitalWrite(pins[3], HIGH);
break;
case 1:
digitalWrite(pins[0], LOW);
digitalWrite(pins[1], LOW);
digitalWrite(pins[2], HIGH);
digitalWrite(pins[3], HIGH);
break;
case 2:
digitalWrite(pins[0], LOW);
digitalWrite(pins[1], LOW);
digitalWrite(pins[2], HIGH);
digitalWrite(pins[3], LOW);
break;
case 3:
digitalWrite(pins[0], LOW);
digitalWrite(pins[1], HIGH);
digitalWrite(pins[2], HIGH);
digitalWrite(pins[3], LOW);
break;
case 4:
digitalWrite(pins[0], LOW);
digitalWrite(pins[1], HIGH);
digitalWrite(pins[2], LOW);
digitalWrite(pins[3], LOW);
break;
case 5:
digitalWrite(pins[0], HIGH);
digitalWrite(pins[1], HIGH);
digitalWrite(pins[2], LOW);
digitalWrite(pins[3], LOW);
break;
case 6:
digitalWrite(pins[0], HIGH);
digitalWrite(pins[1], LOW);
digitalWrite(pins[2], LOW);
digitalWrite(pins[3], LOW);
break;
case 7:
digitalWrite(pins[0], HIGH);
digitalWrite(pins[1], LOW);
digitalWrite(pins[2], LOW);
digitalWrite(pins[3], HIGH);
break;
default:
digitalWrite(pins[0], LOW);
digitalWrite(pins[1], LOW);
digitalWrite(pins[2], LOW);
digitalWrite(pins[3], LOW);
break;
}
SetDirection(Direction, Steps);
delayMicroseconds(1000);
}
}

void SetDirection(boolean &amp;Direction, int &amp;Steps) {
if (Direction) {
Steps = (Steps + 1) % 8;
} else {
Steps = (Steps - 1 + 8) % 8;
}
}

void setup() {
setupPins(MOTOR1_BASE_PIN);
setupPins(MOTOR2_BASE_PIN);
Serial.begin(9600);
}

void loop() {
if (Serial.available() > 0) { // Check if there is data available to read
int input = Serial.read(); // Read one byte from the serial buffer

// Example usage based on the input
// You might want to use a more complex parsing logic to read multiple characters
// and interpret them as commands or parameters
Serial.println("Got ");
Serial.println(input);
if (input == '1') {
Direction1 = 1;
stepper(MOTOR1_BASE_PIN, Steps1, Direction1, 1000); // 100 steps for motor 1
}
if (input == '2') {
Direction1 = 0;
stepper(MOTOR1_BASE_PIN, Steps1, Direction1, 1000); // 100 steps for motor 1
}
// Uncomment and modify the below line if you want to control more motors
// else if (input == '2') {
// stepper(MOTOR2_BASE_PIN, Steps2, Direction2, 500); // steps for motor 2
// }

}
delay(10); // Delay between reads, adjust as necessary
}

🤖 Assistant

这是适用于 Raspberry Pi Pico W 的 Micropython 版本代码:

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
from machine import Pin, UART
import utime

MOTOR1_BASE_PIN = 6
MOTOR2_BASE_PIN = 10

Steps1 = 0
Steps2 = 0

Direction1 = True
Direction2 = True

uart = UART(0, baudrate=9600)

def setupPins(basePin):
for i in range(4):
pin = Pin(basePin + i, Pin.OUT)
pin.value(0)

def stepper(basePin, steps, direction, xw):
pins = [Pin(basePin + i, Pin.OUT) for i in range(4)]

for _ in range(xw):
if steps == 0:
pins[0].value(0)
pins[1].value(0)
pins[2].value(0)
pins[3].value(1)
elif steps == 1:
pins[0].value(0)
pins[1].value(0)
pins[2].value(1)
pins[3].value(1)
elif steps == 2:
pins[0].value(0)
pins[1].value(0)
pins[2].value(1)
pins[3].value(0)
elif steps == 3:
pins[0].value(0)
pins[1].value(1)
pins[2].value(1)
pins[3].value(0)
elif steps == 4:
pins[0].value(0)
pins[1].value(1)
pins[2].value(0)
pins[3].value(0)
elif steps == 5:
pins[0].value(1)
pins[1].value(1)
pins[2].value(0)
pins[3].value(0)
elif steps == 6:
pins[0].value(1)
pins[1].value(0)
pins[2].value(0)
pins[3].value(0)
elif steps == 7:
pins[0].value(1)
pins[1].value(0)
pins[2].value(0)
pins[3].value(1)
else:
pins[0].value(0)
pins[1].value(0)
pins[2].value(0)
pins[3].value(0)

setDirection(direction, steps)
utime.sleep_us(1000)

def setDirection(direction, steps):
if direction:
steps = (steps + 1) % 8
else:
steps = (steps - 1 + 8) % 8
return steps

def setup():
setupPins(MOTOR1_BASE_PIN)
setupPins(MOTOR2_BASE_PIN)

setup()

while True:
if uart.any():
input_data = uart.read(1)
if input_data:
input_char = input_data.decode('utf-8')
print("Got ", input_char)
if input_char == '1':
Direction1 = True
Steps1 = stepper(MOTOR1_BASE_PIN, Steps1, Direction1, 1000)
elif input_char == '2':
Direction1 = False
Steps1 = stepper(MOTOR1_BASE_PIN, Steps1, Direction1, 1000)
utime.sleep(0.01)

该代码将原始的 C++ Arduino 代码转换为 Python,适用于在 Raspberry Pi Pico W 上运行的 MicroPython。它设置了引脚,从 UART 读取输入,并相应地驱动步进电机。注意,setDirection 函数现在返回更新的步数值,而 stepper 函数则相应更新 Steps 变量。

在另一个项目中,我需要用一些高级的机器学习模型在一个交互循环中对一些图像进行分类。我可以自己编写,也可以让模型帮我完成。

ChatGPT 对话记录 - 高级的机器学习模型在一个交互循环中对一些图像进行分类

对话记录

🧑‍🦰User

如何使用 open_clip 进行分类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import torch
from PIL import Image
import open_clip

model, _, preprocess = open_clip.create_model_and_transforms('ViT-g-14', pretrained='laion2b_s12b_b42k')
model.eval() # model in train mode by default, impacts some models with BatchNorm or stochastic depth active
tokenizer = open_clip.get_tokenizer('ViT-g-14')

image = preprocess(Image.open("test/ts.jpg")).unsqueeze(0)
text = tokenizer(["a picture of taylor swift", "a picture of AOC"])

with torch.no_grad(), torch.cuda.amp.autocast():
image_features = model.encode_image(image)
text_features = model.encode_text(text)
image_features /= image_features.norm(dim=-1, keepdim=True)
text_features /= text_features.norm(dim=-1, keepdim=True)

text_probs = (100.0 * image_features @ text_features.T).softmax(dim=-1)

print("Label probs:", text_probs) # prints: [[1., 0., 0.]]

编写一个响应 HTTP 请求的服务,并接收给定的一组提示词和一组图像,将每张图像与每个提示词进行分类。

🤖 Assistant

要创建一个响应 HTTP 请求的服务,使用 open_clip 模型对给定的提示词进行图像分类,可以使用像 Flask 或 FastAPI 这样的 Web 服务器框架。以下是使用 FastAPI 的示例,它是一个现代的、高性能的、易于使用的构建 API 的框架。

此服务将执行以下任务:

  1. 接收包含图像和提示词的 HTTP POST 请求。
  2. 使用 open_clip 模型对图像进行提示词分类。
  3. 返回分类概率。

步骤 1:设置环境

你需要安装 Python,然后使用 pip 安装所需的包:

1
pip install fastapi[all] torch pillow open_clip

步骤 2:创建 FastAPI 应用程序

创建一个名为 clip_server.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
39
40
41
42
43
44
45
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse
import torch
from PIL import Image
import open_clip
from io import BytesIO
import numpy as np

app = FastAPI()

# Load the CLIP model
model, _, preprocess = open_clip.create_model_and_transforms('ViT-g-14', pretrained='laion2b_s12b_b42k')
model.eval()
tokenizer = open_clip.get_tokenizer('ViT-g-14')

@app.post("/classify/")
async def classify_images(images: list[UploadFile], prompts: list[str]):
if not images or not prompts:
raise HTTPException(status_code=400, detail="Images and prompts cannot be empty.")

try:
# Process each image and prompt
text_tokens = tokenizer(prompts)
results = []
for image in images:
pil_image = Image.open(BytesIO(await image.read()))
image_tensor = preprocess(pil_image).unsqueeze(0)

with torch.no_grad(), torch.cuda.amp.autocast():
image_features = model.encode_image(image_tensor)
text_features = model.encode_text(text_tokens)
image_features /= image_features.norm(dim=-1, keepdim=True)
text_features /= text_features.norm(dim=-1, keepdim=True)

text_probs = (100.0 * image_features @ text_features.T).softmax(dim=-1)
results.append(text_probs.tolist())

return JSONResponse(content={"results": results})
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

步骤 3:运行服务

在终端中执行以下命令以运行你的 FastAPI 应用程序:

1
python clip_server. Py

这将启动位于 localhost 上的服务器,端口为 8000。你现在可以向 http://localhost:8000/classify/ 发送带有图像和提示词的 POST 请求。

步骤 4:测试服务

你可以使用 Postman 或编写一个简单的 python 脚本,利用 python 的 requests 库来测试 API:

1
2
3
4
5
6
7
8
9
import requests

url = 'http://localhost:8000/classify/'
files = {'images': open('path_to_image.jpg', 'rb')}
data = {'prompts': ['a picture of taylor swift', 'a picture of AOC']}

response = requests.post(url, files=files, data=data)
print(response.json())

此基本设置展示了如何使用 open_clip 和 FastAPI 创建服务。你可以通过添加更强大的错误处理、日志记录和其他功能来扩展此示例。

解释事物给我听

我最近开始接触电子学。我年轻时曾经做过一些电子项目,在大学也上过几门相关课程。但现在当我想实际进行项目时,我发现有许多小问题让我难以展开。

我可以去读一本关于实用电子学的书。事实上,我可能会在某个时间点去这么做,以便深入理解这个领域。但我不想把时间花在“学习”上。事实上,我进行电子学项目的一半原因是为了摆脱一天的论文阅读和写作。

在这种情况下,LLMs 表现出色。虽然它们的知识不如世界上最优秀的专家,但有成千上万甚至上百万人知道我可能提出的任何电子学问题的答案。这意味着语言模型也可能知道这些答案。它愿意为我提供所有问题的答案,这样我就可以享受自己想要的乐趣,而不必纠结于细节。尽管我本可以多花一些功夫通过搜索找到答案,但在经历了一天的复杂研究代码后,直接让模型替我完成任务的便捷性让人感到非常轻松。

这里有一些例子,展示了我如何向语言模型提问一些关于电子学的基本问题。这些答案是否完美?谁知道呢。但它们比什么都不知道要好得多。

(这篇文章已经相当长了,读到这里你可能和我写到这里一样感到疲惫。所以我不再对这些例子进行评论了。)

这里是我问了一些关于 PCB 设计的基本问题

ChatGPT 对话记录 - 一些关于 PCB 设计的基本问题

对话记录

🧑‍🦰User

1
电阻的“间距”在 PCB 设计中意味着什么?

🤖 Assistant

在 PCB(印刷电路板)设计中,电阻的“间距”指的是电阻两个引脚中心之间的距离。这一距离对于设计和布局 PCB 至关重要,因为它决定了电阻引脚对应的孔或焊盘的间隔。

间距在 PCB 设计中具有以下重要作用:

  1. 元件放置:了解电阻的间距有助于在 PCB 布局中精确放置元件,确保引脚能够准确插入预留的孔或表面贴装焊盘中。

  2. 孔/焊盘间距:对于通孔电阻,间距决定了 PCB 上钻孔的间隔。对于表面贴装电阻,它则影响焊盘的间距。

  3. 兼容性:确保电阻的间距与 PCB 上的焊盘相匹配,对于组装和焊接至关重要。如果间距不匹配,可能会导致组装错误或电气连接不良。

  4. 布线:在 PCB 设计中,间距影响着走线布局,特别是在密集设计中。合理的焊盘或孔的间距能够帮助更轻松、高效地布置电气连接。

例如,常见的通孔电阻可能具有 0.4 英寸(10.16 mm)的间距,而表面贴装电阻的间距则可能更小,例如 0.05 英寸(1.27 mm)。理解并正确使用间距测量能够确保 PCB 设计的可靠性与可制造性。

🧑‍🦰User

1
HC 50 在晶体振荡器中代表什么?

🤖 Assistant

“HC 50” 指的是一种特定封装类型的晶体振荡器。在电子元件中,封装类型通常指标准化的外形或尺寸,以确保元件可以准确地安装在 PCB 上。“HC 50” 的含义包括:

  1. 封装类型:HC 50 代表“封装晶体”(Housed Crystal)类型 50,这是一种用于晶体振荡器的标准化封装外形与尺寸。

  2. 物理尺寸:HC 50 封装具有标准化的长度、宽度和高度,确保它能够适配为此封装设计的 PCB 布局。

  3. 引脚配置:封装还指定了引脚的数量和间距(即“pitch”)。这对于确保与 PCB 焊盘的兼容性至关重要。

  4. 封装保护:HC 50 振荡器通常采用气密封装,以保护内部晶体免受湿气和污染物的影响,从而保证其性能稳定。

  5. 应用领域:HC 50 封装通常用于微控制器、通信设备和其他需要精确计时的电子设备。

理解 HC 50 等封装类型对于电子设计的重要性如下:

  • 设计兼容性:确保振荡器能够适配 PCB 上指定的空间。
  • 制造效率:标准化封装如 HC 50 有助于自动化生产过程。
  • 性能可靠性:合适的封装和引脚配置确保振荡器在实际应用中的稳定性能。

总而言之,HC 50 是一种适用于晶体振荡器的标准封装类型,它通过规定物理尺寸、引脚配置和封装保护,确保电子设计中的兼容性和性能稳定。

🧑‍🦰User

1
标准 16 MHz 晶体是什么?

🤖 Assistant

标准的 16 MHz 晶体是一种振荡频率为 16 百万次每秒(16 兆赫兹)的石英晶体振荡器。石英晶体用于电子电路中,以产生精确且稳定的时钟信号,这对于各种设备的计时和同步至关重要。以下是标准 16 MHz 晶体的关键特性:

主要特性

  1. 频率:16 MHz,意味着晶体每秒振荡 16 百万次。
  2. 封装类型:晶体有不同的封装类型,例如 HC-49、HC-49 S、HC-49 U、SMD(表面贴装器件)等。封装类型决定了晶体的物理尺寸和引脚配置。
  3. 负载电容:通常由制造商指定,如 18 pF、20 pF 或 32 pF,这是实现指定振荡频率所需的电容值。
  4. 频率公差:指实际振荡频率与指定 16 MHz 的接近程度,通常以百万分之一(ppm)为单位表示。常见公差为 ±20 ppm、±30 ppm 等。
  5. 温度稳定性:表示晶体频率在温度变化下的稳定性,通常以 ppm/°C 为单位表示。

应用领域

  • 微控制器与微处理器:提供时钟信号,用于计时和同步。
  • 通信设备:用于无线电、收发器及其他通信设备。
  • 定时器与计数器:在精确计时应用中至关重要。
  • 消费电子:常见于手表、时钟等计时相关产品中。

示例规格

  • 频率:16 MHz
  • 负载电容:18 pF
  • 频率公差:±30 ppm
  • 温度稳定性:±50 ppm(温度范围 -20°C 到 +70°C)
  • **等效串联电阻 (ESR)**:通常为 40Ω 到 60Ω

引脚配置

对于通孔晶体(如 HC-49 U),通常有两个引脚用于连接。表面贴装晶体的配置则有所不同,但通常也有两个焊盘用于 PCB 安装。

电路中的应用

  • 振荡电路:晶体通常与电容器和放大器(如晶体管或运算放大器)一起构成振荡电路。
  • 微控制器时钟输入:晶体通常直接连接到微控制器或微处理器的时钟输入引脚。

示例

一个典型的 16 MHz 晶体振荡器可能如下所示:

  • 零件号:HC-49 S-16 MHz
  • 封装类型:HC-49 S(常见的通孔封装)
  • 频率:16 MHz
  • 负载电容:18 pF
  • 频率公差:±30 ppm
  • 温度稳定性:±50 ppm

这种标准的 16 MHz 晶体广泛用于各种电子应用中,能够提供精确的时钟信号,以满足计时和同步需求。

🧑‍🦰User

1
如何将自定义库导入 KiCad?

🤖 Assistant

将自定义库导入 KiCad 需要经过一些步骤。无论是符号库、封装库还是 3 D 模型库,以下是导入自定义库的详细步骤:

导入符号库

  1. 打开 KiCad 和原理图编辑器

  2. 启动 KiCad 并打开你的项目。

  3. 打开原理图编辑器(Eeschema)。

  4. 访问库管理

  5. 在顶部菜单中选择 Preferences

  6. 选择 Manage Symbol Libraries

  7. 添加新库

  8. Manage Symbol Libraries 对话框中,你会看到两个选项卡:Global LibrariesProject Specific Libraries

  9. 若要为当前项目添加库,请使用 Project Specific Libraries 选项卡;若希望库适用于所有项目,则使用 Global Libraries 选项卡。

  10. 点击文件夹图标浏览并选择你的自定义库文件(扩展名为 .lib)。

  11. 添加库路径

  12. 点击 OK 将库添加到你的项目或全局列表中。

  13. 确保库路径正确且已正确引用。

导入封装库

  1. 打开 KiCad 和 PCB 编辑器

  2. 在 KiCad 中打开你的项目。

  3. 打开 PCB 编辑器(Pcbnew)。

  4. 访问库管理

  5. 在顶部菜单中选择 Preferences

  6. 选择 Manage Footprint Libraries

  7. 添加新库

  8. 类似于符号库,你会看到 Global LibrariesProject Specific Libraries 选项卡。

  9. 点击 Project Specific Libraries 选项卡以添加当前项目的封装库;若希望库适用于所有项目,则点击 Global Libraries

  10. 点击文件夹图标浏览并选择你的自定义封装库文件(扩展名为 .mod 或包含封装文件的目录)。

  11. 添加库路径

  12. 点击 OK 将封装库添加到你的项目或全局列表中。

  13. 确保路径正确且已正确引用。

导入 3 D 模型库

  1. 打开 PCB 编辑器

  2. 在 KiCad 中打开你的项目。

  3. 打开 PCB 编辑器(Pcbnew)。

  4. 分配 3 D 模型

  5. 在顶部菜单中选择 Preferences

  6. 选择 Configure Paths 来设置 3 D 模型路径(如果它们不在默认位置)。

  7. 将 3 D 模型添加到封装

  8. 打开特定元件的封装属性。

  9. 转到 3D Settings 选项卡。

  10. 添加 3 D 模型文件路径(扩展名如 .wrl.step.stp 等)。

常规提示

  • 库管理:KiCad 的库管理可通过原理图和 PCB 编辑器中的 Preferences 菜单访问。
  • 库路径:确保自定义库的路径已正确设置,并可从 KiCad 内部访问。
  • 项目 vs. 全局:决定库是否应对所有项目可用(全局)或仅当前项目可用(项目专用)。

通过这些步骤,你应该能够有效地将自定义库导入 KiCad,从而在 PCB 设计中使用你的自定义符号、封装和 3 D 模型。

🧑‍🦰User

1
通孔元件的标准孔径是多少?

🤖 Assistant

PCB 上通孔元件的标准孔径会因元件引脚的类型和尺寸而有所不同。然而,PCB 设计中通常遵循一些通用的指南和标准:

通用指南

  1. 电阻、电容和小型二极管

  2. 引脚直径:通常为 0.6 mm 到 0.8 mm。

  3. 孔径:通常为 0.8 mm 到 1.0 mm。

  4. 集成电路(IC)和较大元件

  5. 引脚直径:通常为 0.25 mm 到 0.5 mm。

  6. 孔径:通常为 0.6 mm 到 0.8 mm。

  7. 连接器和较大引脚元件

  8. 引脚直径:可能更大,通常在 1.0 mm 到 1.5 mm 之间。

  9. 孔径:通常为 1.2 mm 到 1.8 mm 或更大,取决于引脚尺寸。

特定标准

IPC(电子工业联接协会)提供了孔径标准:

  • IPC-2221(印刷板设计通用标准)
  • 建议孔径比大多数元件的引脚直径大 0.2 mm 到 0.25 mm。

示例计算

如果你的元件引脚直径为 0.8 mm: - 推荐孔径:0.8 mm(引脚直径)+ 0.25 mm(公差)= 1.05 mm - 常见选择可能是 1.1 mm 的孔径。

制造商规格

始终查阅制造商的数据表以获取具体元件的推荐孔径。数据表通常会提供推荐的孔径尺寸。

实际示例

对于典型的通孔电阻,其引脚直径为 0.6 mm: - 孔径:0.6 mm(引脚直径)+ 0.2 mm(公差)= 0.8 mm

总结

尽管具体孔径会因元件不同而有所变化,但通常的经验法则是将引脚直径加 0.2 mm 到 0.25 mm,以确定合适的孔径。始终查阅元件数据表和相关标准,以确保设计的准确性。

这里是我问了一些关于焊接的基本问题

ChatGPT 对话记录 - 一些关于焊接的基本问题

对话记录

🧑‍🦰User

1
我有一个引脚间距为 0.05 英寸的 LoRa 无线芯片,如何将它连接到引脚间距为 0.1 英寸的面包板上?

🤖 Assistant

要在引脚间距为 0.1 英寸的面包板上使用引脚间距为 0.05 英寸的 LoRa 无线芯片,您可以考虑以下几种方法:

1. 使用适配板

您可以使用专门设计的适配板,将 0.05 英寸的引脚间距转换为 0.1 英寸。这种适配板一侧有 0.05 英寸的引脚插孔,另一侧则是 0.1 英寸的标准间距插孔,使您能够轻松将芯片连接到面包板上。

2. 焊接导线

如果没有适配板,您可以将短导线焊接到 LoRa 芯片的每个引脚上,然后将导线的另一端插入面包板。这种方法需要一定的焊接技巧,务必小心操作,以避免短路。

3. 使用分线板

您可以寻找专为特定 LoRa 芯片设计的分线板。这种分线板通常集成了所有必要的组件,并提供 0.1 英寸间距的插针,便于插入面包板。

4. 使用带焊接引脚的试验板

如果您对焊接比较熟悉,可以使用带孔试验板 (Proto Board) 自制适配器。将 LoRa 芯片焊接到试验板上,然后通过导线将每个引脚连接到 0.1 英寸间距的插针上。

使用适配板的步骤:

  1. 获取适配板:购买一块具有 0.05 英寸和 0.1 英寸间距插孔的适配板,常见电子元件供应商都有售。
  2. 焊接 LoRa 芯片:将 LoRa 芯片焊接到适配板的 0.05 英寸侧。
  3. 添加插针:在适配板的 0.1 英寸侧焊接插针。
  4. 插入面包板:将适配板上的插针插入面包板的孔中。

适配板示例:

以下是一种常用的适配板:

  • SSOP to DIP 适配板:这块适配板可将 0.05 英寸间距的芯片转换为 0.1 英寸间距的插针。

小提示:

  • 检查兼容性:确保适配板与您的 LoRa 芯片的引脚数和布局匹配。
  • 使用热缩管:如果直接焊接导线,请使用热缩管保护连接。
  • 反复检查连接:在给电路上电之前,务必检查所有连接,确保无误,以免损坏元件。

通过以上方法,您可以轻松将引脚间距为 0.05 英寸的 LoRa 芯片连接到标准 0.1 英寸间距的面包板上。

🧑‍🦰User

1
我应该使用哪种适配器?

🤖 Assistant

要将引脚间距为 0.05 英寸的 LoRa 无线芯片连接到引脚间距为 0.1 英寸的面包板上,推荐使用 SSOP (Shrink Small Outline Package) to DIP (Dual Inline Package) 适配板。这种适配板专门设计用于将 SSOP 封装的细间距芯片转换为标准间距的插针。以下是如何使用这种适配板的详细步骤:

推荐适配板:SSOP to DIP 适配板

SSOP to DIP 适配板特点:

  • 引脚间距转换:将 0.05 英寸 (1.27 mm) 间距转换为 0.1 英寸 (2.54 mm) 间距。
  • 引脚数可选:根据您的 LoRa 芯片选择合适的引脚数。
  • 兼容标准插针:适配板提供 0.1 英寸间距的插针,可直接插入面包板。

使用 SSOP to DIP 适配板的步骤:

  1. 购买适配板:购买一块与您的 LoRa 芯片引脚数匹配的 SSOP to DIP 适配板。常见供应商有 SparkFun、Adafruit 等。

  2. 焊接 LoRa 芯片到适配板

  3. 对齐 LoRa 芯片的引脚与适配板 0.05 英寸侧的焊盘。

  4. 使用细尖焊接铁和助焊剂,小心地将每个引脚焊接到相应焊盘上。

  5. 添加 0.1 英寸插针

  6. 将 0.1 英寸插针插入适配板 0.1 英寸侧的孔中,并焊接固定。

  7. 插入面包板

  8. 焊接完成后,将插针插入面包板。适配板将芯片的细间距引脚转换为标准的面包板间距。

适配板示例:

以下是一种常用的适配板:

  • SSOP to DIP 适配板
  • 示例产品:SSOP-20 to DIP-20 适配板
  • 功能:将 SSOP-20 (0.05 英寸间距) 转换为 DIP-20 (0.1 英寸间距)
  • 供应商:SparkFun、Adafruit、Digi-Key、Mouser

焊接技巧:

  • 使用助焊剂:助焊剂能帮助焊锡更好地流动,并确保干净的焊接连接。
  • 细尖焊接铁:使用细尖焊接铁避免引脚之间的焊锡桥接。
  • 检查短路:焊接后,用放大镜检查引脚之间是否有焊锡桥接。
  • 测试连接:使用万用表检查 LoRa 芯片引脚与插针之间的导通情况。

通过使用 SSOP to DIP 适配板,您可以轻松地将 LoRa 芯片与标准面包板和原型设计工具集成。

🧑‍🦰User

1
如何焊接 SSOP 部件?我熟悉普通的 0.1 英寸插针焊接,但这是表面贴装吗?

🤖 Assistant

是的,SSOP (Shrink Small Outline Package) 是一种表面贴装技术 (SMT) 封装类型。焊接 SSOP 部件由于其引脚间距较小,需要比通孔元件更高的精度。以下是逐步焊接 SSOP 部件的指南:

所需工具和材料:

  • 细尖焊接铁
  • 焊锡丝 (直径约 0.02 英寸或更细)
  • 助焊剂 (最好是笔状助焊剂,便于涂抹)
  • 吸锡线
  • 放大镜
  • 镊子
  • SSOP to DIP 适配板
  • SSOP LoRa 芯片

焊接步骤:

  1. 准备适配板
  2. 清洁适配板,确保没有灰尘或油脂。
  3. 在芯片焊盘上涂助焊剂。
  4. 对齐 SSOP 芯片
  5. 使用镊子,小心地将 SSOP 芯片放置在适配板上,对齐引脚与焊盘。
  6. 确保芯片定位正确,避免焊锡桥接。
  7. 固定芯片
  8. 先焊接芯片每个角上的一个引脚,以固定芯片位置。
  9. 确保芯片对齐后,再继续焊接其他引脚。
  10. 焊接所有引脚
  11. 在所有引脚上涂助焊剂以防止桥接。
  12. 方法 1: 拖焊法
    • 将少量焊锡涂在焊接铁尖端。
    • 轻轻拖动焊接铁沿芯片引脚,焊锡应均匀分布在引脚和焊盘上。
  13. 方法 2: 逐点焊接法
    • 对每个引脚单独焊接。加热引脚和焊盘,然后引入焊锡。
  14. 检查焊点
  15. 使用放大镜检查焊接情况,特别是是否有引脚短路。
  16. 修复短路
  17. 若发现焊锡桥接,涂抹助焊剂后使用吸锡线移除多余焊锡。
  18. 清洁电路板
  19. 确保焊接良好后,使用异丙醇清洁电路板,去除残留助焊剂。
  20. 焊接插针
  21. 将 0.1 英寸插针插入适配板上的孔中,并焊接固定。

焊接技巧:

  • 使用助焊剂:助焊剂对于 SMT 焊接至关重要,它能防止氧化并帮助焊锡流动。
  • 多加练习:在处理实际项目之前,可以先在备用板上练习焊接技巧。
  • 控制温度:使用温度可控的焊接铁,并保持在适当温度 (如 350°C)。
  • 耐心操作:确保每个引脚都焊接到位,避免急于求成。

通过这些步骤,您可以成功地将 SSOP 部件焊接到适配板上,使其与标准面包板兼容,方便进行原型设计。

这里是我问了一些关于电容器的基本问题

ChatGPT 对话记录 - 一些关于电容器的基本问题

对话记录

🧑‍🦰User

电容上的 “681” 代表什么?

🤖 Assistant

电容上的数字 “681” 一般表示其电容量,单位为皮法拉 (pF)。这种编码通常由三位数字组成,前两位表示有效数字,第三位表示乘数 (即添加的零的数量)。

对于 “681”:

  • 前两位数字 “68” 表示有效数字。
  • 第三位数字 “1” 表示乘数。

所以,”681” 代表 680 皮法拉 (pF)。

类似地,电容上的 “104” 也表示电容量,单位为皮法拉 (pF):

  • 前两位数字 “10” 表示有效数字。
  • 第三位数字 “4” 表示乘数。

因此,”104” 代表 100,000 皮法拉 (pF),也就是 100 纳法拉 (nF) 或 0.1 微法拉 (µF)。

🧑‍🦰User

104?

🤖 Assistant

电容器上标有“104”的数字通常表示其电容值,以皮法拉 (pF) 为单位。具体来说:

  • 前两位数字“10”表示有效数字“10”。
  • 第三位数字“4”表示乘数,即在“10”后面添加 4 个零。

因此,标有“104”的电容器的电容值为 100,000 皮法拉 (pF),也就是 100 纳法拉 (nF) 或 0.1 微法拉 (µF)。

这里是我问了一些关于 LED 的基本问题

ChatGPT 对话记录 - 一些关于 LED 的基本问题

对话记录

🧑‍🦰User

这是什么意思 60 mW/sr @ 100 mA?

🤖 Assistant

“60 mW/sr @ 100 mA” 这个符号通常用于描述光学或发光设备 (如 LED 或激光二极管) 的性能参数。具体来说:

  1. 60 mW/sr

    • mW 表示毫瓦,是功率单位。
    • sr 表示球面度,是立体角单位。
    • 因此 “mW/sr” 表示每单位立体角发出的功率,即辐射强度。在这里,表示辐射强度为 60 毫瓦每球面度。
  2. @ 100 mA

    • mA 表示毫安,是电流单位。
    • “@ 100 mA” 表示该设备在 100 毫安电流下的工作状态。

综上所述,这表示在 100 毫安电流下,该设备发出的辐射强度为 60 毫瓦每球面度。

这里是我问了一些关于软盘的基本问题

ChatGPT 对话记录 - 一些关于软盘的基本问题

对话记录

🧑‍🦰User

1.44 MB 软盘是双面的吗?

🤖 Assistant

是的,1.44 MB 软盘,即通常称为 3.5 英寸高密度 (HD) 软盘,是双面的。这意味着磁盘的两面都用于存储数据,总容量为 1.44 兆字节。软盘驱动器可以读取和写入磁盘的两面。

🧑‍🦰User

它是经过纠错后的 1.44 兆字节的有效容量吗?

🤖 Assistant

3.5 英寸软盘的 1.44 MB 容量是用户可使用的存储空间,已经包含了格式化和纠错所需的空间。磁盘的原始未格式化容量实际上约为 2 MB,但其中一部分被用于文件系统、扇区头和纠错码,因此实际可用空间为 1.44 MB。

我还可以继续列举,但我想你已经明白了这个道理。

解决已有方案的任务

几乎所有事情都曾被别人做过。你想做的几乎没有什么是全新且未曾尝试过的。而语言模型在提供以前见过的解决方案方面表现得非常出色。

在最近的一个项目中,我需要提高一些 python 代码的性能。所以我先(1)请 LLM 将其重写为 C 语言代码,然后(2)请它构建接口,以便我可以从 python 调用 C 代码。

这些任务并不难。将 python 转换为 C 语言,我相信自己可以在一两个小时内完成。虽然我不完全了解 python 到 C 的 API 是如何工作的,但我可以通过阅读文档来弄清楚。然而,如果需要我亲自去做,我可能永远不会完成这些任务,因为这并不是关键任务,我宁愿让计算机处理这个问题,而不愿意花时间优化这些不常用的代码。

将 python 转换为 C 语言(大部分情况下)是一个针对简单程序的技术性过程,且只有一种标准的 python 到 C 的调用约定。所以,我直接请求 LLM 帮我完成这个任务。

从那以后,我就习惯了这种方式,几乎每次需要快速运行的代码时,我都会描述我在 python 中的需求,并请求生成优化的 C 代码

ChatGPT 对话记录 - 描述我在 python 中的需求,并请求生成优化的 C 代码

对话记录

🧑‍🦰User

你能帮我把这个 python 程序转换成一个更快的 C 程序吗?

1
Print (collections.Counter ([x for x in open (sys. Argv[1]). Read ()]))

🤖 Assistant

当然!您可以将现有的 python 程序 (用于统计文件中字符出现次数) 转换为更高效的 C 程序。

以下是一个 C 程序的示例,该程序读取文件 (通过命令行参数传递) 的内容,并统计每个字符的出现次数:

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

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return EXIT_FAILURE;
}

FILE *file = fopen(argv[1], "r");
if (file == NULL) {
fprintf(stderr, "Failed to open file: %s\n", argv[1]);
return EXIT_FAILURE;
}

int counts[256] = {0}; // Assuming ASCII characters
int c;

while ((c = fgetc(file)) != EOF) {
if (c >= 0 &amp;&amp; c < 256) {
counts[c]++;
}
}

for (int i = 0; i < 256; i++) {
if (counts[i] > 0) {
printf("%c: %d\n", i, counts[i]);
}
}

fclose(file);
return EXIT_SUCCESS;
}

使用 C 编译器 (如 gccclang) 编译此程序,并通过文件名作为命令行参数运行它。

请注意,由于 C 语言不像 python 那样提供高级抽象,因此可能会在行为上存在一些差异,特别是在处理 Unicode 或其他字符编码时。此 C 程序假定处理 ASCII 字符,如果需要处理更复杂的字符集,需要实现额外的逻辑。

有时我会做同样的事情,如果我认为 Rust 输出比 C 输出更容易判断正确性,我就会请求生成 Rust 代码而不是 C 代码

ChatGPT 对话记录 - 请求生成 Rust 代码而不是 C 代码

对话记录

🧑‍🦰User

将此函数重写为 Rust:

1
2
3
4
5
6
7
8
9
10
import numpy as np
import os

for f in os.listdir("/tmp/big000"):
if 'sizes' not in f: continue
arr = np.frombuffer(open("/tmp/big000/"+f,"rb").read(), dtype=np.uint8)
arr = arr.reshape((-1,5))
arr = np.pad(arr, ((0, 0), (0, 3)))
arr = arr.flatten().view(np.uint64)
print(np.max(arr))

🤖 Assistant

这是使用 ndarraybyteorderwalkdir crate 的 Rust 等效函数:

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
use ndarray::{Array2, ArrayView1, ShapeBuilder, s};
use byteorder::{LittleEndian, ReadBytesExt};
use std::fs::File;
use std::io::{BufReader, Read};
use walkdir::WalkDir;

fn main() {
let dir = "/tmp/big000";

for entry in WalkDir::new(dir) {
let entry = entry.unwrap();
if entry.file_type().is_file() && entry.file_name().to_string_lossy().contains("sizes") {
let path = entry.path();

let mut file = BufReader::new(File::open(path).expect("Unable to open file"));
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).expect("Unable to read file");

let mut reader = std::io::Cursor::new(buffer);
let len = reader.get_ref().len() / 5;
let mut arr: Vec<u8> = Vec::with_capacity(len * 5);
for _ in 0..len {
arr.extend_from_slice(&reader.read_u8().unwrap().to_le_bytes());
arr.extend_from_slice(&reader.read_u32::<LittleEndian>().unwrap().to_le_bytes());
}

let arr = Array2::from_shape_vec((len, 5).strides((1, len)), arr).unwrap();
let arr = arr.select(Axis(1), &[0, 1, 2, 3, 4]);
let arr = ArrayView1::from(&arr.as_slice().unwrap())
.into_shape((len * 8).strides((1, len * 8)))
.unwrap();

let max_val = arr.iter().map(|&x| u64::from_le_bytes(x.to_le_bytes())).max();
println!("{:?}", max_val);
}
}
}

在此 Rust 代码中,ndarray crate 用于处理多维数组,byteorder crate 用于读取字节数据,walkdir crate 用于从目录读取文件。请注意,您需要将这些 crates 添加到您的 Cargo.toml 文件中。

1
2
3
4
5
[dependencies]
ndarray = "0.15.4"
byteorder = "1.4.3"
walkdir = "2.3.2"

再举一个例子,使用 multiprocessing 库并行化一个 python 函数并不难。你需要编写一些模板代码,它基本上就能完成任务。但编写这些代码既麻烦,又会干扰实际工作。现在,每次我需要这样做时,我都会请 LLM 代劳。

ChatGPT 对话记录 - 使用 multiprocessing 库并行化一个 python 函数

对话记录

🧑‍🦰User

1
2
3
4
5
6
7
8
9
10
11
12
import random
import os

data = open("a.out", "rb").read()

for i in range(len(data)):
fixed = data[:i] + b'\xff\xff\xff\xff' + data[i+4:]
fn = str(random.random())
open("/tmp/"+fn, "wb").write(data)
os.chmod("/tmp/"+fn, 0o755)
print(os.popen("/tmp/"+fn).read())
exit(0)

使用 multiprocessing.Pool 并行化这个操作,并在文件的各部分上开启 128 个任务

🤖 Assistant

要使用 python 中的 multiprocessing.Pool 并行化这个任务,您需要创建一个执行特定操作的函数,然后使用 Pool 将此函数映射到文件的不同部分。以下是如何实现:

  1. 定义一个函数,执行基于给定索引的文件内容修改和处理。
  2. 使用 multiprocessing.Pool 并行执行该函数,并在数据的不同部分上运行。

以下是修改后的代码:

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
import random
import os
import multiprocessing as mp

def process_file(index, data):
fixed = data[:index] + b'\xff\xff\xff\xff' + data[index+4:]
fn = str(random.random())
file_path = "/tmp/" + fn
with open(file_path, "wb") as f:
f.write(fixed)
os.chmod(file_path, 0o755)
result = os.popen(file_path).read()
os.remove(file_path) # Clean up the temporary file
return result

def main():
data = open("a.out", "rb").read()

with mp.Pool(128) as pool:
results = pool.starmap(process_file, [(i, data) for i in range(len(data) - 4)])

for result in results:
print(result)

if __name__ == "__main__":
main()

该脚本打开文件 a.out,读取其内容,然后使用 128 个工作进程的池来处理文件的不同部分。process_file 函数在每个索引处修改数据,将其写入临时文件,执行它,读取输出并返回。

需要考虑以下几点:

  • 确保您的环境支持 128 个并行进程,并确保您有权限从 /tmp 执行二进制文件。
  • 此脚本假定 a.out 文件和生成的文件是可执行的,并在运行时产生有意义的输出。
  • 需要处理临时文件的删除 (os.Remove(file_path)) 以避免 /tmp 目录混乱。

或者,当我测试 API 时,我通常会先写一个 curl 请求来初步实现它。当它正常工作后,我希望以编程方式重复这项任务,通常会将其转换为 Python。以前,我可能会采取一些不理想的方法,比如调用 os. Popen () 来运行 curl 命令,但这并不是最佳方案。更好的方法是将其转换为 python 的 requests 库;但这需要时间,所以我可能不会去做。但现在,我可以直接请求 LLM 来完成这项转换,从而在更短的时间内获得更清晰的程序。

ChatGPT 对话记录 - 将其转换为 python 的 requests 库

对话记录

🧑‍🦰User

将此 curl 命令转换为 python 中使用 requests 的函数,并将内容作为参数传递

1
2
3
4
5
6
7
8
curl https://api.openai.com/v1/chat/completions \ 
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": "Say this is a test!"}],
"temperature": 0.7
}'

🤖 Assistant

您提供的 curl 命令使用 JSON 负载和特定的请求头向 OpenAI API 发出 POST 请求。以下是如何使用 requests 库将其转换为 python 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
import json

def make_request(content, api_key):
url = "https://api.openai.com/v1/chat/completions"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
data = {
"model": "gpt-3.5-turbo",
"messages": [{"role": "user", "content": content}],
"temperature": 0.7
}

response = requests.post(url, headers=headers, data=json.dumps(data))

return response.json() # Return response as JSON

您可以通过调用 make_request(content, api_key) 来使用此函数,其中 content 是您想要发送的内容,api_key 是您的 OpenAI API 密钥。

请将 api_key 替换为您的实际 OpenAI API 密钥,以便认证请求。请保密您的 API 密钥,避免将其发布在公共平台上。

确保在生产代码中妥善处理异常,例如网络连接中断、服务器宕机或 API 密钥错误。

在即将开展的一个项目中,我可能会在这里讨论,我需要了解人们通常用作简单无线电发射器的东西。因为我想要的是人们的常见答案,LLM 是完美的选择!

ChatGPT 对话记录 - 我需要了解人们通常用作简单无线电发射器的东西

对话记录

🧑‍🦰User

我想通过无线电传输低比特率的数据,但不需要 LoRa 的长距离,应该使用什么技术?

🤖 Assistant

如果您需要传输低比特率的数据,且不需要 LoRa 的长距离,以下几种技术可能适合您:

  1. NRF 24 L 01

    • 频率:2.4 GHz
    • 数据速率:最高 2 Mbps,但可以选择较低速率以减少功耗并提高可靠性。
    • 通信距离:通常为 100 米 (视距)。
    • 应用场景:短距离无线通信、遥控系统、传感器网络。
  2. HC-12

    • 频率:433 MHz
    • 数据速率:最高 1 Mbps,常用于低速率下以提高通信距离和可靠性。
    • 通信距离:在低速率下可达 1 公里 (视距)。
    • 应用场景:无线数据传输、遥控、遥测。
  3. RFM 69

    • 频率:315/433/868/915 MHz (因型号和地区而异)
    • 数据速率:最高 300 kbps
    • 通信距离:在低速率下通常可达 500 米 (视距)。
    • 应用场景:传感器网络、家庭自动化、无线数据传输。
  4. **蓝牙低功耗 (BLE)**:

    • 频率:2.4 GHz
    • 数据速率:通常为 1 Mbps,但可以降低速率以节省功耗。
    • 通信距离:通常为 100 米 (视距)。
    • 应用场景:短距离无线通信、物联网设备、可穿戴设备。
  5. Zigbee

    • 频率:2.4 GHz (最常见),也可用于 868 MHz 和 915 MHz 频段。
    • 数据速率:最高 250 kbps。
    • 通信距离:通常为 100 米 (视距)。
    • 应用场景:家庭自动化、工业控制、无线传感器网络。

如果您能提供更多详细的需求,比如所需的数据速率、功耗限制以及环境因素,我可以进一步帮助您选择最合适的技术。

解决常见错误

在 2022 年之前,当我遇到流行工具或库的错误信息时,我通常会按照以下步骤进行:

  1. 复制错误信息
  2. 粘贴到 Google 中
  3. 点击顶部的 Stack Overflow 链接
  4. 确认问题是否与我想要解决的相符;如果不相符,则返回第 2 步
  5. 应用最优解决方案
  6. 如果不奏效,返回第 2 步,修改搜索词,尝试其他方法

说实话,通常出错的工具与最终任务相去甚远,而你并不关心如何让它工作,只要它能正常工作就行。那么,到了 2024 年,这个流程发生了什么变化?

  1. 复制错误信息
  2. 向 LLM 询问“如何修复此错误?[error]”
  3. 按照 LLM 建议的逐步解决方案进行操作
  4. 如果不奏效,就说“这没有用”

现在,我没有相关的对话记录可以展示给你。(或者,在一小时的查找中我找不到。)但实际上,我已经将其直接集成到我的工作流程中了。

我是一个 Emacs 用户。我设置了环境,当我运行一个程序并且它以非零状态码退出(意味着出错)时,它会自动调用最新最快的 LLM 来解释问题,并同时请求生成一个可以直接应用于修复代码的补丁。

目前,模型在大多数情况下还不足以胜过我,但它们已经非常接近。而且偶尔当 LLM 修复了一个我知道因为某个错别字而导致的难以发现的 bug 时,我会感到惊喜。

以及许多其他事情

上面链接的所有对话只占我过去一年中与 LLM 互动总数的不到 2%。

如果你做个计算,会发现我在过去 12 个月里发起了超过 2000 次与 LLM 的对话。

我没有链接到这些其他对话,不是因为模型没能帮到我(尽管确实有不少这种情况),而是因为(1)它们中的很多与我已经提到的那些模式相同,或者(2)它们不容易解释清楚,无法让你看出它们为何对我有用。

我完全预计在未来我对这些模型的使用将继续增长。作为参考,2024 年我通过 Web 界面进行的 LLM 查询比 2023 年增加了 30%——至于 API 查询的增长,我无法计算,但我估计至少增加了 2 到 3 倍。

评估 LLM 能做什么,而不是它们不能做什么

我在面试求职者时收到的最佳建议之一是,根据他们能做的事情来评估,而不是他们不能做的事情。

如果我问你一些琐碎的问题,可能会让你显得能力不足。举个极端的例子:世界上有十亿人讲普通话,而我连数到十都不会。如果有人给我一份普通话的小学考试卷,我肯定会一败涂地。

即使在计算机科学领域,也有许多我完全不熟悉的领域。比如,我对 SQL 的理解仅限于如何写一个有效的 SELECT 语句——这确实是我唯一能写的语句。

我对数据库有很深的理论理解;我上过研究生级别的数据库课程,理解事务的运作原理及其实现方式,也了解各种索引策略的优缺点。但我从未实际操作过生产环境中的数据库,所以在实际应用中我无从下手。

因此,当我看到有人在网上争论说 LLM 只是炒作,因为“它们甚至不能做到 [X]”时,我很难理解这种观点。这里的 [X] 可能是:

  • … 数一个句子中的单词数量!
  • … 写一首每个词都以字母“a”开头的诗!
  • … 计算两个两位数的乘积!
  • … 从列表中随机选择一个元素!

但你上次真正需要做这些事情,并且认为 LLM 是合适的工具是什么时候呢?

同样的道理,我不会因为人类无法在脑中处理 64 位整数——这对计算机来说是微不足道的任务——就否定人类的价值。也不应该因为你可以构造出一个 LLM 无法解决的任务而否定它们的价值。重要的问题是,你能否找到 LLM 在其中能发挥作用的任务?

程序员们已经深刻理解了某种工具在不同场景下可能具有不同的用途。如果你要编写一个操作系统,也许你应该用 C 而不是 Python。没有人会因为“python 不能强制变量对齐到 32 字节边界”就觉得它可笑,因为这是抽象层次的问题。同样,语言模型也在一个非常高的抽象层次上运作;你不能指望它们解决连最简单程序都能解决的任务。但你可以指望它们解决_不同_类型的任务。

结论

我写这篇文章有两个目的。首先,正如开头所说,我想论证 LLMs 已经为我带来了极大的价值。其次,我注意到很多人说“我喜欢使用 LLMs 的想法,但不知道它们如何帮助我”。所以,如果你也是这样想的,希望你能通过这些例子了解到我是如何使用它们的。

对我来说,LLMs 能做的事情很多。它们不能做所有事情,甚至不能解决大部分问题。但当前的模型,已经为我带来了相当大的价值。

在展示这些例子后,我常常听到的反驳是:“那些任务很简单!任何计算机科学的本科生都能做到!”这确实没错。本科生可能通过几个小时的搜索,告诉我如何正确诊断 CUDA 错误,或者该重新安装哪些包。他们也许能在几个小时内用 C 语言重写那个程序,或者通过学习相关教材,教会我我想知道的内容。不幸的是,我没有这样一个随时解答我问题的本科生。但我有 LLM。因此,虽然 LLM 还不足以解决我编程工作中最具挑战性的部分,但它们能够轻松应对简单任务。

五年前,LLMs 最多只能写出看似英语的段落。当时,我们已经惊讶于它们能在句子之间形成连贯的想法,它们的实际用处几乎为零。但今天,它们已经将我的编程效率提高了至少 50%,并减少了很多繁琐的工作,让我能够完成一些我本不会尝试的项目。

因此,当有人说“LLMs 只是炒作”,并声称它们对任何人都没有实际价值时,我知道他们是错的,因为它们确实为我带来了价值。也许我是个例外,可能我是唯一一个发现这些模型对我有用的人。我只能代表我自己发言。但考虑到 LLMs 能显著提高我的生产力——一个在使用 LLM 前已有 20 年编程经验的人——我相信还有很多人也能从中受益。

而这只是基于当前可用的模型。接下来的五年会带来什么变化,让我们拭目以待。在我的下一篇文章中,我会对此进行一些推测。但我不确定我应该感到兴奋,还是害怕。

后记

WayToAGI 的创始人 AJ 把文章丢给我的时候,一下子被吸引住了。它和我写了 2 个月都没写完的一篇文章视角很贴合。希望能尽快完成。这篇文章的翻译和排版花费了 1 周时间,希望能给你带来一些启发。特别是和 AI 对话的部分,兼顾到国内小伙伴,都做了翻译。

翻译这篇文章的时候一度想要自己做一个 hexo 的插件,还是克制住了,找了 timeline 实现了类似的效果。另外获取这些内容,让 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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
const { chromium } = require('playwright');
const fs = require('fs');
const { DateTime } = require('luxon');
const cheerio = require('cheerio');
const path = require('path');

async function main() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://nicholas.carlini.com/writing/2024/how-i-use-ai.html');

// Wait for the page to fully load
await page.waitForSelector('body');

// Extract links
const chatgptLinks = await page.evaluate(() => {
const links = Array.from(document.querySelectorAll('a[href*="chatgpt.com"]'));
return links.map(link => link.href);
});

console.log(`Found ${chatgptLinks.length} ChatGPT links`);

for (const link of chatgptLinks) {
await processChatgptLink(link, browser);
}

await browser.close();
}

let conversationCount = 0;



// 新增的函数,用于处理文件名
function sanitizeFilename(filename) {
// 替换不允许的字符为下划线
let sanitized = filename.replace(/[<>:"\/\\|?*\x00-\x1F]/g, '_');

// 移除首尾的空格和点
sanitized = sanitized.trim().replace(/^\.+|\.+$/g, '');

// 如果文件名为空,使用默认名称
if (!sanitized) {
sanitized = 'untitled';
}

// 确保文件名不超过255个字符(包括扩展名)
const ext = path.extname(sanitized);
const name = path.basename(sanitized, ext);
if (sanitized.length > 255) {
sanitized = name.slice(0, 251 - ext.length) + ext;
}

return sanitized;
}

async function processChatgptLink(link, browser) {
console.log(`Processing link: ${link}`);
const page = await browser.newPage();
await page.goto(link);

// Wait for the page to fully load
await page.waitForSelector('article');

// Extract the first h1 content and date
const { h1Content, dateContent } = await page.evaluate(() => {
const h1 = document.querySelector('h1');
const dateDiv = document.querySelector('div.pt-3.text-base.text-gray-400');
return {
h1Content: h1 ? h1.textContent.trim() : 'Default Title',
dateContent: dateDiv ? dateDiv.textContent.trim() : 'Unknown Date'
};
});

// Extract content
const content = await page.content();
const timelineContent = await convertToTimeline(content, h1Content);

// Increment conversation count
conversationCount++;
const paddedCount = String(conversationCount).padStart(3, '0');

// Generate filename and title
const today = DateTime.now().toFormat('yyyyMMdd');
let filename = `${today} 【AI赋能工作流】一个程序员的自白:我如何利用大语言模型将工作效率提升50%翻译 ChatGPT 会话 之 ${paddedCount} ${h1Content}.md`;
filename = sanitizeFilename(filename); // 使用sanitizeFilename函数处理文件名
const title = `【AI赋能工作流】一个程序员的自白:我如何利用大语言模型将工作效率提升50%翻译 ChatGPT 会话 之 ${paddedCount} ${h1Content}`;

// Create markdown content
const markdownContent = `---
title: ${title}
date: ${DateTime.now().toFormat('yyyy-MM-dd HH:mm')}
tags:
- 我如何用AI
categories: AI文本
cover:
---

# 写在前面

* 1
* 2
* 3


# ${h1Content}
${dateContent}
${link}

---

${timelineContent}
`;

// Save markdown content to file
fs.writeFileSync(filename, markdownContent);
console.log(`Saved content to ${filename}`);

await page.close();
}

async function convertToTimeline(content, h1Content) {
const $ = cheerio.load(content);
let timelineContent = '{% timeline ' + h1Content + ' %}\n'; // You might need to replace 'title' here

$('article').each((i, article) => {
const role = $(article).find('div[data-message-author-role]').data('message-author-role');
let contentText = '';

if (role === 'user') {
contentText = $(article).find('.bg-\\[\\#f4f4f4\\]').text().trim();
} else if (role === 'assistant') {
const contentDiv = $(article).find('div.markdown');
if (contentDiv.length > 0) {
contentDiv.children().each((j, child) => {
if ($(child).is('pre')) {
// 处理代码块
const codeElement = $(child).find('code');
const code = codeElement.text();
const classAttr = codeElement.attr('class') || '';
const languageMatch = classAttr.match(/language-(\w+)/);
const language = languageMatch ? languageMatch[1] : '';
contentText += `\`\`\`${language}\n${code}\n\`\`\`\n\n`;
} else {
// 处理普通文本
contentText += $(child).text() + '\n\n';
}
});
}
}

contentText = contentText.trim();

if (contentText) {
if (role === 'user') {
timelineContent += `<!-- timeline 🧑 User -->\n${contentText}\n<!-- endtimeline -->\n\n`;
} else if (role === 'assistant') {
timelineContent += `<!-- timeline 🤖 Assistant -->\n${contentText}\n<!-- endtimeline -->\n\n`;
}
}
});

timelineContent += '{% endtimeline %}';
return timelineContent;
}

main().catch(console.error);