Реализация нейросетевого мультиклассификатора своими руками

искусственный интеллект глубокое обучение Нейронные сети

Эта статья была написана ** Луо Чжоуянstupidme.me.lzy@gmail.com**Оригинал, просьба указать автора и источник для перепечатки. Несанкционированное коммерческое использование запрещено.

Реализуйте простую нейронную сеть для классификации без использования какой-либо среды глубокого обучения. Вам потребуется рука об руку построить нейронную сеть, включая выбор функции потерь, а код обратного распространения был написан от руки.

сгенерировать некоторые данные

Создайте некоторые данные, которые менее подвержены линейной классификации.

import numpy as np
import matplotlib.pyplot as plt

N = 100 # 每一个类别的生成的点的数量
D = 2 # 每个点的维度,这里使用平面,所以是2维数据
K = 3 # 类别数量,我们一共生成3个类别的点

# 所有的样本数据,一共300个点,每个点用2个维度表示
# 所有训练数据就是一个300*2的二维矩阵
X = np.zeros((N*K, D))
# 标签数据,一共是300个点,每个点对应一个类别,
# 所以标签是一个300*1的矩阵
y = np.zeros(N*K, dtype='uint8')

# 生成训练数据
for j in range(K):
    ix = range(N*j, N*(j+1))
    r = np.linspace(0.0, 1, N)
    t = np.linspace(j*4, (j+1)*4, N) + np.random.randn(N)*0.2
    X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]
    y[ix] = j
    
plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)
plt.show()

Обучите линейный классификатор Softmax

Обучите линейный классификатор, используя softmax и кросс-энтропийную потерю.

Фактически, он напрямую использует softmax для мультиклассификации, используя потерю перекрестной энтропии в качестве функции потерь и обучая модель линейной классификации.

import numpy as np
import matplotlib.pyplot as plt

N = 100
D = 2
K = 3
X = np.zeros((N*K, D))
y = np.zeros(N*K, dtype='uint8')

for j in range(K):
    ix = range(N*j, N*(j+1))
    r = np.linspace(0.0, 1, N)
    t = np.linspace(j*4, (j+1)*4, N) + np.random.randn(N)*0.2
    X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]
    y[ix] = j
    
# plt.scatter(X[:, 0], X[:, 1], c=y, s=40, cmap=plt.cm.Spectral)
# plt.show()

# 初始化权重和偏置
W = 0.01 * np.random.randn(D, K)
b = np.zeros((1, K))

step_size = 1e-0
reg = 1e-3 # regularization strength


# 获取训练样本数量
num_examples = X.shape[0]

for i in range(200):

    # 计算分类得分
    scores = np.dot(X, W) + b

    # 计算 softmax得分
    exp_scores = np.exp(scores)
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)

    # 使用交叉熵损失
    correct_log_probs = -np.log(probs[range(num_examples), y])

    # 计算训练集的data loss,总的损失除以样本数量
    data_loss = np.sum(correct_log_probs) / num_examples
    # 计算正则项损失reg loss,使用L2正则
    # reg就是lambda
    reg_loss = 0.5 * reg * np.sum(W * W)
    # 计算总的损失函数
    loss = data_loss + reg_loss
    
    if i%10 == 0:
        print("iteration %4d loss: %f" % (i, loss))

    # 计算梯度,反向传播
    # 为什么 dscores = probs ??
    dscores = probs
    dscores[range(num_examples), y] -= 1
    dscores /= num_examples

    dW = np.dot(X.T, dscores)
    db = np.sum(dscores, axis=0, keepdims=True)
    dW += reg * W # 正则项的梯度,dW不是第一次出现,必须累加

    # 更新参数
    W += -step_size * dW
    b += -step_size * db

    
# 训练结束,估算准确率
scores = np.dot(X, W) + b
# 在第二个维度(类别维度)取出概率最高的分类
predicted_class = np.argmax(scores, axis=1)
print("Training accuracy: %.2f" % (np.mean(predicted_class == y)))

iteration    0 loss: 1.097993
iteration   10 loss: 0.908688
iteration   20 loss: 0.838372
iteration   30 loss: 0.806482
iteration   40 loss: 0.789911
iteration   50 loss: 0.780488
iteration   60 loss: 0.774783
iteration   70 loss: 0.771169
iteration   80 loss: 0.768801
iteration   90 loss: 0.767208
iteration  100 loss: 0.766114
iteration  110 loss: 0.765351
iteration  120 loss: 0.764811
iteration  130 loss: 0.764425
iteration  140 loss: 0.764147
iteration  150 loss: 0.763945
iteration  160 loss: 0.763797
iteration  170 loss: 0.763688
iteration  180 loss: 0.763608
iteration  190 loss: 0.763548
Training accuracy: 0.51

В приведенном выше коде есть вопрос: почемуdscores = probs?

То, что получает функция Softmax, — это нормализованный вектор вероятности, который мы используемp_kпредставляет вероятность того, что класс равен k. Так:

p_k = \frac{e^{f_k}}{\sum_je^{f_j}}

Тогда наша кросс-энтропийная потеря равна:

L_i = -\log(p_{y_i})

Тогда есть:

\frac{\partial L_i }{ \partial f_k } = p_k - \mathbb{1}(y_i = k)

Эта формула показывает:Увеличение правильного классификационного балла может уменьшить потери!

Гипотетический вектор вероятности

p=[0.2, 0.3, 0.5]

А вторые 0,3 — это значение вероятности правильной классификации. Так как же выглядит наш градиент?

Согласно приведенной выше формуле, только при правильной классификации градиент становится исходной вероятностью классификации-1, других позициях градиент равен исходной вероятности классификации, то есть:

df = [0.2, -0.7, 0.5]

А вотf не наша функция оценок?

то есть

dscores = df = probs(y_i != k)

Тогда дляy_i=kгде мы-1Достаточно.

Итак, имеем следующую формулу:

# 一般情况下的df,也就是dscores
dscores = probs
# 预测分类正好是正确分类的情况,需要在该位置的梯度值减去1
dscores[range(num_examples), y] -= 1
# 平均
dscores /= num_examples

Короче говоря, это из-за природы самой функции softmax! ! !

Видно, что правильная скорость только0.51.

Это также ожидаемо, поскольку линейные характеристики данных не очевидны. Форсировать линейный классификатор, конечно, плохо получается.

В Stanford cs231n есть диаграмма, показывающая границу решения для этой модели:

обучить нейронную сеть

Приведенный выше линейный классификатор softmax работает плохо, давайте попробуем обучить нейронную сеть.

код показывает, как показано ниже:

import numpy as np

N = 100
D = 2
K = 3

X = np.zeros((N*K, D))
y = np.zeros(N*K, dtype='uint8')

for j in range(K):
    ix = range(N*j, N*(j+1))
    r = np.linspace(0.0, 1, N)
    t = np.linspace(j*4, (j+1)*4, N) + np.random.randn(N)*0.2
    X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]
    y[ix] = j

h = 100 # 隐藏层的神经元数量

# 第一个层的权重和偏置初始化
W1 = 0.01 * np.random.randn(D, h)
b1 = np.zeros((1, h))

# 第二层的权重和偏置初始化
W2 = 0.01 * np.random.randn(h, K)
b2 = np.zeros((1, K))

step_size = 1e-0
reg = 1e-3 # regularization strength

# 获取训练样本数量
num_examples = X.shape[0]

for i in range(10000):

    # 计算第一个隐藏层的输出,使用ReLU激活函数
    hidden_layer = np.maximum(0, np.dot(X, W1) + b1)
    # 计算输出层的结果,也就是最终的分类得分
    scores = np.dot(hidden_layer, W2) + b2
    
    # softmax
    exp_scores = np.exp(scores)
    probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True) # [N x K]
  
    # 计算损失,和之前的一样
    correct_logprobs = -np.log(probs[range(num_examples),y])
    data_loss = np.sum(correct_logprobs)/num_examples
    reg_loss = 0.5*reg*np.sum(W1*W1) + 0.5*reg*np.sum(W2*W2)
    loss = data_loss + reg_loss
    
    if i % 1000 == 0:
        print ("iteration %4d loss %f" % (i, loss))
  
    # 计算scores的梯度
    dscores = probs
    dscores[range(num_examples),y] -= 1
    dscores /= num_examples
  
    # 计算梯度,反向传播
    dW2 = np.dot(hidden_layer.T, dscores)
    db2 = np.sum(dscores, axis=0, keepdims=True)
    
    # 反向传播隐藏层
    dhidden = np.dot(dscores, W2.T)
    # 反向传播ReLu函数
    dhidden[hidden_layer <= 0] = 0
    
    dW1 = np.dot(X.T, dhidden)
    db1 = np.sum(dhidden, axis=0, keepdims=True)
    
    # 加上正则项
    dW2 += reg * W2
    dW1 += reg * W1
    
    # 更新参数
    W1 += -step_size * dW1
    b1 += -step_size * db1
    W2 += -step_size * dW2
    b2 += -step_size * db2

# 训练结束,估算正确率
hidden_layer = np.maximum(0, np.dot(X, W1) + b1)
scores = np.dot(hidden_layer, W2) + b2
predicted_class = np.argmax(scores, axis=1)
print("Training accuracy: %.2f" % (np.mean(predicted_class == y)))

iteration    0 loss 1.109818
iteration 1000 loss 0.277248
iteration 2000 loss 0.202578
iteration 3000 loss 0.192406
iteration 4000 loss 0.189857
iteration 5000 loss 0.189404
iteration 6000 loss 0.189292
iteration 7000 loss 0.189199
iteration 8000 loss 0.189143
iteration 9000 loss 0.189097
Training accuracy: 0.99

Видно, что точность увеличилась до0.99.

В Stanford cs231n также есть диаграмма, показывающая границу решения этой нейронной сети:

свяжитесь со мной

  • Email: stupidme.me.lzy@gmail.com
  • WeChat: luozhouyang0528
  • Личный публичный номер, возможно вас заинтересует