1. 線形ニューラルネットワーク
線形ニューラル ネットワークは構造的にパーセプトロンに非常に似ていますが、主な違いは活性化関数です。パーセプトロンの活性化関数は 2 つの可能な値、つまり -1 と 1 のみを出力できますが、線形ニューラル ネットワークの出力は任意の値を取ることができ、その活性化関数は次の図のピュアリン関数などの線形関数です。 。通常、線形ニューラル ネットワークは LMS (最小二乗平均) アルゴリズムを使用してネットワークの重みとバイアスを調整します。
LMS アルゴリズムは、Widrow と Hoff が適応線形要素のスキーム パターン認識を研究した際に提案したもので、最小平均二乗アルゴリズムとしても知られています。LMS アルゴリズムはウィーナー フィルターに基づいており、再帰的に最適解に近づく最急降下法の助けを借りて開発されました。したがって、LMS アルゴリズムには、計算の複雑さが低く、限られた精度を使用してアルゴリズムの安定性を達成できるという利点があり、LMS アルゴリズムは適応アルゴリズムの中で最も安定し、最も広く使用されているアルゴリズムとなっています。LMS アルゴリズムの具体的なプロセスは次のとおりです: (1) グローバル ステップ サイズと層数 (次数) を含むパラメータを決定します; (2) 初期値を初期化します; (3) 結果出力、エラー信号を含む演算そして体重更新。
線形ニューラル ネットワークの最も一般的な機能は、線形回帰問題と線形分類問題を解決することであり、これら 2 種類の問題は機械学習における教師あり学習に属します。教師あり学習は、仮説を使用して入力と特定の出力の間のマッピング関係 (通常は回帰問題と分類問題) を近似する正解に基づいています。
以下は、線形回帰および線形分類の問題の詳細な説明です。
2. 線形回帰
回帰は統計における最も強力なツールの 1 つであり、連続分布データの予測に適しています。入力値が与えられると、特定の値を予測できます。回帰の目的は、目標値を予測するための回帰式を立てることであり、回帰式の回帰係数を所定の方法で解くことにより、出力値と入力値との写像関係を得ることができる。
マッピング関係に基づく関数形式は、線形回帰、二次回帰、非線形回帰などに分類でき、入力値の次元に応じて、単回帰、二項回帰、さらには重回帰に分類できます。したがって、線形回帰は、点集合Dを与え、一次関数を使用して点集合を近似し、点集合と近似関数の間の誤差を最小化することであり、得られた一次関数は線形回帰式です。
2.1 線形回帰モデル
線形回帰では、まず次の仮定を行う必要があります: (1) 独立変数xと従属変数yの関係は線形である、つまり、yはx内の要素の加重和として表現できます、(2) 特定のランダムな外乱項目(ノイズ)が存在する場合、正規分布に従うなどのランダム性を持たせる必要があります。
研究者らは現実に特化して、車のブランド効果、車の性能、車の年式に基づいて中古車の市場価格を予測したいと考えています。中古車の市場価格を予測するモデルを開発するには、研究者はブランド価値、性能、車両の年式、中古車の市場価格などの実際のデータセットを収集する必要があります。機械学習の用語では、データセットを学習データセット、各データをサンプルまたはデータポイント、予測対象(中古車の市場価格)をラベル、独立変数(予測の基礎となるブランド価値、性能、車両の年式などを特徴または共変量と呼びます。データセット内のサンプル数を表すためにnを使用できます。インデックスiを持つサンプルの場合、入力は次のように表現できます。
対応するラベルは です。
上記の仮定に基づいて、この関係は次のように想像できます。
ベクトルの観点からは、次のように表現できます。
ベクトルx は単一のデータ サンプルの特徴に対応し、X はデータ セット全体のn個のサンプルを表します。ここで、 Xの各行はサンプル、各列は特徴です。
特徴セットXの場合、予測値は次のように表すことができます。
トレーニング データの特徴Xと対応する既知のラベルyが与えられた場合、線形回帰の目標は、Xと同じ分布からサンプリングされた新しいサンプル特徴が与えられた場合に、そのバイアスによって次のような重みベクトルwとバイアスbのセットを見つけることです。新しいサンプルのラベルを予測する際の誤差を可能な限り小さくします。
最適なモデル パラメーターwおよびbを見つけ始める前に、モデルの品質を測定する方法とモデルの品質を改善する方法も必要です。
2.2 損失関数
損失関数は、ターゲットの実際の値と予測値の差を定量化できます。以下の図に示すように、通常は損失として非負の数値を選択し、値が小さいほど損失は小さくなり、完全な予測の損失は 0 になります。回帰問題で最もよく使用される損失関数は二乗誤差関数です。サンプルiの予測値が 、対応する実ラベルが の場合、二乗誤差は次の式として定義できます。
2.3 分析溶液
線形回帰の解は数式で簡単に表現でき、このような解を解析解と呼びます。まず、すべてのパラメータを含む行列に列を追加することで、バイアスbをパラメータwに組み込みます。私たちの目的は、次の式を最小限に抑えることです。
これには、損失平面上に臨界点が 1 つだけあり、領域全体の損失最小値に対応します。wに関する損失の導関数を0 に設定すると、次の解析解が得られます。
線形回帰のような単純な問題には分析的な解決策がありますが、すべての問題に分析的な解決策があるわけではありません。分析ソリューションは良好な数学的分析を実行できますが、分析ソリューションには問題に対して厳しい制限があるため、深層学習で広く使用することができません。
2.4 確率的勾配降下法
勾配降下法の使用法は、モデル パラメーターに関する損失関数の導関数 (勾配) を計算することです。パラメーターの各更新の前に、データ セット全体を走査する必要があります。通常、更新を計算する必要があるたびに、小さなバッチのサンプルがランダムに選択されます。このバリアントは、小バッチ確率的勾配降下法と呼ばれます。
各反復では、まず、固定数のトレーニング サンプルで構成されるミニバッチをランダムにサンプリングします。次に、モデル パラメーターに関するミニバッチ平均損失の導関数を計算します。最後に、勾配に所定の正の数ηを乗算し、現在のパラメーター値からそれを減算します。
2.5 Python で線形回帰を実装する
まず、線形モデル パラメーターb = 4.2 とノイズ項εを使用してデータセットを生成し、データセットとラベルの式を生成します。
問題を単純化するために、 ε が平均 0 の正規分布に従うと仮定し、標準偏差を 0.01 に設定します。
# 根据带有噪声的线性模型构造一个人造数据集。我们使用线性模型参数
# w , b 和噪声d生成数据集及其标签
def synthetic_data(w, b, num_examples):
X = torch.normal(0,1,(num_examples, len(w))) ###生成mean=0, std=1, size=(num_examples, len(w)) 的向量
y = torch.matmul(X,w) + b
y += torch.normal(0, 0.01, y.shape)
return X, y.reshape((-1,1)) ### 将Y 转换为列向量
true_w = torch.tensor([2, -3.4])
true_b = 4.2
features, labels = synthetic_data(true_w, true_b, 1000)
print('features:', features[0], '\nlabel:', labels[0])
plt.figure()
plt.scatter(features[:, 1].detach().numpy(),
labels.detach().numpy(),1)
plt.show()
データ生成関数を定義した後、Xとyを特徴とラベルとして定義できます。データ グラフは次の図に示されています。図の散布点から、データ分布は線形要件を満たしていることがわかります。
データを定義したら、バッチ サイズ、機能、ラベルなど、データを受け入れる関数を定義する必要があります。
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]
batch_size = 10
for X, y in data_iter(batch_size,features, labels):
print(X, '\n',y )
break
データをトレーニングする必要があるため、データをスクランブルする必要があることに注意してください、つまり、random.shuffle. ここでは、(10, 2) としてデータ形式を取得するために読み取ったデータを出力します。
tensor([[-0.9799, -0.5394],
[-0.1818, 0.4705],
[-1.0967, -2.5218],
[-1.4719, 0.4218],
[-0.7889, -1.4477],
[-0.2622, -0.1918],
[-1.1138, -0.8647],
[-0.5958, -0.3762],
[-1.6837, -2.3087],
[-1.5623, -0.2522]])
tensor([[ 4.0597],
[ 2.2267],
[10.5830],
[-0.1603],
[ 7.5412],
[ 4.3141],
[ 4.9181],
[ 4.2847],
[ 8.6720],
[ 1.9464]])
データの読み取り後、トレーニング フェーズに入ることができます。主な手順は次のとおりです: (1) w は勾配の計算に参加する必要があるため、重みw を初期化します。(2)バイアススカラーbを初期化します。(3) を定義します。モデル linreg; (4) 損失関数を定義; (5) 勾配最適化関数を定義します。
###定义初始化模型参数 w 与b
w = torch.normal (0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(size=(1,1), requires_grad=True) ## 因为偏差是个标量 所以size为1*1
def linreg(X, w, b):
'''线性回归模型'''
return torch.matmul(X, w) + b
def squared_loss(y_hat, y):
''''均方损失'''
return (y_hat-y.reshape(y_hat.shape))**2 / 2 ###向量大小可能不一样,所以统一reshape 成size(y_hat)
def sgd(params, lr, batch_size): ##
'''
1. params :给定所有参数
2. lr:学习率
3. batch_size: 输入的批次量大小
小批量随机梯度下降
'''
with torch.no_grad():
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_() ##torch不会自动将梯度重新设置为0,这里需手动设置
その後、トレーニングフェーズに入ることができ、学習率と反復回数を設定する必要があります。
lr = 0.03
num_epochs = 20
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)
l.sum().backward()
sgd([w,b], lr, batch_size)
with torch.no_grad():
train_1 = loss(net(features, w, b), labels)
print(f'epoch{epoch + 1}, loss{float(train_1.mean()):f}')
## 人工数据集 可以手动查看误差
print(f'w的估计误差:{true_w - w.reshape(true_w.shape)}')
print(f'b的估计误差:{true_b - b}') ## b 是个标量 不需要resize
3. 線形分類
3.1 分類
线性神经网络也可用于解决分类问题,对于任意一个输入,分类的任务就是其分配到K个类别之一的。属于类别的所有样本x构成的集合,称为类别的决策区域记为,决策区域之间的边界称为决策边界。
所谓线性分类模型是指决策边界为线性边界的分类模型。即,线性分类模型的决策边界具有如下的形式:
下图展示了二维输入空间的线性和非线性分类模型的情况。
简单来说,分类问题就是对样本进行类别的划分,在实际生活中经常会面临这类问题。例如,把猫和狗分成不同的类,具体到不同品种,猫和狗又各自会分为不同的类型,这就是一种分类问题。抽象成散点来说,可以将图中的散点按照坐标分为两类,两种类型大致分布在各自的区域中。若要解决这个问题,就要让训练好的网络具有两个输入节点,分别输入x坐标和y坐标;还需要两个输出节点,输出分类编码。通常采用的分类编码在计算机学科中被称为独热编码(One-hot),如上图(b)中的散点可分为两类,可以让输出10代表第一类,01代表第二类。推广到多分类上,可以让100代表第一类,010代表第二类,001代表第三类。
关于二分类问题的输出也可以不采用独热编码,可以只有一个输出,输出的范围为0~1,代表输入被认定为是第一类或第二类的概率。这种情况下,输出层的激活函数一般用Sigmoid函数。
3.2 用python实现分类
为了简单起见和普适性,此处不引用猫狗识别这样的实际问题,而是采用具有一定随机性的散点分布进行展示。
3.2.1 准备工作
首先导入需要的模块,如下:
import torch ## 导入torch模块
import torch.nn as nn ## 简化nn模块
import matplotlib.pyplot as plt ## 导入并简化matplotlib模块
我们希望构建大致在两种范围内的散点集,而这些散点还需要具有一定的随机性。此处引用torch中的normal()接口,该接口适用于产生符合均值为mean、标准差为std的正态分布随机数,其中mean和std不一定是一个值,也可以是一个数组,但两个数组的size必须相同。
torch.normal(mean,std)
在参数均为数组的情况下,最后生成的Tensor变量的size也与数组size相同。
x = torch.ones(5)
y = torch.normal(x, 1)
print(y)
上述代码运行后会产生一维随机数组,随机值符合均值为1,标准差为1的正态分布,运行结果如下:
tensor([1.5439, 0.8622, 1.4256, 0.0724, 1.1194])
如果把x改成二维数组,如:
x = torch.ones(2, 2)
运行结果如下:
tensor([[ 0.4945, 1.3048],
[-0.4960, 1.2658]])
我们希望构建两种不同的样本集,但两种样本集又有着自己的总体特征,可以让两个不同的散点集合符合不同的正态分布,例如:
data = torch.ones(100,2) ## 数据总数(总框架)
x0 = torch.normal(2*data, 1) ## 第一类坐标
x1 = torch.normal(-2*data, 1) ## 第二类坐标
## 画图
for item in x0:
plt.scatter(item[0],item[1])
for item in x1:
plt.scatter(item[0],item[1])
plt.show()
x0和x1均继承了data的尺寸,是一个二维数组,尺寸为100*2。因此,x0和x1均可代表一个点集,因为正态分布参数不同,所以两个点集是不同的点集,绘制的散点图如下图所示。
在训练网络时候,我们希望训练集是一个整体,所以将x0和x1合并成一个样本集。合并Tensor张量的接口为torch.cat(),例如:
import torch
x0 = torch.ones(2, 2) ## 创建二维Tensor张量
x1 = torch.zeros(2, 2)
print(x0)
print(x1)
x = torch.cat((x0, x1), 0) ## 按列合并
print(x)
x = torch.cat((x0, x1), 1) ## 按行合并
print(x)
x = torch.cat((x0, x1)) ## 默认按列合并
print(x)
输出结果如下:
tensor([[1., 1.],
[1., 1.]])
tensor([[0., 0.],
[0., 0.]])
tensor([[1., 1.],
[1., 1.],
[0., 0.],
[0., 0.]])
tensor([[1., 1., 0., 0.],
[1., 1., 0., 0.]])
tensor([[1., 1.],
[1., 1.],
[0., 0.],
[0., 0.]])
将样本按列合并之后,第一列代表所有的x坐标,第二列代表所有的y坐标。合并完成之后,为防止数据类型错误,将其转换成Float类型的Tensor变量,代码如下:
x = torch.cat((x0, x1)).type(torch.FloatTensor)
之后,给散点集打上标签,告诉计算机哪些是第一类点,哪些是第二类点,可以将第一类标记为0,第二类标记为1,用y来储存这些标签。
y0 = torch.zeros(100) ## 第一类标签储存为0
y1 = torch.ones(100) ## 第二类标签储存为1
y = torch.cat((y0, y1)).type(torch.LongTensor)
3.2.2 构建网络
根据分类问题的分析,网络需要有两个输入和输出,采用隐藏节点为15的隐含层,选择ReLU函数作为隐含层激活函数,Softmax函数作为输出层激活函数。
构建网络的方法和回归问题类似,代码如下:
class Net(nn.Module): ## 定义类,储存网络结构
def __init__(self):
super(Net, self).__init__() ## nn模块搭建网络
self.classify = nn.Sequential( ## nn模块搭建网络
nn.Linear(2, 15), ## 全连接层,2个输入,15个输出
nn.ReLU(), ## ReLU激活函数
nn.Linear(15, 2), ## 全连接层,15个输入,2个输出
nn.Softmax(dim=1)
)
def forward(self, x): ## 定义前向传播过程
classification = self.classify(x) ## 将x传入网络
return classification ## 返回预测值
3.2.3 训练网络
构建网络后,需要对网络进行训练,训练的设置和前面设置的几乎一致,采用SGD算法进行优化。通常分类问题采用交叉熵函数作为损失函数,接口为CrossEntropyLoss(),代码如下:
net = Net()
optimizer = torch.optim.SGD(net.parameters(), lr=0.03) ## 设置优化器
loss_func = nn.CrossEntropyLoss() ## 设置损失函数
for epoch in range(100): # 训练部分
out = net(x) ## 实际输出
loss = loss_func(out, y) ## 实际输出和期望输出传入损失函数
optimizer.zero_grad() ## 清除梯度
loss.backward() ## 误差反向传播
optimizer.step() ## 优化器开始优化
3.2.4 完整程序(附可视化过程)
使用神经网络解决分类问题的完整程序代码如下:
import torch ## 导入torch模块
import torch.nn as nn ## 简化nn模块
import matplotlib.pyplot as plt ## 导入并简化matplotlib模块
data = torch.ones(100, 2) ## 数据总数(总框架)
x0 = torch.normal(2 * data, 1) ## 第一类坐标
x1 = torch.normal(-2 * data, 1) ## 第二类坐标
y0 = torch.zeros(100) ## 第一类标签储存为0
y1 = torch.ones(100) ## 第二类标签储存为1
x = torch.cat((x0, x1)).type(torch.FloatTensor)
y = torch.cat((y0, y1)).type(torch.LongTensor)
class Net(nn.Module): ## 定义类,储存网络结构
def __init__(self):
super(Net, self).__init__() ## nn模块搭建网络
self.classify = nn.Sequential( ## nn模块搭建网络
nn.Linear(2, 15), ## 全连接层,2个输入,15个输出
nn.ReLU(), ## ReLU激活函数
nn.Linear(15, 2), ## 全连接层,15个输入,2个输出
nn.Softmax(dim=1)
)
def forward(self, x): ## 定义前向传播过程
classification = self.classify(x) ## 将x传入网络
return classification ## 返回预测值
net = Net()
optimizer = torch.optim.SGD(net.parameters(), lr=0.03) ## 设置优化器
loss_func = nn.CrossEntropyLoss() ## 设置损失函数
plt.ion ## 打开交互模式
for epoch in range(100): # 训练部分
out = net(x) ## 实际输出
loss = loss_func(out, y) ## 实际输出和期望输出传入损失函数
optimizer.zero_grad() ## 清除梯度
loss.backward() ## 误差反向传播
optimizer.step() ## 优化器开始优化
if epoch % 2 == 0: ## 每2poch显示
plt.cla() ## 清除上一次绘图
classification = torch.max(out, 1)[1] ## 返回每一行中最大值的下标
class_y = classification.data.numpy() ## 转换成numpy数组
target_y = y.data.numpy() ## 标签页转换成numpy数组
plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=class_y,
s=100, cmap='RdYlGn') ## 绘制散点图
accuracy = sum(class_y == target_y) / 200 ## 计算准确率
plt.text(1.5, -4, f'Accuracy={accuracy}',
fontdict={'size': 20, 'color': 'red'}) ## 显示准确率
plt.pause(0.4) ## 时间0.4s
plt.show()
plt.ioff() ## 关闭交互模式
plt.show()
输出结果大致变化过程如下图所示:
当准确率逼近1时,可以看到分类已经完成,且效果显著,说明训练的模型有效。