Maven打包的三种方式

Maven可以使用mvn package指令对项目进行打包,如果使用Java -jar xxx.jar执行运行jar文件,会出现"no main manifest attribute, in xxx.jar"(没有设置Main-Class)、ClassNotFoundException(找不到依赖包)等错误。

要想jar包能直接通过java -jar xxx.jar运行,需要满足:

  1. 在jar包中的META-INF/MANIFEST.MF中指定Main-Class属性,这样才能确定程序的入口在哪里;

  2. 要能加载到依赖包。

使用Maven有以下几种方法可以生成能直接运行的jar包,可以根据需要选择一种合适的方法。

方法一:使用maven-jar-plugin和maven-dependency-plugin插件打包

在pom.xml中配置:

<build>  
    <plugins>  
        <plugin>  
            <groupId>org.apache.maven.plugins</groupId>  
            <artifactId>maven-jar-plugin</artifactId>  
            <version>2.6</version>  
            <configuration>  
                <archive>  
                    <manifest>  
                        <addClasspath>true</addClasspath>  
                        <classpathPrefix>lib/</classpathPrefix>  
                        <mainClass>com.xxx.Main</mainClass>  
                    </manifest>  
                </archive>  
            </configuration>  
        </plugin>  
        <plugin>  
            <groupId>org.apache.maven.plugins</groupId>  
            <artifactId>maven-dependency-plugin</artifactId>  
            <version>2.10</version>  
            <executions>  
                <execution>  
                    <id>copy-dependencies</id>  
                    <phase>package</phase>  
                    <goals>  
                        <goal>copy-dependencies</goal>  
                    </goals>  
                    <configuration>  
                        <outputDirectory>${project.build.directory}/lib</outputDirectory>  
                    </configuration>  
                </execution>  
            </executions>  
        </plugin>  
    </plugins>  
</build>  

maven-jar-plugin用于生成META-INF/MANIFEST.MF文件的部分内容,<mainClass>com.xxx.Main</mainClass>指定MANIFEST.MF中的Main-Class属性值,<addClasspath>true</addClasspath>会在MANIFEST.MF加上Class-Path项并配置依赖包,<classpathPrefix>lib/</classpathPrefix>指定依赖包所在目录。

例如下面是一个通过maven-jar-plugin插件生成的MANIFEST.MF文件片段:

Class-Path: lib/commons-logging-1.2.jar lib/commons-io-2.4.jar  
Main-Class: com.xxx.Main  

只是生成MANIFEST.MF文件还不够,maven-dependency-plugin插件用于将依赖包拷贝到<outputDirectory>${project.build.directory}/lib</outputDirectory>指定的位置,即lib目录下。

配置完成后,通过mvn package指令打包,会在target目录下生成jar包,并将依赖包拷贝到target/lib目录下,目录结构如下:

file

指定了Main-Class,有了依赖包,那么就可以直接通过java -jar xxx.jar运行jar包。

这种方式生成jar包有个缺点,就是生成的jar包太多不便于管理,下面两种方式只生成一个jar文件,包含项目本身的代码、资源以及所有的依赖包。

方法二:使用maven-assembly-plugin插件打包

在pom.xml中配置:

<build>  
    <plugins>  

        <plugin>  
            <groupId>org.apache.maven.plugins</groupId>  
            <artifactId>maven-assembly-plugin</artifactId>  
            <version>2.5.5</version>  
            <configuration>  
                <archive>  
                    <manifest>  
                        <mainClass>com.xxx.Main</mainClass>  
                    </manifest>  
                </archive>  
                <descriptorRefs>  
                    <descriptorRef>jar-with-dependencies</descriptorRef>  
                </descriptorRefs>  
            </configuration>  
        </plugin>  

    </plugins>  
</build>

打包方式:

mvn package assembly:single

打包后会在target目录下生成一个xxx-jar-with-dependencies.jar文件,这个文件不但包含了自己项目中的代码和资源,还包含了所有依赖包的内容。所以可以直接通过java -jar来运行。

此外还可以直接通过mvn package来打包,无需assembly:single,不过需要加上一些配置:

<build>  
    <plugins>  

        <plugin>  
            <groupId>org.apache.maven.plugins</groupId>  
            <artifactId>maven-assembly-plugin</artifactId>  
            <version>2.5.5</version>  
            <configuration>  
                <archive>  
                    <manifest>  
                        <mainClass>com.xxx.Main</mainClass>  
                    </manifest>  
                </archive>  
                <descriptorRefs>  
                    <descriptorRef>jar-with-dependencies</descriptorRef>  
                </descriptorRefs>  
            </configuration>  
            <executions>  
                <execution>  
                    <id>make-assembly</id>  
                    <phase>package</phase>  
                    <goals>  
                        <goal>single</goal>  
                    </goals>  
                </execution>  
            </executions>  
        </plugin>  

    </plugins>  
</build>  

其中<phase>package</phase>、<goal>single</goal>即表示在执行package打包时,执行assembly:single,所以可以直接使用mvn package打包。
不过,如果项目中用到spring Framework,用这种方式打出来的包运行时会出错,使用下面的方法三可以处理。

方法三:使用maven-shade-plugin插件打包
在pom.xml中配置:

<build>  
    <plugins>  

        <plugin>  
            <groupId>org.apache.maven.plugins</groupId>  
            <artifactId>maven-shade-plugin</artifactId>  
            <version>2.4.1</version>  
            <executions>  
                <execution>  
                    <phase>package</phase>  
                    <goals>  
                        <goal>shade</goal>  
                    </goals>  
                    <configuration>  
                        <transformers>  
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">  
                                <mainClass>com.xxx.Main</mainClass>  
                            </transformer>  
                        </transformers>  
                    </configuration>  
                </execution>  
            </executions>  
        </plugin>  

    </plugins>  
</build>  

配置完成后,执行mvn package即可打包。在target目录下会生成两个jar包,注意不是original-xxx.jar文件,而是另外一个。和maven-assembly-plugin一样,生成的jar文件包含了所有依赖,所以可以直接运行。
如果项目中用到了Spring Framework,将依赖打到一个jar包中,运行时会出现读取XML schema文件出错。原因是Spring Framework的多个jar包中包含相同的文件spring.handlersspring.schemas,如果生成一个jar包会互相覆盖。为了避免互相影响,可以使用AppendingTransformer来对文件内容追加合并:

<build>  
    <plugins>  
        <plugin>  
            <groupId>org.apache.maven.plugins</groupId>  
            <artifactId>maven-shade-plugin</artifactId>  
            <version>2.4.1</version>  
            <executions>  
                <execution>  
                    <phase>package</phase>  
                    <goals>  
                        <goal>shade</goal>  
                    </goals>  
                    <configuration>  
                        <transformers>  
                            <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">  
                                <mainClass>com.xxx.Main</mainClass>  
                            </transformer>  
                            <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">  
                                <resource>META-INF/spring.handlers</resource>  
                            </transformer>  
                            <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">  
                                <resource>META-INF/spring.schemas</resource>  
                            </transformer>  
                        </transformers>  
                    </configuration>  
                </execution>  
            </executions>  
        </plugin>  
    </plugins>  
</build>  

配置完成后,执行mvn package即可打包。

Views: 134

配置 Flume Source

安装netcat

Netcat 是一款简单的Unix工具,简称 nc,安全界叫它瑞士军刀, 使用UDP和TCP协议。 它是一个可靠的容易被其他程序所启用的后台操作工具,同时它也被用作网络的测试工具或黑客工具。 使用它你可以轻易的建立任何连接。内建有很多实用的工具。

$> sudo yum install nmap-ncat.x86_64

nc的一些用法:

端口测试

检测主机上8080端口服务是否开放

telnet 192.168.1.2 8080

或者

nc -vz 192.168.1.2 8080

z表示不发送数据,v表示显示额外信息

nc 命令后面的 8080 可以写成一个范围进行扫描:

nc -v -v -w3 -z 192.168.1.2 8080-8083

两次 -v 是让它报告更详细的内容,-w3 是设置扫描超时时间为 3 秒。

传输测试

A 主机上监听了 8080 端口

nc -l -p 8080

然后在 B 主机上连接过去:

nc 192.168.1.2 8080

两边就可以会话了,随便输入点什么按回车,另外一边应该会显示出来.

netcat source

配置

[hello.conf]

#声明三种组件
a1.sources = r1
a1.channels = c1
a1.sinks = k1

#定义source信息
a1.sources.r1.type=netcat
a1.sources.r1.bind=localhost
a1.sources.r1.port=8888

#定义sink信息
a1.sinks.k1.type=logger

#定义channel信息
a1.channels.c1.type=memory

#绑定在一起
a1.sources.r1.channels=c1
a1.sinks.k1.channel=c1

运行

  1. 启动flume agent$> bin/flume-ng agent -f conf/hello.conf -n a1 -Dflume.root.logger=INFO,console
  2. 启动nc的客户端$> nc localhost 8888 $nc> hello world
  3. 在Flume的终端输出hello world.

exec source

实时日志收集,实时收集日志。

a1.sources = r1
a1.sinks = k1
a1.channels = c1

a1.sources.r1.type=exec
a1.sources.r1.command=tail -F /home/centos/test.txt

a1.sinks.k1.type=logger
a1.channels.c1.type=memory

a1.sources.r1.channels=c1
a1.sinks.k1.channel=c1

spooldir 源

监控一个文件夹,静态文件, 批量收集。
收集完之后,会重命名文件成新文件.COMPLETED.

配置文件

[spooldir_r.conf]

a1.sources = r1
a1.channels = c1
a1.sinks = k1

a1.sources.r1.type=spooldir
a1.sources.r1.spoolDir=/home/centos/spool
a1.sources.r1.fileHeader=true

a1.sinks.k1.type=logger

a1.channels.c1.type=memory

a1.sources.r1.channels=c1
a1.sinks.k1.channel=c1

创建目录

$>mkdir ~/spool

启动flume

$>bin/flume-ng agent -f ../conf/helloworld.conf -n a1 -Dflume.root.logger=INFO,console

seq source

生成事件序列的源, 一般用于测试.
[seq]

a1.sources = r1
a1.channels = c1
a1.sinks = k1

a1.sources.r1.type=seq
a1.sources.r1.totalEvents=1000

a1.sinks.k1.type=logger

a1.channels.c1.type=memory

a1.sources.r1.channels=c1
a1.sinks.k1.channel=c1

[运行]

$>bin/flume-ng agent -f ../conf/helloworld.conf -n a1 -Dflume.root.logger=INFO,console

Stress Source

用于压力测试的源.

a1.sources = stresssource-1
a1.channels = memoryChannel-1
a1.sources.stresssource-1.type = org.apache.flume.source.StressSource
a1.sources.stresssource-1.size = 10240
a1.sources.stresssource-1.maxTotalEvents = 1000000
a1.sources.stresssource-1.channels = memoryChannel-1

TailDir Source

Taildir Source目前只是个预览版本,还不能运行在windows系统上。

Taildir Source监控指定的一些文件,并在检测到新的一行数据产生的时候几乎实时地读取它们,如果新的一行数据还没写完,Taildir Source会等到这行写完后再读取。

Taildir Source是可靠的,即使发生文件滚动也不会丢失数据。它会定期地以JSON格式在一个专门用于定位的文件上记录每个文件的最后读取位置。如果Flume由于某种原因停止或挂掉,它可以从文件的标记位置重新开始读取。

Taildir Source还可以从任意指定的位置开始读取文件。默认情况下,它将从每个文件的第一行开始读取。

文件按照修改时间的顺序来读取。修改时间最早的文件将最先被读取(简单记成:先来先走)。

Taildir Source不重命名、删除或修改它监控的文件。当前不支持读取二进制文件。只能逐行读取文本文件。

文件滚动(file rotate)就是我们常见的log4j等日志框架或者系统会自动丢弃日志文件中时间久远的日志,一般按照日志文件大小或时间来自动分割或丢弃的机制。

练习

使用Flume监听整个目录的实时追加文件,并上传至HDFS

#步骤一:agent Name
a1.sources = r1
a1.sinks = k1
a1.channels = c1

#步骤二:source
# Describe/configure the source
a1.sources.r1.type = TAILDIR 
a1.sources.r1.positionFile = /opt/module/flume/tail_dir.json -- 指定position_file 的位置(记录每次上传后的偏移量,实现断点续传的关键)
a1.sources.r1.filegroups = f1 f2 -- 监控的文件目录集合
a1.sources.r1.filegroups.f1 = /opt/module/flume/files/.*file.* -- 定义监控的文件目录1
a1.sources.r1.filegroups.f2 = /opt/module/flume/files/.*log.* -- 定义监控的文件目录2

#步骤三: channel selector
a1.sources.r1.selector.type = replicating

#步骤四: channel
# Describe the channel
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100

#步骤五: sinkprocessor,默认配置defaultsinkprocessor
#步骤六: sink
a1.sinks.k1.type = hdfs
a1.sinks.k1.hdfs.path = hdfs://hadoop102:9820/flume/upload3/%Y%m%d/%H

#上传文件的前缀
a1.sinks.k1.hdfs.filePrefix = upload-
#是否按照时间滚动文件夹
a1.sinks.k1.hdfs.round = true
#多少时间单位创建一个新的文件夹
a1.sinks.k1.hdfs.roundValue = 1
#重新定义时间单位
a1.sinks.k1.hdfs.roundUnit = hour
#是否使用本地时间戳
a1.sinks.k1.hdfs.useLocalTimeStamp = true
#积攒多少个Event才flush到HDFS一次
a1.sinks.k1.hdfs.batchSize = 100
#设置文件类型,可支持压缩
a1.sinks.k1.hdfs.fileType = DataStream
#多久生成一个新的文件
a1.sinks.k1.hdfs.rollInterval = 60 
#设置每个文件的滚动大小大概是128M
a1.sinks.k1.hdfs.rollSize = 134217700
#文件的滚动与Event数量无关
a1.sinks.k1.hdfs.rollCount = 0
#步骤七:连接source、channel、sink
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1

Views: 342

神奇的魔数:0x5f3759df

Quake-III Arena (雷神之锤3)是90年代的经典游戏之一。

该系列的游戏不但画面和内容不错,而且即使计算机配置低,也能极其流畅地运行。这要归功于它3D引擎的开发者约翰-卡马克(John Carmack)。

事实上早在90年代初DOS时代,只要能在PC上搞个小动画都能让人惊叹一番的时候,John Carmack就推出了石破天惊的Castle Wolfstein, 然后再接再励,doom, doomII, Quake...每次都把3-D技术推到极致。他的3D引擎代码资极度高效,几乎是在压榨PC机的每条运算指令。当初MS的Direct3D也得听取他的意见,修改了不少API。

最近,QUAKE的开发商ID SOFTWARE遵守GPL协议,公开了QUAKE-III的原代码,让世人有幸目睹Carmack传奇的3D引擎的原码。

我们知道,越底层的函数,调用越频繁。3D引擎归根到底还是数学运算。

那么找到最底层的数学运算函数(game/code/q_math.c),必然是精心编写的。里面有很多有趣的函数,很多都令人惊奇,估计我们几年时间都学不完。

在/code/game/q_math.c里发现了这样一段代码。

它的作用是将一个数开平方并取倒。

那么你会如何通过代码写出下面这个公式?

f(x) = \frac{1}{\sqrt{x}} \\

如果是我的话,一行代码搞定float y = 1 / sqrt(x)。而《雷神之锤3》实现的方式就非常有趣:

float Q_rsqrt( float number )
{
  long i;
  float x2, y;
  const float threehalfs = 1.5F;

  x2 = number * 0.5F;
  y = number;
  i = * ( long * ) &y; // evil floating point bit level hacking
  i = 0x5f3759df - ( i >> 1 ); // what the fuck?
  y = * ( float * ) &i;
  y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
  // y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed

  #ifndef Q3_VM
  #ifdef __linux__
  assert( !isnan(y) ); // bk010122 - FPE?
  #endif
  #endif
  return y;
}

函数返回1/sqrt(x),这个函数在图像处理中比sqrt(x)更有用。

注意到这个函数只用了一次叠代!(其实就是根本没用叠代,直接运算)。编译,实验,这个函数不仅工作的很好,而且比标准的sqrt()函数快4倍!要知道,编译器自带的函数,可是经过严格仔细的汇编优化的啊!

这个简洁的函数,最核心,也是最让人费解的,就是标注了“what the fuck?”的一句:

i = 0x5f3759df - ( i >> 1 );

再加上

y = y * ( threehalfs - ( x2 * y * y ) );

两句话就完成了开方运算!而且注意到,核心那句是定点移位运算,速度极快!特别在很多没有乘法指令的RISC结构CPU上,这样做是极其高效的。

如果你看到这个0x5f3759df数字,想必有点小懵逼,人生三问开始了:

  1. 它是谁?
  2. 它怎么来的?
  3. 它有什么用?

下面就会介绍这个magic number从何来,去何处。

为何需要这个算法?

当你需要在游戏中实现一些物理效果,比如光影效果、反射效果时,所关注的点其实是某个向量的方向,而不是这个向量的长度,如果能将所有向量给单位化,很多计算就会变得比较简单。

所以在计算中很重要的一点就是计算出单位向量,而在真正在运行代码中需要根据某个向量计算相应的单位向量,根据某个向量(x, y, z)计算法向量公式如下

x_0 = x * \frac{1}{\sqrt{x^2 + y^2 + z^ 2}}  \\
y_0 = y * \frac{1}{\sqrt{x^2 + y^2 + z^ 2}}  \\
z_0 = z * \frac{1}{\sqrt{x^2 + y^2 + z^ 2}}  \\

可以看出来计算一个数的平方根的倒数其实非常频繁,所以需要一个很快的算法去计算平方根倒数。众所周知,乘法和加法在计算机中被设计得非常快,所以x<em>x+y</em>y+z*z计算起来真的非常快,但是

sqrt(xx + yy + zz)求平方根算法很慢,求一个数的倒数即除法也很慢,所以上面的一行代码实现平方根倒数所消耗的时间会特别慢。而Q_rsqrt(x</em>x + y<em>y + z</em>z)里面的代码没有看到任何除法,以及求平方根,里面全部都是乘法、位移等运算速度很快的操作,平方根倒数速算其实是计算的近似数,大约1%的误差,但是运算速度是之前的三倍,下面就会解释这几行代码。

初始化参数

long i;

float x2, y;

const float threehalfs = 1.5F;

x2 = number * 0.5F;

y = number;

首先函数的参数是一个32位浮点数,之后声明一个32位整型变量i,继续声明两个32位浮点数x2,y,声明一个32位浮点数常量threehalfs,即表示\frac{3}{2},之后两行也非常简单,一个是将number的一半赋值给x2,将number赋值给y,你会发现很有趣的一点,就是这些变量不管是整型还是浮点型,其在内存中的长度都是32位,这其实是magic发生的基础。

接着看下面几行的注释(因为直接看代码也看不懂),分别是evil bit hackwhat the fucknewton iteration,这三个注释其实就说明了整个算法重要的三步,在真正解释这三个步骤之前,先来说说浮点数的在内存中表示。

二进制数的表示方法

在日常生活中,我们通常以十进制的方式表示现实生活中的各种数,这种数被称为真值,对于负数,我们会在数的前面加一个‘-’号表示这个数是小于0,‘+’号(通常不写)去表示一个正数。而在计算机中只能表示0、1两种状态,所以下正负号在计算机中会以0、1的形式表示,通常放在最高位,作为符号位。为了能够方便对这些机器数进行算术运算、提高运算速度,计算机设计了许多种表示方式,其中比较常见的是:原码、反码、补码以及移码。下面主要以8bit长度的数0000 0100,即十进制数4,去介绍这几种表示方式

  • 原码

原码表示方式是最容易理解的,首先第一位为符号位,后面七位表示的就是真值,如果表示负数,只需要将符号位置为1即可,后面七位依然为真值,所以4与-4的原码为0000 0100 和 1000 0100。所以很容易就得出原码的表示范围为[-127, 127],会存在两个特殊的数为+0-0

  • 反码

正数的反码即是其原码,而负数的反码就是在保留符号位的基础上,其他位全部取反,所以4与-4的反码为0000 0100 和 1111 1011。所以反码表示范围为[-127, 127],依然存在两个特殊的数为+0-0

  • 补码

正数的补码即是其原码,而负数的补码就是在保留符号位的基础上,其他位全部取反,最后加1,即在反码的基础上+1,所以4与-4的补码是0000 0100 和 1111 1100。所以补码表示范围为[-128,127],之前的-0在补码中被表示成了-128,可以多表示一个数,另外补码统一了正数0和负数0。

  • 移码

假设数值用八位有符号二进制表示, 则移码表示在数轴上将真值在范围从[-128, 127]变为[0, 255],即向上偏移量128,即将补码的符号位取反(将负数平移到正数区间内),所以4与-4的移码为1000 0100 和 0111 1100,移码的好处是可以直观方便的比较两数在数轴上的大小。

浮点数

先考虑一个问题,如果你用32位二进制如何表示4.25?可能会是这样:

0000 0000 0000 0100 . 0100 0000 0000 0000

这放在普通十进制,这种想法其实非常常见,但是这种方式放在二进制世界中,总共1位符号位,15号整数位,16位小数位,总共表示数的范围只有[-2^{15}, 2^{15}],而对于长整型的范围却有[-2^{31}, 2^{31}],差的倍数有6w5,可见这种方式为了小数表示抛弃了一半的位数,得不偿失,所以有人提出了IEEE754标准。

IEEE754

在描述这个标准前,先在这里说下科学计数法,在十进制中科学计数法表示如下

\begin{aligned} & 12300 && 1.23 * 10^{4} \\ & 0.00123 && 1.23 * 10^{-3} \\ \end{aligned}  \\

同样的,可以将科学计数法运用到二进制中

\begin{aligned} & 11,000 && 1.1 * 2^{4} \\ & 0.0101 && 1.01 * 2^{-2} \\ \end{aligned} \\

所以IEEE754也是采用的是科学计数法的形式,会将32位数分为以下三部分

  • Sign Bit

首先第一位是符号位,0表示正数,1表示负数,而在平方根的计算中,明显不会涉及到负数,所以第一位肯定是为0的。

  • Exponent

第二部分用8位bit表示指数部分(乘上2的几次方),可以表示数的范围是[0, 255],但是这个只能表示正数,所以需要把负数也加进来,而IEEE754标准中阶码表示方式为移码,之所以要表示为移码的方式是在浮点数比较中,比较阶码的大小会变得非常简单,按位比较即可。不过和正常的移码有一点小区别是,0000 00001111 1111用来表示非规格化数与一些特殊数,所以偏移量从128变为127,表示范围也就变成了[-127, 126]

举个例子,众所周知啊,4这个数的8bit真值为0000 0100,加上127的偏移量变成131,即阶码的二进制表示为1000 0011

  • Mantissa

二进制科学计数法中小数部分就是剩余的23位,由于第一位二进制数总是1,所以用这23位bit表示剩余的数值,真正计算的时候再在最高位加上1即可。

下面我们以9.625这个数改成IEEE 754标准的机器数:

首先变为相应的二进制数为1001.101,用规范的浮点数表达应为1.001101 * 2^{3},所以符号段为0,指数部分的移码为1000 0010,有效数字去掉1后为001101,所以最终结果为

0 |10000010 |00110100000000000000000

二进制数转换

在前面我们介绍了一个浮点数是如何在计算机中以机器数来表示的,现在我们要对这个浮点数进行一些骚操作,以方便之后对这个浮点数的处理。

同样的,我们以9.625这个数为例,首先我们令阶码的真值为E,则有

E = 1000,0010_{(2)} = 130_{(10)}  \\

令余数为M,则有

M = 00110100000000000000000_{(2)} = {0.001101 * 2^{23}}_{(2)} \\

现在我们先不认为这个是浮点数,这个数就是32位长整型,令这个数为L,它所表示的十进制数便是这样

L = 2^{23} * E + M \\

这个数L便是这32位所表示的无符号整型,这个数后面有大用,我们先暂且把它放在这儿,后续再来看。

然后我们通过一个公式来表示这个浮点数F的十进制数

F = (1 + \frac{M}{2^{23}}) * 2^{E - 127} \\

尾数加1是因为在IEEE754标准中把首位的一去掉了,所以计算的时候需要把这个一给加上,然后阶码减去127是因为偏移为127,要在8位真值的基础上减去127才是其表示真正的值。

然后有趣的事情就发生了,我们现在将这个数F取下对数

\log_2 F = \log_2 (1 + \frac{M}{2^{23}}) + E - 127 \\

在这个公式,E-127自然非常好计算,但是前面的对数计算起来是比较麻烦的,所以我们可以找个近似的函数去代替对数。

现在看下y=log_2(1 + x)y=x的图像

根据图像,我们很容易得出下面这个结论

\lim_{x \to 0^+} \log_2 (1 + x) = x  \\
lim_{x \to 1^-} \log_2 (1 + x) = x \\

然后很容易发现,在[0, 1]这个范围内,\log_2(x+1)x其实是非常相近的,那样我们可以取一个在[0,1]的校正系数\mu$,使得下面公式成立

\log_2 (x + 1) \approx x + \mu \\

到此,我们知道了怎样去简化对数,所以我们可以将这个简化代入上面浮点数表示中,就可以得到

\begin{aligned} \log_2 F &= \log_2 (1 + \frac{M}{2^{23}}) + E - 127 \\ & \approx (\frac{M}{2^{23}} + \mu) + E - 127 \\ & = (\frac{M}{2^{23}} + \frac{E * 2^{23}}{2^{23}}) + \mu - 127 \\ & = \frac{1}{2^{23}} (M + E * 2^{23}) + \mu - 127 \end{aligned} \\

看见这个M + E * 2 ^{23}\\应该比较熟悉吧,这个数就是浮点数F的二进制数L,然后代入约等式中得到

\log_2 F \approx \frac{L}{2^{23}} + \mu - 127 \\

在某种程度上,不考虑放缩与变换,我们可以认为浮点数的二进制表示L其实就是其本身F的对数形式,即

\log_2 F \approx L \\

三部曲

经过上面一系列复杂的数字处理操作,我们终于可以开始我们的算法三部曲了

evil bit hack

众所周知啊,每个变量都有自己的地址,程序运行的时候就会通过这个地址拿到这个变量的值,然后进行一系列的计算,比如i和y在内存中会这样表示

我们对长整型这些数据很好进行位移运算,比如我想将这个数乘以或者除以2^n\\,只需要左移或者右移N个位就可以,但是浮点数明显无法进行位运算,它本身二进制表示就不是为了位运算设计的。

然后,现在就会提出一个想法,我把float转成int,然后进行位运算不就行了,代码如下long i = (long) y

假设y为3.33,进行长整型强转后,C语言会直接丢弃尾数,i也就变成了3,丢失这么多精度,谁干啊,如果我们想一位都不动地进行位运算,就是下面这份代码i = <em> ( long </em> ) &y

这个强转过程其实没改变内存中任何东西,首先它并没有改变0x3d6f3d791这个地址,也没改变这个地址中所存储的数据,可以认为改变的是C语言的“认知”,原本是要以IEEE754标准去读取这个地址中数据,但是C语言现在认为这个是长整型的地址,按长整型方式读取就行。所以(long<em>)&y代表0x3d6f3d791这个地址中存储的是长整型数据,然后我通过</em>运算符从这个地址中拿出数据,赋值给i这个长整型变量。

这行代码做了什么事呢,&y首先将浮点数y的地址找出来,可以认为就是0x3d6f3d79这个地址,它的类型其实是float <em>,C语言便会以浮点数的形式将这个数取出来,而想让C语言认为这个是长整型类型,就必须进行地址的类型的强转,将float </em>强转成long *

这样我们其实避开了数字本身的意义,而是通过地址变换完整地拿出了这个二进制数据。

what the fuck

众所周知啊,位运算中的左移和右移一位分别会使原数乘以或者除以2,比如

\begin{aligned} x & & 110 &= 6 \\ x << 1 & & 1100 &= 12 \\ x >> 1 & & 11 &= 3 \\ \end{aligned} \\

我们想办法把平方根倒数做一个简单的转化

\frac{1}{\sqrt x} = x^{-\frac{1}{2}} \\

这个等式其实就是我们的最终目标,接下的计算就会逐渐往这个等式靠近,得到一个近似的结果。

在前面一步的叙述中,我们得出这样一个结论,浮点数的二进制表示其实就是其本身的对数形式,想要求的浮点数存储在y中,则有

y = 9.625 \\  \\
log_2 (y) \approx i = 01000001 \ 00011010 \ 00000000 \ 00000000 \\

也就是说i中其实存储着y的对数,当然还需要进行一系列的转换与缩放。

前面提到过,直接运算一个数的平方根倒数,所以不如直接计算平方根倒数的对数,然后就会有如下等式

\begin{aligned} \log_2 (\frac{1}{\sqrt y}) = \log_2 (y^{- \frac{1}{2}}) & = - \frac{1}{2} \log_2(y) \\ \log_2 (y) & \approx i \\ \log_2 (\frac{1}{\sqrt y}) & \approx -(i >> 1) \end{aligned}  \\

除法同样计算速度是比较慢的,所以我们用右移代替除法,这也就解释了-(i >> 1)其实是为了计算\log_2 (\frac{1}{\sqrt y})的结果。

所以0x5f3759df这个数到底咋来的,为啥要这么计算,并且-(i >> 1)并不完全是\log_2 (\frac{1}{\sqrt y})的近似值啊,根据公式还得除以2^{23},还得加上一定的误差。

先别急,我们先算下这个magic number是咋来的,令\Gamma为y的平方根倒数,则有

\begin{aligned} \Gamma & = \frac{1}{\sqrt y} \\ \log_2 \Gamma & = \log_2 (\frac{1}{\sqrt y}) = -\frac{1}{2} \log_2 (y) \end{aligned} \\

然后我们代入上面那个浮点数对数的公式中,则有

\begin{aligned} \log_2 \Gamma & = \frac{1}{2^{23}} (M_{\Gamma} + E_{\Gamma} * 2^{23}) + \mu - 127 \\ \log_2 y & = \frac{1}{2^{23}} (M_y + E_y * 2^{23}) + \mu - 127 \\ \frac{1}{2^{23}} (M_{\Gamma} + E_{\Gamma} * 2^{23}) + \mu - 127 & = -\frac{1}{2}(\frac{1}{2^{23}} (M_y + E_y * 2^{23}) + \mu - 127) \end{aligned} \\

现在经过一定的变换能够得到下面这个式子

\begin{aligned} (M_{\Gamma} + E_{\Gamma} * 2^{23}) & = \frac{3}{2}2^{23}(127 - \mu) -\frac{1}{2} (M_y + E_y * 2^{23}) \\ & = 0x5f3759df - (i >> 1) \end{aligned} \\

这里\mu是就是之前简化对数计算引进的误差,通过一定计算,得到最合适的\mu,来得到\Gamma的近似值。这个计算过程偏向纯数学化的,具体过程请查阅《FAST INVERSE SQUARE ROOT》这篇论文。

原算法中取的\mu值为0.0450465,然后计算一下

\frac{3}{2}2^{23}(127 - \mu) = \frac{3}{2}2^{23}(127 - 0.0450465) = 1597463007 \\

这个数的十六进制就是0x5f3759df,也就是上面提到的那个magin number,然后我们根据这个数去计算得到的近似值,并与真正的\frac{1}{\sqrt y}进行比较,具体函数图像如下

从上面的图像可以看到,在[1,100]这个区间内,所得到的近似值曲线已经和原始值制拟合的比较好了,这样我们已经完成了前面几个比较重要的步骤。y = <em> ( float </em> ) &i;

这样我们通过和evial bit hack的逆向步骤,即将一个长整型的内存地址,转变成一个浮点型的内存地址,然后根据IEEE754标准取出这个浮点数,即我们要求的\Gamma的近似值,其实到这里应该是算法差不多结束了,但是这个近似值还存在一定的误差,还需要经过一定的处理降低误差,更接近真实值。

Newton Iteration

本身我们已经得到了一个比较好的近似值,但是仍然存在一定的误差,而牛顿迭代法可以这个近似值更加接近真实的值,近一步减少误差。

牛顿迭代法本身是为了找到一个方程根的方法,比如现在有一个方程f(x)=0,需要找到这个方程的根,但是解方程嘛,是不会解方程的,所以可以找到一个近似值来代替这个真正的解。

如上图,假设f(x)=0的解为x=x_c,我们需要首先给一个x的近似值x_0,通过这个x_0,不断求得一个与x_c更接近的值。

(x_0, f(x_0))处做切线,切线的斜率就是f(x)x=x_0的导数f'(x_0),然后来求这条切线与x轴的交点x_1,则有

x_1 = x_0 - \frac{f(x_0)}{f'(x_0)}  \\

这样我们就完成了一次迭代,从图像上可以看见x_1x_0更接近于真正的解,下一次迭代基于x_1进行同样的步骤,就能得到比x_1更好的近似值x_2,所以牛顿迭代公式非常简单

x_{n + 1} = x_n - \frac{f(x_n)}{f'(x_0)} \\

当这个迭代次数接近无限时,x_n也就越接近真正的解。

而最后一行就是经过一次迭代后的简化公式,这个公式怎么来的呢。对于一个浮点数a,要求它的平分根倒数,则有

y = \frac{1}{\sqrt a} \\

通过这个公式能构成一个函数

f(y) = \frac{1}{y^2} - a \\

求这个y值,其实就是求f(y)=0的根,所以迭代公式就是

\begin{aligned} y_{n+1} & = y_n - \frac{f(y_n)}{f'(y_n)} \\ & = y_n - \frac{y_n^{-2} - a}{-2 * y_n^{-3}} \\ & = y_n * (\frac{3}{2} - \frac{1}{2}* a * y_n^2) \end{aligned} \\

这个公式对应的算法中的代码y = y <em> (threehalfs - ( x2 </em> y * y ) )

至此,你的代码就更接近真实的y=\frac{1}{\sqrt x},在更接近真实答案的同时,运行速率也大大提升。仅仅牺牲了一点点的准确性,却能提高整个的速度,这其实就是算法在优化中的一个比较重要的点。

Lomont -“Fast Inverse Square Root”。

普渡大学的数学家Chris Lomont看了这个算法以后觉得有趣,决定要研究一下卡马克弄出来的这个猜测值有什么奥秘。Lomont也是个牛人,在精心研究之后从理论上也推导出一个最佳猜测值,和卡马克的数字非常接近, 0x5f37642f。卡马克真牛,他是外星人吗?

传奇并没有在这里结束。Lomont计算出结果以后非常满意,于是拿自己计算出的起始值和卡马克的神秘数字做比赛,看看谁的数字能够更快更精确的求得平方根。结果是卡马克赢了... 谁也不知道卡马克是怎么找到这个数字的。

最后Lomont怒了,采用暴力方法一个数字一个数字试过来,终于找到一个比卡马克数字要好上那么一丁点的数字,虽然实际上这两个数字所产生的结果非常近似,这个暴力得出的数字是0x5f375a86。

Lomont为此写下一篇论文,"Fast Inverse Square Root"。

给出了InvSqrt函数的定义:

float InvSqrt(float x)
{
  float xhalf = 0.5f * x;
  int i = *(int *)&x;
  i = 0x5f3759df - (i>>1);
  x = *(float *)&i;
  x = x * (1.5f - xhalf * x * x);
  return x;
}

本文参考自:

知乎Elb(字节跳动大力教育前端团队)-https://wjrsbu.smartapps.cn/zhihu/article?id=445813662&isShared=1&_swebfr=1&_swebFromHost=baiduboxapp

Views: 191

Sqoop环境安装

问题陈述

Sqoop是Hadoop生态系统和RDBMS之间进行数据传输的一个工具。在学习Sqoop之前首先需要完成学习环境的搭建。这里为了学习方便,采用单机部署方式。

最初Sqoop是Hadoop的一个子项目,它设计只能在Linux操作系统上运行。

先决条件

安装Sqoop的必要前提条件是:

  • 准备Linux操作系统(Centos7)
  • 安装Java环境(JDK1.8)
  • 安装Hadoop环境(Hadoop 3.1.4)

另外为了学习Sqoop的大部分功能,还需要需要安装:

  • Zookeeper 3.5.9)
  • HBase 2.2.3
  • MySQL 5.7
  • Hive 3.1.2

解决办法

准备Linux操作系统

配置主机名映射

设置网络:

虚拟机安装Centos7时
硬盘设置大一些,如40G或更多
不要设置预先分配磁盘空间.
网络适配器设置为NAT连接

打开虚拟网络编辑器

记住NAT模式所在的虚拟网卡对应的子网IP,子网掩码以及网关IP.

然后进入虚拟机终端,设置静态IP

$ vi /etc/sysconfig/network-scripts/ifcfg-ens33
NAME="ens33"
TYPE="Ethernet"
DEVICE="ens33"
BROWSER_ONLY="no"
DEFROUTE="yes"
PROXY_METHOD="none"
IPV4_FAILURE_FATAL="no"
IPV6INIT="yes"
IPV6_AUTOCONF="yes"
IPV6_DEFROUTE="yes"
IPV6_FAILURE_FATAL="no"
IPV6_ADDR_GEN_MODE="stable-privacy"
IPV6_PRIVACY="no"
UUID="b00b9ac0-60c2-4d34-ab88-2413055463cf"

ONBOOT="yes"
BOOTPROTO="static"
IPADDR="192.168.186.100"
PREFIX="24"
GATEWAY="192.168.186.2"
DNS1="223.5.5.5"
DNS2="8.8.8.8"

其中需要修改的是:

  • ONTBOOT设置yes可以实现自动联网
  • BOOTPROTO="static" 设置静态IP,防止IP发生变化
  • IPADDR的前三段要和NAT虚拟网卡的子网IP一致,且第四段在0~254之间选择,又不能和NAT虚拟网卡的子网掩码和其他相同网络中的主机IP重复.
  • PREFIX=24是设置子网掩码的位数长度,换算十进制就是255.255.255.0,因此PREFIX=24也可以直接替换成NETMASK="255.255.255.0"
  • DNS1设置的是阿里的公共DNS地址"223.5.5.5",DNS2设置的是谷歌的公共DNS地址"8,8,8,8"

设置好了之后需要重新启动网络:

$ sudo service network restart
Restarting network (via systemctl):                        [  确定  ]
$ sudo service network status
已配置设备:
lo ens33
当前活跃设备:
lo ens33

查看本机ip:

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
  link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  inet 127.0.0.1/8 scope host lo
    valid_lft forever preferred_lft forever
  inet6 ::1/128 scope host
    valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
  link/ether 00:0c:29:b2:5b:75 brd ff:ff:ff:ff:ff:ff
  inet 192.168.186.100/24 brd 192.168.186.255 scope global noprefixroute ens33
    valid_lft forever preferred_lft forever
  inet6 fe80::40db:ee8d:77c1:fbf7/64 scope link noprefixroute
    valid_lft forever preferred_lft forever

设置静态主机名:

# hostnamectl --static set-hostname hadoop100

在hosts文件中配置主机名和本机ip之间的映射关系
(注释掉localhost的部分)

vi /etc/hosts
#127.0.0.1  localhost localhost.localdomain localhost4 localhost4.localdomain4
#::1     localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.186.100 hadoop100

创建专用账号

不建议直接使用root账号, 这里我们创建一个hadoop账号用于接下来的所有操作.

创建hadoop用户

# useradd hadoop    

设置hadoop用户的密码

# password hadoop

为hadoop账号设置sudoer权限

vi /etc/sudoers

找到root ALL=(ALL) ALL在下面添加一行

## Allow root to run any commands anywhere
root    ALL=(ALL)   ALL
delucia ALL=(ALL)   NOPASSWD:ALL

修改完成后,切换到Hadoop用户

# su hadoop

以后在使用需要root权限的命令时,就可以在命令前面加上sudo来提升权限,且无需输入hadoop密码, 如:

$ sudo ls /root

接下来的所有操作, 如果没有特殊说明, 一律使用hadoop账号.

配置SSH免密登录

由于Hadoop集群的机器之间ssh通信默认需要输入密码,在集群运行时我们不可能为每一次通信都手动输入密码,因此需要配置机器之间的ssh的免密登录。单机伪分布式的Hadoop环境样需要配置本地对本地ssh连接的免密,流程如下:

  1. 首先ssh-keygen命令生成RSA加密的密钥对(公钥和私钥)。

    $ ssh-keygen -t rsa
    Generating public/private rsa key pair.
    Enter file in which to save the key (/home/hadoop/.ssh/id_rsa): 
    Created directory '/home/hadoop/.ssh'.
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in /home/hadoop/.ssh/id_rsa.
    Your public key has been saved in /home/hadoop/.ssh/id_rsa.pub.
    The key fingerprint is:
    SHA256:yQYChs4eniVLeICaI2bCB9HopbXUBE9v0lpLjBACUnM hadoop@hadoop100
    The key's randomart image is:
    +---[RSA 2048]----+
    |==O+Eo      |
    |=+.O+.=     |
    |Bo* o+.B     |
    |B%.+ .*o..    |
    |OoB . .S    |
    | =   .     |
    |         |
    +----[SHA256]-----+
  2. 将生成的公钥添加到~/.ssh目录下的authorized_keys文件中。并为authorized_keys文件设置600权限。

    $ cd ~/.ssh/
    $ cat id_rsa.pub >> authorized_keys
    $ chmod 600 authorized_keys

    以上三个命令可以使用一个命令代替:

    $ ssh-copy-id hadoop100
  3. 使用ssh命令连接本地终端,如果不需要输入密码则说明本地的SSH免密配置成功。

    $ ssh hadoop@hadoop100
    The authenticity of host 'hadoop100 (192.168.186.100)' can't be established.
    ECDSA key fingerprint is SHA256:aGLhdt3bIuqtPgrFWnhgrfTKUbDh4CWVTfIgr5E5oV0.
    ECDSA key fingerprint is MD5:b8:bd:b3:65:fe:77:2c:06:2d:ec:58:3a:97:51:dd:ca.
    Are you sure you want to continue connecting (yes/no)? yes
    Warning: Permanently added 'hadoop100,192.168.186.100' (ECDSA) to the list of known hosts.
    Last login: Sat Jan 9 10:16:53 2021 from 192.168.186.1
    
  4. 登出

    $ exit
    Connection to hadoop100 closed.

配置时间同步

集群中的通信和文件传输一般是以系统时间作为约定条件的。所以当集群中机器之间系统如果不一致可能导致各种问题发生,比如访问时间过长,甚至失败。所以配置机器之间的时间同步非常重要。不过由于我们使用的学习环境是单机部署,所以无需配置时间同步。

统一目录结构

目录规划如下:

/opt/ 
  ├── bin         # 脚本和命令
  ├── data        # 程序需要使用的数据
  ├── download    # 下载的软件安装包
  ├── pkg         # 解压方式安装的软件
  └── tmp         # 存放程序生成的临时文件

使用hadoop账户创建目录:

$ sudo mkdir /opt/download
$ sudo mkdir /opt/data
$ sudo mkdir /opt/bin
$ sudo mkdir /opt/tmp
$ sudo mkdir /opt/pkg

为了使用方便更改opt下目录的用户及其所在用户组为hadoop:

$ sudo chown hadoop:hadoop /opt/*
$ ls
bin data download pkg tmp

$ ll
总用量 0
drwxr-xr-x. 2 hadoop hadoop 6 1月  9 14:29 bin
drwxr-xr-x. 2 hadoop hadoop 6 1月  9 14:29 data
drwxr-xr-x. 2 hadoop hadoop 6 1月  9 14:29 download
drwxr-xr-x. 2 hadoop hadoop 6 1月  9 14:37 pkg
drwxr-xr-x. 2 hadoop hadoop 6 1月  9 14:36 tmp

安装Java环境

检查是否已经装过Java JDK

$ rpm -qa | grep java

$ yum list installed | grep java

如果没有安装过Java 则需要安装,JDK版本建议1.8+

Java JDK的下载和解压

去官网下载JDK1.8的安装包,上传到/opt/download/,然后解压到/opt/pkg

$ tar -zxvf jdk-8u261-linux-x64.tar.gz 
$ mv jdk1.8.0_261 /opt/pkg/java

配置java环境变量

确认当前目录为jdk的解压路径

$ pwd
/opt/pkg/java

编辑/etc/profile.d/hadoop.env.sh配置文件(没有则创建)

$ sudo vim /etc/profile.d/hadoop.env.sh

添加新的环境变量配置

# JAVA_HOME
export JAVA_HOME=/opt/pkg/java
PATH=$JAVA_HOME/bin:$PATH

export PATH

使新的环境变量立刻生效

$ source /etc/profile.d/env.sh

验证环境变量

$ java -version
$ java
$ javac

安装Hadoop

下载和解压

从官网下载hadoop-3.1.4.tar.gz上传到服务器,解压到指定目录:

$ tar -zxvf hadoop.tar.gz -C /opt/pkg/

编辑/etc/profile.d/env.sh配置文件,添加环境变量:

# HADOOP_HOME
export HADOOP_HOME=/opt/pkg/hadoop
export PATH=$HADOOP_HOME/bin:$HADOOP_HOME/sbin:$PATH

使新的环境变量立刻生效:

$ source /etc/profile.d/env.sh

验证:

$ hadoop version

修改Hadoop相关命令执行环境

找到Hadoop安装目录下的hadoop/etc/hadoop/hadoop-env.sh文件,找到这一处将JAVA_HOME修改为真实JDK路径即可:

# The java implementation to use.
export JAVA_HOME=/opt/pkg/java

找到hadoop/etc/hadoop/yarn.env.sh文件,做同样修改:

# export JAVA_HOME=/home/y/libexec/jdk1.6.0/
export JAVA_HOME=/opt/pkg/java

找到hadoop/etc/hadoop/mapred.env.sh,做同样修改:

# export JAVA_HOME=/home/y/libexec/jdk1.6.0/
export JAVA_HOME=/opt/pkg/java

修改Hadoop配置

来到 hadoop/etc/hadoop/,修改以下配置文件。

1) hadoop/etc/hadoop/core-site.xml – Hadoop核心配置文件

    <configuration>
      <!-- 指定NameNode的地址和端口. -->
      <property>
        <name>fs.defaultFS</name>
        <value>hdfs://hadoop100:8020</value>
      </property>

      <!-- 指定HDFS系统运行时产生的文件的存储目录. -->
      <property>
        <name>hadoop.tmp.dir</name>
        <value>/opt/pkg/hadoop/data/tmp</value>
      </property>

     <!-- 缓冲区大小,实际工作中根据服务器性能动态调整;默认值4096 -->
     <property>
       <name>io.file.buffer.size</name>
       <value>4096</value>
     </property>

     <!-- 开启hdfs的垃圾桶机制,删除掉的数据可以从垃圾桶中回收,单位分钟;默认值0 -->
     <property>
       <name>fs.trash.interval</name>
       <value>10080</value>
     </property>
   </configuration>

注意:主机名要修改成本机的实际主机名。

hadoop.tmp.dir十分重要,此目录下保存hadoop集群中namenode和datanode的所有数据。

2) hadoop/etc/hadoop/hdfs-site.xml – HDFS相关配置

    <configuration>
      <!-- 设置HDFS中的数据副本数. -->
      <property>
        <name>dfs.replication</name>
        <value>1</value>
      </property>

      <!-- 设置Hadoop的Secondary NameNode的主机配置 -->
      <property>
        <name>dfs.namenode.secondary.http-address</name>
        <value>hadoop100:9868</value>
      </property>

      <property>
         <name>dfs.namenode.http-address</name>
        <value>hadoop100:9870</value>
      </property>

      <!-- 是否检查操作HDFS文件系统的用户权限. -->
      <property>
      <name>dfs.permissions</name>
      <value>false</value>
     </property>
   </configuration>

dfs.replication默认是3,为了节省虚拟机资源,这里设置为1

​ 全分布式情况下,SecondaryNameNode和NameNode 应分开部署

dfs.namenode.secondary.http-address默认就是本地,如果是伪分布式可以不用配置

3) hadoop/etc/hadoop/mapred-site.xml – mapreduce 相关配置

   <configuration>
     <!-- 指定MapReduce程序由Yarn进行调度. -->
     <property>
       <name>mapreduce.framework.name</name>
       <value>yarn</value>
     </property>

     <!-- Mapreduce的Job历史记录服务器主机端口设置. -->
     <property>
       <name>mapreduce.jobhistory.address</name>
       <value>hadoop100:10020</value>
     </property>

     <!-- Mapreduce的Job历史记录的Webapp端地址. -->
     <property>
       <name>mapreduce.jobhistory.webapp.address</name>
       <value>hadoop100:19888</value>
     </property>

     <property>
       <name>yarn.app.mapreduce.am.env</name>
       <value>HADOOP_MAPRED_HOME=/opt/pkg/hadoop</value>
     </property>

     <property>
       <name>mapreduce.map.env</name>
       <value>HADOOP_MAPRED_HOME=/opt/pkg/hadoop</value>
     </property>

     <property>
       <name>mapreduce.reduce.env</name>
       <value>HADOOP_MAPRED_HOME=/opt/pkg/hadoop</value>
     </property>
   </configuration>

mapreduce.jobhistory相关配置是可选配置,用于查看MR任务的历史日志。

​ 这里主机名千万不要弄错,不然任务执行会失败,且不容易找原因。

​ 需要手动启动MapReduceJobHistory后台服务才能在Yarn的页面打开历史日志。

4) 配置 yarn-site.xml

    <configuration>
     <!-- 设置Yarn的ResourceManager节点主机名. -->
     <property>
       <name>yarn.resourcemanager.hostname</name>
       <value>hadoop100</value>
     </property>

     <!-- 设置Mapper端将数据发送到Reducer端的方式. -->
     <property>
       <name>yarn.nodemanager.aux-services</name>
       <value>mapreduce_shuffle</value>
     </property>

     <!-- 是否开启日志手机功能. -->
     <property>
       <name>yarn.log-aggregation-enable</name>
       <value>true</value>
     </property>

     <!-- 日志保留时间(7天). -->
     <property>
       <name>yarn.log-aggregation.retain-seconds</name>
       <value>604800</value>
     </property>

     <!-- 如果vmem、pmem资源不够,会报错,此处将资源监察置为false -->
     <property>
       <name>yarn.nodemanager.vmem-check-enabled</name>
       <value>false</value>
     </property>

     <property>
       <name>yarn.nodemanager.pmem-check-enabled</name>
       <value>false</value>
     </property>
    </configuration>

5) workers DataNode 节点配置

   vi workers

   $ vi workers
   hadoop100

这里单机伪分布式环境可以不进行修改,默认是localhost, 也可以改成本机的主机名。

全分布式配置则需要每行输入一个DataNode主机名。

注意DataNode的主机名中不要有空格和空行,因为其他脚本会获取相关主机名信息。

格式化名称节点

$ hdfs namenode -format
21/01/09 19:27:21 INFO namenode.NameNode: STARTUP_MSG: 
/************************************************************
STARTUP_MSG: Starting NameNode
STARTUP_MSG:  host = hadoop100/192.168.186.100
STARTUP_MSG:  args = [-format]
STARTUP_MSG:  version = 2.7.3
************************************************************/
21/01/09 19:27:21 INFO namenode.NameNode: registered UNIX signal handlers for [TERM, HUP, INT]
21/01/09 19:27:21 INFO namenode.NameNode: createNameNode [-format]
Formatting using clusterid: CID-08318e9e-e202-48f3-bcb1-548ca50310c9
21/01/09 19:27:22 INFO util.GSet: Computing capacity for map BlocksMap
21/01/09 19:27:22 INFO util.GSet: VM type    = 64-bit
21/01/09 19:27:22 INFO util.GSet: 2.0% max memory 966.7 MB = 19.3 MB
21/01/09 19:27:22 INFO util.GSet: capacity   = 2^21 = 2097152 entries
21/01/09 19:27:22 INFO blockmanagement.BlockManager: dfs.block.access.token.enable=false
21/01/09 19:27:22 INFO blockmanagement.BlockManager: defaultReplication     = 1
21/01/09 19:27:22 INFO blockmanagement.BlockManager: maxReplication       = 512
21/01/09 19:27:22 INFO blockmanagement.BlockManager: minReplication       = 1
21/01/09 19:27:22 INFO blockmanagement.BlockManager: maxReplicationStreams   = 2
21/01/09 19:27:22 INFO blockmanagement.BlockManager: replicationRecheckInterval = 3000
21/01/09 19:27:22 INFO blockmanagement.BlockManager: encryptDataTransfer    = false
21/01/09 19:27:22 INFO blockmanagement.BlockManager: maxNumBlocksToLog     = 1000
21/01/09 19:27:22 INFO namenode.FSNamesystem: fsOwner       = hadoop (auth:SIMPLE)
21/01/09 19:27:22 INFO namenode.FSNamesystem: supergroup     = supergroup
21/01/09 19:27:22 INFO namenode.FSNamesystem: isPermissionEnabled = false
21/01/09 19:27:22 INFO namenode.FSNamesystem: HA Enabled: false
21/01/09 19:27:22 INFO namenode.FSNamesystem: Append Enabled: true
21/01/09 19:27:23 INFO common.Storage: Storage directory /opt/pkg/hadoop/data/tmp/dfs/name has been successfully formatted.
/************************************************************
SHUTDOWN_MSG: Shutting down NameNode at hadoop100/192.168.186.100
************************************************************/

运行和测试

启动Hadoop环境,刚启动Hadoop的HDFS系统后会有几秒的安全模式,安全模式期间无法进行任何数据处理,这也是为什么不建议使用start-all.sh脚本一次性启动DFS进程和Yarn进程,而是先启动dfs后过30秒左右再启动Yarn相关进程。

1) 启动所有DFS进程:

   $ start-dfs.sh
   Starting namenodes on [hadoop100]
   hadoop100: starting namenode, logging to /opt/pkg/hadoop/logs/hadoop-hadoop-namenode-hadoop100.out
   hadoop100: starting datanode, logging to /opt/pkg/hadoop/logs/hadoop-hadoop-datanode-hadoop100.out
   Starting secondary namenodes [hadoop100]
   hadoop100: starting secondarynamenode, logging to /opt/pkg/hadoop/logs/hadoop-hadoop-secondarynamenode-hadoop100.out

2) 启动所有YARN进程:

   $ start-yarn.sh
   starting yarn daemons
   starting resourcemanager, logging to /opt/pkg/hadoop/logs/yarn-hadoop-resourcemanager-hadoop100.out
   hadoop100: starting nodemanager, logging to /opt/pkg/hadoop/logs/yarn-hadoop-nodemanager-hadoop100.out

启动MapReduceJobHistory后台服务 – 用于查看MR执行的历史日志

   $ mr-jobhistory-daemon.sh start historyserver

3) 查看是否相关进程都成功启动

执行jps命令,看看是否会有如下进程:

   $ jps
   14608 NodeManager
   14361 SecondaryNameNode
   14203 DataNode
   14510 ResourceManager
   14079 NameNode

4) 单一进程管理

   # 在主节点上使用以下命令启动 HDFS NameNode: 
   hdfs --daemon start namenode

   # 在主节点上使用以下命令启动 HDFS SecondaryNamenode: 
   hdfs --daemon start secondarynamenode

   # 在从节点上使用以下命令启动 HDFS DataNode: 
   hdfs --daemon start datanode

   # 在主节点上使用以下命令启动 YARN ResourceManager: 
   yarn --daemon start resourcemanager

   # 在从节点上使用以下命令启动 YARN nodemanager: 
   yarn --daemon start nodemanager

以上脚本位于$HADOOP_HOME/sbin/目录下。如果想要停止某个节点上某个进程,只需要把命令中的start 改为stop 即可。

Web界面进行验证

访问http://hadoop100:9870查看HDFS情况

image-20220210022917279

访问http://hadoop100:8088查看YARN情况

image-20220210022937527

测试Hadoop集群

使用官方自带的示例程序测试Hadoop集群

启动DFS和YARN进程,找到测试程序的位置:

$ cd /opt/pkg/hadoop/share/hadoop/mapreduce
$ hadoop jar hadoop-mapreduce-examples-2.7.3.jar wordcount
Usage: wordcount <in> [<in>...] <out>

准备输入文件并上传到HDFS系统

$ cat /opt/data/mapred/input/wc.txt
hadoop hadoop hadoop
hi hi hi hello hadoop
hello world hadoop

$ hadoop fs -mkdir -p /input/wc

$ hadoop fs -put wc.txt /input/wc/
Found 1 items
-rw-r--r--  1 hadoop supergroup     62 2021-01-09 20:15 /input/wc/wc.txt

$ hadoop fs -cat /input/wc/wc.txt
hadoop hadoop hadoop
hi hi hi hello hadoop
hello world hadoop

运行官方示例程序wordcount,并将结果输出到/output/wc之中

$ cd /opt/pkg/hadoop/share/hadoop/mapreduce
$ hadoop jar hadoop-mapreduce-examples-3.1.4.jar wordcount /input/wc/ /output/wc/

控制台输出:

2020-01-23 18:38:45,914 INFO client.RMProxy: Connecting to ResourceManager at hadoop100/192.168.186.100:8032
2020-01-23 18:38:47,204 INFO mapreduce.JobResourceUploader: Disabling Erasure Coding for path: /tmp/hadoop-yarn/staging/hadoop/.staging/job_1642908422458_0001
2020-01-23 18:38:47,988 INFO input.FileInputFormat: Total input files to process : 1
2020-01-23 18:38:49,033 INFO mapreduce.JobSubmitter: number of splits:1
2020-01-23 18:38:49,788 INFO mapreduce.JobSubmitter: Submitting tokens for job: job_1642908422458_0001
2020-01-23 18:38:49,790 INFO mapreduce.JobSubmitter: Executing with tokens: []
2020-01-23 18:38:50,108 INFO conf.Configuration: resource-types.xml not found
2020-01-23 18:38:50,108 INFO resource.ResourceUtils: Unable to find 'resource-types.xml'.
2020-01-23 18:38:50,740 INFO impl.YarnClientImpl: Submitted application application_1642908422458_0001
2020-01-23 18:38:50,796 INFO mapreduce.Job: The url to track the job: http://hadoop100:8088/proxy/application_1642908422458_0001/
2020-01-23 18:38:50,797 INFO mapreduce.Job: Running job: job_1642908422458_0001
2020-01-23 18:39:09,424 INFO mapreduce.Job: Job job_1642908422458_0001 running in uber mode : false
2020-01-23 18:39:09,425 INFO mapreduce.Job: map 0% reduce 0%
2020-01-23 18:39:19,633 INFO mapreduce.Job: map 100% reduce 0%
2020-01-23 18:39:28,781 INFO mapreduce.Job: map 100% reduce 100%
2020-01-23 18:39:30,812 INFO mapreduce.Job: Job job_1642908422458_0001 completed successfully
2020-01-23 18:39:30,973 INFO mapreduce.Job: Counters: 53
    File System Counters
        FILE: Number of bytes read=52
        FILE: Number of bytes written=444077
         FILE: Number of read operations=0
        FILE: Number of large read operations=0
        FILE: Number of write operations=0
        HDFS: Number of bytes read=165
        HDFS: Number of bytes written=30
        HDFS: Number of read operations=8
        HDFS: Number of large read operations=0
        HDFS: Number of write operations=2
     Job Counters
        Launched map tasks=1
        Launched reduce tasks=1
        Data-local map tasks=1
        Total time spent by all maps in occupied slots (ms)=8195
        Total time spent by all reduces in occupied slots (ms)=6333
        Total time spent by all map tasks (ms)=8195
        Total time spent by all reduce tasks (ms)=6333
        Total vcore-milliseconds taken by all map tasks=8195
        Total vcore-milliseconds taken by all reduce tasks=6333
        Total megabyte-milliseconds taken by all map tasks=8391680
        Total megabyte-milliseconds taken by all reduce tasks=6484992
    Map-Reduce Framework
        Map input records=4
         Map output records=11
        Map output bytes=106
        Map output materialized bytes=52
        Input split bytes=102
        Combine input records=11
        Combine output records=4
        Reduce input groups=4
        Reduce shuffle bytes=52
        Reduce input records=4
        Reduce output records=4
        Spilled Records=8
        Shuffled Maps =1
        Failed Shuffles=0
        Merged Map outputs=1
        GC time elapsed (ms)=235
        CPU time spent (ms)=3340
        Physical memory (bytes) snapshot=366059520
        Virtual memory (bytes) snapshot=5470892032
        Total committed heap usage (bytes)=291639296
        Peak Map Physical memory (bytes)=233541632
        Peak Map Virtual memory (bytes)=2732072960
        Peak Reduce Physical memory (bytes)=132517888
        Peak Reduce Virtual memory (bytes)=2738819072
    Shuffle Errors
        BAD_ID=0
        CONNECTION=0
        IO_ERROR=0
        WRONG_LENGTH=0
        WRONG_MAP=0
        WRONG_REDUCE=0
    File Input Format Counters
        Bytes Read=63
    File Output Format Counters
        Bytes Written=30

注意,输入是文件夹,可以指定多个。输出是一个必须不存在的文件夹路径。

查看保存在HDFS上的结果:

$ hadoop fs -ls /output/wc/
Found 2 items
-rw-r--r--  1 hadoop supergroup     0 2021-01-09 20:23 /output/wc/_SUCCESS
-rw-r--r--  1 hadoop supergroup     30 2021-01-09 20:23 /output/wc/part-r-00000

$ hadoop fs -cat /output/wc/part-r-00000
hadoop 5
hello  2
hi 3
world  1

在MR任务执行时,可以通过Yarn的Web UI界面查看进度:

image-20220210022958848

执行完毕以后点击指定MapReduce程序的TrackingUI一栏下的History可以查看历史日志记录

image-20220210023011146

如果跳转页面报404说明没有启动JobHistoryServer服务。

也可以在HDFS的Web界面上查看结果。

位置:Utilities > HDFS browser > /output/wc/ > part-r-00000

image-20220210023023180

关闭集群

$ stop-all.sh
This script is Deprecated. Instead use stop-dfs.sh and stop-yarn.sh
Stopping namenodes on [hadoop100]
hadoop100: stopping namenode
hadoop100: stopping datanode
Stopping secondary namenodes [hadoop100]
hadoop100: stopping secondarynamenode
stopping yarn daemons
stopping resourcemanager
hadoop100: stopping nodemanager
no proxyserver to stop

安装HBase

Hbase有自带的Zookeeper, 为了更好的使用建议使用自己安装的zookeeper环境.

安装Zookeeper

从官网下载apache-zookeeper-3.5.9-bin.tar.gz,安装到/opt/pkg/zookeeper,单机模式部署,配置文件 $ZOOKEEPER_HOME/conf/zoo.cfg

# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/opt/tmp/zookeeper/data
dataLogDir=/opt/tmp/zookeeper/dataLog
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
#
# Be sure to read the maintenance section of the
# administrator guide before turning on autopurge.
#
# http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
#
# The number of snapshots to retain in dataDir
#autopurge.snapRetainCount=3
# Purge task interval in hours
# Set to "0" to disable auto purge feature
#autopurge.purgeInterval=1

下载安装包

安装包下载地址:https://www.apache.org/dyn/closer.lua/hbase/2.2.6/hbase-2.2.3-bin.tar.gz

将安装包上传到hadoop100服务器/opt/download路径下,并进行解压:

$ cd /opt/download
$ tar -zxvf hbase-2.2.3-bin.tar.gz -C /opt/pkg/

配置HBase

修改HBase配置文件hbase-env.sh

$ cd /opt/pkg/hbase-2.2.3/conf
$ vim hbase-env.sh

修改如下两项内容,值如下

export JAVA_HOME=/opt/pkg/java
export HBASE_MANAGES_ZK=false  

修改文件hbase-site.xml

$ vim hbase-site.xml

内容如下

<configuration>
    <!-- 指定hbase在HDFS上存储的路径 -->
    <property>
         <name>hbase.rootdir</name>
        <value>hdfs://hadoop100:8020/hbase</value>
    </property>

    <!-- 指定hbase是否分布式运行 -->
    <property>
        <name>hbase.cluster.distributed</name>
        <value>true</value>
    </property>

    <!-- 指定zookeeper的地址,多个用“,”分割 -->
    <property>
        <name>hbase.zookeeper.quorum</name>
         <value>hadoop100:2181</value>
    </property>

    <!--指定hbase管理页面-->
    <property>
       <name>hbase.master.info.port</name>
       <value>16010</value>
    </property>

    <!-- 在分布式的情况下一定要设置,不然容易出现Hmaster起不来的情况 -->
    <property>
        <name>hbase.unsafe.stream.capability.enforce</name>
        <value>false</value>
    </property>
</configuration>

修改regionservers配置文件,指定HBase的从节点主机名:

$ vim regionservers
hadoop100

添加HBase环境变量

export HBASE_HOME=/opt/pkg/hbase-2.2.3
export PATH=$PATH:$HBASE_HOME/bin

重新执行/etc/profile,让环境变量生效

source /etc/profile 

HBase的启动与停止:

启动HBase前需要提前启动HDFS及ZooKeeper集群:

如果没开启hdfs,请在运行命令:

$ start-dfs.sh

如果没开启zookeeper,请运行命令:

$ zkServer.sh start conf/zoo.cfg

执行以下命令启动HBase集群

$ start-hbase.sh

启动完后,jps查看HBase相关进程是否都正常运行:

$ jps
7601 QuorumPeerMain
1670 NameNode
1975 SecondaryNameNode
6695 HMaster
6841 HRegionServer
1787 DataNode
2491 JobHistoryServer
2236 ResourceManager
6958 Jps
2351 NodeManager

使用HBase提供的Shell客户端进行访问:

$ hbase shell
HBase Shell
Use "help" to get list of supported commands.
Use "exit" to quit this interactive shell.
For Reference, please visit: http://hbase.apache.org/2.0/book.html#shell
Version 2.2.3, r6a830d87542b766bd3dc4cfdee28655f62de3974, 2020年 01月 10日 星期五 18:27:51 CST
Took 0.0025 seconds
hbase(main):001:0> status

访问HBase的WEB UI界面:

浏览器页面访问 http://hadoop100:16010

image-20220210023039865

停止HBase相关进程的命令:

stop-hbase.sh

安装MySQL

下载rpm-bundle包

$ wget http://mirrors.163.com/mysql/Downloads/MySQL-5.7/mysql-5.7.33-1.el7.x86_64.rpm-bundle.tar
$ tar xvf mysql-5.7.33-1.el7.x86_64.rpm-bundle.tar
$ mkdir mysql-jars
$ mv mysql-comm*.rpm mysql-jars/
$ cd mysql-jars/
$ ls
mysql-community-client-5.7.33-1.el7.x86_64.rpm
mysql-community-common-5.7.33-1.el7.x86_64.rpm
mysql-community-libs-5.7.33-1.el7.x86_64.rpm
mysql-community-libs-compat-5.7.33-1.el7.x86_64.rpm
mysql-community-server-5.7.33-1.el7.x86_64.rpm

依次手动安装

sudo rpm -ivh mysql-community-common-5.7.33-1.el7.x86_64.rpm
sudo rpm -ivh mysql-community-libs-5.7.33-1.el7.x86_64.rpm
sudo rpm -ivh mysql-community-libs-compat-5.7.33-1.el7.x86_64.rpm
sudo rpm -ivh mysql-community-client-5.7.33-1.el7.x86_64.rpm
sudo rpm -ivh mysql-community-server-5.7.33-1.el7.x86_64.rpm

启动MySQL服务

启动MySQL服务

$ systemctl start mysqld.service

$ service mysqld start

配置开机启动

$ systemctl enable mysqld.service

查看运行状态

$ systemctl status mysqld.service

mysql   2574   1 1 23:49 ?    00:00:00 /usr/sbin/mysqld --daemonize --pid-file=/var/run/mysqld/mysqld.pid

查看到进程信息

$ sudo netstat -anpl | grep mysql
tcp6    0   0 :::3306         :::*          LISTEN   946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45824  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45818  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45838  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45808  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45816  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45820  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45826  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45822  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45814  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45846  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45828  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45832  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45842  ESTABLISHED 946/mysqld
tcp6    0    0 192.168.186.100:3306  192.168.186.100:45830  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45844  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45812  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45836  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306   192.168.186.100:45834  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45840  ESTABLISHED 946/mysqld
tcp6    0   0 192.168.186.100:3306  192.168.186.100:45810  ESTABLISHED 946/mysqld
unix 2   [ ACC ]   STREAM   LISTENING   20523  946/mysqld      /var/lib/mysql/mysql.sock

查看端口信息:

可以看出mysql server的进程mysqld所使用的默认端口即3306

$ sudo netstat -anpl | grep tcp
tcp    0   0 0.0.0.0:22       0.0.0.0:*        LISTEN   1412/sshd     
tcp    0   52 192.168.186.103:22   192.168.186.1:54058   ESTABLISHED 2287/sshd: hadoop 
tcp6    0   0 :::3306         :::*          LISTEN   2574/mysqld    
tcp6    0   0 192.168.186.103:3888  :::*          LISTEN   2060/java     
tcp6    0   0 :::22          :::*          LISTEN   1412/sshd     
tcp6    0   0 :::37791        :::*          LISTEN   2060/java     
tcp6    0    0 :::2181         :::*          LISTEN   2060/java 

修改密码

找到临时密码:

第一次登陆mysql需要root的临时密码,这个密码是安装时随机生成在MySQL的服务器日志中的:

$ grep "temporary password" /var/log/mysqld.log
2020-02-26T17:05:45.104999Z 1 [Note] A temporary password is generated for root@localhost: bl/!6qaU.wuX
$ mysql -u roop -p 

这时输入临时密码即可登录MySQL客户端。

修改密码安全策略:

第一次登陆MySQL客户端终端后系统会很快提示你修改掉默认密码

mysql> show databases;
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

mysql> alter user 'root'@'localhost' identified by 'niit1234';
ERROR 1819 (HY000): Your password does not satisfy the current policy requirements

基于默认密码安全策略,所设置的密码必须要包含大小写字母、数字和字符。如果不考虑安全问题,可以修改策略:

mysql> set global validate_password_policy=0;
mysql> set global validate_password_length=1;

设置新root密码并开启远程权限:

Mysql客户端远程访问因为安全原因默认是关闭的。我们需要将root的访问权限扩大都允许从任意ip访问:

mysql>GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'IDENTIFIED BY 'niit1234' WITH GRANT OPTION;

现在虽然修改了远程访问权限,但是还没有生效,因此我们需要刷新权限:

mysql>FLUSH PRIVILEGES;

修改默认字符集

修改MySQL配置文件:

vi /etc/my.cnf

在文件后面追加:

# 默认服务器内部操作字符集
character-set-server=utf8mb4
# 默认服务器内部操作字符集校对规则
collation-server=utf8mb4_general_ci
# 默认的存储引擎
default-storage-engine=InnoDB
# 初始化连接时设置以下字符集:
# character_set_client
# character_set_results
# character_set_connection
init_connect='set names utf8mb4'

[client]
default-character-set=utf8mb4

[mysql]
default-character-set=utf8mb4

重启MySQL服务

$ sudo service mysqld restart

安装Hive

下载安装包

从官网下载hive安装包:apache-hive-3.1.2-bin.tar.gz

规划安装目录:/opt/pkg/hive

上传安装包到hadoop100服务器中

解压到安装路径

解压安装包到指定的规划目录/opt/pkg/

$ cd /export/softwares/
$ tar -xzvf apache-hive-3.1.2-bin.tar.gz -C /opt/pkg/

修改配置文件

进入hive安装目录:

$ cd /opt/pkg

重新命名hive目录:

$ mv apache-hive-3.1.2-bin/hive

修改/opt/pkg//conf目录下的hive-site.xml,默认没有该文件, 需要手动创建:

$ cd /opt/pkg/hive/conf/
$ vim hive-site.xml

进入编辑模式, 文件内容如下:

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
   <property>
       <name>javax.jdo.option.ConnectionURL</name>
       <value>jdbc:mysql://hadoop100:3306/metastore?useSSL=false</value>
   </property>

   <property>
       <name>javax.jdo.option.ConnectionDriverName</name>
       <value>com.mysql.jdbc.Driver</value>
   </property>

   <property>
       <name>javax.jdo.option.ConnectionUserName</name>
       <value>root</value>
   </property>

   <property>
       <name>hive.metastore.warehouse.dir</name>
       <value>/user/hive/warehouse</value>
   </property>

   <property>
       <name>javax.jdo.option.ConnectionPassword</name>
       <value>niit1234</value>
   </property>

   <property>
       <name>hive.metastore.schema.verification</name>
       <value>false</value>
   </property>

   <property>
       <name>hive.metastore.event.db.notification.api.auth</name>
       <value>false</value>
   </property>

    <property>
       <name>hive.cli.print.current.db</name>
       <value>true</value>
   </property>

    <property>
       <name>hive.cli.print.header</name>
       <value>true</value>
   </property>

   <property>
       <name>hive.server2.thrift.bind.host</name>
       <value>hadoop100</value>
   </property>

   <property>
       <name>hive.server2.thrift.port</name>
       <value>10000</value>
   </property>
</configuration>

创建hive日志存储目录

$ mkdir /opt/pkg/hive/logs/

重命名日志配置文件模板为hive-log4j.properties

$ pwd
/opt/pkg/hive/conf

$ mv hive-log4j2.properties.template hive-log4j2.properties
$ vim hive-log4j2.properties # 修改文件

修改此文件的hive.log.dir属性的值:

#更改以下内容,设置我们的hive的日志文件存放的路径,便于排查问题

hive.log.dir=/opt/pkg/hive/logs/

拷贝mysql驱动包

由于运行hive时,需要向mysql数据库中读写元数据,所以需要将mysql的驱动包上传到hive的lib目录下。

上传mysql驱动包,如mysql-connector-java-5.1.38.jar/opt/download/目录中:

$ cp mysql-connector-java-5.1.38.jar /opt/pkg/hive/lib/

解决日志Jar包冲突

# 进入lib目录
$ cd /opt/pkg/hive/lib/

# 重新命名 或者直接删除
$ mv log4j-slf4j-impl-2.10.0.jar log4j-slf4j-impl-2.10.0.jar.bak

配置Hive环境变量

# HIVE
export HIVE_HOME=/opt/pkg/hive
export PATH=$PATH:$HIVE_HOME/bin

# HCATLOG
export HCAT_HOME=$HIVE_HOME/hcatalog
export PATH=$PATH:$HCAT_HOME/bin

之后别忘记source使环境变量配置文件的修改生效

初始化元数据库

开启MySQL客户端连接MySQL服务, 用户名root, 密码niit1234,创建hive元数据库, 数据库名称需要和hive-site.xml中配置的一致:

$ mysql -uroot -pniit1234
create database metastore;
show databases;

退出mysql:

Exit;

初始化元数据库:

$ schematool -initSchema -dbType mysql -verbose

看到schemaTool completed 表示初始化成功

启动Hive服务

前提:Hadoop集群、MySQL服务均已启动,执行命令:

nohup hive --service metastore >/tmp/metastore.log 2>&1 &
nohup hive --service hiveserver2 >/tmp/hiveServer2.log 2>&1 &

验证Hive安装是否成功

在hadoop100上任意目录启动hive的命令行客户端beeline:

$ beeline
Beeline version 3.1.2 by Apache Hive
beeline> !connect jdbc:hive2://localhost:10000
Connecting to jdbc:hive2://localhost:10000
Enter username for jdbc:hive2://localhost:10000: hadoop
Enter password for jdbc:hive2://localhost:10000: ******
Connected to: Apache Hive (version 3.1.2)
Driver: Hive JDBC (version 3.1.2)
Transaction isolation: TRANSACTION_REPEATABLE_READ
0: jdbc:hive2://localhost:10000> show databases;
+----------------+
| database_name |
+----------------+
| default    |
+----------------+
2 rows selected (1.93 seconds)

认证时密码直接回车即可, 如果能看到以上信息说明hive安装成功:

退出客户端:

0: jdbc:hive2://localhost:10000> !quit
Closing: 0: jdbc:hive2://localhost:10000 quit;

使用beeline连接失败的解决办法

如果hiveserver2已经正常运行在本机的10000端口上, 但使用beeline连接hiveserver2报错, WARN jdbc.HiveConnection: Failed to connect to localhost:10000
主要的原因可能是hadoop引入了用户代理机制, 不允许上层系统直接使用实际用户.

解决办法: 在hadoop的核心配置文件core-site.xml中添加:

<property>
   <name>hadoop.proxyuser.hadoop.hosts</name>
   <value>*</value>
</property>
<property>
   <name>hadoop.proxyuser.hadoop.groups</name>
   <value>*</value>
</property>

其中hadoop.proxyuser.XXX的XXX就是代理用户名,我这里设置成hadoop
然后重启hadoop,否则以上修改不生效。

$ stop-all.sh

$ start-dfs.sh
$ start-yarn.sh

再次执行下面命令就可以正常连接欸蓝:

beeline -u jdbc:hive2://localhost:10000

安装Sqoop

Sqoop下载和安装

下载地址:http://archive.apache.org/dist/sqoop/1.4.7/

wget http://archive.apache.org/dist/sqoop/1.4.7/sqoop-1.4.7.bin__hadoop-2.6.0.tar.gz

解压sqoop-1.4.7.bin__hadoop-2.6.0.tar.gz到指定目录下:

tar -zxvf sqoop-1.4.7.bin__hadoop-2.6.0.tar.gz -C /opt/pkg/

修改Sqoop安装目录名称:

$ cd /opt/pkg/
$ mv sqoop-1.4.7.bin__hadoop-2.6.0/ sqoop

配置Sqoop环境变量

$ vi ~/.bash_profile

#sqoop
export SQOOP_HOME=/opt/pkg/sqoop
export PATH=$PATH:$SQOOP_HOME/bin

把Sqoop所依赖的相关环境变量都配置上,修改sqoop-env.sh

$ mv conf/sqoop-env-template.sh conf/sqoop-env.sh
$ vi conf/sqoop-env.sh 

# Set Hadoop-specific environment variables here.

#Set path to where bin/hadoop is available
export HADOOP_COMMON_HOME=/opt/pkg/hadoop

#Set path to where hadoop-*-core.jar is available
export HADOOP_MAPRED_HOME=/opt/pkg/hadoop

#set the path to where bin/hbase is available
export HBASE_HOME=/opt/pkg/hbase

#Set the path to where bin/hive is available
export HIVE_HOME=/opt/pkg/hive

#Set the path for where zookeper config dir is
export ZOOCFGDIR=/opt/pkg/zookeeper/conf

修改sqoop-site.xml, 具体配置如下文件所示:

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
  <property>
  <name>sqoop.metastore.client.enable.autoconnect</name>
  <value>true</value>
  <description>If true, Sqoop will connect to a local metastore
   for job management when no other metastore arguments are
   provided.
  </description>
 </property>
 <property>
  <name>sqoop.metastore.client.autoconnect.url</name>
  <value>jdbc:hsqldb:file:/tmp/sqoop-meta/meta.db;shutdown=true</value>
  <description>The connect string to use when connecting to a
   job-management metastore. If unspecified, uses ~/.sqoop/.
   You can specify a different path here.
  </description>
 </property>
 <property>
  <name>sqoop.metastore.client.autoconnect.username</name>
  <value>SA</value>
  <description>The username to bind to the metastore.
  </description>
 </property>
 <property>
  <name>sqoop.metastore.client.autoconnect.password</name>
  <value></value>
  <description>The password to bind to the metastore.
  </description>
 </property>
  <property>
  <name>sqoop.metastore.client.record.password</name>
  <value>true</value>
  <description>If true, allow saved passwords in the metastore.
  </description>
 </property>
 <property>
  <name>sqoop.metastore.server.location</name>
  <value>/tmp/sqoop-metastore/shared.db</value>
  <description>Path to the shared metastore database files.
  If this is not set, it will be placed in ~/.sqoop/.
  </description>
 </property>
 <property>
  <name>sqoop.metastore.server.port</name>
  <value>16000</value>
  <description>Port that this metastore should listen on.
  </description>
 </property>
</configuration>

修改configure-sqoop

$ vi bin/configure-sqoop

如果没有安装accumulo,则将有关ACCUMULO_HOME的判断逻辑注释掉:

$ vi bin/configure-sqoop
 94 #if [ -z "${ACCUMULO_HOME}" ]; then
 95 # if [ -d "/usr/lib/accumulo" ]; then
 96 #  ACCUMULO_HOME=/usr/lib/accumulo
 97 # else
 98 #  ACCUMULO_HOME=${SQOOP_HOME}/../accumulo
 99 # fi
100 #fi

140 #if [ ! -d "${ACCUMULO_HOME}" ]; then
141 # echo "Warning: $ACCUMULO_HOME does not exist! Accumulo imports will fail."
142 # echo 'Please set $ACCUMULO_HOME to the root of your Accumulo installation.'
143 #fi

这样做的目的是避免将来运行时出现类似下面的警告信息:

Warning: /opt/pkg/sqoop/bin/…/…/accumulo does not exist! Accumulo imports will fail.
Please set $ACCUMULO_HOME to the root of your Accumulo installation.

将MySQL的驱动(MySQL5.7对应的驱动版本应为5.x版本)上传到Sqoop安装目录下的lib目录下:

$ cp mysql-connector-java-5.1.44-bin.jar /opt/pkg/sqoop/lib/

$HIVE_HOME/lib/hive-common-3.1.2.jar拷贝或者软链接到$SQOOP_HOME/lib

$ ln -s /opt/pkg/hive/lib/hive-common-3.1.2.jar /opt/pkg/sqoop/lib

如果需要解析json,可下载java-json.jar放到sqoop目录下的lib里。

下载地址:http://www.java2s.com/Code/Jar/j/Downloadjavajsonjar.htm

$ cp java-json.jar /opt/pkg/sqoop/lib/

如果需要avro序列化,可将hadoop里面的avro的jar包拷贝或者软链接到sqoop目录下的lib里。

$ ln -s /opt/pkg/hadoop/share/hadoop/common/lib/avro-1.7.7.jar /opt/pkg/sqoop/lib/

练习

完成以下练习:

练习1:

安装好Sqoop学习环境后使用cd命令进入到sqoop安装目录, 输入以下命令并观察输出:

$ sqoop version
2020-01-23 23:43:20,287 INFO sqoop.Sqoop: Running Sqoop version: 1.4.7
Sqoop 1.4.7
git commit id 2328971411f57f0cb683dfb79d19d4d19d185dd8
Compiled by maugli on Thu Dec 21 15:59:58 STD 2017

如果输出了类似内容, 表明Sqoop的安装初步完成.

Views: 917

04 回归与聚类算法

线性回归

学习目标

  • 记忆线性回归的原理过程
  • 应用LinearRegression或SGDRegressor实现回归预测
  • 记忆回归算法的评估标准及其公式

应用

  • 波士顿房价预测

1、 线性回归的原理

1.1 线性回归应用场景\

  • 房价预测
  • 销售额度预测
  • 金融:贷款额度预测、利用线性回归以及系数分析因子

1.2 什么是线性回归

1.2.1定义与公式

线性回归(Linear regression)是利用回归方程(函数)一个或多个自变量(特征值)和因变量(目标值)之间关系进行建模的一种分析方式。

  • 特点:只有一个自变量的情况称为单变量回归,大于一个自变量情况的叫做多元回归

那么怎么理解呢?我们来看几个例子

  • 期末成绩:0.7×考试成绩+0.3×平时成
  • 房子价格 = 0.02×中心区域的距离 + 0.04×城市一氧化氮浓度 + (-0.12×自住房平均房价) + 0.254×城镇犯罪率

上面两个例子,我们看到特征值与目标值之间建立的一个关系,这个可以理解为回归方程

1.2.2 线性回归的特征与目标的关系分析

线性回归当中的关系有两种,一种是线性关系,另一种是非线性关系。在这里我们只能画一个平面更好去理解,所以都用单个特征举例子。

  • 线性关系
  • 多变量线性关系

注释:如果在单特征与目标值的关系呈直线关系,或者两个特征与目标值呈现平面的关系, 更高维度的我们不用自己去想,记住这种关系即可

非线性关系

注释:为什么会这样的关系呢?原因是什么?

我们后面 讲解过拟合欠拟合

重点介绍. 如果是非线性关系,那么回归方程可以理解为:

θ0 + θ1×x1 + θ2×x^2 + θ3×x^3

2、线性回归的损失和优化原理(理解记忆)

假设刚才的房子例子,真实的数据之间存在这样的关系:

真实房子价格 = 0.02×中心区域的距离 + 0.04×城市一氧化氮浓度 + (-0.12×自住房平均房价) + 0.254×城镇犯罪率

那么现在呢,我们随意指定一个关系(猜测), 随机指定关系:

预测房子价格 = 0.25×中心区域的距离 + 0.14×城市一氧化氮浓度 + 0.42×自住房平均房价 + 0.34×城镇犯罪率

请问这样的话,会发生什么?真实结果与我们预测的结果之间是不是存在一定的误差呢?类似这样样子

那么存在这个误差,我们将这个误差给衡量出来

2.1 损失函数

总损失定义为:

  • y_i为第i个训练样本的真实值
  • h(x_i)为第i个训练样本特征值组合预测函数
  • 又称最小二乘法

如何去减少这个损失,使我们预测的更加准确些?既然存在了这个损失,我们一直说机器学习有自动学习的功能,在线性回归这里更是能够体现。这里可以通过一些优化方法去优化(其实是数学当中的求导功能)回归的总损失!!!

2.2 优化算法

如何去求模型当中的W,使得损失最小?(目的是找到最小损失对应的W值)

线性回归经常使用的两种优化算法

正规方程

理解:X为特征值矩阵,y为目标值矩阵。直接求到最好的结果
缺点:当特征过多过复杂时,求解速度太慢并且得不到结果

损失行数求解1

梯度下降(Gradient Descent)公式:

理解:α为学习速率,需要手动指定(超参数),α旁边的整体表示方向. 沿着这个函数下降的方向找,最后就能找到山谷的最低点,然后更新W值.
使用:面对训练数据规模十分庞大的任务 ,能够找到较好的结果

我们通过两个图更好理解梯度下降的过程

单变量的梯度下降:

多变量的梯度下降:

所以有了梯度下降这样一个优化算法,回归就有了"自动学习"的能力

2.3 优化动态图演示

线性回归优化动态图

3、 线性回归API

  • sklearn.linear_model.LinearRegression(fit_intercept=True)
    • 通过正规方程优化
    • fit_intercept:是否计算偏置
    • LinearRegression.coef_:回归系数
    • LinearRegression.intercept_:偏置
  • sklearn.linear_model.SGDRegressor(loss="squared_loss", fit_intercept=True, learning_rate ='invscaling', eta0=0.01)
    • SGDRegressor类实现了随机梯度下降学习,它支持不同的loss函数和正则化惩罚项来拟合线性回归模型。
    • loss:损失类型
      • loss=”squared_loss”: 普通最小二乘法
    • fit_intercept:是否计算偏置
    • learning_rate : string, optional
      • 学习率填充
      • 'constant': eta = eta0
      • 'optimal': eta = 1.0 / (alpha * (t + t0)) [default]
      • 'invscaling': eta = eta0 / pow(t, power_t)
        • power_t=0.25:存在父类当中
      • 对于一个常数值的学习率来说,可以使用learning_rate=’constant’ ,并使用eta0来指定学习率。
    • SGDRegressor.coef_:回归系数
    • SGDRegressor.intercept_:偏置

sklearn提供给我们两种实现的API, 可以根据选择使用

4、波士顿房价预测

数据介绍

属性:

给定的这些特征,是专家们得出的影响房价的结果属性。我们此阶段不需要自己去探究特征是否有用,只需要使用这些特征。到后面量化很多特征需要我们自己去寻找

4.1 分析

回归当中的数据大小不一致,是否会导致结果影响较大。所以需要做标准化处理。同时我们对目标值也需要做标准化处理。

  • 数据分割与标准化处理
  • 回归预测
  • 线性回归的算法效果评估


4.2 回归性能评估

均方误差(Mean Squared Error)MSE)评价机制:

线性回归评估:

注:y^i 为预测值,¯y为真实值

  • sklearn.metrics.mean_squared_error(y_true, y_pred)
    • 均方误差回归损失
    • y_true:真实值
    • y_pred:预测值
    • return:浮点数结果

4.2 代码

def mylinearregression():
    """
    线性回归预测房子价格
    :return:
    """
    lb = load_boston()
    #
    # print(lb.data)
    #
    # print(lb.target)

    # 对数据集进行划分
    x_train, x_test, y_train, y_test = train_test_split(lb.data, lb.target, test_size=0.3, random_state=24)

    # 需要做标准化处理对于特征值处理
    std_x = StandardScaler()

    x_train = std_x.fit_transform(x_train)
    x_test = std_x.fit_transform(x_test)
    # print(x_train)

    # 对于目标值进行标准化
    std_y = StandardScaler()

    y_train = std_y.fit_transform(y_train)
    y_test = std_y.transform(y_test)
    y_test = std_y.inverse_transform(y_test)

    # 使用线性模型进行预测
    # 使用正规方程求解
    lr = LinearRegression()
    # # 此时在干什么?
    lr.fit(x_train, y_train)

    y_lr_predict = std_y.inverse_transform(lr.predict(x_test))

    print(lr.coef_)

    print("正规方程预测的结果为:", y_lr_predict)

    print("正规方程的均方误差为:", mean_squared_error(y_test, y_lr_predict))

    # 梯度下降进行预测
    sgd = SGDRegressor()
    #
    sgd.fit(x_train, y_train)
    print("SGD的权重参数为:", sgd.coef_)
    #
    y_sgd_predict = std_y.inverse_transform(sgd.predict(x_test))
    #
    print("SGD的预测的结果为:", y_sgd_predict)
    #
    # # 怎么评判这两个方法好坏
    print("SGD的均方误差为:", mean_squared_error(y_test, y_sgd_predict))

    return None

我们也可以尝试去修改学习率

sgd = SGDRegressor(learning_rate='constant', eta0=0.001)

此时我们可以通过调参数,找到学习率效果更好的值。

4.3 正规方程和梯度下降对比

  • 文字对比
梯度下降正规方程
需要选择学习率不需要
需要迭代求解一次运算得出
特征数量较大可以使用需要计算方程,时间复杂度高O(n3)
  • 选择:
    • 小规模数据:
      • LinearRegression(不能解决拟合问题)
      • 岭回归
    • 大规模数据:SGDRegressor

5、拓展-关于优化方法GD、SGD、SAG

5.1 GD

梯度下降(Gradient Descent),原始的梯度下降法需要计算所有样本的值才能够得出梯度,计算量大,所以后面才有会一系列的改进。

5.2 SGD

随机梯度下降(Stochastic gradient descent)是一个优化方法。它在一次迭代时只考虑一个训练样本。

  • SGD的优点是:
    • 高效
    • 容易实现
  • SGD的缺点是:
    • SGD需要许多超参数:比如正则项参数、迭代数。
    • SGD对于特征标准化是敏感的。

5.3 SAG

随机平均梯度法(Stochasitc Average Gradient),由于收敛的速度太慢,有人提出SAG等基于梯度下降的算法

Scikit-learn:SGDRegressor、岭回归、逻辑回归等当中都会有SAG优化

6、总结

  • 线性回归的损失函数-均方误差
  • 线性回归的优化方法
    • 正规方程
    • 梯度下降
  • 线性回归的性能衡量方法-均方误差
  • sklearn的SGDRegressor API 参数

欠拟合与过拟合

  • 学习目标
    • 说明线性回归(不带正则化)的缺点
    • 说明过拟合与欠拟合的原因以及解决方法

问题:训练数据训练的很好啊,误差也不大,为什么在测试集上面有问题呢?

当算法在某个数据集当中出现这种情况,可能就出现了过拟合现象。

1、 什么是过拟合与欠拟合

  • 欠拟合
  • 过拟合
  • 分析
    • 第一种情况:因为机器学习到的天鹅特征太少了,导致区分标准太粗糙,不能准确识别出天鹅。
    • 第二种情况:机器已经基本能区别天鹅和其他动物了。然后,很不巧已有的天鹅图片全是白天鹅的,于是机器经过学习后,会认为天鹅的羽毛都是白的,以后看到羽毛是黑的天鹅就会认为那不是天鹅。

1.1 定义

  • 过拟合:一个假设在训练数据上能够获得比其他假设更好的拟合, 但是在测试数据集上却不能很好地拟合数据,此时认为这个假设出现了过拟合的现象。(模型过于复杂)
  • 欠拟合:一个假设在训练数据上不能获得更好的拟合,并且在测试数据集上也不能很好地拟合数据,此时认为这个假设出现了欠拟合的现象。(模型过于简单)

那么是什么原因导致模型复杂?线性回归进行训练学习的时候变成模型会变得复杂,这里就对应前面说的线性回归的两种关系,非线性关系的数据,也就是存在很多无用的特征或者现实中的事物特征跟目标值的关系并不是简单的线性关系。

2、 原因以及解决办法

  • 欠拟合原因以及解决办法
    • 原因:学习到数据的特征过少
    • 解决办法:增加数据的特征数量
  • 过拟合原因以及解决办法
    • 原因:原始特征过多,存在一些嘈杂特征, 模型过于复杂是因为模型尝试去兼顾各个测试数据点
    • 解决办法:
      • 正则化

在这里针对回归,我们选择了正则化。但是对于其他机器学习算法如分类算法来说也会出现这样的问题,除了一些算法本身作用之外(决策树、神经网络),我们更多的也是去自己做特征选择,包括之前说的删除、合并一些特征

如何解决?

正则化:

在学习的时候,数据提供的特征有些影响模型复杂度或者这个特征的数据点异常较多,所以算法在学习的时候尽量减少这个特征的影响(甚至删除某个特征的影响),这就是正则化

注:调整时候,算法并不知道某个特征影响,而是去调整参数得出优化的结果

2.1 正则化类别

  • L2正则化
    • 作用:可以使得其中一些W的都很小,都接近于0,削弱某个特征的影响
    • 优点:越小的参数说明模型越简单,越简单的模型则越不容易产生过拟合现象
    • 岭(Ridge)回归
  • L1正则化
    • 作用:可以使得其中一些W的值直接为0,删除这个特征的影响
    • LASSO回归

2.2 拓展-原理(了解)

线性回归的损失函数用最小二乘法,等价于当预测值与真实值的误差满足正态分布时的极大似然估计;

岭回归的损失函数,是最小二乘法+L2范数,等价于当预测值与真实值的误差满足正态分布,且权重值也满足正态分布(先验分布)时的最大后验估计;

LASSO的损失函数,是最小二乘法+L1范数,等价于等价于当预测值与真实值的误差满足正态分布,且且权重值满足拉普拉斯分布(先验分布)时的最大后验估计

线性回归的改进-岭回归

  • 学习目标
    • 说明岭回归的原理即与线性回归的不同之处
    • 说明正则化对于权重参数的影响
    • 说明L1和L2正则化的区别
  • 实际应用
    • 波士顿房价预测

1、 带有L2正则化的线性回归-岭回归

岭回归,其实也是一种线性回归。只不过在算法建立回归方程时候,加上正则化的限制,从而达到解决过拟合的效果

1.1 API

  • sklearn.linear_model.Ridge(alpha=1.0, fit_intercept=True,solver="auto", normalize=False)
    • 具有l2正则化的线性回归
    • alpha:正则化力度,也叫 λ
      • λ取值:0~1 1~10
    • solver:会根据数据自动选择优化方法
      • sag:如果数据集、特征都比较大,选择该随机梯度下降优化
    • normalize:数据是否进行标准化
      • normalize=False:可以在fit之前调用preprocessing.StandardScaler标准化数据
    • Ridge.coef_:回归权重
    • Ridge.intercept_:回归偏置
All last four solvers support both dense and sparse data. However,
only 'sag' supports sparse input when `fit_intercept` is True.

Ridge方法相当于SGDRegressor(penalty='l2', loss="squared_loss"),只不过SGDRegressor实现了一个普通的随机梯度下降学习,推荐使用Ridge(实现了SAG)

  • sklearn.linear_model.RidgeCV(_BaseRidgeCV, RegressorMixin)
    • 具有l2正则化的线性回归,可以进行交叉验证
    • coef_:回归系数
class _BaseRidgeCV(LinearModel):
    def __init__(self, alphas=(0.1, 1.0, 10.0),
                 fit_intercept=True, normalize=False, scoring=None,
                 cv=None, gcv_mode=None,
                 store_cv_values=False):

1.2 观察正则化程度的变化,对结果的影响?

  • 正则化力度越大,权重系数会越小
  • 正则化力度越小,权重系数会越大

1.3 波士顿房价预测

rd = Ridge(alpha=1.0)

rd.fit(x_train, y_train)
print("岭回归的权重参数为:", rd.coef_)

y_rd_predict = std_y.inverse_transform(rd.predict(x_test))

print("岭回归的预测的结果为:", y_rd_predict)


print("岭回归的均方误差为:", mean_squared_error(y_test, y_rd_predict))

分类算法-逻辑回归与二分类

  • 学习目标
    • 说明逻辑回归的损失函数
    • 说明逻辑回归的优化方法
    • 说明sigmoid函数
    • 知道逻辑回归的应用场景
    • 知道精确率、召回率指标的区别
    • 知道F1-score指标说明召回率的实际意义
    • 说明如何解决样本不均衡情况下的评估
    • 了解ROC曲线的意义说明AUC指标大小
    • 应用classification_report实现精确率、召回率计算
    • 应用roc_auc_score实现指标计算
  • 具体应用
    • 二分类问题
      • 癌症患者预测
      • 皮马印第安人糖尿病预测

逻辑回归(Logistic Regression)是机器学习中的一种分类模型,逻辑回归是一种分类算法,虽然名字中带有回归,但是它与回归之间有一定的联系, 根据数据特征计算属于某一类别的概率p(x), 根据概率数值判断其所属类别 。由于算法的简单和高效,在实际中应用非常广泛。

1、逻辑回归的应用场景

  • 广告点击率
  • 是否为垃圾邮件
  • 是否患病
  • 金融诈骗
  • 虚假账号

看到上面的例子,我们可以发现其中的特点,那就是都属于两个类别之间的判断, 比如是不是垃圾邮件,是猫或是狗. 逻辑回归就是解决二分类问题的利器。

(神经网络模型底层也是基于逻辑回归实现的)

2、 逻辑回归的原理

2.1 输入

逻辑回归的输入就是一个线性回归的结果。

2.2 激活函数

  • sigmoid函数

特征值x的概率P(x)等于:

当概率大于等于0.5, 输出y=1;

当概率小于0.5, 输出y=0

其中,y为类别结果, P为概率,x为特征值,θ为常量

  • 分析
    • 回归的结果输入到sigmoid函数当中
    • 输出结果:[0, 1]区间中的一个概率值,默认为0.5为阈值

逻辑回归最终的分类是通过属于某个类别的概率值来判断是否属于某个类别,并且这个类别默认标记为1(正例),另外的一个类别会标记为0(反例)。(方便损失计算)

输出结果解释(重要):假设有两个类别A,B,并且假设我们的概率值为属于A(1)这个类别的概率值。现在有一个样本的输入到逻辑回归输出结果0.6,那么这个概率值超过0.5,意味着我们训练或者预测的结果就是A(1)类别。那么反之,如果得出结果为0.3那么,训练或者预测结果就为B(0)类别。

所以接下来我们回忆之前的线性回归预测结果我们用均方误差衡量,那如果对于逻辑回归,我们预测的结果不对该怎么去衡量这个损失呢?我们来看这样一张图

那么如何去衡量逻辑回归的预测结果与真实结果的差异呢?

2.3 损失以及优化

2.3.1 损失

逻辑回归的损失,称之为对数似然损失,公式如下:

  • 分开类别:

单个对数似然损失

怎么理解单个的式子呢?这个要根据log的函数图像来理解。

当y=1时:

  • 综合完整损失函数

看到这个式子,其实跟我们讲的信息熵类似。

接下来我们呢就带入上面那个例子来计算一遍,就能理解意义了。

我们已经知道,log(P), P值越大,结果越小,所以我们可以对着这个损失的式子去分析

2.3.2 优化

同样使用梯度下降优化算法,去减少损失函数的值。这样去更新逻辑回归前面对应算法的权重参数,提升原本属于1类别的概率,降低原本是0类别的概率。

3、逻辑回归API

  • sklearn.linear_model.LogisticRegression(solver='liblinear', penalty=‘l2’, C = 1.0)
    • solver:优化求解方式(默认开源的liblinear库实现,内部使用了坐标轴下降法来迭代优化损失函数)
      • sag:根据数据集自动选择,随机平均梯度下降
    • penalty:正则化的种类
    • C:正则化力度

默认将类别数量少的当做正例

LogisticRegression方法相当于 SGDClassifier(loss="log", penalty=" "),SGDClassifier实现了一个普通的随机梯度下降学习,也支持平均随机梯度下降法(ASGD),可以通过设置average=True。而使用LogisticRegression(实现了SAG)

4、 案例:癌症分类预测-良/恶性乳腺癌肿瘤预测

  • 数据介绍

原始数据的下载地址:https://archive.ics.uci.edu/ml/machine-learning-databases/

数据描述

(1)699条样本,共11列数据,第一列用语检索的id,后9列分别是与肿瘤

相关的医学特征,最后一列表示肿瘤类型的数值。

(2)包含16个缺失值,用”?”标出。

4.1 分析

  • 缺失值处理
  • 标准化处理
  • 逻辑回归预测

4.2 代码

def logisticregression():
    """
    逻辑回归进行癌症预测
    :return: None
    """
    # 1、读取数据,处理缺失值以及标准化
    column_name = ['Sample code number', 'Clump Thickness', 'Uniformity of Cell Size', 'Uniformity of Cell Shape',
                   'Marginal Adhesion', 'Single Epithelial Cell Size', 'Bare Nuclei', 'Bland Chromatin',
                   'Normal Nucleoli', 'Mitoses', 'Class']

    data = pd.read_csv("https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data",
                       names=column_name)

    # 删除缺失值
    data = data.replace(to_replace='?', value=np.nan)

    data = data.dropna()

    # 取出特征值
    x = data[column_name[1:10]]

    y = data[column_name[10]]

    # 分割数据集
    x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3)

    # 进行标准化
    std = StandardScaler()

    x_train = std.fit_transform(x_train)

    x_test = std.transform(x_test)

    # 使用逻辑回归
    lr = LogisticRegression()

    lr.fit(x_train, y_train)

    print("得出来的权重:", lr.coef_)

    # 预测类别
    print("预测的类别:", lr.predict(x_test))

    # 得出准确率
    print("预测的准确率:", lr.score(x_test, y_test))
    return None

在很多分类场景当中我们不一定只关注预测的准确率!!!!!

手机评判好坏

比如以这个癌症举例子!!!我们并不关注预测的准确率,而是关注在所有的样本当中,癌症患者有没有被全部预测(检测)出来。

5. 案例: 皮马印第安人糖尿病预测

皮马印第安人糖尿病数据集介绍

数据集来自Kaggle, 也可以从飞浆公开数据集中下载

image-20211227180249217

需求: 根据给定皮马印第安人的样本信息, 包括怀孕次数, 胰岛素水平, 体重指数, 年龄四个特征, 预测是否患有糖尿病(二分类)

导入数据集

 import pandas as pd
 path = '../data/pima-indians-diabetes.csv'
 pima = pd.read_csv(path)
 ​
 # 由于csv文件中缺少表头第一行描述信息, 下面将描述信息插入到第一行
 pima.loc[-1] = pima.columns.tolist()
 pima.index = pima.index + 1
 pima.sort_index(inplace=True)
 pima.columns = ['pregnant', 'glucose', 'bp', 'skin', 'insulin', 'bmi', 'pedigree', 'age', 'label']
 pima.head()

输出​

pregnantglucosebpskininsulinbmipedigreeagelabel
061487235033.60.627501
11856629026.60.351310
28183640023.30.672321
318966239428.10.167210
40137403516843.12.288331

保存特征和标签到变量中

# X y赋值
 feature_names = ['pregnant', 'insulin', 'bmi', 'age']
 X = pima[feature_names]
 y = pima.label
 # 避免后续在logreg.fit时不识别标签的类型object
 y= y. astype('int')
 # 维度确认
 print(X.shape)
 print(y.shape)
 (768, 4)
 (768,)

将数据集合分割为训练集和测试集

 # 数据分离
 from sklearn.model_selection import train_test_split
 X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

训练模型

 # 模型训练
 from sklearn.linear_model import LogisticRegression
 logreg = LogisticRegression()
 logreg.fit(X_train, y_train)

确认模型

 LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
                    intercept_scaling=1, l1_ratio=None, max_iter=100,
                    multi_class='auto', n_jobs=None, penalty='l2',
                    random_state=None, solver='lbfgs', tol=0.0001, verbose=0,
                    warm_start=False)

使用模型预测结果

 # 测试数据集结果预测
 y_pred = logreg.predict(X_test)

计算准确率

 # 使用准确率进行评估
 from sklearn import metrics
 print(metrics.accuracy_score(y_test, y_pred))
 0.6770833333333334

确认正负样本数据量

 # 确认正负样本数据量
 y_test.value_counts()

输出:

 0    130
 1     62
 Name: label, dtype: int64

判断为正确(1)的比例:

 # 1的比例
 y_test.mean()

输出:

 0.3229166666666667

判断为正确(0)的比例:

 # 0的比例
 1 - y_test.mean()

输出:

 0.6770833333333333

空准确率:

 # 空准确率(都预测了占比多的标签时的正确率)
 max(y_test.mean(), 1 -y_test.mean())

输出:

 0.6770833333333333

发现总的准确率还不如空准确率, 所以这个模型看起来并不是很成功 结论, 分类准确率只能衡量整体预测效果, 但是没有反映真实细节信息

 # 比对前面25组样本的正样本的预测情况
 print(y_test.values[:25])
 print(y_pred[:25] )

输出:

 [1 0 0 1 0 0 1 1 0 0 1 1 0 0 0 0 1 0 0 0 1 1 0 0 0]
 [0 0 0 0 0 0 0 1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0]

6、分类的评估方法

使用准确率进行模型评估的局限性

模型评估回顾

  • 目的: 通过模型评估对比模型表现, 确定合适的模型参数
  • 方法: 计算测试数据集预测准确率以评估模型表现

使用准确率预测的局限性:

  • 无法真实反映模型针对各分类的预测准确度

比如对于0-1预测的准确率:

image-20211227180639988

准确率的评估只能看到整体, 无法得知细节, 比如:

比如1000个数据(900个实际分类是1,100个实际分类是0)

全部预测为1 , 整体准确率是90%

虽然1的准确率是100%, 而0被全部预测为1错误率100%就有大问题了.

6.1 混淆矩阵, 模型衡量指标及其意义

为了解决上述问题, 为了衡量分类算法的准确程度, 这里引入了混淆矩阵, 又称为误差矩阵

6.1.1混淆矩阵

在分类任务下,预测结果(Predicted Condition)与正确标记(True Condition)之间存在四种不同的组合,构成混淆矩阵(适用于多分类)

此图片的alt属性为空;文件名为image-111-1024x355.png

把样本分成四类:真正例 (TP), 伪正例(FP), 真反例(TN) , 伪 反 例(FN)

6.1.2 混淆矩阵的六个指标:

混淆矩阵指标的特点:

  • 分类任务中, 相比单一的预测准确率, 混淆矩阵提供了更全面的模型评估信息
  • 通过混淆矩阵, 我们可以计算出多样的模型表现衡量指标, 从而更好的选择模型

哪个指标更关键?

  • 取决应用场景
  • 对于垃圾邮件检测来说, 正样本为"垃圾邮件", 希望普通邮件(负样本)不要被判断为垃圾邮件(正样本)
    • 希望判断为垃圾邮件的样本都是判断正确的, 需要关注精确率
    • 希望所有的垃圾邮件都能尽可能被判断出来, 还需要关注召回率
  • 对于异常交易检测来说, 正样本为"异常交易"
    • 希望所有的异常交易都被检测到,即判断为正常的交易中尽可能不存在异常交易, 需要关注特异度

精确率(Precision)与召回率(Recall)来比较:

  • 精确率:从你预测结果为正例的样本中, 找出真实也为正例的样本的比例
  • 召回率:在真实为正例的样本中, 找出全部预测结果为正例的样本所占比例(查的全,对正样本的区分能力)

那么怎么更好理解这个两个概念,看下图:

还有其他的评估标准,F1-score,反映了模型的稳健型

下面使用混淆矩阵来对上述皮马印第安人糖尿病预测模型进行评估:

计算并展示混淆矩阵

 # 计算并展示混淆矩阵
 confution_matrix = metrics.confusion_matrix(y_test, y_pred)
 print(confution_matrix)
 [[114  16]
  [ 46  16]]
image-20211227181325046

计算从混淆矩阵得来的各种指标:

 # 将矩阵中的四个结果赋值
 TN = confution_matrix[0, 0]
 FP = confution_matrix[0, 1]
 FN = confution_matrix[1, 0]
 TP = confution_matrix[1, 1]
 # 计算指标 
 # 精确度 - 整体样本中预测正确的比率, 和前面得出的准确率结果相等
 accuracy = (TP + TN)/(TP + TN + FP + FN)
 print("accuracy_rate: ", accuracy)
 # 错误率 - 整体样本中预测错误的比率, 可以用 (100% - 精确度) 直接得出
 miss_rate = 1 - accuracy
 print("miss_rate: ", accuracy)
 # 召回率 - 在你认为是正样本的样本中, 预测正确的比率
 recall = TP/(TP + FN)
 print("recall_rate: ", recall)
 # 特异度 - 在你认为是负样本的样本中, 预测正确的比率
 specificity = TN/(TN + FP)
 print("specificity_rate: ", specificity)
 # 精确率 - 在预测结果为正的样本中, 预测正确的比率 
 precision = TP/(TP + FP)
 print("precision_rate: ", precision)
 # F1分数 - 综合 precision 和 recall 的一个判断指标
 f1_score = (2 * precision * recall)/(precision + recall)
 print("f1_score: ", precision)
 ​
 accuracy_rate:  0.6770833333333334
 miss_rate:  0.6770833333333334
 recall_rate:  0.25806451612903225
 specificity_rate:  0.8769230769230769
 precision_rate:  0.5
 f1_score:  0.5

观察以上结果, 发现这个模型还是很有问题的, 最大的问题就是召回率过低 - 即在你认为是负样本的样本中预测正确的成功几率过低. 当然最终需要得到什么样的模型, 还是取决于实际应用.

6.1.3 分类评估报告API
  • sklearn.metrics.classification_report(y_true, y_pred, labels=[], target_names=None )
    • y_true:真实目标值
    • y_pred:估计器预测目标值
    • labels:指定类别对应的数字
    • target_names:目标类别名称
    • return:每个类别精确率与召回率
print("精确率和召回率为:", classification_report(y_test, lr.predict(x_test), labels=[2, 4], target_names=['良性', '恶性']))

假设这样一个情况,如果99个样本癌症,1个样本非癌症,不管怎样我全都预测正例(默认癌症为正例),准确率就为99%但是这样效果并不好,这就是样本不均衡下的评估问题

问题:如何衡量样本不均衡下的评估?

6.2 ROC曲线与AUC指标

6.2.1 知道TPR与FPR
  • TPR = TP / (TP + FN)
    • 所有真实类别为1的样本中,预测类别为1的比例
  • FPR = FP / (FP + FN)
    • 所有真实类别为0的样本中,预测类别为1的比例

6.2.2 ROC曲线

  • ROC曲线的横轴就是FPRate,纵轴就是TPRate,当二者相等时,表示的意义则是:对于不论真实类别是1还是0的样本,分类器预测为1的概率是相等的,此时AUC为0.5
6.2.3AUC指标
  • AUC的概率意义是随机取一对正负样本,正样本得分大于负样本的概率
  • AUC的最小值为0.5,最大值为1,取值越高越好
  • AUC=1,完美分类器,采用这个预测模型时,不管设定什么阈值都能得出完美预测。绝大多数预测的场合,不存在完美分类器。
  • 0.5<AUC<1,优于随机猜测。这个分类器(模型)妥善设定阈值的话,能有预测价值。

最终AUC的范围在[0.5, 1]之间,并且越接近1越好

6.2.4 AUC计算API
  • from sklearn.metrics import roc_auc_score
    • sklearn.metrics.roc_auc_score(y_true, y_score)
      • 计算ROC曲线面积,即AUC值
      • y_true:每个样本的真实类别,必须为0(反例),1(正例)标记
      • y_score:每个样本预测的概率值
# 0.5~1之间,越接近于1约好
y_test = np.where(y_test > 2.5, 1, 0)

print("AUC指标:", roc_auc_score(y_test, lr.predict(x_test)))
6.2.5、总结
  • AUC只能用来评价二分类
  • AUC非常适合评价样本不平衡中的分类器性能

模型保存和加载

  • 学习目标
    • 应用joblib实现模型的保存与加载

当训练或者计算好一个模型之后,那么如果别人需要我们提供结果预测,就需要保存模型(主要是保存算法的参数)

1、sklearn模型的保存和加载API

  • from sklearn.externals import joblib
    • 保存:joblib.dump(rf, 'test.pkl')
    • 加载:estimator = joblib.load('test.pkl')

2、线性回归的模型保存加载案例

  • 保存
# 使用线性模型进行预测
# 使用正规方程求解
lr = LinearRegression()
# 此时在干什么?
lr.fit(x_train, y_train)
# 保存训练完结束的模型
joblib.dump(lr, "test.pkl")
  • 加载
# 通过已有的模型去预测房价
model = joblib.load("test.pkl")
print("从文件加载进来的模型预测房价的结果:", std_y.inverse_transform(model.predict(x_test)))

无监督学习-K-means算法

  • 学习目标
    • 说明K-means算法原理
    • 说明K-means的性能评估标准轮廓系数
    • 说明K-means的优缺点
  • 实际应用
    • instacart用户聚类

回忆非监督学习的特点?

1、 什么是无监督学习

  • 一家广告平台需要根据相似的人口学特征和购买习惯将美国人口分成不同的小组,以便广告客户可以通过有关联的广告接触到他们的目标客户。
  • Airbnb 需要将自己的房屋清单分组成不同的社区,以便用户能更轻松地查阅这些清单。
  • 一个数据科学团队需要降低一个大型数据集的维度的数量,以便简化建模和降低文件大小。

我们可以怎样最有用地对其进行归纳和分组?我们可以怎样以一种压缩格式有效地表征数据?这都是无监督学习的目标,之所以称之为无监督,是因为这是从无标签的数据开始学习的。

2、 无监督学习包含算法

  • 聚类
    • K-means(K均值聚类)
  • 降维
    • PCA

3、 K-means原理

我们先来看一下一个K-means的聚类效果图

K-means 如何聚类

3.1 K-means聚类步骤

  • 1、随机设置K个特征空间内的点作为初始的聚类中心
  • 2、对于其他每个点计算到K个中心的距离,未知的点选择最近的一个聚类中心点作为标记类别
  • 3、接着对着标记的聚类中心之后,重新计算出每个聚类的新中心点(平均值)
  • 4、如果计算得出的新中心点与原中心点一样,那么结束,否则重新进行第二步过程

我们以一张图来解释效果

4、K-meansAPI

  • sklearn.cluster.KMeans(n_clusters=8,init=‘k-means++’)
    • k-means聚类
    • n_clusters:开始的聚类中心数量
    • init:初始化方法,默认为'k-means ++’
    • labels_:默认标记的类型,可以和真实值比较(不是值比较)

5、 案例:k-means对Instacart Market用户聚类

5.1 分析

  • 1、降维之后的数据
  • 2、k-means聚类
  • 3、聚类结果显示

5.2 代码

# 取500个用户进行测试
cust = data[:500]
km = KMeans(n_clusters=4)
km.fit(cust)
pre = km.predict(cust)

问题:如何去评估聚类的效果呢?

6、Kmeans性能评估指标

6.1 轮廓系数

轮廓系数公式:

注:对于每个点i 为已聚类数据中的样本 ,b_i 为i 到其它族群的所有样本的距离最小值,a_i 为i 到本身簇的距离平均值。最终计算出所有的样本点的轮廓系数平均值

6.2 轮廓系数值分析

  • 分析过程(我们以一个蓝1点为例)
    • 1、计算出蓝1离本身族群所有点的距离的平均值a_i
    • 2、蓝1到其它两个族群的距离计算出平均值红平均,绿平均,取最小的那个距离作为b_i
    • 根据公式:极端值考虑:如果b_i >>a_i: 那么公式结果趋近于1;如果a_i>>>b_i: 那么公式结果趋近于-1

6.3 结论

如果b_i>>a_i:趋近于1效果越好, b_i<<a_i:趋近于-1,效果不好。轮廓系数的值是介于 [-1,1] ,越趋近于1代表内聚度和分离度都相对较优。

6.4 轮廓系数API

  • sklearn.metrics.silhouette_score(X, labels)
    • 计算所有样本的平均轮廓系数
    • X:特征值
    • labels:被聚类标记的目标值

6.5 用户聚类结果评估

silhouette_score(cust, pre)

7、K-means总结

  • 特点分析:采用迭代式算法,直观易懂并且非常实用
  • 缺点:容易收敛到局部最优解(多次聚类)

注意:聚类一般做在分类之前

课后回顾

1、线性回归的参数求解的方法是什么?

答案: 正规方程和梯度下降

2、什么是过拟合? 原因有哪些?

答案: 过拟合就是训练误差很小, 但是测试误差很大

​ 原因有: 样本偏差, 模型过于复杂

3、分类问题, 回归问题, 聚类问题的评估方法分别是什么?

答案: 分类问题的评估方法是准确率, 精确率和召回率

回归问题的评估方法是均方差

聚类问题的评估方法是轮廓系数

机器学习的算法选择

以上我们学习了这么多种有关机器学习的算法, 那么面对具体问题的时候该如何选择呢?

sklearn的官方手册给出了一份 cheat sheet, 可以简单的解决你的困惑.

英文版

中文版

Views: 312