Apple Silicon macOSでディープラーニングの環境を構築する(Miniforge使用、Tensorflow、Tensorflow addons導入)

Apple Silicon(M1、M1Max) macOSでディープラーニングの環境を構築する方法について解説します。

本手順は2022/4/1時点のものです。現状ではHomebrewやPyenv等でインストールしたPythonではTensorflowを導入できないようです。この状況も今後変わってくる可能性があります。

Miniforgeのインストール

現状ではMiniforgeを使うのが最も楽な手順のようです。

MiniforgeのGithubサイトからApple Silicon用のインストーラをダウンロードしてください。ファイル名はMiniforge3-MacOSX-arm64.shとなっています。

迷うところはありませんが、念のため応答する箇所について掲載しておきます。

$ bash ~/Downloads/Miniforge3-MacOSX-arm64.sh                                                                                                                                                           

Welcome to Miniforge3 4.12.0-0

In order to continue the installation process, please review the license
agreement.
Please, press ENTER to continue
>>> ← ENTERを押下
Miniforge installer code uses BSD-3-Clause license as stated below.

 ・・・

Do you accept the license terms? [yes|no]
[no] >>> yes ← yesを入力してENTERを押下

Miniforge3 will now be installed into this location:
/Users/t0k0sh1/miniforge3

  - Press ENTER to confirm the location
  - Press CTRL-C to abort the installation
  - Or specify a different location below

[/Users/t0k0sh1/miniforge3] >>> ← ENTERを押下
PREFIX=/Users/t0k0sh1/miniforge3
Unpacking payload ...

 ・・・

Do you wish the installer to initialize Miniforge3
by running conda init? [yes|no]
[no] >>> no ← noを入力してENTERを押下

You have chosen to not have conda modify your shell scripts at all.
To activate conda's base environment in your current shell session:

eval "$(/Users/t0k0sh1/miniforge3/bin/conda shell.YOUR_SHELL_NAME hook)"

To install conda's shell functions for easier access, first activate, then:

conda init

If you'd prefer that conda's base environment not be activated on startup,
   set the auto_activate_base parameter to false:

conda config --set auto_activate_base false

Thank you for installing Miniforge3!

インストールが完了すると、以下のようなメッセージが表示されています。このうち、YOUR_SHELL_NAME部分を書き換えてシェルの設定ファイルに追記します。

eval "$(/Users/t0k0sh1/miniforge3/bin/conda shell.YOUR_SHELL_NAME hook)"

macOSのデフォルトシェルはzshですので、以下のように書き換えて、.zshrcに追記してください。

eval "$(/Users/t0k0sh1/miniforge3/bin/conda shell.zsh hook)"

conda環境の設定を行う

次にconda環境の設定を行います。

conda環境の自動有効化をOFFにする

前述の作業が完了し、再度シェルにログインすると、自動でconda環境が有効になります。これでも問題ない方は以下の設定変更を行う必要はありませんが、そうでない方はconda環境が自動で有効にならないように設定変更をしてください。

$ conda config --set auto_activate_base false

一度conda環境を無効化しておきます。

$ conda deactivate

Tensorflowをインストールする環境を作成する

Tensorflowをインストールする環境を作成しましょう。

執筆時点では、TensorFlow 2.8.0が最新なため、これをインストールします。Tensorflow 2.8.0ではPython 3.9に対応しているため、これを使用します。

$ conda create --name tensorflow28 python=3.9

環境名はなんでも構いませんが、ここではtensorflow28としています。

$ conda create --name tensorflow28 python=3.9                                                                                                                                                           
Collecting package metadata (current_repodata.json): done
Solving environment: done

 ・・・

#
# To activate this environment, use
#
#     $ conda activate tensorflow28
#
# To deactivate an active environment, use
#
#     $ conda deactivate

作成した環境を有効化します。

$ conda activate tensorflow28

以下の手順は作成した環境が有効化されていることを前提に進めます。

Tensorflowをインストールする

作成・有効化した環境にTensorflowをインストールします。

Numpy、OpenCV、Matplotlibをインストールする

Tensorflowをインストールする前にNumpy、OpenCV、Matplotlibをインストールしておいた方がよいようなので、先にconda installコマンドでインストールします。

$ conda install numpy opencv matplotlib

Tensorflowをインストールする

Tensorflowを以下の順番でインストールします。

$ conda install -c apple tensorflow-deps
$ python -m pip install tensorflow-macos
$ python -m pip install tensorflow-metal

Tensorflow addonsのビルド・インストールする

Tensorflowの拡張ライブラリであるTensorflow addonsを使用するためには、Apple Siliconではソースコードからビルドしてインストール必要があります(conda installpip installではうまくいかない)。

ビルドにはbazelが必要なため、まずはこれをインストールします。

$ conda install bazel

次にwheelsetuptoolsが最新でないとビルドに失敗するという報告もあるため、最新化しておきます。

$ python -m pip install --upgrade wheel setuptools

準備が整いましたので、Tensorflow addonsをダウンロードし、ビルドします。

$ git clone https://github.com/tensorflow/addons.git
$ cd addons
$ python ./configure.py
$ bazel build build_pip_pkg
$ bazel-bin/build_pip_pkg artifacts

ビルドが完了すると、artifactsディレクトリの下にファイルが作成されます。

$ ls ./artifacts                                                                                                                                                                                          
tensorflow_addons-0.17.0.dev0-cp39-cp39-macosx_11_0_arm64.whl

作成されていることが確認できましたら、これをインストールします。

$ python -m pip install ./artifacts/tensorflow_addons-0.17.0.dev0-cp39-cp39-macosx_11_0_arm64.whl

これで、TensorflowおよびTensorflow addonsのインストールが完了となります。

参考文献

本手順は以下を参考に作成しました。

[Java]ラムダ式を使ってコレクションを操作する(Java Lambda)

Java 8から導入されたラムダ式を使ってコレクションを操作する方法について解説します。ここでは、実用的なものに的を絞って解説しています。

前提(説明時に使用しているDTO)

説明で使用しているDTOを提示しておきます。ここではUserDtoクラスを使用しています。

import java.io.Serializable;

public class UserDto implements Serializable {
    private String userId;
    private String userName;
    private int age;

    // Setter, Getterは省略

    public String toString() {
        return "userId=" + userId + ",userName=" + userName + ",age=" + age;
    }

}

ListをMapに変換する

ListをMapに変換する方法について解説します。

以下の例では、UserDtoクラスを要素に持つListをMapに変換しています。MapのキーはUserDtoのuserId、値はUserDtoにしています。

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        // UserDtoを要素として持つListを用意する
        List<UserDto> list = new ArrayList<>();
        UserDto user1 = new UserDto();
        user1.setUserId("001");
        user1.setUserName("John Smith");
        user1.setAge(20);
        list.add(user1);
        UserDto user2 = new UserDto();
        user2.setUserId("002");
        user2.setUserName("Maria Cambell");
        user2.setAge(28);
        list.add(user2);
        
        // ListをMapに変換する(キーはUserDTOのUserIdを使用する)
        Map<String, UserDto> map = list.stream().collect(Collectors.toMap(s -> s.getUserId(), s -> s));

        // Listの出力結果を確認する
        System.out.println(list);
        // [userId=001,userName=John Smith,age=20,userId=002, userName=Maria Cambell,age=28]
        
        // 変換後のMapの出力結果を確認する
        System.out.println(map);
        // {001=userId=001,userName=John Smith,age=20, 002=userId=002,userName=Maria Cambell,age=28}
    }
}

Listから一部の項目を抽出する

Listから一部の項目を抽出する方法について解説します。

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        // UserDtoを要素として持つListを用意する
        List<UserDto> list = new ArrayList<>();
        UserDto user1 = new UserDto();
        user1.setUserId("001");
        user1.setUserName("John Smith");
        user1.setAge(20);
        list.add(user1);
        UserDto user2 = new UserDto();
        user2.setUserId("002");
        user2.setUserName("Maria Cambell");
        user2.setAge(28);
        list.add(user2);

        // Listから項目の一部(ここではUserDtoのuserIdを抽出)し、リストにする
        List<String> userIds = list.stream().map(s -> s.getUserId()).collect(Collectors.toList());
  
        
        // 抽出前のListの内容を確認する
        System.out.println(list);
        // [userId=001,userName=John Smith,age=20, userId=002,userName=Maria Cambell,age=28]
        // 抽出した項目を確認する
        System.out.println(userIds);
        // [001, 002]
    }
}

[Oracle]SQL*Plusで実行計画を取得する(SET AUTOTRACE)

実行計画の確認にはいくつかを方法がありますが、ここでは最も手軽に実施できるSQL*Plusでの実行計画の取得の方法について解説します。

実行計画を取得するための設定を行う

私がよく使っている設定をご紹介します。とりあえずこの設定を使っていただければ実行計画を取得できるようになります。

SQL> set lines 1000
SQL> set pages 1000
SQL> set timing on
SQL> set autotrace traceonly

以上です。

この設定を行った状態でSQLを実行すると以下のようになります。

SQL> select * from dept
  2  /

レコードが選択されませんでした。

経過: 00:00:00.02

実行計画
----------------------------------------------------------
Plan hash value: 3383998547

--------------------------------------------------------------------------
| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |      |     1 |    30 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| DEPT |     1 |    30 |     2   (0)| 00:00:01 |
--------------------------------------------------------------------------


統計
----------------------------------------------------------
          0  recursive calls
          0  db block gets
          0  consistent gets
          0  physical reads
          0  redo size
        449  bytes sent via SQL*Net to client
        369  bytes received via SQL*Net from client
          1  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          0  rows processed

SQL>

実行計画と統計情報が出力されていることがわかります。

上記手順を実施しているときにエラーが出る場合があります。その場合は後述の「手順の実施時にエラーが出る場合」を参照してください。

これだけだとよくわからないと思いますので、各コマンドについて解説します。

表示幅を広げる

linesizeで結果行の幅を設定します。実行計画の出力内容は表を含むため、途中で折り返されるとかなり見にくいです。そこでとりあえず折り返されないような大きな値を設定しています。

正式にはlinesizeですが、linesでも設定できます。タイプ数が少ない方が楽なのでlinesを使用しています。

set lines 1000

1ページの行数を増やす

実行計画の出力の途中で改ページすると見づらいため、1ページの行数も増やしています。こちらもとりあえずで増やしていますが、普通に使う分には問題は起こらないと思いますが、あまりにも長いSQLの実行計画を確認するときに、改ページされるようであれば値を増やしてください。

先ほどと同様にpagesizeが正式な設定ですが、pagesでも設定できます。

set pages 1000

タイミング統計をONにする

タイミング統計をONにすることで、実行時間を10ミリ秒単位で出力できるようになります。

経過: 00:00:00.02

実行時間もチューニングの重要な情報源となりますし、出力結果が邪魔になることもないので、ONにしておきましょう。

set timing on

実行計画の設定を行う

実行計画の出力設定は、autotraceで行います。出力できる内容は、

  • 実行結果
  • 実行計画
  • 統計情報

の3つがありますが、実行結果を必要としない場合はtraceonlyを設定し、実行結果を確認したい場合はoffに切り替えるのがおすすめです。

設定説明
set autotrace on explain実行結果と実行計画を表示する
set autotrace on statistics実行結果と統計情報を表示し、実行計画をは表示しない
set autotrace on実行結果、実行計画、統計情報を表示する
set autotrace traceonly実行計画と統計情報を表示し、実行結果は表示しない
set autotrace offautotraceの設定をOFFにする
set autotrace traceonly

手順の実行時にエラーが出る場合

これから説明する手順を実行したときに、以下のようなエラーが出る場合があります。

SQL> set autotrace traceonly
SP2-0618: セッション識別子が見つかりません。PLUSTRACEロールが有効かを確認してください。
SP2-0611: STATISTICSレポートを使用可能にするときにエラーが発生しました。

このエラーが出る場合は、以下の手順を実行してPLUSTRACEロールを有効にしてください。

SYSDBAでCDBにログインする

ロールを追加するため、SYSDBACDBにログインします。

ORACLE_SID=ORCLCDB sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Tue Mar 29 15:44:05 2022
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> 

PDBに切り替える

ロールを作成するPDBに切り替えます。ここではpdb1というPDBに切り替えています。

SQL> alter session set container=pdb1;

Session altered.

SQL> 

ロールを作成するPDBというのは、実行計画を取得するPDBのことです。

PLUSTRACEロールを作成するSQLファイルを実行する

OracleDatabaseに用意されているSQLファイルを実行します。SQLファイルのファイル名はplustrace.sqlではなくplustrce.sqlですので、打ち間違いに注意してください(aがない)。

SQL> @?/sqlplus/admin/plustrce.sql
SQL> 
SQL> drop role plustrace;
drop role plustrace
          *
ERROR at line 1:
ORA-01919: role 'PLUSTRACE' does not exist


SQL> create role plustrace;

Role created.

SQL> 
SQL> grant select on v_$sesstat to plustrace;

Grant succeeded.

SQL> grant select on v_$statname to plustrace;

Grant succeeded.

SQL> grant select on v_$mystat to plustrace;

Grant succeeded.

SQL> grant plustrace to dba with admin option;

Grant succeeded.

SQL> 
SQL> set echo off

PLUSTRACEロールをユーザーに付与する

作成したPLUSTRACEロールをユーザーに付与します。ここでは、user01ユーザーに設定しています。

SQL> grant plustrace to user01;

Grant succeeded.

SQL>
SQL> exit
Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

再度、該当ユーザーでログインしてautotraceの設定を行ってください。

[Python]VSCodeでPythonのフォーマッタを設定する(black等のライブラリをPrettierプラグインと共存させる)

Visual Studio Codeで、Python以外はPrettierプラグインを使用し、PythonはPrettierプラグインを使わずにblackを使用する方法について解説します。

共通の設定

Prettierプラグインを有効化するにはeditor.defaultFormatterを設定します。保存時にフォーマットしたいので、editor.formatOnSaveも設定します。

{

  ・・・
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  ・・・
 }

Pythonの設定

次にPythonの設定です。PythonではPrettierプラグインを使用せずにblackを使用します。ただし、editor.defaultFormatterの設定が有効な場合、editor.defaultFormatterの方が優先されてしまうため、Pythonではこの設定を無効化(null)します。

以下は、blackを使用するように設定している例です。ここではblackを使用していますが、他のライブラリでも構いません。

{
  ・・・
  "[python]": {
    "editor.defaultFormatter": null
  },
  "python.formatting.provider": "black",
  ・・・

}

上記設定により、PythonだけはPrettierプラグインを使用せず、black等のライブラリを使用できます。

外部キー制約でのON DELETE CASCADEオプションの使い方

外部キー制約でのON DELETE CASCADEオプションの使い方を具体的な例を使って解説します。

ON DELETE CASCADEオプションは親テーブル(参照先のテーブル)を削除するときに子テーブル(参照元のテーブル)も一緒に削除するために使用するオプションになります。

解説に使用するデータベースとテーブルについて

解説に使用するデータベースとテーブルについて説明します。

確認にはOracle Database 19cを使用しています。若干の構文の違いはありますが、他の主要なデータベースでも同様の操作が可能です。

まずは親テーブルを作成します。親テーブルのMAINテーブルは主キーIDのみを持ったテーブルです。

CREATE TABLE MAIN
(
  ID NUMBER(3),
  CONSTRAINT MAIN_PK PRIMARY KEY (ID)
)

次に子テーブルを作成します。子テーブルのSUBテーブルはIDとSEQを主キーに持つテーブルです。

CREATE TABLE SUB
(
  ID  NUMBER(3),
  SEQ NUMBER(2),
  CONSTRAINT SUB_PK PRIMARY KEY (ID, SEQ)
)

ON DELETE CASCADE オプションなしの場合

では、ON DELETE CASCADEオプションを使用しない場合の動作を見てみましょう。

SUBテーブルのIDに外部キー制約を設定します。

SQL> ALTER TABLE SUB ADD CONSTRAINT SUB_ID_FK
   2 FOREIGN KEY (ID) REFERENCES MAIN(ID)

表が変更されました。

では、データを投入して削除時の動作を確認します。

SQL> INSERT INTO MAIN VALUES (1)

1行が作成されました。
SQL> INSERT INTO SUB VALUES (1, 1)

1行が作成されました。
SQL> INSERT INTO SUB VALUES (1, 2)

1行が作成されました。

削除する前にデータを確認しておきましょう。

SQL> SELECT * FROM MAIN

ID
--
 1

1行が選択されました。
SQL> SELECT * FROM SUB

ID SEQ
-- ---
 1   1
 1   2

2行が選択されました。

MAINテーブルのID=1のレコードにSUBテーブルのID=1のレコードが2件ぶら下がっている形になっています。

データが投入できましたので、親テーブルMAINテーブルのレコードを削除してみます。

SQL> DELETE FROM MAIN WHERE ID = 1
DELETE FROM MAIN WHERE ID = 1
*
行1でエラーが発生しました。:
ORA-02292: 整合性制約(USER01.SUB_ID_FK)に違反しました - 子レコードがあります

整合性制約(外部キー制約)で削除に失敗しました。ON DELETE CASCADEオプションがない場合は、子テーブルを削除してからでないと親テーブルを削除できません。

念のためデータが削除されていないことを確認しておきます。

SQL> SELECT * FROM MAIN

ID
--
 1

1行が選択されました。
SQL> SELECT * FROM SUB

ID SEQ
-- ---
 1   1
 1   2

2行が選択されました。

ON DELETE CASCADEオプションありの場合

では、ON DELETE CASCADEオプションをつけた場合の削除の動きを確認します。

すでに設定済みの外部キーを削除しておきます。

SQL> ALTER TABLE SUB DROP CONSTRAINT  SUB_ID_FK

表が変更されました。

今度はON DELETE CASCADEオプション付きで外部キー制約を設定します。

SQL> ALTER TABLE SUB ADD CONSTRAINT SUB_ID_FK
   2 FOREIGN KEY (ID) REFERENCES MAIN(ID)
   3 ON DELETE CASCADE

表が変更されました。

では、もう一度削除を試します。

> DELETE FROM MAIN WHERE ID = 1

1行が削除されました。

今度は削除できました。

削除後のデータを確認します。

SQL> SELECT * FROM MAIN

レコードが選択されませんでした。
SQL> SELECT * FROM SUB

レコードが選択されませんでした。

削除したMAINテーブルのレコードだけでなく、SUBテーブルのID=1のレコードも削除されています。

どういった場合にON DELETE CASCADEオプションを使うか

まず、外部キー制約を使用する場合に限られているということが前提となります。

上記の前提の上で、以下の2つのどちらの効果を狙いたいかで決めるとよいでしょう。

  • 子テーブルにデータがあるときに親テーブルが削除されるのを防ぎたい
  • 親テーブルが削除されるときに子テーブルをまとめて削除したい

もう少し具体的な例で説明します。

ON DELETE CASCADEオプションを使いたくないケース

ON DELETE CASCADEオプションを使いたくないケース、使うべきでないケースとして挙げられるのが、マスタテーブルに対して外部キー制約を設定しているケースです。

例えば、注文テーブルにある顧客番号に対して、顧客マスタの顧客番号への外部キーを設定している場合、ON DELETE CASCADEオプションを設定していると、注文テーブルのレコードの削除によって顧客マスタのレコードが削除される場合があります。
これは望ましい挙動でないことから、マスタテーブルへの外部キー設定の場合はON DELETE CASCADEオプションを使用するべきではありません。

ON DELETE CASCADE オプションを使いたいケース

ON DELETE CASCADEオプションを使いたいケースとして挙げられるのが、子テーブルが親テーブルに対して外部キー制約を設定しているケースです。

例えば、注文テーブルと注文明細テーブルがあり、注文明細の注文番号に対して、注文テーブルの注文番号への外部キーを設定している場合は、ON DELETE CASCADEオプションを設定していると、注文テーブルのレコード削除時に注文明細テーブルのレコードをまとめて削除してくれるため便利です。
ON DELETE CASCADEオプションを設定していない場合は、先に注文明細テーブルの該当する注文番号のレコードを先に削除してからでないと注文テーブルのレコードを削除できないため、ON DELETE CASCADEオプションを使うことが削除を行うプログラムの構造をすっきりとさせることができる効果があります。

まとめ

前述の例をまとめると以下のようになります。

CREATE TABLE 注文
(
  注文番号 NUMBER(10),
  顧客番号 NUMBER(3),
  CONSTRAINT 注文_PK PRIMARY KEY (ID)
)

CREATE TABLE 注文明細
(
  注文番号 NUMBER(10),
  注文明細連番 NUMBER(10),
  CONSTRAINT 注文明細_PK PRIMARY KEY (注文番号, 注文明細番号)
)

CREATE TABLE 顧客マスタ
(
  顧客番号 NUMBER(3),
  CONSTRAINT 顧客マスタ_PK PRIMARY KEY (顧客番号)

-- 注文から顧客マスタへの外部キーではON DELETE CASCADEオプションをつけない
ALTER TABLE SUB ADD CONSTRAINT 注文_顧客番号_FK
FOREIGN KEY (顧客番号) REFERENCES 顧客マスタ(顧客番号)

-- 注文明細から注文への外部キーではON DELETE CASCADEオプションをつける
ALTER TABLE SUB ADD CONSTRAINT 注文明細_注文番号_FK
FOREIGN KEY (注文番号) REFERENCES 注文(注文番号)
ON DELETE CASCADE

このようにON DELETE CASCADEオプションを適切に設定する/しないことでデータを安全に、効率よく管理できるようになります。

[Git]ファイルをステージングエリアに追加する(git add)

Gitでファイルをステージングエリア(インデックス)に追加する方法を解説します。

ファイル名を指定して追加する

一番オーソドックスな方法は、追加したいファイルをgit addコマンドで指定する方法です。

$ git add test.txt

ファイルをまとめて追加する

変更をまとめて追加したい場合は、ファイル名の代わりに.を指定するとまとめて追加できます。

$ git add .

-Aオプションを使っても同様のことができます。

$ git add -A

ワイルドカードを指定して追加する

特定の拡張子のファイルや特定のフォルダ(ディレクトリ)にあるファイルをまとめて追加したい場合は、ワイルドカードを使うと便利です。

$ git add *.java
$ git add src/*

Gitで管理しているファイルだけをまとめて追加する

git add .の場合、Gitで管理しているファイルも管理していないファイルもまとめて追加されます。Gitで管理しているファイルだけを追加したい場合は-uオプションを使います。

$ git add -u

[NumPy]正規化(Normalization)を行う

正規化とはデータを扱いやすいスケールに変更する手法です。ここでは代表的なMin-Max normalization、Z-score normalizationについて解説します。

  • Min-Max normalization
  • Z-score normalization

Min-Max normalization

正規化というとMin-Max normalizationを指すといわれることもありますが、正直そこまで気にする必要はありません。どちらかというと、正規化を行うときにデータの性質に応じて適切な手法を選択できる方が重要です。

Min-Max normalizationとは

Min-Max normalizationとは、データを最小値0、最大値1にスケールする手法です。

データ$x_i$をMin-Max normalizationで正規化した$x^{\prime}_i$は以下の式で求めます。

$$ x^{\prime}_i = \frac{x_i – min(x)}{max(x) – min(x)} \quad (i = 1, 2, \cdots , n) $$

Min-Max normalizationは外れ値に弱いという特徴があります。最小値と最大値がはっきりしている場合に適した手法です。外れ値が含まれる場合は後述のZ-score normalizationを使用する方が適しています。

NumPyによる実装

では、実際にNumPyで実装してみましょう。

まずは、正規化前の5×5の行列を作成します。再現しやすいように乱数のシードは固定しておきます。

np.random.seed(123)
x = np.random.random((5, 5))
print(x)
[[0.69646919 0.28613933 0.22685145 0.55131477 0.71946897]
 [0.42310646 0.9807642  0.68482974 0.4809319  0.39211752]
 [0.34317802 0.72904971 0.43857224 0.0596779  0.39804426]
 [0.73799541 0.18249173 0.17545176 0.53155137 0.53182759]
 [0.63440096 0.84943179 0.72445532 0.61102351 0.72244338]]

正規化前後でどう変わったかわかりやすいように最小値、最大値、平均値、標準偏差を求めておきます。

print('最小値 :%f' % np.min(x))
print('最大値 :%f' % np.max(x))
print('平均値 :%f' % np.mean(x))
print('標準偏差:%f' % np.std(x))
最小値 :0.059678
最大値 :0.980764
平均値 :0.524464
標準偏差:0.226296

式の定義どおりに実装します。np.min関数で最小値、np.max関数で最大値を求めています。

x_norm = (x - np.min(x)) / (np.max(x) - np.min(x))
print(x_norm)
[[0.02615693 0.72523256 0.32482616 0.41610292 0.11393134]
 [[0.69134813 0.24586343 0.18149608 0.53375766 0.71631841]
 [0.39456516 1.         0.67871147 0.45734477 0.36092125]
 [0.30778888 0.72671997 0.41135597 0.         0.36735576]
 [0.73643209 0.13333586 0.12569274 0.51230105 0.51260093]
 [0.62396223 0.85741574 0.72173197 0.59858193 0.71954765]]

ぱっと見で最小値0、最大値1であることはわかりますが、念のため先ほどと同じように最小値、最大値、平均値、標準偏差を確認しましょう。

print('最小値 :%f' % np.min(x_norm))
print('最大値 :%f' % np.max(x_norm))
print('平均値 :%f' % np.mean(x_norm))
print('標準偏差:%f' % np.std(x_norm))
最小値 :0.000000
最大値 :1.000000
平均値 :0.504606
標準偏差:0.245684

最小値0、最大値1であることが確認できます。加えて、若干値が違いますが、平均値と標準偏差はほとんど変わっていないことがわかります。

最小値0、最大値1にスケールする仕組みを理解する

Z-score normalizationは平均0、標準偏差1にスケールする手法になります。

わかりやすいデータを使って計算過程をひとつずつ確認して、最小値0、最大値1の範囲にスケールする仕組みを確認していきましょう。

まずは、以下のようなデータを用意します。10、20、・・・、50と10ずつ増える5個のデータです。

x = np.array([10, 20, 30, 40, 50])
print(x)
[10 20 30 40 50]

まずは分子から確認します。データから最小値を引きます。最小値は10なので、各要素から引くと以下のようになります。

print(x - np.min(x))
[ 0 10 20 30 40]

次に分母を見てみます。最大値50から最小値10を引くので、40になります。

print(np.max(x) - np.min(x))
40

すでに計算した分子の最大値は40で、分母と同じ値ですので、割り算の結果は1になります。一方で、最小値は0ですので、割り算の結果は0になります。それ以外の値は最小値と最大値の間に収まるため、計算結果はすべて0以上1以下になります。

print((x - np.min(x)) / (np.max(x) - np.min(x)))
[0.   0.25 0.5  0.75 1.  ]

Z-score normalization

次にZ-score normalizationについて確認します。標準化(Standardization)とも呼ばれることもありますが、こちらもそれほど気にする必要はありません。

Z-score normalizationとは

データ$x_i$をZ-score normalizationで正規化した$x^{\prime}_i$は以下の式で求めます。

$$ x^{\prime}_i = \frac{x_i – \bar{x}}{\sigma} \quad (i = 1, 2, \cdots , n) $$

NumPyによる実装

正規化前のデータおよび最小値、最大値、平均値、標準偏差を再掲しておきます。

np.random.seed(123)
x = np.random.random((5, 5))
print(x)
[[0.69646919 0.28613933 0.22685145 0.55131477 0.71946897]
 [0.42310646 0.9807642  0.68482974 0.4809319  0.39211752]
 [0.34317802 0.72904971 0.43857224 0.0596779  0.39804426]
 [0.73799541 0.18249173 0.17545176 0.53155137 0.53182759]
 [0.63440096 0.84943179 0.72445532 0.61102351 0.72244338]]
print('最小値 :%f' % np.min(x))
print('最大値 :%f' % np.max(x))
print('平均値 :%f' % np.mean(x))
print('標準偏差:%f' % np.std(x))
最小値 :0.059678
最大値 :0.980764
平均値 :0.524464
標準偏差:0.226296

こちらも同様に式どおりに実装します。np.mean関数が平均(算術平均)、np.std関数が標準偏差を求める関数です。

x_norm = (x - np.mean(x)) / np.std(x)
print(x_norm)
[[ 0.76008999 -1.05315055 -1.31514268  0.11865512  0.86172564]
 [-0.44789519  2.01638476  0.70865548 -0.19236556 -0.58483479]
 [-0.8010976   0.90406276 -0.37955215 -2.05387975 -0.55864464]
 [ 0.94359364 -1.51116753 -1.54227706  0.03132102  0.0325416 ]
 [ 0.48581156  1.43602912  0.88376026  0.38250702  0.87486952]]

正規化後の最小値、最大値、平均値、標準偏差を見てみましょう。

print('最小値 :%f' % np.min(x_norm))
print('最大値 :%f' % np.max(x_norm))
print('平均値 :%f' % np.mean(x_norm))
print('標準偏差:%f' % np.std(x_norm))
最小値 :-2.053880
最大値 :2.016385
平均値 :-0.000000
標準偏差:1.000000

平均値0、標準偏差1にスケールされていることがわかります。また、最小値と最大値も変わっていることにも注意してください。

平均値0、標準偏差1にスケールする仕組みを理解する

Z-scoreについても平均値0、標準偏差1にスケール仕組みをひとつずつ確認していきましょう。

データは先ほどと同じデータを使います。

x = np.array([10, 20, 30, 40, 50])
print(x)
[10 20 30 40 50]

このデータの平均は30、標準偏差は$\sqrt{200}$になります。

print('平均値 :%f' % np.mean(x))
print('標準偏差:%f' % np.std(x))
平均値 :30.000000
標準偏差:14.142136

まずは分子から見ていきます。平均値30を引くので、分子の平均値は0になります。同様に標準偏差求めると$\sqrt{200}$になっています。

print('平均値 :%f' % (x - np.mean(x)).mean())
print('標準偏差:%f' % (x - np.mean(x)).std())
平均値 :0.000000
標準偏差:14.142136

分母は分子の標準偏差と同じ$\sqrt{200}$でしたので、割った結果の標準偏差は1になります。

print((x - np.mean(x)) / np.std(x))
[-1.41421356 -0.70710678  0.          0.70710678  1.41421356]

実際に平均値と標準偏差を求めます。

print('平均値 :%f' % np.mean((x - np.mean(x)) / np.std(x)))
print('標準偏差:%f' % np.std((x - np.mean(x)) / np.std(x)))
平均値 :0.000000
標準偏差:1.000000

外れ値に弱いとはどういうことか

Min-Max normalizationは外れ値に弱いという説明をしました。これがどういうことか、Z-score normalizationなら大丈夫なのかについて具体的な例を使って確認します。

以下のような具体例を用意します。

1,000人分の身長データを使って正規化を行います。身長データは正規分布に従いますが、990件は正常データで10件は外れ値であるとします。
この外れ値は100の位の1が読み取れず欠損したデータ(180cmが80cmになってしまった)が10件あるとします。

まずは必要なパッケージをインポートします。

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

正常データを作成する

まずは正常データ990件を用意します。それっぽい身長データにするために、平均値170、標準偏差10の正規分布に従う乱数を生成します。

np.random.seed(123)
x = np.random.normal(loc=170, scale=10, size=990)

これをヒストグラムで描画してみます。

plt.hist(x, bins=100)
plt.show()

numpy.random.normal関数を使って作成したデータですので正規分布に従っています。

ではこのデータをMin-Max normalization、Z-score normalizationで正規化し、同様にグラフを出力してみましょう。

正常データをMin-Max normalizationで正規化する

まずはMin-Max normalizationを使って正規化します。

x_norm = (x - np.min(x)) / (np.max(x) - np.min(x))
plt.hist(x_norm, bins=100)
plt.show()

最小値0、最大値1にスケールしただけで分布は変わっていません。

正常データをZ-score normalizationで正規化する

次にZ-score normalizationを使って正規化してみます。

x_norm = (x - np.mean(x)) / np.std(x)
plt.hist(x_norm, bins=100)
plt.show()

こちらも中心が0になっているだけで、分布は変わっていません。

外れ値を生成して正常データに加える

では、外れ値を10件生成し、それを正常データに加えていきます。

e1 = np.random.normal(loc=170, scale=10, size=10)-100
print(e1)
[60.91733437 76.55533937 72.40244965 63.547099   73.60026527 51.22368305
 72.34040489 76.24323922 78.11939825 49.77843013]

外れ値は単純に100を引いた値を使います。

このデータを正常値に加え、念のためシャッフルしておきます。

x2 = np.concatenate([x, e1])
np.random.shuffle(x2)
plt.hist(x2, bins=100)
plt.show()

外れ値は小さい値を用意したため、グラフは右に寄った形になっています。

では、これをMin-Max normalization、Z-score normalizationで正規化してみます。

外れ値を含むデータをMin-Max normalizationで正規化する

先ほどと同様の手順で正規化してグラフを出力します。

x2_norm = (x2 - np.min(x2)) / (np.max(x2) - np.min(x2))
plt.hist(x2_norm, bins=100)
plt.show()

外れ値を含まない場合は0.2から0.8くらいの範囲にデータが分布していましたが、外れ値を含むと0.6から0.9くらいの範囲に分布していることがわかります。
もともとは0.5くらいが中心でしたが、0.7から0.8あたりに中心が来ており、データが偏っていることになります。

これが外れ値に弱い(外れ値に敏感ともいう)ということになります。

外れ値を含むデータをZ-score normalizationで正規化する

では、Z-score normalizationだとどうなるでしょうか。

x2_norm = (x2 - np.mean(x2)) / np.std(x2)
plt.hist(x2_norm, bins=100)
plt.show()

こちらもグラフ自体は偏っていますが、外れ値を含まない場合も外れ値を含む場合も-2から2の範囲に分布していることがわかります。
外れ値を含んでいますが、正規化後の分布はほとんど変わっていない、すなわち外れ値に強いということがいえます。

このことから外れ値を含む場合や外れ値を除外できない場合は、Min-Max normalizationよりもZ-score normalizationの方が適しているといえます。
ただし、外れ値のデータを除外したり、外れ値を修正・補完して、想定される最小値・最大値の範囲できるのであれば、Min-Max normalizationを使用して正規化することも可能であることも覚えておいてください。

[NumPy]単位行列を作成する(eye関数、identity関数)

NumPyで単位行列(identity matrix)を作成する2つの方法について解説します。

NumPyには、単位行列を作成する方法が2つ用意されています。

  • eye関数(numpy.eye) – N×Mの単位行列を作成する
  • identity関数(numpy.identity) – 正方行列の単位行列を作成する

N×Mの単位行列を作成するeye関数

単位行列を作成する関数としてよく見かけるのがeye関数です。以下のように書くことで単位行列を作成することができます。

# 3x3の単位行列を作成する
E = np.eye(3)
print(E)

この例では引数に3を指定することで、3×3の単位行列を作成していますが、引数をもう一つ指定することで、正方行列以外の単位行列を作ることもできます。

以下の例では引数に3と4を指定することで、3×4の単位行列を作成しています。

# 3x4の単位行列を作成する
E = np.eye(3, 4)
print(E)
[[1. 0. 0. 0.]
 [0. 1. 0. 0.]
 [0. 0. 1. 0.]]

正方行列の単位行列を作成するidentity関数

eye関数に比べると知名度はありませんが、正方行列の単位行列を作成することができる、identity関数も用意されています。

先ほどと同様に3×3の単位行列を作成する場合は、以下のように書きます。

# 3x3の単位行列を作成する
E = np.identity(3)
print(E)
[[1. 0. 0.]
 [0. 1. 0.]
 [0. 0. 1.]]

どのように使い分けるのか

結論からいうと、使い分ける必要はありません。これまでどおりeye関数だけを使用していただいても、eye関数とidentity関数を使い分けても構いません。とりあえず、2つの方法があることだけは覚えておいてください。

PDBにローカルユーザーを作成する(Oracle Database 19c)

PDBにローカルユーザーの作成を行う手順について解説します。

前提

手順に使用しているデータベース環境およびCDB、PDBの設定、作成するローカルユーザーは以下のとおりです。

手順に沿ってローカルユーザーを作成する際は適宜読み替えてください。

項目
バージョンOracle Database 19c (19.3.0)
CDB
 SIDORCLCDB
PDB
 PDBローカル管理者ユーザーPDBADMIN
 PDBローカル管理者パスワードoracle
 SIDPDB1
ローカルユーザー
 ユーザーuser01
 パスワードpassword

PDBローカル管理者ユーザーにDBA権限を与える

PDBローカル管理者ユーザーを使ってローカルユーザーを作成するため、PDBローカル管理者ユーザーにDBA権限を付与します。

PDBローカル管理者ユーザーの権限を確認する

まずはCDBにOS認証でCDBに接続しますが、ORACLE_SID環境変数が必要となるため、ORACLE_SID環境変数を確認します。

$ echo $ORACLE_SID

この環境はDocker上に構築したOracle Database 19cですが、ログインしてみるとORACLE_SID環境変数は設定されていませんでした。

ORACLE_SID環境変数を設定してSQL*Plusを起動します。

$ ORACLE_SID=ORCLCDB sqlplus / as sysdba

SQL*Plus: Release 19.0.0.0.0 - Production on Sat Mar 19 14:36:24 2022
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.


Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> 

念のため、CDBに接続出来ていることを確認しましょう。

SQL> show con_name

CON_NAME
------------------------------
CDB$ROOT
SQL> 

PDBについても確認します。今回はすでに作成済みのPDBであるPDB1にローカルユーザーを作成していきます。

SQL> show pdbs

    CON_ID CON_NAME                       OPEN MODE  RESTRICTED
---------- ------------------------------ ---------- ----------
         2 PDB$SEED                       READ ONLY  NO
         3 PDB1                           READ WRITE NO
SQL> 

PDB1に切り替えます。

SQL> alter session set container=pdb1;

Session altered.

SQL> 

PDB1に切り替わったことを確認します。

SQL> show con_name

CON_NAME
------------------------------
PDB1
SQL>

PDBローカル管理者ユーザーPDBADMINに必要な権限があるかどうか確認します。

ここでは、以下の2つのテーブルを確認していきます。

  • SYS.DBA_ROLE_PRIVS – データベース内のすべてのユーザーおよびロールに付与されたロールを示す
  • SYS.DBA_SYS_PRIVS – ユーザーおよびロールに付与されたシステム権限を示す

まずはSYS.DBA_ROLE_PRIVSを確認します。

SQL> SELECT GRANTEE, GRANTED_ROLE, ADMIN_OPTION
  2  FROM SYS.DBA_ROLE_PRIVS
  3  WHERE GRANTEE = 'PDBADMIN'
  4  /

GRANTEE  GRANTED_ROLE ADM
-------- ------------ ---
PDBADMIN PDB_DBA      YES

SQL> 

PDB_DBAというロールを付与されていることがわかります。

次にSYS.DBA_SYS_PRIVSも確認しておきましょう。

SQL> select GRANTEE, PRIVILEGE, ADMIN_OPTION FROM SYS.DBA_SYS_PRIVS where GRANTEE = 'PDBADMIN'
  2  /

no rows selected

SQL> 

特に権限は付与されていません。

では、PDBADMINに与えられていたPDB_DBAロールについても確認していきます。

まずは、SYS.DBA_ROLE_PRIVSを確認します。

SQL> SELECT GRANTEE, GRANTED_ROLE, ADMIN_OPTION
  2  FROM SYS.DBA_ROLE_PRIVS
  3  WHERE GRANTEE = 'PDB_DBA'
  4  /

GRANTEE GRANTED_ROLE ADM
------- ------------ ---
PDB_DBA CONNECT      NO

SQL> 

CONNECTロールが付与されているだけでした。

次にSYS.DBA_SYS_PRIVSを確認します。

SQL> SELECT GRANTEE, PRIVILEGE, ADMIN_OPTION
  2  FROM SYS.DBA_SYS_PRIVS
  3  WHERE GRANTEE = 'PDB_DBA'
  4  /

GRANTEE PRIVILEGE                                ADM
------- ---------------------------------------- ---
PDB_DBA CREATE PLUGGABLE DATABASE                NO
PDB_DBA CREATE SESSION                           NO

SQL> 

CREATE PLUGGABLE DATABASECREATE SESSIONが付与されているだけで、DBA関連の権限は付与されていないことがわかります。

PDB_DBAロールにDBAロールを付与する

PDBローカル管理者ユーザーでローカルユーザーを作成できるようにするために、PDBローカル管理者ユーザーに付与されていたPDB_DBAロールにDBAロールを付与することにします。

SQL> GRANT DBA TO PDB_DBA;

Grant succeeded.

SQL> 

PDB_DBAロールにDBAロールが付与されているはずなので、確認しておきましょう。

SQL> SELECT GRANTEE, GRANTED_ROLE, ADMIN_OPTION
  2  FROM SYS.DBA_ROLE_PRIVS
  3  WHERE GRANTEE = 'PDB_DBA'
  4  /

GRANTEE GRANTED_ROLE ADM
------- ------------ ---
PDB_DBA DBA          NO
PDB_DBA CONNECT      NO

もともと付与されていたCONNECTロールに加え、DBAロールが付与されていることを確認できました。

これでPDBローカル管理者ユーザーPDBADMINでローカルユーザーの作成ができるようになったので、一度ログアウトしましょう。

SQL> exit
Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

ローカルユーザーを作成する

今度はPDBADMINユーザーでPDB1へログインします。

$ sqlplus pdbadmin/oracle@pdb1

SQL*Plus: Release 19.0.0.0.0 - Production on Sat Mar 19 15:09:27 2022
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.

Last Successful login time: Sat Mar 19 2022 13:56:08 +00:00

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> 

くどいようですが、念のためPDB1に接続していることを確認します。

SQL> show con_name

CON_NAME
------------------------------
PDB1
SQL> 

テーブルスペースの確認

ローカルユーザーに割り当てるテーブルスペースを作成するべきかどうかを判断するために、テーブルスペースを確認します。

SQL> SELECT TABLESPACE_NAME, BLOCK_SIZE, INITIAL_EXTENT, MAX_EXTENTS, MAX_SIZE, STATUS, CONTENTS
  2  FROM SYS.DBA_TABLESPACES
  3  /

TABLESPACE_NAME BLOCK_SIZE INITIAL_EXTENT MAX_EXTENTS   MAX_SIZE STATUS CONTENTS
--------------- ---------- -------------- ----------- ---------- ------ ---------------------
SYSTEM                8192          65536  2147483645 2147483645 ONLINE PERMANENT
SYSAUX                8192          65536  2147483645 2147483645 ONLINE PERMANENT
UNDOTBS1              8192          65536  2147483645 2147483645 ONLINE UNDO
TEMP                  8192        1048576             2147483645 ONLINE TEMPORARY
USERS                 8192          65536  2147483645 2147483645 ONLINE PERMANENT


SQL> SELECT FILE_NAME, TABLESPACE_NAME, BYTES, BLOCKS, STATUS
  2  FROM SYS.DBA_DATA_FILES 
  3  /

FILE_NAME                                      TABLESPACE_NAME      BYTES     BLOCKS STATUS
---------------------------------------------- --------------- ---------- ---------- ---------
/opt/oracle/oradata/ORCLCDB/PDB1/undotbs01.dbf UNDOTBS1         104857600      12800 AVAILABLE
/opt/oracle/oradata/ORCLCDB/PDB1/sysaux01.dbf  SYSAUX           367001600      44800 AVAILABLE
/opt/oracle/oradata/ORCLCDB/PDB1/system01.dbf  SYSTEM           283115520      34560 AVAILABLE
/opt/oracle/oradata/ORCLCDB/PDB1/users01.dbf   USERS              5242880        640 AVAILABLE


SQL> 

デフォルトに使用したいUSERSテーブルスペース、テンポラリに使用したいTEMPテーブルスペースがすでに存在しているため、テーブルスペースは作成せず進めます。

ローカルユーザーを作成する

では、ローカルユーザーuser01を作成します。テーブルスペースは先ほど確認したUSERSテーブルスペースとTEMPテーブルスペースを使用します。

SQL> CREATE USER user01
  2  IDENTIFIED BY password
  3  DEFAULT TABLESPACE USERS
  4  QUOTA UNLIMITED ON USERS
  5  TEMPORARY TABLESPACE TEMP
  6  /

User created.

SQL> 

ローカルユーザーに権限を付与する

ユーザーを作成しただけでは何もできないユーザーになっているため、必要な権限を付与します。ここでは以下の3つを付与します。

  • RESOURCEロール – テーブル等のオブジェクト作成を可能にするロール
  • CREATE SESSION権限 – データベースに接続するために必要な権限
  • UNLIMITED TABLESPACE権限 – テーブルスペースを制限なしで使用できる権限
SQL> GRANT RESOURCE TO user01
  2  /

Grant succeeded.

SQL> GRANT CREATE SESSION TO user01
  2  /

Grant succeeded.

SQL> GRANT UNLIMITED TABLESPACE TO user01
  2  /

Grant succeeded.

SQL>

ロール、権限が付与されているか確認します。

SQL> SELECT GRANTEE, GRANTED_ROLE, ADMIN_OPTION
  2  FROM SYS.DBA_ROLE_PRIVS
  3  WHERE GRANTEE = 'USER01' 
  4  /

GRANTEE GRANTED_ROLE ADM
------- ------------ ---
USER01  RESOURCE     NO


SQL> SELECT GRANTEE, PRIVILEGE, ADMIN_OPTION
  2  FROM SYS.DBA_SYS_PRIVS
  3  WHERE GRANTEE = 'USER01'
  4  /

GRANTE PRIVILEGE                                ADM
------ ---------------------------------------- ---
USER01 UNLIMITED TABLESPACE                     NO
USER01 CREATE SESSION                           NO


SQL> 

意図したとおりに設定できていることが確認できました。

動作確認を行う

user01ユーザーが使用可能か確認するために、一度切断します。

SQL> exit
Disconnected from Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

user01ユーザーでログインしてみます。

$ sqlplus user01/password@PDB1

SQL*Plus: Release 19.0.0.0.0 - Production on Sat Mar 19 15:28:15 2022
Version 19.3.0.0.0

Copyright (c) 1982, 2019, Oracle.  All rights reserved.

Last Successful login time: Sat Mar 19 2022 15:27:20 +00:00

Connected to:
Oracle Database 19c Enterprise Edition Release 19.0.0.0.0 - Production
Version 19.3.0.0.0

SQL> 

問題なく接続できていることを確認できました。

次にテーブルを作成し、データを追加、検索をしてみましょう。

SQL> CREATE TABLE emp
  2  (
  3    empno   VARCHAR2(10) NOT NULL,
  4    empname VARCHAR2(50),
  5    gender  NUMBER(1, 0)
  6  )
  7  /

Table created.

SQL> INSERT INTO emp VALUES ('ABC', 'DEF', '1')
  2  /

1 row created.

SQL> SELECT * FROM emp 
  2  /

EMPNO      EMPNAME                                                GENDER
---------- -------------------------------------------------- ----------
ABC        DEF                                                         1

SQL>

動作確認に使用したテーブルは不要なため、削除しておきます。

SQL> DROP TABLE emp 
  2  /

以上でローカルユーザーの作成が完了しました。

参考文献

sudoなしでdockerコマンドを実行できるようにする(dockerグループへユーザーを追加)

dockerコマンドを実行するときにsudoをつけずに実行できるようにする方法について解説します。

一般ユーザーがsudoなしで実行できるのは好ましい設定ではありません。本設定はローカル環境での利用にとどめ、ほかの人と共有して使用するサーバーや本番環境では、誤ってコンテナを停止してしまうなどの事故を防ぐためsudoを使用して操作することを推奨します。

ユーザーをdockerグループに追加する

手順自体はとてもシンプルですが、一つずつ確認していきます。

dockerグループの確認

まずはdockerグループがあるか確認します。

$ cat /etc/group | grep docker                                                                                                                                                           
docker:x:957:

Dockerのインストール方法にもよりますが、たいていはdockerグループが存在しています。

dockerグループが存在しない場合(コマンドを実行しても何も表示されない場合)は、以下のコマンドを実行してdockerグループを作成してください。

$ sudo groupadd docker

ユーザーをdockerグループに追加する

gpasswdコマンドを使ってdockerコマンドを使用したいユーザーをdockerグループに追加します。現在のユーザーを追加したい場合は以下のように実行します。

$ sudo gpasswd -a $USER docker                                                                                                                                                         
[sudo] password for xxx: 
Adding user xxx to group docker

現在のユーザー以外を追加したい場合は、$USERの部分を対象のユーザーに置き換えてください。

dockerデーモンを再起動する(dockerグループを作成した場合のみ)

dockerグループを作成した場合は、dockerデーモンの再起動が必要な場合があります。

systemctlを使用している場合は、以下のコマンドを実行します。

$ sudo systemctl restart docker                                                                                                                                                         
[sudo] password for xxx: 

ローカル環境で再起動しても問題ないのであれば、再起動していただいても構いません。

ログアウトして再度ログインする

前の手順で再起動している方はそのままログインしてください。それ以外の場合は一度ログアウトしてから、再度ログインしてください。

以上で、sudoなしでdockerコマンド(docker-composeコマンドも)が実行できるようになります。

なぜPermission deniedになるのか

そもそも、なぜdockerコマンドをsudoなしで実行するとPermission deniedになるかというと、dockerデーモンがUnixソケットを使用しており、dockerデーモンがrootユーザーで実行しているためです。

$ ls -l /var/run/docker.sock
srw-rw----  1 root    docker     0 Mar 19 09:51 docker.sock

実際に/var/run/docker.sockを見てみると、rootユーザーが所有者になっていることがわかります。

このことから、dockerデーモンと通信するdockerコマンドの実行には、docker.sockを読み取ることのできるrootユーザーまたはrootユーザー相当の権限が必要なため、sudoを使用します。

一方で、グループはdockerになっており、同様に読み取り権限があるため、ユーザーをdockerグループに追加することで、sudoを使用したときと同様にdockerコマンドが使用できるようになるという仕組みです。

ISOファイルをUSBフラッシュドライブ(USBメモリ)に書き込む(Rufus)

最近では、CD/DVDドライブが搭載されていないPCが多いため、LinuxのインストールなどではUSBフラッシュドライブ(以降はUSBメモリと表記)を使うことが一般的です。

本記事では、WindowsでISOファイルをUSBメモリに書き込むRufusというツールの使い方について解説します。

Rufusのダウンロード

まずは、公式サイトへアクセスします。

下のほうへスクロールしていくと、ダウンロードというセクションが見つかります。ここにある「Rufus x.xx」(執筆時点では「Rufus 3.18」)というリンクをクリックしてダウンロードしてください。

使用方法

ダウンロードしたrufus-x.xx.exeを起動すると、初回は以下のようなダイアログが表示される場合があります。

お好きなほうを選択してください。

ダイアログが閉じると、Rufusが起動します。

デバイスやボリュームラベルの表示内容はご使用のUSBメモリによって異なりますが、特に気にする必要はありません。
私は前回Linux Mint 20.3をインストールしたUSBメモリを使用しているため、このような表示になっています。

使用方法について行うことは多くなく、以下の3点を押さえておけば問題ありません。

  1. USBメモリを挿す
  2. ISOファイルを選択する
  3. 「スタート」ボタンをクリック(そして待つ)

まずは、USBメモリを挿しておいてください。デバイス欄にそのUSBの情報が表示されます。USBメモリを挿していない場合やうまく認識していない場合はデバイス欄は空欄になります。

USBメモリが認識されたら、次にISOファイルを選択します。ブートの種類欄の右の「選択」ボタンをクリックしてファイル選択ダイアログを表示します。

事前にダウンロードしておいたISOファイルを選択して、「開く」ボタンをクリックしてください。

USBメモリに書き込む準備ができました。「スタート」ボタンをクリックしてください。

いくつかダイアログが表示される場合があります。

ISOHybrid イメージの検出

ISO イメージモードで書き込む(推奨)」を選択したままで、「OK」ボタンをクリックしてください。

ダウンロードが必要です

「はい」ボタンをクリックしてください。

警告

すでにUSBメモリにデータがある場合に表示されます。使用しているUSBメモリが間違っていないことが確認できているのであれば「OK」ボタンをクリックしてください。

USBメモリへの書き込みが始まると、プログレスバーが増えていきます。

プログレスバーが100%になると、「スタート」ボタンがクリックできるようになります。

完了を通知するダイアログは示されないため、デバイス欄のUSBメモリの名前が変更されていること、やプログレスバーが100%で「準備完了」になっていることを確認してください。

asdfでよく使うコマンド集(アップデート、プラグイン、バージョン)

asdfの中でもよく使う、アップデート関連、プラグイン関連、バージョン関連のコマンドについて解説します。

アップデート関連

プラグイン自体のアップデートや追加したプラグインのアップデートについて解説します。

asdf自体のアップデート

それほど頻繁に実施する必要はりませんが、asdf自体をアップデートする場合は、以下のコマンドを実行します。

asdf update

すべてのプラグインのアップデート

特段理由がなければすべてのプラグインをアップデートする方が楽です。すでに追加済みのプラグインをすべてアップデートするときは、以下のコマンドを実行します。

asdf plugin-update --all

特定のプラグインのみをアップデート

たくさんのプラグインを追加していて全部アップデートするのは時間がかかる、バージョンをあげたくないプラグインがある、など何かしらの理由があって特定のプラグインのみをアップデートしたいときは、以下のコマンドを実行します。

asdf plugin-update <プラグイン名>

<プラグイン名>には、アップデートしたいプラグインを指定します。

プラグイン関連

プラグインの表示、追加、削除など基本的な操作について解説します。

プラグインの一覧表示

プラグインを一覧表示するときは、以下のコマンドを実行します。

asdf plugin list all

大量に表示されますので、Grepで対象を絞り込む方がよいでしょう。

$ asdf plugin list all | grep node                                                                                                                                                                                
nodejs                       *https://github.com/asdf-vm/asdf-nodejs.git

上記の例では、Node.jsのプラグイン名を調べるためににnodeでGrepし、nodejsであることが分かりました。

現在インストール中のプラグインの一覧表示

すでにインストール済みのプラグインをしたいときは、以下のコマンドを実行します。

asdf plugin list

プラグインの追加

プラグインを追加するときは、以下のコマンドを実行します。

asdf plugin add <プラグイン名>

<プラグイン名>のところは、追加したいプラグインの名前を指定します。

プラグインの削除

プラグインを削除するときは、以下のコマンドを実行します。

asdf plugin remove <プラグイン名>

<プラグイン名>のところは、削除したいプラグインの名前を指定します。

バージョン関連

バージョンに関する操作について解説します。

使用可能なバージョンの一覧表示

特定のプラグインの使用可能なバージョンを一覧表示したいときは、以下のコマンドを実行します。

asdf list all <プラグイン名>

こちらもGrepを併用することでインストールしたいバージョンを特定するのがよいでしょう。

asdf list all nodejs | grep ^11.

上記のでは、nodejsパッケージのうち、11系だけをGrepで絞り込んでいます。

インストール済みのバージョンを一覧表示

すでにインストール済みのバージョンを一覧表示したいときは、以下のコマンドを実行します。

asdf list <プラグイン名>

バージョンをインストールする

指定したバージョンをインストールするときは、以下のコマンドを実行します。

asdf install <プラグイン名> <バージョン>

<プラグイン名>はインストールしたいプラグインの名前、<バージョン>はインストールしたいバージョンを指定します。

バージョンをアンインストールする

指定したバージョンをアンインストールするときは、以下のコマンドを実行します。

asdf uninstall <プラグイン名> <バージョン>

<プラグイン名>はアンインストールしたいプラグインの名前、<バージョン>はアンインストールしたいバージョンを指定します。

バージョンの設定

asdfでは、globallocalshellの3つの設定範囲があります。使用目的によって適切な設定範囲を選択することで、効率よくバージョン管理を行うことができます。

globalバージョンの設定

globalバージョンは、特に指定がない場合に使用されるバージョンです。globalバージョンを設定するときは、以下のコマンドを実行します。

asdf global <プラグイン名> <バージョン>

<プラグイン名>はglobalバージョンを設定したいプラグインの名前、<バージョン>はglobalバージョンに設定したいバージョンを指定します。

globalバージョンは、システム全体で使用するバージョンを設定するとよいでしょう。言い方を変えると、特段理由がない場合に使用するバージョンであるとも言えます。例えば、メインストリームのバージョンやLTSのバージョンを設定しておく運用方法が適切です。

localバージョンの設定

特定のフォルダ(ディレクトリ)内でのみ有効なバージョンです。globalバージョンで使用しているバージョンとは異なるバージョンを使いたい場合に使用するとよいでしょう。

localバージョンを設定するときは、以下のコマンドを実行します。

asdf local <プラグイン名> <バージョン>

現在のフォルダ(ディレクトリ)およびそのサブフォルダ(ディレクトリ)ではlocalバージョンが有効になります。このとき、.tool-versionsが作成され、使用するプラグインとバージョンが記録されます。

localバージョンは、プロジェクトで使用するバージョンを指定するために使用するとよいでしょう。
例えば、普段はPython 3.9.xを使用していますが、今携わっているプロジェクトでは、Python 3.7.xでないとビルドができないとしましょう。
この場合、globalバージョンは3.9.xですが、プロジェクトのフォルダ(ディレクトリ)内ではPython 3.7.xをlocalバージョンに指定しておくことで、このプロジェクト内だけは3.7.xを使用することができます。
もし、複数の保守プロジェクトを抱えている場合であれば、この恩恵はとても大きいものになります。

shellバージョンの設定

シェル内でのみ有効なバージョンです。使用頻度は高くありませんが、使用するコマンドが特定のバージョンを要求する場合などに重宝します。

shellバージョンを設定するときは、以下のコマンドを実行します。

asdf shell <プラグイン名> <バージョン>

これは現在のシェルの設定が変わるわけではなく、新にシェルを起動し、そこでのバージョンが設定したバージョンになります。そのため、使用を終了する場合は、exitコマンドで抜けることができます。

shellバージョンは、特定のコマンドやツールを実行する際に、特定のバージョンまたは特定のバージョン以下でないと実行できない、といった場合に使用するとよいでしょう。
例えば、プロジェクトでは、Node.js 16.xを使用しています。リリース用のツールもNode.jsで書かれていますが、古いツールなためNode.js 11.xでないと動作しません。
こういった場合には、globalバージョンまたはlocalバージョンは16.xを設定し、ツールを実行するときだけ、shellバージョンに11.xを設定してコマンドを実行する運用にする方が、バージョンアップ対応を行うよりもコストメリットがあります。

モバイルバージョンを終了