Hinton公开课 PA2


typora-copy-images-to: ./

Hinton 公开课PA2

这篇文章是Hinto公开课Neural Networks for Machine Learning的笔记,因为不常用Matlab,会有很多关于Matlab的解释。

背景介绍

作业中使用了一个250个单词的集合表示单词表,数据集中的每一条记录都有4个单词,作业的目的是使用前三个单词作为输入预测第4个单词。

这个作业对应的课件是Lec 4,以word的概率表示为出发点,介绍了bp。比较难理解的点是word 的encode,以及在嵌入层中的表示[1][2]

嵌入层的含义

在课中的小测验中已经透漏了为什么要将每个单词占用一个元素的位置:

  • 线性可分开
  • 各个数据独立,保证没有先验性知识存在。

Load Data和Struct

Matlab的Struct

data.mat和load.m数据相关,用load data.m后获得一个struct,matlab中的stuct跟C的比较类似,是众多支持的dtatype中的一种,支持的函数很多,比较有用的一个是fieldnames获得字段名,其他函数参考这里。在octave的command line里边打data可以获得该类型的提示:

>> data
data =
  scalar structure containing the fields:
      testData =
           Columns 1 through 25:
           xx xx xx

使用fieldnames函数获得如下字段:

>> fieldnames(data)
ans =
{
  [1, 1] = testData
  [2, 1] = trainData
  [3, 1] = validData
  [4, 1] = vocab
}

有点不适应matlab里边什么都用矩阵索引的方式,另外,为什么是scalar sruct?看matlab官网的tutorial怎么创建一个struct

patient(1).name = 'John Doe';
patient(1).billing = 127.00;
patient(1).test = [79, 75, 73; 180, 178, 177.5; 220, 210, 205];
patient

patient = scalar struct containing the fields::
       name: 'John Doe'
       billing: 127
       test: [3×3 double]

往array里边添加一个struct:

patient(2).name = 'Ann Lane';
patient(2).billing = 28.50;
patient(2).test = [68, 70, 68; 118, 118, 119; 172, 170, 169];
patient

patient = 1×2 struct array with fields:
    name
    billing
    test

此时就变成了1x2 struct array不再是scaar了。有一个有趣的章节是Cell vs. Struct Arrays,cell和struct的区别是struct可以用field name索引,而cell只能用index索引。看下面例子:

temperature(1,:) = {'2009-12-31', [45, 49, 0]};
temperature(2,:) = {'2010-04-03', [54, 68, 21]};
temperature(3,:) = {'2010-06-20', [72, 85, 53]};
temperature(4,:) = {'2010-09-15', [63, 81, 56]};
temperature(5,:) = {'2010-12-09', [38, 54, 18]};

temperature
temperature = 5×2 cell array
    '2009-12-31'    [1×3 double]
    '2010-04-03'    [1×3 double]
    '2010-06-20'    [1×3 double]
    '2010-09-15'    [1×3 double]
    '2010-12-09'    [1×3 double]

创建数组的方式可以看到仍然是(row, col)这种,数组索引都是从1开始。很像是Python里边的tuple,用行列方式获得数据。例如:

>> temperature(:, 1)
ans = 
{
  [1, 1] = 2009-12-31
  [2, 1] = 2010-04-03
  [3, 1] = 2010-06-20
  [4, 1] = 2010-09-15
  [5, 1] = 2010-12-09
}

>> temperature(1, :)
ans =
{
  [1,1] = 2009-12-31
  [1,2] = 45   49    0
}

顺便说一下,在command里边用clear可以清除已经设置的变量。用whos或者class 查看变量的类型。

代码

function [train_input, train_target, valid_input, valid_target, test_input, test_target, vocab] = load_data(N)
% This method loads the training, validation and test set.
% It also divides the training set into mini-batches.
% Inputs:
%   N: Mini-batch size. 批量的大小
% Outputs:
%   train_input: An array of size D X N X M, where
%                 D: number of input dimensions (in this case, 3).
%                 N: size of each mini-batch (in this case, 100).
%                 M: number of minibatches.
%   train_target: An array of size 1 X N X M.
%   valid_input: An array of size D X number of points in the validation set.
%   test: An array of size D X number of points in the test set.
%   vocab: Vocabulary containing index to word mapping.

load data.mat;
numdims = size(data.trainData, 1);
D = numdims - 1;
M = floor(size(data.trainData, 2) / N);
train_input = reshape(data.trainData(1:D, 1:N * M), D, N, M);
train_target = reshape(data.trainData(D + 1, 1:N * M), 1, N, M);
valid_input = data.validData(1:D, :);
valid_target = data.validData(D + 1, :);
test_input = data.testData(1:D, :);
test_target = data.testData(D + 1, :);
vocab = data.vocab;
end

size函数的使用方法:

sz = size(A) returns a row vector whose elements contain the length of the corresponding dimension of A. For example, if A is a 3-by-4 matrix, then size(A) returns the vector [3 4]. The length of sz is ndims(A).
If A is a table or timetable, then size(A) returns a two-element row vector consisting of the number of rows and the number of table variables.

szdim = size(A,dim) returns the length of dimension dim.
[m,n] = size(A) returns the number of rows and columns when A is a matrix.
[sz1,...,szN] = size(A) returns the length of each dimension of A separately.

可以看到,size返回了一个dimension的数组,可以通过参数返回某一维的数据。

(1:D, 1:N * M) 前者是说取1-D行,后面的意思是说取1-N列乘以M次,也就是多少个1-N。

data.trainData是一个4 x 372550的矩阵,分成了两部分,train_input和train_target,前者是三维矩阵,后者是一维矩阵。然后通过reshape分成一个个的batch。这样N = 100, D = 3, M = 3725。

>> data.trainData(1:4, 1:10)
ans = 
   28  184  183  117  223   42  242  223   74   42
   26   44   32  247  190   74   32   32   32  192
   90  249   76  201  249   26  223  158  221   91
  144  117  122  186    6   32   32  144   32   68
>> train_input(1:3, 1:10)
ans =
   28  184  183  117  223   42  242  223   74   42
   26   44   32  247  190   74   32   32   32  192
   90  249   76  201  249   26  223  158  221   91
>> train_target(1, 1:10)
ans =
  144  117  122  186    6   32   32  144   32   68

validData和testData的大小都是4 x 46568,比train的大小小了一个数量级。可以看到,train是分批量的,valid和test是不用分批量的。输入是列向量。

Train 数据集

初始化代码和参数配置

% This function trains a neural network language model.
function [model] = train(epochs)
% Inputs:
%   epochs: Number of epochs to run.
% Output:
%   model: A struct containing the learned weights and biases and vocabulary.

% SET HYPERPARAMETERS HERE.
batchsize = 100;      % Mini-batch size.
learning_rate = 0.1;  % Learning rate; default = 0.1.
momentum = 0.9;       % Momentum; default = 0.9.
numhid1 = 50;         % Dimensionality of embedding space; default = 50.
numhid2 = 200;        % Number of units in hidden layer; default = 200.
init_wt = 0.01;       % Standard deviation of the normal distribution
                      % which is sampled to get the initial weights; default = 0.01

epochs表示训练多少个来回,momentum表示使用了动量gradient descent方法。

% VARIABLES FOR TRACKING TRAINING PROGRESS.
show_training_CE_after = 100;
show_validation_CE_after = 1000;

cross entropy (CE) 表示交叉熵,每100个batch求一次平均交叉熵,每1000个batch后运行一次validation,这时求一次valid 交叉熵。

% LOAD DATA.
[train_input, train_target, valid_input, valid_target, ...
  test_input, test_target, vocab] = load_data(batchsize);
[numwords, batchsize, numbatches] = size(train_input); 
vocab_size = size(vocab, 2);

除了第一节的内容,numwords = 3, batchsize = 100, numbatches = 3725, 补充一下这里的vocab是一个行向量,一共有250个,所以vocab_size = 250.

% INITIALIZE WEIGHTS AND BIASES.
word_embedding_weights = init_wt * randn(vocab_size, numhid1);
embed_to_hid_weights = init_wt * randn(numwords * numhid1, numhid2);
hid_to_output_weights = init_wt * randn(numhid2, vocab_size);
hid_bias = zeros(numhid2, 1);
output_bias = zeros(vocab_size, 1);

word_embedding_weights_delta = zeros(vocab_size, numhid1);
word_embedding_weights_gradient = zeros(vocab_size, numhid1);
embed_to_hid_weights_delta = zeros(numwords * numhid1, numhid2);
hid_to_output_weights_delta = zeros(numhid2, vocab_size);
hid_bias_delta = zeros(numhid2, 1);
output_bias_delta = zeros(vocab_size, 1);
expansion_matrix = eye(vocab_size);
count = 0;
tiny = exp(-30);

randn 表示创建一个服从正态分布的随机数,参数就是行、列,同类型的函数族:randn() randn(n)zeros 的类型跟randn 完全相同。eye 也类似,就是identity matrix。网络图:

  • word_embedding_weights = [250, 50]
  • embed_to_hid_weights = [3 * 50, 200]
  • hid_to_output_weight = [200, 250]
  • hid_bias = [200, 1]
  • output_bias = [250, 1]

这个图里边输入是三个单词的索引,输出是推测出来的第四个单词的索引,嵌入层默认有50个,隐藏层默认有200个。所以输入到嵌入层是在word_embedding_weights 里边查找对应的index。

Q: 这里为什么要用50个是什么意思?不是只有三个输入吗?

A: 这个网络图画的很精简,没有画出神经元的个数,事实上,嵌入层有50个神经元,与输入层是全连**接关系。同样的隐藏层与嵌入层也是这种关系。所以输入在每个weight矩阵里边都有表示。

Q: 每个单词是如何表示的?

A: 每个单词一个索引。每个单词都与50个神经元有联系。

Train

前向传播

% TRAIN.
for epoch = 1:epochs
  fprintf(1, 'Epoch %d\n', epoch);
  this_chunk_CE = 0;
  trainset_CE = 0;
  % LOOP OVER MINI-BATCHES.
  for m = 1:numbatches
    input_batch = train_input(:, :, m);
    target_batch = train_target(:, :, m);

    % FORWARD PROPAGATE.
    % Compute the state of each layer in the network given the input batch
    % and all weights and biases
    [embedding_layer_state, hidden_layer_state, output_layer_state] = ...
      fprop(input_batch, ...
            word_embedding_weights, embed_to_hid_weights, ...
            hid_to_output_weights, hid_bias, output_bias);

input_batch是[3, 100],target_batch是[1, 100]。fprop的代码中,首先计算word embedding layer的值:

[numwords, batchsize] = size(input_batch);
[vocab_size, numhid1] = size(word_embedding_weights);
numhid2 = size(embed_to_hid_weights, 2);

%% COMPUTE STATE OF WORD EMBEDDING LAYER.
% Look up the inputs word indices in the word_embedding_weights matrix.
embedding_layer_state = reshape(...
  word_embedding_weights(reshape(input_batch, 1, []),:)',...
  numhid1 * numwords, []);

这里用实际上做的还是矩阵乘法,但是用index缩小了计算量[1]。相当于:

inputs = zeros(numwords * batchszie, vocabsize) % [batch input Number, features number]
inputs * word_embedding_weights

最终得到的embedding_layer_state是[300, 50] -> [150, 100]的矩阵。

Q: reshape(input_batch, 1, [])中[]是什么意思?

A: 首先看一下不用[],MATLAB会报错,看这里的api介绍 []的作用是让matlab自己去推导个数。 所以这一行意思是把3*100的input拉直成一个1 x 300的列向量。

Q: 怎么用input_batch的值查找weight矩阵?

A: word_embedding_weights是一个[250, 50]的矩阵,用[1, 300]的列向量索引使用了matlab的Assigning to Elements Outside Array Bounds 特性,使用的是将input的值每个都作为行索引,最终将结果扩展成一个[300, 50]的矩阵。顺便提这里matlab的matrix indexing,matlab中矩阵是按列在内存中布局的,也就是说最好是按列计算。

Q: 还是不列举这个过程,能不能举个例子?

A: 例子如下,input的作用就是选哪一行。

>> weights = magic(3)
weights = 
    8    1    6
    3    5    7
    4    9    2
>> input = [3, 2, 1, 3]' % the value should not exceed 3 or error occurs.
>> weights(input, :)
ans =
    4    9    2
    3    5    7
    8    1    6
    4    9    2

Q:例子中为什么用列向量作为input?而不是行向量

A: 实验了一下是一样的,列向量更有效率(memory layout)?, 重新测试如下:

>> input = [3, 2]'
>> weights(input, :)
ans =
    4    9    2
    3    5    7
>> input = [3, 2]
ans =
    4    9    2
    3    5    7

具体形状如何变化参考:Accessing Multiple Elements


  1. http://spaces.ac.cn/archives/4122/ "词向量与Embedding究竟是怎么回事?" ↩

  2. https://zhuanlan.zhihu.com/p/27830489 "YJango的Word Embedding--介绍" ↩

你可能感兴趣的:(Hinton公开课 PA2)