修改神经网络对单个示例进行分类

Posted

技术标签:

【中文标题】修改神经网络对单个示例进行分类【英文标题】:Modify neural net to classify single example 【发布时间】:2018-04-28 17:22:11 【问题描述】:

这是我在深度学习课程中对 Andrew NG 的神经网络之一的自定义扩展,我正在尝试为二进制分类而不是生成 0 或 1 对多个示例进行分类。

输入和输出都是热编码的。

没有太多的训练,我得到了'train accuracy: 67.51658067499625 %'的准确率

如何分类单个训练示例而不是分类所有训练示例?

我认为我的实现中存在一个错误,因为该网络的问题是训练示例 (train_set_x) 和输出值 (train_set_y) 都需要具有相同的维度或收到与矩阵维度相关的错误。 例如使用:

train_set_x = np.array([
    [1,1,1,1],[0,1,1,1],[0,0,1,1]
])

train_set_y = np.array([
    [1,1,1],[1,1,0],[1,1,1]
])

返回错误:

ValueError                                Traceback (most recent call last)
<ipython-input-11-0d356e8d66f3> in <module>()
     27 print(A)
     28 
---> 29 np.multiply(train_set_y,A)
     30 
     31 def initialize_with_zeros(numberOfTrainingExamples):

ValueError: 操作数不能与形状一起广播 (3,3) (1,4)

网络代码:

import numpy as np
import matplotlib.pyplot as plt
import h5py
import scipy
from scipy import ndimage
import pandas as pd
%matplotlib inline

train_set_x = np.array([
    [1,1,1,1],[0,1,1,1],[0,0,1,1]
])

train_set_y = np.array([
    [1,1,1,0],[1,1,0,0],[1,1,1,1]
])

numberOfFeatures = 4
numberOfTrainingExamples = 3

def sigmoid(z):
    s = 1 / (1 + np.exp(-z))  
    return s

w = np.zeros((numberOfTrainingExamples , 1))
b = 0
A = sigmoid(np.dot(w.T , train_set_x))    
print(A)

np.multiply(train_set_y,A)

def initialize_with_zeros(numberOfTrainingExamples):
    w = np.zeros((numberOfTrainingExamples , 1))
    b = 0
    return w, b

def propagate(w, b, X, Y):

    m = X.shape[1]

    A = sigmoid(np.dot(w.T , X) + b)    

    cost = -(1/m)*np.sum(np.multiply(Y,np.log(A)) + np.multiply((1-Y),np.log(1-A)), axis=1)

    dw =  ( 1 / m ) *   np.dot( X, ( A - Y ).T )    # consumes ( A - Y )
    db =  ( 1 / m ) *   np.sum( A - Y )    # consumes ( A - Y ) again

#     cost = np.squeeze(cost)

    grads = "dw": dw,
             "db": db

    return grads, cost

def optimize(w, b, X, Y, num_iterations, learning_rate, print_cost = True):

    costs = []

    for i in range(num_iterations):

        grads, cost = propagate(w, b, X, Y)

        dw = grads["dw"]
        db = grads["db"]

        w = w - (learning_rate * dw)
        b = b - (learning_rate * db)

        if i % 100 == 0:
            costs.append(cost)

        if print_cost and i % 10000 == 0:
            print(cost)

    params = "w": w,
              "b": b

    grads = "dw": dw,
             "db": db

    return params, grads, costs

def model(X_train, Y_train, num_iterations, learning_rate = 0.5, print_cost = False):

    w, b = initialize_with_zeros(numberOfTrainingExamples)

    parameters, grads, costs = optimize(w, b, X_train, Y_train, num_iterations, learning_rate, print_cost = True)
    w = parameters["w"]
    b = parameters["b"]
    Y_prediction_train = sigmoid(np.dot(w.T , X_train) + b) 

    print("train accuracy:  %".format(100 - np.mean(np.abs(Y_prediction_train - Y_train)) * 100))

model(train_set_x, train_set_y, num_iterations = 20000, learning_rate = 0.0001, print_cost = True)

更新:此实现中存在一个错误,即训练示例对 (train_set_x , train_set_y) 必须包含相同的维度。可以指出线性代数应该如何修改的方向吗?

更新 2:

我修改了@Paul Panzer 的答案,使学习率为 0.001 并且 train_set_x 和 train_set_y 对是唯一的:

train_set_x = np.array([
    [1,1,1,1,1],[0,1,1,1,1],[0,0,1,1,0],[0,0,1,0,1]
])

train_set_y = np.array([
    [1,0,0],[0,0,1],[0,1,0],[1,0,1]
])

grads = model(train_set_x, train_set_y, num_iterations = 20000, learning_rate = 0.001, print_cost = True)

# To classify single training example : 

print(sigmoid(dw @ [0,0,1,1,0] + db))

此更新产生以下输出:

-2.09657359028
-3.94918577439
[[ 0.74043089  0.32851512  0.14776077  0.77970162]
 [ 0.04810012  0.08033521  0.72846174  0.1063849 ]
 [ 0.25956911  0.67148488  0.22029838  0.85223923]]
[[1 0 0 1]
 [0 0 1 0]
 [0 1 0 1]]
train accuracy: 79.84462279013312 %
[[ 0.51309252  0.48853845  0.50945862]
 [ 0.5110232   0.48646923  0.50738869]
 [ 0.51354109  0.48898712  0.50990734]]

print(sigmoid(dw @ [0,0,1,1,0] + db)) 是否应该生成一个经过四舍五入后匹配 train_set_y 对应值的向量:[0,1,0]

修改以产生一个向量(将[0,0,1,1,0]添加到numpy数组并进行转置):

print(sigmoid(dw @ np.array([[0,0,1,1,0]]).T + db))

返回:

array([[ 0.51309252],
       [ 0.48646923],
       [ 0.50990734]])

同样,当需要[0,1,0] 时,将这些值四舍五入到最接近的整数会生成向量[1,0,1]

这些是为单个训练示例生成预测的错误操作?

【问题讨论】:

【参考方案1】:

你的困难来自不匹配的维度,所以让我们来解决这个问题,试着把它们弄清楚。

您的网络有许多输入和功能,我们将它们的编号称为N_in(在您的代码中为numberOfFeatures)。它有许多对应于不同类的输出,我们称它们为N_out。输入和输出通过权重w连接。

现在问题来了。连接是全对全的,因此我们需要为N_out x N_in 的每一对输出和输入设置一个权重。因此,在您的代码中,w 的形状必须更改为(N_out, N_in)。您可能还希望每个输出都有一个偏移量 b,因此 b 应该是大小为 (N_out,) 或更确切地说是 (N_out, 1) 的向量,因此它与 2d 项配合得很好。

我已经在下面的修改后的代码中修复了这个问题,并试图让它非常明确。我还把一个模拟数据创建者扔进了讨价还价的地方。

关于 one-hot 编码的分类输出,我不是神经网络方面的专家,但我认为,大多数人都理解它,因此类是互斥的,所以你的模拟输出中的每个样本都应该有一个,其余的零。

旁注:

在某一时刻,一个相互竞争的答案建议您摆脱成本函数中的 1-... 术语。虽然这对我来说似乎是一个有趣的想法,但我的直觉(编辑现在确认使用无梯度最小化器;在下面的代码中使用activation="hybrid"。求解器将简单地最大化所有 在至少一个训练示例中处于活动状态的输出。)它不会像那样工作,因为成本将无法惩罚误报(详细解释见下文)。为了使其工作,您必须添加某种正则化。一种似乎有效的方法是使用softmax 而不是sigmoidsoftmaxsigmoid 是二进制一样,是单热的。它确保输出是“fuzzy one-hot”。

因此我的建议是:

如果您想坚持使用 sigmoid 并且不明确强制执行 one-hot 预测。保留1-... 术语。 如果您想使用较短的成本函数。执行一次性预测。例如,使用softmax 而不是sigmoid

我已将activation="sigmoid"|"softmax"|"hybrid" 参数添加到在模型之间切换的代码中。我还提供了 scipy 通用最小化器,当成本梯度不可用时,它可能很有用。

回顾成本函数的工作原理:

成本是术语的所有类和所有训练样本的总和

-y log (y') - (1-y) log (1-y')

其中 y 是预期响应,即输入的“y”训练样本(“x”训练样本)给出的响应。 y' 是预测,即具有当前权重和偏差的网络生成的响应。现在,由于预期响应为 0 或 1,因此可以编写单个类别和单个训练样本的成本

-log (y')   if   y = 1
-log(1-y')  if   y = 0

因为在第一种情况下 (1-y) 为零,所以第二项消失,在第二种情况下,y 为零,所以第一项消失。 如果

预期响应 y 为 1,网络预测 y' 接近于零 预期响应 y 为 0,网络预测 y' 接近 1

换句话说,成本在惩罚错误预测方面发挥了作用。现在,如果我们放弃第二个术语(1-y) log (1-y'),这个机制的一半就消失了。如果预期响应为 1,则低预测仍会产生成本,但如果预期响应为 0,则无论预测如何,尤其是高预测(或 假阳性 em>) 不会受到惩罚。

现在,因为总成本是所有训练样本的总和,所以存在三种可能性。

所有训练样本都规定类为零: 那么成本将完全独立于该类的预测,并且无法进行学习

一些训练样本将班级归零,一些归一: 然后因为“假阴性”或“未命中”仍然受到惩罚,但假阳性不是网络将找到最小化成本的最简单方法,即不加选择地增加对所有样本的类的预测

所有训练样本都规定类是一类: 与第二种情况基本相同,只是在这里没有问题,因为这是正确的行为

最后,为什么我们使用softmax 而不是sigmoid 会起作用?误报仍然是不可见的。现在很容易看出,softmax 的所有类的总和为 1。因此,如果至少减少另一个类别以进行补偿,我只能增加一个类别的预测。特别是,没有假阴性就不可能有假阳性,并且成本会检测到假阴性。

关于如何获得二元预测:

对于二进制预期响应,四舍五入确实是适当的过程。对于 one-hot,我宁愿找到最大值,将其设置为 1,将所有其他值设置为零。我添加了一个方便的函数 predict 来实现它。

import numpy as np
from scipy import optimize as opt
from collections import namedtuple

# First, a few structures to keep ourselves organized

Problem_Size = namedtuple('Problem_Size', 'Out In Samples')
Data = namedtuple('Data', 'Out In')
Network = namedtuple('Network', 'w b activation cost gradient most_likely')

def get_dims(Out, In, transpose=False):
    """extract dimensions and ensure everything is 2d
    return Data, Dims"""
    # gracefully acccept lists etc.
    Out, In = np.asanyarray(Out), np.asanyarray(In)
    if transpose:
        Out, In = Out.T, In.T
    # if it's a single sample make sure it's n x 1
    Out = Out[:, None] if len(Out.shape) == 1 else Out
    In = In[:, None] if len(In.shape) == 1 else In
    Dims = Problem_Size(Out.shape[0], *In.shape)
    if Dims.Samples != Out.shape[1]:
        raise ValueError("number of samples must be the same for Out and In")
    return Data(Out, In), Dims


def sigmoid(z):
    s = 1 / (1 + np.exp(-z))  
    return s

def sig_cost(Net, data):
    A = process(data.In, Net)
    logA = np.log(A)
    return -(data.Out * logA + (1-data.Out) * (1-logA)).sum(axis=0).mean()

def sig_grad (Net, Dims, data):
    A = process(data.In, Net)
    return dict(dw =  (A - data.Out) @ data.In.T / Dims.Samples,
                db =  (A - data.Out).mean(axis=1, keepdims=True))

def sig_ml(z):
    return np.round(z).astype(int)

def sof_ml(z):
    hot = np.argmax(z, axis=0)
    z = np.zeros(z.shape, dtype=int)
    z[hot, np.arange(len(hot))] = 1
    return z

def softmax(z):
    z = z - z.max(axis=0, keepdims=True)
    z = np.exp(z)
    return z / z.sum(axis=0, keepdims=True)

def sof_cost(Net, data):
    A = process(data.In, Net)
    logA = np.log(A)
    return -(data.Out * logA).sum(axis=0).mean()

sof_grad = sig_grad

def get_net(Dims, activation='softmax'):
    activation, cost, gradient, ml = 
        'sigmoid': (sigmoid, sig_cost, sig_grad, sig_ml),
        'softmax': (softmax, sof_cost, sof_grad, sof_ml),
        'hybrid': (sigmoid, sof_cost, None, sig_ml)[activation]
    return Network(w=np.zeros((Dims.Out, Dims.In)),
                   b=np.zeros((Dims.Out, 1)),
                   activation=activation, cost=cost, gradient=gradient,
                   most_likely=ml)

def process(In, Net):
    return Net.activation(Net.w @ In + Net.b)

def propagate(data, Dims, Net):
    return Net.gradient(Net, Dims, data), Net.cost(Net, data)

def optimize_no_grad(Net, Dims, data):
    def f(x):
        Net.w[...] = x[:Net.w.size].reshape(Net.w.shape)
        Net.b[...] = x[Net.w.size:].reshape(Net.b.shape)
        return Net.cost(Net, data)
    x = np.r_[Net.w.ravel(), Net.b.ravel()]
    res = opt.minimize(f, x, options=dict(maxiter=10000)).x
    Net.w[...] = res[:Net.w.size].reshape(Net.w.shape)
    Net.b[...] = res[Net.w.size:].reshape(Net.b.shape)

def optimize(Net, Dims, data, num_iterations, learning_rate, print_cost = True):

    w, b = Net.w, Net.b
    costs = []

    for i in range(num_iterations):

        grads, cost = propagate(data, Dims, Net)

        dw = grads["dw"]
        db = grads["db"]

        w -= learning_rate * dw
        b -= learning_rate * db

        if i % 100 == 0:
            costs.append(cost)

        if print_cost and i % 10000 == 0:
            print(cost)

    return grads, costs

def model(X_train, Y_train, num_iterations, learning_rate = 0.5, print_cost = False, activation='sigmoid'):

    data, Dims = get_dims(Y_train, X_train, transpose=True)
    Net = get_net(Dims, activation)

    if Net.gradient is None:
        optimize_no_grad(Net, Dims, data)
    else:
        grads, costs = optimize(Net, Dims, data, num_iterations, learning_rate, print_cost = True)

    Y_prediction_train = process(data.In, Net)

    print(Y_prediction_train)
    print(data.Out)
    print(Y_prediction_train.sum(axis=0))
    print("train accuracy:  %".format(100 - np.mean(np.abs(Y_prediction_train - data.Out)) * 100))
    return Net

def predict(In, Net, probability=False):
    In = np.asanyarray(In)
    is1d = In.ndim == 1
    if is1d:
        In = In.reshape(-1, 1)
    Out = process(In, Net)
    if not probability:
        Out = Net.most_likely(Out)
    if is1d:
        Out = Out.reshape(-1)
    return Out

def create_data(Dims):
    Out = np.zeros((Dims.Out, Dims.Samples), dtype=int)
    Out[np.random.randint(0, Dims.Out, (Dims.Samples,)), np.arange(Dims.Samples)] = 1
    In = np.random.randint(0, 2, (Dims.In, Dims.Samples))
    return Data(Out, In)

train_set_x = np.array([
    [1,1,1,1,1],[0,1,1,1,1],[0,0,1,1,0],[0,0,1,0,1]
])

train_set_y = np.array([
    [1,0,0],[1,0,0],[0,0,1],[0,0,1]
])

Net1 = model(train_set_x, train_set_y, num_iterations = 20000, learning_rate = 0.001, print_cost = True, activation='sigmoid')

Net2 = model(train_set_x, train_set_y, num_iterations = 20000, learning_rate = 0.001, print_cost = True, activation='softmax')

Net3 = model(train_set_x, train_set_y, num_iterations = 20000, learning_rate = 0.001, print_cost = True, activation='hybrid')

Dims = Problem_Size(8, 100, 50)
data = create_data(Dims)
model(data.In.T, data.Out.T, num_iterations = 40000, learning_rate = 0.001, print_cost = True, activation='softmax') 
model(data.In.T, data.Out.T, num_iterations = 40000, learning_rate = 0.001, print_cost = True, activation='sigmoid') 

【讨论】:

@blue-sky 你能用print(sigmoid(w @ [[0],[0],[1],[1],[0]] + b)) 代替print(sigmoid(dw @ [0,0,1,1,0] + db)) 吗? dwdb 是对权重和偏差的修正;您可能想自己尝试权重和偏差。另外,我认为您需要在这里使用列向量,注意嵌套括号。 我稍微更新了您的代码,以便从优化函数访问 w 、 b 值,执行 print(sigmoid(w @ [[0],[0],[1],[1],[0]] + b)) 并且预测现在看起来不错。将花一些时间了解您和其他人的代码并重新发布更新的工作版本,谢谢。 “成本将无法惩罚误报。” ,“求解器将简单地最大化所有在至少一个训练示例中处于活动状态的输出”您能否详细说明这些 cmets,因为我不清楚成本函数如何特别影响误报?训练示例中的非活动输出和活动输出有什么区别? @blue-sky 活跃和不活跃我只是指 1 和 0。对于您的其他问题,我已经更新了答案,请参阅新部分“回顾成本函数的工作原理” @blue-sky 我认为您无法在前一天接受?好吧,无论如何,谢谢。【参考方案2】:

如何修复错误以及如何扩展实现以在更多类之间进行分类的想法都可以通过一些维度分析来解决。

我假设您通过对多个示例进行分类意味着多个类而不是多个样本,因为我们需要多个样本来训练甚至 2 个类。

其中N = 样本数,D = 特征数,K = 类别数(K=2 是一种特殊情况,可以将其缩减为一维,即K=1 y=0 表示一类,y=1 表示另一类)。数据应具有以下维度:

X: N * D #input
y: N * K #output 
W: D * K #weights, also dW has same dimensions
b: 1 * K #bias, also db has same dimensions
#A should have same dimensions as y

只要正确完成点积,尺寸的顺序就可以互换。

首先处理您的错误:您将 W 初始化为 N * K 而不是 D * K 即。在二进制情况下:

w = np.zeros((numberOfTrainingExamples , 1))
#instead of 
w = np.zeros((numberOfFeatures , 1))

这意味着您初始化W 以纠正尺寸的唯一时间是yX(巧合)具有相同的尺寸。

这也会弄乱你的点积:

np.dot(X, w) # or np.dot(w.T,X.T) if you define y as [K * N] dimensions
#instead of 
np.dot(w.T , X) 

np.dot( X.T, ( A - Y ) ) #np.dot( X.T, ( A - Y ).T ) if y:[K * N]
#instead of
np.dot( X, ( A - Y ).T )  

还要确保成本函数返回一个数字(即不是数组)。

第二次转到K&gt;2,您需要进行一些更改。 b 不再是单个数字,而是一个向量(一维数组)。 yW 从一维数组变为二维数组。为避免混淆和难以发现的错误,最好将 KND 设置为不同的值

【讨论】:

我不知道,您对成本函数的最后声明对我来说似乎很可疑。详情见我的帖子。在我看来,它假设预测是单热或和对一的,一旦您取消 (1-Y)... 术语,就不会强制执行。 问题不在于真实类,而在于预测。您的成本函数不会比完美预测更糟糕地评价一个单一的预测。 (严格来说,将 1-eps 换成 1) @PaulPanzer 在分类问题中,通常不存在“一体式”预测。只有一个值是非零的,即“真”类。在不止一个类的 y 非零的情况下,您当然是正确的。正如您所指出的,我的建议仅在这种情况下是合理的,并且可能不适用于 OP 的情况。但这仍然是最常见的情况,可能对其他人有用。 对不起,我可能用错了术语。预测是指训练后网络的输出。问题是:没有人告诉网络“all-one”预测通常不存在,所以无论如何它都会很高兴地做出一个。您可以检查使用我的代码与 `activation="hybrid"。它使用无梯度最小化器来排除我将梯度作为错误来源的错误。

以上是关于修改神经网络对单个示例进行分类的主要内容,如果未能解决你的问题,请参考以下文章

MATLAB 中神经网络分类的 10 折交叉验证示例

从头开始的神经网络 - 预测单个示例

具有未分类输入的人工神经网络

使用 Matlab 进行基于神经网络的分类的参数设置

图像序列的分类(固定数量)

Linux系统学习