要读写这些数据源,首先需要创建SparkSession:
import os
from pyspark.sql import SparkSession, Row
spark = SparkSession.builder \
.appName("Demo data sources") \
.getOrCreate()
为便于演示,我们构造一个DataFrame:
data = [Row(Country="France",Age=44,Salary=72000,Purchased="No"),
Row(Country="Spain",Age=27,Salary=48000,Purchased="Yes"),
Row(Country="Germany",Age=30,Salary=54000,Purchased="No"),
Row(Country="Spain",Age=38,Salary=61000,Purchased="No"),
Row(Country="Germany",Age=40,Salary=65000,Purchased="Yes"),
Row(Country="France",Age=35,Salary=58000,Purchased="Yes"),
Row(Country="Spain",Age=27,Salary=52000,Purchased="No"),
Row(Country="France",Age=48,Salary=79000,Purchased="Yes"),
Row(Country="Germany",Age=50,Salary=83000,Purchased="No"),
Row(Country="France",Age=37,Salary=67000,Purchased="Yes")]
df = spark.createDataFrame(data)
df.show()
运行结果:
+---+-------+---------+------+
|Age|Country|Purchased|Salary|
+---+-------+---------+------+
| 44| France| No| 72000|
| 27| Spain| Yes| 48000|
| 30|Germany| No| 54000|
| 38| Spain| No| 61000|
| 40|Germany| Yes| 65000|
| 35| France| Yes| 58000|
| 27| Spain| No| 52000|
| 48| France| Yes| 79000|
| 50|Germany| No| 83000|
| 37| France| Yes| 67000|
+---+-------+---------+------+
将dataframe数据集保存为csv, json, parquet和HIVE Table格式文件:
df.write.csv("data/demo.csv", mode='overwrite', header=True)
df.write.json("data/demo.json", mode = 'ignore')
df.write.parquet("data/demo.parquet", mode = 'ignore')
df.write.saveAsTable("demo")
创建一个临时表:customers,它是随同spark Session存在的:
df.createOrReplaceTempView("customers")
查看(用HIVE SQL)有哪些表:
spark.sql("show tables").show()
+--------+---------+-----------+
|database|tableName|isTemporary|
+--------+---------+-----------+
| default| demo| false|
| |customers| true|
+--------+---------+-----------+
用HIVE SQL查询表:
spark.sql("SELECT * FROM customers WHERE Salary > 60000").show()
+---+-------+---------+------+
|Age|Country|Purchased|Salary|
+---+-------+---------+------+
| 44| France| No| 72000|
| 38| Spain| No| 61000|
| 40|Germany| Yes| 65000|
| 48| France| Yes| 79000|
| 50|Germany| No| 83000|
| 37| France| Yes| 67000|
+---+-------+---------+------+
spark.sql("SELECT * FROM customers WHERE Country='France'").show()
+---+-------+---------+------+
|Age|Country|Purchased|Salary|
+---+-------+---------+------+
| 44| France| No| 72000|
| 35| France| Yes| 58000|
| 48| France| Yes| 79000|
| 37| France| Yes| 67000|
+---+-------+---------+------+
读txt文件,内容将放在value字段中:
df = spark.read.text("data/demo.csv")
df.show(3)
+--------------------+
| value|
+--------------------+
|Age,Country,Purch...|
| 27,Spain,No,52000|
| 48,France,Yes,79000|
+--------------------+
only showing top 3 rows
读csv文件(csv文件中可能包含有字段名称信息的文件头,但数据类型需要推断,你也可以显式指定):
df = spark.read.csv("data/demo.csv", inferSchema = True, header = True)
df.show(3)
+---+-------+---------+------+
|Age|Country|Purchased|Salary|
+---+-------+---------+------+
| 27| Spain| No| 52000|
| 48| France| Yes| 79000|
| 50|Germany| No| 83000|
+---+-------+---------+------+
only showing top 3 rows
读json文件(json文件中包含的字段的名称,但数据类型需要推断,你也可以显式指定):
df = spark.read.json("data/demo.json")
df.show(3)
+---+-------+---------+------+
|Age|Country|Purchased|Salary|
+---+-------+---------+------+
| 44| France| No| 72000|
| 27| Spain| Yes| 48000|
| 30|Germany| No| 54000|
+---+-------+---------+------+
only showing top 3 rows
读parquet文件(parquet文件中保存了数据结构的元信息):
df = spark.read.parquet("data/demo.parquet")
df.show(3)
+-------+---+------+---------+
|Country|Age|Salary|Purchased|
+-------+---+------+---------+
| France| 44| 72000| No|
| Spain| 27| 48000| Yes|
|Germany| 30| 54000| No|
+-------+---+------+---------+
only showing top 3 rows
2019年1月24日星期四
大数据之数据存取:Spark存取文件系统数据源
我们常见的数据文件包括:csv, json, txt和parquet等。Spark对这些文件的读写都提供了内建的支持。
2019年1月23日星期三
大数据之数据存取:从HDFS读excel文件到pandas dataframe
难点:pandas不能直接从hdfs读excel文件。
解决方案:先读excel文件内容到RDD,然后把RDD内容从cluster传回本地,最后调用pandas.read_excel()方法
步骤:
(1)设置pyspark环境
import os
import sys
spark_home = os.environ.get('SPARK_HOME', None)
if not spark_home:
raise ValueError('SPARK_HOME environment variable is not set')
sys.path.insert(0, os.path.join(spark_home, 'python'))
# sys.path.insert(0, os.path.join(spark_home, 'python/lib/py4j-0.10.7-src.zip'))
(2)创建spark context
from pyspark import SparkConf, SparkContext
conf = SparkConf().setAppName('Read Excel File on HDFS to Pandas Dataframe')
sc = SparkContext(conf=conf)
(3)读excel文件内容到RDD
rdd = sc.binaryFiles("/HomeProject/my-demo/demo.xlsx")
(4)把RDD内容从cluster传回本地
arr = rdd.collect()
(4)调用pandas.read_excel()方法读文件内容到pandas dataframe
import io
import pandas as pd
df = pd.read_excel(io.BytesIO(arr[0][1]), header = 0)
df.head(5)
解决方案:先读excel文件内容到RDD,然后把RDD内容从cluster传回本地,最后调用pandas.read_excel()方法
步骤:
(1)设置pyspark环境
import os
import sys
spark_home = os.environ.get('SPARK_HOME', None)
if not spark_home:
raise ValueError('SPARK_HOME environment variable is not set')
sys.path.insert(0, os.path.join(spark_home, 'python'))
# sys.path.insert(0, os.path.join(spark_home, 'python/lib/py4j-0.10.7-src.zip'))
(2)创建spark context
from pyspark import SparkConf, SparkContext
conf = SparkConf().setAppName('Read Excel File on HDFS to Pandas Dataframe')
sc = SparkContext(conf=conf)
(3)读excel文件内容到RDD
rdd = sc.binaryFiles("/HomeProject/my-demo/demo.xlsx")
(4)把RDD内容从cluster传回本地
arr = rdd.collect()
(4)调用pandas.read_excel()方法读文件内容到pandas dataframe
import io
import pandas as pd
df = pd.read_excel(io.BytesIO(arr[0][1]), header = 0)
df.head(5)
2019年1月12日星期六
Keras源码分析(10):Model
文件:/keras/engine/training.py
把Model类放在training.py文件中,说明它肯定与训练有关,由下面Model的定义我们知道,它继承自Network,所以,Model具有Network和训练的功能,此外它还具有什么功能呢?
在Model类主要有四大功能模块: compile, fit, evaluate和predict。下面,我们来一步步解析。
class Model(Network):
一、compile:模型编译。用于配置训练模型。用compile接受的每一个参数对model进行配置。
(1) 传入的loss是个损失函数的字典或列表,它对应着模型的多个输出,在每个输出上使用不同的损失;
(2) loss只是一个损失函数的名称,如果模型有多个输出,则所有的输出都使用相同的损失函数。
不管是哪种,模型最小化的损失值将是所有单个损失的总和。
如果传入的参数target_tensors不为None,即下面的code,说明要使用外部指定的目标张量,它可以是单个张量(单输出模型),张量列表,或一个映射输出名称到目标张量的字典。
(1) temporal: 即表示要执行按时间步采样权重(2D权重);
(2) None,这是默认,为采样权重(1D)。
如果模型有多个输出,则可以传递一个 mode 字典或列表,以指示在每个输出上使用指定的sample_weight_mode。
二、fit:模型训练。在所有的fit参数中,x为训练数据,y为标签数据,validation_split指定有多少比例的训练数据用作验证数据,validation_data为验证数据集,epochs为训练轮次,batch_size为批大小。
处理验证数据:有两种情况:
(1)是否需要验证:通过置do_validation决定,缺省是False,即不需要;但如果传入了参数validation_data或者validation_split或者validation_steps,则do_validation=True,意味着需要验证;
(2)验证数据产生,与下面if分支相对应:
a)由参数validation_data直接传入;否则
b)由validation_split指定一个划分比例,从训练数据中分出一部分作为验证数据;否则
c)当指定了validation_steps,一般与steps_per_epoch结合使用,这里validation_data则为测试数据和验证数据的生成器,本参数指定验证数据生成器的返回次数。
验证函数的输入是这种形式的元组:(val_x, val_y, val_sample_weights)或者(val_x, val_y, val_sample_weights, lr),其中,val_x: 验证数据, val_y: 验证数据标签, val_sample_weights: 样本权重, lr: 学习速率。
把Model类放在training.py文件中,说明它肯定与训练有关,由下面Model的定义我们知道,它继承自Network,所以,Model具有Network和训练的功能,此外它还具有什么功能呢?
在Model类主要有四大功能模块: compile, fit, evaluate和predict。下面,我们来一步步解析。
class Model(Network):
一、compile:模型编译。用于配置训练模型。用compile接受的每一个参数对model进行配置。
def compile(self, optimizer,
loss=None,
metrics=None,
loss_weights=None,
sample_weight_mode=None,
weighted_metrics=None,
target_tensors=None,
**kwargs):
self.optimizer = optimizers.get(optimizer)
self.loss = loss or []
self.metrics = metrics or []
self.loss_weights = loss_weights
self.sample_weight_mode = sample_weight_mode
self.weighted_metrics = weighted_metrics处理参数loss,准备损失函数(loss functions)。有2种情形:(1) 传入的loss是个损失函数的字典或列表,它对应着模型的多个输出,在每个输出上使用不同的损失;
(2) loss只是一个损失函数的名称,如果模型有多个输出,则所有的输出都使用相同的损失函数。
不管是哪种,模型最小化的损失值将是所有单个损失的总和。
if isinstance(loss, dict):
loss_functions = []
for name in self.output_names:
loss_functions.append(losses.get(loss.get(name)))
elif isinstance(loss, list):
loss_functions = [losses.get(l) for l in loss]
else:
loss_function = losses.get(loss)
loss_functions = [loss_function for _ in range(len(self.outputs))]
self.loss_functions = loss_functions
weighted_losses = [
weighted_masked_objective(fn) for fn in loss_functions]
skip_target_indices = []
skip_target_weighing_indices = []
self._feed_outputs = []
self._feed_output_names = []
self._feed_output_shapes = []
self._feed_loss_fns = []
for i in range(len(weighted_losses)):
if weighted_losses[i] is None:
skip_target_indices.append(i)
skip_target_weighing_indices.append(i)处理损失权重loss_weights,它是用以衡量损失函数对不同的模型输出的贡献。 模型将最小化的误差值是由 loss_weights 对每个输出上的损失进行加权的加权总和误差。if loss_weights is None:
loss_weights_list = [1. for _ in range(len(self.outputs))]
elif isinstance(loss_weights, dict):
loss_weights_list = []
for name in self.output_names:
loss_weights_list.append(loss_weights.get(name, 1.))
elif isinstance(loss_weights, list):
loss_weights_list = loss_weights
else:
raise TypeError('Could not interpret loss_weights argument: ')处理target_tensors,创建模型的目标(targets of model)。如果传入的参数target_tensors不为None,即下面的code,说明要使用外部指定的目标张量,它可以是单个张量(单输出模型),张量列表,或一个映射输出名称到目标张量的字典。
self.targets = []
self._feed_targets = []
if target_tensors is not None:
if isinstance(target_tensors, list):
elif isinstance(target_tensors, dict):
tmp_target_tensors = []
for name in self.output_names:
tmp_target_tensors.append(target_tensors.get(name, None))
target_tensors = tmp_target_tensors
elif K.is_tensor(target_tensors):
target_tensors = [target_tensors]
else:
raise TypeError('Expected `target_tensors` to be a tensor')如果target_tensors为None(默认情况),更或者是其中的某个为None,Keras 将为模型的目标创建一个占位符,在训练过程中将使用目标数据。for i in range(len(self.outputs)):
if i in skip_target_indices:
self.targets.append(None)
else:
shape = K.int_shape(self.outputs[i])
name = self.output_names[i]
if target_tensors is not None:
target = target_tensors[i]
else:
target = None
if target is None or K.is_placeholder(target):
if target is None:
target = K.placeholder(
ndim=len(shape),
name=name + '_target',
sparse=K.is_sparse(self.outputs[i]),
dtype=K.dtype(self.outputs[i]))
self._feed_targets.append(target)
self._feed_outputs.append(self.outputs[i])
self._feed_output_names.append(name)
self._feed_output_shapes.append(shape)
self._feed_loss_fns.append(self.loss_functions[i])
else:
skip_target_weighing_indices.append(i)
self.targets.append(target)处理样本权重模式sample_weight_mode,有两种情况: (1) temporal: 即表示要执行按时间步采样权重(2D权重);
(2) None,这是默认,为采样权重(1D)。
如果模型有多个输出,则可以传递一个 mode 字典或列表,以指示在每个输出上使用指定的sample_weight_mode。
sample_weights = []
sample_weight_modes = []
if isinstance(sample_weight_mode, dict):
for i, name in enumerate(self.output_names):
if i in skip_target_weighing_indices:
weight = None
sample_weight_modes.append(None)
else:
if sample_weight_mode.get(name) == 'temporal':
weight = K.placeholder(ndim=2,
name=name + '_sample_weights')
sample_weight_modes.append('temporal')
else:
weight = K.placeholder(ndim=1,
name=name + '_sample_weights')
sample_weight_modes.append(None)
sample_weights.append(weight)
elif isinstance(sample_weight_mode, list):
for i in range(len(self.output_names)):
if i in skip_target_weighing_indices:
weight = None
sample_weight_modes.append(None)
else:
mode = sample_weight_mode[i]
name = self.output_names[i]
if mode == 'temporal':
weight = K.placeholder(ndim=2,
name=name + '_sample_weights')
sample_weight_modes.append('temporal')
else:
weight = K.placeholder(ndim=1,
name=name + '_sample_weights')
sample_weight_modes.append(None)
sample_weights.append(weight)
else:
for i, name in enumerate(self.output_names):
if i in skip_target_weighing_indices:
sample_weight_modes.append(None)
sample_weights.append(None)
else:
if sample_weight_mode == 'temporal':
sample_weights.append(
K.placeholder(ndim=2,
name=name + '_sample_weights'))
sample_weight_modes.append('temporal')
else:
sample_weights.append(
K.placeholder(ndim=1,
name=name + '_sample_weights'))
sample_weight_modes.append(None)
self.sample_weight_modes = sample_weight_modes
self._feed_sample_weight_modes = []
for i in range(len(self.outputs)):
if i not in skip_target_weighing_indices:
self._feed_sample_weight_modes.append(
self.sample_weight_modes[i])
self.metrics_names = ['loss']
self.metrics_tensors = []计算总损失(total loss): dot(output_loss, loss_weight) + self.lossestotal_loss = None
with K.name_scope('loss'):
for i in range(len(self.outputs)):
if i in skip_target_indices:
continue
y_true = self.targets[i]
y_pred = self.outputs[i]
weighted_loss = weighted_losses[i]
sample_weight = sample_weights[i]
mask = masks[i]
loss_weight = loss_weights_list[i]
with K.name_scope(self.output_names[i] + '_loss'):
output_loss = weighted_loss(y_true, y_pred,
sample_weight, mask)
if len(self.outputs) > 1:
self.metrics_tensors.append(output_loss)
self.metrics_names.append(self.output_names[i] + '_loss')
if total_loss is None:
total_loss = loss_weight * output_loss
else:
total_loss += loss_weight * output_loss
if total_loss is None:
if not self.losses:
raise ValueError('The model cannot be compiled '
'because it has no loss to optimize.')
else:
total_loss = 0.
for loss_tensor in self.losses:
total_loss += loss_tensor处理metrics,metrics指定了训练和测试期间的模型评估指标。可以为多输出模型的不同输出指定不同的评估指标,它可以是一个dict字典或list列表,如 metrics = {'output_a':'accuracy'}。通常指标名称可以用全名,如:accuracy,crossentropy等,也可能简写,如:acc,ce等。nested_metrics = collect_metrics(metrics, self.output_names)
nested_weighted_metrics = collect_metrics(weighted_metrics,
self.output_names)
self.metrics_updates = []
self.stateful_metric_names = []
self.stateful_metric_functions = []
def handle_metrics(metrics, weights=None):
metric_name_prefix = 'weighted_' if weights is not None else ''
for metric in metrics:
if metric in ('accuracy', 'acc', 'crossentropy', 'ce'):
output_shape = K.int_shape(self.outputs[i])
if (output_shape[-1] == 1 or
self.loss_functions[i] == losses.binary_crossentropy):
if metric in ('accuracy', 'acc'):
metric_fn = metrics_module.binary_accuracy
elif metric in ('crossentropy', 'ce'):
metric_fn = metrics_module.binary_crossentropy
elif (self.loss_functions[i] ==
losses.sparse_categorical_crossentropy):
if metric in ('accuracy', 'acc'):
metric_fn = metrics_module.sparse_categorical_accuracy
elif metric in ('crossentropy', 'ce'):
metric_fn = (
metrics_module.sparse_categorical_crossentropy)
else:
if metric in ('accuracy', 'acc'):
metric_fn = metrics_module.categorical_accuracy
elif metric in ('crossentropy', 'ce'):
metric_fn = metrics_module.categorical_crossentropy
if metric in ('accuracy', 'acc'):
suffix = 'acc'
elif metric in ('crossentropy', 'ce'):
suffix = 'ce'
weighted_metric_fn = weighted_masked_objective(metric_fn)
metric_name = metric_name_prefix + suffix
else:
metric_fn = metrics_module.get(metric)
weighted_metric_fn = weighted_masked_objective(metric_fn)
if hasattr(metric_fn, 'name'):
metric_name = metric_fn.name
else:
metric_name = metric_fn.__name__
metric_name = metric_name_prefix + metric_name
with K.name_scope(metric_name):
metric_result = weighted_metric_fn(y_true, y_pred,
weights=weights,
mask=masks[i])
if len(self.output_names) > 1:
metric_name = self.output_names[i] + '_' + metric_name
j = 1
base_metric_name = metric_name
while metric_name in self.metrics_names:
metric_name = base_metric_name + '_' + str(j)
j += 1
self.metrics_names.append(metric_name)
self.metrics_tensors.append(metric_result)
if isinstance(metric_fn, Layer) and metric_fn.stateful:
self.stateful_metric_names.append(metric_name)
self.stateful_metric_functions.append(metric_fn)
self.metrics_updates += metric_fn.updates
with K.name_scope('metrics'):
for i in range(len(self.outputs)):
if i in skip_target_indices:
continue
y_true = self.targets[i]
y_pred = self.outputs[i]
weights = sample_weights[i]
output_metrics = nested_metrics[i]
output_weighted_metrics = nested_weighted_metrics[i]
handle_metrics(output_metrics)
handle_metrics(output_weighted_metrics, weights=weights)
为梯度和状态更新做准备
self.total_loss = total_loss
self.sample_weights = sample_weights
self._feed_sample_weights = []
for i in range(len(self.sample_weights)):
if i not in skip_target_weighing_indices:
self._feed_sample_weights.append(sample_weights[i])
为了节省时间,对于训练函数、测试函数和预测函数设置的惰性编译
self._function_kwargs = kwargs
self.train_function = None
self.test_function = None
self.predict_function = None
trainable_weights = self.trainable_weights
self._collected_trainable_weights = trainable_weights二、fit:模型训练。在所有的fit参数中,x为训练数据,y为标签数据,validation_split指定有多少比例的训练数据用作验证数据,validation_data为验证数据集,epochs为训练轮次,batch_size为批大小。
def fit(self,
x=None,
y=None,
batch_size=None,
epochs=1,
verbose=1,
callbacks=None,
validation_split=0.,
validation_data=None,
shuffle=True,
class_weight=None,
sample_weight=None,
initial_epoch=0,
steps_per_epoch=None,
validation_steps=None,
**kwargs):
对用户输入的数据进行校验,并转换成适合模型处理的标准数据格式
x, y, sample_weights = self._standardize_user_data(
x, y,
sample_weight=sample_weight,
class_weight=class_weight,
batch_size=batch_size)处理验证数据:有两种情况:
(1)是否需要验证:通过置do_validation决定,缺省是False,即不需要;但如果传入了参数validation_data或者validation_split或者validation_steps,则do_validation=True,意味着需要验证;
(2)验证数据产生,与下面if分支相对应:
a)由参数validation_data直接传入;否则
b)由validation_split指定一个划分比例,从训练数据中分出一部分作为验证数据;否则
c)当指定了validation_steps,一般与steps_per_epoch结合使用,这里validation_data则为测试数据和验证数据的生成器,本参数指定验证数据生成器的返回次数。
验证函数的输入是这种形式的元组:(val_x, val_y, val_sample_weights)或者(val_x, val_y, val_sample_weights, lr),其中,val_x: 验证数据, val_y: 验证数据标签, val_sample_weights: 样本权重, lr: 学习速率。
do_validation = False
if validation_data:
do_validation = True
if len(validation_data) == 2:
val_x, val_y = validation_data
val_sample_weight = None
elif len(validation_data) == 3:
val_x, val_y, val_sample_weight = validation_data
else:
raise ValueError('When passing validation_data, '
'it must contain 2 (x_val, y_val) '
'or 3 (x_val, y_val, val_sample_weights) '
'items, however it contains %d items' %
len(validation_data))
val_x, val_y, val_sample_weights = self._standardize_user_data(
val_x, val_y,
sample_weight=val_sample_weight,
batch_size=batch_size)
if self._uses_dynamic_learning_phase():
val_inputs = val_x + val_y + val_sample_weights + [0.]
else:
val_inputs = val_x + val_y + val_sample_weights
elif validation_split and 0. < validation_split < 1.:
if any(K.is_tensor(t) for t in x):
raise ValueError(
'If your data is in the form of symbolic tensors, '
'you cannot use `validation_split`.')
do_validation = True
if hasattr(x[0], 'shape'):
split_at = int(int(x[0].shape[0]) * (1. - validation_split))
else:
split_at = int(len(x[0]) * (1. - validation_split))
x, val_x = (slice_arrays(x, 0, split_at),
slice_arrays(x, split_at))
y, val_y = (slice_arrays(y, 0, split_at),
slice_arrays(y, split_at))
sample_weights, val_sample_weights = (
slice_arrays(sample_weights, 0, split_at),
slice_arrays(sample_weights, split_at))
if self._uses_dynamic_learning_phase():
val_inputs = val_x + val_y + val_sample_weights + [0.]
else:
val_inputs = val_x + val_y + val_sample_weights
elif validation_steps:
do_validation = True
if self._uses_dynamic_learning_phase():
val_inputs = [0.]
为训练准备输入数组和训练函数。训练函数的输入是这种形式的元组:(x, y, sample_weights) 或者 (x, y, sample_weights, lr),其中,x: 训练数据, y: 标签, sample_weights: 样本权重, lr: 学习速率。
if self._uses_dynamic_learning_phase():
fit_inputs = x + y + sample_weights + [1.]
else:
fit_inputs = x + y + sample_weights
self._make_train_function()
fit_function = self.train_function
out_labels = self.metrics_names
准备输验证函数:
if do_validation:
self._make_test_function()
val_function = self.test_function
callback_metrics = copy.copy(out_labels) + [
'val_' + n for n in out_labels]
else:
callback_metrics = copy.copy(out_labels)
val_function = None
val_inputs = []
由training_arrays.fit_loop实现循环训练逻辑:
return training_arrays.fit_loop(self, fit_function, fit_inputs,
out_labels=out_labels,
batch_size=batch_size,
epochs=epochs,
verbose=verbose,
callbacks=callbacks,
val_function=val_function,
val_inputs=val_inputs,
shuffle=shuffle,
callback_metrics=callback_metrics,
initial_epoch=initial_epoch,
steps_per_epoch=steps_per_epoch,
validation_steps=validation_steps)三、evaluate: 模型评估。在测试模式下对模型进行评估,按batch计算模型的误差损失值和其它可能的评估指标量。其代码逻辑与fit类似。
def evaluate(self, x=None, y=None,
batch_size=None,
verbose=1,
sample_weight=None,
steps=None):
对用户输入的数据进行校验,并转换成适合模型处理的标准数据格式
x, y, sample_weights = self._standardize_user_data(
x, y,
sample_weight=sample_weight,
batch_size=batch_size)
为评估准备输入数组和测试函数
if self._uses_dynamic_learning_phase():
ins = x + y + sample_weights + [0.]
else:
ins = x + y + sample_weights
self._make_test_function()
f = self.test_function
由training_arrays.test_loop实现循环评估逻辑:
return training_arrays.test_loop(self, f, ins,
batch_size=batch_size,
verbose=verbose,
steps=steps)四、predict:预测。对输入的数据x进行预测,输出为对应的预测值(numpy array)
def predict(self, x,
batch_size=None,
verbose=0,
steps=None):
对用户输入的数据进行校验,并转换成适合模型处理的标准数据格式
x, _, _ = self._standardize_user_data(x)
if self.stateful:
if x[0].shape[0] > batch_size and x[0].shape[0] % batch_size != 0:
raise ValueError('In a stateful network, '
'you should only pass inputs with '
'a number of samples that can be '
'divided by the batch size. Found: ' +
str(x[0].shape[0]) + ' samples. '
'Batch size: ' + str(batch_size) + '.')
为预测准备输入数组和预测函数
if self._uses_dynamic_learning_phase():
ins = x + [0.]
else:
ins = x
self._make_predict_function()
由training_arrays.predict_loop实现预测逻辑:
f = self.predict_function
return training_arrays.predict_loop(self, f, ins,
batch_size=batch_size,
verbose=verbose,
steps=steps)2019年1月4日星期五
Keras源码分析(9):Network
文件:/keras/engine/network.py
在前面我们讨论Node的作用时说过,Node对象把层和层,输入和输出联结了起来,最终把一个多层网络连成了一个有向无环图(DAG),而Network对象正是实现这样的过程。
class Network(Layer):
首先,要说明的是,它为什么要继承自Layer,应该有这样几点:
(1)因为它要生成一个有向无环图(DAG),有入口也有出口,有输入也有输出,这一点跟Layer是一致的;
(2)它生成有向无环图(DAG)的目的是为了计算,为了触发DAG进行计算,就需要提供一个函数给外部调用,这个函数就是call, 这一点跟Layer也是一致的;
(3)模型作为一个Layer可嵌套使用。
由上可见,DAG图的构建实际上是由调用_init_graph_network完成的。下面我们略去了Network作为Layer的有关代码后,它的主要逻辑也看得很清楚了。它进一步调用位于同一文件中的外部_map_graph_network,从而获得了关键的几个数据结构nodes, nodes_by_depth, layers, layers_by_depth,并把它们保存到对象的相应的成员变量中。
到此build_map结束,开始准备调用build_map函数。首先,初始化finished_nodes和nodes_in_progress,然后,从outputs循环开始调用build_map函数,按反向递归构建DAG图。
(1)network_nodes:所有nodes集合;
(2)layer_indices:layer->index字典;
(3)nodes_in_decreasing_depth:按遍历顺序保存的nodes列表。
其中结果(1)将被_map_graph_network返回,我们仍需用(2)和(3)来构建节点的深度、按深度递减的层列表和层的深度,即:nodes_by_depth, layers, layers_by_depth。
Network继承自Layer,所以计算也是由Network对象的call方法完成。为了减少重复计算的开销,Network对象对同一inputs和masks的计算结果进行了缓存(self._output_tensor_cache),如果已计算过了,则直接从缓存中取出;如果没有,则调用内部方法self.run_internal_graph进行计算。
在前面我们讨论Node的作用时说过,Node对象把层和层,输入和输出联结了起来,最终把一个多层网络连成了一个有向无环图(DAG),而Network对象正是实现这样的过程。
class Network(Layer):
首先,要说明的是,它为什么要继承自Layer,应该有这样几点:
(1)因为它要生成一个有向无环图(DAG),有入口也有出口,有输入也有输出,这一点跟Layer是一致的;
(2)它生成有向无环图(DAG)的目的是为了计算,为了触发DAG进行计算,就需要提供一个函数给外部调用,这个函数就是call, 这一点跟Layer也是一致的;
(3)模型作为一个Layer可嵌套使用。
DAG图的构建是在Network对象实例化时完成的。
def __init__(self, *args, **kwargs):
if (len(args) == 2 or
len(args) == 1 and 'outputs' in kwargs or
'inputs' in kwargs and 'outputs' in kwargs):
self._init_graph_network(*args, **kwargs)
else:
self._init_subclassed_network(**kwargs)由上可见,DAG图的构建实际上是由调用_init_graph_network完成的。下面我们略去了Network作为Layer的有关代码后,它的主要逻辑也看得很清楚了。它进一步调用位于同一文件中的外部_map_graph_network,从而获得了关键的几个数据结构nodes, nodes_by_depth, layers, layers_by_depth,并把它们保存到对象的相应的成员变量中。
def _init_graph_network(self, inputs, outputs, name=None):
......
self.inputs = to_list(inputs, allow_tuple=True)
self.outputs = to_list(outputs, allow_tuple=True)
......
nodes, nodes_by_depth, layers, layers_by_depth = _map_graph_network(
self.inputs, self.outputs)
self._network_nodes = nodes
self._nodes_by_depth = nodes_by_depth
self._layers = layers
self._layers_by_depth = layers_by_depth
......
现在,我们来看看_map_graph_network的代码:
def _map_graph_network(inputs, outputs):
# 初始化几个数据结构用来保存与所要创建的图相关的nodes和layers信息
network_nodes = set() # ids of all nodes relevant to the Network
nodes_depths = {} # dict {node: depth value}
layers_depths = {} # dict {layer: depth value}
layer_indices = {} # dict {layer: index in traversal}
nodes_in_decreasing_depth = []
下面build_map内部数据是一个递归函数,它是要从传入的tensor开始反向递归构建DAG图。
在递归构建过程中,那些相关的node将呈现下列3种状态中的一种:
(1)已完成(finished_nodes)
(2)进行中(nodes_in_progress)
(3)将要处理(layer._inbound_nodes[node_index])
所有这些都作为参数传给了build_map函数。
def build_map(tensor,
finished_nodes,
nodes_in_progress,
layer,
node_index,
tensor_index):
# 获得将要处理的node
node = layer._inbound_nodes[node_index]
# 检查nodes_in_progress以避免环
if node in nodes_in_progress:
raise ValueError('The tensor ' + str(tensor) + ' at layer "' +
layer.name + '" is part of a cycle.')
# 防止重复劳动
if node in finished_nodes:
return
# 更新外层network_nodes集合
node_key = _make_node_key(layer.name, node_index)
network_nodes.add(node_key)
# 更新外层layer_indices字典
if layer not in layer_indices:
layer_indices[layer] = len(layer_indices)
# 开始处理该node
nodes_in_progress.add(node)
# 深度优先搜索那些连接到当前node的输入方向上的tensors
for i in range(len(node.inbound_layers)):
x = node.input_tensors[i]
layer = node.inbound_layers[i]
node_index = node.node_indices[i]
tensor_index = node.tensor_indices[i]
build_map(x, finished_nodes, nodes_in_progress, layer,
node_index, tensor_index)
# 处理完毕,将当前node加到finished_nodes中,
# 并从nodes_in_progress中移出
finished_nodes.add(node)
nodes_in_progress.remove(node)
# 这是最为关键一步,它将所有nodes的遍历深度保存到
# 队列nodes_in_decreasing_depth中,此队列将是后
# 续正向计算和误差反向传播执行的依据
nodes_in_decreasing_depth.append(node)到此build_map结束,开始准备调用build_map函数。首先,初始化finished_nodes和nodes_in_progress,然后,从outputs循环开始调用build_map函数,按反向递归构建DAG图。
finished_nodes = set()
nodes_in_progress = set()
for x in outputs:
layer, node_index, tensor_index = x._keras_history
build_map(x, finished_nodes, nodes_in_progress,
layer=layer,
node_index=node_index,
tensor_index=tensor_index)上述调用build_map函数产生了3个结果:(1)network_nodes:所有nodes集合;
(2)layer_indices:layer->index字典;
(3)nodes_in_decreasing_depth:按遍历顺序保存的nodes列表。
其中结果(1)将被_map_graph_network返回,我们仍需用(2)和(3)来构建节点的深度、按深度递减的层列表和层的深度,即:nodes_by_depth, layers, layers_by_depth。
# 根据nodes_in_decreasing_depth计算
# 节点到深度的映射nodes_depths:node->depth,
# 以及层到深度的映射layers_depths:layer->depth
for node in reversed(nodes_in_decreasing_depth):
depth = nodes_depths.setdefault(node, 0)
previous_depth = layers_depths.get(node.outbound_layer, 0)
depth = max(depth, previous_depth)
layers_depths[node.outbound_layer] = depth
nodes_depths[node] = depth
for i in range(len(node.inbound_layers)):
inbound_layer = node.inbound_layers[i]
node_index = node.node_indices[i]
inbound_node = inbound_layer._inbound_nodes[node_index]
previous_depth = nodes_depths.get(inbound_node, 0)
nodes_depths[inbound_node] = max(depth + 1, previous_depth)
# 按深度对节点进行分组,即:depth->nodes,从而得到nodes_by_depth
nodes_by_depth = {}
for node, depth in nodes_depths.items():
if depth not in nodes_by_depth:
nodes_by_depth[depth] = []
nodes_by_depth[depth].append(node)
# 按深度对层进行分组,即:depth->layers,从而得到layers_by_depth
layers_by_depth = {}
for layer, depth in layers_depths.items():
if depth not in layers_by_depth:
layers_by_depth[depth] = []
layers_by_depth[depth].append(layer)
# 按深度下降的顺序组织排列所有层
depth_keys = list(layers_by_depth.keys())
depth_keys.sort(reverse=True)
layers = []
for depth in depth_keys:
layers_for_depth = layers_by_depth[depth]
# 如果深度相同,则按遍历的顺序
layers_for_depth.sort(key=lambda x: layer_indices[x])
layers.extend(layers_for_depth)
......
返回节点的集合、节点的深度、按深度递减的层列表和层的深度
return network_nodes, nodes_by_depth, layers, layers_by_depthNetwork继承自Layer,所以计算也是由Network对象的call方法完成。为了减少重复计算的开销,Network对象对同一inputs和masks的计算结果进行了缓存(self._output_tensor_cache),如果已计算过了,则直接从缓存中取出;如果没有,则调用内部方法self.run_internal_graph进行计算。
def call(self, inputs, mask=None):
inputs = to_list(inputs)
if mask is None:
masks = [None for _ in range(len(inputs))]
else:
masks = to_list(mask)
cache_key = object_list_uid(inputs)
cache_key += '_' + object_list_uid(masks)
if cache_key in self._output_tensor_cache:
return self._output_tensor_cache[cache_key]
else:
output_tensors, _, _ = self.run_internal_graph(inputs, masks)
return output_tensors
# 根据inputs计算network的输出tensors。
def run_internal_graph(self, inputs, masks=None):
if masks is None:
masks = [None for _ in range(len(inputs))]
# tensor_map中存放所有已计算过的tensors和masks
# 用传入的inputs和masks初始化tensor_map
tensor_map = {}
for x, y, mask in zip(self.inputs, inputs, masks):
tensor_map[str(id(x))] = (y, mask)
# 依深度递减遍历
depth_keys = list(self._nodes_by_depth.keys())
depth_keys.sort(reverse=True)
for depth in depth_keys:
nodes = self._nodes_by_depth[depth]
for node in nodes:
layer = node.outbound_layer
reference_input_tensors = node.input_tensors
reference_output_tensors = node.output_tensors
# 检查reference_input_tensors中的所有tensors是否都在tensor_map中
# 即是否都已计算过了,把其中已计算过的放进computed_data
computed_data = [] # List of tuples (input, mask).
for x in reference_input_tensors:
if str(id(x)) in tensor_map:
computed_data.append(tensor_map[str(id(x))])
# 检查的方法是比较computed_data和reference_input_tensors中元素的个数是否相等
if len(computed_data) == len(reference_input_tensors):
# 相等则表示当前layer的所有input_tensors都已计算过了,所以可以调用layer.call方法了
with K.name_scope(layer.name):
if node.arguments:
kwargs = node.arguments
else:
kwargs = {}
if len(computed_data) == 1:
computed_tensor, computed_mask = computed_data[0]
if has_arg(layer.call, 'mask'):
if 'mask' not in kwargs:
kwargs['mask'] = computed_mask
output_tensors = to_list(
layer.call(computed_tensor, **kwargs))
output_masks = layer.compute_mask(computed_tensor,
computed_mask)
if output_masks is None:
output_masks = [None for _ in output_tensors]
else:
output_masks = to_list(output_masks)
computed_tensors = [computed_tensor]
computed_masks = [computed_mask]
else:
computed_tensors = [x[0] for x in computed_data]
computed_masks = [x[1] for x in computed_data]
if has_arg(layer.call, 'mask'):
if 'mask' not in kwargs:
kwargs['mask'] = computed_masks
output_tensors = to_list(
layer.call(computed_tensors, **kwargs))
output_masks = layer.compute_mask(computed_tensors,
computed_masks)
if output_masks is None:
output_masks = [None for _ in output_tensors]
else:
output_masks = to_list(output_masks)
if (hasattr(layer, 'activity_regularizer') and
layer.activity_regularizer is not None):
with K.name_scope('activity_regularizer'):
regularization_losses = [
layer.activity_regularizer(x)
for x in output_tensors]
layer.add_loss(regularization_losses,
inputs=computed_tensors)
......
# 把当前计算过的output_tensors和output_masks加入到tensor_map中
for x, y, mask in zip(reference_output_tensors,
output_tensors,
output_masks):
tensor_map[str(id(x))] = (y, mask)
# 从tensor_map提取output_tensors和output_masks
# 从output_tensors中提到output_shapes
output_tensors = []
output_masks = []
output_shapes = []
for x in self.outputs:
assert str(id(x)) in tensor_map, 'Could not compute output ' + str(x)
tensor, mask = tensor_map[str(id(x))]
if hasattr(tensor, '_keras_shape') and output_shapes is not None:
shape = tensor._keras_shape
output_shapes.append(shape)
else:
output_shapes = None
output_tensors.append(tensor)
output_masks.append(mask)
return output_tensors, output_masks, output_shapes
2018年12月27日星期四
Keras源码分析(8):Core Layers
文件:/keras/layers/core.py
Keras在它的keras.layers.core.py模块中,定义了一系列常用的层,包括Dense全连接层、Activation激活层、Flatten展平层等等,它们都继承自基类 Layer,下面将对它们一一阅读并解析。
一、Masking层
它的目的用来跳过某些时间步。这个层的设置和使用有些让人费解,所以是网上关于层发疑问最多的:其中如何设置mask?它是如何工作的?是最核心的两个问题,这正是我们要重点分析的。
初始化Masking对象。我们的目的是要屏蔽Input Tensor中的某些特殊的时间步,即是要对所有列都含有某个特定值的某些时间步进行屏蔽,如[1., 1., 1., 1., 1.],但不是对任意一种模式的时间步都可以屏蔽的。我们要把这样的一个值传递给Masking对象的__init__方法,Masking对象的__init__方法中接收这个值的参数就是mask_value。
mask_value的缺省值是 0., 所以如果你什么参数值都没传的话,那么就是要屏蔽Input Tensor中那些全为 0 的行。此外,要想让这个mask起作用的话,还必须设置self.supports_masking这个标志为True。
下列call函数让我们知道masking是如何工作的。其中有个技巧,就是运用逻辑运算“与”和“或”。首先,用“不等”(K.not_equal)对Input Tensor中的每个特征值与mask_value进行比较;然后用“任意一个”(K.any)逐时间步进行检查,检查结果是:除了所有特征值都是与mask_value相等的时间步,它的结果值为False外,其它的时间步的结果值都是True;再就是用K.cast把逻辑的False和True它们转换为浮点值的0.或1.;最后,把inputs与该转换过后的结果相乘。经过这样一些列的操作后,把要屏蔽的行的值都变成了0.,从而在后续的运算中,屏蔽了该时间步的作用。
二、Dropout层
该层的作用是要在网络的训练阶段按一定的比率(rate)随机地(在Tensorflow中是用一致分布)丢弃(屏蔽)部分输入单元,目的是为了防止过配。
它的初始化参数中有两个是可想而知的:
(1)rate:取[0,1]之间的一个浮点娄,设置丢弃(屏蔽)输入单元的比例。
(2)seed: Python整数用作随机数种子。
而另外一个参数noise_shape,从字面看倒是很直接,但理解起来却不那么容易,它是用来指定要对哪些维做dropout。举个例子:设input shape是(batch_size, timesteps, features),我们要对timesteps维做dropout,那么我们可能这样设置noise_shape:noise_shape=(batch_size, 1, features)。这就是说,我们把noise_shape与input_shape逐维进行比较,如果不同且noise_shape将该维置为1的,即是要对该维做dropout。
参考:Dropout: A Simple Way to Prevent Neural Networks from Overfitting
三、SpatialDropout1D层
该层是Dropout层的运用于1D的特殊情况,对于这种case, 输入tensor的shape应该是(samples, timesteps, channels),dropout将作用于timesteps维,所以noise_shape应该是(samples, 1, channels)。
参考:Efficient Object Localization Using Convolutional Networks
四、SpatialDropout2D层
该层是Dropout层的运用于2D图像的情况,对于这种case, 依据图像格式的不同,即channels在图像数据中位置的不同(channels_last或channels_first), 输入tensor的shape应该是(samples, rows, cols, channels)抑或是(samples, channels, rows, cols),不管是哪一种,dropout将作用于rows维和cols维,所以相应地noise_shape应该是(samples, 1, 1, channels)抑或是(samples, channels, 1, 1)。
参考:Efficient Object Localization Using Convolutional Networks
五、SpatialDropout3D层
理解了前面的1D和2D,SpatialDropout3Db也就很容易理解了。对于这种case, 依据图像格式的不同,即channels在图像数据中位置的不同(channels_last或channels_first), 输入tensor的shape应该是(samples, dim1, dim2, dim3, channels)抑或是(samples, channels, dim1, dim2, dim3),不管是哪一种data_format,dropout将作用于dim1维、dim2维和dim3维,所以相应地noise_shape应该是(samples, 1, 1, 1, channels)抑或是(samples, channels, 1, 1, 1)。
参考:Efficient Object Localization Using Convolutional Networks
六、Activation激活层
这个层非常简单,闵是将激活函数应用于输入。该激活函数由初始化参数 activation 指定。
七、Reshape层
对输入的尺寸重新调整。如果使用此层作为模型中的第一层,则需要使用参数 input_shape (其中不包括样本数samples的维)。在target_shape中的某一维可以使用 “-1”, 表示维度推断。
此内部函数将处理维度推断,变量unknown用来保存待推断维度的轴索引,变量known用来统计target_shape已知的维数,所以我们用所有input_shape中维数除以known, 就可推断待定的维数。
八、Permute层
按照给定的模式重新排列输入维度的位置。它与前面讨论的Reshape层不同:
Reshape层改变输入数据的形状,但没改变输入数据的顺序,例如:
Reshape(dims=(2,-1)),作用在[[1, 2, 3, 4, 5, 6]]上,输出结果是:[[[ 1. 2. 3.], [ 4. 5. 6.]]],
Reshape(dims=(3,-1)), 作用在[[1, 2, 3, 4, 5, 6]]上,输出结果是:[[[ 1. 2.],[ 3. 4.],[ 5. 6.]]];
而Permute层则仅是转换了维度的位置,即顺序变了,但大小未变。例如:
Permute(dims=(2,1)), 作用在[[[1, 2, 3],[4, 5, 6]]]上,输出结果是:[[[ 1. 4.], [ 2. 5.], [ 3. 6.]]]。
请注意上述例子中输入数据的维度及顺序。对于Permute,它的输入数据的shape是(1,2,3), 而经Permute置换后的output shape是(1,3,2),通过这个例子,我们可能很好地解释参数dims=(2,1):它是一个模式,我们把它写成:dims[output_index-1]=input_index, 这里output_index从1开始,所以我们得到这样一个对应关系:output_index = 1,input_index = 2;output_index = 2,input_index = 1;因此,input shape是(1,2,3),对应的output shape是(1,3,2),其中samples维保持不变。
九、Flatten层
Flatten层用来将输入“展平”,即把多维输入一维化,常用于卷积层到全连接层的过渡。Flatten不影响批大小(batch size)。
十、RepeatVector层
这一层很简单,它将2D输入重复指定的次数。如果输入shape是(batch_size, features,则输出将是(batch_size, n, features)。
十一、Lambda层
Lambda层可将任意的表达式封装成 Layer 对象。该层非常适合于那种只想对流经该层的数据做变换,而不需要进行参数学习的情形。关于如何使用该层,Keras文档中有两个很好的例子可参阅。
class Lambda(Layer):
在该层的初始化参数中,function参数是不可或缺的,其定义形式象这样的:function(inputs, **arguments),它接受输入tensor(s)作为它的第1个参数。
十二、Dense全连接层
该层实现以下操作:output = activation(dot(input, kernel) + bias),这也是我们在经典的神经网络中非常熟知的在神经元节点中执行的计算公式。这里,activation是按元素计算的激活函数,kernel是由网络层创建的权值矩阵,bias是由网络层创建的偏置向量。
十三、ActivityRegularization层
我们对英文文档的翻译就是:对基于代价函数的输入活动应用一个更新。这种解释让人一头雾水。我们的解释一点点深入:
(1)首先是初始化:它通过参数传入的两个正则因子l1和l2,初始化一个activity_regularizer=regularizers.L1L2(l1=l1, l2=l2),它到底有什么用?
(2)接下来是我们的分析。一般情况下,我们对loss的计算包括两个部分:
a. 由数据本身计算得的loss,记作:DataLoss
b. 由数据的分部而增加的扰动,即正则项,记为: RegularizationLoss
因此,loss = DataLoss + RegularizationLoss
(3)RegularizationLoss双分为两种:
a. 基于权重的正则损失,对于这种情况,RegularizationLoss = f(Weights)
b. 基于输入活动的正则损失,对于这种情况,RegularizationLoss = f(outputs)
(4)在我们非常清楚输入数据集的分布的情况下,我们将应用基于输入活动的正则损失。就是这里的情形。
Keras在它的keras.layers.core.py模块中,定义了一系列常用的层,包括Dense全连接层、Activation激活层、Flatten展平层等等,它们都继承自基类 Layer,下面将对它们一一阅读并解析。
一、Masking层
它的目的用来跳过某些时间步。这个层的设置和使用有些让人费解,所以是网上关于层发疑问最多的:其中如何设置mask?它是如何工作的?是最核心的两个问题,这正是我们要重点分析的。
初始化Masking对象。我们的目的是要屏蔽Input Tensor中的某些特殊的时间步,即是要对所有列都含有某个特定值的某些时间步进行屏蔽,如[1., 1., 1., 1., 1.],但不是对任意一种模式的时间步都可以屏蔽的。我们要把这样的一个值传递给Masking对象的__init__方法,Masking对象的__init__方法中接收这个值的参数就是mask_value。
mask_value的缺省值是 0., 所以如果你什么参数值都没传的话,那么就是要屏蔽Input Tensor中那些全为 0 的行。此外,要想让这个mask起作用的话,还必须设置self.supports_masking这个标志为True。
class Masking(Layer):
def __init__(self, mask_value=0., **kwargs):
super(Masking, self).__init__(**kwargs)
self.supports_masking = True
self.mask_value = mask_value下列call函数让我们知道masking是如何工作的。其中有个技巧,就是运用逻辑运算“与”和“或”。首先,用“不等”(K.not_equal)对Input Tensor中的每个特征值与mask_value进行比较;然后用“任意一个”(K.any)逐时间步进行检查,检查结果是:除了所有特征值都是与mask_value相等的时间步,它的结果值为False外,其它的时间步的结果值都是True;再就是用K.cast把逻辑的False和True它们转换为浮点值的0.或1.;最后,把inputs与该转换过后的结果相乘。经过这样一些列的操作后,把要屏蔽的行的值都变成了0.,从而在后续的运算中,屏蔽了该时间步的作用。
def call(self, inputs):
boolean_mask = K.any(K.not_equal(inputs, self.mask_value),
axis=-1, keepdims=True)
return inputs * K.cast(boolean_mask, K.dtype(inputs))
计算output shape,它与input shape一致。
def compute_output_shape(self, input_shape):
return input_shape二、Dropout层
该层的作用是要在网络的训练阶段按一定的比率(rate)随机地(在Tensorflow中是用一致分布)丢弃(屏蔽)部分输入单元,目的是为了防止过配。
它的初始化参数中有两个是可想而知的:
(1)rate:取[0,1]之间的一个浮点娄,设置丢弃(屏蔽)输入单元的比例。
(2)seed: Python整数用作随机数种子。
而另外一个参数noise_shape,从字面看倒是很直接,但理解起来却不那么容易,它是用来指定要对哪些维做dropout。举个例子:设input shape是(batch_size, timesteps, features),我们要对timesteps维做dropout,那么我们可能这样设置noise_shape:noise_shape=(batch_size, 1, features)。这就是说,我们把noise_shape与input_shape逐维进行比较,如果不同且noise_shape将该维置为1的,即是要对该维做dropout。
参考:Dropout: A Simple Way to Prevent Neural Networks from Overfitting
class Dropout(Layer):
@interfaces.legacy_dropout_support
def __init__(self, rate, noise_shape=None, seed=None, **kwargs):
super(Dropout, self).__init__(**kwargs)
self.rate = min(1., max(0., rate))
self.noise_shape = noise_shape
self.seed = seed
self.supports_masking = True
def _get_noise_shape(self, inputs):
if self.noise_shape is None:
return self.noise_shape
symbolic_shape = K.shape(inputs)
noise_shape = [symbolic_shape[axis] if shape is None else shape
for axis, shape in enumerate(self.noise_shape)]
return tuple(noise_shape)
实际dropout是由后台支持库做的,见K.dropout与K.in_train_phase。
def call(self, inputs, training=None):
if 0. < self.rate < 1.:
noise_shape = self._get_noise_shape(inputs)
def dropped_inputs():
return K.dropout(inputs, self.rate, noise_shape,
seed=self.seed)
return K.in_train_phase(dropped_inputs, inputs,
training=training)
return inputs
def compute_output_shape(self, input_shape):
return input_shape三、SpatialDropout1D层
该层是Dropout层的运用于1D的特殊情况,对于这种case, 输入tensor的shape应该是(samples, timesteps, channels),dropout将作用于timesteps维,所以noise_shape应该是(samples, 1, channels)。
参考:Efficient Object Localization Using Convolutional Networks
class SpatialDropout1D(Dropout):
输入张量的维度应该是3,所以传入参数ndim=3给InputSpec
@interfaces.legacy_spatialdropout1d_support
def __init__(self, rate, **kwargs):
super(SpatialDropout1D, self).__init__(rate, **kwargs)
self.input_spec = InputSpec(ndim=3)
计算noise_shape,timesteps维为1,其它维跟输入tensor相同
def _get_noise_shape(self, inputs):
input_shape = K.shape(inputs)
noise_shape = (input_shape[0], 1, input_shape[2])
return noise_shape四、SpatialDropout2D层
该层是Dropout层的运用于2D图像的情况,对于这种case, 依据图像格式的不同,即channels在图像数据中位置的不同(channels_last或channels_first), 输入tensor的shape应该是(samples, rows, cols, channels)抑或是(samples, channels, rows, cols),不管是哪一种,dropout将作用于rows维和cols维,所以相应地noise_shape应该是(samples, 1, 1, channels)抑或是(samples, channels, 1, 1)。
参考:Efficient Object Localization Using Convolutional Networks
class SpatialDropout2D(Dropout):参数data_format用来指定图像格式,即channels_last或channels_first,如果参数没有指定的话,默认将取自你在 Keras 的配置文件 ~/.keras/keras.json 中设置的 image_data_format 的值,如果你从未设置过它,那么它将是 channels_last
@interfaces.legacy_spatialdropoutNd_support
def __init__(self, rate, data_format=None, **kwargs):
super(SpatialDropout2D, self).__init__(rate, **kwargs)
self.data_format = K.normalize_data_format(data_format)
self.input_spec = InputSpec(ndim=4)
def _get_noise_shape(self, inputs):
input_shape = K.shape(inputs)
if self.data_format == 'channels_first':
noise_shape = (input_shape[0], input_shape[1], 1, 1)
else:
noise_shape = (input_shape[0], 1, 1, input_shape[3])
return noise_shape五、SpatialDropout3D层
理解了前面的1D和2D,SpatialDropout3Db也就很容易理解了。对于这种case, 依据图像格式的不同,即channels在图像数据中位置的不同(channels_last或channels_first), 输入tensor的shape应该是(samples, dim1, dim2, dim3, channels)抑或是(samples, channels, dim1, dim2, dim3),不管是哪一种data_format,dropout将作用于dim1维、dim2维和dim3维,所以相应地noise_shape应该是(samples, 1, 1, 1, channels)抑或是(samples, channels, 1, 1, 1)。
参考:Efficient Object Localization Using Convolutional Networks
class SpatialDropout3D(Dropout):
@interfaces.legacy_spatialdropoutNd_support
def __init__(self, rate, data_format=None, **kwargs):
super(SpatialDropout3D, self).__init__(rate, **kwargs)
self.data_format = K.normalize_data_format(data_format)
self.input_spec = InputSpec(ndim=5)
def _get_noise_shape(self, inputs):
input_shape = K.shape(inputs)
if self.data_format == 'channels_first':
noise_shape = (input_shape[0], input_shape[1], 1, 1, 1)
else:
noise_shape = (input_shape[0], 1, 1, 1, input_shape[4])
return noise_shape六、Activation激活层
这个层非常简单,闵是将激活函数应用于输入。该激活函数由初始化参数 activation 指定。
class Activation(Layer):
def __init__(self, activation, **kwargs):
super(Activation, self).__init__(**kwargs)
self.supports_masking = True
self.activation = activations.get(activation)
def call(self, inputs):
return self.activation(inputs)
def get_config(self):
config = {'activation': activations.serialize(self.activation)}
base_config = super(Activation, self).get_config()
return dict(list(base_config.items()) + list(config.items()))
def compute_output_shape(self, input_shape):
return input_shape七、Reshape层
对输入的尺寸重新调整。如果使用此层作为模型中的第一层,则需要使用参数 input_shape (其中不包括样本数samples的维)。在target_shape中的某一维可以使用 “-1”, 表示维度推断。
class Reshape(Layer):
def __init__(self, target_shape, **kwargs):
super(Reshape, self).__init__(**kwargs)
self.target_shape = tuple(target_shape)此内部函数将处理维度推断,变量unknown用来保存待推断维度的轴索引,变量known用来统计target_shape已知的维数,所以我们用所有input_shape中维数除以known, 就可推断待定的维数。
def _fix_unknown_dimension(self, input_shape, output_shape):
output_shape = list(output_shape)
msg = 'total size of new array must be unchanged'
known, unknown = 1, None
for index, dim in enumerate(output_shape):
if dim < 0:
if unknown is None:
unknown = index
else:
raise ValueError('Can only specify one unknown dimension.')
else:
known *= dim
original = np.prod(input_shape, dtype=int)
if unknown is not None:
if known == 0 or original % known != 0:
raise ValueError(msg)
output_shape[unknown] = original // known
elif original != known:
raise ValueError(msg)
return tuple(output_shape)
计算输出shape
def compute_output_shape(self, input_shape):
if None in input_shape[1:]:
# input shape (partially) unknown? replace -1's with None's
return ((input_shape[0],) +
tuple(s if s != -1 else None for s in self.target_shape))
else:
# input shape known? then we can compute the output shape
return (input_shape[0],) + self._fix_unknown_dimension(
input_shape[1:], self.target_shape)
执行维度尺寸调整
def call(self, inputs):
return K.reshape(inputs, (K.shape(inputs)[0],) + self.target_shape)八、Permute层
按照给定的模式重新排列输入维度的位置。它与前面讨论的Reshape层不同:
Reshape层改变输入数据的形状,但没改变输入数据的顺序,例如:
Reshape(dims=(2,-1)),作用在[[1, 2, 3, 4, 5, 6]]上,输出结果是:[[[ 1. 2. 3.], [ 4. 5. 6.]]],
Reshape(dims=(3,-1)), 作用在[[1, 2, 3, 4, 5, 6]]上,输出结果是:[[[ 1. 2.],[ 3. 4.],[ 5. 6.]]];
而Permute层则仅是转换了维度的位置,即顺序变了,但大小未变。例如:
Permute(dims=(2,1)), 作用在[[[1, 2, 3],[4, 5, 6]]]上,输出结果是:[[[ 1. 4.], [ 2. 5.], [ 3. 6.]]]。
请注意上述例子中输入数据的维度及顺序。对于Permute,它的输入数据的shape是(1,2,3), 而经Permute置换后的output shape是(1,3,2),通过这个例子,我们可能很好地解释参数dims=(2,1):它是一个模式,我们把它写成:dims[output_index-1]=input_index, 这里output_index从1开始,所以我们得到这样一个对应关系:output_index = 1,input_index = 2;output_index = 2,input_index = 1;因此,input shape是(1,2,3),对应的output shape是(1,3,2),其中samples维保持不变。
class Permute(Layer):
因为dims中不包括samples维,所以ndim=len(dims) + 1
def __init__(self, dims, **kwargs):
super(Permute, self).__init__(**kwargs)
self.dims = tuple(dims)
self.input_spec = InputSpec(ndim=len(self.dims) + 1)首先复制input_shape到output_shape,然后根据dims模式中指定的output_index和input_index,令output_shape[output_index] = input_shape[input_index]即可def compute_output_shape(self, input_shape):
input_shape = list(input_shape)
output_shape = copy.copy(input_shape)
for i, dim in enumerate(self.dims):
target_dim = input_shape[dim]
output_shape[i + 1] = target_dim
return tuple(output_shape)
实施置换
def call(self, inputs):
return K.permute_dimensions(inputs, (0,) + self.dims)九、Flatten层
Flatten层用来将输入“展平”,即把多维输入一维化,常用于卷积层到全连接层的过渡。Flatten不影响批大小(batch size)。
class Flatten(Layer):
既然是要展开,当然输入的最小维度不低于3,即min_ndim=3
def __init__(self, data_format=None, **kwargs):
super(Flatten, self).__init__(**kwargs)
self.input_spec = InputSpec(min_ndim=3)
self.data_format = K.normalize_data_format(data_format)
展平后的维度大小计算,它应该是第0维batch大小不变,而第1维应该是input_shape中除了batch维之外的其它所有维大小的乘积
def compute_output_shape(self, input_shape):
if not all(input_shape[1:]):
raise ValueError('The shape of the input to "Flatten" '
'is not fully defined '
'(got ' + str(input_shape[1:]) + '. '
'Make sure to pass a complete "input_shape" '
'or "batch_input_shape" argument to the first '
'layer in your model.')
return (input_shape[0], np.prod(input_shape[1:]))
在展平之前,首先要老虑到channels的维的位置,对于channels_first情形,要用维度置换方法,变成channels_last,然后进行展平操作
def call(self, inputs):
if self.data_format == 'channels_first':
# Ensure works for any dim
permutation = [0]
permutation.extend([i for i in
range(2, K.ndim(inputs))])
permutation.append(1)
inputs = K.permute_dimensions(inputs, permutation)
return K.batch_flatten(inputs)十、RepeatVector层
这一层很简单,它将2D输入重复指定的次数。如果输入shape是(batch_size, features,则输出将是(batch_size, n, features)。
class RepeatVector(Layer):
指定重复次数n
def __init__(self, n, **kwargs):
super(RepeatVector, self).__init__(**kwargs)
self.n = n
self.input_spec = InputSpec(ndim=2)
计算output shape
def compute_output_shape(self, input_shape):
return (input_shape[0], self.n, input_shape[1])
重复操作
def call(self, inputs):
return K.repeat(inputs, self.n)十一、Lambda层
Lambda层可将任意的表达式封装成 Layer 对象。该层非常适合于那种只想对流经该层的数据做变换,而不需要进行参数学习的情形。关于如何使用该层,Keras文档中有两个很好的例子可参阅。
class Lambda(Layer):
在该层的初始化参数中,function参数是不可或缺的,其定义形式象这样的:function(inputs, **arguments),它接受输入tensor(s)作为它的第1个参数。
@interfaces.legacy_lambda_support
def __init__(self, function, output_shape=None,
mask=None, arguments=None, **kwargs):
super(Lambda, self).__init__(**kwargs)
self.function = function
self.arguments = arguments if arguments else {}
如果有mask,则须置supports_masking标志
if mask is not None:
self.supports_masking = True
self.mask = mask
处理output_shape参数,有3种情形:
(1)可以是None
if output_shape is None:
self._output_shape = None
(2)可以是一个tuple或list
elif isinstance(output_shape, (tuple, list)):
self._output_shape = tuple(output_shape)
(3)可以是一个函数,
else:
if not callable(output_shape):
raise TypeError('In Lambda, `output_shape` '
'must be a list, a tuple, or a function.')
self._output_shape = output_shape
计算output shape,对应于output_shape的初始化3种情形
def compute_output_shape(self, input_shape):
(1)由tensorflow或CNTK从inputs推断
if self._output_shape is None:
# With TensorFlow or CNTK, we can infer the output shape directly:
if K.backend() in ('tensorflow', 'cntk'):
if isinstance(input_shape, list):
xs = [K.placeholder(shape=shape) for shape in input_shape]
x = self.call(xs)
else:
x = K.placeholder(shape=input_shape)
x = self.call(x)
if isinstance(x, list):
return [K.int_shape(x_elem) for x_elem in x]
else:
return K.int_shape(x)
# Otherwise, we default to the input shape.
warnings.warn('`output_shape` argument not specified for layer {} '
'and cannot be automatically inferred '
'with the Theano backend. '
'Defaulting to output shape `{}` '
'(same as input shape). '
'If the expected output shape is different, '
'specify it via the `output_shape` argument.'
.format(self.name, input_shape))
return input_shape
(2)对于tuple或list,因为其中不包括样本大小维(或batch_size),所以须将input_shape中batch_size放丰output shape的最前
elif isinstance(self._output_shape, (tuple, list)):
if isinstance(input_shape, list):
num_samples = input_shape[0][0]
else:
num_samples = input_shape[0] if input_shape else None
return (num_samples,) + tuple(self._output_shape)
(3)将_output_shape函数作用于input_shape获取output shape
else:
shape = self._output_shape(input_shape)
if not isinstance(shape, (list, tuple)):
raise ValueError('`output_shape` function must return a tuple or '
'a list of tuples.')
if isinstance(shape, list):
if isinstance(shape[0], int) or shape[0] is None:
shape = tuple(shape)
return shape
进行函数调用
def call(self, inputs, mask=None):
arguments = self.arguments
if has_arg(self.function, 'mask'):
arguments['mask'] = mask
return self.function(inputs, **arguments)十二、Dense全连接层
该层实现以下操作:output = activation(dot(input, kernel) + bias),这也是我们在经典的神经网络中非常熟知的在神经元节点中执行的计算公式。这里,activation是按元素计算的激活函数,kernel是由网络层创建的权值矩阵,bias是由网络层创建的偏置向量。
class Dense(Layer):
这里的这些初始化参数名称作为神经网络的名词,也是我们在经典神经网络中非常熟悉的,后面会有专门的解读。
@interfaces.legacy_dense_support
def __init__(self, units,
activation=None,
use_bias=True,
kernel_initializer='glorot_uniform',
bias_initializer='zeros',
kernel_regularizer=None,
bias_regularizer=None,
activity_regularizer=None,
kernel_constraint=None,
bias_constraint=None,
**kwargs):
if 'input_shape' not in kwargs and 'input_dim' in kwargs:
kwargs['input_shape'] = (kwargs.pop('input_dim'),)
super(Dense, self).__init__(**kwargs)
self.units = units
self.activation = activations.get(activation)
self.use_bias = use_bias
self.kernel_initializer = initializers.get(kernel_initializer)
self.bias_initializer = initializers.get(bias_initializer)
self.kernel_regularizer = regularizers.get(kernel_regularizer)
self.bias_regularizer = regularizers.get(bias_regularizer)
self.activity_regularizer = regularizers.get(activity_regularizer)
self.kernel_constraint = constraints.get(kernel_constraint)
self.bias_constraint = constraints.get(bias_constraint)
self.input_spec = InputSpec(min_ndim=2)
self.supports_masking = True
根据input_shape,创建该层的权重kernel和bias
def build(self, input_shape):
assert len(input_shape) >= 2
input_dim = input_shape[-1]
self.kernel = self.add_weight(shape=(input_dim, self.units),
initializer=self.kernel_initializer,
name='kernel',
regularizer=self.kernel_regularizer,
constraint=self.kernel_constraint)
只在 use_bias 为 True 时才会创建bias
if self.use_bias:
self.bias = self.add_weight(shape=(self.units,),
initializer=self.bias_initializer,
name='bias',
regularizer=self.bias_regularizer,
constraint=self.bias_constraint)
else:
self.bias = None
self.input_spec = InputSpec(min_ndim=2, axes={-1: input_dim})
self.built = True
实现操作:output = activation(dot(input, kernel) + bias)
def call(self, inputs):
output = K.dot(inputs, self.kernel)
if self.use_bias:
output = K.bias_add(output, self.bias, data_format='channels_last')
if self.activation is not None:
output = self.activation(output)
return output
依据input_shape中的batch_size和参数units计算output shape
def compute_output_shape(self, input_shape):
assert input_shape and len(input_shape) >= 2
assert input_shape[-1]
output_shape = list(input_shape)
output_shape[-1] = self.units
return tuple(output_shape)十三、ActivityRegularization层
我们对英文文档的翻译就是:对基于代价函数的输入活动应用一个更新。这种解释让人一头雾水。我们的解释一点点深入:
(1)首先是初始化:它通过参数传入的两个正则因子l1和l2,初始化一个activity_regularizer=regularizers.L1L2(l1=l1, l2=l2),它到底有什么用?
(2)接下来是我们的分析。一般情况下,我们对loss的计算包括两个部分:
a. 由数据本身计算得的loss,记作:DataLoss
b. 由数据的分部而增加的扰动,即正则项,记为: RegularizationLoss
因此,loss = DataLoss + RegularizationLoss
(3)RegularizationLoss双分为两种:
a. 基于权重的正则损失,对于这种情况,RegularizationLoss = f(Weights)
b. 基于输入活动的正则损失,对于这种情况,RegularizationLoss = f(outputs)
(4)在我们非常清楚输入数据集的分布的情况下,我们将应用基于输入活动的正则损失。就是这里的情形。
class ActivityRegularization(Layer):
def __init__(self, l1=0., l2=0., **kwargs):
super(ActivityRegularization, self).__init__(**kwargs)
self.supports_masking = True
self.l1 = l1
self.l2 = l2
self.activity_regularizer = regularizers.L1L2(l1=l1, l2=l2)
def compute_output_shape(self, input_shape):
return input_shape
2018年12月21日星期五
Keras源码分析(7):Layer
文件:/keras/engine/base_layer.py
在前面第5节我们已经接触到一个简单而特别的层:InputLayer,它继承自基类:Layer。尽管简单,但关于层的基本逻辑倒也清楚明白。
在Node一节中,我们也说了许多与Node相关的Layer的内容。接下来我们仍需对这个基类Layer作进一步讨论。
这是Layer的主要处理逻辑所在,给定输入,产生输出。因为各种layer作用的不同,所以在些基类中无法做具体实现,它们的具体功能留待各种子类去实现。
这个函数对call进行了进一步的包装。下面的分析中,我们仅保留了代码的主要部分,而省略了一些次要的代码,目的是让它的逻辑显得更加清晰。
根据self.built标志确定是否需要build。如果需要,则首先要从inputs中获取input_shapes,这是build函数所唯一需要的,然后,把收集到的input_shapes传给build,执行build操作。build的作用在下面有解释,在此基类中build实际是个空操作。
此处调用call,实现本层的主要逻辑,获得output。在此基类中call函数仅仅是将inputs原样奉还。
遍历input_tensors,从每个input_tensor._keras_history中得到input_layer,node_index和tensor_index,把它们分别放进inbound_layers,node_indices和tensor_indices中。
从前面Node一节我们已经知道,这个新的Node对象将把自己加到当前layer的_inbound_nodes列表中,同时也加到所有inbound_layer的_outbound_nodes列表中。
对于上面的代码,我们把解释的重点放在最后一句上,因为它让我们清楚地知道,在output tensor的_keras_history中到底放进了什么。
(1)self:输出该第i个tensor的layer对象
(2)len(self._inbound_nodes) - 1:与第i个tensor相关的node对象在当前layer._inbound_nodes中的位置,即node_index
(3)i: 当然是第i个tensor在output_tensors中的位置,即:tensor_index
根据input_shape,创建该层的权重weights。不同的层可能有不同的创建方法,故留待子类去实现
在前面第5节我们已经接触到一个简单而特别的层:InputLayer,它继承自基类:Layer。尽管简单,但关于层的基本逻辑倒也清楚明白。
在Node一节中,我们也说了许多与Node相关的Layer的内容。接下来我们仍需对这个基类Layer作进一步讨论。
class Layer(object):这是Layer的主要处理逻辑所在,给定输入,产生输出。因为各种layer作用的不同,所以在些基类中无法做具体实现,它们的具体功能留待各种子类去实现。
def call(self, inputs, **kwargs):
return inputs这个函数对call进行了进一步的包装。下面的分析中,我们仅保留了代码的主要部分,而省略了一些次要的代码,目的是让它的逻辑显得更加清晰。
def __call__(self, inputs, **kwargs):
根据self.built标志确定是否需要build。如果需要,则首先要从inputs中获取input_shapes,这是build函数所唯一需要的,然后,把收集到的input_shapes传给build,执行build操作。build的作用在下面有解释,在此基类中build实际是个空操作。
if not self.built:
input_shapes = []
for x_elem in to_list(inputs):
if hasattr(x_elem, '_keras_shape'):
input_shapes.append(x_elem._keras_shape)
elif hasattr(K, 'int_shape'):
input_shapes.append(K.int_shape(x_elem))
else:
raise ValueError('......')
self.build(unpack_singleton(input_shapes))此处调用call,实现本层的主要逻辑,获得output。在此基类中call函数仅仅是将inputs原样奉还。
output = self.call(inputs, **kwargs)
......
# 如果call未对inputs进行修改,为避免inputs元数据的丢失,需要对它进行复制
output_ls = to_list(output)
inputs_ls = to_list(inputs)
output_ls_copy = []
for x in output_ls:
if x in inputs_ls:
x = K.identity(x)
output_ls_copy.append(x)
output = unpack_singleton(output_ls_copy)
......
# 调用_add_inbound_node创建层间连接并保存历史
self._add_inbound_node(input_tensors=inputs,
output_tensors=output,
input_masks=previous_mask,
output_masks=output_mask,
input_shapes=input_shape,
output_shapes=output_shape,
arguments=user_kwargs)
......
#返回本层输出
return output
这是一个内部方法,用来创建输入方向的node
def _add_inbound_node(self, input_tensors, output_tensors,
input_masks, output_masks,
input_shapes, output_shapes, arguments=None):
input_tensors = to_list(input_tensors)
output_tensors = to_list(output_tensors)
input_masks = to_list(input_masks)
output_masks = to_list(output_masks)
input_shapes = to_list(input_shapes)
output_shapes = to_list(output_shapes)遍历input_tensors,从每个input_tensor._keras_history中得到input_layer,node_index和tensor_index,把它们分别放进inbound_layers,node_indices和tensor_indices中。
inbound_layers = []
node_indices = []
tensor_indices = []
for x in input_tensors:
if hasattr(x, '_keras_history'):
inbound_layer, node_index, tensor_index = x._keras_history
inbound_layers.append(inbound_layer)
node_indices.append(node_index)
tensor_indices.append(tensor_index)
else:
inbound_layers.append(None)
node_indices.append(None)
tensor_indices.append(None)从前面Node一节我们已经知道,这个新的Node对象将把自己加到当前layer的_inbound_nodes列表中,同时也加到所有inbound_layer的_outbound_nodes列表中。
Node(
self,
inbound_layers=inbound_layers,
node_indices=node_indices,
tensor_indices=tensor_indices,
input_tensors=input_tensors,
output_tensors=output_tensors,
input_masks=input_masks,
output_masks=output_masks,
input_shapes=input_shapes,
output_shapes=output_shapes,
arguments=arguments
)
更新输出output_tensors的history, _keras_shape和_uses_learning_phase。
for i in range(len(output_tensors)):
output_tensors[i]._keras_shape = output_shapes[i]
uses_lp = any(
[getattr(x, '_uses_learning_phase', False)
for x in input_tensors])
uses_lp = getattr(self, 'uses_learning_phase', False) or uses_lp
output_tensors[i]._uses_learning_phase = getattr(
output_tensors[i], '_uses_learning_phase', False) or uses_lp
output_tensors[i]._keras_history = (self, len(self._inbound_nodes) - 1, i)对于上面的代码,我们把解释的重点放在最后一句上,因为它让我们清楚地知道,在output tensor的_keras_history中到底放进了什么。
(1)self:输出该第i个tensor的layer对象
(2)len(self._inbound_nodes) - 1:与第i个tensor相关的node对象在当前layer._inbound_nodes中的位置,即node_index
(3)i: 当然是第i个tensor在output_tensors中的位置,即:tensor_index
根据input_shape,创建该层的权重weights。不同的层可能有不同的创建方法,故留待子类去实现
def build(self, input_shape):
self.built = True
获取layer与给定node相关的输出tensor(s)
def get_output_at(self, node_index):
return self._get_node_attribute_at_index(node_index,
'output_tensors',
'output')
获取在layer._inbound_nodes[node_index]位置的node中由attr指定属性的值values
def _get_node_attribute_at_index(self, node_index, attr, attr_name):
......
values = getattr(self._inbound_nodes[node_index], attr)
return unpack_singleton(values)
2018年12月20日星期四
Keras源码分析(6):Node
文件:/keras/engine/base_layer.py
在上一节,我们提到了Node,它的作用是用来联结两个层。这一节我们具体来看一看Node是如何联结两个层的。
对于Node这个命名,一开始会让人觉提很纳闷:为什么叫Node,难道它是一个神经元节点吗?它到底是什么,我们还是来看看源码吧。
下面是Node对象的关键处理:它把自己分别追加到outbound_layer._inbound_nodes和inbound_layers中的每个layer._outbound_nodes的列表中。这样做的目的,显然是建立起从输入层到输出层关联。现在你可以画一个流程图,左边是几个输入层,右边是一个输出层,中间再画一个圆圈并标上Node,用线把它与左右两边的层连起来,此时,你看这个Node像节点吗?
现在我们回头来解释上面的第(6)条:
首先说说Node对象是在什么时候产生的。每当我们要建立一个网络时,我们需要new一些层,当传入tensors给它们的时候,就会调用这些层的__call__方法,这些层的__call__方法每调用一次,就会产生一个新的Node对象,并把当前的layer作为outbound_layer传给该Node对象,接下来该Node对象把自己分别放到outbound_layer._inbound_nodes列表和每一个inbound_layers._outbound_nodes的列表中,这一点我们在上面已经看到了。对于单输入输出,这种映射关系很清楚,到此一切都OK,但是对于有多个输入源的层,则问题就复杂了点。例如:
像上面这种情况,一个layer的输入可能是多个tensor的列表,进而产生的输出也是多个输出tensor的列表,所以,由这样的层作为inbound_layer的时候,我们需要知道input_tensor在inbound_layer的输出tensor列表中的位置,即:tensor_index。
对于像这样一个layer对象的输入和输出可能是一个列表的情况,我们要想从output_tensors中得到某个输出,则必须指定它在这个输出列表中的索引(tensor_index)。
从例子中我们把思路拉回来,考虑当前layer(outbound_layer)的所有输入,把与它们相关的node_index和tensor_index收集起来,分别依次放进两个node_indices和tensor_indices中,然后传递给该新建的node,并保存在它相应的成员变量中。这样,结合tensor._keras_history,我们把输出和输入的关系也就建立起来了。
尽管只是简单几行代码,解释起来并不简单。总之,通过许多Node对象把层和层,输入和输出连成了一个有向图,让它们彼此可以相互追溯。现在你可以再画一个图,左边是输入,右边是输出,中间再画一个圆圈并标上Node,用线把它与左右两边连起来,现在你再看这个Node像节点吗?
在上一节,我们提到了Node,它的作用是用来联结两个层。这一节我们具体来看一看Node是如何联结两个层的。
对于Node这个命名,一开始会让人觉提很纳闷:为什么叫Node,难道它是一个神经元节点吗?它到底是什么,我们还是来看看源码吧。
class Node(object):
def __init__(self, outbound_layer,
inbound_layers, node_indices, tensor_indices,
input_tensors, output_tensors,
input_masks, output_masks,
input_shapes, output_shapes,
arguments=None):
从Node对象的成员变量,我们来看看Node对象中到底保存了什么:
(1)首先是输出层,也是该Node对象被new出来的那个层,该层可以看作是我们分析Node的立足点或参照点
self.outbound_layer = outbound_layer
(2)输入层列表,因为可能有多个输入,所以是个列表
self.inbound_layers = inbound_layers
(3)输入和输出张量列表,因为可能有多个输入输出,且它们一一对应
self.input_tensors = input_tensors
self.output_tensors = output_tensors
(4)输入和输出mask张量列表,它们也是和输入输出一一对应
self.input_masks = input_masks
self.output_masks = output_masks
(5)输入和输出张量shape列表,它们也是和输入输出一一对应
self.input_shapes = input_shapes
self.output_shapes = output_shapes
(6)要解释清楚下面的变量,需要多一些口舌,解释我们放在后面。
self.node_indices = node_indices
self.tensor_indices = tensor_indices这就是说,Node对象要保存的信息都是从外部传进来的,所以要想真正全面理解Node,我们还需要深入探寻创建和使用它的代码的上下文。下面是Node对象的关键处理:它把自己分别追加到outbound_layer._inbound_nodes和inbound_layers中的每个layer._outbound_nodes的列表中。这样做的目的,显然是建立起从输入层到输出层关联。现在你可以画一个流程图,左边是几个输入层,右边是一个输出层,中间再画一个圆圈并标上Node,用线把它与左右两边的层连起来,此时,你看这个Node像节点吗?
for layer in inbound_layers:
if layer is not None:
layer._outbound_nodes.append(self)
outbound_layer._inbound_nodes.append(self)
现在我们回头来解释上面的第(6)条:
首先说说Node对象是在什么时候产生的。每当我们要建立一个网络时,我们需要new一些层,当传入tensors给它们的时候,就会调用这些层的__call__方法,这些层的__call__方法每调用一次,就会产生一个新的Node对象,并把当前的layer作为outbound_layer传给该Node对象,接下来该Node对象把自己分别放到outbound_layer._inbound_nodes列表和每一个inbound_layers._outbound_nodes的列表中,这一点我们在上面已经看到了。对于单输入输出,这种映射关系很清楚,到此一切都OK,但是对于有多个输入源的层,则问题就复杂了点。例如:
a = Input(shape=(280, 256)) b = Input(shape=(280, 256)) lstm = LSTM(32) encoded_a = lstm(a) encoded_b = lstm(b)当第一次调用lstm(a)的时候,它创建了一个node,并把这个node放到lstm的_inbound_nodes的第0个位置,第二次调用lstm(b)的时候,它又创建了一个node,并把这个node放到lstm的_inbound_nodes的第1个位置。当我们要想获取它们的输出时,我们可以这样做:
encoded_a = lstm.get_output_at(0)
encoded_a = lstm.get_output_at(1)这就是说,我们想要得到lstm的某个输出,不是简单地用lstm.output,而是用lstm.get_output_at(index)。像上面这种情况,一个layer的输入可能是多个tensor的列表,进而产生的输出也是多个输出tensor的列表,所以,由这样的层作为inbound_layer的时候,我们需要知道input_tensor在inbound_layer的输出tensor列表中的位置,即:tensor_index。
对于像这样一个layer对象的输入和输出可能是一个列表的情况,我们要想从output_tensors中得到某个输出,则必须指定它在这个输出列表中的索引(tensor_index)。
从例子中我们把思路拉回来,考虑当前layer(outbound_layer)的所有输入,把与它们相关的node_index和tensor_index收集起来,分别依次放进两个node_indices和tensor_indices中,然后传递给该新建的node,并保存在它相应的成员变量中。这样,结合tensor._keras_history,我们把输出和输入的关系也就建立起来了。
尽管只是简单几行代码,解释起来并不简单。总之,通过许多Node对象把层和层,输入和输出连成了一个有向图,让它们彼此可以相互追溯。现在你可以再画一个图,左边是输入,右边是输出,中间再画一个圆圈并标上Node,用线把它与左右两边连起来,现在你再看这个Node像节点吗?
2018年12月16日星期日
Keras源码分析(5):Tensor、Input 和 InputLayer
文件:/keras/engine/input_layer.py
Tensor,翻译过来就是张量。
TensorFlow支持两种类型的Tensor,一种是立即执行的(eager execution),一种是图执行的(graph execution)。图执行的Tensor一般只是一个空壳(placeholder),没有值(tensor_content),它只定义了维度(shape)和数据类型(dtype),当然还有部分定义好的方法,待未来填值计算。TensorFlow将那些待计算的tensor组织成有向图(graph),后面tensor的计算依赖它前面的tensor的计算结果,因此,只要位于图入口处的那些tensor填入了值,就可执行图计算,就能输出结果。
Keras是建立在Tensorflow,CNTK或Theano之上的是一个高级神经网络库。Keras中的Tensor对底层TensorFlow或Theano的张量进行了扩展,加入了如下两个属性:
_Keras_history: 保存最近作用于这个tensor上的Layer对象及有关元数据。
_keras_shape: 保存(batch_size, input_dim,)与输入数据有关的维度大小的元组
张量是深度学习框架中的一个非常核心的概念,模型(内部)的计算都是基于张量进行的。
例子:
下面来看Input是如何定义的:
unpack_singleton(outputs):如果outputs只有一个元素,则返回outputs[0],否则返回outputs。
下面我们来看InputLayer的源码:
接下来是一个逻辑处理和理解上的一个关键部分:new一个输入节点(Node)对象。Node对象的作用是用来联结两个层,我们将另辟章节对它进行分析,在这里我们只粗略地讨论一下传给它的参数。
Node的参数有4个主要部分:
(1)layers,包括outbound_layer和inbound_layers,这里outbound_layer接受的是这个InputLayer对象本身(self),inbound_layers=[],因为InputLayer是第一个输入层,所以它的inbound_layers是空。
(2)tensors,包括输入张量和输出张量,它们都等于[input_tensor],即:input_tensors=[input_tensor],output_tensors=[input_tensor],所以说这一层其实什么也没干。
(3)shapes,很自然地,输入和输出的维度参数(包括batch_size)都是[batch_input_shape],即:input_shapes=[batch_input_shape], output_shapes=[batch_input_shape]
(4)indices,这是一个很难理解的东东,需要在后面展开来讨论。 这里且注意这样一个细节就是output tensor(这里是input_tensor)的_keras_history,它的值的形式是(layer, node_index, tensor_index)这样的3元组,这里赋的值是(self, 0, 0)。
Tensor,翻译过来就是张量。
TensorFlow支持两种类型的Tensor,一种是立即执行的(eager execution),一种是图执行的(graph execution)。图执行的Tensor一般只是一个空壳(placeholder),没有值(tensor_content),它只定义了维度(shape)和数据类型(dtype),当然还有部分定义好的方法,待未来填值计算。TensorFlow将那些待计算的tensor组织成有向图(graph),后面tensor的计算依赖它前面的tensor的计算结果,因此,只要位于图入口处的那些tensor填入了值,就可执行图计算,就能输出结果。
Keras是建立在Tensorflow,CNTK或Theano之上的是一个高级神经网络库。Keras中的Tensor对底层TensorFlow或Theano的张量进行了扩展,加入了如下两个属性:
_Keras_history: 保存最近作用于这个tensor上的Layer对象及有关元数据。
_keras_shape: 保存(batch_size, input_dim,)与输入数据有关的维度大小的元组
张量是深度学习框架中的一个非常核心的概念,模型(内部)的计算都是基于张量进行的。
例子:
from keras.layers import Input, Dense
from keras.models import Model
inputs = Input(shape=(784,))
x = Dense(64, activation='relu')(inputs)
x = Dense(64, activation='relu')(x)
predictions = Dense(10, activation='softmax')(x)
model = Model(inputs=inputs, outputs=predictions)
model.compile(optimizer='rmsprop',
loss='categorical_crossentropy',
metrics=['accuracy'])
model.fit(data, labels)
上述的inputs和predictions都是tensor。显然,这里的tensor是一个图执行的tensor,由model.fit填入数据,然后执行计算。下面来看Input是如何定义的:
def Input(shape=None, batch_shape=None,
name=None, dtype=None, sparse=False,
tensor=None):
......
if shape is not None and not batch_shape:
batch_shape = (None,) + tuple(shape)
if not dtype:
dtype = K.floatx()
input_layer = InputLayer(batch_input_shape=batch_shape,
name=name, dtype=dtype,
sparse=sparse,
input_tensor=tensor)
outputs = input_layer._inbound_nodes[0].output_tensors
return unpack_singleton(outputs)
代码中引入了一个InputLayer的对象,它是整个网络的起始输入层,它实际做的只是对原始数据的原样输出。从这里可知这个Input实际上在内部定义了一个输入层,并把该输入层的输出作为自己的输出返回,
也即返回这里的input_tensor,如果它不是None;否则返回下面代码中的K.placeholder对象。unpack_singleton(outputs):如果outputs只有一个元素,则返回outputs[0],否则返回outputs。
下面我们来看InputLayer的源码:
class InputLayer(Layer):
@interfaces.legacy_input_support
def __init__(self, input_shape=None, batch_size=None,
batch_input_shape=None,
dtype=None, input_tensor=None, sparse=False, name=None):
......
self.trainable = False
self.built = True
self.sparse = sparse
self.supports_masking = True
......
self.batch_input_shape = batch_input_shape
self.dtype = dtype
if input_tensor is None:
self.is_placeholder = True
input_tensor = K.placeholder(shape=batch_input_shape,
dtype=dtype,
sparse=self.sparse,
name=self.name)
else:
self.is_placeholder = False
input_tensor._keras_shape = batch_input_shape
input_tensor._uses_learning_phase = False
input_tensor._keras_history = (self, 0, 0)
Node(self,
inbound_layers=[],
node_indices=[],
tensor_indices=[],
input_tensors=[input_tensor],
output_tensors=[input_tensor],
input_masks=[None],
output_masks=[None],
input_shapes=[batch_input_shape],
output_shapes=[batch_input_shape])
其中省略了一些对函数参数处理的代码。
对于NN中的每一层,不外乎三个部分:输入、处理(data的正向传播和error的反向传播)和输出。
在InputLayer的这段init的代码中,首先是对输入input_tensor的处理,如果是None,
则产生一个placeholder作为input tensor。从这里可看到所谓的tensor的占位符的特性。接下来是一个逻辑处理和理解上的一个关键部分:new一个输入节点(Node)对象。Node对象的作用是用来联结两个层,我们将另辟章节对它进行分析,在这里我们只粗略地讨论一下传给它的参数。
Node的参数有4个主要部分:
(1)layers,包括outbound_layer和inbound_layers,这里outbound_layer接受的是这个InputLayer对象本身(self),inbound_layers=[],因为InputLayer是第一个输入层,所以它的inbound_layers是空。
(2)tensors,包括输入张量和输出张量,它们都等于[input_tensor],即:input_tensors=[input_tensor],output_tensors=[input_tensor],所以说这一层其实什么也没干。
(3)shapes,很自然地,输入和输出的维度参数(包括batch_size)都是[batch_input_shape],即:input_shapes=[batch_input_shape], output_shapes=[batch_input_shape]
(4)indices,这是一个很难理解的东东,需要在后面展开来讨论。 这里且注意这样一个细节就是output tensor(这里是input_tensor)的_keras_history,它的值的形式是(layer, node_index, tensor_index)这样的3元组,这里赋的值是(self, 0, 0)。
2018年12月14日星期五
Keras源码分析(4):数据获取
文件:keras/datasets/mnist.py
这个源码可以看作是前篇utils.data_utils.get_file的一个使用案例。
在你的home目录下,你可以找到这个下载的数据集文件,具体路径是:~/.keras/datasets/mnist.npz,它是一个zip压缩文件,打开它你会发现它包含4个numpy数组序列化存储文件:x_train.npy, y_train.npy,x_test.npy, y_test.npy, 这也是为什么np.load返回包含相应4个键值的原因。
MNIST是一个包含60,000个训练图像和10,000测试图像的手写数字的数据集,图像的维度是28x28。下图是取自测试集中的样例:

这个源码可以看作是前篇utils.data_utils.get_file的一个使用案例。
def load_data(path='mnist.npz'):
path = get_file(path,
origin='https://s3.amazonaws.com/img-datasets/mnist.npz',
file_hash='8a61469f7ea1b51cbae51d4f78837e45')
f = np.load(path)
x_train, y_train = f['x_train'], f['y_train']
x_test, y_test = f['x_test'], f['y_test']
f.close()
return (x_train, y_train), (x_test, y_test)
在这个load_data函数中,它简单直接地hard code了数据集文件的URL和文件hash校验值作为utils.data_utils.get_file的参数,进行数据集下载,并得到下载后缓存于本地的完整的文件路径path, 然后利用numpy.load载入数据到内存并返回一个dict,其中包含四项:x_train, y_train,x_test, y_test,它们对应的值是numpy数组。在你的home目录下,你可以找到这个下载的数据集文件,具体路径是:~/.keras/datasets/mnist.npz,它是一个zip压缩文件,打开它你会发现它包含4个numpy数组序列化存储文件:x_train.npy, y_train.npy,x_test.npy, y_test.npy, 这也是为什么np.load返回包含相应4个键值的原因。
MNIST是一个包含60,000个训练图像和10,000测试图像的手写数字的数据集,图像的维度是28x28。下图是取自测试集中的样例:
2018年12月13日星期四
Keras源码分析(3):数据集下载
文件:/keras/utils/data_utils.py
在examples目录下的许多例子都涉及到数据集下载,查看源码你会发现它们最终都是通过keras.utils.data_utils.get_file函数下载的,这是一个普适的数据集下载工具函数,所以有必要了解其功能,以便更好的应用。get_file函数签名如下:
get_file(fname, origin, untar=False, md5_hash=None, file_hash=None, cache_subdir='datasets', hash_algorithm='auto', extract=False, archive_format='auto', cache_dir=None)
fname指的是缓存到本地的文件名,origin其实就是数据集文件的下载地址,即URL,其它参数基本都是不言自明的。函数大致实现如下:
(1)依据cache_dir得到数据文件就存放的文件夹,即:datadir。正常情况下,应该是/.keras/,但如果上述目录不可存取,则是/tmp/.keras/'
在examples目录下的许多例子都涉及到数据集下载,查看源码你会发现它们最终都是通过keras.utils.data_utils.get_file函数下载的,这是一个普适的数据集下载工具函数,所以有必要了解其功能,以便更好的应用。get_file函数签名如下:
get_file(fname, origin, untar=False, md5_hash=None, file_hash=None, cache_subdir='datasets', hash_algorithm='auto', extract=False, archive_format='auto', cache_dir=None)
fname指的是缓存到本地的文件名,origin其实就是数据集文件的下载地址,即URL,其它参数基本都是不言自明的。函数大致实现如下:
(1)依据cache_dir得到数据文件就存放的文件夹,即:datadir。正常情况下,应该是
if cache_dir is None:
cache_dir = os.path.join(os.path.expanduser('~'), '.keras')
datadir_base = os.path.expanduser(cache_dir)
if not os.access(datadir_base, os.W_OK):
datadir_base = os.path.join('/tmp', '.keras')
datadir = os.path.join(datadir_base, cache_subdir)
if not os.path.exists(datadir):
os.makedirs(datadir)
(2)由datadir和fname得到下载到本地的文件名:fpath
if untar:
untar_fpath = os.path.join(datadir, fname)
fpath = untar_fpath + '.tar.gz'else:
fpath = os.path.join(datadir, fname)
(3)如果文件存在,则不用下载,
download = False
if os.path.exists(fpath):
......
else:
download = True
(4)否则,下载文件
if download:
print('Downloading data from', origin)
try:
try:
urlretrieve(origin, fpath, dl_progress)
except HTTPError as e:
raise Exception(error_msg.format(origin, e.code, e.msg))
except URLError as e:
raise Exception(error_msg.format(origin, e.errno, e.reason))
except (Exception, KeyboardInterrupt):
if os.path.exists(fpath):
os.remove(fpath)
raise
(5)确定是否解压,如需要用extract(untar已过时),最后返回已下载的文件路径
if untar:
if not os.path.exists(untar_fpath):
_extract_archive(fpath, datadir, archive_format='tar')
return untar_fpath
if extract:
_extract_archive(fpath, datadir, archive_format)
return fpath
2018年12月12日星期三
Keras源码分析(2):入门示例
文件:keras/examples/mnist_cnn.py
在这里我们选择mnist_cnn作为入门示例。这是一个非常简单的深度卷积神经网络,它运行在MNIST数据集上,在12轮训练之后能达到99.25%的精度。其源码及解释如下:
这很简单,没什么好说的
定义模型的超参数: 批大小、标签类别数量和训练轮数。
有些人对这几个概念比较模糊,所以顺便解释一下:批量大小指的是一次训练的样本数目, 它将影响到模型的优化程度和训练速度。当一个完整的数据集通过了神经网络一次并且返回了一次,就称为一个epoch。还有一个概念就是迭代(iteration),迭代是batch需要完成一个epoch的次数。打个比方,一个数据集有2000个训练样本,将这2000个样本分成大小为500的batch,那么完成一个epoch就需要4个iteration。
获取数据集,至于如何获取,将在后面接下来的两节进行分析。mnist.load_data返回的是这样一个形式的元组:(训练集,测试集),(训练集标签,测试集标签)。
重新格式化数据集的维度。对于图像文件,有的是chanel在前(channels_first),即:(img_chanels, img_rows, img_cols),有的是chanel在后(channels_last),即 (img_rows, img_cols, img_chanels)。
把数据转换成浮点类型和归一化(Normalization),即将每个数据单元统一映射到[0,1]区间上。如果是有量纲表达式的数据,也要把它们变换为无量纲表达式,成为纯量。经过归一化处理的数据,所有特征都处于同一数量级,可以消除指标之间的量纲和量纲单位的影响,以防止某些特征指标占优。
在这里我们选择mnist_cnn作为入门示例。这是一个非常简单的深度卷积神经网络,它运行在MNIST数据集上,在12轮训练之后能达到99.25%的精度。其源码及解释如下:
这很简单,没什么好说的
import keras from keras.datasets import mnist from keras.models import Sequential from keras.layers import Dense, Dropout, Flatten from keras.layers import Conv2D, MaxPooling2D from keras import backend as K
定义模型的超参数: 批大小、标签类别数量和训练轮数。
有些人对这几个概念比较模糊,所以顺便解释一下:批量大小指的是一次训练的样本数目, 它将影响到模型的优化程度和训练速度。当一个完整的数据集通过了神经网络一次并且返回了一次,就称为一个epoch。还有一个概念就是迭代(iteration),迭代是batch需要完成一个epoch的次数。打个比方,一个数据集有2000个训练样本,将这2000个样本分成大小为500的batch,那么完成一个epoch就需要4个iteration。
batch_size = 128 num_classes = 10 epochs = 12 定义输入图像的维度尺寸,要与数据集中图像的大小要一致。 img_rows, img_cols = 28, 28
获取数据集,至于如何获取,将在后面接下来的两节进行分析。mnist.load_data返回的是这样一个形式的元组:(训练集,测试集),(训练集标签,测试集标签)。
# the data, split between train and test sets (x_train, y_train), (x_test, y_test) = mnist.load_data()
重新格式化数据集的维度。对于图像文件,有的是chanel在前(channels_first),即:(img_chanels, img_rows, img_cols),有的是chanel在后(channels_last),即 (img_rows, img_cols, img_chanels)。
if K.image_data_format() == 'channels_first':
x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
input_shape = (1, img_rows, img_cols)
else:
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)
把数据转换成浮点类型和归一化(Normalization),即将每个数据单元统一映射到[0,1]区间上。如果是有量纲表达式的数据,也要把它们变换为无量纲表达式,成为纯量。经过归一化处理的数据,所有特征都处于同一数量级,可以消除指标之间的量纲和量纲单位的影响,以防止某些特征指标占优。
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
采用one-hot编码处理类别标签,即将一个类别向量转换成二分类矩阵。例如:
array([0, 2, 1, 2, 0])有3个类别 {0, 1, 2}
转换后是这个样子:
array([[ 1., 0., 0.],
[ 0., 0., 1.],
[ 0., 1., 0.],
[ 0., 0., 1.],
[ 1., 0., 0.]], dtype=float32)
matricesy_train = keras.utils.to_categorical(y_train, num_classes)
y_test = keras.utils.to_categorical(y_test, num_classes)
以下只做了简单注释,因为每个部分都是重点,所以后续要分单独章节阅读分析。
#new一个Sequential模型
model = Sequential()
# 增加层
model.add(Conv2D(32, kernel_size=(3, 3),
activation='relu',
input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))
# 配置模型
model.compile(loss=keras.losses.categorical_crossentropy,
optimizer=keras.optimizers.Adadelta(),
metrics=['accuracy'])
# 用数据训练模型
model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(x_test, y_test))
# 模型评估
score = model.evaluate(x_test, y_test, verbose=0)
print('Test loss:', score[0])
print('Test accuracy:', score[1])
Keras源码分析(1):包结构概览
参考:github
我们只重点关注以下目录和文件:
1)examples,它是学习keras入门的好地方,所以我们将从这里着手(见下一节),沿着由易到难,由浅入深的方式一步一步阅读分析。
2)application,如果只是想用keras,这里应该是你学习的第二步,从这里你不仅可以学到如何用keras编程,更重要的是你能学到如何架构深度神经网络。在弄清楚几个经典的深度神经网络应用后,你就可以付诸实践,尝试去解决实际应用中的问题了。
3)engine和layers,是Keras的最核心部分,当然也是要重点阅读分析的部分。
4)activations.py,constraints.py,initializers.py,losses.py,metrics.py,optimizers.py,regularizers.py等直接在keras包下的文件,对于这些文件中的内容,可能是理解原理比分析代码更重要。
需要申明的是:
(1)本“Keras源码分析”系列,只是对Keras的主要代码逻辑进行解析,并不是对所有代码逐行注解,因为这容易陷入或纠缠到一些非常具体的细节,甚至是版本细节,从而导致抓小放大;
(2)本“Keras源码分析”系列,也不是对所有类和函数参数进行解释说明,这方面最好和最完整的文档就是Keras的官方文档。
(3)转载请注明出处和链接。
Keras在github上的源码包结构如下:
|-- docs #说明文档
|-- examples #应用示例
|-- test #测试文件
|-- keras #核心源码
|-- application #应用实例,如VGG16,RESNET50
|-- backend #底层接口,如:tensorflow_backend,theano_backend
|-- datasets #数据获取,如boston_housing,mnist
|-- engine #网络架构
|-- layers #层相关
|-- legacy #遗留源码
|-- preprocessing #预处理函数
|-- utils #实用工具
|-- wrappers #scikit-learn封装类
|-- activations.py #激活函数
|-- callbacks.py #回调函数
|-- constraints.py #权重约束,如非零约束等
|-- initializers.py #初始化方法
|-- losses.py #损失函数
|-- metrics.py #度量方法
|-- models.py #模型工具
|-- objectives.py #目标函数,也就是损失函数,为兼容而保留此文件
|-- optimizers.py #优化方法,如SGD,Adam等
|-- regularizers.py #正则项,如L1,L2等
我们只重点关注以下目录和文件:
1)examples,它是学习keras入门的好地方,所以我们将从这里着手(见下一节),沿着由易到难,由浅入深的方式一步一步阅读分析。
2)application,如果只是想用keras,这里应该是你学习的第二步,从这里你不仅可以学到如何用keras编程,更重要的是你能学到如何架构深度神经网络。在弄清楚几个经典的深度神经网络应用后,你就可以付诸实践,尝试去解决实际应用中的问题了。
3)engine和layers,是Keras的最核心部分,当然也是要重点阅读分析的部分。
4)activations.py,constraints.py,initializers.py,losses.py,metrics.py,optimizers.py,regularizers.py等直接在keras包下的文件,对于这些文件中的内容,可能是理解原理比分析代码更重要。
需要申明的是:
(1)本“Keras源码分析”系列,只是对Keras的主要代码逻辑进行解析,并不是对所有代码逐行注解,因为这容易陷入或纠缠到一些非常具体的细节,甚至是版本细节,从而导致抓小放大;
(2)本“Keras源码分析”系列,也不是对所有类和函数参数进行解释说明,这方面最好和最完整的文档就是Keras的官方文档。
(3)转载请注明出处和链接。
订阅:
博文 (Atom)