Note: This only works when running the code as a standalone Java application, like you do with unit tests and regular applications. if you try to do this inside a Java Applet, you will need to fiddle around with
SecurityManager
.
To access a private field you will need to call Class.getDeclaredFiled(String name)
method
The methods Class.getField(String name)
only return public fields.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
PS:
by calling Field.setAccessible(true)
just turn off the access checks for this particular Field instance, for reflection only. Now you can access it even if it is private/protected or package scope. even if the caller is not part of those scopes
But you still can’t access the field using normal code which would be disallowed by compiler.
To access a private method you will need to call the Class.getDeclaredMethod(String name, Class[] parameterTypes)
The methods Class.getMethod(String name, Class[] parameterTypes)
only return public methods
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 |
|
Some notes about Java dev tools and debuggers
build lifecycle targets
-Dmaven.test.skip=true -Dhttps.proxyHost=127.0.0.1 -Dhttps.proxyPort=1081 -Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=1081
Java REPL(Read Eval Print Loop) import after Java 9
javap TestDecompile.class - decompile .class file to human readable format. Does not show content of methods though. javap -c TestDecompile.class - show jvm bytecode in human readable -form, including methods.
These are zip files that have a META-INF folder with a Manifest.mf file inside.
<?xml version="1.0" encoding="UTF-8"?>
<build>
<finalName>indexer-spider</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.4.1</version>
<configuration>
<!-- get all project dependencies -->
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<!-- MainClass in mainfest make a executable jar -->
<archive>
<manifest>
<mainClass>org.apache.maven.indexer.examples.BasicUsageExample</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<!-- bind to the packaging phase -->
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
-Xagentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=<port>
首先,在编写Rails应用时,代码会预加载:通过约定类定义所在的文件名与类名一致映射,实现自动加载
Rails 通过config.cache_classes参数来设置常见加载的模式,主要有以下两种形式:
除了加载的方式不同,在config.cache_classes = false
时,Rails还会启用Reloader中间件
在代码发生变更时,通过remove_constant
/const_missing
等方法实现么常量、模块热替换
1 2 3 4 |
|
下面,本文逐步解析下Ruby及Rails下的常量加载机制
Ruby中常见的常量:
其中,既然module和class在Ruby中本质就是常量的话,类和模块定义的嵌套创建的命名空间也是常量了
Ruby 的常量嵌套从内向外展开,嵌套通过Module.nesting方法审查
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 |
|
从上面的例子看出,嵌套中的类和模块的名称与所在的命名空间没有必然联系
嵌套是解释器维护的一个内部堆栈,根据下述规则修改:
1. 执行 class 关键字后面的定义体时,类对象入栈;执行完毕后出栈。
2. 执行 module 关键字后面的定义体时,模块对象入栈;执行完毕后出栈。
3. 执行 class << object 打开的单例类时,类对象入栈;执行完毕后出栈。
4. 调用 instance_eval
时如果传入字符串参数,接收者的单例类入栈求值的代码所在的嵌套层次。
5. 调用 class_eval
或 module_eval
时如果传入字符串参数,接收者入栈求值的代码所在的嵌套层次.
6. 顶层代码中由 Kernel#load 解释嵌套是空的,除非调用 load 时把第二个参数设为真值;如果是这样,Ruby 会创建一个匿名模块,将其入栈。
定义类和模块的本质是为常量赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
常量赋值的一条特殊规则:如果被赋值的对象是匿名类或模块,Ruby 会把对象的名称设为常量的名称。
自此之后常量和实例发生的事情无关紧要。例如,可以把常量删除,类对象可以赋值给其他常量,或者不再存储于常量中,等等。名称一旦设定就不会再变。
当常量存储在模块中,常量就会和类和模块中的常量表关联映射(类似哈希表)
1 2 3 |
|
解析模块定义体时,会在Colors常量中的常量表新建一条映射,把"RED"映射到字符串"0xff0000"
相对常量、绝对常量、限定常量
1 2 |
|
相对常量解析: 在代码中的特定位置,假如使用 cref 表示嵌套中的第一个元素,如果没有嵌套,则表示 Object。
限定常量解析: 上面例子 Invoice 由 Billing 限定,解析算法如下
但Rails 的自动加载机制没有仿照这个算法,查找的起点是要自动加载的常量名称和限定的类或模块对象
如果缺失限定常量,Rails 不会在父级命名空间中查找。
但是有一点要留意:缺失常量时,Rails 不知道它是相对引用还是限定引用。
如果类或模块的父级命名空间中没有缺失的常量,Rails 假定引用的是相对常量。否则是限定常量。
还有在Rails开发环境中,常量时惰性加载的。遇到不存在的常量再触发const_missing
使用Rails的自动加载机制
但在生产环境中,预先把所有 autoload 目录下的文件都加载过了。没有触发const_missing
使用Ruby本身的常量查找
config.cache_classes 设为 false 时,Rails 会重新自动加载常量
在应用运行的过程中,如果相关的逻辑有变,会重新加载代码。为此,Rails 会监控下述文件:
如果这些文件中的内容有变,有个中间件会发现,然后重新加载代码。
主要原理:
1. 先覆写 const_missing
方法,按需去load对应依赖
2. 监听文件变化,自动加载机制会记录自动加载的常量
3. 检测到发生变更,重新加载机制使用 Module#remove_const 方法把它们从相应的类和模块中删除
4. 这样,运行代码时那些常量就变成未知了,从而按需重新加载文件。
但是,因为类之间的依赖极难处理。Rails默认reloader模块经常比较极端,不止重新加载有变化的代码,而是重载一切
Module#autoload 是Ruby 提供的惰性常量加载机制,可以遍历应用树调用autoload把文件名和常规的常量名对应起来
但是,Module#autoload 只能使用 require 加载文件,因此无法重新加载。
不仅如此,它使用的还仅是 require 关键字,而不是 Kernel#require 方法。
因此,删除文件后,它无法移除声明。如果使用 Module#remove_const 把常量删除了,不会触发 Module#autoload
综上,在Rails的常量自动加载机制中使用了覆写Module#const_missing 的方式来实现
Rails(ActiveSupport) 中的会根据触发 const_missing 的常量名称来猜测并尝试加载对应的文件, 以加载 Auth::User 为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Demo::Role 找不到 User 常量, 触发 const_missing(const_name)
, 此处 const_name == 'User'
ActiveSupport 中先拼接出来一个查询的起点 “#{Demo::Role.name}::#{const_name}”, 即 Demo::Role::User
首先尝试查找 autoload_paths 下的 demo/role/user.rb, 没找到
然后往上走一层, 尝试查找 autoload_paths 下的 demo/user.rb, 找到了
]]>Magit 是 Emacs 下对git的封装,利用Magit可以让你在Emacs中即可完成对git仓库的管理(Emacs果然是一个伪装成编辑器的操作系统)
其次,Magit本不是Emacs的内置插件,使用时需要自己安装;具体的安装方法在Magit的官网上已经有相关教程了,这里我就不再赘述了
下面主要是列举一些日常开发和git管理中比较常使用的一些功能:
该命令就类似于git status(查看项目的当前状态);但是,在Magit中显示的状态信息会比git status更加丰富 其中包括:HEAD、Tag、未追踪文件、Stash列表、未staged文件、未push文件、最近commit等信息 然后,在相应的条目上回车还可以进行看到更加详细的内容,包括对应文件的修改、具体的commit信息等等
在magit status buffer中键入?可以提示Magit的功能列表以及其相对应的key bindings,新手通过这样一个帮助列表,就可以找到对应的git功能一一操作试试,一段时间后就可以熟悉整个magit的操作了
Stage命令就类似于git add操作,在你修改了相关的git管理下的文件后,若是还未运行git add时,该文件处于Unstaged的状态 处于Unstaged状态的文件在git commit的时候其变更内容就不会提交;而运行git add [filename] 之后文件就变成Stage状态了, 此时如果再执行git commit,对应的文件变更内容则提交到本地,然后文件状态变更未Unpushed
Unstage命令就是Stage命令的反向操作,其对应git reset HEAD [filename],在Magit中Stage/Unstage不仅能够作用于单个文件、所有的changes,还能作用于某个文件的部分区域上;在magit展开文件的diff时,你还发现在文件差异中用@@符号区分的差异区域,在对应的区域内键入Stage/Unstage命令即可仅仅存在该区域中的变更,然后在commit提交时,也可以单单提交这一部分变更
在magit-status-buffer中键入c键即为最常用的git commit指令,当然后除了最普通的Commit(cc)操作外 Magit还支持许多commit的扩展命令
key bindings | command | description |
---|---|---|
cc | Commit | 最普通的 git commit |
ce | Extend | 当前Staged的文件合并到上一次提交中 git commit –amend –no-edit |
ca | Amend | 只修改上次提交的日志 git commit –amend |
Pull命令对应git pull在git管理中用于拉取远程仓库代码,常用的组合命令有以下:
key bindings | description |
---|---|
Fu | pull from upstream |
Fp | pull from pushremote |
Fe | pull from elsewhere 会引导你从哪pull |
对于Fu 和 Fp来说,upstream是pushremote的上级,这样的场景对应fork分支开发的工作流; 比如User A 有个仓库 Project,User B fork了Project,这样对于User B来说 UserA/Project就是upstream,而pushremote是UserB/Project 另外,在Magit中只有设置了pushremote分支,这样magit status buffer才会显示有哪些变更没有push和pull
对应git push 命令
key bindings | command | description |
---|---|---|
pu | push to upstream | 最普通的git push |
pe | push to elsewhere | 会引导你push到哪个远程分支 |
po | push another branch to | 会引导你push到哪个分支 |
pT | push a tag | push 一个tag标签 |
pt | push all tag | push 所有tag标签 |
除此之外,上面的key bindings组合还可以添加对应的扩展参数,比如强制push,即p-Fu
对应git log,查看git日志记录
key bindings | description |
---|---|
ll | 查看当前分支的日志 |
lo | log other 查看其他分支的日志 |
在具体的commit上使用 l 键还可以根据给出的命令组合进一步查看commit提交的详情信息
对应git cherry-pick,选择某一次的commit在当前分支重新commit一次,适用于合并代码但又不想merge整个PR和分支的场景
对应git Stash,将临时的未commit的变更内容暂存起来 常用命令有:
key bindings | command | description |
---|---|---|
zz | git stash | 暂存 |
zp | git stash pop | 恢复 |
除此之外,还有一个有意思的用法是,当你希望单单想stash变更文件列表中的一个文件时,可以先将目标文件Stage在index索引区,然后适用 zi 组合暂存index区域,这样就可以实现单一文件的stash功能
对应git中的checkout之类的命令,用作于放弃更改和删除相关的操作,例如,放弃一个Unstage文件的更改、删除一个Stash、删除一个@@区域差异等等
类似git reset命令,放弃最近的n次提交,这n次的提交内容变成staged状态,之后可以进行合并提交或者丢弃 只需要在日志日光标定位到想要丢弃的log,即可回滚到这一次的提交状态
对应git中合并分支的操作,常用的组合命令为 mm ,之后会提示选择与哪个分支进行merge
对应git中的变基操作
key bindings | description |
---|---|
ru | rebase on upstream |
rp | rebase on pushremote |
re | 会提示你以哪个分支为基点进行rebase |
1 2 3 4 5 6 7 8 |
|
ActiveSupport::Concern简单定义了一个内部的错误类型, 这个自定义错误类型主要用于提醒我们在扩展了ActiveSupport::Concern的模块中
只能够显式调用模块方法ActiveSupport::Concern::included一次,
第二次调用的话就会抛出这个自定义的错误类型。
我们前面说过扩展了ActiveSupport::Concern的模块我把它简称为依赖模块,那我们怎么知道一个模块是不是依赖模块呢?答案就在这个类方法。
1 2 3 4 5 6 |
|
写过Ruby的应该都知道,这个是当一个模块被扩展(extend)之后就会被调用的一个回调方法,
并以扩展它的模块做为参数(base)传入该回调方法。
当一个模块扩展了ActiveSupport::Concern之后就会在模块内部设置一个实例变量
@_dependencies并设置为空数组,我们姑且把它看做是ActiveSupport::Concern的“胎记”,
后面我们会利用这个“胎记”所包含的依赖项,优雅地增强我们的终端模块。
在分析后面的方法之前我们先来简单看一下的原理。
我们都知道在Ruby里面如何定义一个类,并且定义相关的类方法,和实例方法
(我们只需要用class关键字就能够很容易做到这一点),但不知道大家是否了解还有这样一种方式?
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 |
|
运行结果
I am class method I am class method in instance block I am instance method
运行结果有点意思,下面简单来分析一下: 我们以代码块的方式收集了类方法class_method,class_method_in_instance_block,
以及实例方法instance_method。然后我们创建一个空的类Example,
用Class#class_eval方法来打开类,在类的上下文环境下运行块instance_block,
其实这就相当于我们在类上下文中运行相应的语句,这样就能够得到Example#instance_method,
Example::class_method_in_instance_block这两个方法了。 另外,我们从已有的知识中了解到,可以通过Class#extend扩展模块的方式来获得类方法。
为此我们可以把class_block这个代码块包裹在模块ClassModule中,
最后我们只需要扩展(extend)这个模块就可以得到相应的类方法Example#class_method了。
ActiveSupport::Concern其实就是这种黑科技,
这里我简单把接下来的过程分为两部分
1)收集方法。
2)对收集的方法进行功能扩展
1 2 3 4 5 6 7 8 9 10 11 12 |
|
首先我们来看ActiveSupport::Concern#included,
当ActiveSupport::Concern被扩展之后这个included方法就会变成相应模块的类方法了。
咦,这不是一个模块被包含之后才会被调用的回调函数吗?没错,为了不影响它原来的功能rails团队采用了个巧妙的做法,
当我们在扩展了ActiveSupport::Concern的模块的上下文中显式调用included方法,
并且不带任何参数而只传入代码块的情况下,便会在模块内部设置一个@_included_block实例变量来接收这个代码块,
换句话说这个实例变量就是我们将来需要在终端模块上下文运行的代码块。
而在其他情况下则通过super关键字来调用原始版本的included方法。
这样既增强了included方法又不影响原始方法的使用。
另外我们也注意到了,在一个模块里面included方法只能被显式调用一次,
否则会抛出MultipleIncludedBlocks这个自定义的错误,这便是前面定义的内部类的应用场景。
OK,收集完需要在模块上下文运行的代码,我们接下来要收集类方法了。
1 2 3 4 5 6 7 8 9 10 |
|
其实这个东西跟前面的included方法原理差不多,也是通过代码块来收集方法,只是有一点点不同,
我们需要把这个收集到的代码块包裹到一个模块中,以后再扩展这个模块。
首先通过const_defined?来判断常量ClassMethods是否存在,
该方法的第二个参数false表示只从当前模块查找该常量,而不会从祖先链中去查找。
如果没有则以ClassMethods为名定义一个模块,
然后以Module#module_eval方法打开该模块并在模块的上下文运行我们所接收的代码块class_methods_module_definition。
这样模块ClassMethods就会包含对应的方法了。
在以后的日子里我们只需要扩展ClassMethods,该模块里面的方法就能成为目标模块的类方法了。
先来认识一下append_features,我们需要知道的是它会在模块被包含的时候调用,并且它会在included回调方法之前被调用。
接下来我们看看扩展功能的源代码,它应该是ActiveSupport::Concern里面最复杂的方法了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
现在我们可以看到之前设置“胎记”起作用了,设置了“胎记”的依赖模块也可以被其他的依赖模块所包含,
但他们并不会进行相应的功能扩展,他们会做的只是在“胎记”列表@_dependencies里面添加对应的依赖选项。
然后返回false。这也是我们第一个条件分支的逻辑。 当我们的依赖模块终于被终端模块(不含“胎记”的模块)包含的时候我们程序便可以走else分支的逻辑。
如果该依赖模块已经在终端模块的祖先链中的话则表明这个模块已经在终端模块增强过了,我们就没必要做重复工作,直接返回false。
否则的话继续执行后面的代码,接下来这个语句有点意思 @_dependencies.each { |dep| base.include(dep) }
我们会以终端模块的身份去包含当前依赖模块的@_dependencies列表里面的所有模块,
这个时候我们@_dependencies的模块又会进入各自相应的append_features方法,并且都会走else分支,
然后查看各自@_dependencies接下来又会以终端模块的身份再去包含列表里的那些模块,
以此类推。这递归的过程就像是链式反应,这样就能保证不管各个依赖模块之间的依赖关系如何,
我们的终端模块都不用太过在意了,反正最后都会被我们终端模块给包含掉。
接下来调用super关键字来调用原始的append_features方法,保证了原始的功能。
毕竟我们这里做的是对原始功能加强,而并不是要复写掉原始功能。
最后我们在每一个依赖模块内部都会执行大家所熟悉的语句 base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods) base.class_eval(&@included_block) if instance_variable_defined?(:@included_block)
最最最后,咱们的终端模块就会具备我们预先定义好的类方法,实例方法。
并且可以直接运行一些预先定义好需要在模块上下文运行的类方法了。
源码分析暂时告一段落,为了让我们对ActiveSupport::Concern印象更加深刻一些。最后我用一个简单的例子来展示一下这个库的优雅之处
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
|
最后的打印结果是
“[]” “[A]” “[B]” active3 class method active2 in C active1 class method number3 method number2 in C number1 method
利用ActiveSupport::Concern我们可以用少量的代码,优雅地定义我们的扩展模块,并在需要的时候进行功能增强。
在该例子中我通过打印“胎记”可以知道依赖模块之间的依赖关系,
而且他们之间的依赖关系终端模块根本不会在意,它只需要包含一个依赖模块,
便可以得到有依赖关系其他依赖模块中所定义的功能了。
另外,我在模块C中做了些手脚,复写了它所依赖的模块B中所收集的方法,
是为了展示我们所收集方法在各个有依赖关系模块中的优先级关系。
]]>文本并不像其他数值型的数据一样可以比较轻易的通过运算、函数、方程、矩阵等来表达他们之间的相互关系。
在处理一篇文本的时候,假设每一个文本储存为一篇文档,那么从人的视角来看,这可以说是一段有序的词序列
统计学家将这些序列的生成,生动地描绘成了一个“上帝的游戏”,即人类产生的所有语料的文本都可以看作是:一个伟大的上帝在天堂中掷骰子形成的。我们所看到的文本其实就是这个游戏掷了若干次后产生的序列。
那么,在这个游戏中,我们最需要关注的两个核心问题就出现了:
对应这两个问题,各大学家持着不同的观点,于是便有了以下三种模型:
Unigram Model是非常简单直接的,它假设
那现在我们把上帝这个唯一的骰子各个面的概率记为,然后我们把掷这个V面骰子的实验记作
那么对应一篇文档d,该文档生成的概率就是
而文档和文档之间我们认为是独立的,所以如果语料中有多篇文档,则该语料生成的概率就是
在Unigram Model中,我们又假设了文档之间是独立可交换的。即,词与词之间的顺序对文档表示并不造成影响,一篇文档相当于一个袋子,里面装着一些词。这样的模型也称为词袋模型(Bag-of-words)。
那么,如果语料中的总词频是N,在N个词中,如果我们关注每一个词的发生次数,则正好是一个多项分布
此时,语料的概率是
在贝叶斯统计学派看来,上帝拥有唯一一个固定的骰子是不合理的。于是他们觉得以上模型的骰子不应该唯一固定,而应该也是一个随机变量。
那这样我们就可以将整个掷骰子的游戏过程更新成以下形式:
在以上的假设之下,由于我们事先并不知道上帝用了哪个骰子,所以每个骰子都是有可能被使用的,只是使用的概率由先验分布决定,对每一个具体的骰子,由该骰子产生数据的概率是,所以最终数据产生的概率就是对每个骰子上产生的数据概率进行积分累加求和
在贝叶斯分析的框架之下,此处的先验分布概率其实就是一个多项分布的概率,其中一个比较好的选择即多项分布对应的共轭分布:Dirichlet分布
再来回顾Unigram Model我们发现:这个模型的假设过于简单,和人类真实的书写有着较大的差距
从人类视角看,我们在日常构思文章中,我们往往要先确定自己文章的主旨,包含了哪些主题,然后再围绕着这些主题展开阐述。
例如,一篇关于现代教育的文章,它可能就包含了这些主题:教育方法、多媒体技术、互联网等。篇幅上可能以教育方法为主,而其他为辅。然后,在不同的主题里面就包含了许多主题领域内常见的关键词。例如,谈到互联网时,我们会提及Web、Tcp等。
这样一种直观的想法就在PLSA模型中进行了明确的体现,如果我们再利用这个想法更新“掷骰子”的游戏就有以下情形:
在上面的游戏规则中,文档与文档之间顺序无关,同一个文档内的词的顺序也是无关的。所以还是一个bag-of-words模型。
那么,在第m篇文档Dm中每个词的生成概率为:
:对应游戏中K个topic-word骰子中第z个骰子对应的词列表
:文档对应的第z个主题,即对应的doc-topic
所以整篇文档的生成概率就为:
就像Unigram Model 加入贝叶斯框架那样,doc-topic和topic-word都是模型的参数,即随机变量。于是类似对Unigram Model的改造对以上两个骰子模型加入先验分布。然后由于,都对应着多项分布,因此先验分布依旧选择Drichlet分布。于是得到的这个新模型就是LDA(Latent Dirichlet Allocation)模型
在LDA模型中,上帝的游戏规则就又被更新成如下情形:
至此,入门篇(一)的内容就写到这了。由于是第一篇,文章中避过了许多较为复杂的数学证明和计算,尤其是关于LDA模型的。这样是为了,先建立对文本建模思路和过程的直观认识,而不是一上来就深究细节。
加上笔者目前也是在学习阶段,后面的细节再慢慢地一一补充,大家共同进步 !!
\(^_^)/
有一个由字符组成的等式,WWWDOT-GOOGLE = DOTCOM,每个字符代表一个0-9之间的数字,WWWDOT、GOOGLE和DOTCOM都是合法的数字,不能以零开头。请找出一组字符与数字的对应关系,使得他们可以互相转换,并且替换之后能够满足等式。
从排列组合的角度来看,这道题是一道典型的排列组合问题,题目中一共出现了9个字母。
如果不考虑0开头的情况下,这样的组合应该有10x9x8x7x6x5x4x3x2=3628800种组合。
在这样的情况之下,计算机的穷举处理应该是毫无压力的。
首先为了能够表示这样一种可变的字符元素列表,我们需要自定义一种数据结构。首先我们知道这个自定义结构中应该包含有三个属性,分别是 字符本身,字符代表的数字以及是否为数字的最高位,因为最高位是不能为0的,所以在这里我们要区别对待。
1 2 3 4 5 6 |
|
接着我们初始化这个列表。=
1 2 3 4 5 |
|
因为这是一个组合问题,那么两个字母就不能被指定为相同的数字,这样我们需要定义额外的占用标识。
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 |
|
根据题目要求,W,G,D三者都不能为0,为了加快穷举速度可以对他们为0的情况进行剪枝。
IsValueValid是评估函数,在剪枝操作之后,callback的被调用次数可以减少约30%。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
]]>问题答案
777589 - 188103 = 589486
777589 - 188106 = 589483
隐马尔可夫模型是一个基于马尔可夫链的统计模型。
马尔可夫链因安德烈·马尔可夫(Andrey Markov,1856-1922)得名,是数学中具有马尔可夫性质的离散时间随机过程。该过程中,在给定当前知识或信息的情况下,过去(即当期以前的历史状态)对于预测将来(即当期以后的未来状态)是无关的。
虽然从维基百科上摘取下来的概念和定义看着十分的晦涩难懂,但是模型背后的思想是非常简单的,即:首先假设你的系统可以建模为马尔可夫链,然后,系统所发出的信号(输出的可见结果)仅取决于系统当前的状态。那么,隐马尔可夫模型代表的一种情景就是:系统的状态对你而言是不可见的,你仅仅只能观测到系统所发出的信号或者说是系统所输出的结果。
举个通俗易懂的栗子。
假设你有一个住在国外的朋友,他通常会根据天气来安排他的日常活动。你不知道他的国家那边的天气如何(系统的状态),但你确可以在跟他的聊天中知道他今天进行了什么活动(系统的输出),然后这就是一个简单的隐马尔可夫模型。
我们将整个模型
现在有三个比较关键的问题有待我们解决:
1. 知道整个模型后,你朋友告诉你他这三天的活动是:散步(Walk),购物(Shop),清洁屋子(Clean),那么根据模型,计算产生这些行为序列的概率是多少?
2. 知道整个模型后,朋友让你根据他的活动猜一猜他那边这三天天气怎么样
3. 朋友告诉你三天里他做了些什么,然后让你找出他活动规律的模型。
为了解决以上的问题我们首先要了解一些有关HMM的基本元素先:
初始概率分布:初始概率分布即事件初始时发生的概率,我们这里隐藏的状态是天气,然后我们在图中可以看出的初始概率有:天气为晴天的概率为0.8 ; 天气为雨天的概率为0.2
转移概率矩阵P:转移功率矩阵就可以通过一副图来描述了。
晴天 | 雨天 |
---|---|
晴天 | 0.7 |
雨天 | 0.6 |
其中,列参数表示第一天的状态,行参数表示第二天状态。表格中的第一行的含义就是已知第一天为晴天,那么第二天为晴天的概率是0.7,而为雨天的概率只是0.3。
观测量的概率分布B:在这个问题中观测量就是朋友的活动,其概率分布就分别表示为朋友在晴天和雨天的情况下进行散步,购物,打扫屋子各项活动的可能性(概率)。
现在,我们再次回到上面提到的三个问题。其实,每个问题的解决解决在历史上早有前人给出了算法。
问题1 -> Forward Algorithm,向前算法 或 Backward Algorithm,向后算法。
问题2 -> Viterbi Algorithm,维特比算法。
问题3 -> Baum-Welch Algorithm,鲍姆-维尔奇算法。
假设前提:
已知,雨天,朋友选择去散步,购物,收拾的概率分别是0.1,0.4,0.5, 而如果是晴天,选择去散步,购物,收拾的概率分别是0.6,0.3,0.1。
三天活动序列:散步(Walk),购物(Shop),清洁屋子(Clean)
然后我们先计算 t = 1 时,发生 “散步” 行为的概率,如果下雨,则;如果为晴天,则
t = 2 时,发生 “购物” 的概率,如果 t = 2 下雨,则
如果为晴天,则
t = 3 时的算法也可以依此类推,
;
所以,最终:
从上面的例子可以看出,向前算法计算了每个时间点时,每个状态的发生观测序列的概率,看似复杂,但在T变大时,复杂度也会随之降低。
既然,向前算法是在时间 t = 1 的时候,一步一步向前计算。那么反过来,向后算法就是从最后一个状态往前推。
假设最初时
那么:
其中第一项则是转移概率,第二天下雨转到第三天下雨的概率为0.4;第二项则是观测概率,第三天下雨的状况下,在家收拾的概率为0.5;第三项就是我们定义的向后变量(backward variable)。
同理也可推得其他数据,并且最终答案与向前算法的求解相同。
利用动态规划求解概率最大的路径(最优路径)。利用动态规划,可以解决任何一个图中的最短路径问题。而维特比算法是针对一个特殊的图——篱笆网络的有向图(Lattice)的最短路径提出的。
我们假设用符号来表示系统的第 i 种状态的第j个可能的值。如果把每个状态按照不同的值展开,就可以得到以下的篱笆网络(Lattice):
那么从第一个状态到最后一个状态的任何一条路径(path)都可能产生我们观察到的输出序列Y。当然,这些路径的可能性不一样,而我们要做的就是找到最可能的这条路径。对于每一条给定的路径我们都可以用公式:
计算出它的可能性,但是随着组合增多,它使得序列状态数的增长呈指数爆炸式。
为了解决这个问题,需要一个最好能和状态数成正比的算法。也就是我们要讲的维特比算法。
维特比算法的基础可以概括成三点:
如果概率最大的路径P(或者说最优路径)经过某个点,如果上图中的,那么这条路径上从起始点S 到 的这一段子路径Q,一定是S到的最短路径。否则用S到的最短路径R来代替Q,便构成了一条比P更短的路径。
从S到E的路径必定经过第i个时刻的某个状态,假定第i个时刻有k个状态,那么如果记录了从S到第i个状态的所以k个节点的最短路径,最终最短路径必经过其中的一条。这样,在任何时刻,只要考虑非常有限条最短路径即可。
结合上述两点,假定当我们从状态 i 进入状态 i + 1 时,从 S 到状态 i 上各个节点的最短路径已经找到,并且记录在这些节点上,那么在计算从起点 S 到第 i + 1 状态的某个节点的最短路径时,只要考虑从S到前一个状态 i 所有的 k 个节点的最短路径,以及从这 k 个节点到,j 的距离即可。
接下来,类似地按照上述方法从第二个状态走到第三个状态,一直走到最后一个状态,就得到了整个网格从头到尾的最短路径。每一步计算的复杂度都和相邻两个状态各自的节点数目的乘积成正比,即。如果假定在这个隐含马尔可夫链中的节点最多的状态有D个节点,也就是说整个网络的宽度为D,那么任何一布的复杂度不超过,由于网络长度是N,所以整个维特比算法的复杂度是 ——本段推理节选自<<数学之美>>第二版
]]>对于一个含有N个未知元素的贝叶斯推断问题,我们隐式地为其先验分布创建了一个N维空间。先验分布上某一点的概率,都投射到某个高维的面或曲线上,其形状由先验分布决定。比如,假定有两个未知元素 ,其先验分布都是(0,5)上的均匀分布,那么先验分布存在于一个边长为5的正方形空间,而其概率面就是正方形上方的一个平面(由于假定了均匀分布,因此每一点概率相同)。
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 |
|
再者,如果 的先验分布为Exp(3)和Exp(10),那么对应的空间便是二维平面上,各维都取正值确定的范围,而对应的概率面的形状就是一个从(0,0)点向正值方向流淌的瀑布。
以下的示例图就描绘了这样的情形,其中颜色越是趋向于暗红的位置,其先验概率就越高。反过来,颜色越是趋向于深蓝的位置,其先验概率就越低。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
这些二维空间的例子很简单,我们的大脑可以轻易想象得到。但实际中,先验分布所在的空间和其概率面往往具有更高的维度。
在实际中,观测样本对空间不会有影响,但它会改变概率面的形状,将其在某些局部区域拉伸或挤压,以表明参数的真实性在哪里。更多的数据意味着对概率面更多的拉伸和挤压,使得最初的概率面形状变得十分奇怪。反之数据越少,那么最初的形状就保留得越好。不管如何,最后得到的概率面描述了后验分布的形状。
假如我们现在想对两个参数为的泊松分布进行估计。那么我们将要分别比较用均匀分布和指数分布来对的先验分布进行假设的不同效果。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
|
四张图里的黑点代表了参数的真实取值,左下图为均匀先验得到的后验分布图。虽然观测值相同,但是两种假设下的后验分布形状是不一样的。其主要原因是因为观测点的位置在两者的假设的前提先验概率是不一样的。这样,我们可以知道,即便只有一个观测值,形成的山峰也试图要包括参数值的真实位置。当然,在真正的推断中,仅用一个观测值显然也是十分不科学的,这里仅仅为了方便阐述而已。
本文参考自《Probabilistic-Programming-and-Bayesian-Methods-for-Hackers》
]]>首先,时间序列预测问题是一个复杂的预测模型问题,它不像一般的回归预测模型。时间序列预测的输入变量是一组按时间顺序的数字序列。它既具有延续性又具有随机性,所以在建模难度上相对回归预测更大。
但同时,正好有一种强大的神经网络适合处理这种存在依赖关系的序列问题:RNN(Recurrent neural networks)。在过去几年中,应用 RNN 在语音识别,语言建模,翻译,图片描述等问题上已经取得一定成功,并且应用领域还在扩展。
Long Short-Term Memory 网络亦称LSTM 网络,是一种在深度学习中应用的循环神经网络。可以学习长期依赖信息。LSTM 由Hochreiter & Schmidhuber (1997)提出,并在近期被Alex Graves进行了改良和推广。在很多问题,LSTM 都取得相当巨大的成功,并得到了广泛的使用。LSTM 通过刻意的设计来避免长期依赖问题。记住长期的信息在实践中是 LSTM 的默认行为,而非需要付出很大代价才能获得的能力。
下面以一个洗发水销售的例子,来实现LSTM。 首先,你可以在这里下载到本文需要用的数据集。这是一个描述了3年内洗发水的月度销售数量的数据集。
1 2 3 4 5 6 7 8 9 10 11 |
|
首先我们把数据集划分成两个部分即:训练集和测试集。 那么我们该如何划分呢?因为我们今天研究的是时间序列分析,所以在数据集的划分上我们也应该按照时间来划分。我们可以将前两年的数据作为我们的训练集而将最后一年的数据作为测试集。
1 2 3 |
|
这里我们假设一个滚动预测的情景,又称前向模型验证(walk-forward model validation)。其原理很简单,举例来说就像当公司的预测期长达一年时,预测会将已过去的月份排除,而将预测期末的月份补上。好比一月份过去后,我们将其从预测中移除,同时次年的一月份就会作为收尾被添加到预测中以便预测总能保持12个月的完整性。
这样通过使用每月新的洗发水销售量来进行下个月的预测,我们就像模拟了一个更接近于真实世界的场景。
最后,我们将所有在测试集上的预测结果收集起来并计算出他们与真实值的均方根误差(RMSE)以此来作为评估我们模型的基准。
持续性预测的基本思路就是从先前的(t-1)时间序列的结果用于预测当前时间(t)的取值。 那么根据以上的思路,我们可以通过滚动预测的原理从训练集的历史数据中获取最后一次观察值并使用它来预测当前时间的可能取值。
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 |
|
通过持续模型的预测,我们得到了一个最基础的预测模型以及RMSE(baseline)为了提升我们预测模型的效果,下面让我们进入正题来构建LSTM模型来对数据集进行时间序列预测。
为了能够构建一个LSTM模型对训练集进行训练,我们首先要对数据进行一下处理:
对于一个时间序列问题,我们可以通过使用从最后一个(t-1)时刻的观测值作为输入的特征X和当前时刻(t)的观测值作为输出Y来实现转换。
因为,需要转换的是一组时间序列数据,所以无法组合成像真正的监督学习那样有明确一对一映射的输入输出关系。尤其是在数据集的最开始或最后时,两个位置总有一个位置无法在训练集中找到对应关系。为了解决这样的问题,我们通常的做法是,在最开始时将输入特征置为0,而它对应的输出就是时间序列的第一个元素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
输出结果: 0 0 0 0.0 266.0 1 266.0 145.9 2 145.9 183.1 3 183.1 119.3 4 119.3 180.3 5 180.3 168.5 6 168.5 231.8 7 231.8 224.5 8 224.5 192.8 9 192.8 122.9 10 122.9 336.5 11 336.5 185.9 12 185.9 194.3 13 194.3 149.5 14 149.5 210.1 15 210.1 273.3 16 273.3 191.4 17 191.4 287.0 18 287.0 226.0 19 226.0 303.6 20 303.6 289.9 21 289.9 421.6 22 421.6 264.5 23 264.5 342.3 24 342.3 339.7 25 339.7 440.4 26 440.4 315.9 27 315.9 439.3 28 439.3 401.3 29 401.3 437.4 30 437.4 575.5 31 575.5 407.6 32 407.6 682.0 33 682.0 475.3 34 475.3 581.3 35 581.3 646.9
虽然不明显,但我们仍可以看出这个洗发水销售数据集在时间上呈上升趋势。因此我们说这个时间序列数据是非平稳的。那么,不平稳怎么办?
答案就是:差分。(有关差分的介绍点击此处)
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 32 33 |
|
经过一阶差分处理后,从图上看还是挺平稳的。
在数据输入前进行标准化可以非常有效的提升收敛速度和效果。尤其如果我们的激活函数是sigmoid或者tanh,其梯度最大的区间是0附近,当输入值很大或者很小的时候,sigmoid或者tanh的变化就基本平坦了(sigmoid的导数sig(1-sig)会趋于0),也就是进行梯度下降进行优化的时候,梯度会趋于0,而倒是优化速度很慢。
如果输入不进行归一化,由于我们初始化的时候一般都是0均值的的正太分布或者小范围的均匀分布(Xavier),如果输入中存在着尺度相差很大的特征,例如(10000,0.001)这样的,很容易导致激活函数的输入w1x1+w2x2+b变的很大或者很小,从而引起梯度趋于0。
而LSTM的默认激活函数就是tanh函数,它的输出范围在-1 到 1 之间,同时这是时间序列数据的首选范围。因此我们可以使用MinMaxScaler类将数据集转换到范围[-1,1]。像其他scikit用于转换数据的方法类一样,它需要以行和列的矩阵格式提供的数据。因此,在转换之前,我们必须重塑NumPy数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
输出结果: Month 1901-01-01 266.0 1901-02-01 145.9 1901-03-01 183.1 1901-04-01 119.3 1901-05-01 180.3 Name: Sales of shampoo over a three year period, dtype: float64 0 -0.478585 1 -0.905456 2 -0.773236 3 -1.000000 4 -0.783188 dtype: float64 0 266.0 1 145.9 2 183.1 3 119.3 4 180.3 dtype: float64
长短期记忆网络(LSTM)是一种递归神经网络(RNN)。 这类网络的的优点是它能学习并记住较长序列,并不依赖预先指定的窗口滞后观察值作为输入。 在Keras中,这被称为stateful,在定义LSTM网络层时将“stateful”语句设定为“True”。
LSTM层要求输入矩阵格式为:[样本,时间步长,特征]
鉴于训练数据集的形式定义为X输入和y输出,必须先将其转化为样本/时间步长/特征的形式。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
|
最后运行结果打印出测试数据集12个月份中每个月份的预期和预测销量。示例还打印了所有预测值得均方根误差。该模型显示洗发水月度销量的均方根误差为111.925,好于持续性模型得出的对应结果136.761。
另外,神经网络的一个难题是初始条件不同,它们给出结果就不同。一种解决办法是修改Keras使用的随机数种子值以确保结果可复制。另一种办法是使用不同的实验设置控制随机初始条件。
]]>对,你没有听错,高大上的GPU,现在不花钱也能用上了。这是Google的一项免费云端机器学习服务,全名Colaboratory。
Colaboratory 是一个 Google 研究项目,旨在帮助传播机器学习培训和研究成果。它是一个 Jupyter 笔记本环境,不需要进行任何设置就可以使用,并且完全在云端运行。Colaboratory 笔记本存储在 Google 云端硬盘中,并且可以共享,就如同您使用 Google 文档或表格一样。Colaboratory 可免费使用,而且最重要的还提供免费的英伟达Tesla K80 GPU。还有这等好事?事不宜迟,本文马上介绍如何使用 Google CoLaboratory 训练神经网络。
在Google Drive上创建文件夹
Colab用的数据都存储在Google Drive云端硬盘上,所以,我们需要先指定要在Google Drive上用的文件夹。
比如说,可以在Google Drive上创建一个“app”文件夹,或者其他什么名字,也可以选择Colab笔记本默认的文件夹。
在刚刚创建的app文件夹里点击右键,选择“更多”,然后从菜单里选择“Colaboratory”,这样就新建出了一个Colab笔记本。
若是更多选项中没有“Colaboratory”选项,可以点击“关联更多应用”选项,然后在打开的页面中,搜索“Colaboratory”,然后再点关联应用,再次点击右键就可以在“更多”选项中看到“Colaboratory”选项了。
新建Colaboratory成功后,在笔记本里点Edit>Notebook settings(编辑>笔记本设置),或者Runtime>Change runtime type(运行时>改变运行时类型),然后在Hardware accelerator(硬件加速器)一栏选择GPU。
然后,Google Colab就可以用了。
为了能让Colaboratory使用到你的Google Drive的文件,我们需要先运行下面这些代码,来安装必要的库、执行授权。
!apt-get install -y -qq software-properties-common python-software-properties module-init-tools
!add-apt-repository -y ppa:alessandro-strada/ppa 2>&1 > /dev/null
!apt-get update -qq 2>&1 > /dev/null
!apt-get -y install -qq google-drive-ocamlfuse fuse
from google.colab import auth
auth.authenticate_user()
from oauth2client.client import GoogleCredentials
creds = GoogleCredentials.get_application_default()
import getpass
!google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret} < /dev/null 2>&1 | grep URL
vcode = getpass.getpass()
!echo {vcode} | google-drive-ocamlfuse -headless -id={creds.client_id} -secret={creds.client_secret}
运行的时候应该会看到下图所示的结果:
看见那个链接之后,点击它,复制验证码并粘贴到文本框里。(这里其实是调用了Google Drive的SDK来访问你的Google Drive,而这个验证码就相当于access_key了)
授权完成后,就可以挂载Google Drive了:
!mkdir -p drive
!google-drive-ocamlfuse drive
这时,我们在本地电脑上创建一个.py文件来测试一下,挂载是否成功以及GPU是否在工作吧。
echo "import tensorflow as tf\nprint(tf.test.gpu_device_name())" > test.py
然后将test.py上传到我们开始时创建的app的文件夹里。
然后在Colaboratory笔记本中运行一下代码:
!python3 drive/app/test.py
不出意外的话,就会输出类似以下的结果:
/usr/local/lib/python3.6/dist-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
from ._conv import register_converters as _register_converters
2018-02-18 12:37:05.172726: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:898] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2018-02-18 12:37:05.172988: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1208] Found device 0 with properties:
name: Tesla K80 major: 3 minor: 7 memoryClockRate(GHz): 0.8235
pciBusID: 0000:00:04.0
totalMemory: 11.17GiB freeMemory: 503.62MiB
2018-02-18 12:37:05.173016: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1308] Adding visible gpu devices: 0
2018-02-18 12:37:05.457665: I tensorflow/core/common_runtime/gpu/gpu_device.cc:989] Creating TensorFlow device (/device:GPU:0 with 243 MB memory) -> physical GPU (device: 0, name: Tesla K80, pci bus id: 0000:00:04.0, compute capability: 3.7)
/device:GPU:0
到这里的话,那么恭喜你,你的GPU环境基本可以用了,只要把你的项目文件夹上传到你的app文件夹下,搭建好深度学习的库环境,就可以通过类似上面的操作进行神经网络训练了。
安装Keras:
!pip install -q keras
import keras
安装PyTorch:
!pip install -q http://download.pytorch.org/whl/cu75/torch-0.2.0.post3-cp27-cp27mu-manylinux1_x86_64.whl torchvision
import torch
安装OpenCV:
!apt-get -qq install -y libsm6 libxext6 && pip install -q -U opencv-python
import cv2
安装XGBoost:
!pip install -q xgboost==0.4a30
import xgboost
安装GraphViz:
!apt-get -qq install -y graphviz && pip install -q pydot
import pydot
安装7zip Reader:
!apt-get -qq install -y libarchive-dev && pip install -q -U libarchive
import libarchive
安装其他库:
用!pip install或者!apt-get install命令。
]]>笔者最近读到一篇交通流仿真的论文里,提到了一个挺有意思的模型 -- 元胞自动机。
在好奇心的驱使之下查询了不少资料,所以今天就来跟大家来分享一下“元胞自动机”这个模型以及它和“康威《生命游戏》”的关系。
为了让话题更加有趣,我们先从《生命游戏》开始谈起。
生命游戏由英国数学家约翰·何顿·康威提出,它其实是一个零玩家游戏,它包括一个二维矩形世界,这个世界中的每个方格居住着一个活着的或死了的细胞。
而整个《生命游戏》是贯彻着一条生命游戏定律的,即:如果一个生命,其周围的同类生命太少,会因为得不到帮助而死亡;如果太多,则会因为得不到足够的生命资源而死亡。 ——英国数学家约翰·康威
一个细胞在下一个时刻生死取决于相邻八个方格中活着的或死了的细胞的数量。如果相邻方格活着的细胞数量过多,这个细胞会因为资源匮乏而在下一个时刻死去;相反,如果周围活细胞过少,这个细胞会因太孤单而死去。实际中,你可以设定周围活细胞的数目怎样时才适宜该细胞的生存。如果这个数目设定过低,世界中的大部分细胞会因为找不到太多的活的邻居而死去,直到整个世界都没有生命;如果这个数目设定过高,世界中又会被生命充满而没有什么变化。
实际中,这个数目一般选取2或者3;这样整个生命世界才不至于太过荒凉或拥挤,而是一种动态的平衡。
这样的话,游戏的规则就是:当一个方格周围有2或3个活细胞时,方格中的活细胞在下一个时刻继续存活;即使这个时刻方格中没有活细胞,在下一个时刻也会“诞生”活细胞。在这个游戏中,还可以设定一些更加复杂的规则,例如当前方格的状况不仅由父一代决定,而且还考虑祖父一代的情况。你还可以作为这个世界的上帝,随意设定某个方格细胞的死活,以观察对世界的影响。
在游戏的进行中,杂乱无序的细胞会逐渐演化出各种精致、有形的结构;这些结构往往有很好的对称性,而且每一代都在变化形状。一些形状已经锁定,不会逐代变化。
有时,一些已经成形的结构会因为一些无序细胞的“入侵”而被破坏。但是形状和秩序经常能从杂乱中产生出来。
生命游戏的原理就是基于元胞自动机的,或者说《生命游戏》就是元胞自动机的一个展示。
元胞自动机(Cellular Automata,简称CA,也有人译为细胞自动机、点格自动机、分子自动机或单元自动机)。是一时间和空间都离散的动力系统。
散布在规则格网 (Lattice Grid)中的每一元胞(Cell)取有限的离散状态,遵循同样的作用规则,依据确定的局部规则作同步更新。大量元胞通过简单的相互作用而构成动态系统的演化。不同于一般的动力学模型,元胞自动机不是由严格定义的物理方程或函数确定,而是用一系列模型构造的规则构成。凡是满足这些规则的模型都可以算作是元胞自动机模型。
因此,元胞自动机是一类模型的总称,或者说是一个方法框架。其特点是时间、空间、状态都离散,每个变量只取有限多个状态,且其状态改变的规则在时间和空间上都是局部的。
初等元胞自动机( Elementary Cellular Automata, ECA)的基本要素如下
元胞以相邻的8个元胞为邻居。即Moore邻居;一个元胞的生死由其在该时刻本身的生死状态和周围八个邻居的状态。
为了解释它,我们可以把计算机中的宇宙想象成是一堆方格子构成的封闭空间,尺寸为N的空间就有N*N个格子。而每一个格子都可以看成是一个生命体,每个生命都有生和死两种状态,如果该格子生就显示蓝色,死则显示白色。每一个格子旁边都有邻居格子存在,如果我们把3x3的9个格子构成的正方形看成一个基本单位的话,那么这个正方形中心的格子的邻居就是它旁边的8个格子。
每个格子的生死遵循下面的原则:
设定图像中每个像素的初始状态后依据上述的游戏规则演绎生命的变化,由于初始状态和迭代次数不同,将会得到令人叹服的优美图案。
这样就把这些若干个格子(生命体)构成了一个复杂的动态世界。运用简单的3条作用规则构成的群体会涌现出很多意想不到的复杂行为,这就是复杂性科学的研究焦点。
在实际应用过程中,有的元胞自动机模型对其中的某些特征进行了扩展,有的在规则设计中引入随机因素,如:森林火灾模型。 又如,在交通、通讯发达的今天, 研究流行病或计算机病毒的传播问题时, 我们还可以将空间背景换成复杂网络的结点,用网络邻接点作为邻居。
这样的调整显然比仍旧使用二维欧氏空间、采用欧氏距离的模型更加符合实际情况。 在大型场所人群紧急疏散问题模拟研究中,可以考虑年龄、性别等因素,即元胞不是同质的,更加有利于使模拟系统接近真实系统。
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
|
效果图
]]>
多余的话就不多说了,今天本文为大家介绍两种使用树莓派来做直播服务器的方法。
首先
我们用到的工具有:
硬件方面:
软件方面:
具体的工作方式就如下图所示:
多个输入源被“喂”到广播服务器,这些多媒体内容就会分发到多个客户端。上图的目的是显示地表明你的流系统能够被分成多个块部署到网络上,允许你广播不同的在线内容,而不需要改变流媒体系统的结构。
无论是树莓派官方摄像头模块还是其他兼容的USB摄像头,连接好摄像头之后,运行命令去启用摄像头:
sudo raspi-config
ffserver.conf,ffserver启动时的配置文件,在这个文件中主要是对网络协议,缓存文件feed1.ffm(见下述)和要发送的流媒体文件的格式参数做具体的设定。
feed1.ffm,可以看成是一个流媒体数据的缓存文件,ffmpeg把转码好的数据发送给ffserver,如果没有客户端连接请求,ffserver把数据缓存到该文件中。
下面就是一个ffserver.conf的一个例子:
Port 8090 # Port to bind the server to
BindAddress 0.0.0.0
MaxHTTPConnections 2000
MaxClients 1000
MaxBandwidth 10000 # Maximum bandwidth per client
# set this high enough to exceed stream bitrate
CustomLog -
NoDaemon # Remove this if you want FFserver to daemonize after start
<Feed feed1.ffm> # This is the input feed where FFmpeg will send
File ./feed1.ffm # video stream.
FileMaxSize 64M # Maximum file size for buffering video
ACL allow 127.0.0.1 # Allowed IPs
</Feed>
<Stream test.webm> # Output stream URL definition
Feed feed1.ffm # Feed from which to receive video
Format webm
# Audio settings
AudioCodec vorbis
AudioBitRate 64 # Audio bitrate
# Video settings
VideoCodec libvpx
VideoSize 720x576 # Video resolution
VideoFrameRate 25 # Video FPS
AVOptionVideo flags +global_header # Parameters passed to encoder
# (same as ffmpeg command-line parameters)
AVOptionVideo cpu-used 0
AVOptionVideo qmin 10
AVOptionVideo qmax 42
AVOptionVideo quality good
AVOptionAudio flags +global_header
PreRoll 15
StartSendOnKey
VideoBitRate 400 # Video bitrate
</Stream>
<Stream status.html> # Server status URL
Format status
# Only allow local people to get the status
ACL allow localhost
ACL allow 192.168.0.0 192.168.255.255
</Stream>
<Redirect index.html> # Just an URL redirect for index
# Redirect index.html to the appropriate site
URL http://www.ffmpeg.org/
</Redirect>
ffserver启动时默认查看 /etc/ffserver.conf 配置文件,你可以通过-f选项控制查阅的配置文件。
ffserver -f ffserver.conf
运行结果如下所示的话,那么ffserver就算是启动成功了。
打开http://localhost:8090/status.html可以看到当前server中各个流的状态。
ffserver启动之后,就可以向
http://localhost:8090/feed1.ffm接入视频流。
注意,这里不需要指定编码格式,FFserver会重新编码。
视频流的来源可以是文件、摄像头或者录制屏幕。
ffmpeg -i testvideo.mp4 http://localhost:8090/feed1.ffm
ffmpeg -f x11grab -r 25 -s 640x512 -i :0.0 -f alsa -i pulse http://localhost:8090/feed1.ffm
这里有两个-f,第一个指的是视频流,第二个指的是音频流。视频流是抓取屏幕形成视频,-r设置帧率为25帧/s,-s设置抓取图像大小为640x512,-i设置录制视频的初始坐标。音频流设置为alsa(Advanced Linux Sound Architecture),从Linux系统中获取音频。这其中这样ffmpeg可以录制屏幕feed到feed1.ffm中。
ffmpeg -f video4linux2 -s 640x480 -r 25 -i /dev/video0 -f alsa -i pulse http://localhost:8090/feed1.ffm
首先
我们用到的工具有:
硬件方面:
软件方面:
因为这里我们要用到nginx的rtmp模块作为服务端,而系统自带的apt安装的nginx是没有这个模块的,所以我们需要先安装后移除nginx然后再手动编译(安装是为了下载好相关依赖)。
sudo apt-get update
#安装 nginx
sudo apt-get -y install nginx
#移除 nginx
sudo apt-get -y remove nginx
sudo apt-get clean
#清空 nginx 的配置文件
sudo rm -rf /etc/nginx/*
#安装编译用的模块
sudo apt-get install -y curl build-essential libpcre3 libpcre3-dev libpcre++-dev zlib1g-dev libcurl4-openssl-dev libssl-dev
#创建存放网页的目录给 nginx 使用
sudo mkdir -p /var/www
#创建编译用的目录
mkdir -p ~/nginx_src
cd ~/nginx_src
#下载 nginx 源码包
wget http://nginx.org/download/nginx-1.11.8.tar.gz
#下载 nginx-rtmp-module 源码包
wget https://github.com/arut/nginx-rtmp-module/archive/master.zip
tar -zxvf nginx-1.11.8.tar.gz
unzip master.zip
cd nginx-1.11.8
#设定编译参数
./configure --prefix=/var/www --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --pid-path=/var/run/nginx.pid --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --with-http_ssl_module --without-http_proxy_module --add-module=/home/pi/nginx_src/nginx-rtmp-module-master
#开始编译安装
make
sudo make install
sudo gedit /etc/nginx/nginx.conf
在末尾添加
rtmp {
server {
listen 1935;
chunk_size 4096;
application live {
live on;
record off;
}
}
}
重启 nginx 服务。
sudo service nginx start
sudo apt-get update
sudo apt-get install libav-tools
#安装 GStreamer
sudo apt-get install gstreamer1.0-tools
#安装 GStreamer 扩展组件
sudo apt-get install libgstreamer1.0-0 libgstreamer1.0-0-dbg libgstreamer1.0-dev liborc-0.4-0 liborc-0.4-0-dbg liborc-0.4-dev liborc-0.4-doc gir1.2-gst-plugins-base-1.0 gir1.2-gstreamer-1.0 gstreamer1.0-alsa gstreamer1.0-doc gstreamer1.0-omx gstreamer1.0-plugins-bad gstreamer1.0-plugins-bad-dbg gstreamer1.0-plugins-bad-doc gstreamer1.0-plugins-base gstreamer1.0-plugins-base-apps gstreamer1.0-plugins-base-dbg gstreamer1.0-plugins-base-doc gstreamer1.0-plugins-good gstreamer1.0-plugins-good-dbg gstreamer1.0-plugins-good-doc gstreamer1.0-plugins-ugly gstreamer1.0-plugins-ugly-dbg gstreamer1.0-plugins-ugly-doc gstreamer1.0-pulseaudio gstreamer1.0-tools gstreamer1.0-x libgstreamer-plugins-bad1.0-0 libgstreamer-plugins-bad1.0-dev libgstreamer-plugins-base1.0-0 libgstreamer-plugins-base1.0-dev
gst-launch-1.0 -v v4l2src device=/dev/video0 ! 'video/x-raw, width=1024, height=768, framerate=30/1' ! queue ! videoconvert ! omxh264enc ! h264parse ! flvmux ! rtmpsink location='rtmp://树莓派的IP地址/live live=1' &
采用以上命令就可以在后台采集USB摄像头拍摄的直播内容并推送到rtmp服务端上了。
呈现直播视频画面:
1、使用 RTMP 播放器播放视频流
例如 VLC 等播放器(桌面版和手机版均有)支持 RTMP 视频流播放,填入 rtmp://树莓派的IP地址/live 即可播放。不过这个软件有数十秒的缓冲延迟,需要设定缓冲时间来缩短延迟。
2、推送至斗鱼直播平台观看
你可能注意到了 GStreamer 这个命令中有 location 这个参数。这个参数是设定采集到的视频流推向哪里,通过设定这个参数可以将视频流推向任何支持 RTMP 协议的服务器。
斗鱼平台同样采用了 RTMP 协议传输直播视频,首先获取斗鱼的 RTMP 推流地址。开启了直播室之后可以获得推流码。注意,斗鱼的推流码是有时限的,取到推流码需要尽快使用以免过期。
]]>首先在讲整体实现之前,笔者先附上自己的开发环境以及使用到的工具、硬件等。
用到的工具有:
树莓派的GPIO引脚共分为两种类型,一种是PHYSICAL NUMBERING
单纯地用从上至下,从左至右的顺序来定义引脚。
另外一种引脚定义方式是GPIO NUMBERING
采用特殊(未知)的方式来标记GPIO接口
这里,我采用的是第一种使用的引脚定义的方式。
L298N 是一种双H桥电机驱动芯片,其中每个H桥可以提供2A的电流,功率部分的供电电压范围是2.5-48v,逻辑部分5v供电,接受5vTTL电平。一般情况下,功率部分的电压应大于6V否则芯片可能不能正常工作。
实物图如下
使用说明如下
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
|
首先,按照依赖配置好树莓派中的python环境,建议使用python3以上版本。
然后在树莓派接入路由后,采用远程终端的方式,运行以上的python脚本。
sudo python3 drive_api.py --speed_percent 5
注:非root用户一定要加上sudo,否则无法读写树莓派的GPIO口。
最后,通过电脑在同一个内网内使用浏览器打开地址192.168.1.208:81/drive(这个地址根据你的树莓派接入路由的实际地址而定,以上为笔者实验使用地址,仅供格式参考)。
在打开的网页内,通过电脑的方向键就可以对树莓派进行“驾驶”了。
]]>今天,偶然地重读了一遍《人月神话》。在IT领域中,即使这本书出版距今已经超过十年,但其中的道理依旧盛行。
《人月神话》虽然是布鲁克斯博士在IBM公司研发并管理System/360计算机家族和OS/360软件支持包期间的项目管理经验,但是其经典程度堪称软件开发项目管理的典范。
翻开《人月神话》这本书的第一感受,这边书不像以往文绉绉的项目管理或软件工程手册。作者用他切身的经验,结合自己精彩的文笔,写出了一本有温度的指导。
书中的很多问题和案例都直击了一个软件开发流程当中出现的情景。作者以一些生动的比喻更为形象的让读者身感同受。
前车之覆,后车之鉴。
在执行项目或任务过程中,一味地添加人员并不能加快项目的进度。
因为软件开发本质上是一项系统工作——错综复杂的关系下实践、沟通、交流的工作量非常之大,它很快就消耗了任务分解节省下来的个人时间。从而,添加更多的人手,实际上是延长了而不是缩短了时间进度。
研究表明,效率高和效率低的实施者之间个体差异非常大,经常能够达到数量级水平。
系统设计之中,概念的完整性应该是最重要的考虑因素,为了反映一系列连贯的设计思路,宁可省略一些不规则的特性和改进。
简洁和直白都来自概念的完整性。在语法上,每个部分应使用相同的技巧;在语义上,应具有同样的相似性。因此,易用性实际上需要设计的一致性和概念的完整性。
在等待时,实现人员应该做什么?
整个创造性活动包括三个独立的阶段:体系结构、设计实现、物理实现,实际情况中,他们往往可以同时开始和并发进行。
坚持至少拥有两个系统或版本以上的开发设想,避免在设计第二个系统的时候就出现过分设计。
文档化的规格,手册不仅要描述包括所有界面在内的用户可见的一切,还要避免描述用户看不见的事物。后者是编程实现人员的的工作范畴,其设计和创造是不应该被限制的。
贯彻执行,计划书写的再完善,没有贯彻执行也是一张白纸而已。
巴比伦塔的管理教训:大型编程项目中的交流和组织能力非常重要。
团队之间的交流沟通方案:
非正式途径:电话、短信、邮件、一切即时通讯手段。
项目会议:常规会议,进度会议。
工作手册及项目文档:准备好开发相关的手册和交互文档。
团队组织的目的是减少所需要的交流和合作的数量,其最好的方法是人力划分和职责限定。
实践是最好的老师,但智者还能从其他地方有所收获。
工作量 = 常数 x 指令数量1.5次方
使用适当的高级语言,编程的生产率可以提高5倍。
书面记录决策是必要的。只有记录下来,分歧才会明朗,矛盾才会突出。书写这项活动需要上百次的细小决定,正是由于它们的存在,人们才能从令人迷惑的现象中得到清晰,确定的策略。
普遍的做法是,选择一种方法,试试看;如果失败了,没关系,再试试别的方法。不管怎么样,重要的是先去尝试。
在项目开发中应该构建 “试验性工厂” 和 “产品” 这两个步骤,不要把产品原型发布给用户。对于大多数项目而言,第一个开发的系统并不合用,它可能太慢、太大或难以使用,这样要解决所有的问题除了重新开始以外,没有其他的办法。
系统软件开发是 “减熵” 的过程,所以它本身是处于亚稳态的。软件维护是 “增熵” 的过程,即使是最熟练的软件维护工作,也只是放缓了系统退化到非稳态的进程。
系统各个组成部分的开发者都会做出一些假设,而这些假设之间的不匹配是大多数致命和难以察觉的bug的主要来源。
模块分割、模块独立、结构化编程、构件单元测试是避免系统性bug的良好手段。
需要什么样的文档?
(1)使用程序:每个用户都需要一段对程序进行描述的文字。可是大多数文档只提供了很少总结性的内容,无法达到用户的要求,就是像描绘了树木,形容了树皮和树叶,但却没有一副森林的图案。
(2)目的:主要功能是什么?开发程序的目的是什么?
(3)环境:程序运行在什么样的机器、硬件配置和操作系统上?
(4)范围:输入的有效范围是什么?允许显示的合法输出范围是什么?
(5)实现功能和使用的算法:精确地阐述它做了什么?
(6)“输入——输出”格式:必须是确切的,完整的。
(7)操作指令:包括控制台及输出内容中正常和异常结束的行为。
(8)选项:用户的功能选项有哪些?如何在选项之间进行挑选?
(9)运行时间:在指定的配置下,解决特定规模问题所需要的时间。
(10)精度和校验:期望结果的精确程度?如何进行精度的检测?
我们团队一年来的开发弊端都有在书中的案例体现。
《人月神话》就像是一个个项目开发小组的倒影,项目交流成本、开发者效率的差异、开发人员各自独立的项目假设造成的隐藏bug、对项目进度的乐观预估,其中最为突出的莫过于是巴比伦塔的管理教训,沟通和有效组织的缺乏,直接拖缓了整个项目的进度。
我想,在经验中总结前进,最有效的莫过于《人月神话》开篇的第一章:前车之覆,后车之鉴。。
By 领沃EdmondFrank
]]>今天我要给大家介绍的主角是Testdisk
首先,Testdisk是一个强大的免费的跨平台的数据恢复工具,根据它的官网上的简介,这款软件主要被设计用于恢复丢失的分区以及修复那些由于人为或者病毒等原因导致分区表错误而无法启动系统的问题。
除此之外,Testdisk更多的特性大家可以参考官方列出的功能列表:
TestDisk can Fix partition table, recover deleted partition Recover FAT32 boot sector from its backup Rebuild FAT12/FAT16/FAT32 boot sector Fix FAT tables Rebuild NTFS boot sector Recover NTFS boot sector from its backup Fix MFT using MFT mirror Locate ext2/ext3/ext4 Backup SuperBlock Undelete files from FAT, exFAT, NTFS and ext2 filesystem Copy files from deleted FAT, exFAT, NTFS and ext2/ext3/ext4 partitions.
对于Testdisk的强大之处,肯定是不容质疑的,对此笔者在之前自己的系统修复过程切身体会过Testdisk的实用与强大之处(在笔者的旧硬盘全盘分区表丢失的情况之下,使用Testdisk成功恢复了大部分的分区,并能成功启动系统。看到系统还能成功开机那一刻别提多激动了!)
Testdisk不仅强大而且还能够跨平台,跨平台,跨平台使用(重要的事情说三遍)并且还支持多种文件系统。
简单的介绍就到这里了!!
下面我们就用一个实际的例子来演示一下 Testdisk的具体使用方法:
本例子摘取自:Testdisk 操作指南 PS:毕竟为了演示而认为制造一些错误也是有一定风险的,所以为了方便和安全起见笔者这里摘抄一份别人的例子啦,反正也是为了向大家安利下这个软件。
前提条件:
使用 TestDisk 的重点:
如果 TestDisk 还没有被安装, 可以从这里下载 TestDisk Download。然后解压缩这个归档文件,包括子文件夹。
所有的硬盘都应该能被TestDisk检测到并且辅以正确的大小列出来:
TestDisk 会显示分区表类型。
TestDisk 显示这个菜单的时候 (参见 TestDisk Menu Items). + 用预设的“analyze”(分析)选项来检查当前的分区结构并搜索丢失的分区。 + 分析过程中按 Enter 键继续。 第一个分区显示了两次,它指向了一个毁坏的分区或一个无效的分区表入口。 + 无效的 NTFS boot 指向了一个错误的 NTFS boot 扇区, 所以这是一个损坏的文件系统。 在扩展分区中,只有一个逻辑分区(分区标签为2)可用。 有一个逻辑分区不见了。 + 选 Quick Search (快速搜索)来继续。 然后,当前的结构就会被列出来。 接下来就可以在当前的分区结构中检查丢失或错误的分区了。
在 Quick Search(快速搜索)的过程中, TestDisk 找到了两个分区,包括那个不见的逻辑分区(标签为 Partition 3 ) + 高亮这个分区并按 p 来列出文件 (若要返回前一页,请按 q ). + 这里所有的目录和文件都正确列出来了。 + 按 Enter 键继续。 + +
当全部分区都可用的时候 并且数据已正确列出,应该选 Write 菜单项保存分区结构. 菜单项 Extd Part gives you the opportunity to decide if the extended partition will use all available disk space or only the required (minimal) space.
当一个分区,第一个,仍然找不到, 高亮菜单项 深度搜索 (没有自动进行的时候) ,按 Enter 键继续.
(经过笔者的几次实验和朋友的反馈,其实到了这一步已经能够解决80%以上的问题了!)
所以,有关Testdisk更加深入的功能和其他详细用法大家可以前往这个中文版的官方指南在这里笔者就不赘述了!
]]>在统计计算中,最大期望(EM)算法是在概率模型中寻找参数最大似然估计或者最大后验估计的算法(机器学习十大算法之一),其中概率模型依赖于无法观测的隐藏变量(Latent Variable)。最大期望经常用在机器学习和计算机视觉的数据聚类(Data Clustering)领域。而本文要讲的就是最大期望算法的基石-最大似然估计。
最大似然估计也称极大似然法,是一种统计方法,它用来求一个样本集的相关概率密度函数的参数。这个方法最早是遗传学家以及统计学家罗纳德·费雪爵士在1912年至1922年间开始使用的。
最大似然法明确地使用概率模型,其目标是寻找能够以较高概率产生观察数据的系统发生树。最大似然法是一类完全基于统计的系统发生树重建方法的代表。
设有外形完全相同的两个箱子,甲箱有99个白球1个黑球,乙箱有1个白球99个黑球.今随机地抽取一箱,然后再从这箱中任取一球,结果发现是白球.问这个箱子是甲箱还是乙箱?
仅仅从取出的球是白球这一点是无法从逻辑上严格加以判定该箱究竟是甲箱还是乙箱的。但是如果现在一定要我们做出选择,那么我们只能这样来考虑:从箱中取出的球是白球这一点来看,甲箱和乙箱哪个看上去更像是真正从中取球的箱子?
我们可以这样来分析,如果该箱是甲箱,则取得白球的概率为0.99;如果该箱是乙箱,则取得白球的概率0.01.因此,用“该箱是甲箱”来解释所取的球是白球这一事件更有说服力一些,从而我们判定甲箱比乙箱更像一些。最后我们做出推断,这球是从甲箱取出的。
看完上面那个简单的例子,下面再来考虑一个抛硬币的例子。假设这个硬币正面跟反面轻重不同。我们把这个硬币抛80次,并把正面的次数记下来,正面记为H,反面记为T),并把抛出一个正面的概率记为p,抛出一个反面的概率记为1 − p。假设我们抛出了49个正面,31 个反面,即49次H,31次T。假设这个硬币是我们从一个装了三个硬币的盒子里头取出的。这三个硬币抛出正面的概率分别为p = 1 / 3, p = 1 / 2, p = 2 / 3. 这些硬币没有标记,所以我们无法知道哪个是哪个。使用最大似然估计,通过这些试验数据,我们可以计算出哪个硬币的可能性最大。这个可能性函数取以下三个值中的一个:
我们可以看到当时,可能性函数取得最大值。这就是p的最大似然估计。
现在假设上面的例子中的盒子中有无数个硬币,对于中的任何一个p, 都有一个抛出正面概率为p的硬币对应,我们再来求其可能性函数的最大值:
两边同时取p微分
求得其解分别为:
使可能性最大的解显然是p = 49 / 80(因为p = 0 和p = 1 这两个解会使可能性为零)。因此我们说最大似然估计值为.
这个结果很容易一般化。只需要用一个字母t代替49用以表达伯努利试验中的被观察数据(即样本)的成功次数,用另一个字母n代表伯努利试验的次数即可。使用完全同样的方法即可以得到最大似然估计值:
对似然函数两边取对数有
对lnL\theta求导数并令之为0:
此方程为对数似然方程。解对数似然方程所得,即为未知参数 的最大似然估计值。
设总体 为未知参数,是来自总体X的样本,是对应的样本值,求的最大似然估计值。
解: X的概率密度为
可得似然函数如下:
取对数,得
令
可得
解得
故的最大似然估计量分别为
首先我们先来看一下最基础的神经网络结构:
由上图的结构可以看出,这个神经网络具有三层,其中输入层不计。而中间的橙色层则为两层隐藏层,最右的蓝色层为输出层。输入从最左边的输入层进行输入,然后经过两次隐藏层和激活函数之后进行输出,这样我们可以把这个神经网络简单地表示成一下的式子:
W为X的权重,而B为函数的偏置。
其中,偏置值B的存在有利于打破数据对称的局面,使得神经网络可以应用在非对称的数据之上。
前向传导:前向传导的思想比较简单,下面的一张图足以概括它的主要思想。
反向传播:反向传播的方法其实也比较简单,其主要思想是涉及求偏导,以及链式求导法则。
梯度下降:梯度下降法是一个最优化算法,通常也称为最速下降法。最速下降法是求解无约束优化问题最简单和最古老的方法之一,虽然现已不具有实用性,但是许多有效算法都是以它为基础进行改进和修正而得到的。最速下降法是用负梯度方向为搜索方向的,最速下降法越接近目标值,步长越小,前进越慢。
首先朴素贝叶斯算法的原始形式可以表达成以下的形式:
除此之外,该算法还有一下特点:
这样转换成矩阵的形式时,我们可以采用独热编码亦称One-hot Encode。
独热编码:
解决了分类标签的问题,那么我们又该怎样用神经网络的线性模型形式来表达贝叶斯公式中概率相乘的情况呢?
没错,就是使用对数函数。根据对数函数的性质,我们就可以通过对数变换,将乘法转换成加法的形式,我们可以把上面的朴素贝叶斯公式改写成:
那么我们就可以用退化成线性模型的神经网络来实现朴素贝叶斯模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
首先,决策树的原理主要就是通过数据信息熵的变化来选择当前的最优分类点,然后从根开始一步一步扩展成树。而实质上,最后成功构建出来的决策树,其从根节点开始到每个分类叶子节点的路径对应的都是一组高维空间上的超平面组合。决策树的分类也就是用一组超平面去划分数据空间,使得最后剩下一个唯一确定的标识。
知道决策树的本质之后,我们就可以用这样的方法来将决策树算法迁移到神经网络上:
* 第一个隐藏层表达决策树的中间节点所对应的超平面
* 第二个隐藏层表达各个决策的路径
* 第二个隐藏层和输出层之间的权值矩阵表达各个叶节点
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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
|
根据上述的原理和理论,我们可以将朴素贝叶斯和决策树转换成神经网络模型,但是转换之后是否存在意义呢?
首先本身可以通过简单log对数转换成线性模型的朴素贝叶斯算法来说,其转换的步骤并不复杂,但却能够对朴素贝叶斯的独立假设进行一定的微调修正。
那么对于决策树来说,神经网络的介入可以对决策树的硬边界作一定的修正和“软化”作用。
]]>1)训练数据
在训练一个深度学习的模型之前,我们首先需要准备的就是训练数据,若是图片的话其中就包括:图片的内容以及他的标签。
注:学习的分类目标也是包括在训练数据里面的
2)学习目标
学习的目标往往就是一个二分类或者多分类问题。而对于最后的效果,我们需要达到当我们输入一个待预测或分类的值时,正确的结果应该对应那个最大概率的输出项。
3)损失函数
简单来说,深度学习的分类和回归的本质就是,找到一个使得在所有样本项上取得的误差值最小的函数。而预测值与真实值的误差我们可以通过他们之间的距离计算得出。
为了达到一个分类或预测准确的效果,我们就要找到一个网络中的对应的超参数使得网络的预测与真实值的误差是最小的。其中一个简单而粗暴的方法就是:枚举法。但是这样做的效率显然非常的低效。为了能够更加优化地找到或者说是接近使得网络取得最小误差的超参数我们可以采用梯度下降法,其根据预设的学习率不断更新权重的梯度来接近局部最优解。
其具体过程图如下所示:
梯度下降的缺点:
由于梯度下降每次计算时都是随机选取一个开始点,再根据学习率来慢慢减小全局误差。这样一来,学习率的设定就十分重要了,过大的学习率容易越过最低点,而过小的学习率又使得误差降低的速度过慢,且过小的学习率也会造成学习过程中陷入局部最低点后无法跳出。但实际上由于精度误差的问题梯度下降永远无法到达真正意义上的全局最低点,即无法取得全局最有解。但在多次的迭代运算后一般可以达到一个可接受的损失误差的局部最优解。
具体图示如下:
反向传播算法:这是一种高效的计算权值梯度的方式。
有关算法的详细介绍可以参考:
http://speech.ee.ntu.edu.tw/~tlkagk/courses/MLDS_2015_2/Lecture/DNN%20backprop.ecm.mp4/index.html
通常我们在使用流行框架来构建神经网络时,不用亲自考虑如何去计算和处理梯度值,框架的作者在实现框架时已经做好了相关处理。
根据 A visual proof that neural nets can compute any function 文章的描述任何的连续函数 f 都可以用一个隐藏层内有足够多的神经元的神经网络来近似。
既然这样,为什么今天流行的是深度网络而不是广度网络呢?
根据 Seide, Frank, Gang Li, and Dong Yu. “Conversational Speech Transcription
Using Context-Dependent Deep Neural Networks.” Interspeech. 2011.
论文的研究,广度和深度网络对降低全局误差时的参数如下表所示:
根据上图的研究结果,我们可以发现使用多层的神经元能够更加容易近似一些函数,这其实就跟我们的电子电路中的逻辑电路类似,即便在电子电路中两层的逻辑门电路就可以实现任意的逻辑操作,但是使用多层的逻辑门电路可以更容易的构建一些逻辑操作。
深度学习中还有一个特点就是模块化,在一层层的网络层的堆叠中,每一层都会作为一个模块来学习数据。简单来说,深度学习的过程其实就是一个自动提取特征的过程。对于传统的机器学习而言,数据科学家通过特征工程,提取出数据的特征,再利用特征对数据进行建模以此达到分类预测的效果。深度学习通过各个神经元的加权组合以及反向传播的权值调整,使得整个网络的每一层都渐渐趋向稳定,且其稳定值能够在那个维度上进行部分数据的划分,简单来说就是一个区域性的能够对数据有所区分的特性。那随着各个神经层的共同作用使得深度学习在分类预测应用上效果显著。
最后,深度学习在图像分类的本质大概可以用以下这张图片概括:
深度学习的概念源于人工神经网络的研究。含多隐层的多层感知器就是一种深度学习结构。深度学习通过组合低层特征形成更加抽象的高层表示属性类别或特征,以发现数据的分布式特征表示。
深度学习的概念由Hinton等人于2006年提出。基于深度置信网络(DBN)提出非监督贪心逐层训练算法,为解决深层结构相关的优化难题带来希望,随后提出多层自动编码器深层结构。此外Lecun等人提出的卷积神经网络是第一个真正多层结构学习算法,它利用空间相对关系减少参数数目以提高训练性能。
深度学习是机器学习中一种基于对数据进行表征学习的方法。观测值(例如一幅图像)可以使用多种方式来表示,如每个像素强度值的向量,或者更抽象地表示成一系列边、特定形状的区域等。而使用某些特定的表示方法更容易从实例中学习任务(例如,人脸识别或面部表情识别)。深度学习的好处是用非监督式或半监督式的特征学习和分层特征提取高效算法来替代手工获取特征。
(以上内容摘取自百度百科)
个人理解:如果说机器学习是为了找出一个能够代表输入变量和输出变量的关系的函数的话;那么深度学习就是先根据输入和输出变量之间的关系,列出一系列能够代表他们之间关系的函数,然后再从这个函数集之中提取一个最优的函数。
随着神经网络的应用和深度学习在人工智能领域的大放异彩,很多人都说神经网络的是最成功的仿真模型。那么他的结构究竟是怎样子的呢?
一个简单的神经网咯函数(一般称作:神经元),就如上图所示。
他的主要执行过程:
多个输入a X 各自的权重w + 偏置值b => 激活函数 => 输出
其中,在这个流程之中,我们可能比较迷惑的是那个激活函数Activation function。
Activation Function:即激活函数,目前的常用的激活函数由挺多的,例如,Simmoid Function,tanh,relu等等。虽然形式上不同,但是他们大体的目的都是较为一致的,就是用来加入非线性因素的,因为线性模型的表达能力不够。
同时,激活函数可以将非常大或非常小的数据映射到“逻辑空间”[-1,1]之间,这样映射过后的数据更适合在反向传播算法中进行梯度下降。
上面我们提及的仅仅是神经网络中的一个神经元,他是神经网络之中最基本的组成单位。但是如果要构建一个强大智能的神经网络,仅仅靠一个神经元是不行的。于是,我们便可以将多个神经元分层连接起来,这样才构成了我们所知道的神经网络。
既然,神经网络的构成本质就是神经元的连接,那么不同的连接方式就会形成不同的神经网络结构如全连接前馈网络,多层感知器,卷积神经网络,循环神经网络等等。
在众多的连接之间,全连接的前馈网络不仅较为简单,也是很多深层网络的基础。他的基本连接方式如下图片所示:
其中,一般来说神经网络的第一层通常都是输入层,而最后一层便是输出层以及中间的都统一称作隐藏层。深度神经网络中的“深”便代表了这个网络中间有非常多的隐藏层。
通常,输出层一般为Softmax 层,并且其可以为任意值。在应用中,输出的结果通常用概率的形式表达,其具体形式如下图所示:
那么,我们知道了神经网络的组成之后,我们要是想自己构建一个神经网络,我们又该如何确定神经网络的层数和每层的神经元的个数呢?
就目前来说,并没有相当的严谨的理论来指导神经网络的构建。我们往往需要依靠直觉和训练测试结果的误差反馈来一步一步选择我们的层数和神经元数以达到要求的效果。
]]>