2023/11/12

PrivateGPTの使い方メモ

はじめに


PrivateGPTを試したのでメモ。PrivateGPTのドキュメントは
https://docs.privategpt.dev/
に公開されており、少なくともLinux環境かつGPUを利用する条件では、このドキュメント通りにインストールすると使えるようになります。

使い方


インストールが完了すると、
PGPT_PROFILES=local make run
でローカル実行できます。具体的な実行コードはMakefileに記載されています。実行後、 http://localhost:8001/ にアクセスすると利用できます。

ドキュメントをPrivateGPTに取り込むには、

PGPT_PROFILES=local make ingest /path/to/docments
を実行します。8487440a6f8d135のリビジョンのコードでは、コードを改変しない限りディレクトリしか指定できません。実行すると、ディレクトリにあるファイルが解析されてPrivateGPTに追加されます。実行するたびにファイル名が同じものを除いて追加されていきます。削除方法はPrivateGPTのドキュメントに記載されています。

ドキュメントの取り込み時は単にサーバーを動かすときよりもGPUのメモリを使用するため、もしGPUのメモリが足りない場合は

CUDA_VISIBLE_DEVICES="" PGPT_PROFILES=local make ingest /path/to/docments
のようにして、GPUを見えなくすればCPUで処理してくれます。

2023/10/26

Diffusion MNIST その3

はじめに


その1で試したDiffusion MNISTについて、 ノイズの強さを表す時刻\(t\)をニューラルネットワークに伝えないとどうなるのかを見ていきます。

方法


https://github.com/MarceloGennari/diffusion_mnist のConditionalUNetの\(t\)が関連する行、つまり、TemporalEmbedding部分をコメントアウトします。具体的には
class ConditionalUNet(UNet):
    (省略)
    def forward(self, x: Tensor, t: Tensor, label: Tensor) -> Tensor:
        x0 = x #self.embedding1(x, t)
        x1 = self.block1(x0)
        x1 = self.label_emb1(x1, label)
        #x1 = self.embedding2(x1, t)
        x2 = self.block2(self.down1(x1))
        x2 = self.label_emb2(x2, label)
        #x2 = self.embedding3(x2, t)
        crossed = self.label_emb3(self.block3(self.down2(x2)), label)
        x3 = self.up1(self.attention1(crossed))
        x4 = torch.cat([x2, x3], dim=1)
        #x4 = self.embedding4(x4, t)
        x5 = self.up2(self.label_emb4(self.block4(x4), label))
        x6 = torch.cat([x5, x1], dim=1)
        x6 = self.label_emb5(x6, label)
        #x6 = self.embedding5(x6, t)
        out = self.out(self.block5(x6))
        return out
とします。

結果


その2で試した時間刻みを100にしたバージョンをベースに比較します。左側がTemporalEmbeddingありで、右側がなしに対応します。
t=50 TemporalEmbeddingあり
t=50 TemporalEmbeddingなし

t=0 TemporalEmbeddingあり
t=0 TemporalEmbeddingなし

TemporalEmbeddingなしの場合はノイズが多いように見えるので、画像の明るさをGIMPを使って上げたものが下図です。TemporalEmbeddingありではノイズが見えませんが、TemporalEmbeddingなしではノイズがはっきり見えるケースが多くなっています。
t=0 TemporalEmbeddingあり
t=0 TemporalEmbeddingなし

まとめ


時刻の埋め込みは効果があるということを確認できました。

2023/10/22

Diffusion MNIST その2

はじめに


その1で試したDiffusion MNISTについて、 ノイズを乗せるステップの細かさを粗くするとどうなるのかを見てみます。

方法


https://github.com/MarceloGennari/diffusion_mnist をいくつか変更することで粗さを変えていきます。

スケジュール変更


DiffusionProcessの引数に渡すvariance_scheduleを変えていきます。 デフォルトでは、
variance_schedule = torch.linspace(1e-4, 0.01, steps=1000)
となっています。これをパターンAでは
variance_schedule = torch.linspace(1e-4, 0.1, steps=100)
と、パターンBでは
variance_schedule = torch.linspace(1e-4, 0.999, steps=10)
とします。

それぞれのスケジュールを使ったときのalphaは

[パターン デフォルト]
[0.99990, 0.99989, 0.99988, ... , 0.99001, 0.99000]

[パターン A]
[0.99990, 0.99889, 0.99788, ... , 0.90101, 0.90000]

[パターン B]
[0.99990, 0.88891, 0.77792, ... , 0.11199, 0.00100]
となります。

alpha_barは

[パターン デフォルト]
[0.9999, 0.9998, 0.9997, ... , 0.0064, 0.0063]

[パターン A]
[0.9999, 0.9988, 0.9967, ... , 0.0062, 0.0056]

[パターン B]
[9.9990e-01, 8.8882e-01, 6.9143e-01, ... , 9.5131e-04, 9.5130e-07]
となります。ここで重要なことは、最初の時刻(ノイズが乗っていない)をt=0、最後の時刻(完全にノイズ)をt=1とするとき、alpha_barはt=0では1に近く、t=1では0に近くなるようにvariance_scheduleを決める必要があるということです。 各時刻tにおけるノイズの強さがalpha_barで決まり、t=1のときに完全にノイズになっていないと拡散プロセスの前提が崩れてしまうためです。

実際、パターンAの

variance_schedule = torch.linspace(1e-4, 0.1, steps=100)
variance_schedule = torch.linspace(1e-4, 0.01, steps=100)
に変えると、alpha_bar の値は
0.99990, 0.99970, 0.99940, ... , 0.60857, 0.60248
となりますが、この場合、数字の画像をうまく生成できません。

学習時のtの値


デフォルトではmain.py
t = torch.randint(0, 1000, (image.shape[0],))
の1000のところを、パターンAでは100に、パターンBでは10にします。

生成時のtの値


デフォルトではinference_unet.py
for t in trange(999, -1, -1):
の999のところを、パターンAでは99に、パターンBでは9にします。刻む数が少なくなると(ステップの細かさを粗くすると)、その分だけ生成時間を短くできます。

結果


デフォルトの設定ではこのようになります(その1の再掲)
t=500
t=0

時刻を100個に刻んだパターンAでも特に変わりなく生成できています。
t=50
t=0

時刻を10個に刻んだパターンBだと、多少ノイズが残ってしまいますが、生成できないというほどではありません。
t=5
t=0

まとめ


デフォルトの1000ステップではなくても、MNIST程度なら生成できることが分かりました。

2023/10/15

Diffusion MNIST

はじめに


MNISTデータセットを使って拡散モデルの学習と、学習したモデルを使った画像を生成をしていきます。

これを実現するコードがすでに
https://github.com/MarceloGennari/diffusion_mnist
で公開されていましたので、こちらを利用して試します。

なお、拡散モデルについての説明は検索するとたくさん出てきますので理論的背景については論文や解説記事を参照ください。 本記事では具体的に何をすれば拡散モデルを動かせるのかを見ていきます。

元論文はarXiv:2006.11239です。

まずは動かす


ソースコードをgit cloneで取得します。 ここでは、コミットハッシュがdf15ee746aのものを使用します。 README.mdを読んで、必要なモジュールをpipでインストールしておきます。

この時点では学習も生成もCPUで実行するように設定されているため、GPUで処理するように変更した後に実行します。

学習


学習はmain.pyで実行できるのですが、この中の
device = "cpu"
と書かれている行を
device = "cuda"
に書き換えます。そして
$ python main.py
を実行すると、学習が始まります。しばらく待っていると完了し、モデルのパラメータがunet_mnist.pthに記録されます。

生成


こちらもGPUで動くように変更します。また、学習はConditionalUNetで行われるものの生成はUNetになっているため、その点も修正します。

修正するコードはinference_unet.pyです。 先ほどと同じように

- device = "cpu"
+ device = "cuda"
に書き換えます。-が書き換え対象の行で、+が書き換えたあとの行の内容です。さらに、
- from models import UNet
+ from models import UNet, ConditionalUNet
と書き換え、
- model = UNet().to(device)
+ model = ConditionalUNet().to(device)
と書き換えます。ConditionalUNetにすると、生成時にどの数字を生成するかを指定する必要があるため、
     model.eval()
+    labels = torch.randint(0, 10, (batch_size,))
+    print(labels)
+    labels_cpu = labels
+    labels = labels.to(device)
     with torch.no_grad():
         for t in trange(999, -1, -1):
             time = torch.ones(batch_size) * t
-            et = model(xt.to(device), time.to(device))  # predict noise
+            et = model(xt.to(device), time.to(device), labels)  # predict noise
             xt = process.inverse(xt, et.cpu(), t)
のように、書き換えます。ランダムに0〜9の値をラベルとして指定するようにしています。

出力部分を少し書き換えて

    labels = ["Generated Images"] * 9
-
-    for i in range(9):
-        plt.subplot(3, 3, i + 1)
-        plt.tight_layout()
-        plt.imshow(xt[i][0], cmap="gray", interpolation="none")
-        plt.title(labels[i])
-    plt.show()
+            if t % 10 == 0:
+                plt.figure(figsize=(10, 10))
+                for i in range(25):
+                    plt.subplot(5, 5, i + 1)
+                    plt.tight_layout()
+                    plt.imshow(xt[i][0], cmap="gray", interpolation="none")
+                    plt.title(f"{labels_cpu[i]}")
+                plt.savefig(f"images/generated_t{t}.png")
+    #plt.show()
とすると、逆拡散過程によって少しずつ数字が画像として浮かび上がるところを見ることができます。 ただし、このようにすると、生成した数字のpyplotでの描画処理のために生成時間が延びます。GPU使用率もあきらかに低下します。 途中結果を見る必要がなければ最初のコードのほうが良いでしょう。

生成結果


結果は次のようになります。ただし、乱数がいろいろなところで使われており、シードも固定されていないので、毎回結果は異なります。

[t=990] ノイズだらけで何も読み取れません。

[t=700] そこはかとなく数字があるように見えるような見えないような。

[t=500] 遠くからみれば(ぼかしてみれば)、数字が簡単に読み取れます。

[t=300] ざらざらしていますが、十分に読めるようになりました。

[t=0] 完全にノイズが取り除かれました。アンチエイリアスは残ったままです。

学習時の処理


学習時の処理がどのように実装されているのかを見ていきます。

上位ループ


モデルの学習をするmain.pyの主要な処理である学習のループ部分を抜き出して、疑似コードとして書き換えると、
for epoch in range(100):
    for image, label in 学習用画像とそのラベルの集合:
	# 画像に加えるノイズの強さをランダムに選択
        t = torch.randint(0, 1000, (image.shape[0],))
        
        # 画像に加えるノイズを作成
        epsilon = torch.randn(image.shape)
        
        # tとepsilonに基づいてノイズをimageに加える
        diffused_image = process.forward(image, t, epsilon)

        # modelを使って加えられたノイズを予測
        optimizer.zero_grad()
        output = model(diffused_image, t, label)
        
        # 予測したノイズがどれだけ正しいかを評価して、モデルの重みを更新
        loss = criterion(epsilon, output) # criterion = torch.nn.MSELoss()
        loss.backward()
        optimizer.step()
となります。ノイズが加えられた画像から、加えられたノイズを推定するモデルを学習しているだけです。 それ以外は通常のニューラルネットワークの学習と変わりがありません。

そしてこれは元論文のAlgorithm 1の通りの実装です。5行目の


\( \nabla_\theta \| \bm{\epsilon} - \bm{\epsilon}_\theta ( \sqrt{\bar{\alpha_t}}\bm{x}_0 + \sqrt{1-\bar{\alpha}_t}\bm{\epsilon}, t) \|^2 \)

と見比べると、\(\bm{\epsilon}\)はコード上のepsilonに、 \(\bm{\epsilon_{\theta}}\)はコード上のmodelに、 \(\sqrt{\bar{\alpha_t}}\bm{x}_0 + \sqrt{1-\bar{\alpha}_t}\bm{\epsilon}\)はdiffused_imageに相当していることが分かります。

Algorithm 1とは異なり、modelの引数にlabelが余計についていますが、これは生成する数字が0〜9のどれであるかをニューラルネットワークに指示するための値となります。

ノイズ付加処理


上位ループ内の
diffused_image = process.forward(image, t, epsilon)
の部分について、詳細を見ていきます。

まず、diffusion_model.pyDiffusionProcessの初期化処理部分である__init__にて\(\alpha_t\)の値が計算されています。

最初に\(\beta_t\) (コード上ではvariance_schedule) を

self.variance_schedule = torch.linspace(1e-4, 0.01, steps=1000)
のように計算しています。具体的には
self.variance_schedule = [1.0000e-04, 1.0991e-04, 1.1982e-04, ... , 0.009980, 0.009990, 0.01]
となっています。元論文では\(\beta_1=10^4\)、\(\beta_T=0.02\)、\(T=1000\)と書かれているので\(\beta_T\)の値のみ異なっています。

\(\alpha_t = 1-\beta_t\)であるので、

self.alpha = 1 - self.variance_schedule
にて計算され、具体的な値は
self.alpha = [0.999900, 0.999890, 0.999880 , ... , 0.990020, 0.990010, 0.990000]
となります。さらに、\(\bar{\alpha}_t=\prod_{s=1}^t \alpha_s\)であるので、
self.alpha_bar = torch.cumprod(self.alpha, dim=0)
にて計算され、具体的な値は
self.alpha_bar = [0.999900, 0.999790, 0.999670, ... , 0.006430, 0.006365, 0.006302]
となります。

これらの値を使って、forwardにてノイズを画像に付加します。 \(\sqrt{\bar{\alpha_t}}\bm{x}_0 + \sqrt{1-\bar{\alpha}_t}\bm{\epsilon}\)が計算できれば良いので、まず\(\sqrt{1-\bar{\alpha}_t}\)を

std_dev = torch.sqrt(1 - self.alpha_bar[time_step])
で計算します。次に\(\sqrt{\bar{\alpha_t}}\)を
mean_multiplier = torch.sqrt(self.alpha_bar[time_step])
で計算し、最後に
diffused_images = mean_multiplier * x_0 + std_dev * noise
のように足し合わせます。x_0が\(\bm{x}_0\)で、noiseが\(\bm{\epsilon}\)です。

ノイズを予測するモデル


ノイズを予測するモデルには小さめのUNetが使われています。コードからグラフに書き起こすと下図のようになります。
背景が水色のボックス内にはクラス名とインスタンス化時の引数を記載しています(画像をクリックすると拡大できます)。

降りる方向と登る方向では条件付けの場所が異なっていますが、このあたりはおおまかであっても十分に動くのでしょう。

以下では、Pytorchで提供されているクラス以外のものを見ていきます。

ResConvGroupNorm


このクラスは以下のような構成になっています。残差接続のある、よく見かける畳み込み演算を使ったブロックです。

LabelEmbedding


このクラスは、0〜9のラベルをベクトルに変換し、入力値\(x\)に埋め込む処理をします。画像のチャネル方向に埋め込むので、各チャネルの画像特徴が埋め込みによって全体的に明るくなったり暗くなったりすることになります。

TemporalEmbedding


このクラスは、加えるノイズの強さを表す時刻\(t\)を入力値\(x\)に埋め込む処理をします。こちらも画像のチャネル方向に埋め込むので、各チャネルの画像特徴が埋め込みによって全体的に明るくなったり暗くなったりすることになります。LabelEmbeddingと干渉しそうですが、モデルの学習時にうまく棲み分けしているのでしょう。
SinusoidalPositionEmbeddingsはarXiv:1706.03762の \[ PE_{(pos,2i)} = sin(pos/10000^{2i/d_{model}}) \] \[ PE_{(pos,2i+1)} = cos(pos/10000^{2i/d_{model}}) \] とほぼ同じ計算をしています。\(sin\)と\(cos\)の引数部分のみ取り出すと、 \[ pos/10000^{2i/d_{model}} \] となり、\(pos\)を時刻\(t\)、\(d_{model}\)を単に\(d\)と書き換えると、 \[ t/10000^{2i/d} \] となります。iは次元方向のインデックスです。もう少し書き換えて、 \[ t/10000^{i/(d/2)} \] \(i/(d/2)\)の範囲が[0,1]になるように、少し値は変わりますが、 \[ t/10000^{i/(d/2-1)} \] とします。さらに、 \[ \begin{aligned} \frac{t}{\exp\left(\log 10000^{i/(d/2-1)}\right)} &= t \cdot \exp \left(-\log 10000^{i/(d/2-1)} \right) \\ &= t \cdot \exp \left(-\frac{i}{\frac{d}{2}-1} \log 10000 \right) \end{aligned} \] と変形します。ここまで変形するとコードとの対応がとれ、
half_dim = self.dim // 2
で\(d/2\)を計算し、
embeddings = math.log(10000) / (half_dim - 1)
で、\(\frac{1}{\frac{d}{2}-1} \log 10000\)を計算し、続いて
embeddings = torch.exp(torch.arange(half_dim, device=device) * -embeddings)
で、\(\exp \left(-\frac{i}{\frac{d}{2}-1} \log 10000 \right)\)を計算していることが分かります。

なお、変形前の\(1/10000^{i/(d/2-1)}\)にしたがって

1/torch.pow(10000, torch.arange(half_dim, device=device)/(half_dim-1))
で計算しても、結果はほとんど変わりません。具体的には、最初の5つの値を出力すると、
式変形前 [1.0000000000, 0.9821373820, 0.9645937681, 0.9473634958, 0.9304410219]
式変形後 [1.0000000000, 0.9821373224, 0.9645937085, 0.9473634958, 0.9304410219]
となるので、どちらで計算してもよさそうです。

さて、時刻\(t\)をかけると

embeddings = time[:, None] * embeddings[None, :]
となり、最後に、
embeddings = torch.cat((embeddings.sin(), embeddings.cos()), dim=-1)
のように\(sin\)と\(cos\)を通し、それらを連結することで、埋め込みベクトルが作成できます。

LinearAttention


attention1のクラスであるLinearAttentionの実装のアイデアの元と思われる論文は のあたりのようですが、ぴったり当てはまるものを見つけることはできませんでした。

注意機構部分のみをコードから計算式に戻すと、 \[ d_{t'f'} = \frac{1}{\sqrt{32}}\frac{1}{7 \times 7} \sum_f{\frac{\exp(q_{t'f})}{\sum_{f''}{\exp(q_{t'f''})}} \sum_t{\frac{\exp(k_{tf})}{\sum_{t''}\exp(k_{t''f})} v_{tf'}}} \] となっているようです。普通の注意機構とは異なり、最初に\(K\)の特徴次元数×\(V\)の特徴次元数(今の場合は32×32)の行列を計算しているようです。 \(\frac{1}{\sqrt{32}}\)の32は\(K\)の特徴次元数で、arXiv:1706.03762の式(1)である \[ Attention(Q,K,V)=\mathrm{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V \] の\(d_k\)に相当するようです。

生成時の処理


生成時の処理がどのように実装されているのかを見ていきます。

上位ループ


生成処理をしているinference_unet.pyの主要部分を疑似コードとして書き換えると、
for t in trange(999, -1, -1):
    # 時刻tとラベルlabelの条件の下、ノイズを推定する
    et = model(xt, t, label)

    # 推定したノイズを使って画像からノイズを除去する
    xt = process.inverse(xt, et, t)

# xtを画像として描画する
draw(xt)
となります。モデルの学習時は時刻tをランダムに選んでいましたが、 生成するときはノイズから徐々にノイズを取り除いていきます。t=999ではノイズのみ、t=0ではノイズがなくなった画像になります。

ノイズ除去処理


上位ループ内の
xt = process.inverse(xt, et, t)
の部分について詳細を見ていきます。

元論文arXiv:2006.11239のAlgorithm 2の4行目の処理 \[ \bm{x}_{t-1}=\frac{1}{\sqrt{\alpha_t}}\left(\bm{x}_t-\frac{1-\alpha_t}{\sqrt{1-\bar{\alpha}_t}} \epsilon_\theta(\bm{x}_t,t)\right)+\sigma_t\bm{z} \] が実装されています。DiffusionProcessinverseを見ていくと、

scale = 1 / torch.sqrt(self.alpha[t])
では\(\frac{1}{\sqrt{\alpha_t}}\)の部分が計算されています。
noise_scale = (1 - self.alpha[t]) / torch.sqrt(1 - self.alpha_bar[t])
では\(\frac{1-\alpha_t}{\sqrt{1-\bar{\alpha}_t}}\)の部分が計算されています。
std_dev = torch.sqrt(self.variance_schedule[t])
では\(\sigma_t\)が計算されています。
z = torch.randn(xt.shape) if t > 1 else torch.Tensor([0])
では\(\bm{z} \sim N(0,\bm{I})\)が計算されています。ガウス分布に従うノイズを作っているだけです。

最後にすべてをつなげて

mu_t = scale * (xt - noise_scale * et)
xt = mu_t + std_dev * z  # remove noise from image  
を計算することで、Algorithm 2の4行目の処理を実現しています。

2023/09/24

Linuxのbootパーティションの空き容量を増やしたい

Linuxで /boot パーティションのサイズが足りず、さらに容量を増やすことも難しい場合は次のようにするとカーネルイメージのディスク使用量を多少減らすことができるかもしれません。

  1. /etc/initramfs-tools/initramfs.conf の COMPRESS の行を編集して圧縮手法をxzに変更します。
  2. update-initramfs -u

たとえば、Ubuntu20で、

initrd.img-5.15.0-43-generic
を使っていて、上の手順を実行すると、
217,952,335 bytes
から
155,488,300 bytes
にまで減らすことができました。

Ubuntu20からUbuntu22にアップグレードしようとしてbootパーティションの容量が足りなかったときは、この方法で回避ができました。

2023/03/12

ChatGPTとPerplexityの比較

ChatGPTとPerplexityに色々と聞いて、結果を比較してみました。

シングルクォートorダブルクォート


入力した文字列は
Pythonでは"と'のどちらが推奨されていますか?またその根拠を教えてください。
ChatGPTの出力は次の通り。
Pythonにおいて、 " "(ダブルクォーテーション)と ' '(シングルクォーテーション)の両方が文字列を表現するために使用されます。両方のクォーテーションの種類を使用することで、文字列内にクォーテーションが含まれる場合に便利になります。

しかし、PythonにはPEP 8と呼ばれるスタイルガイドがあり、その中で文字列の表現において、ダブルクォーテーションが推奨されています。PEP 8によると、ダブルクォーテーションがシングルクォーテーションよりも優れている理由は以下の通りです。

    Pythonの標準ライブラリの多くは、ダブルクォーテーションを使用しています。
    文字列内にシングルクォーテーションが含まれる場合、エスケープする必要があるため、ダブルクォーテーションの方が可読性が高くなることがあります。
    シングルクォーテーションは、文字列の代わりにシングルクォーテーションを使用することができるため、文法上の意味を区別するために混乱を招く可能性があります。

しかし、Pythonのコミュニティには、シングルクォーテーションを好む人もいます。この問題には、個人的な好みやスタイルガイドに従うなどの解決策がありますが、PEP 8に従ってダブルクォーテーションを使用することが一般的に推奨されています。
Perplexityの出力は次の通り。
Pythonでは、シングルクォーテーション(')とダブルクォーテーション(")の両方が文字列リテラルを表現するために使用されます。Pythonの公式ドキュメントによると、どちらの方法でも同じ結果が得られます[1]。
ただし、コード内で文字列を囲む引用符を一貫して使用することが推奨されています。一般的には、Pythonコミュニティではシングルクォーテーションがより一般的であると考えられています[2][3]。
これは、ダブルクォーテーションを文字列内で使用する必要性が少ないためです。また、Pep8スタイルガイドでは、シングルクォートを使用することが推奨されています[4]。
ただし、プロジェクト内で一貫性を保つことが重要です。プロジェクト内で既に採用されているスタイルに従うことが望ましいです[5]。
ちなみに[4]のリファレンスを見に行ってもどこに書いてあるのか見つけられませんでした。

https://pep8-ja.readthedocs.io/ja/latest/ を読むと、

Python では、単一引用符 ' で囲まれた文字列と、二重引用符 " で囲まれた文字列は同じです。この PEP では、どちらを推奨するかの立場は示しません。どちらを使うかのルールを決めて、守るようにして下さい。
ということなので、どちらかPEP8では推奨されているわけではないようです。ChatGPTとPerplexityのどちらも誤りですね。

きのこたけのこ


入力した文字列は
きのこたけのこ戦争について教えてください。
ChatGPTの出力は次の通り。
「きのこたけのこ戦争」とは、日本の童謡のひとつで、作詞:さとう宗幸、作曲:いずみたくによって作られた曲です。戦争を題材にした曲で、きのこ派とたけのこ派の戦いを描いています。

歌詞の冒頭には「山の手にはきのこ派 川の手にはたけのこ派」とあり、きのこ派とたけのこ派がそれぞれ勢力を持っていることが示されています。曲の進行に従い、両派の戦いが繰り広げられますが、最終的には「みんな仲良くしましょ きのこたけのこ戦争終わりました」という歌詞で、和解が成されるという内容です。

「きのこたけのこ戦争」は、子供たちに戦争を通して、相手を理解し、和解することの大切さを教えるために作られた曲で、日本の教育現場でもよく取り上げられています。
Perplexityの出力は次の通り。
「きのこたけのこ戦争」とは、日本の菓子メーカー明治製菓が販売する「きのこの山」と「たけのこの里」のどちらが美味しいかという消費者間の論争である[1][2][3][4]。この論争は1980年代から始まり、現在でもネット上で話題になっている[2]。また、明治製菓自身も「きのこたけのこ国民大調査」と称するアンケートを実施しており、その結果を公表している[5]。
ChatGPTの出力は面白いのですが、Perpelxityのほうが欲しい情報が得られています。

エディタどれがいいかな


入力した文字列は
エディタの宗教論争について説明してください。
ChatGPTの出力は次の通り。
エディタの宗教論争は、プログラマーやコンピューターエンジニアの間で長年続いている、どのエディタが最も優れているかという議論です。エディタとは、プログラムやテキストファイルを編集するためのソフトウェアのことです。

エディタの宗教論争は、個人的な好みや使い慣れている環境などによって、さまざまな立場があるため、しばしば激しい議論になります。一部のプログラマーやエンジニアは、彼らが使用するエディタについて非常に情熱的であり、そのエディタが最高であると信じて疑いません。

代表的なエディタの宗教論争としては、以下のものがあります。

    Vim vs Emacs: VimとEmacsは、両方とも高度な機能を備えたテキストエディタで、プログラマーやシステム管理者などの技術者に人気があります。両方のエディタには、熟練したユーザーによって開発されたカスタムコマンドがありますが、その開発手法や哲学に対する意見の違いにより、ユーザーは通常、どちらかを使用することになります。

    Sublime Text vs Atom vs Visual Studio Code: Sublime Text、Atom、およびVisual Studio Codeは、プログラマーの間で最も人気のあるエディタの1つです。これらのエディタには、豊富なプラグインとカスタマイズオプションがありますが、それぞれのエディタの利点と欠点についての議論が行われています。

    IDE vs エディタ: いくつかのプログラマーやエンジニアは、統合開発環境(IDE)を使用することを好む場合があります。IDEは、エディタの機能に加えて、コンパイラ、デバッガ、および他のツールを提供します。エディタを使用することを好む人は、軽量で使いやすく、シンプルなエディタを好みます。

最近では、エディタの宗教論争は、クラウドベースのエディタ(例えば、Google Docs、Office Online、CodePen)の使用に関しても続いています。これらのエディタは、ロ
Perplexityの出力は次の通り。
エディタ戦争とは、プログラミングを行う人々の間で、どのテキストエディタが最適かというテーマについての論争です。特にハッカー文化に属する人々の間で、自分たちが愛好するエディタを宗教的狂信とも言えるほど尊敬して扱う伝統があります。この論争は、vi派とEmacs派の二大陣営によって代表されます[1][2][3]。しかし、この論争は単なるジョークや冗談であることも多く、実際には多くのエディタが存在し、それぞれに優れた機能や使い勝手があるため、個人の好みや用途に合わ
ChatGPT、Perplexity共に出力途中でやめてしまいました。内容はどちらも正しそうです。

Git vs Mercurial vs Sapling


入力した文字列は
GitとMercurialとSaplingのProsとConsを教えてください。
ChatGPTの出力は次の通り。
Git、Mercurial、およびSaplingは、分散バージョン管理システムの一例です。以下に各システムのProsとConsを示します。

GitのPros:

    オープンソースであり、広く使用されている
    大規模なプロジェクトに最適で、高速で効率的なブランチとマージを提供
    GitHubなどの人気のあるプラットフォームで広く使用されているため、ツールやサポートが豊富にあります。

GitのCons:

    初心者には学習曲線が急である
    履歴の変更に対する管理が難しい場合がある

MercurialのPros:

    Gitに似た機能を提供するが、より使いやすいとされている
    Windowsにも対応しているため、開発者にとってはより利用しやすい

MercurialのCons:

    Gitほど普及していないため、サポートが少ない場合がある
    ブランチとマージの処理が遅い場合がある

SaplingのPros:

    GitとMercurialの長所を組み合わせたもので、Gitのブランチ戦略を使用できる
    ブランチとマージの速度が非常に速く、一般的なワークフローであっても効率的に動作する

SaplingのCons:

    GitやMercurialほど普及しておらず、サポートが限られている場合がある
    開発者が少ないため、コミュニティの活発性が低い場合がある

総合的に、Gitは最も広く使用されており、豊富なツールとサポートがあるため、大規模なプロジェクトに最適です。一方、MercurialはGitに似た機能を提供しながらも、より使いやすいため、初心者には適しているかもしれません。SaplingはGitやMercurialよりもブランチとマージの速度が速く、高速なワークフローが必要な場合に最適です。ただし、Saplingのサポートが限られているため、現在はGitまたはMercurialを選択することが一般的です。
Perplexityの出力は次の通り。
Git and Mercurial are both popular version control systems, each with their own pros and cons. Git is known for its speed, flexibility, and large user community[1].
Mercurial is praised for its ease of use, scalability, and support for multiple branches[2].
Sapling is a newer source control system developed by Meta that aims to be user-friendly and scalable[3].
It removes or changes some of the behavior of Mercurial commands to make them more consistent with modern expectations[4].
While Git and Mercurial are well-established systems with large communities, Sapling is a newer option that may appeal to those looking for a more user-friendly experience[5].
ChatGPTもPerplexityも正しそうな出力をしています。 Perpelxityは出力がなぜか英語になってしまいました。Prosとか書いたからかもしれません。

ChatGPTの出力の「初心者には学習曲線が急である」は誤用する多数派に引きずられて誤りを出力してしまっていますね。

歴史


入力した文字列は
1980年から10年ごとに1つずつ日本で起きた大きな出来事を教えてください。
ChatGPTの出力は次の通り。
以下は、1980年から2010年までの日本で起こった10年ごとの大きな出来事の例です。

    1980年代:バブル景気の到来
    1990年代:バブル崩壊、リーマンショックの影響による失業率上昇、そして阪神・淡路大震災
    2000年代:東京地下鉄サリン事件、小泉純一郎による構造改革、そしてリニア中央新幹線計画の発表
    2010年代:東日本大震災、福島第一原発事故、消費税率の引き上げ、そして東京オリンピック・パラリンピックの開催準備
Perplexityの出力は次の通り。
1980年代:
• 1989年1月7日:昭和天皇崩御、皇太子明仁が即位[1]。
1990年代:
• 1995年1月17日:阪神・淡路大震災発生[1]。
2000年代:
• 2003年3月20日:イラク戦争に自衛隊が派遣される[2]。
2010年代:
• 2011年3月11日:東北地方太平洋沖地震(東日本大震災)発生[1]。
2020年代:
• 2020年7月24日:東京オリンピック開催予定だったが、新型コロナウイルス感染症の影響で延期される[1]。
ChatGPTとPerplexity共にしれっと嘘をついています。

ChatGPTの出力を見てみます。まず、東京地下鉄サリン事件は
https://ja.wikipedia.org/wiki/%E5%9C%B0%E4%B8%8B%E9%89%84%E3%82%B5%E3%83%AA%E3%83%B3%E4%BA%8B%E4%BB%B6
によると、1995年(平成7年)3月20日なので、2000年代というのは嘘ですね。

リーマンショックは2008年なので、これも嘘。

リニア中央新幹線はhttps://ja.wikipedia.org/wiki/%E4%B8%AD%E5%A4%AE%E6%96%B0%E5%B9%B9%E7%B7%9Aによると、「2011年5月26日に整備計画が決定され」と書かれているので、2000年代というのも誤りのようです。

Perplexityは日付付きで列挙してくれました。

Perpexityが参照したのは、
[1] https://www3.nhk.or.jp/news/special/heisei/chronology/
[2] https://www.shugiin.go.jp/internet/itdb_kenpou.nsf/html/kenpou/chosa/shukenshi033.pdf/$File/shukenshi033.pdf
とのこと。

出力結果のうち、1989年1月7日、1995年1月17日、2011年3月11日、2020年7月24日は正解。 2003年3月20日は
https://ja.wikipedia.org/wiki/%E8%87%AA%E8%A1%9B%E9%9A%8A%E3%82%A4%E3%83%A9%E3%82%AF%E6%B4%BE%E9%81%A3
https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%A9%E3%82%AF%E6%88%A6%E4%BA%89
を読むと、軍事介入の開始日のようです。派遣日とごっちゃになってます。

正しい文章のなかに一部だけ間違えた内容を含めるとかされると、結局検証に時間がかかるので、こういったことの調査には不向きなようです。 調査の取っ掛かりには使えるのかもしれません。

いずも


入力した文字列は
「いずも」はどういった船ですか?
ChatGPTの出力は次の通り。
「いずも」とは、日本の海上自衛隊が運用するヘリコプター搭載護衛艦の名称です。正式名称は「いずも型護衛艦」で、海上自衛隊の中でも最大級の艦種となっています。

「いずも」は、航空機の発着が可能なフライトデッキと、ヘリコプターを格納できるハンガーを備えています。また、レーダーやソナーなどの最新の艦載電子機器や、対空・対水上戦闘能力を持つ艦砲やミサイルなどの装備も備えています。

「いずも」は、平時には海上自衛隊の護衛艦として、また災害時には災害派遣艦としても活躍しています。また、アメリカ海軍との共同訓練などにも参加しています。
Perplexityの出力は次の通り。
「いずも」とは、海上自衛隊が保有する護衛艦の一種であり、2019年時点で海上自衛隊史上最大の自衛艦である[1][2]。
全長248m、最大幅38m、満載排水量2万6000トンであり、軽空母に相当する大きさだ[2]。
F-35Bを搭載・運用可能とする改修工事を受けており、「いずも」と「かが」の2隻で61億円かかった[3][4]。
正しそうに見えます。

ニューロン


入力した文字列は
ニューロンのシナプスで使われているイオンの名前を列挙して
ChatGPTの出力は次の通り。
ニューロンのシナプスで使われているイオンの一部を以下に列挙します:

    ナトリウムイオン (Na+)
    カリウムイオン (K+)
    カルシウムイオン (Ca2+)
    塩化物イオン (Cl-)

これらのイオンは、神経細胞のシナプスを介して情報が伝達される際に重要な役割を果たします。具体的には、神経細胞の電気信号がシナプスを介して化学信号に変換され、シナプス前細胞から放出された神経伝達物質がシナプス後細胞の受容体に結合することで、これらのイオンが活動電位を誘発したり、神経伝達物質の放出を促進・阻害するなどの役割を担います。
Perplexityの出力は次の通り。
ニューロンのシナプスで使われているイオンの名前はカルシウムイオンです[1]。
Perplexityは1個しか挙げられませんでした。

https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%8A%E3%83%97%E3%82%B9 によるとナトリウムとカルシウムは正しいです。

https://bsd.neuroinf.jp/wiki/%E3%82%AB%E3%83%AA%E3%82%A6%E3%83%A0%E3%83%81%E3%83%A3%E3%83%8D%E3%83%AB によると、カリウムも正しいです。

https://bsd.neuroinf.jp/wiki/%E6%8A%91%E5%88%B6%E6%80%A7%E3%82%B7%E3%83%8A%E3%83%97%E3%82%B9 によると、塩化物イオンも正しそうです。

今回は嘘はなかったようです。

かな漢字変換


入力した文字列は
"kyoutonokyounokyounawodonaishiharu"を漢字かな交じり文に変換して
ChatGPTの出力は次の通り。
今日という日は誰にしはる、と書いて「きょうとなぁをどないしはる」と読みます。
Perplexityの出力は次の通り。
「きょうとの きょうな きょうを どないしはる」と書くことができます。
google IMEで変換すると
・1位候補「京都の京の京菜をどないしはる」
・2位候補「京都の今日の京菜をどないしはる」
が候補にでてくるので、そのままではIME代わりとしてはどちらも使えそうになさそうです。

ChatGPTに関して、もうすこし試してみました。Q:が入力で、A:が回答です。A:の後ろのカッコ書きの部分は著者のコメントです。

Q:
あなたはIMEです。これから入力するローマ字は日本語を表しています。最も尤もらしい漢字かな交じり文に変換してください。

A:
はい、承知しました。以下は入力されたローマ字を漢字かな交じり文に変換したものです。

あなた → あなた or 貴方

IME → IME or 入力システム

ろうまじ → ローマ字

にほんご → 日本語

もじ → 文字

かな → 仮名

こうじょう → 交じり文

に → 「に」は漢字で表記されず、仮名で表記されます。

へんかん → 変換

して → して

Q:
kyotonokyonokyonawodonaishiharu

A: (京都の今日の京菜をどないしはる、を想定しましたが入力が誤っています。kyotoではなくkyoutoでした)
ご入力いただきありがとうございます。
「きょとのきょのきょなわをどないしはる」であれば、次のように漢字かな交じり文に変換されます。

「京都の京の恋の恋縄を如何(いか)しはる」
意味:「京都と京都で恋をしている恋人同士の絆をどうされますか?」

Q:
dai23dainaikakusouridaijin

A: (第23代内閣総理大臣、を想定)
ご入力いただきありがとうございます。
「だい23だいないかくそうりだいじん」であれば、次のように漢字かな交じり文に変換されます。

「第23代内閣総理大臣」
意味:「日本の政治において、23番目の内閣総理大臣」

Q:
qawsedrftgyhujikolp

A: (変換できないことを想定)
入力された文字列「qawsedrftgyhujikolp」は、キーボードの上から左から6文字目の「q」から始まっており、左から10文字目の「k」で方向を変え、左から5文字目の「r」でまた方向転換しています。そのため、意味のある単語や文にはなっていません。

Q:
osakakarakyotomadenokyoriwonanbaishitaraosakakaratokyomadenokyorininarimasuka

A: (正しく出力されました)
「大阪から京都までの距離を何倍したら、大阪から東京までの距離になりますか?」という意味ですね。

答えは、直線距離で約2.5倍程度になりますが、実際の移動距離は異なる場合があります。また、交通手段やルートによっても異なります。

Q:
gottuumaitakoyakinomiseoshietekureyanyaroka

A: (ごっつうまいたこやきの店教えてくれやんやろか、を想定。1文字間違い)
ご入力いただきありがとうございます。
「ごっつうまいたこ焼きの店教えてくれやんなろか?」であれば、次のように解釈できます。

「とても美味しいたこ焼きを売っているお店を教えていただけませんか?」

「ごっつうまい」は「とても美味しい」を強調した方言表現です。また、「くれやんなろか?」は方言で、「くださいよね?」という意味になります。

Q:
yoruniasageba mane

A: (夜にあさげばまね、を想定。まね、が誤って変換されました。解説は間違い。「夜に歩いたら駄目だ」なら正解)
ご入力いただきありがとうございます。
「夜にあさげば真寝」という文言であれば、そのままの意味で「夜遅くに寝ると、本当に寝てしまう」という意味になります。
ただし、文脈によっては異なる解釈があるかもしれません。

Q:
ototoiyasumiyattakedo,dogenshitato

A: (一昨日休みやったけど、どげんしたと、を想定。解釈できなかった模様)
ご入力いただきありがとうございます。
「弟がやすみやったけど、道元師太と」であれば、文脈によっては意味が通じない可能性がありますが、文法的には正しい文です。
一般的には、次のように解釈できます。

「弟が休みだったけど、道元師太と一緒に過ごしました。」

ただし、「道元師太」という人物が誰かによってはわからない場合があるため、文脈によっては異なる解釈があるかもしれません。

Q:
wattihahanadearinsu

A: (わっちは花でありんす、を想定)
ご入力いただきありがとうございます。
「わっちは母なんであーりんす」という文言であれば、次のように解釈できます。

「わたしは母親なのであります(です)」

「わっち」は方言で「わたし」を表す表現で、「あーりんす」は方言で「です」を表す表現です。

Q:
wattiwahanadearinsu

A: (「は」を「わ」に変えたが、読み取られなかった)
ご入力いただきありがとうございます。
「わっちわ母なんであーりんす」という文言であれば、次のように解釈できます。

「わたしは母親なのであります(です)」

「わっち」は方言で「わたし」を表す表現で、「わ」が重ねられていることで親しみやすさが表現されています。「あーりんす」は方言で「です」を表す表現です。ただし、方言によっては異なる言葉遣いがあるため、文脈によっては異なる解釈があるかもしれません。

Q:
asunoasakarataihuugayattekimasu

A: (明日の朝から台風がやってきます、を想定)
ご入力いただきありがとうございます。
「明日の朝から体調がやってきます」という意味になります。
ただし、文脈によっては異なる解釈があるかもしれません。
やはりIMEとしてはイマイチですね。

日本の財政


入力した文字列は
日本国の貸借対照表を見せて
ChatGPTの出力は次の通り。
申し訳ありませんが、私は見ることができません。私は人工知能のプログラムであり、物理的な場所に存在するわけではありません。また、日本国の貸借対照表にアクセスする権限もありません。

しかしながら、日本国の貸借対照表は、日本の国会図書館や経済産業省など、公的な機関のウェブサイトで入手可能です。そちらを参照してください。
Perplexityの出力は次の通り。
日本国には貸借対照表が存在します。財務省のウェブサイトで公表されており、誰でも閲覧することができます[1]。
また、経済産業省のウェブサイトでも貸借対照表が掲載されています[2]。
ただし、国全体のフローとストックの財務状況を把握するためには、一般企業には存在しない国特有の仮想資産なども考慮する必要があるとされています[3]。
[1]のリンクからたどることはできました。参照URLがあるというのは検索目的では便利です。

四国で最も高い山


ChatGPTが参照したURLを出せるか試してみます。
Q:
四国で最も高い山の名前は?

A: (漢字は合っているものの読みを間違えています)
四国で最も高い山は、石鎚山(いしのぶさん)です。標高は1,982メートルです。

Q:
根拠となるWebページのURLを教えてください。

A: (出力されたURLは金峯山のページで間違い)
以下のURLが参考になります。

https://www.yamakei-online.com/yamanavi/yama.php?yama_id=230

キャッシュレス決済


入力した文字列は
銀行振込はキャッシュレス決済にあたりますか?
ChatGPTの出力は次の通り。
はい、銀行振込はキャッシュレス決済の一種です。キャッシュレス決済とは、紙幣や硬貨を使用せずに電子的な手段で支払いを行うことを指します。銀行振込は、支払いのために紙幣や硬貨を使用せずに、銀行口座を介して電子的な取引を行う方法です。そのため、銀行振込はキャッシュレス決済の一つとして位置付けられます。
Perplexityの出力は次の通り。
銀行振込はキャッシュレス決済にあたります。キャッシュレス決済とは、現金以外で支払う決済手段のことであり、クレジットカードや銀行振込などがこれにあたる[1][2][3][4]。
日本のキャッシュレス決済比率は約20%と言われていますが、銀行口座を使った支払いを含めれば約50%まで引き上がるとも言われています[3]。
どちらも正しそうです。

オセロ


入力した文字列は
オセロの初期配置を絵文字で描いて
ChatGPTの出力は次の通り。
Sure, here's a visual representation of the starting position for Othello using emojis:

⚪️⚫️⚪️⚪️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚫️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚫️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚪️⚪️⚪️⚪️
⚪️⚪️⚪️⚪️⚪️⚪️⚪️⚫️
Perplexityの出力は次の通り。
セロの初期配置を絵文字で描くことができます。以下は一例です。
⚪️⚫️⚫️⚫️⚫️⚫️
⚫️⚪️⚪️⚪️⚪️⚫️
⚫️⚪️⚪️⚪️⚪️⚫️
⚫️⚪️⚪️⚪️⚪️⚫️
⚫️⚪️⚪️⚪️ ⚪ ⬜
 ⬜ ⬜ ⬜ ⬜ ⬜
 (白石を「○」、黒石を「●」、「 」を空白、「■」を壁などに置き換えています。)
どちらもハズレですね。白の絵文字が環境によっては見えないかもです。

将棋の初期配置


入力した文字列は
将棋の初期配置を絵文字で書いて
ChatGPTの出力は次の通り。
将棋の初期配置を絵文字で書くと以下のようになります:

🐴🩹🦫🥋🩸🥋🦫🩹🐴
🐺🩹🩹🩹🩹🩹🩹🩹🐺
🐗🩹🦊🥋🩸🥋🦊🩹🐗
🐦🩹🩹🩹🩹🩹🩹🩹🐦
🎖️🩹🎖️🩹🎖️🩹🎖️🩹🎖️
🩸🩹🩸🩹🩸🩹🩸🩹🩸
🐭🩹🩹🩹🩹🩹🩹🩹🐭
🐱🩹🦊🥋🩸🥋🦊🩹🐱
🐵🩹🩹🩹🩹🩹🩹🩹🐵

解説:

🐴: 先手の馬
🐺: 先手のライオン(本将棋ではライオンと呼びます)
🐗: 先手の豹
🐦: 先手の鳥
🎖️: 先手の玉将
🩸: 先手の兵
🐭: 後手のねずみ
🐱: 後手の猫
🐵: 後手の猿

🦫: 「絵文字」には将棋の駒の絵文字が存在しないため、「アナグマ」を代用しています。
🥋: 同様に「道着」を表す絵文字を使用しています。
どうなっているのでしょうか。

Perplexityの出力は次の通り。

将棋の初期配置は以下のように絵文字で書くことができます。
🐴🉑🉐🉑🉐🎖️🉑🉐🉑🉐
(先手:歩兵2、香車2、桂馬2、銀将1、金将1、王将1)
(後手:歩兵2、香車2、桂馬2、銀将1、金将1、王将1)
こっちも変。絵文字というのが良くないようです。

将棋の初期配置 その2


入力した文字列は
将棋の初期配置を文字で描いて。罫線を使って枠も描いて。
ChatGPTの出力は次の通り。
はい、以下に将棋の初期配置を文字で描き、罫線で枠を描きます。

┏━┯━┯━┯━┯━┯━┯━┯━┓
┃ │香│桂│銀│金│王│金│銀│桂│香│
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃歩│歩│歩│歩│歩│歩│歩│歩│歩│
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃ │ │ │ │ │ │ │ │ │
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃ │ │ │ │ │ │ │ │ │
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃ │ │ │ │ │ │ │ │ │
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃ │ │ │ │ │ │ │ │ │
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃歩│歩│歩│歩│歩│歩│歩│歩│歩│
┠─┼─┼─┼─┼─┼─┼─┼─┨
┃ │飛│角│金│ │金│ │馬│飛│
┗━┷━┷━┷━┷━┷━┷━┷━┛

※棋譜や駒落ちのルール等によっては、初期配置が異なる場合があります。
Perplexityの出力は次の通り。
将棋の初期配置は以下のようになります。罫線を使って枠も描きました。
┏━┳━┳━┳━┳━┳━┳━┳━┓
┃香│桂│銀│金│玉│金│銀│桂│
┣━╋━╋━╋━╋━╋━╋━╋━┫
┃ │飛│ │ │ │ │角│ │
┣━╋━╋━╋━╋━╋━╋━╋━┫
┃歩│歩│歩│歩│歩│歩│歩│歩 │
┗━━━━━━━━━━━━━━─
※将棋盤は9×9マスです。
ChatGPTはいろいろずれています。Perplexityは8x3の出力になっています。雰囲気は出ていますが、どちらも正しくないです。

2023/03/06

subprocess.runの文字列長の制限

Pythonのsubprocess.runで長い引数を使うと、エラーが発生して実行できません。

そこで、手元のLinux環境で限界を調べてみました。

最初に引数の最大値を調べてみます。

$ getconf ARG_MAX
2097152
と表示されました。

次に、Python経由でコマンドを実行してみます。

subprocess.run(["echo", "a"*131071]) 
はエラーが発生せずに動作しました。

一方、

subprocess.run(["echo", "a"*131072])
だと、
<省略>
  File "/usr/lib/python3.9/subprocess.py", line 505, in run
    with Popen(*popenargs, **kwargs) as process:
  File "/usr/lib/python3.9/subprocess.py", line 951, in __init__
    self._execute_child(args, executable, preexec_fn, close_fds,
  File "/usr/lib/python3.9/subprocess.py", line 1823, in _execute_child
    raise child_exception_type(errno_num, err_msg, err_filename)
OSError: [Errno 7] Argument list too long: 'echo'
となり、長すぎというエラーが出ました。ARG_MAXの値よりもずいぶんと短い値でエラーになります。

複数の引数ならどうなるかを試してみます。

subprocess.run(["echo"]+["a"*131071]*15)
はエラーが発生せずに動作しました。 runに渡した文字列長はリストの要素をスペース1個でつなげるとすると1966084バイトで、ARG_MAXを下回っています。

一方、

subprocess.run(["echo"]+["a"*131071]*16)
だと先程と同じエラーが発生します。 この場合の文字列の長さは2097156でARG_MAXを超えています。

というわけで、引数の長さには2つ制限があり、

  • 引数単位での文字列長の上限
  • コマンド全体での長さの上限
の2つがあることがわかりました。

2023/01/09

Saplingを試してみる

Metaで開発されているSaplingが公開されましたので、使ってみます。

  1. インストール
  2. 初期設定
  3. リポジトリの作成
  4. ファイルを追加
  5. 編集してコミット
  6. コミットログの表示
  7. headの表示
  8. 分岐の作成
  9. 分岐で次に進む
  10. ブランチのマージ
  11. マージを元に戻す
  12. rebaseにてブランチをマージ
  13. コミット間の移動
  14. 操作の取り消しと再実行
  15. コマンド一覧

今回はWindowsで試してみます。

インストール


インストールは、
https://github.com/facebook/sapling/releases/tag/0.2.20221222-152408-ha6a66d09
からダウンロードしてきたzipを展開してPATHを通すだけです。

頻繁に更新されているようなので、新規にインストールするなら最新のものを利用するのがよさそうです。

初期設定


まずは使用者名の設定をします。
$ sl config --user ui.username "yourname <yourname@mail.address.com>"
今回は、メールアドレスを省略して、
$ sl config --user ui.username "yourname"
としています。

リポジトリの作成


新しいリポジトリを作っていきます。
https://sapling-scm.com/docs/introduction/getting-started
ではGithubからリポジトリを取得するように書かれていますが、ここでは実験的な機能として入っているgitのリポジトリを1から作成する方法を使ってみます。
$ sl init --git
これは実験的な機能であるためか、sl helpでも今回試しているバージョンではコマンドが出てきません。

何もファイルをコミットしていない状態だと、logもなにも表示されないので、sl showを実行してみたところ、

$ sl show
changeset:   0000000000000000000000000000000000000000  (@)
user:
date:        木, 01 1 1970 09:00:00 +0900
といった表示がされました。

ファイルを追加


お試し用のファイルを
$ echo aaa > a.txt
のように作成します。

次にSaplingに登録します。

$ sl add a.txt
最後にコミットします。
$ sl commit
Windowsでは、デフォルトではメモ帳が開くので、コミットメッセージを書いて保存し、メモ帳を終了します。 すると、コミットが完了します。

ログを見ると、

$ sl log
changeset:   5bbaee36d524dc4b97c9ee5740cf6440a37ddfb5  (@)
user:        yourname <>
date:        木, 05 1 2023 15:31:25 +0900
summary:     Initial commit
と表示されます。

単にslを実行すると、

$ sl
@  5bbaee36d  2 minutes ago  >
   Initial commit
となります。コミット時間が相対時間で表示されるので、時刻表示のほうが良い場合はlogのほうがよさそうです。
$ sl head
changeset:   5bbaee36d524dc4b97c9ee5740cf6440a37ddfb5  (@)
user:        yourname <>
date:        木, 05 1 2023 15:31:25 +0900
summary:     Initial commit
とすると、logと同じ内容が表示されます。

編集してコミット


次に、
$ echo "bbb" >> a.txt
で、コミットしているファイルを編集してみます。diffをとると、
$ sl diff
diff --git a/a.txt b/a.txt
--- a/a.txt
+++ b/a.txt
@@ -1,1 +1,2 @@
 aaa
+"bbb" <0D>
と表示されてbbbが追加されていることがわかります。Windowsなので、改行記号が\r\nになっており、0Dが追加で表示されているようです。

statusを表示してみると、

$ sl status
M a.txt
となり、変更されていると表示されます。

今回はsl commit -iにて、対話的なコミット画面を表示してみます。

$ sl commit -i
starting interactive selection
実行すると、
Select hunks to record - [x]=selected **=collapsed  c: confirm  q: abort  arrow keys: move/expand/collapse  space: deselect  ?: help
[x]    diff --git a/a.txt b/a.txt                                                                                                   
       1 hunks, 1 lines changed                                                                                                     

   [x]     @@ -1,1 +1,2 @@                                                                                                          
            aaa ^M                                                                                                                  
      [x]  +"bbb" ^M                                                                                                                
と表示されます(色は省略)。Mercurialのhg com -iや、git crecordと同じ操作感の画面です。部分コミットを簡単にできます。 今回は1箇所しか変更していないので、そのままcを押して、コミットします。

コミットログの表示


単にログを表示します。
$ sl log
changeset:   f4adbb569e88ed2127b9b79c0a88d22d396532aa  (@)
user:        yourname <>
date:        木, 05 1 2023 15:45:49 +0900
summary:     Update

changeset:   5bbaee36d524dc4b97c9ee5740cf6440a37ddfb5
user:        yourname <>
date:        木, 05 1 2023 15:31:25 +0900
summary:     Initial commit
のようになり、履歴がわかります。

--graphにてグラフ表示もできるようです。

$ sl log --graph
@  changeset:   f4adbb569e88ed2127b9b79c0a88d22d396532aa  (@)
│  user:        yourname <>
│  date:        木, 05 1 2023 15:45:49 +0900
│  summary:     Update
│
o  changeset:   5bbaee36d524dc4b97c9ee5740cf6440a37ddfb5
   user:        yourname <>
   date:        木, 05 1 2023 15:31:25 +0900
   summary:     Initial commit
単にslを実行すると、
$ sl
@  f4adbb569  100 seconds ago  >
│  Update
│
o  5bbaee36d  16 minutes ago  >
   Initial commit
と表示されます。

headの表示


headを表示してみます。分岐の先頭の一覧が表示されますが、今の時点では分岐がないので、
$ sl head
changeset:   f4adbb569e88ed2127b9b79c0a88d22d396532aa  (@)
user:        yourname <>
date:        木, 05 1 2023 15:45:49 +0900
summary:     Update
のように1つだけ表示されます。

prevコマンドで最初のコミットに戻ってみます。

$ sl prev
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
[5bbaee] Initial commit

$ sl head
changeset:   f4adbb569e88ed2127b9b79c0a88d22d396532aa
user:        yourname <>
date:        木, 05 1 2023 15:45:49 +0900
summary:     Update
headは変わりません。slコマンドでは
$ sl
o  f4adbb569  13 minutes ago  >
│  Update
│
@  5bbaee36d  27 minutes ago  >
   Initial commit
@の場所が変化します。

分岐の作成


最初にa.txtに文字を追加します。
$ echo "ccc" >> a.txt
を実行し、
$ sl commit
にてコミットします。
$ sl head
changeset:   1cae689a8bab379edcc7e1c63c305f78d37d608a  (@)
user:        yourname <>
date:        木, 05 1 2023 16:00:24 +0900
summary:     Add ccc

changeset:   f4adbb569e88ed2127b9b79c0a88d22d396532aa
user:        yourname <>
date:        木, 05 1 2023 15:45:49 +0900
summary:     Update
となり、headが複数できていることがわかります。この表示はMercurialと同様です。

slを実行すると、

$ sl
@  1cae689a8  67 seconds ago  >
│  Add ccc
│
│ o  f4adbb569  15 minutes ago  >
├─╯  Update
│
o  5bbaee36d  30 minutes ago  >
   Initial commit
となります。罫線の幅を半角表示にするときれいに位置がそろうのですが、CSSでうまく書けなかったため、ずれたままで。

また、─╯ は2文字ですが、コマンドプロンプトウィンドウでは、重ねて表示されており、分岐した線のように見えます。 一方、WSL Terminalで当該文字列を表示すると重ならないです。

話はそれますが、 ╯ はUnicodeで256F、UTF-8でE2 95 AFです。

分岐で次に進む


まず、今の状態でsl prevを実行すると、
$ sl
o  1cae689a8  4 minutes ago  >
│  Add ccc
│
│ o  f4adbb569  18 minutes ago  >
├─╯  Update
│
@  5bbaee36d  33 minutes ago  >
   Initial commit
となり、最初のコミットに戻ってきます。

この位置では分岐があるのでnextを実行するとどうなるかというと、

$ sl next
commit 5bbaee36d524 has multiple children, namely:
(1) [f4adbb] Update
(2) [1cae68] Add ccc
which commit to move to [1-2/(c)ancel]?
というメッセージが出力され、どちらに進むか選ぶようになっています。

(1)側に進んでみると、

which commit to move to [1-2/(c)ancel]?  1
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
[f4adbb] Update
と表示され、(1)側に移動できます。
$ sl
o  1cae689a8  6 minutes ago  >
│  Add ccc
│
│ @  f4adbb569  20 minutes ago  >
├─╯  Update
│
o  5bbaee36d  35 minutes ago  >
   Initial commit
となり、たしかに、f4adbb569 に移動できていることが確認できます。

隣の分岐に移動するには、

$ sl goto 1cae
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
でよさそうです。
$ sl
@  1cae689a8  9 minutes ago  >
│  Add ccc
│
│ o  f4adbb569  24 minutes ago  >
├─╯  Update
│
o  5bbaee36d  38 minutes ago  >
   Initial commit
となっており、移動できていることが確認できます。

ブランチのマージ


さきほど作成したブランチをマージします。Saplingではマージよりrebaseを使うように設計されているようですが (https://sapling-scm.com/docs/introduction/のCaveatsより)、 mergeコマンドはあるのでそれでマージしてみます。
$ sl merge
を実行すると、kdiff3をインストールしているためか、kdiff3が立ち上がって、コンフリクトを解消するよう指示が出ます。

手動でマージすると、

$ sl merge
merging a.txt
running merge tool kdiff3 for file a.txt
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
と表示され、マージができました。この段階ではコミットされておらず、
$ sl
@  1cae689a8  25 minutes ago  >
│  Add ccc
│
│ @  f4adbb569  39 minutes ago  >
├─╯  Update
│
o  5bbaee36d  54 minutes ago  >
   Initial commit
のように表示されます。@が2箇所に表示されます。
$ sl commit
でコミットすると、
$ sl
@    b84d24eda  15 seconds ago  >
├─╯  Merge
│ │
o │  f4adbb569  41 minutes ago  >
│ │  Update
│ │
│ o  1cae689a8  26 minutes ago  >
├─╯  Add ccc
│
o  5bbaee36d  55 minutes ago  >
   Initial commit
と表示され、めでたくマージできたことが確認できます。

マージを元に戻す


rebaseが推奨であるようなので、一旦これを元に戻してみます。

インタラクティブにできそうなので、-i付きで実行してみましたが、

$ sl undo -i
abort: interactive ui is not supported on Windows
となり残念ながら実行できません。-iなしで試してみると、
$ sl undo
undone to 木 1 05 16:09:49 2023 +0900, before commit
hint[undo-uncommit-unamend]: undoing commits discards their changes.
to restore the changes to the working copy, run 'sl revert -r b84d24eda1a8 --all'
in the future, you can use 'sl uncommit' instead of 'sl undo' to keep changes
hint[hint-ack]: use 'sl hint --ack undo-uncommit-unamend' to silence these hints

$ sl
@  1cae689a8  30 minutes ago  >
│  Add ccc
│
│ o  f4adbb569  44 minutes ago  >
├─╯  Update
│
o  5bbaee36d  59 minutes ago  >
   Initial commit
と表示され、マージ処理を元に戻せています。マージしたファイルを取得する方法もメッセージに書かれており、 消えていてもリビジョン(=コミットのハッシュ値)さえわかっていればrevertで戻せるようです。

試して見ると、

$ sl revert -r b84d24eda1a8 --all
reverting a.txt

$ sl diff
diff --git a/a.txt b/a.txt
--- a/a.txt
+++ b/a.txt
@@ -1,2 +1,3 @@
 aaa
+"bbb" <0D>
 "ccc"
となり、たしかにマージ処理したa.txtが戻ってきます。

さらにundoできるか試してみると、

$ sl undo
abort: uncommitted changes
となります。revertで戻したファイルが未コミットのファイルとなっており、この状態ではundoはできないようです。

rebaseにてブランチをマージ


先程はブランチのコミットを維持したままマージしましたが、今度はrebaseでマージしてみます。 枝を別の場所に継ぎ変えるということです。

今の状態は

$ sl
@  1cae689a8  38 minutes ago  >
│  Add ccc
│
│ o  f4adbb569  52 minutes ago  >
├─╯  Update
│
o  5bbaee36d  67 minutes ago  >
   Initial commit
となっています。ここで
$ sl rebase f4ad
abort: you must specify a destination (-d) for the rebase

$ sl rebase -r f4ad
abort: you must specify a destination (-d) for the rebase

$ sl rebase -r 1cae
abort: you must specify a destination (-d) for the rebase

$ sl rebase
abort: you must specify a destination (-d) for the rebase
としてみると、すべてエラーとなります。

ヘルプを見ると

sl rebase [-s REV | -b REV] [-d REV] [OPTION][-i]
と表示されるので、エラーメッセージの通りrebase先を-dで指定する必要があるようです。
sl rebase -r f4adbb569 -d 1cae689a8
rebasing f4adbb569e88 "Update"
merging a.txt
abort: must use on-disk merge for this rebase (hit merge conflicts in a.txt), but you have working copy changes
(commit, revert, or shelve them)
さきほどローカルのa.txtをマージ後のものに戻していたので、エラーとなってしまっています。
$ sl revert a.txt
で元に戻します(マージ後のローカルのa.txtの内容を破棄します)。

再度rebaseしてみます。

$ sl rebase -r f4adbb569 -d 1cae689a8
rebasing f4adbb569e88 "Update"
merging a.txt
hit merge conflicts (in a.txt); switching to on-disk merge
rebasing f4adbb569e88 "Update"
merging a.txt
running merge tool kdiff3 for file a.txt
f4adbb569e88 -> f292f8d2ac45 "Update"
今回もkdiff3が立ち上がるので、手動でマージすると、rebaseが完了します。
$ sl
o  f292f8d2a  88 seconds ago  >
│  Update
│
@  1cae689a8  49 minutes ago  >
│  Add ccc
│
o  5bbaee36d  78 minutes ago  >
   Initial commit
となり、一直線になりました。 ヘルプを読むとrebaseしたコミットを適用先にコピーし、さらにrebaseしたブランチを隠すという処理が行われているようです。
$ sl head
changeset:   f292f8d2ac45f92a9737cf4d2dd38be2e1a6fb11
user:        yourname <>
date:        木, 05 1 2023 16:47:58 +0900
summary:     Update
のように、headも1つになりました。コミット履歴が読みやすいので、マージよりもコミット履歴の管理としてはこの方が良いのかもしれません。 一方で、リビジョン番号は変わってしまうので、リビジョン番号を維持しておきたい場合はmergeのほうがよいかもしれません。 rebaseでも元のコミットは隠されているだけで残ってはいますが、コピーができてしまってどれとどれが同じコミットなのかを後から追いにくくなりそうですので、リビジョン番号が重要な場合はmergeでしょうか。 状況に合わせて適切な方法を利用することになりそうです。

コミット間の移動


これまでの説明で既に使用していますが、コミット間の移動は近くのコミットであればnextとprevで移動するのが簡単です。
$ sl next
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
[f292f8] Update

$ sl
@  f292f8d2a  15 minutes ago  >
│  Update
│
o  1cae689a8  62 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit

$ echo ddd >> a.txt
$ sl commit

$ sl
@  da3ea5b13  22 seconds ago  >
│  Add ddd
│
o  f292f8d2a  15 minutes ago  >
│  Update
│
o  1cae689a8  63 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit

$ sl prev
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
[f292f8] Update

$ sl
o  da3ea5b13  50 seconds ago  >
│  Add ddd
│
@  f292f8d2a  16 minutes ago  >
│  Update
│
o  1cae689a8  64 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit

操作の取り消しと再実行


なんらかの制限はあるようですが、操作の取り消し(undo)と再実行(redo)も簡単にできます。

先程の操作を元に戻していきます。

$ sl undo
undone to 木 1 05 17:03:41 2023 +0900, before prev

$ sl
@  da3ea5b13  81 seconds ago  >
│  Add ddd
│
o  f292f8d2a  16 minutes ago  >
│  Update
│
o  1cae689a8  64 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
確かにprevコマンドを実行する前の状態に戻りました。さらに取り消すと、
$ sl undo
undone to 木 1 05 17:02:45 2023 +0900, before commit

$ sl
@  f292f8d2a  17 minutes ago  >
│  Update
│
o  1cae689a8  65 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
dddを追加したコミットが消えました。もう一度実行すると、
$ sl undo
undone to 木 1 05 16:48:26 2023 +0900, before next

$ sl
o  f292f8d2a  18 minutes ago  >
│  Update
│
@  1cae689a8  65 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
となり、参照している場所も戻りました。

今度はredoで一挙にundoをundoしてみます。

$ sl redo 10
undone to 木 1 05 17:05:19 2023 +0900, before undo

$ sl
@  f292f8d2a  19 minutes ago  >
│  Update
│
o  1cae689a8  67 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
再実行はできましたが、残念ながら一括ではundo操作を戻せませんでした。
$ sl redo 2
undone to 木 1 05 17:04:52 2023 +0900, before undo

$ sl
@  da3ea5b13  5 minutes ago  >
│  Add ddd
│
o  f292f8d2a  21 minutes ago  >
│  Update
│
o  1cae689a8  69 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
残りのundoした回数である2を指定しても1回再実行できるだけで引数の値には効果がないようです。再度redoすると、
$ sl redo
undone to 木 1 05 17:04:19 2023 +0900, before undo

$ sl redo
abort: nothing to redo

$ sl
o  da3ea5b13  6 minutes ago  >
│  Add ddd
│
@  f292f8d2a  21 minutes ago  >
│  Update
│
o  1cae689a8  69 minutes ago  >
│  Add ccc
│
o  5bbaee36d  Today at 15:31  >
   Initial commit
となり、undoする前に戻ってくることはできました。
You can run 'sl redo' multiple times to undo a series of 'sl undo' commands. Alternatively, you can explicitly specify the number of 'sl undo' commands to undo by providing a number as a positional argument.
とヘルプには書いてありましたが、できないようです。

コマンド一覧


slコマンドのヘルプ表示では表示されないコマンドを含めたコマンド一覧が
https://sapling-scm.com/docs/category/commands/
に載っています。