Wetts's blog

Stay Hungry, Stay Foolish.

0%

  1. 打开 console 出现如下提示

    1
    2
    3
    The default interactive shell is now zsh.
    To update your account to use zsh, please run `chsh -s /bin/zsh`.
    For more details, please visit https://support.apple.com/kb/HT208050.
  2. 查看系统支持的shell风格命令:

    1
    cat /etc/shells
  3. 解决方法

    1. 修改默认 shell: chsh -s /bin/zsh
    2. 继续使用bash,但又不想出现提示语,则在 ~/.bash_profile 中添加 export BASH_SILENCE_DEPRECATION_WARNING=1

修改对 django 项目中的错误语法提示

  1. 安装 pylint-django 的 Python 包
  2. 在 settings.json 中添加如下配置
    1
    2
    3
    4
    "python.linting.pylintArgs": [
    "--load-plugins",
    "pylint_django"
    ],

1

原因是

1
It is nice for a Python module to have a docstring, explaining what the module does, what it provides, examples of how to use the classes. This is different from the comments that you often see at the beginning of a file giving the copyright and license information, which IMO should not go in the docstring (some even argue that they should disappear altogether, see eg.

2

原因是

1
2
3
As your code is not contained in a class or function it is expecting those variables to be constants and as such they should be uppercase.

You can read PEP8 for further information.

通过 VSCode 的插件 Settings Sync 可以同步配置信息。

1
2
3
4
5
6
7
8
9
10

在另外的电脑上需要读取配置的话,将如下 gist id 和 token 都输入到新机器就能同步了。
11

在项目的 launch.json 中添加如下配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"name": "Django",
"type": "python",
"request": "launch",
"stopOnEntry": false,
"pythonPath": "${config:python.pythonPath}",
"program": "${workspaceRoot}/manage.py",
"args": [
"runserver",
"--no-color",
"--noreload"
],
"debugOptions": [
"WaitOnAbnormalExit",
"WaitOnNormalExit",
"RedirectOutput",
"DjangoDebugging"
]
}

在 Mac 环境下配置:菜单栏 Code -> 首选项 -> 用户代码片段

自行按需添加如下文件
1

例如 Python 的头文件可设置为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"PY_HEADER": {
"prefix": "header",
"body": [
"# -*- encoding: utf-8 -*-",
"'''",
"@File : $TM_FILENAME",
"@Time : $CURRENT_YEAR/$CURRENT_MONTH/$CURRENT_DATE $CURRENT_HOUR:$CURRENT_MINUTE:$CURRENT_SECOND",
"@Author : Zhang Wensong",
"@Version : 1.0",
"@Contact : zhang.wetts@163.com",
"@Desc : $1",
"'''",
"",
"# here put the import lib",
"$0"
],
}

Markdown 的头文件可设置为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"MD_HEADER": {
"prefix": "header",
"body": [
"title: $TM_FILENAME",
"date: $CURRENT_YEAR/$CURRENT_MONTH/$CURRENT_DATE $CURRENT_HOUR:$CURRENT_MINUTE:$CURRENT_SECOND",
"tags:",
"- $1",
"categories:",
"- $2",
"",
"---",
"",
"$0"
],
}

可能遇到的问题:在 Markdown 文件中无法使用代码片段

在 settings.json 中添加如下配置

1
2
3
"[markdown]": {
"editor.quickSuggestions": true
}

转自:https://blog.csdn.net/zzti_erlie/article/details/94396113

mybatis 利用动态代理帮我们生成了接口的实现类,这个类就是 org.apache.ibatis.binding.MapperProxy

静态代理

又是一年毕业季,很多小伙伴开始去大城市打拼。来大城市第一件事就是租房,免不了和中介打交道,因为很多房东很忙,你根本找不到他。从这个场景中就可以抽象出来代理模式

  • ISubject: 被访问者资源的抽象
  • SubjectImpl: 被访问者具体实现类(房东)
  • SubjectProxy: 被访问者的代理实现类(中介)

UML图如下

1

举个例子来理解一下这个设计模式

老板让记录一下用户服务的响应时间,用代理模式来实现这个功能。

1
2
3
public interface IUserService {
public void request();
}
1
2
3
4
5
6
public class UserServiceImpl implements IUserService {
@Override
public void request() {
System.out.println("this is userService");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class UserServiceProxy implements IUserService {

private IUserService userService;

public UserServiceProxy(IUserService userService) {
this.userService = userService;
}

@Override
public void request() {
long startTime = System.currentTimeMillis();
userService.request();
System.out.println("reques cost :" + (System.currentTimeMillis() - startTime));
}

public static void main(String[] args) {
IUserService userService = new UserServiceImpl();
UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
// this is userService
// reques cost :0
userServiceProxy.request();
}
}

一切看起来都非常的美好,老板又发话了,把产品服务的响应时间也记录一下吧。又得写如下 3 个类

  • IProductService
  • ProductServiceImpl
  • ProductServiceProxy

UserServiceProxy 和 ProductServiceProxy 这两个代理类的逻辑都差不多,却还得写 2 次。其实这个还好,如果老板说,把现有系统的几十个服务的响应时间都记录一下吧,你是不是要疯了?这得写多少代理类啊?

动态代理

黑暗总是暂时的,终究会迎来黎明,在 JDK1.3 之后引入了一种称之为动态代理(Dynamic Proxy)的机制。使用该机制,我们可以为指定的接口在系统运行期间动态地生成代理对象,从而帮助我们走出最初使用静态代理实现 AOP 的窘境

动态代理的实现主要由一个类和一个接口组成,即 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口。

让我们用动态代理来改造一下上面记录系统响应时间的功能。虽然要为 IUserService 和 IProductService 两种服务提供代理对象,但因为代理对象中要添加的横切逻辑是一样的。所以我们只需要实现一个 InvocationHandler 就可以了。代码如下

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
public class RequestCostInvocationHandler implements InvocationHandler {

private Object target;

public RequestCostInvocationHandler(Object target) {
this.target = target;
}

/** 被代理对象的任何方法被执行时,都会先进入这个方法 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("request")) {
long startTime = System.currentTimeMillis();
// 执行目标对象的方法
method.invoke(target, args);
System.out.println("reques cost :" + (System.currentTimeMillis() - startTime));
}
return null;
}

public static void main(String[] args) {

// 3个参数解释如下
// classloader,生成代理类
// 代理类应该实现的接口
// 实现InvocationHandler的切面类
IUserService userService = (IUserService) Proxy.newProxyInstance(IUserService.class.getClassLoader(),
new Class[]{IUserService.class}, new RequestCostInvocationHandler(new UserServiceImpl()));

IProductService productService = (IProductService) Proxy.newProxyInstance(IProductService.class.getClassLoader(),
new Class[]{IProductService.class}, new RequestCostInvocationHandler(new ProductServiceImpl()));

// this is userService
// reques cost :0
userService.request();

// this is productService
// reques cost :0
productService.request();
}

}

UML 图如下。恭喜你,你现在已经理解了 Spring AOP 是怎么回事了,就是这么简单,今天先不展开谈 Spring

2

先简单谈谈动态代理在 Mybatis 中是如何被大佬玩的出神入化的

Mybatis 核心设计思路

相信用过 mybatis 的小伙伴都能理解下面这段代码,通过 roleMapper 这个接口直接从数据库中拿到一个对象

1
Role role = roleMapper.getRole(3L);

直觉告诉我,一个接口是不能运行的啊,一定有接口的实现类,可是这个实现类我自己没写啊,难道 mybatis 帮我们生成了?你猜的没错,mybatis 利用动态代理帮我们生成了接口的实现类,这个类就是 org.apache.ibatis.binding.MapperProxy,我先画一下 UML 图,MapperProxy 就是下图中的 SubjectProxy 类

3
和上面的 UML 类图对比一下,发现不就少了一个 SubjectImpl 类吗?那应该就是 SubjectProxy 类把 SubjectImple 类要做的事情做了呗,猜对了。SubjectProxy 通过 SubjectImple 和 SubjectImple.xml 之间的映射关系知道自己应该执行什么 SQL。所以 mybatis 最核心的思路就是这么个意思,细节之类的可以看源码,理清最主要的思路,看源码就能把握住重点。

Mybatis插件原理

mybatis 的插件也用到了动态代理,还用到了责任链模式,我就不从源码角度分析了。说一下大概实现,我们用插件肯定是为了在原先的基础上增加新功能。增加一个插件,mybatis 就在原先类的基础上用动态代理生成一个代理对象,如果有多个插件,就在代理对象的基础上再生成代理对象,形式和如下函数差不多

1
plugin2( plugin1( start() ) )

我再给你写个例子,你再看看相关的源码分析文章(也许我以后会写),很快就能理解了。

在 mybatis 中要想用插件,有如下 2 个步骤

  1. 在 mybatis-config.xml 中配置插件,如下所示
    1
    2
    3
    4
    <plugins>
    <plugin interceptor="org.xrq.mybatis.plugin.FirstInterceptor" />
    <plugin interceptor="org.xrq.mybatis.plugin.SecondInterceptor" />
    </plugins>
  2. 插件类还得实现 Interceptor 接口

我现在给一个需求,一个应用返回字符串 0,我加一个插件在字符串的左右两边加 plugin1,再加一个插件在字符串的左右两边加 plugin2,开写

返回字符串的接口

1
2
3
4
public interface IGetStr {
public String getStrZero();
public String getStrOne();
}

返回字符串的实现类

1
2
3
4
5
6
7
8
9
10
11
12
public class GetStrImpl implements IGetStr {

@Override
public String getStrZero() {
return "0";
}

@Override
public String getStrOne() {
return "1";
}
}

定义拦截器接口

1
2
3
4
5
6
7
8
9
10
11
12
public interface Interceptor {

/** 执行拦截逻辑的方法 */
Object intercept(Invocation invocation);

/**
* target是被拦截的对象,它的作用是给被拦截对象生成一个代理对象,并返回它。
* 为了方便,可以直接使用Mybatis中org.apache.ibatis.plugin类的wrap方法(是静态方法)生成代理对象
* 我这里也写了一个Plugin方法
*/
Object plugin(Object target);
}

看到一个不认识的类Invocation,定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Invocation {

private final Object target;
private final Method method;
private final Object[] args;

public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}

public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}

就是简单的封装了一下目标对象,目标方法和目标方法的参数。proceed方法就是执行目标对象的目标方法

Plugin 算是一个工具类,生成代理对象

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
public class Plugin implements InvocationHandler {

/** 目标对象 */
private final Object target;
/** Interceptor对象 */
private final Interceptor interceptor;

public Plugin(Object target, Interceptor interceptor) {
this.target = target;
this.interceptor = interceptor;
}

/** 生成代理对象 */
public static Object wrap(Object target, Interceptor interceptor) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
new Class[]{IGetStr.class},
new Plugin(target, interceptor));
}

/** 被代理对象的方法执行时,这个方法会被执行 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 只为方法getStrZero生成代理对象
if (method.getName().equals("getStrZero")) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
}
}

写第一个插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class FirstInterceptor implements Interceptor {


/** 执行拦截逻辑的方法 */
@Override
public Object intercept(Invocation invocation) {
try {
return "plugin1 " + invocation.proceed() + " plugin1";
} catch (Exception e) {
return null;
}
}

/** 为原先的类生成代理对象 */
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}

有 2 个方法

plugin 是为插件生成代理对象,用了我自己写的 Plugin 工具类
intercept 是增加拦截逻辑,invocation.proceed() 是执行目标对象的目标方法,前文说过了哈,这里我们只对输出做了改变

第二个插件和第一个插件类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SecondInterceptor implements Interceptor {

@Override
public Object intercept(Invocation invocation) {
try {
return "plugin2 " + invocation.proceed() + " plugin2";
} catch (Exception e) {
return null;
}
}

@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
}

用一个容器保存插件,这里用到了责任链模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class InterceptorChain {

/** 放拦截器 */
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}

public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
}

pluginAll 方法是精髓,为每个插件一层一层的生成代理对象,就像套娃娃一样。

验证一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Main {

public static void main(String[] args) {

// 配置插件
InterceptorChain interceptorChain = new InterceptorChain();
interceptorChain.addInterceptor(new FirstInterceptor());
interceptorChain.addInterceptor(new SecondInterceptor());

// 获得代理对象
IGetStr getStr = new GetStrImpl();
getStr = (IGetStr) interceptorChain.pluginAll(getStr);

String result = getStr.getStrZero();
// plugin2 plugin1 0 plugin1 plugin2
System.out.println(result);

result = getStr.getStrOne();
// 1
System.out.println(result);
}
}

大功告成,可以看到先定义的插件先执行。

类有点多,如果看的有点晕,多看几次,你就很容易理解了,我这里还是精简了很多。

一个 InvocationHandler 接口被大佬玩出了新境界,果然编程这件事还得靠想象力

  • 双线性映射:在数论中,一个双线性映射是由两个向量空间上的元素,生成第三个向量空间上一个元素之函数,并且该函数对每个参数都是线性的。例如矩阵乘法就是一个例子。

    • 矩阵乘法是双线性映射 ${\displaystyle M(m,n)\times M(n,p)\rightarrow M(m,p)}$。
    • 如果在实数 ${\displaystyle \mathbb {R} }$ 上的向量空间 ${\displaystyle V}$ 承载了内积,则内积是双线性映射 ${\displaystyle V\times V\rightarrow \mathbb {R} }$。
  • 行列式表示空间线性变换后面积缩放的比例。

  • 秩表示变换后的空间维数。更精确的定义是列空间的维数。

  • 变换后落在原点的向量集合被称为所选矩阵的“零空间“(Null space)或”核“(Kernel)。

  • 一个多维空间到一维空间的线性变换的对偶是多维空间中的某个特定向量。

转自:https://blog.csdn.net/zzti_erlie/article/details/102769447

介绍

在分布式系统中,分布锁是一个最基础的工具类。例如,部署了2个有付款功能的微服务中,用户有可能对一个订单发起2次付款操作,而这2次请求可能被发到2个服务中,所以必须得用分布式锁防止重复提交,获取到锁的服务正常进行付款操作,获取不到锁的服务提示重复操作。

我司封装了大量的基础工具类,当我们想使用分布式锁的时候只要做3件事情

  1. 在数据库中建globallocktable表
  2. 引入相应的jar包
  3. 在代码中写上@Autowired GlobalLockComponent globalLockComponent即可使用这个组件

看完这篇文章你也可以用springboot-starter的方式实现一个同样的功能。

建表

1
2
3
4
5
6
7
CREATE TABLE `globallocktable` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`lockKey` varchar(60) NOT NULL COMMENT '锁名称',
`createTime` datetime NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `lockKey` (`lockKey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='全局锁';

让别人使用的组件

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
@Component
public class GlobalLockComponent {

@Resource
private GlobalLockTableDAO globalLockDAO;

/**
* 尝试获得锁,成功为true,失败为false
*/
public boolean tryLock(String key) {
return GlobalLockUtil.tryLock(this.globalLockDAO, key);
}

/**
* 如果已经有其他程序占用该锁,并且超过timeoutMs(毫秒)时间,就强制清除这个锁占用
* 即根据key先删除记录,再添加记录
*/
public boolean tryLockWithClear(String key, Long timeoutMs) {
return GlobalLockUtil.tryLockWithClear(this.globalLockDAO, key, timeoutMs);
}

/**
* 释放锁,根据key删除记录
*/
public void releasLock(String key) {
GlobalLockUtil.releasLock(this.globalLockDAO, key);
}

}

锁对象定义如下

1
2
3
4
5
6
7
public class GlobalLockTable {

private Integer id;
private String lockKey;
private Date createTime;
// 省略get和set方法
}

GlobalLockTableDAO定义如下

1
2
3
4
5
6
7
8
9
10
public interface GlobalLockTableDAO {

int deleteByPrimaryKey(Integer id);

int deleteByLockKey(String lockKey);

GlobalLockTable selectByLockKey(String key);

int insertSelectiveWithTest(GlobalLockTable record);
}

具体加锁和解锁逻辑

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
public class GlobalLockUtil {

private static Logger logger = LoggerFactory.getLogger(GlobalLockUtil.class);

private static GlobalLockTable tryLockInternal(GlobalLockTableDAO lockDAO, String key) {
GlobalLockTable insert = new GlobalLockTable();
insert.setCreateTime(new Date());
insert.setLockKey(key);
// 注意的地方1
int count = lockDAO.insertSelectiveWithTest(insert);
if (count == 0) {
GlobalLockTable ready = lockDAO.selectByLockKey(key);
logger.warn("can not lock the key: {}, {}, {}", insert.getLockKey(), ready.getCreateTime(),
ready.getId());
return ready;
}
logger.info("yes got the lock by key: {}", insert.getId(), insert.getLockKey());
return null;
}

/** 超时清除锁占用,并重新加锁 **/
public static boolean tryLockWithClear(GlobalLockTableDAO lockDAO, String key, Long timeoutMs) {
GlobalLockTable lock = tryLockInternal(lockDAO, key);
if (lock == null) return true;
if (System.currentTimeMillis() - lock.getCreateTime().getTime() <= timeoutMs) {
logger.warn("sorry, can not get the key. : {}, {}, {}", key, lock.getId(), lock.getCreateTime());
return false;
}
logger.warn("the key already timeout wthin : {}, {}, will clear", key, timeoutMs);
// 注意的地方2
int count = lockDAO.deleteByPrimaryKey(lock.getId());
if (count == 0) {
logger.warn("sorry, the key already preemptived by others: {}, {}", lock.getId(), lock.getLockKey());
return false;
}
lock = tryLockInternal(lockDAO, key);
return lock != null ? false : true;
}

/** 加锁 **/
public static boolean tryLock(GlobalLockTableDAO lockDAO, String key) {
return tryLockInternal(lockDAO, key) == null ? true : false;
}

/** 解锁 **/
public static void releasLock(GlobalLockTableDAO lockDAO, String key) {
lockDAO.deleteByLockKey(key);
}
}

这个工具类有2个特别有意思的地方,先看注意的地方2(上面代码中标识了)

  1. 为了避免锁长时间不释放,用Redis实现的话可以设置锁超时时间,超时自动释放(后面会写用Redis实现分布式锁)用MySQL实现的话可以先删除后添加。可以看到删除的时候使用id删的,不是用name删的。为啥呢?先自己想一下

因为如果是通过name删的话,有可能别人删了这个锁后,又通过name加了锁,还没到超时时间,结果你却根据name删除了。通过id删的话,当返回的id=0时,说明别人已经重新加锁了,你需要重新获取。

  1. GlobalLockTable 对象dao层的其他方法都见名知意,来看一个这个方法。即代码中的注意点1
    可以看到每次尝试加锁的时候,并不是先select,而是直接insertSelectiveWithTest,这样就少了一个查询时间,提高了效率

insertSelectiveWithTest的作用是当lockKey存在时不进行插入操作,返回0。当lockKey不存在时进行插入操作,返回1

1
2
3
4
5
6
7
<insert id="insertSelectiveWithTest" useGeneratedKeys="true" keyProperty="id" parameterType="com.javashitang.middleware.lock.mysql.pojo.GlobalLockTable">
insert into `globallocktable` (`id`,
`lockKey`, `createTime` )
select #{id,jdbcType=INTEGER}, #{lockKey,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}
from dual where not exists
(select 1 from globallocktable where lockKey = #{lockKey,jdbcType=VARCHAR})
</insert>

使用

当我们想使用时,就只写业务逻辑就行了,非常方便

1
2
3
4
5
6
7
8
9
10
if (!globalLockComponent.tryLock(name)) {
// 没有获取到锁返回
return;
}
try {
// 这里写业务逻辑
} catch (Exception e) {
} finally {
globalLockComponent.releasLock(name)
}

转自:https://zhuanlan.zhihu.com/p/37609917

协方差矩阵在统计学和机器学习中随处可见,一般而言,可视作方差和协方差两部分组成,即方差构成了对角线上的元素,协方差构成了非对角线上的元素。本文旨在从几何角度介绍我们所熟知的协方差矩阵。

方差和协方差的定义

在统计学中,方差是用来度量单个随机变量的离散程度,而协方差则一般用来刻画两个随机变量的相似程度,其中,方差 的计算公式为
$$
\sigma_{x}^{2}=\frac{1}{n-1} \sum_{i=1}^{n}\left(x_{i}-\overline{x}\right)^{2}
$$
其中,$n$ 表示样本量,符号 $\overline{x}$ 表示观测样本的均值,这个定义在初中阶段就已经开始接触了。

在此基础上,协方差 的计算公式被定义为
$$
\sigma(x, y)=\frac{1}{n-1} \sum_{i=1}^{n}\left(x_{i}-\overline{x}\right)\left(y_{i}-\overline{y}\right)
$$
在公式中,符号 $\overline{x}, \overline{y}$ 分别表示两个随机变量所对应的观测样本均值,据此,我们发现:方差 $\sigma^2$ 可视作随机变量 $x$ 关于其自身的协方差 $\sigma(x, x)$ .

从方差/协方差到协方差矩阵

根据方差的定义,给定 $d$ 个随机变量 $x_k, k=1, 2,…,d$ ,则这些随机变量的方差为
$$
\sigma\left(x_{k}, x_{k}\right)=\frac{1}{n-1} \sum_{i=1}^{n}\left(x_{k i}-\overline{x}{k}\right)^{2}, k=1,2, \ldots, d
$$
其中,为方便书写,$x
{ki}$ 表示随机变量 $x_k$ 中的第 $i$ 个观测样本,$n$ 表示样本量,每个随机变量所对应的观测样本数量均为 $n$。

对于这些随机变量,我们还可以根据协方差的定义,求出 两两之间的协方差__,即
$$
\sigma\left(x
{m}, x
{k}\right)=\frac{1}{n-1} \sum_{i=1}^{n}\left(x_{m i}-\overline{x}{m}\right)\left(x{k i}-\overline{x}{k}\right)
$$
因此,__协方差矩阵
_ 为
$$
\Sigma=\left[\begin{array}{ccc}{\sigma\left(x_{1}, x_{1}\right)} & {\cdots} & {\sigma\left(x_{1}, x_{d}\right)} \ {\vdots} & {\ddots} & {\vdots} \ {\sigma\left(x_{d}, x_{1}\right)} & {\cdots} & {\sigma\left(x_{d}, x_{d}\right)}\end{array}\right] \in \mathbb{R}^{d \times d}
$$
其中,对角线上的元素为各个随机变量的方差,非对角线上的元素为两两随机变量之间的协方差,根据协方差的定义,我们可以认定:矩阵 $\Sigma$ 为对称矩阵(symmetric matrix),其大小为 $d \times d$ 。

多元正态分布与线性变换

假设一个向量 $x$ 服从均值向量为 $\mu$ 、协方差矩阵为 $\Sigma$ 的多元正态分布(multi-variate Gaussian distribution),则
$$
p(\boldsymbol{x})=|2 \pi \Sigma|^{-1 / 2} \exp \left(-\frac{1}{2}(\boldsymbol{x}-\boldsymbol{\mu})^{T} \Sigma^{-1}(\boldsymbol{x}-\boldsymbol{\mu})\right)
$$
令该分布的均值向量为 $\mu = 0$,由于指数项外面的系数 $|2 \pi \Sigma|^{-1 / 2}$ 通常作为常数,故可将多元正态分布简化为
$$
p(\boldsymbol{x}) \propto \exp \left(-\frac{1}{2} \boldsymbol{x}^{T} \Sigma^{-1} \boldsymbol{x}\right)
$$
再令 $\boldsymbol{x}=(y, z)^{T}$,包含两个随机变量 $y$ 和 $z$,则协方差矩阵可写成如下形式:
$$
\Sigma=\left[\begin{array}{ll}{\sigma(y, y)} & {\sigma(y, z)} \ {\sigma(z, y)} & {\sigma(z, z)}\end{array}\right] \in \mathbb{R}^{2 \times 2}
$$
用单位矩阵(identity matrix) $I$ 作为协方差矩阵,随机变量 $y$ 和 $x$ 的方差均为1,则生成如干个随机数如图1所示。

1(图1 标准的二元正态分布)

在生成的若干个随机数中,每个点的似然为
$$
\mathcal{L}(\boldsymbol{x}) \propto \exp \left(-\frac{1}{2} \boldsymbol{x}^{T} \boldsymbol{x}\right)
$$
对图1中的所有点考虑一个线性变换(linear transformation): $t = Ax$,我们能够得到图2.
2(图1 标准的二元正态分布)
图2 经过线性变换的二元正态分布,先将图1的纵坐标压缩0.5倍,再将所有点逆时针旋转30°得到。

在线性变换中,矩阵 $A$ 被称为变换矩阵(transformation matrix),为了将图1中的点经过线性变换得到我们想要的图2,其实我们需要构造两个矩阵:

  • 尺度矩阵(scaling matrix):
    $$
    S=\left[\begin{array}{ll}{s_{y}} & {0} \ {0} & {s_{z}}\end{array}\right]
    $$
  • 旋转矩阵(rotation matrix)
    $$
    R=\left[\begin{array}{cc}{\cos (\theta)} & {-\sin (\theta)} \ {\sin (\theta)} & {\cos (\theta)}\end{array}\right]
    $$
    其中,$\theta$ 为顺时针旋转的度数。

变换矩阵、尺度矩阵和旋转矩阵三者的关系式:$A = RS$

在这个例子中,尺度矩阵为 $S=\left[\begin{array}{cc}{1} & {0} \ {0} & {\frac{1}{2}}\end{array}\right]$ ,旋转矩阵为 $R=\left[\begin{array}{cc}{\cos \left(-\frac{\pi}{6}\right)} & {-\sin \left(-\frac{\pi}{6}\right)} \ {\sin \left(-\frac{\pi}{6}\right)} & {\cos \left(-\frac{\pi}{6}\right)}\end{array}\right]=\left[\begin{array}{cc}{\frac{\sqrt{3}}{2}} & {\frac{1}{2}} \ {-\frac{1}{2}} & {\frac{\sqrt{3}}{2}}\end{array}\right]$ ,故变换矩阵为 $A=R S=\left[\begin{array}{cc}{\frac{\sqrt{3}}{2}} & {\frac{1}{4}} \ {-\frac{1}{2}} & {\frac{\sqrt{3}}{4}}\end{array}\right]$

另外,需要考虑的是,经过了线性变换,$t$ 的分布是什么样子呢?

将 $x = A^{-1}t$ 带入前面给出的似然 $\mathcal{L}(\boldsymbol{t})$ ,有

$$
\begin{array}{l}{\mathcal{L}(\boldsymbol{t}) \propto \exp \left(-\frac{1}{2}\left(A^{-1} \boldsymbol{t}\right)^{T}\left(A^{-1} \boldsymbol{t}\right)\right)} \ {=\exp \left(-\frac{1}{2} \boldsymbol{t}^{T}\left(\boldsymbol{A} \boldsymbol{A}^{T}\right)^{-1} \boldsymbol{t}\right)}\end{array}
$$

由此可以得到,多元正态分布的协方差矩阵为
$$
\Sigma=A A^{T}=\left[\begin{array}{cc}{\frac{\sqrt{3}}{2}} & {\frac{1}{4}} \ {-\frac{1}{2}} & {\frac{\sqrt{3}}{4}}\end{array}\right]\left[\begin{array}{cc}{\frac{\sqrt{3}}{2}} & {-\frac{1}{2}} \ {\frac{1}{4}} & {\frac{\sqrt{3}}{4}}\end{array}\right]=\left[\begin{array}{cc}{\frac{13}{16}} & {-\frac{3 \sqrt{3}}{16}} \ {-\frac{3 \sqrt{3}}{16}} & {\frac{7}{16}}\end{array}\right]
$$

协方差矩阵的特征值分解

回到我们已经学过的线性代数内容,对于任意对称矩阵 $\Sigma$,存在一个特征值分解(eigenvalue decomposition, EVD):$\Sigma = U\Lambda U^T$

其中,$U$ 的每一列都是相互正交的特征向量,且是单位向量,满足 $U^TU=I$,$\Lambda$ 对角线上的元素是从大到小排列的特征值,非对角线上的元素均为0。

当然,这条公式在这里也可以很容易地写成如下形式:
$$
\Sigma=\left(U \Lambda^{1 / 2}\right)\left(U \Lambda^{1 / 2}\right)^{T}=A A^{T}
$$
其中, $A = U\Lambda^{1/2}$ ,因此,通俗地说,任意一个协方差矩阵都可以视为线性变换的结果。

在上面的例子中,特征向量构成的矩阵为
$$
U=R=\left[\begin{array}{cc}{\cos (\theta)} & {-\sin (\theta)} \ {\sin (\theta)} & {\cos (\theta)}\end{array}\right]=\left[\begin{array}{cc}{\frac{\sqrt{3}}{2}} & {\frac{1}{2}} \ {-\frac{1}{2}} & {\frac{\sqrt{3}}{2}}\end{array}\right]
$$
特征值构成的矩阵为
$$
\Lambda=S S^{T}=\left[\begin{array}{cc}{s_{y}^{2}} & {0} \ {0} & {s_{z}^{2}}\end{array}\right]=\left[\begin{array}{cc}{1} & {0} \ {0} & {\frac{1}{4}}\end{array}\right]
$$
到这里,我们发现:多元正态分布的概率密度是由协方差矩阵的特征向量控制旋转(rotation),特征值控制尺度(scale),除了协方差矩阵,均值向量会控制概率密度的位置,在图1和图2中,均值向量为 $0$,因此,概率密度的中心位于坐标原点。

集合and空间


以下转自知乎

空间
空间-表

1
2
3
4
作者:qang pan
链接:https://www.zhihu.com/question/19967778/answer/28403912
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

什么是赋范线性空间、内积空间,度量空间,希尔伯特空间?

现代数学的一个特点就是以集合为研究对象,这样的好处就是可以将很多不同问题的本质抽象出来,变成同一个问题,当然这样的坏处就是描述起来比较抽象,很多人就难以理解了。既然是研究集合,每个人感兴趣的角度不同,研究的方向也就不同。为了能有效地研究集合,必须给集合赋予一些“结构”(从一些具体问题抽象出来的结构)。

从数学的本质来看,最基本的集合有两类:线性空间(有线性结构的集合)、度量空间(有度量结构的集合)。

对线性空间而言,主要研究集合的描述,直观地说就是如何清楚地告诉地别人这个集合是什么样子。为了描述清楚,就引入了基(相当于三维空间中的坐标系)的概念,所以对于一个 线性空间 来说,只要知道其 即可,集合中的元素只要知道其在给定基下的坐标即可。

但线性空间中的元素没有“长度”(相当于三维空间中线段的长度),为了量化线性空间中的元素,所以又在线性空间引入特殊的“长度”,即范数。赋予了 范数 的线性空间即称为 __赋犯线性空间__。

但赋范线性空间中两个元素之间没有角度的概念,为了解决该问题,所以在线性空间中又引入了内积的概念。

因为有度量,所以可以在度量空间、赋范线性空间以及内积空间中引入极限,但抽象空间中的极限与实数上的极限有一个很大的不同就是,极限点可能不在原来给定的集合中,所以又引入了完备的概念,完备的内积空间 就称为 __Hilbert 空间__。

这几个空间之间的关系是:线性空间与度量空间是两个不同的概念,没有交集。

  • 赋范线性空间就是赋予了范数的线性空间,也是度量空间(具有线性结构的度量空间)
  • 内积空间是赋范线性空间
  • 希尔伯特空间就是完备的内积空间。

(线性空间 + 范数 = 赋范空间 + 线性结构) + 内积 = 内积空间 + 完备性 = 希尔伯特空间。


  • 线性完备内积空间称作希尔伯特空间
  • 线性完备赋范空间称作巴拿赫空间
  • 有限维线性内积空间称作欧几里得空间