离弦的博客

我若为王,舍我其谁


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

  • 搜索

tensorflow入门笔记1

发表于 2018-05-14 | 分类于 tensorflow |

1、MNIST数据集介绍

        首先介绍MNIST数据集。如果所示,MNIST数据集主要由一些手写数字的图片和相应的标签组成,图片一共有10类,分别对应0~9,共10个阿拉伯数字。如下图:

1

MNIST 数据集可在 http://yann.lecun.com/exdb/mnist/ 获取, 它包含了四个部分:

  • Training set images: train-images-idx3-ubyte.gz (9.9 MB, 解压后 47 MB, 包含 60,000 个样本)
  • Training set labels: train-labels-idx1-ubyte.gz (29 KB, 解压后 60 KB, 包含 60,000 个标签)
  • Test set images: t10k-images-idx3-ubyte.gz (1.6 MB, 解压后 7.8 MB, 包含 10,000 个样本)
  • Test set labels: t10k-labels-idx1-ubyte.gz (5KB, 解压后 10 KB, 包含 10,000 个标签)

        MNIST 数据集来自美国国家标准与技术研究所, National Institute of Standards and Technology (NIST). 训练集 (training set) 由来自 250 个不同人手写的数字构成, 其中 50% 是高中学生, 50% 来自人口普查局 (the Census Bureau) 的工作人员. 测试集(test set) 也是同样比例的手写数字数据。 60,000 个训练样本和 10,000 个测试样本.

阅读全文 »

embedding

发表于 2018-05-11 |

搜狐新闻数据(SogouCS)

介绍:

来自搜狐新闻2012年6月—7月期间国内,国际,体育,社会,娱乐等18个频道的新闻数据,提供URL和正文信息

格式说明:

数据格式为



页面URL

页面ID

页面标题

页面内容

注意:content字段去除了HTML标签,保存的是新闻正文文本

完整版(648MB):tar.gz格式,zip格式

1、构建中文语料库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 搜狐新闻 2.1G
tar -zxvf news_sohusite_xml.full.tar.gz
cat news_sohusite_xml.dat | iconv -f gb18030 -t utf-8 | grep "<content>" > news_sohusite.txt
sed -i 's/<content>//g' news_sohusite.txt
sed -i 's/<\/content>//g' news_sohusite.txt
python -m jieba -d ' ' news_sohusite.txt > news_sohusite_cutword.txt


# 全网新闻 1.8G
tar -zxvf news_tensites_xml.full.tar.gz
cat news_tensites_xml.full.tar.gz | iconv -f gb18030 -t utf-8 | grep "<content>" > news_tensite.txt
sed -i 's/<content>//g' news_tensite.txt
sed -i 's/<\/content>//g' news_tensite.txt
python -m jieba -d ' ' news_tensite.txt > news_tensite_cutword.txt

第一条命令解压之后会生成news_sohusite_xml.dat文件

2、利用gensim库进行训练

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import logging
from gensim.models import word2vec
import multiprocessing

# Loading corpus...
if __name__ == '__main__':
input_file = "news_sohu_text.txt"
vector_file = "news_sohu_embedding"
# 保存字典文件
vocab_file = "vocabulary_file"
sentences = word2vec.Text8Corpus(input_file)

'''
Training embedding...
embedding_dim : 100
window_size :5
minimum_count of word :5
epoch of model(iter):10
'''
# 记录各种优先级的东西; 激活日志记录
logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s', level=logging.INFO)
# sg=0 使用cbow训练, sg=1对低频词较为敏感,默认是sg=0
model = word2vec.Word2Vec(sentences, size=100, window=5, min_count=6, iter=10, workers=multiprocessing.cpu_count())
# Saving word_embedding
model.wv.save_word2vec_format(vector_file, fvocab=vocab_file, binary=False)

​ input_file是输入文件,如果是训练词向量,必须是分词好的文件,词与词之间空格隔开。如果训练的是字向量,字与字之间同样用空格隔开。一个句子是一个列表,所有的句子是二维列表。

3、加载训练好的embedding并测试

1
2
3
4
5
6
7
8
9
# 加载模型,embedding_file是训练好的向量文件
model = gensim.models.KeyedVectors.load_word2vec_format(embedding_file, binary=False)
temp1 = model.most_similar('我') # 测试相关词
temp2 = model.most_similar('好') # 测试相关词
t = model.similarity("他", "她") # 测试相似度

print(temp1)
print(temp2)
print(t)

我在搜狐新闻训练的向量测试的结果:

[('咱', 0.8646953105926514), ('他', 0.857693076133728), ('你', 0.7627511024475098), ('她', 0.7525184750556946), ('俺', 0.7336732149124146), ('它', 0.7071460485458374), ('牠', 0.46839216351509094), ('谁', 0.4390422999858856), ('您', 0.40126579999923706), ('尤', 0.3958999514579773)]

[('懂', 0.4896905720233917), ('棒', 0.4554690718650818), ('是', 0.4444722533226013), ('佳', 0.44040754437446594), ('让', 0.43987756967544556), ('快', 0.4291210174560547), ('朋', 0.42199385166168213), ('错', 0.4218539297580719), ('多', 0.42170557379722595), ('乖',0.4164968729019165)]`` 0.813737169426`

4、训练注意

Word2vec 有多个影响训练速度和质量的参数。

其中之一是用来修剪内部字典树的。在一个数以亿计的预料中出现一到两次的单词非常有可能是噪音或不需要被关注的。另外,也没有足够的数据对他们进行有意义的训练。因此,最好的办法就是直接将他们忽略掉。
model = Word2Vec(sentences, min_count=10) # default value is 5

  1. 对于设定 min_count 的值,合理的范围是0 - 100,可以根据数据集的规模进行调整。
  2. 另一个参数是神经网络 NN 层单元数,它也对应了训练算法的自由程度。

    model = Word2Vec(sentences, size=200) # default value is 100
    更大的 size 值需要更多的训练数据,但也同时可以得到更准确的模型。合理的取值范围是几十到几百。

3.最后一个主要参数是训练并行粒度,用来加速训练。
model = Word2Vec(sentences, workers=4) # default = 1 worker = no parallelization
该参数只有在机器已安装 Cython 情况下才会起到作用。如没有 Cython,则只能单核运行。

5、评估

Word2vec 训练是一个非监督任务,很难客观地评估结果。评估要依赖于后续的实际应用场景。Google 公布了一个包含 20,000 语法语义的测试样例,形式为 “A is to B as C is to D”。

需要注意的是,如在此测试样例上展示良好性能并不意味着在其它应用场景依然有效,反之亦然。

参考:

1.https://www.cnblogs.com/jkmiao/p/7007763.html

动态规划

发表于 2018-05-07 |

​ 动态规划中递推式的求解方法不是动态规划的本质。 动态规划的本质,是对问题状态的定义和状态转移方程的定义。 动态规划是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。 本题下的其他答案,大多都是在说递推的求解方法,但如何拆分问题,才是动态规划的核心。 而拆分问题,靠的就是状态的定义和状态转移方程的定义。 要解决这个问题,我们首先要定义这个问题和这个问题的子问题。 有人可能会问了,题目都已经在这了,我们还需定义这个问题吗?需要,原因就是问题在字面上看,不容易找不出子问题,而没有子问题,这个题目就没办法解决 。状态转移方程,就是定义了问题和子问题之间的关系。 可以看出,状态转移方程就是带有条件的递推式。

一.阶乘(Factorial)

$1\times 2\times3\times\dots\times N$,整数1到$N$的连乘积。$N$阶乘$N!$

分析:$N!$源自$(N-1)!$,如此就递回分割问题了。

img

阵列的每一格对应每一个问题。设定第一格的答案,再以回圈依序计算其余答案。

img

1
2
3
4
5
6
7
8
9
const int N = 10;
int f[N];
void factorial()
{
f[0] = 0;
f[1] = 1;
for (int i=2; i<=N; ++i)
f[i] = f[i-1] * i;
}
1
2
3
4
5
6
7
8
const int N = 10;

void factorial()
{
int f = 1;
for (int i=2; i<=N; ++i)
f *= i;
}

时间复杂度

总共$N$个问题,每个问题花费$O(1)$时间,总共花费$O(N)$时间

空间复杂度

求$1!$到$N!$:总共$N$个问题,用一条$N$格阵列存储全部问题的答案,空间复杂度为$O(N)$

只求$N!$:用一个变量累计成绩,空间复杂度为$O(1)$

Dynamic Programming: recurrence

Dynamic Programming = Divide and Conquer + Memoization

​ 动态规划是分治法的延伸。当递推分割出来的问题,一而再、再而三出现,就运用记忆法储存这些问题的答案,避免重复求解,以空间换取时间。动态规划的过程,就是反复地读取数据、计算数据、储存数据。

img

  1. 把原问题递归分割成许多更小的问题。(recurrence)

    子问题与原问题的求解方式皆类似。(optimal sub-structure)

    子问题会一而再、再而三的出现。(overlapping sub-problems)

  2. 设计计算过程:

    确认每个问题需要哪些子问题来计算答案。(recurrence)

    确认总共有哪些问题。(state space)

    把问题一一对应到表格。(lookup table)

    决定问题的计算顺序。(computational sequence)

​ 确认初始值、计算范围(initial states / boundary)

  1. 实作,主要有两种方式:

    Top-down

    Bottom-up

1. recurrence

​ 递归分割问题时,当子问题与原问题完全相同,只有数值范围不同,我们称此现象为recurrence,再度出现、一而再出现之意。

【注: recursion 和 recurrence ,中文都翻译为“递归”,然而两者意义大不相同,读者切莫混淆】

​ 此处以爬楼梯问题当做范例。先前于递归法章节,已经谈过如何求踏法,而此处要谈如何求踏法数目。

img

​ 踏上第五阶,只能从第四阶或从第三阶踏过去,因此爬到五阶源自两个子问题:爬到四阶与爬到三阶。

a

​ 爬到五阶的踏法数目,就是综合爬到四阶与爬到三阶的踏法数目。写成数学式子是$f(5)=f(4)+f(3)$,其中$f$表示爬到某阶的踏法数目。

​ 依样画葫芦,得到
$$
f(4)=f(3)+f(2)\\
f(3)=f(2)+f(1)
$$
​ 爬到两阶与爬到一阶无法再分割,没有子问题,直接得到
$$
f(2)=2\\
f(1)=1
$$
整理成一道简明扼要的递归公式:
$$
f(n)= \begin{cases}
1&if n=1 \\
2 & if n =2 \\
f(n-1)+f(n-2) & if n \ge3 and n\le 5
\end{cases}
$$
爬到任何一阶的踏法数目,都可以借由这道递归公式求得。img带入实际数值,递归计算即可。

​ 为什么分割问题之后,就容易计算答案呢?因为分割问题时,同时也分类了这个问题所有可能答案。分类使得答案的规律变得单纯,于是更容易求得答案。

img

2 、state space

想要计算第五阶的踏法数目。

全部的问题是“爬到一节”、“爬到二阶”、“爬到三阶”、“爬到四阶”、“爬到五阶”。

img

至于爬到零阶、爬到负一阶。爬到负二阶以及爬到六阶、爬到七阶没有必要计算。

3、lookup table

​ 建立六格的阵列,存储五个问题的答案。

​ 表格的第零格不使用,第一格式爬到一阶的答案,第二格是爬到二阶的答案,以此类推。

img

​ 如果只计算爬完五阶,也可以建立三个变量交替使用。

4、computational sequence

​ 因为每个问题都依赖阶数少一阶、阶数少两阶这两个问题,所以必须由阶数小的问题开始计算。

​ 计算顺序是爬到一阶、爬到二阶、……、爬到五阶。设定初始值。

5、 initial states / boundary

​ 最先计算的问题时爬到一阶与爬到二阶,必须预先将答案填入表格。写入方程式,才能计算其他问题。心算求得爬到一阶的答案是1,爬到二阶的答案是2。最后计算的问题时原问题是原问题爬到五阶。

​ 为了让表格更顺畅。为了让程式更漂亮,可以加入爬到零阶的答案,对应到表格的第零格。爬到零阶的答案,可以运用爬到一阶的答案与爬到两阶的答案,刻意逆推而得。

img

​ 最后可以把初始值,尚待计算的部分、不需计算的部分,整理成一道递归公式:
$$
f(n)= \begin{cases}
0 & if n<0\\ 0="" 1="" &="" if="" \="" n="0\\" 1&if="" \\="" f(n-1)+f(n-2)="" \ge2="" and="" n\le="" 5\\="">5
\end{cases}
$$

6、实现

​ 直接用递归实作,而不使用记忆体存储各个问题的答案,是最直接的方式,也是最慢的方式。时间复杂度是$O(f(n))$。问题一而再、再而三的出现,不断呼叫同样的函数求解,效率不彰。刚接触DP的新手常犯这种错误。

1
2
3
4
5
6
7
int f(int n)
{
if (n == 0 || n == 1)
return 1;
else
return f(n-1) + f(n-2);
}

​ 正确的DP是一边计算,一边将计算出来的数值存入表格,以便不必重算。这里整理了两种实现方式,各有优缺点。

1)Top-down

2)Bottom-up

img

1)Top-down

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int table[6];   // 表格,储存全部问题的答案。
bool solve[6]; // 记录问题是否已经計算完毕

int f(int n)
{
// [Initial]
if (n == 0 || n == 1) return table[n] = 1;

// [Compute]
// 如果已經計算過,就直接讀取表格的答案。
if (solve[n]) return table[n];

// 如果不曾計算過,就計算一遍,儲存答案。
table[n] = f(n-1) + f(n-2); // 將答案存入表格
solve[n] = true; // 已經計算完畢
return table[n];
}

void stairs_climbing()
{
for (int i=0; i<=5; i++)
solve[i] = false;

int n;
while (cin >> n && (n >= 0 && n <= 5))
cout << "爬到" << n << "阶," << f(n) << "种踏法";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int table[6];   // 合并solve跟table,简化代码。
int f(int n)
{
// [Initial]
    if (n == 0 || n == 1) return 1;

    // [Compute]
    // 用 0 代表该问题还未计算答案
//  if (table[n] != 0) 
return table[n];
if (table[n]) 
return table[n];
    return table[n] = f(n-1) + f(n-2);
}
 
void stairs_climbing()
{
   for (int i=0; i<=5; i++)
     table[i] = 0;
 
    int n;
    while (cin >> n && (n >= 0 && n <= 5))
        cout << "爬到" << n << "阶," << f(n) << "种踏法";
}

​ 这个实现方式的好处是不必斤斤计较计算顺序,因为代码中的递归结构会迫使最小的问题先被计算。这个实现方式的另一个好处是只计算必要的问题,而不必计算所有可能的问题。

​ 这个实现的坏处是代码采用递归结构,不断调用函数,执行效率差。这个实现方式的另一个坏处是无法自由地控制计算顺序,因而无法妥善运用记忆体,浪费了可回收再利用的记忆体。

2)Bottom-up

​ 指定一个计算顺序,然后由最小问题开始计算。特色是代码通常只有几个递归。这个实现方式的好处与坏处与前一个方式恰好互补。

​ 首先建立表格。

1
2
int table[6];
int table[5 + 1];

心算爬到零阶的答案、爬到一阶的答案,填入报个当中,作为初始值。分别天道表格的第零格、第一格。

1
2
table[0] = 1;
table[1] = 1;

​ 尚待计算的部分就是爬到两阶的答案、……、爬到五阶的答案。通常是使用递归,按照计算顺序来计算。

计算过程的实现方式,有两种迥异的风格。一种是往回取值,是常见的实现方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
往回取值
int table[6];

void dynamic_programming()
{
// [Initial]
table[0] = 1;
table[1] = 1;

// [Compute]
for (int i=2; i<=5; i++)
table[i] = table[i-1] + table[i-2];
}

另一种是往后补值,是罕见的实现方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
往后补值int table[6];

void dynamic_programming()
{
// [Initial]
for (int i=0; i<=5; i++) table[i] = 0;
table[0] = 1;
// table[1] = 1; // 剛好可以被算到

// [Compute]
for (int i=0; i<=5; i++)
{
if (i+1 <= 5) table[i+1] += table[i];
if (i+2 <= 5) table[i+2] += table[i];
}
}

7、总结

​ 第一,先找到原问题和其子问题之间的关系,写成递归公式。如此一来,即可利用递归公式,用子子问题的答案,求出子问题的答案;用子问题的答案,求出原问题的答案。

​ 第二。确认可能出现的问题全部总共有哪些;用子问题的答案,求出原问题的答案。

​ 第三。有了递归公式之后,就必须安排出一套计算顺序。大问题的答案,总是以小问题的答案来求得的,所以,小问题的答案必须是先算的,否则大问题的答案从何而来呢?一个好的安排方式,不但使代码易写,还可重复利用记忆体空间。

​ 第四。记得先将最小,最先被计算的问题,心算出答案,储存如表格,内建与代码之中。一道递归公式必须拥有初始值,才有计算其他项。

​ 第五。实现DP的代码时,会建立一个表格,表格存入所有大小问题的答案。安排好每个问题的答案再表格的哪个位置,这样计算时才能知道该在哪里取值。

​ 切勿存取超出表格的原始,产生溢位情形,导致答案算错。计算过程当中,一旦某个问题的答案出错,就会如骨牌效应般一个影响一个,造成很难除错。

tf8

发表于 2018-04-08 |

循环神经网络

​ 某些任务需要能够更好的处理序列的信息,即前面的输入和后面的输入是有关系的。比如,当我们在理解一句话意思时,孤立的理解这句话的每个词是不够的,我们需要处理这些词连接起来的整个序列;当我们处理视频的时候,我们也不能只单独的去分析每一帧,而要分析这些帧连接起来的整个序列。这时,就需要用到深度学习领域中另一类非常重要神经网络:循环神经网络(Recurrent Neural Network)。RNN种类很多,也比较绕脑子。不过读者不用担心,本文将一如既往的对复杂的东西剥茧抽丝,帮助您理解RNNs以及它的训练算法,并动手实现一个循环神经网络。

语言模型

​ RNN是在自然语言处理领域中最先被用起来的,比如,RNN可以为语言模型来建模。那么,什么是语言模型呢?我们可以和电脑玩一个游戏,我们写出一个句子前面的一些词,然后,让电脑帮我们写下接下来的一个词。比如下面这句:

我昨天上学迟到了,老师批评了__。

​ 我们给电脑展示了这句话前面这些词,然后,让电脑写下接下来的一个词。在这个例子中,接下来的这个词最有可能是『我』,而不太可能是『小明』,甚至是『吃饭』。

​ 语言模型就是这样的东西:给定一个一句话前面的部分,预测接下来最有可能的一个词是什么。

​ 语言模型是对一种语言的特征进行建模,它有很多很多用处。比如在语音转文本(STT)的应用中,声学模型输出的结果,往往是若干个可能的候选词,这时候就需要语言模型来从这些候选词中选择一个最可能的。当然,它同样也可以用在图像到文本的识别中(OCR)。

​ 使用RNN之前,语言模型主要是采用N-Gram。N可以是一个自然数,比如2或者3。它的含义是,假设一个词出现的概率只与前面N个词相关。我们以2-Gram为例。首先,对前面的一句话进行切词:

我 昨天 上学 迟到 了 ,老师 批评 了 。

​ 如果用2-Gram进行建模,那么电脑在预测的时候,只会看到前面的『了』,然后,电脑会在语料库中,搜索『了』后面最可能的一个词。不管最后电脑选的是不是『我』,我们都知道这个模型是不靠谱的,因为『了』前面说了那么一大堆实际上是没有用到的。如果是3-Gram模型呢,会搜索『批评了』后面最可能的词,感觉上比2-Gram靠谱了不少,但还是远远不够的。因为这句话最关键的信息『我』,远在9个词之前!

​ 现在读者可能会想,可以提升继续提升N的值呀,比如4-Gram、5-Gram…….。实际上,这个想法是没有实用性的。因为我们想处理任意长度的句子,N设为多少都不合适;另外,模型的大小和N的关系是指数级的,4-Gram模型就会占用海量的存储空间。

所以,该轮到RNN出场了,RNN理论上可以往前看(往后看)任意多个词。

循环神经网络是啥

循环神经网络种类繁多,我们先从最简单的基本循环神经网络开始吧。

基本循环神经网络

下图是一个简单的循环神经网络如,它由输入层、一个隐藏层和一个输出层组成:

1

​ 纳尼?!相信第一次看到这个玩意的读者内心和我一样是崩溃的。因为循环神经网络实在是太难画出来了,网上所有大神们都不得不用了这种抽象艺术手法。不过,静下心来仔细看看的话,其实也是很好理解的。如果把上面有W的那个带箭头的圈去掉,它就变成了最普通的全连接神经网络。x是一个向量,它表示输入层的值(这里面没有画出来表示神经元节点的圆圈);s是一个向量,它表示隐藏层的值(这里隐藏层面画了一个节点,你也可以想象这一层其实是多个节点,节点数与向量s的维度相同);U是输入层到隐藏层的权重矩阵(读者可以回到第三篇文章零基础入门深度学习(3) - 神经网络和反向传播算法,看看我们是怎样用矩阵来表示全连接神经网络的计算的);o也是一个向量,它表示输出层的值;V是隐藏层到输出层的权重矩阵。那么,现在我们来看看W是什么。循环神经网络的隐藏层的值s不仅仅取决于当前这次的输入x,还取决于上一次隐藏层的值s。权重矩阵 W就是隐藏层上一次的值作为这一次的输入的权重。输出层是一个全连接层,它的每个节点都和隐藏层的每个节点相连,隐藏层是循环层。

如果我们把上面的图展开,循环神经网络也可以画成下面这个样子:

2

​

现在看上去就比较清楚了,这个网络在t时刻接收到输入$x_t$之后,隐藏层的值是$s_t$,输出值是$o_t$。关键一点是,$s_t$的值不仅仅取决于$x_t$,还取决于$s_{t-1}$。我们可以用下面的公式来表示循环神经网络的计算方法:
$$
o_t=g(Vs_t)…..(式1)……(1)\\
s_t=f(Ux_t+Ws_{t-1})…..(式2)……(2)
$$
​ 式1是输出层的计算公式,输出层是一个全连接层,也就是它的每个节点都和隐藏层的每个节点相连。V是输出层的权重矩阵,g是激活函数。式2是隐藏层的计算公式,它是循环层。U是输入x的权重矩阵,W是上一次的值作为这一次的输入的权重矩阵,f是激活函数。

从上面的公式我们可以看出,循环层和全连接层的区别就是循环层多了一个权重矩阵 W。

如果反复把式2带入到式1,我们将得到:
$$
\begin{align}
\mathrm{o}_t&=g(V\mathrm{s}_t) ……….(3)\\
&=Vf(U\mathrm{x}_t+W\mathrm{s}_{t-1})…….(4)\\
&=Vf(U\mathrm{x}_t+Wf(U\mathrm{x}_{t-1}+W\mathrm{s}_{t-2}))……….(5)\\
&=Vf(U\mathrm{x}_t+Wf(U\mathrm{x}_{t-1}+Wf(U\mathrm{x}_{t-2}+W\mathrm{s}_{t-3})))……….(6)\\
&=Vf(U\mathrm{x}_t+Wf(U\mathrm{x}_{t-1}+Wf(U\mathrm{x}_{t-2}+Wf(U\mathrm{x}_{t-3}+…))))……….(7)
\end{align}
$$
从上面可以看出,循环神经网络的输出值$o_t$,是受前面历次输入值$x_t、x_{t-1}、x_{t-2}、x_{t-3}$…影响的,这就是为什么循环神经网络可以往前看任意多个输入值的原因。

双向循环神经网络

​ 对于语言模型来说,很多时候光看前面的词是不够的,比如下面这句话:

我的手机坏了,我打算____一部新手机。

​ 可以想象,如果我们只看横线前面的词,手机坏了,那么我是打算修一修?换一部新的?还是大哭一场?这些都是无法确定的。但如果我们也看到了横线后面的词是『一部新手机』,那么,横线上的词填『买』的概率就大得多了。

在上一小节中的基本循环神经网络是无法对此进行建模的,因此,我们需要双向循环神经网络,如下图所示:

3

​ 当遇到这种从未来穿越回来的场景时,难免处于懵逼的状态。不过我们还是可以用屡试不爽的老办法:先分析一个特殊场景,然后再总结一般规律。我们先考虑上图中$y_2$的计算。

​ 从上图可以看出,双向卷积神经网络的隐藏层要保存两个值,一个A参与正向计算,另一个值A’参与反向计算。最终的输出值$y_2$取决于$A_2$和$A’_2$。其计算方法为:
$$
\mathrm{y}_2=g(VA_2+V’A_2’)
$$
$A_2$和$A’_2$则分别计算:
$$
\begin{align}
A_2&=f(WA_1+U\mathrm{x}_2)………(8)\\
A_2’&=f(W’A_3’+U’\mathrm{x}_2)…….(9)\\
\end{align}
$$
​ 现在,我们已经可以看出一般的规律:正向计算时,隐藏层的值$s_t$与$s_t-1$有关;反向计算时,隐藏层的值$s’_t$与$s’_{t-1}$有关;最终的输出取决于正向和反向计算的加和。现在,我们仿照式1和式2,写出双向循环神经网络的计算方法:
$$
\begin{align}
\mathrm{o}_t&=g(V\mathrm{s}_t+V’\mathrm{s}_t’)…….(10)\\
\mathrm{s}_t&=f(U\mathrm{x}_t+W\mathrm{s}_{t-1})…….(11)\\
\mathrm{s}_t’&=f(U’\mathrm{x}_t+W’\mathrm{s}_{t+1}’)……(12)\\
\end{align}
$$

​ 从上面三个公式我们可以看到,正向计算和反向计算不共享权重,也就是说U和U’、W和W’、V和V’都是不同的权重矩阵。

深度循环神经网络

前面我们介绍的循环神经网络只有一个隐藏层,我们当然也可以堆叠两个以上的隐藏层,这样就得到了深度循环神经网络。如下图所示:

4

我们把第i个隐藏层的值表示为$s_t^{(i)}$、$s_t^{‘(i)}$,则深度循环神经网络的计算方式可以表示为:
$$
\begin{align}
\mathrm{o}_t&=g(V^{(i)}\mathrm{s}_t^{(i)}+V’^{(i)}\mathrm{s}_t’^{(i)})…….(13)\\
\mathrm{s}_t^{(i)}&=f(U^{(i)}\mathrm{s}_t^{(i-1)}+W^{(i)}\mathrm{s}_{t-1})……….(14)\\
\mathrm{s}_t’^{(i)}&=f(U’^{(i)}\mathrm{s}_t’^{(i-1)}+W’^{(i)}\mathrm{s}_{t+1}’)……..(15)\\
…(16)\\
\mathrm{s}_t^{(1)}&=f(U^{(1)}\mathrm{x}_t+W^{(1)}\mathrm{s}_{t-1})……..(17)\\
\mathrm{s}_t’^{(1)}&=f(U’^{(1)}\mathrm{x}_t+W’^{(1)}\mathrm{s}_{t+1}’)………(18)\\
\end{align}
$$

循环神经网络的训练

循环神经网络的训练算法:BPTT

BPTT算法是针对循环层的训练算法,它的基本原理和BP算法是一样的,也包含同样的三个步骤:

  1. 前向计算每个神经元的输出值;
  2. 反向计算每个神经元的误差项$\delta_j$值,它是误差函数E对神经元j的加权输入$net_j$的偏导数;
  3. 计算每个权重的梯度。

最后再用随机梯度下降算法更新权重。

循环层如下图所示:

5

前向计算

使用前面的式2对循环层进行前向计算:
$$
\mathrm{s}_t=f(U\mathrm{x}_t+W\mathrm{s}_{t-1})
$$
注意,上面的$s_t$、$x_t$、$s_{t-1}$都是向量,用黑体字母表示;而U、V是矩阵,用大写字母表示。向量的下标表示时刻,例如,表示在t时刻向量s的值。

​ 我们假设输入向量x的维度是m,输出向量s的维度是n,则矩阵U的维度是$n m$,矩阵W的维度是$nn$。下面是上式展开成矩阵的样子,看起来更直观一些:
$$
\begin{align}
\begin{bmatrix}
s_1^t\\
s_2^t\\
.\.\\
s_n^t\\
\end{bmatrix}=f(
\begin{bmatrix}
u_{11} u_{12} … u_{1m}\\
u_{21} u_{22} … u_{2m}\\
.\.\\
u_{n1} u_{n2} … u_{nm}\\
\end{bmatrix}
\begin{bmatrix}
x_1\\
x_2\\
.\.\\
x_m\\
\end{bmatrix}+
\begin{bmatrix}
w_{11} w_{12} … w_{1n}\\
w_{21} w_{22} … w_{2n}\\
.\.\\
w_{n1} w_{n2} … w_{nn}\\
\end{bmatrix}
\begin{bmatrix}
s_1^{t-1}\\
s_2^{t-1}\\
.\.\\
s_n^{t-1}\\
\end{bmatrix})…..(19)
\end{align}
$$
​ 在这里我们用手写体字母表示向量的一个元素,它的下标表示它是这个向量的第几个元素,它的上标表示第几个时刻。例如,$s_t^{j}$表示向量s的第j个元素在t时刻的值。$u_{ji}$表示输入层第i个神经元到循环层第j个神经元的权重。$w_{ji}$表示循环层第t-1时刻的第i个神经元到循环层第t个时刻的第j个神经元的权重。

误差项的计算

​ BTPP算法将第l层t时刻的误差项$\delta_t^l $值沿两个方向传播,一个方向是其传递到上一层网络,得到,这部分只和权重矩阵U有关;另一个是方向是将其沿时间线传递到初始时刻,得到,这部分只和权重矩阵W有关。

我们用向量表示神经元在t时刻的加权输入,因为:

6

其中,$X_t$为输入,A为模型处理部分,$h_t$为输出。

为了更容易地说明递归神经网络,我们把上图展开,得到:

7

​ 这样的一条链状神经网络代表了一个递归神经网络,可以认为它是对相同神经网络的多重复制,每一时刻的神经网络会传递信息给下一时刻。如何理解它呢?假设有这样一个语言模型,我们要根据句子中已出现的词预测当前词是什么,递归神经网络的工作原理如下:

8

其中,W为各类权重,x表示输入,y表示输出,h表示隐层处理状态。

递归神经网络因为具有一定的记忆功能,可以被用来解决很多问题,例如:语音识别、语言模型、机器翻译等。但是它并不能很好地处理长时依赖问题。

长时依赖问题

​ RNN 的关键点之一就是他们可以用来连接先前的信息到当前的任务上,例如使用过去的视频段来推测对当前段的理解。如果 RNN 可以做到这个,他们就变得非常有用。但是真的可以么?答案是,还有很多依赖因素。

​ 有时候,我们仅仅需要知道先前的信息来执行当前的任务。例如,我们有一个语言模型用来基于先前的词来预测下一个词。如果我们试着预测 “the clouds are in the sky” 最后的词,我们并不需要任何其他的上下文 —— 因此下一个词很显然就应该是 sky。在这样的场景中,相关的信息和预测的词位置之间的间隔是非常小的,RNN 可以学会使用先前的信息。

9

​ 不太长的相关信息和位置间隔

​ 但是同样会有一些更加复杂的场景。假设我们试着去预测“I grew up in France… I speak fluent French”最后的词。当前的信息建议下一个词可能是一种语言的名字,但是如果我们需要弄清楚是什么语言,我们是需要先前提到的离当前位置很远的 France 的上下文的。这说明相关信息和当前预测位置之间的间隔就肯定变得相当的大。

​ 不幸的是,在这个间隔不断增大时,RNN 会丧失学习到连接如此远的信息的能力。

10

​ 在理论上,RNN 绝对可以处理这样的 长期依赖 问题。人们可以仔细挑选参数来解决这类问题中的最初级形式,但在实践中,RNN 肯定不能够成功学习到这些知识。Bengio, et al. (1994)等人对该问题进行了深入的研究,他们发现一些使训练 RNN 变得非常困难的相当根本的原因。

​ 隐含内容和输出结果是相同的内容。

​ TensorFlow 实现 RNN Cell 的位置在 python/ops/rnn_cell_impl.py,首先其实现了一个 RNNCell 类,继承了 Layer 类,其内部有三个比较重要的方法,state_size()、output_size()、call() 方法,其中 state_size() 和 output_size() 方法设置为类属性,可以当做属性来调用,实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
@property
def state_size(self):
"""size(s) of state(s) used by this cell.
It can be represented by an Integer, a TensorShape or a tuple of Integers
or TensorShapes.
"""
raise NotImplementedError("Abstract method")

@property
def output_size(self):
"""Integer or TensorShape: size of outputs produced by this cell."""
raise NotImplementedError("Abstract method")

分别代表 Cell 的状态和输出维度,和 Cell 中的神经元数量有关,但这里两个方法都没有实现,意思是说我们必须要实现一个子类继承 RNNCell 类并实现这两个方法。

另外对于 call() 方法,实际上就是当初始化的对象直接被调用的时候触发的方法,实现如下:

1
2
3
4
5
6
7
8
9
def __call__(self, inputs, state, scope=None):
if scope is not None:
with vs.variable_scope(scope,
custom_getter=self._rnn_get_variable) as scope:
return super(RNNCell, self).__call__(inputs, state, scope=scope)
else:
with vs.variable_scope(vs.get_variable_scope(),
custom_getter=self._rnn_get_variable):
return super(RNNCell, self).__call__(inputs, state)

​ 实际上是调用了父类 Layer 的 call() 方法,但父类中 call() 方法中又调用了 call() 方法,而 Layer 类的 call() 方法的实现如下:

1
2
def call(self, inputs, **kwargs):
return inputs

父类的 call() 方法实现非常简单,所以要实现其真正的功能,只需要在继承 RNNCell 类的子类中实现 call() 方法即可。

接下来我们看下 RNN Cell 的最基本的实现,叫做 BasicRNNCell,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class BasicRNNCell(RNNCell):
"""The most basic RNN cell.
Args:
num_units: int, The number of units in the RNN cell.
activation: Nonlinearity to use. Default: `tanh`.
reuse: (optional) Python boolean describing whether to reuse variables
in an existing scope. If not `True`, and the existing scope already has
the given variables, an error is raised.
"""

def __init__(self, num_units, activation=None, reuse=None):
super(BasicRNNCell, self).__init__(_reuse=reuse)
self._num_units = num_units
self._activation = activation or math_ops.tanh
self._linear = None

@property
def state_size(self):
return self._num_units

@property
def output_size(self):
return self._num_units

def call(self, inputs, state):
"""Most basic RNN: output = new_state = act(W * input + U * state + B)."""
if self._linear is None:
self._linear = _Linear([inputs, state], self._num_units, True)

output = self._activation(self._linear([inputs, state]))
return output, output

可以看到在初始化的时候,最重要的一个参数是 num_units,意思就是这个 Cell 中神经元的个数,另外还有一个参数 activation 即默认使用的激活函数,默认使用的 tanh,reuse 代表该 Cell 是否可以被重新使用。

在 state_size()、output_size() 方法里,其返回的内容都是 num_units,即神经元的个数,接下来 call() 方法中,传入的参数为 inputs 和 state,即输入的 x 和 上一次的隐含状态,首先实例化了一个 _Linear 类,这个类实际上就是做线性变换的类,将二者传递过来,然后直接调用,就实现了 w * [inputs, state] + b 的线性变换,其中 _Linear 类的 call() 方法实现如下:

1
2
3
4
5
6
7
8
9
10
def __call__(self, args):
if not self._is_sequence:
args = [args]
if len(args) == 1:
res = math_ops.matmul(args[0], self._weights)
else:
res = math_ops.matmul(array_ops.concat(args, 1), self._weights)
if self._build_bias:
res = nn_ops.bias_add(res, self._biases)
return res

​ 很明显这里传递了 [inputs, state] 作为 call() 方法的 args,会执行 concat() 和 matmul() 方法,然后接着再执行 bias_add() 方法,这样就实现了线性变换。

​ 最后回到 BasicRNNCell 的 call() 方法中,在 _linear() 方法外面又包括了一层 _activation() 方法,即对线性变换应用一次 tanh 激活函数处理,作为输出结果。

​ 最后返回的结果是 output 和 output,第一个代表 output,第二个代表隐状态,其值也等于 output。

tf7

发表于 2018-04-07 |

过拟合

解决办法:

​ 方法一: 增加数据量, 大部分过拟合产生的原因是因为数据量太少了. 如果我们有成千上万的数据, 红线也会慢慢被拉直, 变得没那么扭曲 .

​ 方法二:运用正规化. L1, l2 regularization等等, 这些方法适用于大多数的机器学习, 包括神经网络. 他们的做法大同小异, 我们简化机器学习的关键公式为 y=Wx 。 W为机器需要学习到的各种参数。在过拟合中, W 的值往往变化得特别大或特别小。为了不让W变化太大, 我们在计算误差上做些手脚. 原始的 cost 误差是这样计算, cost = 预测值-真实值的平方. 如果 W 变得太大, 我们就让 cost 也跟着变大, 变成一种惩罚机制. 所以我们把 W 自己考虑进来. 这里 abs 是绝对值. 这一种形式的正规化, 叫做 l1 正规化。L2 正规化和 l1 类似, 只是绝对值换成了平方. 其他的l3, l4 也都是换成了立方和4次方等等. 形式类似. 用这些方法,我们就能保证让学出来的线条不会过于扭曲.
$$
l1,l2…regularization\\
y=Wx\\
L1:cost=(Wx-real \, y)^2+abs(W)\\
L2:cost=(Wx-real \, y)^2+(W)^2
$$
​ 还有一种专门用在神经网络的正规化的方法, 叫作 dropout。在训练的时候, 我们随机忽略掉一些神经元和神经联结 , 是这个神经网络变得”不完整”. 用一个不完整的神经网络训练一次。到第二次再随机忽略另一些, 变成另一个不完整的神经网络。有了这些随机 drop 掉的规则, 我们可以想象其实每次训练的时候, 我们都让每一次预测结果都不会依赖于其中某部分特定的神经元. 像l1, l2正规化一样, 过度依赖的 W , 也就是训练参数的数值会很大, l1, l2会惩罚这些大的 参数。Dropout 的做法是从根本上让神经网络没机会过度依赖。

​ 举个Regression (回归)的例子。

1

​ 第三条曲线存在overfitting问题,尽管它经过了所有的训练点,但是不能很好的反应数据的趋势,预测能力严重不足。 TensorFlow提供了强大的dropout方法来解决overfitting问题。

建立 dropout 层

tensorboard可视化

发表于 2018-04-06 | 分类于 tensorflow |

​         为了更方便 TensorFlow 程序的理解、调试与优化,有一套叫做 TensorBoard 的可视化工具。你可以用 TensorBoard 来展现你的 TensorFlow 图像,绘制图像生成的定量指标图以及附加数据。TensorBoard 通过读取 TensorFlow 的事件文件来运行。TensorFlow 的事件文件包括了你会在 TensorFlow 运行中涉及到的主要数据。。显示的神经网络差不多是这样的:

阅读全文 »

tensorflow增加层和数据可视化

发表于 2018-04-06 | 分类于 tensorflow |

添加层

1
2
3
4
5
6
7
8
9
10
11
12
import tensorflow as tf

def add_layer(inputs,insize,outsize,activation_function=None)
Weights = tf.Variable(tf.random_normal([insize,outsize]))
biases = tf.Variable(tf.zeros([1,outsize]) + 0.1)
Wx_plus_b = tf.matmul(inputs,Weights) + biases
if activation_function is None:
outputs = Wx_plus_b
else:
outputs = activation_function(Wx_plus_b)

return outputs
阅读全文 »

tf.placeholder 与 tf.Variable区别

发表于 2018-04-06 | 分类于 tensorflow |

二者的主要区别在于:

tf.Variable:主要在于一些可训练变量(trainable variables),比如模型的权重(weights,W)或者偏置值(bias);

声明时,必须提供初始值;**

名称的真实含义,在于变量,也即在真实训练时,其值是会改变的,自然事先需要指定初始值;

1
2
weights = tf.Variable( tf.truncated_normal([IMAGE_PIXELS, 		                               hidden1_units],stddev=1./math.sqrt(float(IMAGE_PIXELS)), name='weights'))
biases = tf.Variable(tf.zeros([hidden1_units]), name='biases')
阅读全文 »

tensorflow-变量

发表于 2018-04-04 | 分类于 tensorflow |

        训练模型时,需要使用变量(Variables)保存和更新参数。Variables是包含张量(tensor)的内存缓冲。变量必须要先被初始化(initialize),而且可以在训练时和训练后保存(save)到磁盘中。之后可以再恢复(restore)保存的变量值来训练和测试模型。

1.创建(Creation)

        创建Variable,需将一个tensor传递给Variable()构造函数。可以使用TensorFlow提供的许多ops(操作)初始化张量,参考constants or random values。这些ops都要求指定tensor的shape(形状)。比如

Create two variables.

1
2
3
4
> weights = tf.Variable(tf.random_normal([784, 200], stddev=0.35), 
> name=”weights”)
> biases = tf.Variable(tf.zeros([200]), name=”biases”)
>

>

 

阅读全文 »

tensorflow-会话(session)

发表于 2018-04-04 | 分类于 tensorflow |

        Tensorflow中的会话是来执行定义好的运算的。会话拥有并管理Tensorflow程序运行时的所有资源。当计算完成之后需要关闭会话来帮助系统回收资源,否则可能出现资源泄露的问题。 Tensorflow中使用会话的模式一般有两种,第一种模式需要明确调用会话生成函数和关闭会话函数,流程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import tensorflow as tf

matrix1 = tf.constant([[3,3]])
matrix2 = tf.constant([[2],
[2]])

product = tf.matmul(matrix1, matrix2)

# method 1
sess = tf.Session()
result = sess.run(product)
print(result)
sess.close()
# 输出:[[12]]

​

阅读全文 »
1234
goingcoder

goingcoder

匆忙世间的闲人。

38 日志
8 分类
9 标签
RSS
GitHub
Creative Commons
Links
  • 陈冠希
© 2018 goingcoder
本站访客数: