CNTK API文档翻译(2)——逻辑回归

来源:互联网 发布:云盘 php源码 编辑:程序博客网 时间:2024/06/08 22:30

这篇教程的目标人群是机器学习和CNTK的新手。在这篇教程中,你将训练一个简单但是强大的机器学习模型,这个模型被广泛用于各个行业的应用中。这个训练可以使用CNTK库,通过扩充计算资源(CPU、GPU)来推广到大量的数据集。

介绍

问题: 一个肿瘤医院提供了一些数据,他们想知道一个病人的癌细胞是恶性生长还是良性生长的。这是一个分类问题,为了区分每个病人,我们给出他们的数据和肿瘤大小。从直觉上来说,我们会认为年轻的病人或者肿瘤大小小的病人比较有可能是良性肿瘤。下面的数据模仿这个应用,里面的每个点代表一个病人,红色的点表示恶性肿瘤,蓝色的点表示良性肿瘤,数据如图。注:这是个实验数据,现实生活中对病人的决策不会如此草率,他们经常来自于大量的实验数据和临床数据。
image

目标: 我们的目标是训练处一个自动分类器根据年龄和肿瘤大小来标识每一个患者是良性肿瘤还是恶性肿瘤。在这个教程中,我们将建立一个线性分类器,这是学习深度神经网络的敲门砖。这个线性分类器如图:
image

在上面的图片中,绿色的线表示我们训练好的模型,用来区分蓝色的点和绿色的点。在本教程中,我们将学习如何训练出这条绿线。注:这个分类器把一些蓝色的点分到了绿线的上方,怎么修正这种错误将在以后的教程中给出。

方案: 所有的机器学习算法都有五个通用步骤:数据读取,数据处理,创建模型,训练模型参数,评估模型。

  1. 数据读取:我们生成模拟数据,设置每个数据有两个要素,分别是年龄和肿瘤大小
  2. 数据处理:一般来说,训练涉及到的数据(比如年龄和肿瘤大小)是需要进行标准化的,通常会把他们映射为0到1之间。不过为了使我们的第一个教程足够简单,我们在此省略这步。
  3. 创建模型:我们会引入一个基本的线性模型。
  4. 训练模型:虽然存在很多方法来拟合一个线性模型,不过在CNTK你面我们会使用随机梯度下降算法(SGD)。
  5. 评估模型:用一部分知道分类结果且不用于训练的数据来测试我们的模型,看我们的模型表现得怎么样。

逻辑回归

逻辑回归使用特征参数权重的线性组合来确定某个样本属于设定好的几个类别的可能性。在我们的这个任务中,生成的可能性会在[0,1]之间,然后通过设定一个阈值(比如0.5),来生成一个二进制的标签(0或者1),而且这种方法可以被非常容易的扩展到多个类别。
image

在上面的图片中,每个输入特征值是采用线性加权的,结果使用Sigmoid函数(注:Sigmoid函数的详情请看我的Python与人工神经网络的第二期)映射到0-1之间。对于多个分类的情况,可以使用Softmax函数。(注:Softmax函数的详情请看我的Python与人工神经网络的第九期)。

# Import the relevant componentsfrom __future__ import print_functionimport numpy as npimport sysimport osfrom cntk import *# Select the right target device when this notebook is being tested:if 'TEST_DEVICE' in os.environ:    import cntk    if os.environ['TEST_DEVICE'] == 'cpu':        cntk.device.try_set_default_device(cntk.device.cpu())    else:        cntk.device.try_set_default_device(cntk.device.gpu(0))

数据生成

让我们先使用Numpy库生成一些数据来模拟癌症患者样本,不管这些数据属于恶性肿瘤还是良性肿瘤,他们都有两个特征值(两个维度)。

在我们的例子中,训练数据中的每个数据都有一个标签(蓝色或绿色,良性或恶性)与之对应,我们分别使用0和1表示。

# Define the networkinput_dim = 2num_output_classes = 2

输入数据和标签

在教程中我们使用Numpy生成模拟数据,在解决现实问题时,我们经常会使用数据读取器,他能够读取读取每个样本的所有特征值。在运行之前,我们还需要对数据进行预处理:将年龄这个变量的数值会被映射到与肿瘤大小数值所在的范围内。注:每个样本都可以引入更多的特征数据,用高维的张量表示,这将在以后的教程中介绍。

# Ensure we always get the same amount of randomnessnp.random.seed(0)# Helper function to generate a random data sampledef generate_random_data_sample(sample_size, feature_dim, num_classes):    # Create synthetic data using NumPy.     Y = np.random.randint(size=(sample_size, 1), low=0, high=num_classes)    # Make sure that the data is separable     X = (np.random.randn(sample_size, feature_dim)+3) * (Y+1)    # Specify the data type to match the input variable used later in the tutorial     # (default type is double)    X = X.astype(np.float32)        # converting class 0 into the vector "1 0 0",     # class 1 into vector "0 1 0", ...    class_ind = [Y==class_number for class_number in range(num_classes)]    Y = np.asarray(np.hstack(class_ind), dtype=np.float32)    return X, Y
# Create the input variables denoting the features and the label data. Note: the input # does not need additional info on number of observations (Samples) since CNTK creates only # the network topology first mysamplesize = 32features, labels = generate_random_data_sample(mysamplesize, input_dim, num_output_classes)

让我们来将生成的数据可视化。

注:假设在引入matplotlib.pyplot的时候发生错误,运行一下conda install matplotlib,可以修复pyplot版本支持。如果你的Python运行环境不是Anaconda里带的Python,需要使用pip install安装。(软件安装和环境配置的内容不在本系列文章的计划之内)

# Plot the data import matplotlib.pyplot as plt%matplotlib inline# given this is a 2 class () colors = ['r' if l == 0 else 'b' for l in labels[:,0]]plt.scatter(features[:,0], features[:,1], c=colors)plt.xlabel("Scaled age (in yrs)")plt.ylabel("Tumor size (in cm)")plt.show()

可以显示如第一幅图的图片。

模型创建

逻辑回归神经网络是机器学习领域最简单基础,但是在过去几十年在该领域的应用上发挥了巨大威力。逻辑回归是一个简单的线性模型,有一些我们需要用来分类的输入数据,用矢量表示(下图中的蓝色节点),由这些输入数据计算出评价值(z,下图绿色节点的输入值)。每一个输入数据都通过权重(w,下图中的黑线或者黑线的变体)与输出节点相连。
image

第一步就是计算这个评估值(z)

w=i=1nwi×xi=wx+b

其中w是所有n个权重组成的权重矢量,b叫做偏移量。注:以后的粗体都表示矢量。

最后评估值(z)会使用sigmoid函数或者softmax函数映射到0-1之间。

输入变量(一个关键的CNTK概念):

输入变量是一个用户需要通过编码去填满的一个数据容器,用来表征输入样本,作为机器学习模型的输入值来参与运算。因此,输入变量的大小必须与提供给学习函数的数据的大小一样。比如我们的的实验中,有两个维度,因此input_dim = 2,以后的教程会有更多这方面的内容。

feature = input(input_dim, np.float32)

构建神经网络

linear_layer玩完全是是实现了上面那个等式,其中执行了两个操作:
1. 使用CNTK的times方法来进行权重(w)和特征值(x)的乘积运算,累加所有特征的权重,
2. 再加上偏移量b

这步操作在CNTK上做了底层的优化,并且隐去了一些繁琐的代码。

# Define a dictionary to store the model parametersmydict = {"w":None,"b":None} def linear_layer(input_var, output_dim):    input_dim = input_var.shape[0]    weight_param = parameter(shape=(input_dim, output_dim))    bias_param = parameter(shape=(output_dim))    mydict['w'], mydict['b'] = weight_param, bias_param    return times(input_var, weight_param) + bias_param

z用来表示这层网络的输出值

output_dim = num_output_classesz = linear_layer(feature, output_dim)

学习模型参数

现在这个神经构建好了,我们希望学习出参数w和b。为了实现这点,我们使用softmax函数将评估值z转换成一组概率(p)。

p=softmax(z)

(softmax函数的详情请看我的Python与人工神经网络的第九期。)

训练

Softmax函数的输出值是某个样本属于每个类别的概率。为了训练这个分类器,我们需要决定我们的模型需要去做什么呢?我们需要我们的模型算出来的概率与他标记的类别所示的概率非常接近,为了表示这个,我们引入成本函数。

交叉熵成本函数是机器学习领域经常用的成本函数,其定义如下:

H(p)=j=1Cyjlog(pj)

其中p就是我们通过softmax函数计算出来的概率,y表示标签,也叫校验标签。在我们的二元分类的例子中,这个标签有两个元素。一般来说,如果项目需要分C类,那么标签应该有C个元素,这C个元素中,正确的分类对应的要素质是1,其他的都应该是0。我们强烈要求读者了解交叉熵成本函数的细节。(交叉熵成本函数的详情可以看我的Python与人工神经网络的第五期)

label = input((num_output_classes), np.float32)loss = cross_entropy_with_softmax(z, label)

评估

为了评估分类结果,我们可以比较神经网络输出的结果和标记:

eval_error = classification_error(z, label)

配置训练参数

我们的训练采用各种各样的优化方法来减少成本函数的数值,这些方法中随机梯度下降算法是最流行的一个。通常我们会随机生成模型参数,随机梯度下降算法会计算成本函数以及运算结果和标签之间大差值,使用梯度下降的方法来给模型生成一个新的参数,进入下一个迭代。

上述的模型描述的我们会比较倾向于一次训练一个数据,这样不用在内存中一次加载太多数据,即使数据量太大也可以进行运算。不过单个样本的特异性比较大,这样每次训练的变化会不稳定,不利于训练。一个折中的方法是选择一小段数据集,使用他们的平均成本函数值和插值来更新模型参数,这个数据集叫取样集(minibatch,网上也找不到合适的翻译,这是我翻译的,在以前的文章中我也不知道怎么翻译,可能在不同的场景下翻译的还不同,以后就用取样集了)。

使用取样集我们可以简化大的训练数据集,我们一遍又一遍的使用不同的取样集来更新模型参数,以此来减少成本函数值,当误差率不再明显变化或者说我们设置好的训练轮数到了,我们就说这个模型训练好了。

训练过程中一个重要的参数叫学习率(learning_rate),我们可以把它认为成是每次迭代参数变大大小的标志。以后的教程我们会引入更多的技术细节,不过在这个教程中,有上面的信息,我们就能创建训练器了:

# Instantiate the trainer object to drive the model traininglearning_rate = 0.5lr_schedule = learning_rate_schedule(learning_rate, UnitType.minibatch) learner = sgd(z.parameters, lr_schedule)trainer = Trainer(z, (loss, eval_error), [learner])

在此之前,我们还需要创建一些辅助函数来计算和打印训练中我们需要知道的数据,这些函数仅仅是帮助我们理解训练的过程:

# Define a utility function to compute the moving average sum.# A more efficient implementation is possible with np.cumsum() functiondef moving_average(a, w=10):    if len(a) < w:         return a[:]        return [val if idx < w else sum(a[(idx-w):idx])/w for idx, val in enumerate(a)]# Defines a utility that prints the training progressdef print_training_progress(trainer, mb, frequency, verbose=1):    training_loss, eval_error = "NA", "NA"    if mb % frequency == 0:        training_loss = trainer.previous_minibatch_loss_average        eval_error = trainer.previous_minibatch_evaluation_average        if verbose:             print ("Minibatch: {0}, Loss: {1:.4f}, Error: {2:.2f}".format(mb, training_loss, eval_error))    return mb, training_loss, eval_error

运行训练器

现在我们就可以训练我们的逻辑回归模型了,在本次例子中,每个取样集会包括25个样本,总共大概会训练20000个样本。假设我们只有10000个数据,那么训练器会把这个数据使用两遍。注:在现实的项目中,我们会给定一些有标签的数据,其中的百分之七十用于训练,剩下的会用于检验。

# Initialize the parameters for the trainerminibatch_size = 25num_samples_to_train = 20000num_minibatches_to_train = int(num_samples_to_train  / minibatch_size)
# Run the trainer and perform model trainingtraining_progress_output_freq = 50plotdata = {"batchsize":[], "loss":[], "error":[]}for i in range(0, num_minibatches_to_train):    features, labels = generate_random_data_sample(minibatch_size, input_dim, num_output_classes)    # Specify input variables mapping in the model to actual minibatch data to be trained with    trainer.train_minibatch({feature : features, label : labels})    batchsize, loss, error = print_training_progress(trainer, i,                                                      training_progress_output_freq, verbose=1)    if not (loss == "NA" or error =="NA"):        plotdata["batchsize"].append(batchsize)        plotdata["loss"].append(loss)        plotdata["error"].append(error)

下面是打印的训练过程(每个人训练的情况会有所不同):

Minibatch: 0, Loss: 0.6931, Error: 0.32Minibatch: 50, Loss: 1.4795, Error: 0.36Minibatch: 100, Loss: 1.1093, Error: 0.32Minibatch: 150, Loss: 0.3336, Error: 0.20Minibatch: 200, Loss: 0.1325, Error: 0.08Minibatch: 250, Loss: 0.1334, Error: 0.08Minibatch: 300, Loss: 0.1012, Error: 0.04Minibatch: 350, Loss: 0.1103, Error: 0.04Minibatch: 400, Loss: 0.3099, Error: 0.08Minibatch: 450, Loss: 0.3240, Error: 0.12Minibatch: 500, Loss: 0.3922, Error: 0.20Minibatch: 550, Loss: 0.6709, Error: 0.24Minibatch: 600, Loss: 0.3018, Error: 0.12Minibatch: 650, Loss: 0.1677, Error: 0.12Minibatch: 700, Loss: 0.2782, Error: 0.12Minibatch: 750, Loss: 0.2313, Error: 0.04

评估/测试模型

现在我们完成了神经网络的训练,让我们使用一些在训练中没用过的数据来检验他一下,这叫测试。我们需要创建一些新数据,然后评估模型在他们上运行时候的平均成本函数值和差值。这在代码中使用trainer.test_minibatch完成。留意训练时的差值和测试数据的差值,这是一个关键的检查点。如果大范围的出现测试数据的差值大于训练数据的差值,就表示训练好的模型在其他数据上表现不会很好,这叫做过度拟合(关于过度拟合的详情可以看我的Python与人工神经网络的第六期),过度拟合的解决方法超出了本教程的范围,不过CNTK提供了必要的组件来解决这个问题。

注:我们的测试仅仅使用了一个取样集来表示了一下,在实际使用中会运行多个测试数据的取样集然后报告其平均值。

# Run the trained model on newly generated datasettest_minibatch_size = 25features, labels = generate_random_data_sample(test_minibatch_size, input_dim, num_output_classes)trainer.test_minibatch({feature : features, label : labels})

输出结果

为了评估,我们将神经网络的输出结果映射到0-1之间,然后使用softmax函数将其转换为每类的概率值,来得这个病人是良性肿瘤还是恶性肿瘤。

out = softmax(z)result = out.eval({feature : features})
print("Label    :", [np.argmax(label) for label in labels])print("Predicted:", [np.argmax(result[i,:]) for i in range(len(result))])

结果:
Label : [1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1]
Predicted: [1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1]

让我们来比较一下我们的预测值和实际标记值吧,他们应该是一样的,不过其中有一些还是出错了。

可视化

在这个实验中,使用的都是二位数据,结果非常容易进行可视化。如果遇到高维数据,可视化就比较难了,会使用到比较高端的降维技术。(这部分的代码我就不贴了)


欢迎扫码关注我的微信公众号获取最新文章
image