機械学習(2)ニューラルネットから

このページは学習中です。

前回

参考文献
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装 単行本(ソフトカバー) – 2016/9/24

およびkerasドキュメント
およびwikipedia

ニューラルネット

極めて適当な理解

ネットワーク状の数理モデル

グラフ状に接続されたノードを通して入力を受け、
出力をフィードバックしながら各ノードに対応する係数を調整することを学習、あるいは訓練という。
結果、調整された係数(あるいは重み)は固定され、ネットワーク全体として関数の代替、あるいは近似をなす。
この重みの調整によって完成したネットワークは、未知の入力に対しても識別器、あるいは生成器として機能する。

識別モデル
例えば最終出力ノードが1つであり、このノードの出力が閾値を超えていた場合、この関数はtrue/falseの質問に答えることができると言える。
例えば最終出力ノードが12個あり、各々のノードが干支に対応しているとするならば、この関数は出力ノード12個のうち、最大の出力を吐くノードに対応した動物を認識することができると言える。
ただしこの辺りならば、統計や画像認識、あるいはコンピュータービジョンの領域において一撃で近似を得る手法があったりなかったりする。単にAIや機械学習と言った場合、それらの領域とけっこう重複する。


生成モデル
しばしまたれよ。
文章作ったり画像作ったりはこっちのモデルである。


ニューラルネット、あるいは機械学習のモデルは
それなりに片道通行の層状に理解されることが多い、というか分かりやすい。分岐構造やループ構造を使うと急激に難易度が上がったり上がらなかったりする。

各ノードは、
前の層のノードからの複数の重み(入力に対する重み)と
活性化関数から成るのが基本構成である。

バッチ

ほとんどwikipediaを参照した推理。

例えば入力が画像である場合、
画像をいくらかまとめて扱うならばそれはバッチである。

巨大な全体データに対していくらかのまとまりを作る場合、
本来それはミニバッチであり、ミニバッチを用いた学習はミニバッチ学習である。また、ミニバッチを用いた最適化手法はミニバッチ勾配降下法である。
ミニバッチに対して全体のことをバッチといい、その学習はバッチ学習である。また、バッチを用いた最適化手法はスタンダード、あるいはバッチ勾配降下法であり、これは最急降下法である。

入力の全体データから、ランダムに単一のデータを取得して学習に用いる場合、これはオンライン学習であり、その最適化手法は確率的勾配降下法である。バッチサイズ=1でもある。

また、ミニバッチのことをバッチといっている場合もある。
また、ミニバッチのことをバッチといっている場合もある。
また、ミニバッチのことをバッチといっている場合もある。

なぜミニバッチ化するか

ゼロから作るdeep learningのp79
およびwikipedia参照

各ステップを個別に計算するのではなく、ベクトル化ライブラリを利用することができるため(wikipedia)
各ステップで計算される勾配は、より多くの学習サンプルに対して平均化されるため、収束がスムーズになる可能性もあります(wikipedia)

これはつまりSIMD、つまりGPUを有効利用できるため。1枚あたりに換算すると逆に早い。

あるいは1枚1枚入力していると意図しない画像が紛れていた時にそいつのせいで学習がオシャカになるのを防ぐためと勝手に思っているが、これは不明。収束スムーズと同じことを言ってるかもしれない。

バッチを考慮したネットワーク

ニューラルネットに画像を入力することを考える。
画像1枚をとってもどのような入力形式にすべきかが問われる。
たとえば画像のサイズは(width, height)にすべきか、あるいは1列(width*height)にしてしまうか。
また、画素の位置を意識するなら(height, width)にしなければならない。これは行列(row, col)に対応する。この行列には暗に上下左右の情報が含まれる。
また、各画素は2値(0,1)やグレースケール(0-255)とすべきか、
RGB(0-255,0-255,0-255)かRGBAか。を意識する必要がある。

これらを一般的に扱うなら行列でも足りないため、テンソルを使用することになる。テンソルならば例えば画像は
(width, height, channel)あるいは(height, width, channel)であろう。

256*256のRGB画像なら(256,256,3)。
256*256のグレースケールなら(256,256,1)である。
とにもかくにもテンソルを使いたくなければ
グレースケールなら(256,256)、あるいはRGBなら(width*height, channel)で対応できなくもないように見える。

ところで画像というのは1枚1枚入力するより何枚かまとめて入力した方が効率がよかったり、効果が出たりする。この何枚かまとめたまとまりをバッチという。
この場合、(batch_count, height, width, channel)となる可能性がある。
このケースは、入力側にバッチを押し付けた場合である。
バッチの処理を損失関数に任せる場合、入力は(height, width, channel)で良い。

総じて汎用性を考えるならテンソルを使用せざるを得ない。

活性化関数(activation)

極めて適当な理解
ネットワークは各層の各ノードに活性化関数を使用することで全体としての非線形性を獲得する。これがなければ何層積んでもパーセプトロンである。

ステップ関数

入力:なんでもよい
出力:0か1

$$
h(x)=\begin{cases}
0 & (x\leqq 0) \\
1 & (x>0)
\end{cases}
$$

実装

単純に実数を受ける場合

def step_function(x):
  if(x>0):
    return 1
  else:
    return 0

ndarrayを受ける場合

def step_function(x):
  y = x>0
    return y.astype(np.int)

ndarrayに対する比較演算はbool値のndarray

標準シグモイド

入力:なんでもよい
出力:0から1

$$
h(x)=\frac{1}{1+\exp(-x)}
$$

expはネイピア数を底とした指数関数。
e=2.71…

指数関数は
wikipediaより
指数関数$${\exp_a(x) :=a^x(a>0, a \neq 1)}$$は次の性質を持つ。
a>1のとき狭義増加$${s>t \Rightarrow a^s>a^t}$$
0<a<1のとき狭義減少$${s>t \Rightarrow a^s<a^t}$$

狭義増加関数とは
$${x_1<x_2 \rightarrow f(x_1)<f(x_2)}$$
となるf()の部分。
広義増加関数とは
$${x_1\leqq x_2 \rightarrow f(x_1)\leqq f(x_2)}$$
となるf()の部分。
違いは不等号ないし比較演算子

ここでe>0だからeを底とした指数関数は狭義増加。
今、ネイピア数を底とした指数関数expと一般的な指数関数expの表記をまぜこぜにしているので注意を要する。

ところで
$${x^{-n}}$$は$${\frac{1}{x^n}}$$であったからして、
x>0ならばnの増加に伴って爆速で0に向かって小さくなる。
(x<0ならば符号がくるくる入れ替わりながら0に向かって小さくなる)

つまり

$$
h(x)=\frac{1}{1+\exp(-x)}
$$

におけるネイピア数を底としたexp(-x)は

x=0の時2

$$
\exp(-x)=2, h(x)=0.5
$$

x>0の時、xの増加に伴って

$$
\exp(-x)\rightarrow爆速で0に向かう, h(x)\rightarrow 1に向かう
$$

x<0の時、xの減少に伴って

$$
\exp(-x)\rightarrow爆速で+∞に向かう, h(x)\rightarrow 0に向かう
$$

また、$${\exp(-x)}$$の-x部分に普通の関数を放り込むような使い方もみられる。これは結局その適当な関数の出力がなんであれ、シグモイド関数を通して0-1に収まる。

ReLU(rectified linear unit)

ランプ関数(信号理論などで見られる)
正規化線形ユニット

max(0,x)

入力が負なら0
それ以外ならそのまま返す

理論的にはシグモイドが人気であったが
実用上一番良かったもの。

LeakyReLU

負値入力は入力に比例した小さな負値を返す

max(0.01x, x)

ReLUが0を返してしんじゃう時に使うらしいが、意味ない説もあり、スプリングワッシャー的なものと推測される。

2013年にmax(0.01x, x) がLReL (leaky rectified linear) と命名された。ただし、命名者はこの活性化関数を使う意味はなかったと報告している[5]。
2018年にx*sigmoid(x) がSwish (あるいはSiLU, sigmoid weighted linear) と命名された。この関数はReLUよりも高い分類精度を得ることができると示されている[6]。

wikipedia

[5] Andrew L. Maas; Awni Y. Hannun; Andrew Y. Ng (2013). Rectifier Nonlinearities Improve Neural Network Acoustic Models.
[6] Ramachandran, Prajit; Zoph, Barret; Le, Quoc V. (2017-10-27). “Searching for Activation Functions”. arXiv:1710.05941 [cs].

softmax

その層の出力の総和を1にする。
多クラス分類などに有効。
出力ノードが1つなら2値分類も可能。

$$
y_i=\frac{e^{x_i}}{\sum_{j=1}^{J}e^{x_j}}
$$

Jはその層におけるノード数

損失関数

学習をするためにはまず
入力データと、その入力データがネットワークを通過した後に出力すべき正解データの組を用意する。
これらの組を訓練データやら教師データと呼ぶ。

ネットワークに適当な重みを設定し、
訓練データから入力する。その出力を、組みとなる正解データと比較し、損失を計上する。損失を計上するための関数が損失関数である。

損失は0に近い方が正解に近づく。
ネットワークの重みは損失関数の出力を減少させるように調整される。
そのために用いられるのが損失関数の勾配を用いて重みを調整する勾配法であり、勾配法のために効率的に勾配を求めるのが誤差逆伝播法である。

損失関数はニューラルネットの全層全ノードを通過した出力と、正解データとの比較を行うため、損失関数を1回実行することはニューラルネットを全部たぐり、ネットワークの出力を得るということである。
また、そのような誤差関数の微分を求めるとは、各ノード毎の重み、係数、パラメータをちょっとずらした時の誤差関数の変動を見ることである。
(ゼロから作るDeep Learningの実装例の場合、誤差関数の数値微分は層ごとに行われるが、内部で重みを走査するため、これはノード毎に偏微分値を求めていることになると思われる)
すなわち、誤差関数の微分を素直に求めるとは、極めて効率の悪い行為となる。なので誤差逆伝播法が必要。

ゼロから作るDeep Learningの場合
ある層のノードの重みx[index]をちょっとずらして編微分値を求める
例えば以下のような形。

$$
\frac{\partial Loss(x[index])}{\partial x[index]} = \frac{Loss(x[index]+h)-Loss(x[index]-h)}{2h}
$$

実装的にはLoss関数が現在の重みと正解の重みである(x,t)のndarrayを受けるため、x[index]をx[index]+hなどに入れ替えてLoss(x)した後、x[index]を元の値に戻すという方法をとっている。

https://github.com/oreilly-japan/deep-learning-from-scratch/blob/master/ch04/two_layer_net.py

    # x:入力データ, t:教師データ
    def loss(self, x, t):
        #ネットワークの出力値 
        y = self.predict(x)
        #交差エントロピー誤差 
        return cross_entropy_error(y, t)

2乗和誤差

$$
E=\frac{1}{2}\sum\limits_k(y_k-t_k)^2
$$

tはネットワークの出力。
yは教師、正解データ。

誤差=正解データからの離れ具合=y-t
2乗することでプラスマイナスの符号を消す。
絶対値でも良いが2乗ならば微分の扱いが楽なため2乗される。
1/2は微分のためのお供え。

最小二乗法でおなじみのやつ。


交差エントロピー誤差

kerasの場合categorical_crossentropy
https://github.com/keras-team/keras/blob/master/keras/losses.py#L1944

指数

$$
\sqrt[n]{a}=a^{\frac{1}{n}}\\
\sqrt{a}=a^{\frac{1}{2}}\\
a^{-1}=\frac{1}{a}
$$

対数

$${a>0かつa\neq1}$$の時(底の条件)
かつ$${p>0}$$の時(真数条件)

$${x=a^p}$$に対して$${p=log_a x}$$

すなわちaをp乗するとx

aを底
xを真数
a=10の時、常用対数
a=e(ネイピア数)の時、自然対数

$$
\log_a xy = \log_a x+\log_a y\\
\log_ax^p=p\log_ax\\
\log_aa=1,\log_a1=0
$$

交差エントロピー誤差は

$$
E=-\sum\limits_k t_k \log y_k
$$

バッチ単位の場合

$$
E=-\frac{1}{N}\sum\limits_n \sum\limits_k t_{nk} \log y_{nk}
$$

tはネットワークの出力。
yは教師、正解データ。
logの底はeらしい

//疑似コード
batch_cross_entropy(batch, y)
{
  ret = 0
  for(img in batch)
  {
    t = model(img)
    ret += cross_entropy(t,y)
  }
  return ret/batch.count()
}


平均二乗誤差(MSE)

Mean squared error

平均絶対誤差(MAE)

Mean absolute error


最適化(optimizer)

重みの調整手法にもいろいろある。
基本的にはネットワークに放り込む入力データの扱いに着目し、
入力データをネットワークに全部放り込むのか、
適当に選んで放り込むのか、
適当にピックアップして放り込むのか、などとなる。

勾配法

勾配、微分値を使ってパラメータ修正したら勾配法。

最急降下法

Gradient descent
あるいはstandard (or "batch") gradient descent

最も勾配変化の大きい方向に重みを調整する。
最も急であるためには他のすべてと比較する必要がある。

すなわち全ての入力データに対し、損失関数がたたき出す勾配を計算し、その最大の勾配をもって重みを調整する。
全部調べてから動くため、確率的でない。道が複数でなければ定まった道を転がる。
最急降下法以外はだいたい確率的勾配降下法の亜種となる。
Momentum以下の亜種はだいたい確率的でありだいたいミニバッチで扱われる。

より一般的な最急降下法
数学、あるいは最適化の分野などで用いられる。

以下wikipedia

Gradient descent is based on the observation that if the multi-variable function $${\bm F(\mathbf x)}$$ is defined and differentiable in a neighborhood of a point $${\mathbf a}$$ , then $${\bm F(\mathbf x)}$$ decreases fastest if one goes from $${\mathbf a}$$ in the direction of the negative gradient of $${\bm F}$$ at $${\mathbf a}$$ ,$${-\nabla \bm F(\mathbf a)}$$. It follows that, if

最急降下法とは、多変数関数$${\bm F(\mathbf x)}$$が定義され、かつある点$${\mathbf a}$$の近傍で微分可能であるならば、関数$${\bm F(\mathbf x)}$$は点$${\mathbf a}$$から$${\bm F}$$の負勾配$${-\nabla \bm F(\mathbf a)}$$の方向に向かうと最速で減少する。これは以下のように表される。

$$
\mathbf a_{n+1}=\mathbf a_n-\gamma\nabla\bm F(\mathbf a_n)
$$

for a small enough step size or learning rate $${\gamma \in \mathbb {R}_ {+}}$$, then $${\bm F(\mathbf a_n) \geq \bm F(\mathbf a_{n+1} )}$$.

十分小さいステップサイズ、あるいは学習率$${\gamma}$$に対して$${\bm F(\mathbf a_n) \geq \bm F(\mathbf a_{n+1} )}$$

In other words, the term $${\gamma \nabla \bm F(\mathbf a)}$$ is subtracted from $${\mathbf a}$$  because we want to move against the gradient, toward the local minimum. With this observation in mind, one starts with a guess $${\mathbf x_0 }$$ for a local minimum of $${\bm F}$$, and considers the sequence $${\mathbf x_0,\mathbf x_1,\mathbf x_2,\ldots }$$such that

換言すれば、$${\gamma \nabla \bm F(\mathbf a)}$$項は地点$${\mathbf a}$$から減算される。なぜなら我々は、勾配に逆らって、(関数$${\bm F}$$の)局所最小値に向かって移動したいからです。
この観測を念頭に置きながら、$${\mathbf x_0}$$を関数$${\bm F}$$の局所最小値と仮定すると、以下のような数列 $${\mathbf x_0,\mathbf x_1,\mathbf x_2,\ldots }$$

$$
\mathbf x_{n+1}=\mathbf x_n-\gamma_n \nabla \bm F(\mathbf x_n), n\geq 0
$$

We have a monotonic sequence
に対して、我々は単調増加の数列

$$
\bm f(\mathbf x_0) \geq \bm f(\mathbf x_1) \geq \bm f(\mathbf x_2) \geq \ldots,
$$

を得る。

so, hopefully, the sequence $${(\mathbf x_n)}$$ converges to the desired local minimum. Note that the value of the step size $${\gamma}$$ is allowed to change at every iteration. With certain assumptions on the function $${\bm F}$$(for example, $${\bm F}$$ convex and $${\nabla \bm F}$$ Lipschitz) and particular choices of $${\gamma}$$  (e.g., chosen either via a line search that satisfies the Wolfe conditions, or the Barzilai–Borwein method[7][8] shown as following),

であるならば、数列$${(\mathbf x_n)}$$は局所最小値に収束する。
なお、ステップサイズ$${\gamma}$$は反復毎に変更可能。
関数$${\bm F}$$にある種の仮定(例えば$${\bm F}$$が凸、$${\nabla \bm F}$$がリプシッツ連続というような)をしてみたり、特定の$${\gamma}$$ 値(例えばWolfe条件を満たす直線探索とか、以下のようなBarzilai-Borwein法[7][8])で選択してみたりすると、最小値への収束が保証されます。

ここにBarzilai-Borwein法

convergence to a local minimum can be guaranteed. When the function $${\bm F}$$ is convex, all local minima are also global minima, so in this case gradient descent can converge to the global solution.

関数 $${\bm F}$$ が凸であるとき、すべての局所最小値は大域的な最小値でもあるので、この場合、勾配降下は大域的な解に収束することができます。

This process is illustrated in the adjacent picture. Here, $${\bm F}$$ is assumed to be defined on the plane, and that its graph has a bowl shape. The blue curves are the contour lines, that is, the regions on which the value of $${\bm F}$$ is constant. A red arrow originating at a point shows the direction of the negative gradient at that point. Note that the (negative) gradient at a point is orthogonal to the contour line going through that point. We see that gradient descent leads us to the bottom of the bowl, that is, to the point where the value of the function $${\bm F}$$ is minimal.

この過程を図示したのが次の図です。ここで$${\bm F}$$ は平面上で定義され、そのグラフはお椀型であると仮定します。青い曲線は等高線、つまり$${\bm F}$$ の値が一定である領域を示します。ある点を始点とする赤い矢印は、その点での負の勾配の方向を指示しています。ある点での(負の)勾配は、その点を通る等高線に直交していることに注意が必要です。勾配降下は鉢の底、つまり関数 $${\bm F}$$ の値が最小となる点まで導いてくれることがわかります。

図はwikipediaで見てください。

関数の勾配が等高線の法線となるうんぬんは以下を参考に。
ただしあってるかどうかは分かりません。


確率的勾配降下法(SGD)

stochastic gradient descent
あるいはon-line gradient descent

入力データから適当な1つを選んでネットワークに放り込み、この出力と正解データをもとに損失関数から勾配を計算、重みを調整する。

ミニバッチ確率的勾配降下法

入力データを適当にいくらか見繕ってネットワークに放り込み、最大の勾配を用いて重みを調整する。
いくらかまとめて見繕っていることを強調しているだけにすぎない。

Momentum

確率的勾配降下法は重み調整位置に瞬間移動しながら動く。この場合、勾配がほとんどない地点などに落ち込むとしぬ。Momentumは慣性項を追加し、直前の位置から動いてきた分が考慮されるため、勾配がない地点に落ち込んでもそこから脱出しようとする力が働く。

AdaGrad

adaptive gradient algorithm

学習係数を徐々に小さくする。
これは目的地点に近づいてきたら調整を細かくするという手法。

RMSProp

Root Mean Square Propagation

AdaGrad改良。AdaGradは過去の勾配をすべてひきずるが、それゆえに学習が進むと学習係数が小さくなりすぎ、重みの修正が停止する。RMSPropはそれを補うため、過去の勾配を段階的に捨てていく。

Adam

Momentum+AdaGrad

レイヤー

主にkerasの場合

Dense(全結合層)

この層の各ノードは各々、次の層の各ノードに接続される。
1次元である。

Activation(活性化層)

Denseに組み込むことも独立して配置することもできる。

Dropout(ドロップアウト)

いくつかの入力を捨てる。
過学習抑制に効果がある。

Nitish Srivastava, Geoffrey Hinton, Alex Krizhevsky
Ilya Sutskever, Ruslan Salakhutdinov
Dropout: A Simple Way to Prevent Neural Networks from
Overfitting

BatchNormalization(バッチ正規化)

Sergey Ioffe, Christian Szegedy
Batch Normalization: Accelerating Deep Network Training by
Reducing Internal Covariate Shift

線形レイヤと非線形レイヤの間で使用するのが一般的。

Flatten

入出力のテンソルの次元を調整するための層。
この層の出力は1次元となる。
Denseは1次元なので、Conv2Dの後などにFlattenをかませる必要がある。

Conv1D

音、文字列

Conv2D

画像

畳み込みレイヤーは画像に対してはエッジの抽出など、特徴量の抽出効果がある。

フィルター(カーネル)の設定が必要となる。
kerasでは標準でglorot_uniform

glorot_uniform
Glorot の一様分布(Xavier の一様分布とも呼ばれる)

データセット

MNIST

手書き数字

kerasの場合
https://keras.io/ja/datasets/

from keras.datasets import mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()


参考
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装 単行本(ソフトカバー) – 2016/9/24斎藤 康毅 (著)


 #実行している .pyファイルが格納されているフォルダの名前を取得
dataset_dir = os.path.dirname(os.path.abspath(__file__)) #データセットをpickleファイルとして保存するためのファイルパス 
save_file = dataset_dir + "/mnist.pkl"

ピクルスファイルが無ければMNISTデータのダウンロードから始める(初回)。MNISTデータはMNISTが定めた単純なバイトデータであるため、ちゃんと読み込まなければならない。
既にピクルス保存済みならピクルスのロード。

def load_mnist(normalize=True, flatten=True, one_hot_label=False):
    """MNISTデータセットの読み込み
    Parameters
    ----------
    normalize : 画像のピクセル値を0.0~1.0に正規化する
    one_hot_label :
        one_hot_labelがTrueの場合、ラベルはone-hot配列として返す
        one-hot配列とは、たとえば[0,0,1,0,0,0,0,0,0,0]のような配列
    flatten : 画像を一次元配列に平にするかどうか
    Returns
    -------
    (訓練画像, 訓練ラベル), (テスト画像, テストラベル)
    """
    if not os.path.exists(save_file):
        init_mnist()

    with open(save_file, 'rb') as f:
        dataset = pickle.load(f)

URLにある.gzファイルのダウンロードが試みられる。
既に存在するならスルー。

url_base = 'http://yann.lecun.com/exdb/mnist/'
key_file = {
    'train_img':'train-images-idx3-ubyte.gz',
    'train_label':'train-labels-idx1-ubyte.gz',
    'test_img':'t10k-images-idx3-ubyte.gz',
    'test_label':'t10k-labels-idx1-ubyte.gz'
}

.gzファイルを読み込んで連想配列に紐付けるところ。

def _convert_numpy():
    dataset = {}
    dataset['train_img'] =  _load_img(key_file['train_img'])
    dataset['train_label'] = _load_label(key_file['train_label'])
    dataset['test_img'] = _load_img(key_file['test_img'])
    dataset['test_label'] = _load_label(key_file['test_label'])

    return dataset

.gzファイルをバイトファイルから読み込んでndarrayにするところ

def _load_img(file_name):
    file_path = dataset_dir + "/" + file_name

    print("Converting " + file_name + " to NumPy Array ...")
    with gzip.open(file_path, 'rb') as f:
            data = np.frombuffer(f.read(), np.uint8, offset=16)
    data = data.reshape(-1, img_size)
    print("Done")

    return data

ここでdata.reshape(-1, img_size)におけるimg_sizeは784

data = np.frombuffer(f.read(), np.uint8, offset=16)の時点で
dataは1次元のndarrayである。
つまり全ての画像相当データが1列に並んでいる状態。
reshapeで画像単位にちょん切っている。
-1は他の次元に合わせて調整される。
例えばMNISTの訓練画像は60000枚であるから、reshape(60000, 784)である。テスト画像は10000枚であるからreshape(10000, 784)である。
 
例えばdata[0]は画像1枚であるが、その画僧データは1次元である。
(このソースにおけるflatten==Trueの状態)

Python

np.array

入力:python array
戻値:入力のコピーとなるndarray

np.frombuffer

Parameters :

buffer : buffer_like
An object that exposes the buffer interface.
buffer インターフェースを公開するオブジェクト。
dtype : data-type, optional
Data-type of the returned array; default: float.
返された配列のデータ型。デフォルト:float。
count : int, optional
Number of items to read. -1 means all data in the buffer.
読み込むアイテムの数。-1 はバッファの全データを意味する。
offset : int, optional
Start reading the buffer from this offset (in bytes); default: 0.
このオフセットからバッファの読み込みを開始する(バイト単位);デフォルト:0。
like : array_like, optional
Reference object to allow the creation of arrays which are not NumPy arrays. If an array-like passed in as like supports the array_function protocol, the result will be defined by it. In this case, it ensures the creation of an array object compatible with that passed in via this argument.
NumPy の配列でない配列を作成できるようにするための参照オブジェクトです。likeとして渡された配列のようなものがarray_functionプロトコルをサポートしている場合、結果はそれによって定義されます。この場合、この引数で渡されたものと互換性のある配列オブジェクトが作成されることを保証します。

Returns : out ndarray

ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装 単行本(ソフトカバー) – 2016/9/24斎藤 康毅 (著)
の場合

    with gzip.open(file_path, 'rb') as f:
            data = np.frombuffer(f.read(), np.uint8, offset=16)

.gzファイルをバイトモードで読む(rb)
openの戻値はファイルオブジェクト
バイトモードのファイルオブジェクトをread()するとバイトオブジェクトが返る。

https://docs.python.org/ja/3/tutorial/inputoutput.html

MNISTのイメージデータは16バイト目からから始まるのでoffset=16
uint8は符号なし8ビット整数だからバイト相当である。

TRAINING SET IMAGE FILE (train-images-idx3-ubyte):

[offset] [type] [value] [description]
0000 32 bit integer 0x00000803(2051) magic number
0004 32 bit integer 60000 number of images
0008 32 bit integer 28 number of rows
0012 32 bit integer 28 number of columns
0016 unsigned byte ?? pixel
0017 unsigned byte ?? pixel
........
xxxx unsigned byte ?? pixel

Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).




pickle(ピクルス)

https://docs.python.org/ja/3/library/pickle.html

データ保存のための
pythonデータ構造⇔バイナリ、バイト
をやりとりするやつ。
日本語の慣用句的には酢漬けより塩漬けのがバシッとはまるであろう。

os.path

path操作標準モジュール

https://docs.python.org/ja/3/library/os.path.html
https://github.com/python/cpython/blob/3.10/Lib/ntpath.py

# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
# Previously, this function also truncated pathnames to 8+3 format,
# but as this module is called "ntpath", that's obviously wrong!

def normpath(path):
    """Normalize path, eliminating double slashes, etc."""
    path = os.fspath(path)
    if isinstance(path, bytes):
        sep = b'\\'
        altsep = b'/'
        curdir = b'.'
        pardir = b'..'
        special_prefixes = (b'\\\\.\\', b'\\\\?\\')
    else:
        sep = '\\'
        altsep = '/'
        curdir = '.'
        pardir = '..'
        special_prefixes = ('\\\\.\\', '\\\\?\\')
    if path.startswith(special_prefixes):
        # in the case of paths with these prefixes:
        # \\.\ -> device names
        # \\?\ -> literal paths
        # do not do any normalization, but return the path
        # unchanged apart from the call to os.fspath()
        return path
    path = path.replace(altsep, sep)
    prefix, path = splitdrive(path)

    # collapse initial backslashes
    if path.startswith(sep):
        prefix += sep
        path = path.lstrip(sep)

    comps = path.split(sep)
    i = 0
    while i < len(comps):
        if not comps[i] or comps[i] == curdir:
            del comps[i]
        elif comps[i] == pardir:
            if i > 0 and comps[i-1] != pardir:
                del comps[i-1:i+1]
                i -= 1
            elif i == 0 and prefix.endswith(sep):
                del comps[i]
            else:
                i += 1
        else:
            i += 1
    # If the path is now empty, substitute '.'
    if not prefix and not comps:
        comps.append(curdir)
    return prefix + sep.join(comps)

def _abspath_fallback(path):
    """Return the absolute version of a path as a fallback function in case
    `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for
    more.
    """

    path = os.fspath(path)
    if not isabs(path):
        if isinstance(path, bytes):
            cwd = os.getcwdb()
        else:
            cwd = os.getcwd()
        path = join(cwd, path)
    return normpath(path)

__file__

現在実行中の.pyスクリプトのパス
Python3.9からは絶対パスで返るが、それ以前は状況によっては相対パスの場合もあるとかないとか。abspathで囲うのが妥当か。

Python now gets the absolute path of the script filename specified on the command line (ex: python3 script.py): the __file__ attribute of the __main__ module became an absolute path, rather than a relative path. These paths now remain valid after the current directory is changed by os.chdir(). As a side effect, the traceback also displays the absolute path for __main__ module frames in this case. (Contributed by Victor Stinner in bpo-20443.)

Python はコマンドラインで指定されたスクリプトファイル名 (ex: python3 script.py) の絶対パスを取得するようになりました:
__main__ モジュールの __file__ 属性は相対パスではなく、絶対パスになりました。これらのパスは os.chdir() によってカレントディレクトリが変更された後にも有効です。副次的な効果として、トレースバックはこの場合、__main__ モジュールフレームの絶対パスも表示します。(Contributed by Victor Stinner in bpo-20443.)

bpo-20443
https://bugs.python.org/issue20443
https://github.com/python/cpython/issues/81625

https://docs.python.org/ja/3.9/library/runpy.html#runpy.run_module




この記事が気に入ったらサポートをしてみませんか?