目的:判断一张图片是否是猫。查看两层和四层的区别
1.编写获取数据函数
def load_data():
train_dataset = h5py.File('datasets/train_catvnoncat.h5', "r")
train_set_x_orig = np.array(train_dataset["train_set_x"][:]) # your train set features
train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # your train set labels
test_dataset = h5py.File('datasets/test_catvnoncat.h5', "r")
test_set_x_orig = np.array(test_dataset["test_set_x"][:]) # your test set features
test_set_y_orig = np.array(test_dataset["test_set_y"][:]) # your test set labels
classes = np.array(test_dataset["list_classes"][:]) # the list of classes
train_set_y_orig = train_set_y_orig.reshape((1, train_set_y_orig.shape[0]))#(1,m) 二维数组表示行向量
test_set_y_orig = test_set_y_orig.reshape((1, test_set_y_orig.shape[0]))
return train_set_x_orig, train_set_y_orig,test_set_x_orig, test_set_y_orig, classes
#加载原始数据集
train_x_orig,train_y,test_x_orig,test_y,classes = load_data()
#可视化某一张图片
index=25
plt.imshow(train_x_orig[index]) #可视化原始训练集中第index+1张图片
print("y = "+str(train_y[0,index])+". It is a "+classes[train_y[0,index]].decode("utf-8")+" picture.")
m_train = train_set_y.shape[1] #训练集里图片的数量。
m_test = test_set_y.shape[1] #测试集里图片的数量。
num_px = train_set_x_orig.shape[1] #训练、测试集里面的图片的宽度和高度(均为64x64)。
#现在看一看我们加载的东西的具体情况
print ("训练集的数量: m_train = " + str(m_train))
print ("测试集的数量 : m_test = " + str(m_test))
print ("每张图片的宽/高 : num_px = " + str(num_px))
print ("每张图片的大小 : (" + str(num_px) + ", " + str(num_px) + ", 3)")
print ("训练集_图片的维数 : " + str(train_set_x_orig.shape))
print ("训练集_标签的维数 : " + str(train_set_y.shape))
print ("测试集_图片的维数: " + str(test_set_x_orig.shape))
print ("测试集_标签的维数: " + str(test_set_y.shape))
==========================================
训练集的数量: m_train = 209
测试集的数量 : m_test = 50
每张图片的宽/高 : num_px = 64
每张图片的大小 : (64, 64, 3)
训练集_图片的维数 : (209, 64, 64, 3)
训练集_标签的维数 : (1, 209)
测试集_图片的维数: (50, 64, 64, 3)
测试集_标签的维数: (1, 50)
在此之后,训练和测试数据集是一个numpy数组,【每列代表一个平坦的图像】 ,应该有m_train和m_test列。
当想将形状(a,b,c,d)的矩阵X平铺成形状(b * c * d,a)的矩阵X_flatten时,可以使用以下代码:
#对训练集和测试集中的图片进行转型
train_x_flatten=train_x_orig.reshape(m_train,-1).T #训练集图片的特征矩阵 (num_px*num_px*3,m_train) 每一列为一张图片的拉伸后的特征向量
test_x_flatten=test_x_orig.reshape(m_test,-1).T #测试集图片的特征矩阵 (num_px*num_px*3,m_test) 每一列为一张图片的拉伸后的特征向量
#对图片的特征矩阵进行标准化 除以最大值255 使特征数值在0-1之间
train_x=train_x_flatten/255
test_x=test_x_flatten/255
print ("train_x's shape: " + str(train_x.shape))
print ("test_x's shape: " + str(test_x.shape))
1. 双层神经网络(L=2 单隐层)
2. L层的神经网络(L>=3 隐层数>=2)
1.构建神经网络步骤
**
**
linear>relu>linear>sigmoid
def initialize_parameters(n_x,n_h,n_y):
'''
初始化双层神经网络的参数
参数:
n_x:输入层单元数 取决于输入样本的特征向量维数
n_h:隐层单元数 可以任意设置
n_y:输出层单元数 n_y=1
返回:
parameters:初始化后的参数(字典形式)
W1:(n_h,n_x)
b1:(n_h,1)
W2:(n_y,n_h)
b2:(n_y,1)
'''
W1=np.random.randn(n_h,n_x)*0.01
b1=np.zeros((n_h,1))
W2=np.random.randn(n_y,n_h)*0.01
b2=np.zeros((n_y,1))
assert(W1.shape == (n_h,n_x))
assert(b1.shape == (n_h, 1))
assert(W2.shape == (n_y, n_h))
assert(b2.shape == (n_y, 1))
parameters = {"W1": W1,
"b1": b1,
"W2": W2,
"b2": b2}
return parameters
1.线性向前运算代码实现
公式: Z = W T ∗ X + b Z = W^T*X +b Z=WT∗X+b
def linear_forward(A,W,b):
'''
隐层/输出层 前向传播的线性组合部分
参数:
A:上一层的激活函数输出 初始值是样本特征矩阵X
W:当前层的权重参数矩阵
b: 当前层的偏置参数向量
返回:
Z:当前层的线性组合输出
cache:元组形式 (A,W,b)
'''
Z = np.dot(W,A)+b
assert(Z.shape == (W.shape[0],A.shape[1]))
cache = (A,W,b)
return Z,cache
2.前向激活函数代码实现
def sigmoid(Z):
'''
2分类,输出层采用sigmoid激活函数
输出层前向传播的激活函数部分
参数:
Z:当前层(输出层)的线性组合输出
返回:
A:当前层(输出层)的激活输出
cache: Z
'''
A = 1./(1+np.exp(-Z))
cache = Z
assert(A.shape == Z.shape)
return A,cache
def relu(Z):
'''
隐层统一采用relu激活函数
参数:
Z:当前层(隐层)的线性组合输出
返回:
A:当前层(隐层)的激活输出
cache: Z
'''
A = np.maximum(0,Z)
cache = Z
assert(A.shape == Z.shape)
return A,cache
def linear_activation_forward(A_prev,W,b,activation):
'''
隐层(输出层)的前向传播操作,包括线性组合和激活函数两部分
参数:
A_perv:上一层的激活函数输出 初始值是样本特征矩阵X
W:上一层和当前层之间的权重参数矩阵
b: 上一层和当前层之间的偏置参数向量
activation:使用的激活函数类型 一般所有隐层的激活函数是一样的 输出层如果作2分类使用sigmoid
返回:
A: 当前层的激活函数输出
cache:存储重要的中间结果,方便运算。元组形式(linear_cache,activation_cache)=((A_prev,W,b),Z)
'''
if activation == 'sigmoid':
Z,linear_cache = linear_forward(A_prev,W,b) #线性单元
A,activation_cache = sigmoid(Z) #激活单元
elif activation == 'relu':
Z,linear_cache = linear_forward(A_prev,W,b) #线性单元
A,activation_cache = relu(Z) #激活单元
assert(A.shape == (W.shape[0],A_prev.shape[1]))
cache=(linear_cache,activation_cache)
return A,cache
3.计算损失值
def compute_cost(AL,Y):
'''
实现cost函数,计算代价
参数:
AL:输出层的激活输出 模型最终输出 (1,m)
Y:样本的真实标签 0/1 (1,m)
返回:
cost: 交叉熵代价
'''
cost = np.mean(-Y*np.log(AL)-(1-Y)*np.log(1-AL))
cost = np.squeeze(cost) #Y和AL都是用2维数组表示的向量 cost算出来是[[cost]],利用squeeze把它变成cost
assert(cost.shape == ())
return cost
更新梯度
def update_parameters(parameters,grads,learning_rate):
'''
使用梯度下降法更新模型参数
参数:
parameters:模型参数
grads:计算的参数梯度 字典形式
learning_rate:学习率
返回:
parameters:更新后的参数 字典形式
parameters["W" + str(l)] = ...
parameters["b" + str(l)] = ...
'''
L = len(parameters)//2 #神经网络层数(输入层是第0层 不算输入层)
#一次梯度下降迭代 更新参数
for l in range(L): #l 0~L-1
parameters['W'+str(l+1)] = parameters['W'+str(l+1)] - learning_rate*grads['dW'+str(l+1)]
parameters['b'+str(l+1)] = parameters['b'+str(l+1)] - learning_rate*grads['db'+str(l+1)]
return parameters
4.构建双层神经网络模型
#定义网络结构
n_x = 12288 #num_px*num_px*3
n_h = 7 #隐层单元数 可以设置
n_y = 1 #2分类
layers_dims = (n_x,n_h,n_y) #各层单元数
def two_layer_model(X,Y,layers_dims,learning_rate=0.0075,num_iterations=3000,print_cost=False):
'''
实现双层网络结构
参数:
X:训练集特征矩阵 (n_x,m_train)
Y:训练集样本对于的标签 0/1 (1,m_train)
layers_dims:各层的单元数
learning_rate:学习率
num_iterations:梯度下降法迭代次数
print_cost:为True时,每100次迭代打印一次cost
返回:
parameters:训练好的参数 字典形式
parameters["W" + str(l)] = ...
parameters["b" + str(l)] = ...
'''
np.random.seed(1)
grads = {} #存储每一次反向传播计算的梯度
costs = [] #存储每100次前向传播计算的代价
m = X.shape[1]
(n_x,n_h,n_y) = layers_dims
#初始化模型参数
parameters = initialize_parameters(n_x,n_h,n_y)
#取出每一个初始化后的参数
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']
#梯度下降迭代
#batch GD 每一次使用全部的样本计算梯度
for i in range(num_iterations):
#前向传播
A1,cache1 = linear_activation_forward(X,W1,b1,'relu')
A2,cache2 = linear_activation_forward(A1,W2,b2,'sigmoid')
#计算代价
cost = compute_cost(A2,Y)
#计算反向传播的初始项 dAL=dA2
dA2 = -Y/A2 + (1-Y)/(1-A2)
dA1,dW2,db2 = linear_activation_backward(dA2,cache2,'sigmoid')
dA0,dW1,db1 = linear_activation_backward(dA1,cache1,'relu')
grads['dW1']=dW1
grads['db1']=db1
grads['dW2']=dW2
grads['db2']=db2
#更新参数
parameters = update_parameters(parameters,grads,learning_rate)
#取出每一个更新后的参数
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']
#每100次迭代打印一次代价 并存储
if print_cost and i%100==0:
print("Cost after iteration {}: {}".format(i, np.squeeze(cost)))
costs.append(cost)
#绘制代价对迭代次数的变化曲线
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per hundreds)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
return parameters
parameters = two_layer_model(train_x, train_y, layers_dims = (n_x, n_h, n_y), num_iterations = 2500, print_cost=True)
编写预测函数代码
上一步中的optimize函数会输出已学习的w和b的值,我们可以使用w和b来预测数据集X的标签。
计算预测步骤:
def predict(X,Y,parameters):
'''
使用训练好的模型进行预测
参数:
X:数据集样本特征矩阵(n_x,m)
Y:数据集样本真实标签 0/1 (1,m)
parameters:训练好的参数
返回:
p:数据集样本预测标签 0/1
'''
#取出每一个学习好的的参数
W1 = parameters['W1']
b1 = parameters['b1']
W2 = parameters['W2']
b2 = parameters['b2']
#前向传播 linear_activation_forward定义了一层的前向传播
A1,cache1 = linear_activation_forward(X,W1,b1,'relu')
A2,cache2 = linear_activation_forward(A1,W2,b2,'sigmoid')
m = X.shape[1]
p = np.zeros((1,m))
p[A2 > 0.5] = 1
print("Accuracy: " + str(np.mean(p == Y)))
return p
predictions_train = predict(train_x, train_y, parameters) #训练集上的准确率
predictions_test = predict(test_x, test_y, parameters)#测试集上的准确率
index = 32
plt.imshow(test_x[:,index].reshape((64, 64, 3)))
print ("y = " + str(test_set_y[0,index]) + ", you predicted that it is a \"" + classes[int(predictions_test[0,index])].decode("utf-8") + "\" picture.")
网络结构: [linear>relu]x(L-1)>linear>sigmoid
def initialize_parameters_deep(layer_dims):
'''
参数:
layer_dims:网络每一层的单元数
返回:
parameters:初始化后的参数 字典形式
"W1", "b1", ..., "WL", "bL":
Wl -- weight matrix of shape (layer_dims[l], layer_dims[l-1])
bl -- bias vector of shape (layer_dims[l], 1)
'''
np.random.seed(1)
parameters = {}
L = len(layer_dims) #神经网络层数+1 包括输入层
for l in range(1,L):
parameters['W'+str(l)] = np.random.randn(layer_dims[l],layer_dims[l-1])*np.sqrt(2./layer_dims[l-1]) #*0.01
parameters['b'+str(l)] = np.zeros((layer_dims[l],1))
assert(parameters['W' + str(l)].shape == (layer_dims[l], layer_dims[l-1]))
assert(parameters['b' + str(l)].shape == (layer_dims[l], 1))
return parameters
注意:由于隐藏层统一同relu激活函数,输出层2分类用的是sigmoid,隐藏层可以统一处理,输出层单独处理
#之前定义了每层的前向传播 对于深层网络来说整体的前向传播 可以使用for循环迭代每层的前向传播
def linear_forward(A,W,b):
'''
隐层/输出层 前向传播的线性组合部分
参数:
A:上一层的激活函数输出 初始值是样本特征矩阵X
W:当前层的权重参数矩阵
b: 当前层的偏置参数向量
返回:
Z:当前层的线性组合输出
cache:元组形式 (A,W,b)
'''
Z = np.dot(W,A)+b
assert(Z.shape == (W.shape[0],A.shape[1]))
cache = (A,W,b)
return Z,cache
def sigmoid(Z):
'''
2分类,输出层采用sigmoid激活函数
输出层前向传播的激活函数部分
参数:
Z:当前层(输出层)的线性组合输出
返回:
A:当前层(输出层)的激活输出
cache: Z
'''
A = 1./(1+np.exp(-Z))
cache = Z
assert(A.shape == Z.shape)
return A,cache
def relu(Z):
'''
隐层统一采用relu激活函数
参数:
Z:当前层(隐层)的线性组合输出
返回:
A:当前层(隐层)的激活输出
cache: Z
'''
A = np.maximum(0,Z)
cache = Z
assert(A.shape == Z.shape)
return A,cache
def linear_activation_forward(A_prev,W,b,activation):
'''
隐层(输出层)的前向传播操作,包括线性组合和激活函数两部分
参数:
A_perv:上一层的激活函数输出 初始值是样本特征矩阵X
W:上一层和当前层之间的权重参数矩阵
b: 上一层和当前层之间的偏置参数向量
activation:使用的激活函数类型 一般所有隐层的激活函数是一样的 输出层如果作2分类使用sigmoid
返回:
A: 当前层的激活函数输出
cache:存储重要的中间结果,方便运算。元组形式(linear_cache,activation_cache)=((A_prev,W,b),Z)
'''
if activation == 'sigmoid':
Z,linear_cache = linear_forward(A_prev,W,b) #线性单元
A,activation_cache = sigmoid(Z) #激活单元
elif activation == 'relu':
Z,linear_cache = linear_forward(A_prev,W,b) #线性单元
A,activation_cache = relu(Z) #激活单元
assert(A.shape == (W.shape[0],A_prev.shape[1]))
cache=(linear_cache,activation_cache)
return A,cache
def L_model_forward(X,parameters):
'''
L层神经网络整体的前向传播 调用之前写好的每层的前向传播 用for循环迭代
参数:
X:数据集的特征矩阵 (n_x,m)
parameters:模型参数
返回:
AL:模型最后的输出
caches:列表形式,存储每一层前向传播的cache=(linear_cache,activation_cache)
=((A_prev,W,b),Z)
对于linear_relu_forward()有L-1项cache 下标0~L-2
对于linear_sigmoid_forward()有1项cache 下标L-1
'''
caches = []
A = X #A0 前向传播初始项
L = len(parameters)//2 #神经网络层数(不包含输入层)
#前向传播通项
#隐层和输出层激活函数不同 for循环迭代隐层前向传播 输出层前向传播单独算
#隐层
for l in range(1,L): #l: 1~L-1
A_prev = A
A,cache = linear_activation_forward(A_prev,parameters['W'+str(l)],
parameters['b'+str(l)],'relu')
caches.append(cache)
#输出层
AL,cache = linear_activation_forward(A,parameters['W'+str(L)],
parameters['b'+str(L)],'sigmoid')
caches.append(cache)
return AL,caches
def compute_cost(AL,Y):
'''
实现cost函数,计算代价
参数:
AL:输出层的激活输出 模型最终输出 (1,m)
Y:样本的真实标签 0/1 (1,m)
返回:
cost: 交叉熵代价
'''
cost = np.mean(-Y*np.log(AL)-(1-Y)*np.log(1-AL))
cost = np.squeeze(cost) #Y和AL都是用2维数组表示的向量 cost算出来是[[cost]],利用squeeze把它变成cost
assert(cost.shape == ())
return cost
def sigmoid_backward(dA,cache):
'''
sigmoid激活单元(输出层)的反向传播
参数:
dA:当前层(输出层)激活输出AL的梯度
cache:存储当前层(输出层)的线性组合输出Z,方便激活单元反向传播的计算
返回:
dZ:当前层(输出层)线性组合输出Z的梯度
'''
Z = cache
s = 1./(1+np.exp(-Z))
#dZ=dA*(A对Z求导) A=sigmoid(Z) A对Z的导数=A(1-A)
dZ = dA*s*(1-s)
assert(dZ.shape == Z.shape)
return dZ
def relu_backward(dA,cache):
'''
隐层统一使用relu激活函数
relu激活单元(隐层)的反向传播
参数:
dA:当前层(隐层)激活输出Al的梯度
cache:存储当前层(隐层)的线性组合输出Z,方便激活单元反向传播的计算
返回:
dZ:当前层(隐层)线性组合输出Z的梯度
'''
Z = cache
#dZ=dA*(A对Z求导) 当Z>0时 A对Z求导=1 否则为0
dZ = np.array(dA,copy=True)
dZ[Z<=0] = 0
assert(dZ.shape == Z.shape)
return dZ
def linear_backward(dZ,cache):
'''
输出层/隐层 线性单元的反向传播
参数:
dZ:当前层组合输出Z的梯度
cache:存储当前层前向传播的线性单元参数 (A_prev,W,b),方便线性单元反向传播的计算
返回:
dA_prev:前一层激活输出的梯度
dW:当前层权重参数矩阵的梯度
db:当前层偏置参数向量的梯度
'''
A_prev,W,b = cache
m = A_prev.shape[1] #样本数
dW = 1./m*np.dot(dZ,A_prev.T) #m个样本的平均梯度
db = np.mean(dZ,axis=1,keepdims=True)
dA_prev = np.dot(W.T,dZ)
assert(dW.shape == W.shape)
assert(db.shape == b.shape)
assert(dA_prev.shape == A_prev.shape)
return dA_prev,dW,db
def linear_activation_backward(dA,cache,activation):
"""
输出层\隐层的反向传播操作,包括激活函数和线性组合两部分
dvar 代表 最终输出对var的偏导
参数:
dA:当前层的激活输出梯度dAl 初始值是dAL (代价函数对AL求偏导)
cache:前向传播阶段存储的重要参数和中间结果,便于与反向传播共享参数,方便运算。元组形式(linear_cache,activation_cache)=((A_prev,W,b),Z)
activation:使用的激活函数类型 一般所有隐层的激活函数是一样的 输出层如果作2分类使用sigmoid
返回:
dA_prev:前一层激活输出的梯度dA(l-1)
dW:当前层权重矩阵的梯度 和当前层W维度一致
db:当前层偏置向量的梯度 和当前层b维度一致
"""
linear_cache,activation_cache = cache
if activation == 'sigmoid':
dZ = sigmoid_backward(dA,activation_cache) #激活单元反向传播
dA_prev,dW,db = linear_backward(dZ,linear_cache)#线性单元反向传播
elif activation == 'relu':
dZ = relu_backward(dA,activation_cache) #激活单元反向传播
dA_prev,dW,db = linear_backward(dZ,linear_cache)#线性单元反向传播
return dA_prev,dW,db
#向后传播模型
def L_model_backward(AL,Y,caches):
'''
L层神经网络整体的反向传播 调用之前写好的每层的反向传播 用for循环迭代
参数:
AL:前向传播最终的输出
Y:数据集样本真实标签 0/1
caches:前向传播缓存的重要参数和中间结果 方便运算
列表形式,存储每一层前向传播的cache=(linear_cache,activation_cache)
=((A_prev,W,b),Z)
对于linear_relu_forward()有L-1项cache 下标0~L-2
对于linear_sigmoid_forward()有1项cache 下标L-1
返回:
grads:字典形式 存储每一层参数的梯度:
grads["dA" + str(l)] = ...
grads["dW" + str(l)] = ...
grads["db" + str(l)] = ...
'''
grads = {}
L = len(caches) #网络层数 不包含输入层
m = AL.shape[1]
Y = Y.reshape(AL.shape)
#反向传播初始项
dAL = -Y/AL + (1-Y)/(1-AL)
#输出层单独计算 隐层统一用for循环计算
current_cache = caches[L-1] #输出层前向传播的cache
grads['dA'+str(L)],grads['dW'+str(L)],grads['db'+str(L)] = linear_activation_backward(dAL,
current_cache,'sigmoid') #grads['dAl']实际上是grads['dAl-1'] 便于与dW,db统一处理
for l in reversed(range(L-1)): #l:L-2~0
current_cache = caches[l]
dA_prev_temp, dW_temp, db_temp = linear_activation_backward(grads['dA'+str(l+2)],current_cache,'relu')
grads["dA" + str(l + 1)] = dA_prev_temp
grads["dW" + str(l + 1)] = dW_temp
grads["db" + str(l + 1)] = db_temp
return grads
def update_parameters(parameters,grads,learning_rate):
'''
使用梯度下降法更新模型参数
参数:
parameters:模型参数
grads:计算的参数梯度 字典形式
learning_rate:学习率
返回:
parameters:更新后的参数 字典形式
parameters["W" + str(l)] = ...
parameters["b" + str(l)] = ...
'''
L = len(parameters)//2 #神经网络层数(输入层是第0层 不算输入层)
#一次梯度下降迭代 更新参数
for l in range(L): #l 0~L-1
parameters['W'+str(l+1)] = parameters['W'+str(l+1)] - learning_rate*grads['dW'+str(l+1)]
parameters['b'+str(l+1)] = parameters['b'+str(l+1)] - learning_rate*grads['db'+str(l+1)]
return parameters
#定义网络结构
layers_dims = [12288, 20, 7, 5, 1] # 各层的单元数 四层网
def L_layer_model(X, Y, layers_dims, learning_rate = 0.0075, num_iterations = 3000, print_cost=False):
'''
实现L层网络
参数:
X:训练集样本特征矩阵(n_x,m_train)
Y:训练集样本标签 0/1 (1,m_train)
layers_dims:各层的单元数
learning_rate:学习率
num_iterations:梯度下降法迭代次数
print_cost:为True时,每100次迭代打印一次cost
返回:
parameters:训练好的参数 字典形式
parameters["W" + str(l)] = ...
parameters["b" + str(l)] = ...
'''
np.random.seed(1)
costs = [] #存储每100次前向传播计算的代价
#初始化参数
parameters = initialize_parameters_deep(layers_dims)
#梯度下降迭代
#batch GD 每次反向传播使用全部样本计算梯度
for i in range(num_iterations):
#前向传播
AL,caches = L_model_forward(X,parameters)
#计算代价
cost = compute_cost(AL,Y)
#反向传播计算梯度
grads = L_model_backward(AL,Y,caches)
#更新参数
parameters = update_parameters(parameters,grads,learning_rate)
#每100次迭代打印一次代价 并存储
if print_cost and i%100==0:
print("Cost after iteration {}: {}".format(i, np.squeeze(cost)))
costs.append(cost)
#绘制代价对迭代次数的变化曲线
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per hundreds)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
return parameters
下一篇我们会给此4层神经网络加入:L2正则,dropout,,mini_batch—MBGD(小批量梯度下降),(Momentum、adam)优化器。
此文章借鉴与:https://blog.csdn.net/sdu_hao/article/details/84838375#5.L%E5%B1%82%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C(L%3E%3D3%2C%E9%9A%90%E5%B1%82%E6%95%B0%3E%3D2)