个人博客


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

docker与tensorflow结合使用

发表于 2018-08-10 | 分类于 docker

最近这段时间一直在学习docker的使用,以及如何在docker中使用tensorflow.今天就把在docker中如何使用tensorflow记录一下.

docker安装

我是把docker安装在centos 7.4操作系统上面,在vmware中装的centos,vmware中安装centos很简单.具体的网络配置可以参考vmware nat配置.docker安装很简单,找到docker官网,直接按照上面的步骤安装即可.运行docker version查看版本如下

因为docker 采用的是客户端/服务端的结构,所以这里可以看到client以及server,它们分别都有版本号.

tensorflow

在docker中运行tensorflow的第一步就是要找到自己需要的镜像,我们可以去docker hub找到自己需要的tensorflow镜像.tensorflow的镜像主要分两类,一种是在CPU上面跑的,还有一种是在GPU上面跑的,如果需要GPU的,那么还需要安装nvidia-docker.这里我使用的是CPU版本的.当然我们还需要选择具体的tensorflow版本.这里我拉取的命令如下:

1
docker pull tensorflow/tensorflow:1.9.0-devel-py3

拉取成功之后,运行docker images可以看到有tensorflow镜像.

tensorflow在docker中使用

1
docker run -it -p 8888:8888 --name tf-1.9 tensorflow/tensorflow:1.9.0-devel-py3

运行上面的命令,在容器中启动镜像.-p表示指定端口映射,即将本机的8888端口映射到容器的8888端口.--name用来指定容器的名字为tf-1.9.因为这里采用的镜像是devel模式的,所以默认不启动jupyter.如果想使用默认启动jupyter的镜像,那么直接拉取不带devel的镜像就可以.即拉取最近的镜像docker pull tensorflow/tensorflow
启动之后,我们就进入了容器,ls / 查看容器根目录内容,可以看到有run_jupyter.sh文件.运行此文件,即在根目录下执行./run_jupyter.sh --allow-root,--allow-root参数是因为jupyter启动不推荐使用root,这里是主动允许使用root.然后在浏览器中就可以访问jupyter的内容了.

创建自己的镜像

上面仅仅是跑了一个什么都没有的镜像,如果我们需要在镜像里面跑我们的深度学习程序怎么办呢?这首先做的第一步就是要制作我们自己的镜像.这里我们跑一个简单的mnist数据集,程序可以直接去tensorflow上面找一个例子程序.这里我的程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================

"""Simple, end-to-end, LeNet-5-like convolutional MNIST model example.

This should achieve a test error of 0.7%. Please keep this model as simple and
linear as possible, it is meant as a tutorial for simple convolutional models.
Run with --self_test on the command line to execute a short self-test.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import argparse
import gzip
import os
import sys
import time
import logging

import numpy
from six.moves import urllib
from six.moves import xrange # pylint: disable=redefined-builtin
import tensorflow as tf

# CVDF mirror of http://yann.lecun.com/exdb/mnist/
# 如果WORK_DIRECTORY中没有需要的数据,则从此地址下载数据
SOURCE_URL = 'https://storage.googleapis.com/cvdf-datasets/mnist/'
# 训练数据位置
# WORK_DIRECTORY = 'data'
WORK_DIRECTORY = './MNIST-data'
IMAGE_SIZE = 28
NUM_CHANNELS = 1
PIXEL_DEPTH = 255
NUM_LABELS = 10
VALIDATION_SIZE = 5000 # Size of the validation set.
SEED = 66478 # Set to None for random seed.
BATCH_SIZE = 64
NUM_EPOCHS = 10
EVAL_BATCH_SIZE = 64
EVAL_FREQUENCY = 100 # Number of steps between evaluations.

FLAGS = None

# 打印信息设置
# logging.basicConfig(format='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s',
# level=logging.DEBUG)
logging.basicConfig(level=logging.DEBUG, # 控制台打印的日志级别
filename='cnn_mnist.log',
filemode='a', # 模式,有w和a,w就是写模式,每次都会重新写日志,覆盖之前的日志
# a是追加模式,默认如果不写的话,就是追加模式
format=
'%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'
# 日志格式
)


def data_type():
"""Return the type of the activations, weights, and placeholder variables."""
if FLAGS.use_fp16:
return tf.float16
else:
return tf.float32


def maybe_download(filename):
"""Download the data from Yann's website, unless it's already here."""
if not tf.gfile.Exists(WORK_DIRECTORY):
tf.gfile.MakeDirs(WORK_DIRECTORY)
filepath = os.path.join(WORK_DIRECTORY, filename)
if not tf.gfile.Exists(filepath):
filepath, _ = urllib.request.urlretrieve(SOURCE_URL + filename, filepath)
with tf.gfile.GFile(filepath) as f:
size = f.size()
print('Successfully downloaded', filename, size, 'bytes.')
return filepath


def extract_data(filename, num_images):
"""Extract the images into a 4D tensor [image index, y, x, channels].

Values are rescaled from [0, 255] down to [-0.5, 0.5].
"""
logging.info('Extracting' + filename)
print('Extracting', filename)
with gzip.open(filename) as bytestream:
bytestream.read(16)
buf = bytestream.read(IMAGE_SIZE * IMAGE_SIZE * num_images * NUM_CHANNELS)
data = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.float32)
data = (data - (PIXEL_DEPTH / 2.0)) / PIXEL_DEPTH
data = data.reshape(num_images, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS)
return data


def extract_labels(filename, num_images):
"""Extract the labels into a vector of int64 label IDs."""
logging.info('Extracting' + filename)
print('Extracting', filename)
with gzip.open(filename) as bytestream:
bytestream.read(8)
buf = bytestream.read(1 * num_images)
labels = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.int64)
return labels


def fake_data(num_images):
"""Generate a fake dataset that matches the dimensions of MNIST."""
data = numpy.ndarray(
shape=(num_images, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS),
dtype=numpy.float32)
labels = numpy.zeros(shape=(num_images,), dtype=numpy.int64)
for image in xrange(num_images):
label = image % 2
data[image, :, :, 0] = label - 0.5
labels[image] = label
return data, labels


def error_rate(predictions, labels):
"""Return the error rate based on dense predictions and sparse labels."""
return 100.0 - (
100.0 *
numpy.sum(numpy.argmax(predictions, 1) == labels) /
predictions.shape[0])


def main(_):
if FLAGS.self_test:
logging.info('Running self-test.')
print('Running self-test.')
train_data, train_labels = fake_data(256)
validation_data, validation_labels = fake_data(EVAL_BATCH_SIZE)
test_data, test_labels = fake_data(EVAL_BATCH_SIZE)
num_epochs = 1
else:
# Get the data.
train_data_filename = maybe_download('train-images-idx3-ubyte.gz')
train_labels_filename = maybe_download('train-labels-idx1-ubyte.gz')
test_data_filename = maybe_download('t10k-images-idx3-ubyte.gz')
test_labels_filename = maybe_download('t10k-labels-idx1-ubyte.gz')

# Extract it into numpy arrays.
train_data = extract_data(train_data_filename, 60000)
train_labels = extract_labels(train_labels_filename, 60000)
test_data = extract_data(test_data_filename, 10000)
test_labels = extract_labels(test_labels_filename, 10000)

# Generate a validation set.
validation_data = train_data[:VALIDATION_SIZE, ...]
validation_labels = train_labels[:VALIDATION_SIZE]
train_data = train_data[VALIDATION_SIZE:, ...]
train_labels = train_labels[VALIDATION_SIZE:]
num_epochs = NUM_EPOCHS
train_size = train_labels.shape[0]

# This is where training samples and labels are fed to the graph.
# These placeholder nodes will be fed a batch of training data at each
# training step using the {feed_dict} argument to the Run() call below.
train_data_node = tf.placeholder(
data_type(),
shape=(BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS))
train_labels_node = tf.placeholder(tf.int64, shape=(BATCH_SIZE,))
eval_data = tf.placeholder(
data_type(),
shape=(EVAL_BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS))

# The variables below hold all the trainable weights. They are passed an
# initial value which will be assigned when we call:
# {tf.global_variables_initializer().run()}
conv1_weights = tf.Variable(
tf.truncated_normal([5, 5, NUM_CHANNELS, 32], # 5x5 filter, depth 32.
stddev=0.1,
seed=SEED, dtype=data_type()))
conv1_biases = tf.Variable(tf.zeros([32], dtype=data_type()))
conv2_weights = tf.Variable(tf.truncated_normal(
[5, 5, 32, 64], stddev=0.1,
seed=SEED, dtype=data_type()))
conv2_biases = tf.Variable(tf.constant(0.1, shape=[64], dtype=data_type()))
fc1_weights = tf.Variable( # fully connected, depth 512.
tf.truncated_normal([IMAGE_SIZE // 4 * IMAGE_SIZE // 4 * 64, 512],
stddev=0.1,
seed=SEED,
dtype=data_type()))
fc1_biases = tf.Variable(tf.constant(0.1, shape=[512], dtype=data_type()))
fc2_weights = tf.Variable(tf.truncated_normal([512, NUM_LABELS],
stddev=0.1,
seed=SEED,
dtype=data_type()))
fc2_biases = tf.Variable(tf.constant(
0.1, shape=[NUM_LABELS], dtype=data_type()))

# We will replicate the model structure for the training subgraph, as well
# as the evaluation subgraphs, while sharing the trainable parameters.
def model(data, train=False):
"""The Model definition."""
# 2D convolution, with 'SAME' padding (i.e. the output feature map has
# the same size as the input). Note that {strides} is a 4D array whose
# shape matches the data layout: [image index, y, x, depth].
conv = tf.nn.conv2d(data,
conv1_weights,
strides=[1, 1, 1, 1],
padding='SAME')
# Bias and rectified linear non-linearity.
relu = tf.nn.relu(tf.nn.bias_add(conv, conv1_biases))
# Max pooling. The kernel size spec {ksize} also follows the layout of
# the data. Here we have a pooling window of 2, and a stride of 2.
pool = tf.nn.max_pool(relu,
ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1],
padding='SAME')
conv = tf.nn.conv2d(pool,
conv2_weights,
strides=[1, 1, 1, 1],
padding='SAME')
relu = tf.nn.relu(tf.nn.bias_add(conv, conv2_biases))
pool = tf.nn.max_pool(relu,
ksize=[1, 2, 2, 1],
strides=[1, 2, 2, 1],
padding='SAME')
# Reshape the feature map cuboid into a 2D matrix to feed it to the
# fully connected layers.
pool_shape = pool.get_shape().as_list()
reshape = tf.reshape(
pool,
[pool_shape[0], pool_shape[1] * pool_shape[2] * pool_shape[3]])
# Fully connected layer. Note that the '+' operation automatically
# broadcasts the biases.
hidden = tf.nn.relu(tf.matmul(reshape, fc1_weights) + fc1_biases)
# Add a 50% dropout during training only. Dropout also scales
# activations such that no rescaling is needed at evaluation time.
if train:
hidden = tf.nn.dropout(hidden, 0.5, seed=SEED)
return tf.matmul(hidden, fc2_weights) + fc2_biases

# Training computation: logits + cross-entropy loss.
logits = model(train_data_node, True)
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(
labels=train_labels_node, logits=logits))

# L2 regularization for the fully connected parameters.
regularizers = (tf.nn.l2_loss(fc1_weights) + tf.nn.l2_loss(fc1_biases) +
tf.nn.l2_loss(fc2_weights) + tf.nn.l2_loss(fc2_biases))
# Add the regularization term to the loss.
loss += 5e-4 * regularizers

# Optimizer: set up a variable that's incremented once per batch and
# controls the learning rate decay.
batch = tf.Variable(0, dtype=data_type())
# Decay once per epoch, using an exponential schedule starting at 0.01.
learning_rate = tf.train.exponential_decay(
0.01, # Base learning rate.
batch * BATCH_SIZE, # Current index into the dataset.
train_size, # Decay step.
0.95, # Decay rate.
staircase=True)
# Use simple momentum for the optimization.
optimizer = tf.train.MomentumOptimizer(learning_rate,
0.9).minimize(loss,
global_step=batch)

# Predictions for the current training minibatch.
train_prediction = tf.nn.softmax(logits)

# Predictions for the test and validation, which we'll compute less often.
eval_prediction = tf.nn.softmax(model(eval_data))

# Small utility function to evaluate a dataset by feeding batches of data to
# {eval_data} and pulling the results from {eval_predictions}.
# Saves memory and enables this to run on smaller GPUs.
def eval_in_batches(data, sess):
"""Get all predictions for a dataset by running it in small batches."""
size = data.shape[0]
if size < EVAL_BATCH_SIZE:
logging.error("batch size for evals larger than dataset: %d" % size)
raise ValueError("batch size for evals larger than dataset: %d" % size)
predictions = numpy.ndarray(shape=(size, NUM_LABELS), dtype=numpy.float32)
for begin in xrange(0, size, EVAL_BATCH_SIZE):
end = begin + EVAL_BATCH_SIZE
if end <= size:
predictions[begin:end, :] = sess.run(
eval_prediction,
feed_dict={eval_data: data[begin:end, ...]})
else:
batch_predictions = sess.run(
eval_prediction,
feed_dict={eval_data: data[-EVAL_BATCH_SIZE:, ...]})
predictions[begin:, :] = batch_predictions[begin - size:, :]
return predictions

# Create a local session to run the training.
start_time = time.time()
with tf.Session() as sess:
# Run all the initializers to prepare the trainable parameters.
tf.global_variables_initializer().run()
logging.info('Initialized!')
print('Initialized!')
# Loop through training steps.
for step in xrange(int(num_epochs * train_size) // BATCH_SIZE):
# Compute the offset of the current minibatch in the data.
# Note that we could use better randomization across epochs.
offset = (step * BATCH_SIZE) % (train_size - BATCH_SIZE)
batch_data = train_data[offset:(offset + BATCH_SIZE), ...]
batch_labels = train_labels[offset:(offset + BATCH_SIZE)]
# This dictionary maps the batch data (as a numpy array) to the
# node in the graph it should be fed to.
feed_dict = {train_data_node: batch_data,
train_labels_node: batch_labels}
# Run the optimizer to update weights.
sess.run(optimizer, feed_dict=feed_dict)
# print some extra information once reach the evaluation frequency
if step % EVAL_FREQUENCY == 0:
# fetch some extra nodes' data
l, lr, predictions = sess.run([loss, learning_rate, train_prediction],
feed_dict=feed_dict)
elapsed_time = time.time() - start_time
start_time = time.time()
logging.info('Step %d (epoch %.2f), %.1f ms' %(step, float(step) * BATCH_SIZE / train_size, 1000 * elapsed_time / EVAL_FREQUENCY))
print('Step %d (epoch %.2f), %.1f ms' %
(step, float(step) * BATCH_SIZE / train_size,
1000 * elapsed_time / EVAL_FREQUENCY))
logging.info('Minibatch loss: %.3f, learning rate: %.6f' % (l, lr))
print('Minibatch loss: %.3f, learning rate: %.6f' % (l, lr))
logging.info('Minibatch error: %.1f%%' % error_rate(predictions, batch_labels))
print('Minibatch error: %.1f%%' % error_rate(predictions, batch_labels))
logging.info('Validation error: %.1f%%' % error_rate(eval_in_batches(validation_data, sess), validation_labels))
print('Validation error: %.1f%%' % error_rate(
eval_in_batches(validation_data, sess), validation_labels))
sys.stdout.flush()
# Finally print the result!
test_error = error_rate(eval_in_batches(test_data, sess), test_labels)
logging.info('Test error: %.1f%%' % test_error)
print('Test error: %.1f%%' % test_error)
if FLAGS.self_test:
logging.info('test_error' + test_error)
print('test_error', test_error)
assert test_error == 0.0, 'expected 0.0 test_error, got %.2f' % (
test_error,)


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument(
'--use_fp16',
default=False,
help='Use half floats instead of full floats if True.',
action='store_true')
parser.add_argument(
'--self_test',
default=False,
action='store_true',
help='True if running a self test.')

FLAGS, unparsed = parser.parse_known_args()
tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)

这里我在原来的程序基础上面稍微改了下,因为我已经提前将数据下载好了,所以我让程序直接读取本机指定目录下的训练数据,同时增加了日志文件输出.这是为了在公司的容器云平台上测试获取容器输出文件

编写Dockerfile

我们可以在我们的用户目录下,创建一个空的文件夹,将mnist数据集以及程序文件都拷贝进这个文件夹下.其实数据集应该是放在数据卷中,但是这里为了方便,我直接将训练数据打进了镜像中.然后创建Dockerfile,文件内容如下

1
2
3
4
5
FROM tensorflow/tensorflow:1.9.0-devel-py3

COPY . /home/ll
WORKDIR /home/ll
CMD ['python', 'convolutional.py']

即Dockerfile文件中最后一行表示容器启动的运行的命令

build镜像

1
docker build -t tf:1.9 .

-t参数指定镜像跟tag,最后的.指定了镜像中的上下文.构建完之后使用docker images可以查看多了tf:1.9镜像

运行镜像

运行下面的命令,运行上一步构建好的镜像

1
docker run -it --name test tf:1.9

然后就能够看到训练的输出.

同时可以在看一个连接,进入容器,即运行下面命令

1
docker exec -it test /bin/bash

可以看到如下内容

即看到了cnn_mnist.log的日志输出文件

docker常用命令

发表于 2018-07-31 | 分类于 docker

docker命令

docker基本命令

查看docker版本

docker version

查看docker信息

docker info

启动docker 服务

1
2
3
sudo service docker start
或者
sudo systemctl start docker

镜像命令

列出本机的所有image文件

docker image ls

删除image文件

docker image rm [imageName]

拉取image文件

docker image pull library/hello-world
library/hello-world是 image 文件在仓库里面的位置,其中library是 image 文件所在的组,hello-world是 image 文件的名字.因为Docker官方提供的镜像都在library中,所以可以省略library,即使用下面的命令
docker image pull hello-world

运行image 文件

docker container run hello-world
run命令每运行一次,就会新建一个容器.docker container run命令具有自动抓取 image 文件的功能。如果发现本地没有指定的 image 文件,就会从仓库自动抓取。因此,前面的docker image pull命令并不是必需的步骤.
-v参数: 用来进行数据卷的挂载,将本机主机的目录挂载到容器的某一目录

容器命令

终止容器

docker container kill [containID]

image 文件生成的容器实例,本身也是一个文件,称为容器文件。也就是说,一旦容器生成,就会同时存在两个文件: image 文件和容器文件。而且关闭容器并不会删除容器文件,只是容器停止运行而已。

列出本机正在运行的容器

docker container ls

列出本机所有容器,包括终止运行的容器

docker container ls --all

删除容器文件

docker container rm [containerID]或
docker rm [containerID]
注: 删除正在运行的容器,需要添加-f参数;-v参数可以删除没有用的数据卷

查看容器信息

docker ps

重启容器

restart命令会将运行的容器终止,然后在重新启动
docker container restart [containerID 或 name]

导出容器

导出容器快照到本地文件
docker export [containerID]
示例:
docker export 7691a814370e > ubuntu.tar

创建image 文件

1
2
3
docker image build -t koa-demo .
或者
docker image build -t koa-demo:0.0.1 .

-t 用来指定image 文件的名字,后面可以用冒号指定标签.如果不指定,默认的标签就是latest. 最后的点表示Dockerfile文件所在的路径,一个点表示当前路径

从image文件生成容器

1
2
3
docker container run -p 8000:3000 -it koa-demo /bin/bash
或者
docker container run -p 8000:3000 -it koa-demo:0.0.1 /bin/bash

参数含义:

1
2
3
4
-p参数: 容器的3000端口映射到本机的8000端口.
-it参数: 容器的Shell映射到当前的Shell,然后你在本机窗口输入的命令,就会传入容器.
koa-demo:0.0.1: image文件的名字,默认标签是latest.
/bin/bash: 容器启动以后,内部第一个执行的命令.这里启动Bash,保证用户可以使用Shell

docker container run --rm -p 8000:3000 -it koa-demo /bin/bash
—rm参数:在容器终止运行后自动删除容器文件

其他有用的命令

docker container start

前面的docker container run命令是新建容器,每运行一次,就会新建一个容器。同样的命令运行两次,就会生成两个一模一样的容器文件。如果希望重复使用容器,就要使用docker container start命令,它用来启动已经生成、已经停止运行的容器文件。
docker container start [containerID]

docker container stop

前面的docker container kill命令终止容器运行,相当于向容器里面的主进程发出 SIGKILL 信号。而docker container stop命令也是用来终止容器运行,相当于向容器里面的主进程发出 SIGTERM 信号,然后过一段时间再发出 SIGKILL 信号.
docker container stop [containerID]
这两个信号的差别是,应用程序收到 SIGTERM 信号以后,可以自行进行收尾清理工作,但也可以不理会这个信号。如果收到 SIGKILL 信号,就会强行立即终止,那些正在进行中的操作会全部丢失。

docker container logs

docker container logs命令用来查看 docker 容器的输出,即容器里面 Shell 的标准输出。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令查看输出。
docker container logs [containerID]

docker container exec

docker container exec命令用于进入一个正在运行的 docker 容器。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令进入容器。一旦进入了容器,就可以在容器的 Shell 执行命令了。
docker container exec -it [containerID] /bin/bash

docker container cp

docker container cp命令用于从正在运行的 Docker 容器里面,将文件拷贝到本机。下面是拷贝到当前目录的写法。
docker container cp [containID]:[/path/to/file] .

删除所有不运行的容器

docker rm $(docker ps -a -q)

参考文章:Docker 入门教程-阮一峰

Understanding LSTM Networks

发表于 2018-07-17 | 分类于 deep learning

这是一篇译文,原文地址.如果英文可以,建议直接看英文.

循环神经网络(Recurrent Neural Networks)

在我们思考时,我们不会从头开始,肯定会在思考时加入之前的知识.就如同当你在阅读当前的博客时,你读的每个单词都是基于前面的单词.你不会扔掉所有的东西,然后在从头开始.你的想法有持久性.
传统的神经网络不能使用之前信息.假设你使用传统神经网络来将一部电影中的正在发生的事件分类,怎么使用当前事件之前的信息是很困难的.
循环神经网络解决了这个问题,在其中有循环,允许信息能够保持.

这个图看起来有点奇怪,我们可以将其展开,如下图所示

从图中可以看出,RNN使用了前一时刻的状态来预测当前的状态
RNN在很多领域都取得了成功,比如:语音识别,自然语言处理,翻译,图像字幕等等.这里Andrej Karpathy的blog,里面列举了RNN一些应用.
本文主要介绍LSTM模型,LSTM模型是RNN的一种变种,在很多领域的表现都要比一般的RNN模型更好

长期依赖存在的问题(The Problem of Long-Term Dependencies)

加入我们要基于之前的单词预测下一个单词,如”the clouds are in the sky“,我们要预测最后一个单词sky.在这个例子中,我们不需要更多的上下文,很明显最后一个单词是sky.RNN针对这种情况有很好的表现.

但是在有些情况下,我们需要更多的上下文.例如,我们要预测”I grew up in France… I speak fluent French.”中的最后一个单词French.从最近的信息,比如speak fluent等,可以推测最后一个单词是一种语言.但是如果要知道具体的语言,我们需要更多的上下文.预测点和其对应的相关信息之间的差距很大是由很大可能的.
但是,不幸的是随着差距增加,RNN不可能学会如何连接这种信息

从图中可以看出$h_{t+1}$不能很好地利用$X_0$和$X_1$的信息.理论上,RNN能够学习到长期依赖,但是在实践中,要想在RNN中使用这些长期依赖很困难.幸运的是LSTM模型解决了这个问题.

LSTM网络(LSTM Networks)

LSTM全称Long Short Term Memory,长短期记忆网络.LSTM的设计就是为了避免长依赖问题,记忆长周期的信息是LSTM的默认行为.
所有的递归神经网络都具有重复模块的链式结构.在标准的RNN中,重复模块是一个非常简单的结构,例如单层tanh

LSTM也有这种链式结构,但是重复的模块有一个不同的结构,在其中一个模块中,包含一个四层的神经网络,并且这四层以一种特别方式进行交互.

图中的示例如下

接下来我会详细介绍其中涉及的内容.

LSTMs中的核心思想(The Core Idea Behind LSTMs)

LSTMs中的核心是单元状态,在图中就是最上面的那条水平线.单元状态就像是一条输送带.单元状态沿着整个链流动,对其只有一些线性作用,信息的流动很容易.

LSTM能够移除或者添加信息到单元状态,并且由成为门的结构来控制.门可以选择性的让信息通过或不通过,它是由sigmoid神经网络层和点积操作组成.

sigmoid层的输出范围0-1,描述了可以通过多少的信息.输出为0意味着什么都不通过,输出为1意味着都能通过.从LSTM的结构中,我们可以看到LSTM的一个重复模块包括三个这样的门.

一步一步认识LSTM

LSTM的第一步就是决定从单元状态中丢弃哪些信息.这个决定是由一个称为”遗忘门(forget gate layer)”单层sigmoid层组成.它接收$h{t-1}$和$x_t$作为输入,输出0到1之间的数字.对于前一个单元状态$C{t-1}$,当遗忘门的输出为1时,表示完全保留$C{t-1}$;当遗忘门的输出为0时,表示完全舍弃$C{t-1}$.
我们在回到之前根据之前的内容来预测下一个单词这个问题.在这样的问题中,单元状态可能包括当前对象的性别,所以可以使用正确的代词.但是当遇到一个新的对象时,我们就想要忘记老对象的性别.

图中的$Wf \cdot [h{t-1},xt]$,大家看起来有些不清楚.个人认为比较合适的写法如下
$\sigma(W_fx_t + U_r h
{t-1} + b_f)$.大家可以参考下英文博客,中文博客

接下来这一步就是决定新信息中的哪些部分需要保存在单元状态中.这包括两个部分,第一部分是sigmoid层(input gate layer),决定更新的值.第二部分是一个tanh层,用来创建一个新的候选值.在下一步中,我们将会把这两个结合起来用来给单元状态做更新.在语言模型的例子中,这一步就代表我们将新对象的性别加入单元状态中,用来代替我们需要忘记的老对象性别.

现在要把旧的单元状态$C_{t-1}$更新为$C_t$.上一步已经决定了更新的内容.我们把旧的单元状态乘以$f_t$,表示我们需要忘记的内容.然后将结果加上$i_t*\tilde{C_t}$,这表示将候选值进行缩放或者延伸.在语言模型的例子中,这表示我们从旧对象的性别中去除信息,然后加上新的信息.

最终,我们需要决定输出的内容.输出内容基于单元状态,但是需要一些处理.首先,我们使用一个sigmoid层来决定单元状态的哪部分需要输出.然后,我们将单元状态经过tanh(将值限定在-1到1之间),在乘以sigmoid gate的输出.所以我们仅仅输出了我们决定的.
对于语言模型例子,因为网络之前看到了主语,所以接下来它可能想要输出的信息是关于动词的.例如,网络会根据主语的单数或复数来决定接下来动词的形式.

长短期记忆模型的其他形式(Variants on Long Short Term Memory)

上面提到的是LSTM的一般形式.但是还有好多LSTM的一些变形,虽然变化不大.其中一个流行的LSTM变形是由Gers & Schmidhuber (2000)提出.此模型添加了”peephole connections”,这意味着让门看到了单元状态.

从图中可以看到模型对于所有的gate都添加了peepholes,但是有很多文章只添加了一些peepholes,而另一些则没有.

LSTM另一种变形是将input gate和forget gate进行耦合.这种网络不会单独的决定哪些内容需要忘记,哪些新的信息需要添加,而是一起做这些决定.当我们将要输入内容时,我们仅仅忘记;当我们忘记旧的内容时,我们仅仅输入一些新的值.模型的结构如下

一种引入注意的LSTM变形是GRU(Gated Recurrent Unit),它将forget gate和input gate结合为一个单独的”update gate”.它同样融合了单元状态和隐藏状态以及一些其他的改变.GRU模型要比标准的LSTM模型简单,并且越来越流行.

这里仅仅列举了一些LSTM变形,还有很多其他的LSTM变形.比如Depth Gated RNNs by Yao, et al. (2015).同时在解决长期依赖问题上,也有跟LSTM完全不同的方法,比如Clockwork RNNs by Koutnik, et al. (2014).
Greff, et al. (2015)对这些变形做了一个对比.Jozefowicz, et al. (2015)测试很多的RNN结构,发现在一些特定的任务上比LSTM模型表现的更好的模型.

参考

colah博客
echen博客

echen博客中公式介绍的较为详细

python3学习

发表于 2018-07-10

模块

在python中,一个py文件就是一个模块.而为了避免模块名称冲突,Python又引入了按目录来组织模块的方法,成为Package.引入了包后,对应的模块名前面就添加了包名.类似于Java中的Package.

__init__.py:在包目录下,必须有__init__.py这个文件,如果没有这个文件,那么python会将此文件作为一个普通目录,而不在是一个package.__init__.py可以为空,也可以有代码(一般都是一些import语句).__init__.py本身是一个模块,其模块名为对应的package名称.

Python示例程序

hello.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

import sys

def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')

if __name__=='__main__':
test()

程序说明:程序开始的一,二行是标准注释.第一行注释说明这个文件可以直接在Unix/Linux/Max上运行.第二行表示文件本身使用utf-8编码.第四行是模块的文档注释,第六行是作者信息.
if __name__ == '__main__'::当我们在命令行运行模块时,python解释器会将一个特殊的变量__name__设置为__main__.但是如果在其他模块中导入此模块,python解释器并不是设置__name__变量.

python中的作用域

正常的变量或函数是公开的,可以直接引用如abc,xyz等.
类似__xxx__的变量时特殊变量,也可以直接引用,但是有特殊用途.如__author__,__name__.而模块的文档注释可以用__doc__来访问
类似于_x,__x这样定义的变量或函数是非公开的,类似Java的private.

python模块搜索路径

当我们引入模块时,即使用import导入模块时,python解释器从搜索路径中查找对应的模块.如果找不到,则会报错.
默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中.下面代码可以查看搜索路径内容

1
2
>>> import sys
>>> sys.path

修改搜索路径

可以使用两种方式修改搜索路径

  1. 直接修改sys.path
    1
    2
    >>> import sys
    >>> sys.path.append('待添加路径')
    但是这种方式只在程序运行时有效,程序运行结束后失效
  2. 设置PATHONPATH
    直接将待添加的路径加入PATHONPATH环境变量中即可

类与对象

在python类中,__init__方法就相当于Java中类的构造函数,self表示实例自身.
如果类中的变量定义前加了两个下划线,那么此变量就成为一个私有变量

1
2
3
4
5
6
7
class Student(object):
def __init__(self, name, score):
self.__name = name
self.score = score
a = Student('1', 2)
print(a.__name)
print(a.score)

打印__name时,将会报错,而可以打印score的内容.

鸭子类型

注明:个人解决因为无法限制方法入参的类型,那么传递进去什么都可以

1
2
3
4
5
6
7
8
9
10
class Animal(object):
def run(self):
print('Animal is running...')
def run_twice(animal):
animal.run()
animal.run()
class Timer(object):
def run(self):
print('Start...')
run_twice(Timer())

在Java中,如果定义run_twice方法,必须定义函数的形参类型,如果指定了只能接受Animal类型,则只能传递Animal类型或其子类.但是在python不是这样,只要传递进的类型中存在run方法即可.不过python中不存在限制入参是什么类型.

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

__slots__

python是动态语言,可以在运行过程中给类实例以及类本身添加属性和方法。如果想要限制动态添加的属性,则可以在类中定义__slots__,如下代码

1
2
class Student(object):
__slots__ = ('name', 'age') # 运行绑定的属性限制为name和age

@property

@property是python内置装饰器,可以在给属性添加getter以及setter方法,从而简化代码。如下使用

1
2
3
4
5
6
7
8
9
10
11
12
class Student(object):
@property
def score(self):
return self._score

@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value

第一个score方法被@property修改,从而为score属性添加了getter方法,第二个score方法被@score.setter修饰,添加了setter方法.如果去掉第二个score,则score属性将没有setter方法.

Refactor-improving the Design of Existing Code

发表于 2018-06-23 | 分类于 读书笔记

前言

Refactor-improving the Design of Existing Code(重构-改善既有代码的设计),这是Martin Fowler的一本书.主要是针对Java语言.书已经出版多年了,买这本书也有一年多了,但是一直只看了第一章.最近因为工作不是很忙,就拾起来读了.这里主要是记录下读这本书的一些感想以及收获.
读这本书有个收获,重构不单单只是修改函数名称,重命名变量名称那么简单.比如进行类的分解,将合适的函数放置在合适的类中.而这些都需要我们在平常的编程活动中去实践.
什么时候进行重构呢?当遇到几个方面时,可以考虑进行重构.发现当为函数添加新功能时,并不能很好的添加进去,这时候我们可以进行重构;当写完功能时,我们也可以考虑重构.总之,重构并不是开发过程中必须要通过的一个过程,就像编译-链接等.你可以随时进行重构.

重构

我们在平时编程的时候,不可能一次性的就把问题或者程序写的完美无缺,除非我们已经非常熟知某个问题或者某个领域,即使非常熟悉,可能还有我们没有发现的提升之处.那么当我们的程序写的不完美的时候,我们怎么办呢?这时候就可以采用重构的方法来小步快跑的提升我们程序的易读性,优化程序的结构.而这本书给我们提供了如何进行重构的一系列方法.
整个开发流程:
开发—-> 测试 —-> 重构 —-> 测试

测试

在我们重构之前,一定要有合适的测试.在Java中,我们经常用的就是Junit和TestNG了.其中Junit主要是用来做单元测试,而TestNG主要是功能测试或者集成测试.如果我们是开发人员,可能使用Junit比较多,不过TestNG一般也会用到.在我们重构完成之后,一定要跑测试用例,跑通了所有的测试用例,这项重构才算是完成.

代码的坏味道

  1. 重复代码
    遇到相同的程序结构,应该想办法将它们合二为一,这样程序会变得更好
  2. 过长函数
    函数的代码行数不宜过长,并且函数的命名应该体现出函数做了什么而不是表明怎么做
  3. 过大的类
    当类过大时,我们可以采取Extract class 或者Extract Subclass来减小类的大小
  4. 过长的参数列
    将函数的参数可以用对象来进行传递
  5. 发散式变化
    如果某个类经常因为不同的原因在不同的方向上发生变化,那么这个类就产生了发散式变化.理想情况是针对外界的某一个变化产生的所有相应修改,都应该发生在单一类中.采用 Extract Class将因为特定原因造成的所有变化提炼到另一个类中
  6. 霰弹式修改
    如果遇到某种变化,都必须在许多不同的类内做出许多小修改,那么就产生了霰弹式修改.遇到这种情况,应该使用Move Method和Move Field将所有的修改集中在一个类中.
    发散式修改是指”一个类受多种变化的影响”,而霰弹式修改是指”一种变化引发多个类的相应修改”.理想情况是外界变化与需要修改的类趋于一一对应.
  7. 依恋情结
    函数对某个类的兴趣高过对自己所处类的兴趣.这时候我们应该将此函数移动到此函数对应的类中.
  8. 数据泥团
    两个类中相同的字段,许多函数签名中相同的参数.这些总是绑定在一起出现的数据应该拥有属于它们的对象
  9. 基本类型偏执
    我们有时候可以用对象来替代基本类型,比如当表示由一个起始值和一个结束值组成的range类,一个币种的money类.我们可以创建对应的小对象.
  10. switch惊悚现身
    在面向对象程序中尽量少用switch语句,因为switch容易造成重复.而且修改switch条件时,需要找到所有的switch来进行修改
  11. 平行继承体系
  12. 冗余类
    消除没有用的类
  13. 夸夸其谈未来性
  14. 令人迷惑的暂时字段
  15. 过度耦合的消息链
  16. 中间人
    不要过度使用委托
  17. 狎昵关系
  18. 异曲同工的类
  19. 不完美的类库
  20. 过多的注释

重构方法

这里仅仅记录了部分的重构方法

  1. 提取方法
    在重构中有一个很核心的动作就是将代码提取为一个单独的方法.这种方式其实也杜绝了重复代码的出现,能够在我们修改代码的时候只需要修改一处就可以.并且能够在多个地方使用
  2. 将注释提取为方法
    理想的程序就是代码完全能够表达自己,当我们在程序中看到注释或者添加注释的时候,我们可以先思考下能否将注释下的代码提取为一个方法,并且方法的函数名称能够体现注释的内容,方法名称要体现程序做了什么而不是怎么做.
  3. 明确类的职责
    类的职责不宜过于多,不宜过于复杂.如果看到一个类承担的职责很多,我们可以考虑是否可以将此类拆分,将不属于其应该承担的责任提取到一个新的类中.然后在源类中通过引用来使用新类中的字段或方法
  4. 用查询来代替变量
    就是在我们使用变量时,我们应该采用计算变量的方法来替代此变量.其实对于这一做法,我是抱有怀疑态度的,因为这造成了函数运行多次.会造成性能下降,但是书中说性能优化属于优化阶段的工作,而且这样重构会为优化阶段带来很好的铺垫.这个我觉得还是主要看平时我们的工作中需要具体情况具体分析.不能尽信书,什么东西都需要我们自己的独立思考
  5. 以卫语句取代嵌套条件表达式
    条件表达式的两种形式:1.所有分支都属于正常分支;2.只有一种是正常行为,其他都是不正常行为.针对情况1,建议使用if…else结构;针对情况2,使用if进行判断,然后直接返回结果.这样子处理能够提高程序清晰度
  6. 封装集合
    当我们在类中有个字段是集合时,我们的返回函数不应该直接返回集合自身,而是应该返回集合的只读副本.另外,不应该为这整个集合提供一个设值函数,应该提供为集合添加,删除元素的函数.
  7. 分解条件表达式
    将复杂的条件表达式分解为较简单的表达式,可是试着尝试将if段落以及else then等提炼为单独的函数
  8. 合并条件表达式
    如果发现检查条件各不相同,但是最终的行为一致.应该使用逻辑与和逻辑或合并为一个条件表示式
  9. 引入参数对象
    可以使用对象来替换函数中的众多参数.比如,遇到数据范围的,参数是一个开始时间,结束时间,那么可以新建一个日期范围类,其中的开始时间和结束时间不能修改.用此类来替换开始时间和结束时间.这样做的好处是可以缩短参数列表.
  10. 移除设置函数
    如果类中的某个字段在对象创建之后不能修改,那么就不提供设置函数,并且将此字段设为final

CS231n Convolutional Neural Networks for Visual Recognition

发表于 2018-06-10 | 分类于 deep learning

这是一篇翻译文章,但是也不全是,主要是读了文章之后,用自己的话将其复述出来
来源: CS231n Convolutional Neural Networks for Visual Recognition

CNN

卷积神经网络跟一般的神经网络是非常相似的.它们都由神经元组成,并且这些神经元上都有需要学习的权重和偏置项.每个神经元接收输入,执行点积,然后可选择的将输出进行非线性运算.整个网络表示了一个可微的得分函数:即从原始的图像像素到对应的类.两者都有一个损失函数(例如:SVM/Softmax),并且在一般神经网络中用到的技术也能应用到CNN中.
两者的区别在哪里呢?ConvNet网络假设输入是图像,所以我们能够将某些属性编码到网络结构中.这使得前馈函数更有效率并且能极大的减少网络中的参数.

CNN结构概览

回顾:在一般的神经网络中,神经网络接收一个输入(一个向量),然后经过一系列的隐藏层进行转换.每个隐藏层是由一组神经元组成,每个神经元都与前一层的所有神经元相连,属于同一层的神经元完全独立并且不共享任何连接.最后一层是输出层,在分类问题中,它代表了类的得分.
一般的神经网络不能很好的扩展到完整的图像.在CIFAR-10中,一副图像的大小是32$\times$32$\times$3,所以隐藏层中的一个神经元上将会有32$\times$32$\times$3=3072个权重.参数的数量仍然可以接受,但是全连接结构不能扩展到更大的图像上了.例如,如果图像的大小为200$\times$200$\times$3,那么一个神经元将需要200$\times$200$\times$3=120000个权重.可见,全连接神经网络需要的参数将会非常多.这会导致过拟合.
卷积神经网络充分利用了输入是图像的事实,并且用一个更加合理的方式约束了神经网络的结构.特别的,与一般的神经网络不同,卷积神经网络各层由三维排列的神经元组成:宽度,高度,深度(注意,这里的深度指的是激活的第三维,而不是整个神经网络的深度).例如,在CIFAR-10中的输入图像的维度是32$\times$32$\times$3.我们很快会看到,当前层的神经元仅仅连接前一层中的部分神经元.此外,对于CIFAR-10最终的输出层的维度是1$\times$1$\times$10,因为ConvNet最后会将整个图像转换为一个类得分向量,下面是可视化:

常规神经网络
ConvNet

上图表示一个三层的神经网络,下图表示卷积神经网络.

A ConvNet is made up of Layers. Every Layer has a simple API: It transforms an input 3D volume to an output 3D volume with some differentiable function that may or may not have parameters.

ConvNet 层

就像我们上面描述的,一个简单的ConvNet是一系列层组成,ConvNet的每层通过一个可微的函数将输入转化到输出.ConvNet主要由卷积层,池化层和全连接层组成.下面是一个针对CIFAR-10的简单例子:

  • INPUT(32$\times$32$\times$3):输入是原始图像的像素,一副图像的宽是32,长是32,并且有三个颜色通道R,G,B
  • 卷积层:当前层的神经元只连接前一层的部分神经元.如果我们使用12个过滤器,则经过卷积层后的维度是[32$\times$32$\times$12]
  • RELU:对于输入采用ReLu激活函数,并不会改变输入维度,如果前一个维度是[32$\times$32$\times$12],则经过ReLu之后仍然是[32$\times$32$\times$12]
  • POOL:池化层会在空间维度上执行下采样,这会导致维度变化,[16$\times$16$\times$12],但是注意最后一维没有改变
  • FC(全连接层):这是一个全连接的结构,最后的输出是[1$\times$1$\times$10].
    卷积神经网络就是将原始的像素图像经过一层一层的计算,最后得到最终的分类得分.注意有些层需要参数,而有些层不需要参数.CONV/FC层不仅仅对于输入进行激活,同时需要将权重和偏置作用在输入上.而RELU/POOL只是一个固定的函数,并没有参数.
    总结:

  • 卷积神经网络就是一系列层组成,将输入图像体积转换为输出体积(体积表面了维度)

  • 卷积神经网路包括完全不同的层(e.g. CONV/FC/RELU/POOl)
  • 每一层通过一个可微的函数将3D volume的输入转换为3D volume的输出
  • 有些层需要参数,有些不需要(e.g. CONV/FC 需要, RELU/POOlL不需要)
  • 有些层需要额外的超参数,有些不需要(e.g. CONV/FC/POOL需要,RELU不需要)

因为无法很难画出3D的部分,所以这里每一层只展示了深度部分的一片.最后给出了得分最高的五个标签.这里展示的是一个很小的 VGG网络.查看详细展示

卷积层

卷积层是卷积神经网络的核心部分,并且涉及了大量的计算.卷积层使用过滤器对于原图像进行卷积,过滤器每次只能针对整副图像的一部分进行计算,所以我们需要移动过滤器,遍历整个图像.这里过滤器的大小又叫做神经元的接收域.
Example 1:假设输入为[32$\times$32$\times$3],过滤器大小为5$\times$5,那么卷积层中的某个神经元需要的参数为5$\times$5$\times$3 + 1=76.为什么需要这么多的参数呢?针对颜色通道R,当前过滤器对应的局部区域的点是5$\times$5=25个,有三个通道,所有总的参数为75,另外再加一个偏置项,所有一个神经元总共需要76个参数.注意这里的深度为3,这是因为输入的深度是3.

Example 2:假设输入为[16$\times$16$\times$20],过滤器大小为3$\times$3,那么卷积层中每个神经元都需要3*3*20 + 1=180个参数.

第一张图展示了卷积层,可以看到一个神经元连接了原图像的局部区域,但是连接了所有的深度(这是是三个颜色通道).这里展示了五个神经元,这五个神经元都连接到了图像的同一个局域上.第二张图展示了神经元的计算.
Spatial arrangement 前面我们仅仅讨论了卷积层中的每个神经元如何连接到前一层,我们还没有讨论在卷积层的输出中有多少个神经元.深度,步长,0值填充这些超参数控制着卷积层的输出的大小

  1. 卷积层输出的深度等于过滤器的数量,而每个过滤器就是去寻找输入到卷积层数据的不同之处.如果输入是原始图像,那么过滤器就是去寻找不同方向的边,颜色等.
  2. 步长是过滤器移动的长度,一般常用的是1和2
  3. 0值填充就是在卷积层的输入的边界上填充0值,使用0值填充使得我们能够控制经过卷积层之后的空间大小.
    下面的公式可以用来计算卷积层输出的空间大小其中W(输入数据的大小),F(卷积层神经元的接收域大小),S(步长),P(零值填充的宽度).例如输入为7$\times$7,过滤器为3$\times$3,步长为1,不填充,则得出输出为5$\times$5.当步长变为2时,那么输出变为3$\times$3.

这里的输入只有一个x轴,输入为[1,2,-1,1,-3],过滤器大小为3,采用零值填充.所以W=5,F=3,P=1.图片最右侧是过滤器的权重,偏置为0.左图:步长为1,最后得到的输出的尺寸为5;右图:步长为2,最后得到的输出尺寸为3.所有黄色的神经元共享相同的参数


Use of zero-padding.当步长为1(即S=1),设置$P=(F-1)/2$,这样卷积层的输入跟输出将会有相同大小的空间.
Constraints on strides.

池化层

Normalization Layer

全连接层

全连接层转变为卷积层

ConvNet 结构

层模式

层大小模式

常用CNN

计算考虑

参考

hexo搭建github博客多设备更新

发表于 2018-06-02 | 分类于 hexo

hexo分支上保存了原始博客文件(相关md文件),master分支上是博客展示的内容(相关html)

前段时间由于电脑重新安装了系统,导致自己的博客文件丢失,而github上面的只有发布后的文件,没有博客的源文件。想要恢复到源文件至今还没有找到解决方法,好在原来的博客内容不多,丢失了也无所谓了。但是以后要是换电脑了,这种问题怎么解决呢?今天介绍一个解决方案。
其实方法很简单,首先应该有两个分支,一个分支用来保存发布后的内容,一个分支用来保存源文件.这里用master分支保存发布后的内容,hexo分支保存源文件内容.

第一步

设备A上搭建github博客

第二步

在博客根目录下,打开_config.yml,添加如下内容

1
2
3
4
deploy:
type: git
repository: git@github.com:youname/youname.github.io.git
branch: master

可以看到现在已经有一个master分支,但是这时候个人博客还不是一个git目录

提交代码

1
2
// hexo编译源文件,生成静态文件,也可以分开执行
hexo clean && hexo g && hexo d

hexo clean: 清空博客缓存
hexo g(hexo generator 的简写):生成静态文件
hexo d(hexo deploy的简写): 部署文件,这条命令会使用第一步中的配置信息进行部署

现在打开yourname.github.io就能够看到你的博客了

第三步

因为我用的是next主题(其他主题做相似处理).删除next文件下的.git文件夹,这是因为我们在要我们的博客下创建.git,如果子目录下也有.git,会有问题.然后执行以下命令

1
2
3
4
5
6
7
8
9
10
// git初始化
git init
// 新建分支并切换到新建的分支
git checkout -b 分支名
// 添加所有本地文件到git
git add .
// git提交
git commit -m "提交说明"
// 文件推送到hexo分支
git push origin hexo

以后操作都是在hexo分支中,当我们修改了我们的博客内容时,先执行hexo clean && hexo g && hexo d ,这个命令用来将本地博客发布到github上
然后在将本地内容提交到hexo分支中

第四步

假设我们需要在电脑B上搭建我们之前的博客内容.我们需要拉取我们的hexo分支,因为hexo分支是源文件,master可以不用拉取

1
2
3
4
5
6
// 克隆分支到本地
git clone -b hexo https://github.com/用户名/仓库名.git
// 进入博客文件夹
cd youname.github.io
// 安装依赖
npm install

第五步

电脑B上编辑博客内容,静态文件提交到master分支,源文件提交到hexo分支

1
2
3
4
5
6
7
8
9
10
11
//博文提交到master上面。
hexo clean && hexo g && hexo d
//源文件提交到hexo分支上面。
// 添加源文件
git add .
// git提交
git commit -m ""
// 先拉原来Github分支上的源文件到本地,进行合并
git pull origin hexo
// 比较解决前后版本冲突后,push源文件到Github的分支
git push origin hexo

第六步

在电脑A上可以同步hexo分支,开始更新博客

注意: 以后操作都是在hexo分支中,当我们修改了我们的博客内容时,先执行hexo clean && hexo g && hexo d ,这个命令用来将本地博客发布到github上
然后在将本地内容提交到hexo分支中

<1…34

37 日志
22 分类
45 标签
RSS
© 2024 liang
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4