参考文献:
- 「ディープラーニング体験」
3.1 線形回帰
3.1.1 線形回帰の基本要素
サンプル: nnn表示样権,x ( i ) = [ x 1 ( i ) , x 2 ( i ) , ⋯ , xd ( i ) ] x^{(i)}=[x^{(i)}_1,x^ {(i)}_2,\cdots,x^{(i)}_d]バツ(私)=[ ×1(私)、バツ2(私)、⋯、バツd(私)] はiiを意味します。私はサンプルを作ります。
予測: y ^ = w T x + b \hat{y}=w^Tx+by^=wT ×+b は単一サンプルの予測値を表します。y ^ = X w + b \hat{y}=Xw+by^=Xw+b はすべてのサンプルの予測値を表します。
损失関数数:
L ( w , b ) = ∑ i = 1 n 1 2 ( y ^ ( i ) − y ( i ) ) L(w,b)=\sum\limits_{i=1}^{n}\ frac12\Big(\hat{y}^{(i)}-y^{(i)}\Big)L ( w ,b )=i = 1∑ん21(y^(私)−y( i ) )
確率的勾配降下法: 各反復では、まずミニバッチB \mathcal{B}をランダムにサンプリングします。B、固定数のトレーニング サンプルで構成されます。次に、パラメーターは次のように更新されます:
( w , b ) ← ( w , b ) − η ∣ B ∣ ∑ i ∈ B ∂ ( w , b ) l ( i ) ( w , b ) (\mathbf{w}, b ) \leftarrow (\mathbf{w},b) - \frac{\eta}{|\mathcal{B}|} \sum\limits_{i \in \mathcal{B}} \partial_{(\mathbf{ w },b)} l^{(i)}(\mathbf{w},b)( w 、b )←( w 、b )−∣ B ∣hi ∈ B∑∂( w , b )私( i ) (w、b)
其中, η \eta ηは学習率であり、ハイパーパラメータです。
3.1.2 ベクトル化の高速化
可能な限り効率的な線形代数ライブラリを使用してください。
3.1.3 正規分布と二乗損失
観測にノイズが多いと仮定しますϵ \epsilonϵ :
y = w ⊤ x + b + ϵ 、 y = \mathbf{w}^\top \mathbf{x} + b + \epsilon、y=w⊤ ×+b+ϵ ,
lessϵ∼ N ( 0 , σ 2 ) \epsilon \sim N(0, \sigma^2)ϵ〜N ( 0 ,p2 )。
次の方程式を求めます。
P ( y ∣ x ) = 1 2 π σ 2 exp ( − 1 2 σ 2 ( y − w ⊤ x − b ) 2 ) P(y \mid \mathbf{x}) = \frac{ 1}{\sqrt{2\pi\sigma^2}}\exp\left(-\frac{1}{2\sigma^2}(y - \mathbf{w}^\top \mathbf{ x} - b)^2\右)P (と∣× )=2本_21経験値( −2P _21( y−w⊤ ×−b )2 )
この場合、尤度関数は次のようになります:
L ( w , b ) = ∏ i = 1 np ( y ( i ) ∣ x ( i ) ) L(w,b) = \prod\limits_{i=1}^{n } p(y^{(i)}|\mathbf{x}^{(i)})L ( w ,b )=i = 1∏んp (と( i ) ∣x( i ) )
取对数再加负号,得:
− l ( w , b ) = ∑ i = 1 n ( 1 2 log ( 2 π σ 2 ) + 1 2 σ 2 ( y ( i ) − w ⊤ x ( i ) − b ) 2 ) 。-l(w,b) = \sum\limits_{i=1}^n \bigg(\frac{1}{2} \log(2 \pi \sigma^2) + \frac{1}{2 \ sigma^2} \left(y^{(i)} - \mathbf{w}^\top \mathbf{x}^{(i)} - b\right)^2\bigg)。− l ( w ,b )=i = 1∑ん(21log ( 2 π σ _2 )+2P _21( y(私)−w⊤ ×(私)−b )2) .例外π
、σ \pi、 \sigmap 、σは定数であるため、上式から、線形モデルの最小平均二乗誤差が最尤推定と同等であることがわかります。
3.1.4 線形回帰からディープネットワークへ
3.2 線形回帰を最初から実装する
3.2.1 データセットの生成
1000 個のサンプルを含むデータセットを生成したいとします。各サンプルには、標準正規分布からサンプリングされた2 つの特徴が含まれており、サンプルのラベルは次のとおりです。
y = X w + b + ϵ \mathbf{y}= \mathbf {X} \mathbf{w} + b + \mathbf\epsilony=Xw+b+ϵ
ここで、w = [ 2 , − 3.4 ] ⊤ \mathbf{w} = [2, -3.4]^\topw=[ 2 、− 3.4 ]⊤、b = 4.2 b = 4.2b=4.2 ,ϵ \epsilonϵ は、平均 0、標準偏差 0.01 の正規分布に従います。
def synthetic_data(w, b, num_examples): #@save
"""生成y=Xw+b+噪声"""
X = torch.normal(0, 1, (num_examples, len(w)))
y = torch.matmul(X, w) + b
y += torch.normal(0, 0.01, y.shape)
# 如果没有y.reshape,那么y将只有一个维度
return X, y.reshape((-1, 1))
3.2.2 データセットの読み取り
確率的勾配降下法では、毎回サンプルからサンプルの一部をランダムに選択する必要があるため、data_iter
サンプル抽出を次のように定義できます。
def data_iter(batch_size, features, labels):
num_examples = len(features)
indices = list(range(num_examples))
# 这些样本是随机读取的,没有特定的顺序
random.shuffle(indices)
# 在一轮训练中要用到所有的样本
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)])
# 每次参数更新只用到一小部分样本
yield features[batch_indices], labels[batch_indices]
上記のコードはサンプルを抽出するプロセスを理解するためにのみ使用されており、実際の実装では組み込みの反復子を使用できます。
3.2.3 初期化パラメータ
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
3.2.4 モデルの定義
def linreg(X, w, b):
"""线性回归模型"""
return torch.matmul(X, w) + b
3.2.5 損失関数の定義
def squared_loss(y_hat, y):
"""均方损失"""
# 这里的y.reshape其实是没有必要的,因为labels在前面已经reshape过了
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
3.2.6 最適化アルゴリズムの定義
def sgd(params, lr, batch_size):
"""小批量随机梯度下降"""
# 表示下一个代码块不需要进行梯度计算
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
# 清空梯度
param.grad.zero_()
3.2.7 トレーニング
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
l.sum().backward()
sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f'epoch {
epoch + 1}, loss {
float(train_l.mean()):f}')
3.3 線形回帰の簡単な実装
3.3.1 データの生成
この部分は3.2.1と同じです。
3.3.2 データセットの読み取り
from torch.utils import data
data
API を直接使用してサンプル サンプリングを実行できます。
def load_array(data_arrays, batch_size, is_train=True):
"""构造一个PyTorch数据迭代器"""
# TensorDataset相当于把所有tensor打包,传入的tensor的第0维必须相同
# *的作用是“解压”参数列表
dataset = data.TensorDataset(*data_arrays)
return data.DataLoader(dataset, batch_size, shuffle=is_train)
batch_size = 10
data_iter = load_array((features, labels), batch_size)
# 访问数据
for input,label in data_iter:
print(input,label)
3.3.3 モデルの定義
# nn是神经网络的缩写
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
上記のコードでは、Sequential
複数のレイヤーを直列に接続できます。Linear
完全に接続されたレイヤーが実装され、そのパラメーターによって2,1
入力の形状と出力の形状が指定されます。
3.3.4 モデルパラメータの初期化
# net[0]表示选中网络中的第0层
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
3.3.5 損失関数の定義
# 返回所有样本损失的均值
loss = nn.MSELoss()
3.3.6 最適化アルゴリズムの定義
# SGD的输入为参数和超参数
trainer = torch.optim.SGD(net.parameters(), lr=0.03)
3.3.7 トレーニング
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad()
l.backward()
# 使用优化器对参数进行更新
trainer.step()
l = loss(net(features), labels)
print(f'epoch {
epoch + 1}, loss {
l:f}')
3.4 ソフトマックス回帰
3.4.1 分類の問題
一般に、さまざまなカテゴリはワンホット エンコーディングによって表されます。
3.4.2 ネットワークアーキテクチャ
各サンプルに 4 つの特徴と 3 つの可能なカテゴリがあると仮定すると、ソフトマックス回帰のネットワーク構造は次の図に示されます。
3.4.3 全結合層のパラメータオーバーヘッド
一般に、全結合層にはddがあります。d入力とqqq出力の場合、そのパラメータのオーバーヘッドはO ( dp ) O(dp)O ( d p )。
3.4.4 ソフトマックス演算
分類問題の場合、取得したいのは入力が各カテゴリに属する確率であるため、確率の基本公理を満たすように出力を処理する必要があります:
y ^ = Softmax ( o ) where y ^ j = exp ( oj ) ∑ k exp ( ok ) \hat{\mathbf{y}} = \mathrm{softmax}(\mathbf{o})\quad \text{where}\quad \hat{y}_j = \ frac{\ exp(o_j)}{\sum\limits_k \exp(o_k)}y^=ソフトマックス( o )のy^j=k∑exp ( ok)exp ( oj)
形式、y ^ \hat{\mathbf{y}}y^の各成分は正の定数であり、合計は1 11、およびソフトマックスは変更されませんo \mathbf{o}oと の間の大きさのオーダー
3.4.5 ミニバッチサンプルのベクトル化
KaTeX 解析エラー: 'EOF' が予期されましたが、位置 13 で '&' を取得しました: \mathbf{O} &̲= \mathbf{X}\m。
3.4.6 損失関数
ソフトマックス回帰の尤度関数は次のとおりです。
L ( θ ) = ∏ i = 1 n P ( y ( i ) ∣ x ( i ) ) L(\theta)=\prod\limits_{i=1}^n P(\ mathbf{y}^{(i)} \mid \mathbf{x}^{(i)})L ( i )=i = 1∏んP (と(私)∣バツ( i ) )
取数、得:
− log L ( θ ) = ∑ i = 1 n − log P ( y ( i ) ∣ x ( i ) ) = ∑ i = 1 n ∑ j = 1 q − yj log y ^ j \begin{align} -\log L(\theta)&=\sum\limits_{i=1}^n -\log P(\mathbf{y}^{(i)} \ Mid \mathbf{x}^{(i)})\notag\\ &=\sum\limits_{i=1}^n\sum\limits_{j=1}^q-y_j\log \hat{y} _j \end{整列}−ログ_L ( i )=i = 1∑ん−ログ_P (と(私)∣バツ( i ) )=i = 1∑んj = 1∑q− yjログ_y^j
上の式を次のように説明します。サンプルのラベルはqqの長さであるため、qのワンホット エンコーディングであるため、内部の合計は実際には入力からラベルを導出する確率の負の対数であり、これは− log P ( y ( i ) ∣ x ( i ) ) -\と同じです。log P (\mathbf{y}^{(i)} \mid \mathbf{x}^{(i)})−ログ_P (と(私)∣バツ( i ) ) は同等です。
略称:
l ( y , y ^ ) = ∑ j = 1 q − yj log y ^ jl(\mathbf{y}, \hat{\mathbf{y}})=\sum\limits_{j=1}^ q-y_j\log \hat{y}_jl ( y ,y^)=j = 1∑q− yjログ_y^j
1 つはクロスエントロピー損失(クロスエントロピー損失)
l ( y , y ^ ) = − ∑ j = 1 qyj log exp ( oj ) ∑ k = 1 q exp ( ok ) = ∑ j = 1 qyj log ∑ k = 1 q exp (ok) − ∑ j = 1 qyjoj = log ∑ k = 1 q exp (ok) − ∑ j = 1 qyjoj ∂ ojl ( y , y ^ ) = exp ( oj ) ∑ k = 1 q exp ( ok ) − yj = Softmax ( o ) j − yj \begin{aligned} l(\mathbf{y}, \hat{\mathbf{y}}) &= - \sum_{ j= 1}^q y_j \log \frac{\exp(o_j)}{\sum_{k=1}^q \exp(o_k)}\notag \\ &= \sum_{j=1}^q y_j \log \sum_{k=1}^q \exp(o_k) - \sum_{j=1}^q y_j o_j\note\\ &= \log \sum_{k=1}^q \exp(o_k) - \ sum_{j=1}^q y_j o_j\note\\ \partial_{o_j} l(\mathbf{y}, \hat{\mathbf{y}}) &= \frac{\exp(o_j)} {\ sum_{k=1}^q \exp(o_k)} - y_j = \mathrm{softmax}(\mathbf{o})_j - y_j\notag \end{aligned}l ( y ,y^)∂ああjl ( y ,y^)=−j = 1∑qyjログ_∑k = 1qexp ( ok)exp ( oj)=j = 1∑qyjログ_k = 1∑qexp ( ok)−j = 1∑qyjああj=ログ_k = 1∑qexp ( ok)−j = 1∑qyjああj=∑k = 1qexp ( ok)exp ( oj)−yj=ソフトマックス( o )j−yj
勾配が観測値yyであることがわかります。yと推定値y ^ \hat{y}y^これにより、実際の勾配の計算が非常に簡単になります。
3.5 画像分類データセット
3.5.2 データの小さなバッチの読み取り
batch_size = 256
def get_dataloader_workers():
"""使用4个进程来读取数据"""
return 4
train_iter = data.DataLoader(mnist_train, batch_size, shuffle=True,
num_workers=get_dataloader_workers())
3.6 ソフトマックス回帰を最初から実装する
3.6.1 モデルパラメータの初期化
入力は 28*28 の画像で、長さ 784 のベクトルと見なすことができます。出力は 10 の可能なカテゴリに属する確率であるため、WWW は784*10 行列、bbbは 1*10 の行ベクトルです。
num_inputs = 784
num_outputs = 10
W = torch.normal(0, 0.01, size=(num_inputs, num_outputs), requires_grad=True)
b = torch.zeros(num_outputs, requires_grad=True)
3.6.2 ソフトマックス演算の定義
ソフトマックスの実装は3 つのステップで構成されます。
- 各項を累乗します。
- 各行 (各サンプルはミニバッチ内の行です) を合計して、各サンプルの正規化定数を取得します。
- 各行を正規化定数で除算し、結果の合計が 1 になるようにします。
対応するコードは次のとおりです。
def softmax(X):
X_exp = torch.exp(X)
# 确保求和之后张量的维度不变
partition = X_exp.sum(1, keepdim=True)
return X_exp / partition # 这里应用了广播机制
3.6.3 モデルの定義
def net(X):
return softmax(torch.matmul(X.reshape((-1, W.shape[0])), W) + b)
ここでの入力が画像だけなのはなぜですか?
3.6.4 損失関数の定義
def cross_entropy(y_hat, y):
return - torch.log(y_hat[range(len(y_hat)), y])
cross_entropy(y_hat, y)
その中には、y
サンプルのカテゴリ番号を表すラベル リスト ( など) があります[0,1,3]
。
3.6.5 分類精度
精度 = 正しい予測の数 / 予測の総数
def accuracy(y_hat, y):
"""计算预测正确的数量"""
if len(y_hat.shape) > 1 and y_hat.shape[1] > 1:
y_hat = y_hat.argmax(axis=1)
cmp = y_hat.type(y.dtype) == y
return float(cmp.type(y.dtype).sum())
上記のコードは次のように述べています。 がy_hat
行列の場合、2 番目の次元には各クラスの予測スコアが格納されると想定されます。を使用してargmax
各行の最大要素のインデックスを取得し、予測されたクラスを取得します。次に、y
予測されたカテゴリをグラウンド トゥルース要素と比較します。等価演算子 " ==
" はデータ型に依存するため、 のデータ型と一致するようy_hat
に のデータ型を変換します。y
結果は、0 (偽) と 1 (真) を含むテンソルになります。最後に、合計して正しい予測の数を取得します。
3.7 ソフトマックス回帰の簡単な実装
3.7.1 モデルパラメータの初期化
# PyTorch不会隐式地调整输入的形状。因此,
# 我们在线性层前定义了展平层(flatten),来调整网络输入的形状
net = nn.Sequential(nn.Flatten(), nn.Linear(784, 10))
def init_weights(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, std=0.01)
# apply会对net里的每一层执行init_weights函数
# 所以init_weights函数里的m是用来限定只初始化Linear层参数的
net.apply(init_weights);
3.7.2 損失関数の定義
CorssEntropyLoss
入力は\mathbf{o}ですo (ソフトマックスなし) とラベルのリストの場合、出力はクロス エントロピーです。言い換えれば、ソフトマックスの指数演算は非常にオーバーフローしやすいため、損失を計算するときにソフトマックスを通じて出力を確率に変換する必要はありません。
# none表示不合并结果,即loss为一个列表,元素为每个样本的交叉熵
# 这里之所以选择none,是因为后面既要用到损失的总和,又要用到损失的均值
loss = nn.CrossEntropyLoss(reduction='none')
3.7.3 最適化アルゴリズム
trainer = torch.optim.SGD(net.parameters(), lr=0.1)
3.7.4 トレーニング
# 累加器类
class Accumulator:
"""在n个变量上累加"""
def __init__(self, n):
self.data = [0.0] * n
# 将参数列表逐个加到累加器里
def add(self, *args):
self.data = [a + float(b) for a, b in zip(self.data, args)]
def reset(self):
self.data = [0.0] * len(self.data)
def __getitem__(self, idx):
return self.data[idx]
def train_epoch_ch3(net, train_iter, loss, updater):
"""训练模型一个迭代周期(定义见第3章)"""
# 将模型设置为训练模式
if isinstance(net, torch.nn.Module):
net.train()
# 训练损失总和、训练准确度总和、样本数
metric = Accumulator(3)
for X, y in train_iter:
# 计算梯度并更新参数
y_hat = net(X)
l = loss(y_hat, y)
if isinstance(updater, torch.optim.Optimizer):
# 使用PyTorch内置的优化器和损失函数
updater.zero_grad()
l.mean().backward()
updater.step()
metric.add(float(l.sum()), accuracy(y_hat, y), y.numel())
# 返回训练损失和训练精度
return metric[0] / metric[2], metric[1] / metric[2]
def evaluate_accuracy(net, data_iter): #@save
"""计算在指定数据集上模型的精度"""
if isinstance(net, torch.nn.Module):
net.eval() # 将模型设置为评估模式
metric = Accumulator(2) # 正确预测数、预测总数
with torch.no_grad():
for X, y in data_iter:
# 这里的accuracy出自3.6.5
metric.add(accuracy(net(X), y), y.numel())
return metric[0] / metric[1]
def train_ch3(net, train_iter, test_iter, loss, num_epochs, updater):
"""训练模型(定义见第3章)"""
animator = Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0.3, 0.9],
legend=['train loss', 'train acc', 'test acc'])
for epoch in range(num_epochs):
# 训练一轮
train_metrics = train_epoch_ch3(net, train_iter, loss, updater)
# 在测试集上测试精度
test_acc = evaluate_accuracy(net, test_iter)
animator.add(epoch + 1, train_metrics + (test_acc,))
train_loss, train_acc = train_metrics
# 这条代码的意思是:如果train_loss<0.5则继续执行,否则报错,报错内容为"train_loss"
assert train_loss < 0.5, train_loss
assert train_acc <= 1 and train_acc > 0.7, train_acc
assert test_acc <= 1 and test_acc > 0.7, test_acc
num_epochs = 10
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, trainer)
3.7.5 予測
を使用するだけですy_hat.argmax(axis=1)
。