备忘进度。待输出详细文章

day1: 2020.10.21

lora基础概念入门
从头部署ChirpStack,并完成接入网关及3个lora设备。

网关ISM频段标准:

EU868和EU433主要是欧洲标准,
US915是美国标准,
CN779、CN470是中国标准,
AU915主要是澳大利亚标注,
AS923主要是亚洲其余国家标准,
KR923主要是韩国标准,
IN865主要是印度标准,
RU864主要是俄罗斯标准。

节点的入网/激活

每个要接入LoRaWAN网络的节点,必须经过入网/激活的过程,节点入网的目的是获得云端分配的DevAddr、AppSKey、NwkSEncKey等通信必须的信息,入网方式分以下两种。

OTAA(Over-The-Air Activation)空中激活

OTAA中,节点的AppSKey和NwkSEncKey信息是临时从云端获取的,节点中需要提前烧录NwkKey和Appkey、JoinEUI等信息,节点向云端发送Join-request message请求,云端返回Join-accept message,节点提取Join-accept message中的DevAddr,JoinNonce等信息,并自己计算生成AppSKey和NwkSEncKey等信息。

ABP (Activation By Personalization)人工激活

ABP中,节点需要的DevAddr、AppSKey、NwkSEncKey等信息,不是从云端获取的,而是提前在云端配置好,并烧录到节点固件中,节点使用这些信息可以直接和云端通信,省去了OTAA中的Join-request流程。

ChirpStack 编解码函数功能

在 Device Profile 中有一个 codec 模块。

  • Custom JavaScript codec functions 即JS 编解码函数
Decode解析数据

启动 Decode 之后的数据,多了一个 object 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function toHexString(bytes) {
return bytes.map(function(byte) {
return ("00" + (byte & 0xFF).toString(16)).slice(-2)
}).join('').toUpperCase()
}

function Decode(fPort, bytes, variables) {
var hex = toHexString(bytes)
return {
"Version": hex.substring(0, 2),
"DeviceType": hex.substring(2, 4),
"ReportType": hex.substring(4, 6),
"battery": parseInt(hex.substring(6, 8), 16),
"a_current": parseInt(hex.substring(8, 12), 16),
"b_current": parseInt(hex.substring(12, 16), 16),
"c_current": parseInt(hex.substring(16, 20), 16),
"Mulitplier1": hex.substring(20, 22),
}
}
1
2
3
function Encode(fPort, obj, variables) {
return [];
}

day2: 2020.10.22

通过mqtt打通ChirpStack上下行数据,操作lora设备。

ChirpStack中的mqtt主题示例:

ApplicationID和 DevEUI 可以直接从订阅主题中获得

Events

application/[ApplicationID]/device/[DevEUI]/event/[EventType]

application/[ApplicationID]/device/[DevEUI]/command/down

day3: 2020.10.23

熟悉lora设备的指令操作解析
主要有2种方式,一种是从上面讲的mqtt中进行publish,
另外一种是从ChirpStrack进行上行数据查看及下发数据

下发数据:
9k6kPm
查看上行数据
EWBRYB

待评估处理:

1.调研lora采用的时序数据库,通过美观的方式展现lora的数据

ChirpStack 在应用集成中配置将数据的收发存储到 InfluxDB,再通过 Granfana 进行可视化观察。

时序数据库 InfluxDB集成
Grafana

2.调研lora的监控及报警

Prometheus

3.chripstack整合平台的可行性

评估chripstack的开放api接口的操作能力

4.对硬件实际测试效果

对实际lora设备进行测试,评估lora实际运用的效果

简单记录下这个过程。最后再输出完整过程。

发现 9.23

偶然机会,我到专壹自习室进行了体验,发现专壹自习室内的是由全自动化的设备进行无人化运营的。勾起了我的兴趣。打算把家里改造成智能化
以此记录整个俊瑶的智能家的始末。

cgUJfv

调研-立项-立flag 9.25

再次调研专壹自习室、考拉自习室、一隅自习室。立项,我要把家里改造成智能家居,并确认一下自己提出的几个要求

1.尽可能能接入各个厂商的设备
2.网关协议尽可能的自主可控
3.外网可控。

立个最终效果flag,以此作为目标

o80tAU 1q64kZ QxC8wg

技术初步调研及尝试

首先买个小米多模网关。结论-坑,只兼容小米的部分zigbee设备,对别的设备不兼容。放弃
WzSFZr

技术最终选型

  1. home assistant+群晖(树莓派也可)vuMYFd WfX0H5
  2. zigbee2mqtt9gQejz 3.网关板子 有线版 mFPTeZ 无线版 mFWj6b

初步原理:将所有设备连如网关,再由网关上云,通过云来控制网关下发控制子设备。

进度:

1.接入米家台灯

发现家里的小米台灯是yeelight,立刻接入尝试。

2.接入智能插座wifi版+智能插座蓝牙网关版

pTP7Zz

3.接入人体红外

5P5g7Z

4.接入落地灯

HjxUy6

5.接入慧作吸顶风扇灯 ✖️2

6I5MiW

6.接入无线开关六键版

jAVKcO

激活函数是深度学习,亦或者说人工神经网络中一个十分重要的组成部分,它可以对神经元的接收信息进行非线性变换,将变换后的信息输出到下一层神经元。激活函数作用方式如下公式所示:

01d9Nn

其中,𝐴𝑐𝑡𝑖𝑣𝑎𝑡𝑖𝑜𝑛()就是激活函数。

为什么要使用激活函数呢?当我们不用激活函数时,网络中各层只会根据权重𝑤和偏差𝑏只会进行线性变换,就算有多层网络,也只是相当于多个线性方程的组合,依然只是相当于一个线性回归模型,解决复杂问题的能力有限。我们希望我们的神经网络能够处理复杂任务,如语言翻译和图像分类等,线性变换永远无法执行这样的任务。激活函数得加入能对输入进行非线性变换,使其能够学习和执行更复杂的任务。

另外,激活函数使反向传播成为可能,因为激活函数的误差梯度可以用来调整权重和偏差。如果没有可微的非线性函数,这就不可能实现。

总之,激活函数的作用是能够给神经网络加入一些非线性因素,使得神经网络可以更好地解决较为复杂的问题。

常用激活函数

1、sigmoid函数

sigmoid函数可以将整个实数范围的的任意值映射到[0,1]范围内,当当输入值较大时,sigmoid将返回一个接近于1的值,而当输入值较小时,返回值将接近于0。sigmoid函数数学公式和函数图像如下所示:

m9JssL

感受一下TensorFlow中的sigmoid函数:

1
2
3
import tensorflow as tf
x = tf.linspace(-5., 5.,6)
x

<tf.Tensor: id=3, shape=(6,), dtype=float32, numpy=array([-5., -3., -1., 1., 3., 5.], dtype=float32)>

有两种方式可以调用sigmoid函数:
方式一:

1
tf.keras.activations.sigmoid(x)
1
2
<tf.Tensor: id=4, shape=(6,), dtype=float32, numpy=
array([0.00669285, 0.04742587, 0.26894143, 0.7310586 , 0.95257413 , 0.9933072 ], dtype=float32)>

方式二:

1
tf.sigmoid(x)
1
2
<tf.Tensor: id=5, shape=(6,), dtype=float32, numpy=
array([0.00669285, 0.04742587, 0.26894143, 0.7310586 , 0.95257413 , 0.9933072 ], dtype=float32)>

看,𝑥中所有值都映射到了[0,1]范围内。

sigmoid优缺点总结:

  • 优点:输出的映射区间(0,1)内单调连续,非常适合用作输出层,并且比较容易求导。
  • 缺点:具有软饱和性,即当输入x趋向于无穷的时候,它的导数会趋于0,导致很容易产生梯度消失。
2、relu函数

Relu(Rectified Linear Units修正线性单元),是目前被使用最为频繁得激活函数,relu函数在x<0时,输出始终为0。由于x>0时,relu函数的导数为1,即保持输出为x,所以relu函数能够在x>0时保持梯度不断衰减,从而缓解梯度消失的问题,还能加快收敛速度,还能是神经网络具有稀疏性表达能力,这也是relu激活函数能够被使用在深层神经网络中的原因。由于当x<0时,relu函数的导数为0,导致对应的权重无法更新,这样的神经元被称为”神经元死亡”。

relu函数公式和图像如下:
fMAhbi
在TensorFlow中,relu函数的参数情况比sigmoid复杂,我们先来看一下:

1
tf.keras.activations.relu( x, alpha=0.0, max_value=None, threshold=0 )
  • x:输入的变量
  • alpha:上图中左半边部分图像的斜率,也就是x值为负数(准确说应该是小于threshold)部分的斜率,默认为0
  • max_value:最大值,当x大于max_value时,输出值为max_value
  • threshold:起始点,也就是上面图中拐点处x轴的值
    1
    2
    3
    x = tf.linspace(-5., 5.,6)
    x
    <tf.Tensor: id=9, shape=(6,), dtype=float32, numpy=array([-5., -3., -1., 1., 3., 5.], dtype=float32)>
    1
    2
    tf.keras.activations.relu(x)
    <tf.Tensor: id=10, shape=(6,), dtype=float32, numpy=array([0., 0., 0., 1., 3., 5.], dtype=float32)>
    1
    2
    tf.keras.activations.relu(x,alpha=2.)
    <tf.Tensor: id=11, shape=(6,), dtype=float32, numpy=array([-10., -6., -2., 1., 3., 5.], dtype=float32)>
    1
    2
    tf.keras.activations.relu(x,max_value=2.)  # 大于2部分都将输出为2.
    <tf.Tensor: id=16, shape=(6,), dtype=float32, numpy=array([0., 0., 0., 1., 2., 2.], dtype=float32)>
    1
    2
    tf.keras.activations.relu(x,alpha=2., threshold=3.5)  # 小于3.5的值按照alpha * (x - threshold)计算
    <tf.Tensor: id=27, shape=(6,), dtype=float32, numpy=array([-17., -13., -9., -5., -1., 5.], dtype=float32)>
3、softmax函数

softmax函数是sigmoid函数的进化,在处理分类问题是很方便,它可以将所有输出映射到成概率的形式,即值在[0,1]范围且总和为1。例如输出变量为[1.5,4.4,2.0],经过softmax函数激活后,输出为[0.04802413, 0.87279755, 0.0791784 ],分别对应属于1、2、3类的概率。softmax函数数学公式如下:
mgdmhg

1
2
3
tf.nn.softmax(tf.constant([[1.5,4.4,2.0]]))
<tf.Tensor: id=29, shape=(1, 3), dtype=float32, numpy=
array([[0.04802413, 0.87279755, 0.0791784 ]], dtype=float32)>
1
2
3
tf.keras.activations.softmax(tf.constant([[1.5,4.4,2.0]]))
<tf.Tensor: id=31, shape=(1, 3), dtype=float32, numpy=
array([[0.04802413, 0.87279755, 0.0791784 ]], dtype=float32)>
1
2
x = tf.random.uniform([1,5],minval=-2,maxval=2)
x
1
2
<tf.Tensor: id=38, shape=(1, 5), dtype=float32, numpy=
array([[ 1.9715171 , 0.49954653, -0.37836075, 1.6178164 , 0.80509186]] , dtype=float32)>
1
2
3
tf.keras.activations.softmax(x)
<tf.Tensor: id=39, shape=(1, 5), dtype=float32, numpy=
array([[0.42763966, 0.09813169, 0.04078862, 0.30023944, 0.13320053]] , dtype=float32)>
4、tanh函数

tanh函数无论是功能还是函数图像上斗鱼sigmoid函数十分相似,所以两者的优缺点也一样,区别在于tanh函数将值映射到[-1,1]范围,其数学公式和函数图像如下:

bu58hv

1
2
3
x = tf.linspace(-5., 5.,6)
x
<tf.Tensor: id=43, shape=(6,), dtype=float32, numpy=array([-5., -3., -1., 1., 3., 5.], dtype=float32)>
1
2
3
4
tf.keras.activations.tanh(x)
<tf.Tensor: id=44, shape=(6,), dtype=float32, numpy=
array([-0.99990916, -0.9950547 , -0.7615942 , 0.7615942 , 0.9950547 ,
0.99990916], dtype=float32)>

总结

神经网络中,隐藏层之间的输出大多需要通过激活函数来映射(当然,也可以不用,没有使用激活函数的层一般称为logits层),在构建模型是,需要根据实际数据情况选择激活函数。TensorFlow中的激活函数可不止这4个,本文只是介绍最常用的4个,当然,其他激活函数大多是这几个激活函数的变种。

模型构建

1
2
3
4
5
6
7
8
9
10
11
12
import tensorflow as tf
from tensorflow.keras import layers
print(tf.__version__)
print(tf.keras.__version__)

model = tf.keras.Sequential()
# 往模型中添加一个有64个神经元组成的层,激活函数为relu:
model.add(layers.Dense(64, activation='relu'))
# 再添加一个:
model.add(layers.Dense(64, activation='relu'))
# 添加一个有10个神经元的softmax层作为输出层:
model.add(layers.Dense(10, activation='softmax'))

等价于:

1
2
3
4
5
model = tf.keras.Sequential([
layers.Dense(64, activation='relu'),
layers.Dense(64, activation='relu'),
layers.Dense(10, activation='softmax')]
)

定义神经网络层通过tf.keras.layers模块中的Dense类实现,Dense类构造参数如下:

  • units:指定神经元个数,必须是一个正整数。
  • activation:激活函数,可以是可以是一个可调用对象或标识一个对象的字符串
  • use_bias:布尔型,是否使用是否使用偏置项
  • kernel_initializer和bias_initializer:权值、偏置初始化方法,可以是一个可调用对象或标识一个对象的字符串
  • kernel_regularizer和bias_regularizer:对权值、偏置进行正则化的方法,可以是一个可调用对象或标识一个对象的字符串
  • activity_regularizer:对层的输出进行正则化的方法,可以是一个可调用对象或标识一个对象的字符串
  • kernel_constraint和bias_constraint:对权值矩阵、偏置矩阵的约束方法,可以是一个可调用对象或标识一个对象的字符串

训练模型

建立好模型之后,接下来当然是要进行训练模型了。不过,在训练前还需要做一些配置工作,例如指定优化器、损失函数、评估指标等,这些配置参数的过程一般通过tf.keras.Model.compile方法进行,先来熟悉一下tf.keras.Model.compile方法的三个常用参数:

  • optimizer:tf.keras.optimizers模块中的优化器实例化对象,例如 tf.keras.optimizers.Adam或 tf.keras.optimizers.SGD的实例化对象,当然也可以使用字符串来指代优化器,例如’adam’和’sgd’。
  • loss:损失函数,例如交叉熵、均方差等,通常是tf.keras.losses模块中定义的可调用对象,也可以用用于指代损失函数的字符串。
  • metrics:元素为评估方法的list,通常是定义在tf.keras.metrics模块中定义的可调用对象,也可以用于指代评估方法的字符串。

在知道怎么配置模型训练参数后,就可以根据实际应用情况合理选择优化器、损失函数、评估方法等:

1
2
3
4
5
6
7
8
9
# 回归模型
model.compile(optimizer=tf.keras.optimizers.Adam(0.01), # 指定优化器,学习率为0.01
loss='mse', # 指定均方差作为损失函数
metrics=['mae']) # 添加绝对值误差作为评估方法

# 分类模型
model.compile(optimizer=tf.keras.optimizers.RMSprop(0.01),
loss=tf.keras.losses.CategoricalCrossentropy(), # 分类模型多用交叉熵作为损失函数
metrics=[tf.keras.metrics.CategoricalAccuracy()])

通过compile()配置好模型后,就可以开始训练了。tf.keras中提供了fit()方法对模型进行训练,先来看看fit()方法的主要参数:

  • x和y:训练数据和目标数据
  • epochs:训练周期数,每一个周期都是对训练数据集的一次完整迭代
  • batch_size:簇的大小,一般在数据集是numpy数组类型时使用
  • validation_data:验证数据集,模型训练时,如果你想通过一个额外的验证数据集来监测模型的性能变换,就可以通过这个参数传入验证数据集
  • verbose:日志显示方式,verbose=0为不在标准输出流输出日志信息,verbose=1为输出进度条记录,verbose=2为每个epoch输出一行记录
  • callbacks:回调方法组成的列表,一般是定义在tf.keras.callbacks中的方法
  • validation_split:从训练数据集抽取部分数据作为验证数据集的比例,是一个0到1之间的浮点数。这一参数在输入数据为dataset对象、生成器、keras.utils.Sequence对象是无效。
  • shuffle:是否在每一个周期开始前打乱数据

下面分别说说如何使用fit()方法结合numpy数据和tf.data.Dataset数据进行模型训练。

1
2
3
4
5
6
import numpy as np

data = np.random.random((1000, 32))
labels = np.random.random((1000, 10))

model.fit(data, labels, epochs=10, batch_size=32)

评估与预测

训练好的模型性能如何,评估测试一下就知道了。可以使用模型自带的evaluate()方法和predict()方法对模型进行评估和预测。

1
2
3
4
5
# 如果是numpy数据,可以这么使用
data = np.random.random((1000, 32))
labels = np.random.random((1000, 10))

model.evaluate(data, labels, batch_size=32)
1
2
3
4
5
# 如果数Dataset对象,可以这么使用
dataset = tf.data.Dataset.from_tensor_slices((data, labels))
dataset = dataset.batch(32)

model.evaluate(dataset)

使用predict()方法进行预测:

1
2
3
# numpy数据
result = model.predict(data, batch_size=32)
print(result.shape)
1
2
3
# dataset数据
result = model.predict(dataset)
print(result.shape)

traefik之docker-compose.yml

traefik 一般需要一个配置文件来管理路由,服务,证书等。我们可以通过 docker 启动 traefik 时来挂载配置文件,docker-compose.yaml 文件如下
traefik 默认有一个 dashboard,通过 :8080 端口暴露出去。我们可以在浏览器中直接通过 :8080 访问,但是

使用 IP 地址肯定不是特别方便,此时我们可以配置 Host
在公网环境下访问有安全性问题,此时可以配置 basicAuth,digestAuth,IpWhiteList 或者 openVPN

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3'

services:
reverse-proxy:
image: traefik
ports:
- "52080:80"
- "52081:8080"
volumes:
- ./traefik.toml:/etc/traefik/traefik.toml
- /var/run/docker.sock:/var/run/docker.sock
container_name: traefik
labels:
- "traefik.http.routers.api.rule=Host(`www.hong.local`)"
- "traefik.http.routers.api.service=api@internal"

traefik.toml

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
## Static configuration
[global]
checkNewVersion = true
sendAnonymousUsage = false

[entryPoints]
[entryPoints.http]
address = ":80"

[entryPoints.traefik]
address = ":8080"

[api]
insecure = true
dashboard = true
#debug = true


[providers]
[providers.docker]
endpoint = "unix:///var/run/docker.sock"
defaultRule = "Host(`{{ normalize .Name }}.docker.localhost`)"
# 限制服务发现范围
# 如果设置为 false, 则没有 traefik.enable=true 标签的容器将从生成的路由配置中忽略
exposedByDefault = false
#[providers.file]
# filename = "dynamic_conf.toml"
# watch = true
[metrics]
[metrics.prometheus]

whoami之docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: '3'

services:
whoami:
image: containous/whoami
labels:
# 声明公开此容器访问
- traefik.enable=true
# 服务将响应的域
- traefik.http.routers.whoami.rule=Host(`www.jun.local`)
networks:
default:
external:
name: traefik_default

那 whoami 这个 http 服务做了什么事情呢

暴露了一个 http 服务,主要提供一些 header 以及 ip 信息
配置了容器的 labels,设置该服务的 Host 为 whoami.docker.localhost,给 traefik 提供标记

docker 开机自启动

在运行docker容器时可以加如下参数来保证每次docker服务重启后容器也自动重启:

1
docker run --restart=always

如果已经启动了则可以使用如下命令:

1
docker update --restart=always <CONTAINER ID>

postgres及数据持久化

1
docker run --name docker_postgres -d -p 5432:5432 -v /Users/hongjunyao/Desktop/postgres:/var/lib/postgresql/data -e POSTGRES_DB=baohan -e POSTGRES_USER=honng -e POSTGRES_PASSWORD='hong' postgres:9.6
持久化

mysql及数据持久化

1
docker run -p 3306:3306 --name docker_mysql -e TZ=Asia/Shanghai -v /Users/hongjunyao/Desktop/mysql/conf:/etc/mysql/conf.d -v /Users/hongjunyao/Desktop/mysql/logs:/logs -v /Users/hongjunyao/Desktop/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=hong -d mysql:5.7.23
持久化

redis及数据持久化

1
2
3
4
5
6
7
8

docker run \
-p 6379:6379 \
-v /Users/hongjunyao/Desktop/redis/data:/data \
-v /Users/hongjunyao/Desktop/redis/conf/redis.conf:/etc/redis/redis.conf \
--privileged=true \
--name docker_redis \
-d redis redis-server /etc/redis/redis.conf
持久化

想实现这个效果,就需要将 Puppeteer 的 headless 选项设为 false,并将 slowMo 设为 20-100 中的某个值,前者使得所有浏览器自动化操作可见,后者控制了动作之间的间隔,使其变慢,从而通过人眼可以看清每步操作。示例代码:

1
2
3
4
browser = await puppeteer.launch({
headless: false,
slowMo: 20
});

导航到某个页面
这个操作太常用了!第一步是启动浏览器,那么第二步就是导航到某个页面,代码示例:

1
2
page = await browser.newPage();
await page.goto('https://baidu.com');

上述代码会开启一个新页面,并将其导航到 https://baidu.com。

等待某个 DOM 节点出现
在进行某些页面操作前,我们必须要等待指定的 DOM 加载完成后才能操作,比如,一个 Input 没有加载出来时,你是无法在里面输入字符的等等。在 Puppeteer 中,你可以使用 page.waitForSelector 和选择器来等待某个 DOM 节点出现:

1
await page.waitForSelector('#loginForm');

上述代码会等待 ID 为 loginForm 的节点出现。

等待几毫秒
有时候,你找不到某个特定的时刻,只能通过时间间隔来确定,那么此时你可以使用 page.waitFor(number) 来实现:

1
await page.waitFor(500);

上述代码会等待 500 毫秒。

等待某个 JavaScript 函数返回 true
有时候,你需要等待某个复杂的时刻,这个时刻只能通过一些复杂的 JavaScript 函数来判断,那么此时你可以使用 page.waitFor(Function) 来实现:

1
await page.waitFor(() => !document.querySelector('.ant-spin.ant-spin-spinning'));

上述代码会等待 Antd 中的旋转图标消失。

向某个 Input 中输入字符
为了模拟用户登陆或仅仅就是输入某个表单,我们经常会向某个 Input 中输入字符,那么我们可以使用这个方法:

1
await page.type('#username', 'lewis');

上述代码向 ID 为 username 的 Input 中输入了 lewis。值得一提的是,该方法还会触发 Input 的 keydown、keypress, 和 keyup 事件,所以如果你有该事件的相关功能,也会被测试到哦,是不是很强大?

点击某个节点
在 Puppeteer 中模拟点击某个节点,非常简单,只需要:

1
await page.click('#btn-submit');

上述代码点击了 ID 为 btn-submit 的节点。

在浏览器中执行一段 JavaScript 代码
有时候我们需要在浏览器中执行一段 JavaScript 代码,此时你可以这样写:

1
page.evaluate(() => alert('1'));

上述代码会在浏览器执行 alert(‘1’)。

获取某一个节点的某个属性
有时候我们需要获取某个 Input 的 value,某个链接的 href,某个节点的文本 textContent,或者 outerHTML,那么你可以使用这个方法:

1
2
3
4
const searchValue = await page.$eval('#search', el => el.value);
const preloadHref = await page.$eval('link[rel=preload]', el => el.href);
const text = await page.$eval('.text', el => el.textContent);
const html = await page.$eval('.main-container', e => e.outerHTML);

获取某一类节点的某个属性集合
有时候我们需要获取某一类节点的某个属性集合,那么你可以这么写:

1
const textArray = await page.$$eval('.text', els => Array.from(els).map(el => el.textContent));

上述代码将页面中所有类为 text 的节点中的文本拼装为数组放到了 textArray 中。

postgres的copy to

PostgreSQL 的 COPY TO 直接可以干这个事情,而且导出速度是非常快的。下面例子是把 products 表导出成 CSV :

1
2
3
COPY products
TO '/path/to/output.csv'
WITH csv;

可以导出指定的属性:

1
2
3
COPY products (name, price)
TO '/path/to/output.csv'
WITH csv;

也可以配合查询语句,比如最常见的 SELECT :

1
2
3
4
5
6
7
COPY (
SELECT name, category_name
FROM products
LEFT JOIN categories ON categories.id = products.category_id
)
TO '/path/to/output.csv'
WITH csv;

导入 CSV
跟上面的导出差不多,只是把 TO 换成 FROM ,举例:

1
2
3
COPY products
FROM '/path/to/input.csv'
WITH csv;

这个命令做导入是非常高效的,在开头那篇博客作者的测试中,COPY 只花了 INSERT 方案 1/3 的时间,而后者还用 prepare statement 优化过。

示例

示例1.将整张表拷贝至标准输出
1
2
3
test=# copy tbl_test1 to stdout;
1 HA 12
2 ha 543
示例2.将表的部分字段拷贝至标准输出,并输出字段名称,字段间使用’,’分隔
1
2
3
4
test=# copy tbl_test1(a,b) to stdout delimiter ',' csv header;
a,b
1,HA
2,ha
示例3.将查询结果拷贝至标准输出
1
2
3
test=# copy (select a,b from tbl_test1 except select e,f from tbl_test2 ) to stdout delimiter ',' quote '"' csv header;
a,b
2,ha

将标准输入拷贝至表中需要注意几点

1.字段间分隔符默认使用【Tab】键
2.换行使用回车键
3.结束使用反斜线+英文据点(.)
4.最好指定字段顺序,要不然可能会错位赋值

示例4.将标准输入拷贝至表中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
test=# copy tbl_test1(a,b,c) from stdin;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 1 公举 公主
>> 2 万岁 万万岁
>> \.
COPY 2
test=# select * from tbl_test1 ;
a | b | c
---+------+--------
1 | HA | 12
2 | ha | 543
1 | 公举 | 公主
2 | 万岁 | 万万岁
(4 rows)
示例5.从标准输入拷贝至表中,并将标准输入第一行作为字段名(和表中不符也没关系,copy会自动忽略第一行),字段分隔符为’,’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
test=# copy tbl_test1(a,b,c) from stdin delimiter ',' csv header;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> a,b,c
>> 3,你好,hello
>> 4,超人,super
>> \.
COPY 2
test=# select * from tbl_test1 ;
a | b | c
---+------+--------
1 | HA | 12
2 | ha | 543
1 | 公举 | 公主
2 | 万岁 | 万万岁
3 | 你好 | hello
4 | 超人 | super
(6 rows)

以上是表与标准输出和标准输入间的相互拷贝,表与文件的拷贝和以上完全相同,只是将标准输出和标准输入换成文件。需要注意的是:

1.数据库用户必须有文件所在的路径的写权限。
2.如果表存在中文字符,导出至csv文件时需要设置编码为GBK,否则使用excel打开是中文显示乱码。
3.将文件导入表中时仍要考虑编码问题

示例6.将表拷贝至csv文件中
1
2
test=# copy tbl_test1 to '/tmp/tbl_test1.csv' delimiter ',' csv header;
COPY 6

使用excel打开文件,中文显示为乱码

示例7. 将表以GBK编码拷贝至csv文件中
1
2
test=# copy tbl_test1 to '/tmp/tbl_test1.csv' delimiter ',' csv header encoding 'GBK';
COPY 6

使用excel打开,中文显示正常

示例8.将刚才导出的文件再次拷贝至表中,使用默认编码UTF8
1
2
3
test=# copy tbl_test1(a,b,c) from '/tmp/tbl_test1.csv' delimiter ',' csv header;
ERROR: invalid byte sequence for encoding "UTF8": 0xb9
CONTEXT: COPY tbl_test1, line 4

示例9.将刚才导出的文件再次拷贝至表中,使用GBK编码

1
2
test=# copy tbl_test1(a,b,c) from '/tmp/tbl_test1.csv' delimiter ',' csv header encoding 'GBK';
COPY 6

说明:3Blue1Brown视频的关键点的记录,用来当笔记随时查阅和再学习的。

一、向量的本质

三个视角:

  • 物理专业学生:一定方向和长度的箭头,可以自由移动一个向量保持它不变。
  • 计算机专业学生:向量是有序的数字列表。
  • 数学专业学生:向量可以是任何事物,只要保证两个向量相加以及数字与向量相乘是有意义的即可。

加法的意义:如[a,b]表示点从原点出发沿x轴正轴移动a,沿y轴正轴移动b。那么[a,b]+[c,d]可以先考虑完x轴移动再考虑y轴移动,于是我们得到[a+c,b+d]
数乘的意义:缩放。

二、线性组合、张成的空间与基

基向量:[a,b]可以理解为将基向量$\vec{i}$,$\vec{j}$分别缩放a,b倍以后相加的结果。
线性组合:其实是向量之间的线性组合,其主体是向量,线性组合是一个操作,将各个向量缩放之后,相加在一起,就得到了参与操作的向量之间的线性组合。
张成的空间:v与w全部的线性组合所构成向量集合被称为张成的空间。

三、矩阵与线性变换

变换其实也是一种函数,我们有一个输入向量,然后经过变换之后,得到一个输出向量。整个过程,可以看作是输入的向量移动到了输出的输出的位置。考虑整个平面上的向量,在经过变换之后,得到了一个最新的位置。

四、矩阵乘法与线性变换复合

两个22矩阵a和b相乘,可以看作是对原始空间连续做了两次线性变换,而得到的计算结果c也是一个22的矩阵。使用c对原始空间进行一次线性变换,和连续使用a和b对原始空间进行两次线性变换的效果相同。

矩阵相乘的几何意义是将两次单独的变换变为一次组合变换即可。

该结论到三维空间中也是同样成立的。

五、行列式

如果在二维空间中,我们画出相对应的网格,那么线性变换,就是对这些网格做了拉伸,收缩或者反转。那么如何来定义这种变换的程度呢?就需要用到行列式determinant的概念了。
线性变换后的图形面积缩放比例,正负号与定向有关(右手法则)
在进行线性变换后,原来一个面积为1的单位方格,变成了面积为6的矩形。可以说,线性变换将原空间放大了6倍。
我们知道,行列式的值是有正有负的,那么怎么判断是负数呢?我们可以通过变换后的基向量i和j的方向来判定。

在变换之前,j是在i的左侧的:

如果经过线性变换后,j变成了在i的右侧,那么得到的行列式的值是负的:

那么到三维空间中,行列式的值就告诉我们经过线性变换后,单位体积变化的程度,而行列式的值可以通过右手定则来判定:

行列式这个“怪物”定义初看很奇怪,一堆逆序数什么的让人不免觉得恐惧,但其实它是有实际得不能更实际的物理意义的,理解只需要三步
1,行列式det(A)是针对一个n$\times$n的矩阵A而言的。A表示一个n维空间到n维空间的线性变换。那么什么是线性变换呢?无非是一个压缩或拉伸啊。假想原来空间中有一个n维的立方体(随便什么形状),其中立方体内的每一个点都经过这个线性变换,变成n维空间中的一个新立方体。
2,原来立方体有一个体积$V_{1}$,新的立方体也有一个体积$V_{2}$ 。
3,行列式det(A)是一个数对不对?这个数其实就是 $V_{2} \div V_{1}$ ,结束了。
行列式的计算:
二阶行列式的计算方法是“对角线法则”:

主对角线元素积与副对角线元素积的差

二阶行列式的法则并不适用三阶行列式。三阶行列式的计算方法如下

这个依然叫“对角线法则”,不过是复杂版的:主对角线乘完以后元素位置要平移一下继续相乘,直到x、y、z分别开过头以后,再分别减去x、y、z开头的副对角线乘积。

六、逆矩阵、列空间与零空间

逆矩阵

如果一个变换AA将空间压缩到了一条线上,那么就说AA的秩是1。如果压缩成一个平面,就是2。 所以,”秩“代表着变换后空间的维数。
比如对于2×22×2的矩阵,秩最大就是2,意味着基向量仍旧能张成整个二维空间。但对于3×33×3矩阵,秩为2意味着空间被压缩了。

列空间

列空间有两种解释:
1)假设矩阵A代表一个矩阵变换,原始空间中所有的向量,在经由矩阵A的变换之后,所得到的所有新向量的集合
2)由矩阵A的列向量所长成的空间
比如下面的例子,[[2,-2],[1,-1]]这个矩阵,将二维空间变换为一条直线,那么这条直线就是矩阵的列空间。

零空间

如果某个向量空间在线性变换之后,存在降维,那么就会有一系列原来不是零向量的向量落到了零向量的位置,所有这些向量的集合构成了零空间。

0%