contents
本博客目标
- 创建一个全连接神经网络
- 应用神经元去解决回归和分类问题
- 用随机梯度下降去训练神经元,并用dropout, batch normalization等方法提升性能。
线性神经元
- 单个输入
x为输入,w为权重;b即bias,用于修正output的独立于input的项
2. 多个输入
用keras.Sequential创建模型
from tensorflow import keras
from tensorflow.keras import layers
# Create a network with 1 linear unit
model = keras.Sequential([
layers.Dense(units=1, input_shape=[3])
])
参数 | 说明 |
---|---|
units | 神经元的输出维度 |
input_shape | 输入数据的特征数,是一个list,方便[height, width, channels]这类复杂数据的输入 |
tensor数组
tensor是TensorFlow下的数组,类似于Numpy中的array,用于深度学习。在keras中用来表示weights。
import tensorflow as tf
layers
layers是keras中非常普通的结构,用来完成数据的转换。层的连接方式不同,如CNN,RNN,就会产生不同的效果。
dense layer:一层输入都相同的神经元
激活函数Activation Function
Relu
以下两种写法相同,激活函数可以独立作为一层
model = keras.Sequential([
layers.Dense(units=32,activation="relu",input_shape=[8]),
layers.Dense(units=32,activation="relu"),
layers.Dense(units=1)
])
### YOUR CODE HERE: rewrite this to use activation layers
model = keras.Sequential([
layers.Dense(32, input_shape=[8]),
layers.Activation("relu"),
layers.Dense(units=32),
layers.Activation("relu"),
layers.Dense(1),
])
建立Sequential 模型
Sequential中的layers按照层的叠加顺序写,最后一层是输出层,之前的都为隐藏层,input_shape作为第一层的参数。
第一次建立后,网络中的weigths都是随机的。
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
# the hidden ReLU layers
layers.Dense(units=4, activation='relu', input_shape=[2]),
layers.Dense(units=3, activation='relu'),
# the linear output layer
layers.Dense(units=1),
])
损失函数
loss function:用于评价结果的好坏
优化方法
optimizer:调整weigths以最小化损失函数的算法。
深度学习的优化算法,几乎都基于随机梯度下降的迭代算法,步骤一般如下:
- 在训练集中采样并训练神经网络
- 通过损失函数度量模型预测和真实值的差距
- 朝损失函数减小的方向调整weights
note:放入模型前,要先进行归一化
名称 | 解释 |
---|---|
batch_size | 每次迭代的训练集样本数量 |
iteration | 每次放batch_size个样本,把全部样本放进网络需要多少次,不满一个batch_size的就不用了。 |
epoch | 把全部样本训练过一次为一个epoch |
learning rate | 调整weigths改变的速度 |
Adam | 随机梯度下降的自适应算法 |
optimizer和loss可通过Keras API 微调
model.compile(
optimizer="adam",
loss="mae",
)
PS:
SGDM:
θ
t
=
θ
t
−
1
−
η
m
t
m
t
=
β
1
m
t
−
1
+
(
1
−
β
1
)
g
t
−
1
\theta_t=\theta_{t-1}-\eta m_t\\m_t=\beta_1m_{t-1}+(1-\beta_1)g_{t-1}
θt=θt−1−ηmtmt=β1mt−1+(1−β1)gt−1
RMSProp:
θ
t
=
θ
t
−
1
−
η
v
t
g
t
v
t
=
β
2
v
t
−
1
+
(
1
−
β
1
)
g
t
−
1
2
v
1
=
g
0
2
\theta_t=\theta_{t-1}-\frac{\eta}{\sqrt{v_t}} g_t\\ v_t=\beta_2v_{t-1}+(1-\beta_1)g_{t-1}^2\\v_1=g_0^2
θt=θt−1−vtηgtvt=β2vt−1+(1−β1)gt−12v1=g02
Adam=SGDM+RMSProp
θ
t
=
θ
t
−
1
−
η
v
t
^
+
ϵ
m
t
^
m
t
^
=
m
t
1
−
β
1
t
v
t
^
=
v
t
1
−
β
2
t
\theta_t=\theta_{t-1}-\frac{\eta}{\sqrt{\hat{v_t}}+\epsilon}\hat{m_t}\\ \hat{m_t}=\frac{m_t}{1-\beta_1^t}\\ \hat{v_t}=\frac{v_t}{1-\beta_2^t}
θt=θt−1−vt^+ϵηmt^mt^=1−β1tmtvt^=1−β2tvt
搭建网络
- 先对数据进行归一化,确定特征的维数
import pandas as pd
from IPython.display import display
data = pd.read_csv('XXX.csv')
# Create training and validation splits
df_train = data.sample(frac=0.7, random_state=0)
df_valid = data.drop(df_train.index)
# Scale to [0, 1]
max_ = df_train.max(axis=0)
min_ = df_train.min(axis=0)
df_train = (df_train - min_) / (max_ - min_)
df_valid = (df_valid - min_) / (max_ - min_) #极差是根据训练集来的
# Split features and target
X_train = df_train.drop('quality', axis=1)
X_valid = df_valid.drop('quality', axis=1)
y_train = df_train['quality']
y_valid = df_valid['quality']
- 按需要搭建一个多层神经网络,设定loss function和optimizer
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(512, activation='relu', input_shape=X_train.shape[1]),
layers.Dense(512, activation='relu'),
layers.Dense(512, activation='relu'),
layers.Dense(1),
])
model.compile(
optimizer='adam',
loss='mae',
)
- 训练数据集(导入训练集,验证集可不导入,设置batch_size,设置epochs)
history = model.fit(
X_train, y_train, #导入训练集的数据
validation_data=(X_valid, y_valid), #导入验证集的数据,可不写
batch_size=256, #设置每次随机梯度下降的样本采样量
epochs=10,
)
fit方法记录了整个训练过程的loss,可将其转化为pandas DataFrame 格式后进行可视化
import pandas as pd
# convert the training history to a dataframe
history_df = pd.DataFrame(history.history)
# use Pandas native plot method
history_df['loss'].plot();
若loss呈现出仍在下降的趋势,需要增加训练的epochs,如果已经下降到一个平缓的位置,则增加epochs用处不大。
对于learning rate和batch_size需要考虑的三个问题:
- 模型训练需要话多长时间
- 学习曲线的波动怎么样
- loss能减少到什么程度
batch_size越小,学习曲线波动越大——数据量小相对噪音就会比较大
learning rate越小,更新得慢,意味着收敛需要更长的时间,大的学习率可以加快速度,但过大会导致整个模型不收敛。
learning curve
所有数据里面都会包含一些noise( ϵ \epsilon ϵ)。对于training loss不论学习到的是noise还是signal,learning curve最终都会下降。但是validation loss 只会在模型学习到比较多的signal时下降,模型学习到的噪音信息不能正确泛化应用到新数据上。学习到的噪音信息越多,learning curve和validation loss的差距越大。
however,学习更多数据的同时,必然也会学习到更多的噪音,我们需要make a trade。只要validation loss在继续减小,增加epochs学习新的数据,虽然这个过程增加了噪音,但是可以接受的,但是过了转折点以后,trade-off就不好啦。
这种trade-off其实处理的是欠拟合和过拟合的问题。
阶段 | 主导原因 |
---|---|
1 | 偏差主导,主要是欠拟合 |
2 | 转折点,噪声主导 |
3 | 方差主导(就是过拟合了) |
验证集 | 解释 | 处理 |
---|---|---|
偏差大 | 就是模型欠拟合造成的 | 一般是训练初期,继续训练,或者换一个强学习器 |
方差大 | 方差是模型训练刻画扰动的,相同规模但内容不同的数据集,所带来的模型性能的变化 过拟合,模型太复杂,把一点点改变都学进去了,模型复杂度太高,就出现方差大的表现(过拟合和方差大差不多一个意思,过拟合说的更形象) | 早停止、正则化、加大样本等等,第一还是从数据找原因 |
模型复杂度越高,方差越大,偏差越小
getting more signal while reducing noise.
对于神经网络,模型的能力与神经元的数模和连接方式有关。模型欠拟合,就需要增加模型的拟合能力:
- wider:增加每层的神经元 → \rightarrow →增加线性关系
- deeper:增加神经元层数 → \rightarrow →增加非线性关系
early stoping
目的:
- 在validation loss 不再降低时,及时停止训练
- 将网络的权重weights重置成validation loss最低时的状态
- 同时epochs要足够大,避免没有训练充分就停止出现欠拟合。
通过callback函数(可通过链接,或者自定义),去平衡validation loss上升的原因——overfitting and random batch variance 。
from tensorflow.keras.callbacks import EarlyStopping
early_stopping = EarlyStopping(
min_delta=0.001, # minimium amount of change to count as an improvement
patience=20, # how many epochs to wait before stopping
restore_best_weights=True,
)
在20epochs(patience=20)中 validation loss若没有0.001(min_delta=0.001)的下降,则停止训练,并保留目前为止最优参数(restore_best_weights=True)。
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=256,
epochs=500,
callbacks=[early_stopping], # put your callbacks in a list
verbose=0, # turn off training log,默认是开着
)
history_df = pd.DataFrame(history.history)
history_df.loc[:, ['loss', 'val_loss']].plot();
print("Minimum validation loss: {}".format(history_df['val_loss'].min()))
如果如下图,两条曲线的gap很小,validation loss 也没有增加,可能还欠拟合,需要增加模型复杂度继续训练
PS:按照某个类别特征划分训练集测试集
from sklearn.model_selection import GroupShuffleSplit
# We'll do a "grouped" split to keep all of an artist's songs in one
# split or the other. This is to help prevent signal leakage.
def group_split(X, y, group, train_size=0.75):
splitter = GroupShuffleSplit(train_size=train_size)
train, test = next(splitter.split(X, y, groups=group))
return (X.iloc[train], X.iloc[test], y.iloc[train], y.iloc[test])
X_train, X_valid, y_train, y_valid = group_split(X, y, artists)
special layers
Dropout
过拟合是因为学习得太符合训练集了——神经网络的这种weights组合把训练集的噪音都学得很好。dropout的想法就是要打破这种组合:
在每个epochs随机去掉每层中一定比例的神经元——加大神经网络学习训练集中noise的难度,进而去探索泛化能力更强,鲁棒性更好的weigths组合。
You could also think about dropout as creating a kind of ensemble of networks. The predictions will no longer be made by one big network, but instead by a committee of smaller networks. Individuals in the committee tend to make different kinds of mistakes, but be right at the same time, making the committee as a whole better than any individual. (If you’re familiar with random forests as an ensemble of decision trees, it’s the same idea.)
Adding Dropout
- 使用Dropout需要在每层适当增加神经元
将layers.Dropout放在要应用dropout的layers前面
keras.Sequential([
# ...
layers.Dropout(rate=0.3), # apply 30% dropout to the next layer
layers.Dense(1024),
# ...
])
Batch Normalization
目的:可用于纠正训练慢和不稳定。(每一层的信息利用更充分而不是最后集中在某些值)
结果:对隐藏层的输入也进行标准化,更有效地利用激活函数进行非线性化的过程.(更有效地传递信息)。
添加神经网络的时候, 先有数据 X, 再添加全连接层, 全连接层的计算结果会经过激励函数成为下一层的输入。Batch Normalization (BN) 就被添加在每一个全连接和激励函数之间:
- 标准化工序:将全连接层的输出的batch用均值和方差标准化
使数据大部分集中在激活函数的敏感区域,信息保留更完善
在验证集和测试时,均值和方差要固定 - 反标准化工序:用scale和shift两个参数(可被神经网络学习),对batch进行再调整
若BN操作无效,学习到的scale和shift会抵消抵消一些BN效果
Adding BN
layers.Dense(16),
layers.BatchNormalization(),
layers.Activation('relu'),
layers.BatchNormalization()如果作为第一层,相当于sklearn里的StandardScaler操作
神经网络做分类
和回归问题最大的区别是,损失函数和模型输出不同。
指标 | 式子 | 情 |
---|---|---|
accuracy | c o r r e c t _ n u m b e r t o t a l \frac{correct\_number}{total} totalcorrect_number | 标签类别的比例大致相同 |
上述指标不能直接作为损失函数loss function(SGD需要损失函数光滑)
损失函数 | 解释 | 式子 |
---|---|---|
Cross-entropy | 概率意义上的距离度量 | H ( p , q ) = − ∑ i = 1 n p ( x i ) log ( q ( x i ) ) H(p,q)=-\sum_{i=1}^np(x_i)\log (q(x_i)) H(p,q)=−∑i=1np(xi)log(q(xi)),这里 l o s s = − 1 m ∑ j = 1 m ∑ i = 1 n y i j log y i j log y i j ^ loss=-\frac{1}{m}\sum_{j=1}^m \sum_{i=1}^n y_{ij} \log y_{ij}\log \hat{y_{ij}} loss=−m1∑j=1m∑i=1nyijlogyijlogyij^,n为类别数,m为batch_size |
(
PS:
D
K
L
(
p
∣
∣
q
)
=
−
H
(
p
(
x
)
)
+
[
−
∑
i
=
1
n
p
(
x
i
)
log
(
q
(
x
i
)
)
]
,
D_{KL}(p||q)=-H(p(x))+[-\sum_{i=1}^np(x_i)\log (q(x_i))],
DKL(p∣∣q)=−H(p(x))+[−i=1∑np(xi)log(q(xi))],这里的KL散度为
D
K
L
(
y
∣
∣
y
^
)
D_{KL}(y||\hat{y})
DKL(y∣∣y^).
)
首先,给目标分配神经网络可学习的数值型类别标签。
构建分类的神经网络
sigmoid 函数
可将神经层输出转化到0~1之间的概率,需要确定一个阈值。
在最后一层,应该使用sigmod激活函数去产生类别概率。
from tensorflow import keras
from tensorflow.keras import layers
model = keras.Sequential([
layers.Dense(4, activation='relu', input_shape=[33]),
layers.Dense(4, activation='relu'),
layers.Dense(1, activation='sigmoid'),
])
同时,compile方法也需要变换
model.compile(
optimizer='adam',
loss='binary_crossentropy',
metrics=['binary_accuracy'],
)
early stopping和fit方法不变
early_stopping = keras.callbacks.EarlyStopping(
patience=10,
min_delta=0.001,
restore_best_weights=True,
)
history = model.fit(
X_train, y_train,
validation_data=(X_valid, y_valid),
batch_size=512,
epochs=1000,
callbacks=[early_stopping],
verbose=0, # hide the output because we have so many epochs
)
最后画出学习曲线和正确率变换曲线:
history_df = pd.DataFrame(history.history)
# Start the plot at epoch 5
history_df.loc[5:, ['loss', 'val_loss']].plot()
history_df.loc[5:, ['binary_accuracy', 'val_binary_accuracy']].plot()
print(("Best Validation Loss: {:0.4f}" +\
"\nBest Validation Accuracy: {:0.4f}")\
.format(history_df['val_loss'].min(),
history_df['val_binary_accuracy'].max()))
(simpleImpute,onehotencoder(handle_unknown),make_pipeline