std::setやstd::multisetで末尾の要素を削除する

std::setやstd::multisetで末尾の要素を削除するには、https://www.geeksforgeeks.org/how-to-delete-last-element-from-a-set-in-c/ で紹介されているテクニックを使います。例えばsをstd::setのオブジェクトとした場合、std::prev(s.end()) が末尾の要素を指すので、s.erase(std::prev(s.end())) とすれば末尾の要素を削除できます。例を以下に示します。wandboxで試すこともできます。

#include <iostream>
#include <set>

int main() {
    std::set<int> s{3, 2, 1, 5, 4};
    s.erase(std::prev(s.end()));

    // 1 2 3 4 と表示される
    for(auto e: s) std::cout << e << " ";
    std::cout << std::endl;

    std::multiset<int> ms{3, 2, 1, 5, 5, 4, 4};
    ms.erase(std::prev(ms.end()));

    // 1 2 3 4 4 5 と表示される
    for(auto e: ms) std::cout << e << " ";
    std::cout << std::endl;

    return 0;
}

setやmultisetの要素数がゼロの場合、上記コードは実行時にSegmentation faultを吐くので気をつけてください。

NumPyでベクトルの長さを1に正規化

NumPyでベクトルの長さを1に正規化するには、np.linalg.normを使います。

ベクトルの例

例えば、要素数3のベクトルの長さ(より正確には、ユークリッド距離。すなわちL2ノルム)を1に正規化するには以下のようにします。

vec = np.array([3.0, 4.0, 5.0])
unit_vec = vec / np.linalg.norm(vec, ord=2)  # ordは省略可
print(unit_vec)  # [0.42426407 0.56568542 0.70710678] と表示

検算すると sqrt(0.42426407 ^ 2 + 0.56568542 ^ 2 + 0.70710678 ^ 2) ≒ 1.0で、たしかに正規化できています。

行列の例

素数3の横ベクトルが縦に2つ並んだ行列(shapeが(2, 3))について、横ベクトルそれぞれのL2ノルムを1に正規化する例を以下に示します。

arr = [[3.0, 4.0, 5.0],
       [1.0, 2.0, 3.0]]
arr /= np.linalg.norm(arr, ord=2, axis=1, keepdims=True)
print(arr)

出力結果は以下です。

[[0.42426407 0.56568542 0.70710678]
 [0.26726124 0.53452248 0.80178373]]

素数3の縦ベクトルが横に2つ並んだ行列(shapeが(3, 2))について、縦ベクトルそれぞれのL2ノルムを1に正規化する例を以下に示します。

arr = [[3.0, 1.0],
       [4.0, 2.0],
       [5.0, 3.0]]
arr /= np.linalg.norm(arr, ord=2, axis=0, keepdims=True)
print(arr)

出力結果は以下です。

[[0.42426407 0.26726124]
 [0.56568542 0.53452248]
 [0.70710678 0.80178373]]

random.randint()とnp.random.randint()の定義の違い

Python 3の random.randint() と NumPy の np.random.randint() には次のような微妙だが重大な違いがあります。

random.randint(a, b)

a以上b以下の整数を1個選んで返します。以下のコードで、random.randint(3, 7)が3以上7以下の整数をランダムに生成することを確認できます。

>>> import random
>>> from collections import Counter
>>> Counter([random.randint(3, 7) for _ in range(1000)])
Counter({4: 206, 3: 204, 6: 201, 7: 200, 5: 189})

np.random.randint(a, b)

a以上b未満の数を1個選んで返します。

>>> import numpy as np
>>> from collections import Counter
>>> Counter([np.random.randint(3, 7) for _ in range(1000)])
Counter({6: 276, 3: 252, 5: 245, 4: 227})

np.random.ranom_integers(a, b) (deprecated)

deprecatedですが、かつてはこのような関数がありました。a以上b以下の整数を1個選んで返します。

>>> import numpy as np
>>> from collections import Counter
>>> Counter([np.random.random_integers(3, 7) for _ in range(1000)])
<stdin>:1: DeprecationWarning: This function is deprecated. Please call randint(3, 7 + 1) instead
Counter({7: 214, 3: 207, 4: 194, 6: 193, 5: 192})

参考URL

NumPyのブロードキャストのルール

NumPyのブロードキャストのルールについて曖昧にしか理解していなかったので調べました。わかってしまえば簡単です。

2つのarrayの間でブロードキャストができるかどうかは、2つのarrayのshapeによってのみ決まります。アルゴリズムは以下です。

  • もし2つのarrayの次元が異なれば、小さい方のarrayの左に1を詰めて、同じ次元にする
  • 2つのarrayのshapeのうち、各次元の値を比較。「すべての次元の値が、「一致」または「どちらか片方が1」である」という条件を満たすならば、ブロードキャスト可能。大きな値

例えば、array1が(2, 3, 4, 1), array2が(4, 8)の場合を考えます。

まず、array2のほうがarray1より次元が小さいので、array2の左に1を詰めて、array2を(1, 1, 4, 8)とします。この結果、array1とarray2は以下のようになります。

  • array1: (2, 3, 4, 1)
  • array2: (1, 1, 4, 8)

つぎに、array1, array2の各次元の値を比較していきます。

ここで、array1とarary2は、上で述べた「すべての次元の値が、「一致」または「どちらか片方が1」である」という条件を満たすので、ブロードキャスト可能です。この場合、各次元の値は数値の大きい方に合わせるので、ブロードキャスト後のshapeは(2, 3, 4, 8)になります。

コード例は以下です。

>>> import numpy as np
>>> a = np.zeros((2, 3, 4, 1))
>>> b = np.zeros((4, 8))
>>> (a + b).shape
(2, 3, 4, 8)

参考URLs

tensorflow.kerasでMNIST分類器を学習

tensorflowの初心者向けページをもとに、tensorflow.kerasのチュートリアル記事を読みながら、MNISTデータセットでかんたんな分類器を学習するサンプルコードを作成しました。

NumPyのconcatenate(), vstack(), hstack(), dstack(), stack()の違い

NumPyのconcatenate(), vstack(), hstack(), dstack(), stack()の違い について毎回混乱するのでまとめました。

concatenate()

複数のアレイを、既存の軸に沿って結合する関数です。

基本的に、後述するvstack(), hstack(), dstack()の上位互換がconcatenate()だと思っておけば良さそう。

2つの同shapeなアレイを結合する例を示します。axisの指定によりどの軸に沿って結合するかを指定します。

a = np.random.random((5, 6, 7, 8))
b = np.random.random((5, 6, 7, 8))

c = np.concatenate([a, b], axis=0)  # shape: (10, 6, 7, 8)
c = np.concatenate([a, b], axis=1)  # shape: (5, 12, 7, 8)
c = np.concatenate([a, b], axis=2)  # shape: (5, 6, 14, 8)
c = np.concatenate([a, b], axis=3)  # shape: (5, 6, 7, 16)

# 軸を省略したときはaxis=0を指定したのと同じ
c = np.concatenate([a, b])  # shape: (10, 6, 7, 8)

axisで指定した軸に関しては長さがずれていても結合できます。

a = np.random.random((5, 6, 7, 8))
b = np.random.random((5, 6, 70, 8))

c = np.concatenate([a, b], axis=2)  # shape: (5, 6, 77, 8)

vstack(), hstack(), dstack()

基本的に以下のように思っておけばよいです。

  • vstack(): concatenate(axis=0)と同じ
  • hstack(): concatenate(axis=1)と同じ
  • dstack(): concatenate(axis=2)と同じ

hstack()の例だけ以下に示します。

a = np.random.random((5, 6, 7, 8))
b = np.random.random((5, 6, 7, 8))

c = np.hstack([a, b])  # (5, 12, 7, 8)

しかし、以下に示すように、細部が微妙に異なります。個人的には、これらを覚えるのはかなり困難なので、 事前に結合対象となるアレイのdimを合わせたあとにconcatenate()(または場合に応じてvstack(), hstack(), dstack())を使うほうが 読みやすいコードになるのではないかと思います。

vstack()

vstack()に渡された1-Dアレイ(e.g. (N,))は、結合前に2-Dアレイ(e.g. (1, N,))に拡張されます。よって1-Dアレイと2-Dアレイの結合が可能です。

a = np.random.random((7, ))  # (1, 7)に拡張される
b = np.random.random((100, 7))

c = np.vstack([a, b])  # shape: (101, 7)

hstack()

hstack()に1-Dアレイと1-Dアレイが渡された場合は、例外的に、連結された1-Dアレイが返ります。

a = np.random.random((5,))
b = np.random.random((5,))

c = np.hstack([a, b])  # shape: (10,)

1-Dアレイと2-Dアレイが渡された場合はエラーになります。

dstack()

dstack()に渡された1-Dアレイ(e.g. (N,))は、結合前に3-Dアレイ(e.g. (1,N,1))に拡張されます。

dstack()に渡された2-Dアレイ(e.g. (M,N))は、結合前に3-Dアレイ(e.g. (M,N,1))に拡張されます。

よって1-Dアレイ、2-Dアレイ、3-Dアレイの結合が可能となることがあります。

a = np.random.random((5,))  # (1, 5, 1)に拡張される
b = np.random.random((1,5))  # (1, 5, 1)に拡張される
c = np.random.random((1,5,100)) 

d = np.dstack([a, b, c])  # shape: (1, 5, 1+1+100)

stack()

stack()はこれまで紹介したコードと異なり、新規に軸を生成します。

a = np.random.random((5, 6, 7))
b = np.random.random((5, 6, 7))

c = np.stack([a, b], axis=0)  # shape: (2, 5, 6, 7)
c = np.stack([a, b], axis=1)  # shape: (5, 2, 6, 7)
c = np.stack([a, b], axis=2)  # shape: (5, 6, 2, 7)

# axis=0を指定したのと同じ
c = np.stack([a, b])  # shape: (2, 5, 6, 7)

参考URL

CygwinからWSLに移行するときのメモ

多分15年以上Cygwinを使い続けていますが、いい加減WSLに移行していこうと考えています。以下、CygwinでできていたことをWSLでやるためにはどうすればいいかというメモです。

ExplorerのファイルをDrag & Dropしたときに/mntで始まるパスに自動変換

Cygwinでは、例えば C:\temp\file.txt というファイルを Cygwin のターミナルにD&Dすると、自動的に/cygdrive/c/temp/file.txtというCygwinのためのパスに変換してくれて便利でした。

WSLでは、 Drag & Drop from File Explorer produces incorrect file paths with WSL · Issue #331 · microsoft/terminal · GitHub を見る限り、現時点でこのような仕組みはありません。面倒ですが、毎回

$ wslpath "C:\temp\file1.txt"

というコマンドを使ってパスを変換するしかなさそうです。

小さなことですが、個人的にはWSLに移行する心理的障壁のひとつです。

(追記)WSLのコンソールを便利で高機能な「wsltty」に置き換える:Tech TIPS - @IT を参考に wsltty を入れることで、Drag & Dropしたときに/mntで始まるパスに自動変換できるようになりました!

ファイルの内容をクリップボードにコピー

Cygwinのときは以下のコマンドを使ってfile.txtの内容をクリップボードにコピーしていました。

$ lv file.txt > /dev/clipboard

lvはlessを多元化対応したページャで、EUC-JPなどの文字コードが現役だったころから癖で使い続けています。catでもいいと思います。

WSLでは以下でOKです。.exe をつけるとWindowsのコマンドを呼べるのですね。 (参考: How can one copy text from nano in Ubuntu WSL2 and paste it into a Windows application? - Ask Ubuntu )

$ cat file.txt > clip.exe

文字列のペースト

CygwinではShift + Insertで文字列ペーストできたのですが、WSLでは

2~

という文字列が表示されてしまいます。

Copy and Paste arrives for Linux/WSL Consoles - Windows Command Line によると、タイトルバーで右クリックしてダイアログを開き、「Ctrl+Shift+C/V をコピー/貼り付けとして使用する」にチェックすれば、Ctrl+Shift+V でペーストできるようになります。

(追記) wsltty を入れると慣れ親しんだShift + Insertで文字列ペーストできます。 (参考: terminal - Use shift-Insert to paste in WSL - Unix & Linux Stack Exchange )