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

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 )

Ubuntu 20.04にemacsをインストール

WSL2上のUbuntu 20.04にemacsをインストールしようとして

$ sudo apt install emacs

としたところインストールできませんでした(エラーメッセージは記録し忘れました)。

お決まりの

$ sudo apt update
$ sudo apt upgrade

したあともう一度最初のコマンドを打てばインストールできました。ただし、バージョンは26.3でやや古いです(現時点で最新は27.2)。

その際のログを載せておきます。

$ sudo apt install emacs
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  adwaita-icon-theme at-spi2-core emacs-bin-common emacs-common emacs-el emacs-gtk emacsen-common fontconfig
  fonts-droid-fallback fonts-noto-mono fonts-urw-base35 ghostscript gsfonts gtk-update-icon-cache hicolor-icon-theme
  humanity-icon-theme imagemagick-6-common libatk-bridge2.0-0 libatk1.0-0 libatk1.0-data libatspi2.0-0
  libavahi-client3 libavahi-common-data libavahi-common3 libcairo-gobject2 libcairo2 libcolord2 libcups2 libdatrie1
  libepoxy0 libfftw3-double3 libgd3 libgdk-pixbuf2.0-0 libgdk-pixbuf2.0-bin libgdk-pixbuf2.0-common libgif7 libgomp1
  libgraphite2-3 libgs9 libgs9-common libgtk-3-0 libgtk-3-bin libgtk-3-common libharfbuzz0b libidn11 libijs-0.35
  libjbig0 libjbig2dec0 libjpeg-turbo8 libjpeg8 liblcms2-2 liblqr-1-0 libm17n-0 libmagickcore-6.q16-6
  libmagickwand-6.q16-6 libopenjp2-7 libotf0 libpango-1.0-0 libpangocairo-1.0-0 libpangoft2-1.0-0 libpaper-utils
  libpaper1 libpixman-1-0 librest-0.7-0 librsvg2-2 librsvg2-common libsoup-gnome2.4-1 libthai-data libthai0 libtiff5
  libwayland-cursor0 libwayland-egl1 libwebp6 libwebpmux3 libxcb-render0 libxcursor1 libxkbcommon0 m17n-db
  poppler-data ubuntu-mono
Suggested packages:
  mailutils emacs-common-non-dfsg fonts-noto fonts-freefont-otf | fonts-freefont-ttf fonts-texgyre ghostscript-x
  colord cups-common libfftw3-bin libfftw3-dev libgd-tools gvfs liblcms2-utils m17n-docs libmagickcore-6.q16-6-extra
  librsvg2-bin poppler-utils fonts-japanese-mincho | fonts-ipafont-mincho fonts-japanese-gothic | fonts-ipafont-gothic
  fonts-arphic-ukai fonts-arphic-uming fonts-nanum
The following NEW packages will be installed:
  adwaita-icon-theme at-spi2-core emacs emacs-bin-common emacs-common emacs-el emacs-gtk emacsen-common fontconfig
  fonts-droid-fallback fonts-noto-mono fonts-urw-base35 ghostscript gsfonts gtk-update-icon-cache hicolor-icon-theme
  humanity-icon-theme imagemagick-6-common libatk-bridge2.0-0 libatk1.0-0 libatk1.0-data libatspi2.0-0
  libavahi-client3 libavahi-common-data libavahi-common3 libcairo-gobject2 libcairo2 libcolord2 libcups2 libdatrie1
  libepoxy0 libfftw3-double3 libgd3 libgdk-pixbuf2.0-0 libgdk-pixbuf2.0-bin libgdk-pixbuf2.0-common libgif7 libgomp1
  libgraphite2-3 libgs9 libgs9-common libgtk-3-0 libgtk-3-bin libgtk-3-common libharfbuzz0b libidn11 libijs-0.35
  libjbig0 libjbig2dec0 libjpeg-turbo8 libjpeg8 liblcms2-2 liblqr-1-0 libm17n-0 libmagickcore-6.q16-6
  libmagickwand-6.q16-6 libopenjp2-7 libotf0 libpango-1.0-0 libpangocairo-1.0-0 libpangoft2-1.0-0 libpaper-utils
  libpaper1 libpixman-1-0 librest-0.7-0 librsvg2-2 librsvg2-common libsoup-gnome2.4-1 libthai-data libthai0 libtiff5
  libwayland-cursor0 libwayland-egl1 libwebp6 libwebpmux3 libxcb-render0 libxcursor1 libxkbcommon0 m17n-db
  poppler-data ubuntu-mono
0 upgraded, 81 newly installed, 0 to remove and 0 not upgraded.
Need to get 67.2 MB of archives.
After this operation, 273 MB of additional disk space will be used.
Do you want to continue? [Y/n] Y
(以下略)

ちなみに、snapを使ってemacsをインストールする方法も本来は存在するのですが、WSL2上のUbuntu 20.04では以下のようにエラーが出ました。

$ sudo snap install emacs --classic
Interacting with snapd is not yet supported on Windows Subsystem for Linux.
This command has been left available for documentation purposes only.

emacsの検索・置換に関するメモ

emacsを使うとき、検索はC-s、置換はM-%を使う以上のことをこれまでしてきませんでした。より高度な検索・置換の仕方をまとめました。

検索

単語単位で検索

いくつか方法があります。ユースケースに応じて使い分けてください。

1つ目の方法は、C-s M-s w としてから、検索したい単語を入力する方法です。( 参考 )

2つ目の方法は、C-sでのインクリメンタルサーチ中に、M-s w とする方法です。これで単語単位の検索をするか否かのフラグがトグルします。( 参考 )

3つ目の方法は、まず検索したい語にカーソルがある状態でC-s C-wとしてその語を選択状態にした上で、M-s wとする方法です。( 参考

コマンドが覚えられない場合は、メニューバーの"Options" → "Default Search Options" → "Whole Words"にチェックをつける方法も可です。なお、no windowモードでemacsを起動している場合は、F10でメニューを開けます。

大文字・小文字を区別して検索

これもいくつか方法があります。

1つ目の方法は、C-s M-cとしてから、検索したい単語を入力する方法です。

2つ目の方法は、C-sでのインクリメンタルサーチ中に、M-c とする方法です。これでcase-sensitiveで検索をするか否かのフラグがトグルします。( 参考 )

これも、メニューバーの"Options" → "Default Search Options" → "Ignore Case"をアンチェックする方法も可です。

正規表現で検索

C-u C-sとしてから、検索したい正規表現を入力します。

正規表現は、Pythonに組み込まれているような高度なバージョンではないようです。例えば数字一文字を表す\dは使えず、[0-9]とする必要があります。詳細は ここ を参照。

過去の検索キーワードを呼び出す

C-sでのインクリメンタルサーチ中にM-pまたはM-nです。

参考

文字列検索に関するフラグに関するコマンドは C-s C-h bで調べられます

置換

単語単位で置換

C-u M-%ののち、置換前の単語を入力してEnter、置換後の単語を入力してEnterです。 ( 参考 )

大文字・小文字を区別して置換

デフォルトだと置換前の単語をすべて小文字で入力すると、大文字・小文字をいい感じに変換してくれます (参考) が、これがおせっかいだという場合は、大文字・小文字を区別して置換したくなります。

https://emacs.stackexchange.com/questions/12780/how-to-perform-case-sensitive-query-replace を見る限り、ある単語の検索の途中に、さっき紹介した M-c のようなコマンドでフラグを切り替える方法は用意されてないようでした。

メニューバーの"Options" → "Default Search Options" → "Ignore Case"をアンチェックする方法以上に簡単な方法は今のところ見つけられていません。

正規表現で置換 (query-replace-regexp)

C-M-% もしくは M-x replace-regexp としてから、検索したい正規表現を入力してEnter、置換後の正規表現を入力してEnterです。

C-M-%の入力方法は以下のどちらかです。

  • Escを先押ししたあと、Ctrl + Shiftを押しながら5を押す
  • Ctrl + Alt + Shiftを押しながら5を押す

過去の置換キーワードを呼び出す

置換中にM-pまたはM-nです。

置換前・置換後の文字列を変更

eで置換前、Eで置換後の文字列を変更可能です(参考)。上の「過去の置換キーワードを呼び出す」と組み合わせると便利です。

scikit-imageを使ってサンプル画像を簡単に取得

scikit-imageというPythonのライブラリを使うと、サンプル画像をさっと取ってくることができます。

コード例は以下です。

import skimage


print("scikit-image version: {}".format(skimage.__version__))
coffee_image = skimage.data.coffee()
print(coffee_image.shape)

Google Colabでの実行例を以下に示します。縦400px、横600px、カラー画像がNumPy形式で取得されました。dtypeはnp.uint8です。色順はRGBです。

0.16.2
(400, 600, 3)

どんな画像が取得できたかを見てみます。

import matplotlib.pyplot as plt


plt.imshow(coffee_image)
plt.title('coffee_image')
plt.show()

結果は以下です。 f:id:minus9d:20210731232017p:plain

他にどんな画像を取得できるかは 公式API を見てください。バージョンにより微妙にAPIが異なる(例えば0.19.xで提供されているcat()はscikit-iamge 0.16.2では存在しない)ため要注意です。

取得可能な画像を無理矢理全列挙するコードを以下に示します。

import skimage
import matplotlib.pyplot as plt


for i in dir(skimage.data):
  attr = getattr(skimage.data, i)
  if callable(attr) and not i.startswith('_'):
    try:
        img = attr()
        plt.imshow(img)
        plt.title("{} (shape {})".format(i, img.shape))
        plt.show()
    except Exception:
      pass

結果のうち冒頭部分を以下に示します。

f:id:minus9d:20210802213331p:plain