NN入门,手把手教你用Numpy手撕NN(一)

前言

这是一篇包含极少数学推导的NN入门文章

大概从今年4月份起就想着学一学NN,但是无奈平时时间不多,而且空闲时间都拿去做比赛或是看动漫去了,所以一拖再拖,直到这8月份才正式开始NN的学习。

这篇文章主要参考了《深度学习入门:基于Python的理论与实现》一书,感觉这本书很不错,偏向实践,蛮适合入门。

话不多说,下面开始我们的NN入门(手撕NN)之旅

基础数学知识

这里只对张量进行简单介绍,关于矩阵运算之类的,就靠你们自己另外学啦。

标量(0D张量)

仅包含一个数字的张量叫作标量(scalar,也叫标量张量、零维张量、0D 张量)。在 Numpy 中,一个 float32 或 float64 的数字就是一个标量张量(或标量数组)。你可以用 ndim 属性来查看一个 Numpy 张量的轴的个数。

>>> import numpy as np
>>> x = np.array(1)
>>> x
array(1)
>>> x.ndim
0

向量(1D张量)

数字组成的数组叫作向量(vector)或一维张量(1D 张量)。一维张量只有一个轴。下面是 一个 Numpy 向量。

>>> x = np.array([1, 2, 3, 4, 5])
>>> x
array([1, 2, 3, 4, 5])
>>> x.ndim
1 

这个向量有5 个元素,也被称为5D 向量。

矩阵(2D张量)

向量组成的数组叫作矩阵(matrix)或二维张量(2D 张量)。矩阵有 2 个轴(通常叫作行和列),下面是一个 Numpy 矩阵。

>>> x = np.array([[5, 78, 2, 34, 0],
                  [6, 79, 3, 35, 1],
                  [7, 80, 4, 36, 2]])
>>> x.ndim
2 

第一个轴上的元素叫作行(row),第二个轴上的元素叫作列(column)。在上面的例子中, [5, 78, 2, 34, 0] 是 x 的第一行,[5, 6, 7] 是第一列。

3D张量与更高维张量

将多个矩阵组合成一个新的数组,可以得到一个3D 张量,可以将其直观地理解为数字 组成的立方体。下面是一个 Numpy 的 3D 张量。

>>> x = np.array([[[5, 78, 2, 34, 0],
                   [6, 79, 3, 35, 1],
                   [7, 80, 4, 36, 2]],
                  [[5, 78, 2, 34, 0],
                   [6, 79, 3, 35, 1],
                   [7, 80, 4, 36, 2]],
                  [[5, 78, 2, 34, 0],
                   [6, 79, 3, 35, 1],
                   [7, 80, 4, 36, 2]]])
>>> x.ndim
3 

将多个3D 张量组合成一个数组,可以创建一个4D 张量,以此类推。深度学习处理的一般 是 0D 到 4D 的张量,但处理视频数据时可能会遇到 5D 张量。

神经网络(Neural Network)

神经网络实际上是由多个层(神经网络的基本数据结构)堆叠而成,层是一个数据处理模块,可以将一个 或多个输入张量转换为一个或多个输出张量。下图是一个最简单的网络

这是一个三层神经网络(但实质上只有2层神经元有权重,因此也可称其为“2层网络”),包括输入层中间层(隐藏层)输出层。(个人认为,对于任意一个网络,都可以简化成上图所示的一个三层的神经网络,数据从输入层进入,经过一层运算进入隐藏层,然后在隐藏层中进行各种运算,最后再通过一层运算到达输出层,输出我们所需的结果)。

那么,对于一个最简单的网络,每一层的运算是如何的呢?

如上图所示,假设我们输入了 \(x_1, x_2\), \(x_1, x_2\) 分别乘上到下一层的权重,再加上偏置,得到一个y值,这个y值将作为下一层的输入,用公式表达如下
\[
y = w_1x_1+w_2x_2+b {\tag 1}
\]
可想而知,如果所有的计算都是这样的话,那神经网络就只是一个线性模型,那要如何使其具有非线性呢?

很简单,可以加入激活函数\(h(x)\),那么,我们的公式便可改成
\[
a=w_1x_1+w_2x_2+b {\tag {2.1}}
\]

\[
y=h(a) {\tag {2.2}}
\]

首先,式(2.1)计算加权输入信号和偏置的总和,记为a。然后,式(2.2) 用h(x)函数将a转换为输出y。

激活函数

这里介绍下常用的激活函数

sigmoid函数

说到非线性,比较容易想到的应该是阶跃函数,比如下面代码所示的

def step_function(x):
    if x > 0:
        return 1
    else:
        return 0

但是,由于阶跃函数只有两个值,不存在平滑性,在计算过程中表示能力肯定不够好,所以,又想到sigmoid函数

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

sigmoid函数的平滑性对神经网络的学习具有重要意义。

ReLU函数

在神经网络发展的历史上,sigmoid函数很早就开始被使用了,而最近则主要使用ReLU(Rectified Linear Unit)函数。
\[
h(x)=
\begin{cases}
x,\quad x > 0\0,\quad x<=0
\end{cases}
\tag{3}
\]

def relu(x):
    return np.maximum(0, x)

恒等函数和softmax函数(输出层激活函数)

神经网络可以用在分类问题和回归问题上,不过需要根据情况改变输出 层的激活函数。一般而言,回归问题用恒等函数,分类问题用softmax函数。

恒等函数会将输入按原样输出,对于输入的信息,不加以任何改动地直 接输出。因此,在输出层使用恒等函数时,输入信号会原封不动地被输出。

分类问题中使用的softmax函数可以用下面的式子表示。
\[
y_k = \frac{exp(a_k)}{\sum^n_{i=1}exp(a_i)} \tag{4}
\]

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

上面的softmax函数的实现在计算机的运算上存在有溢出问题。softmax函数的实现中要进行指数函数的运算,但是此时指数函数的值很容易变得非常大。比如,\(e^{10}\)的值 会超过20000,\(e^{100}\)会变成一个后面有40多个0的超大值,\(e^{1000}\)的结果会返回 一个表示无穷大的inf。如果在这些超大值之间进行除法运算,结果会出现“不确定”的情况。

因此对softmax做如下改进

def softmax(a):
    c = np.max(a)
    exp_a = np.exp(a - c) # 溢出对策
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    return y

网络的学习

从之前的介绍来看,设置好神经网络的参数,设置好激活函数,似乎就可以利用该神经网络来做预测了,事实也是入此。但这里存在一个很重要的问题,网络的各个权重参数如何设置?1. 人为设置,这好像就成了人工神经网络,并且十分不现实,一旦网络结构比较大,具有数万个神经元的时候,完全无法设置参数。2. 从数据中学习,这是所有机器学习、深度学习模型的一个很重要的特征,从数据中学习。

下面将介绍神经网络在学习中需要的一些东西

损失函数(loss function)

相信有机器学习基础的对此都不陌生。神经网络每次在学习时,会更新一组权重,通过这组新的权重然后产生一组预测值,那我们如何判断这组权重是否是较优的呢?通过损失函数即可,这里介绍两个损失函数(可跳过)。

损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的 神经网络对监督数据在多大程度上不拟合,在多大程度上不一致。 以“性能的恶劣程度”为指标可能会使人感到不太自然,但是如 果给损失函数乘上一个负值,就可以解释为“在多大程度上不坏”, 即“性能有多好”。并且,“使性能的恶劣程度达到最小”和“使性 能的优良程度达到最大”是等价的,不管是用“恶劣程度”还是“优 良程度”,做的事情本质上都是一样的。

均方误差(mean squared error)

\[
E=\frac{1}{2}\sum_k(y_k-t_k)^2 \tag{5}
\]

def mean_squared_error(y, t):
    return 0.5 * np.sum((y-t)**2)

该损失函数常用于回归问题

交叉熵误差(cross entropy error)

\[
E=-\sum_k{t_klogy_k} \tag{6}
\]

def cross_entropy_error(y, t):
    delta = 1e-7
    return -np.sum(t * np.log(y + delta))

这里,参数y和t是NumPy数组。函数内部在计算np.log时,加上了一 个微小值delta。这是因为,当出现np.log(0)时,np.log(0)会变为负无限大的-inf,这样一来就会导致后续计算无法进行。作为保护性对策,添加一个微小值可以防止负无限大的发生。

交叉熵误差常用于分类问题上

mini-batch 学习

介绍了损失函数之后,其实已经可以利用损失函数开始训练我们的神经网络了,但是,我们每次训练都不止一条数据,如果想要训练出比较好的神经网络模型,在计算损失函数时就必须将所有的训练数据作为对象。以交叉熵误差为例,损失函数改写成下面的式子
\[
E=-\frac{1}{N}\sum_n\sum_kt_{nk}logy_{nk} \tag{7}
\]
但是,同时需考虑,在MNIST数据集中,训练数据有60000条,如果以全部数据为对象求损失函数的和,则计算过程需要花费较长的时间。再者,如果遇到大数据, 数据量会有几百万、几千万之多,这种情况下以全部数据为对象计算损失函数是不现实的。因此,我们从全部数据中选出一部分,作为全部数据的“近似”。神经网络的学习也是从训练数据中选出一批数据(称为mini-batch,小 批量),然后对每个mini-batch进行学习。比如,从60000个训练数据中随机选择200笔,再用这200笔数据进行学习。这种学习方式称为mini-batch学习。

此时交叉熵代码实现如下

def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)

    batch_size = y.shape[0]
    return -np.sum(t * np.log(y + 1e-7)) / batch_size

当监督数据是标签形式(非one-hot表示,而是像“2”“ 7”这样的标签)时,交叉熵误差可通过如下代码实现。

def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size

参数(权重和偏置)优化

上面介绍了更新权重时需要的损失函数,但是,我们要如何利用损失函数来更新权重呢?这里用到了我们熟知的梯度法。

梯度法

机器学习的主要任务是在学习时寻找最优参数。同样地,神经网络也必 须在学习时找到最优参数(权重和偏置)。这里所说的最优参数是指损失函数取最小值时的参数。但是,一般而言,损失函数很复杂,参数空间庞大,我 们不知道它在何处能取得最小值。而通过巧妙地使用梯度来寻找函数最小值 (或者尽可能小的值)的方法就是梯度法,数学表示如下
\[
x_0=x_0-\eta \frac{\partial f}{\partial x_0} \x_1=x_1-\eta \frac{\partial f}{\partial x_1} \tag{8}
\]
式中η表示更新量,在神经网络的学习中,称为学习率(learning rate)。学习率决定在一次学习中,应该学习多少,以及在多大程度上更新参数。

def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x) # 生成和x形状相同的数组
    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished:
        idx = it.multi_index
        tmp_val = x[idx]
        # f(x+h)的计算
        x[idx] = tmp_val + h
        fxh1 = f(x)

        # f(x-h)的计算
        x[idx] = tmp_val - h
        fxh2 = f(x)
        grad[idx] = (fxh1 - fxh2) / (2*h)

        x[idx] = tmp_val # 还原值
        it.iternext()

    return grad

def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x
    for i in range(step_num):
        grad = numerical_gradient(f, x)
        x -= lr * grad

    return x

参数f是要进行最优化的函数,init_x是初始值,lr是学习率learning rate,step_num是梯度法的重复次数。numerical_gradient(f,x)会求函数的梯度,用该梯度乘以学习率得到的值进行更新操作,由step_num指定重复的 次数。

学习率需要事先确定为某个值,比如0.01或0.001。一般而言,这个值 过大或过小,都无法抵达一个“好的位置”。在神经网络的学习中,一般会 一边改变学习率的值,一边确认学习是否正确进行了。

神经网络的梯度

\[
\mathbf{W}=\left(
\begin{matrix}
w_{11} & w_{12} & w_{13} \ w_{21} & w_{22} & w_{23}
\end{matrix}
\right) \\frac{\partial L}{\partial \mathbf{W}} = \left(
\begin{matrix}
\frac{\partial L}{\partial w_{11}} & \frac{\partial L}{\partial w_{12}} &\frac{\partial L}{\partial w_{13}} \ \frac{\partial L}{\partial w_{21}} & \frac{\partial L}{\partial w_{22}} &\frac{\partial L}{\partial w_{23}}
\end{matrix}
\right) \tag{9}
\]

就是需要一个一个算比较麻烦,但是计算机就无所谓了

迭代伪代码如下

 def f(W):
     return net.loss(x, t) 

 dW = numerical_gradient(f, net.W)

学习算法的实现

根据前面的介绍,差不多可以理清神经网络的学习步骤了

  1. mini-batch

    从训练数据中随机选出一部分数据,这部分数据称为mini-batch。我们 的目标是减小mini-batch的损失函数的值。

  2. 梯度计算

    为了减小mini-batch的损失函数的值,需要求出各个权重参数的梯度。 梯度表示损失函数的值减小最多的方向。

  3. 更新参数

    将权重参数沿梯度方向进行微小更新。

  4. 迭代

    重复步骤1、步骤2、步骤3。

神经网络的学习按照上面4个步骤进行。这个方法通过梯度下降法更新参数,不过因为这里使用的数据是随机选择的mini batch数据,所以又称为 随机梯度下降法(stochastic gradient descent)

下面给出一个两层的简单神经网络的实现

class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size,                 weight_init_std=0.01):
        """
        :param: input_size - 输入层的神经元数
        :param: hidden_size - 隐藏层的神经元数
        ;param: output_size - 输出层的神经元数
        """
        # 初始化权重
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)           self.params['b2'] = np.zeros(output_size)

    def predict(self, x):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        return y

     # x:输入数据, t:监督数据
     def loss(self, x, t):
        y = self.predict(x)
        return cross_entropy_error(y, t)

    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        t = np.argmax(t, axis=1)
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy

     # x:输入数据, t:监督数据
     def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])

        return grads

训练

# 数据加载代码
try:
    import urllib.request
except ImportError:
    raise ImportError('You should use Python 3.x')
import os.path
import gzip
import pickle
import os
import numpy as np
url_base = 'http://yann.lecun.com/exdb/mnist/'
key_file = {
    'train_img':'train-images-idx3-ubyte.gz',
    'train_label':'train-labels-idx1-ubyte.gz',
    'test_img':'t10k-images-idx3-ubyte.gz',
    'test_label':'t10k-labels-idx1-ubyte.gz'
}

dataset_dir = os.path.dirname(os.path.abspath(__file__))
save_file = dataset_dir + "/mnist.pkl"

train_num = 60000
test_num = 10000
img_dim = (1, 28, 28)
img_size = 784

def _download(file_name):
    file_path = dataset_dir + "/" + file_name

    if os.path.exists(file_path):
        return

    print("Downloading " + file_name + " ... ")
    urllib.request.urlretrieve(url_base + file_name, file_path)
    print("Done")

def download_mnist():
    for v in key_file.values():
       _download(v)

def _load_label(file_name):
    file_path = dataset_dir + "/" + file_name

    print("Converting " + file_name + " to NumPy Array ...")
    with gzip.open(file_path, 'rb') as f:
            labels = np.frombuffer(f.read(), np.uint8, offset=8)
    print("Done")

    return labels

def _load_img(file_name):
    file_path = dataset_dir + "/" + file_name

    print("Converting " + file_name + " to NumPy Array ...")
    with gzip.open(file_path, 'rb') as f:
            data = np.frombuffer(f.read(), np.uint8, offset=16)
    data = data.reshape(-1, img_size)
    print("Done")

    return data

def _convert_numpy():
    dataset = {}
    dataset['train_img'] =  _load_img(key_file['train_img'])
    dataset['train_label'] = _load_label(key_file['train_label'])
    dataset['test_img'] = _load_img(key_file['test_img'])
    dataset['test_label'] = _load_label(key_file['test_label'])

    return dataset

def init_mnist():
    download_mnist()
    dataset = _convert_numpy()
    print("Creating pickle file ...")
    with open(save_file, 'wb') as f:
        pickle.dump(dataset, f, -1)
    print("Done!")

def _change_one_hot_label(X):
    T = np.zeros((X.size, 10))
    for idx, row in enumerate(T):
        row[X[idx]] = 1

    return T

def load_mnist(normalize=True, flatten=True, one_hot_label=False):
    if not os.path.exists(save_file):
        init_mnist()

    with open(save_file, 'rb') as f:
        dataset = pickle.load(f)

    if normalize:
        for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].astype(np.float32)
            dataset[key] /= 255.0

    if one_hot_label:
        dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
        dataset['test_label'] = _change_one_hot_label(dataset['test_label'])

    if not flatten:
         for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].reshape(-1, 1, 28, 28)

    return (dataset['train_img'], dataset['train_label']), (dataset['test_img'], dataset['test_label'])
# NN训练代码
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_ laobel = True)
train_loss_list = []
train_acc_list = []
test_acc_list = []
# 平均每个epoch的重复次数
iter_per_epoch = max(train_size / batch_size, 1)

# 超参数
iters_num = 10000
batch_size = 100
learning_rate = 0.1

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

for i in range(iters_num):
    # 获取mini-batch
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    # 计算梯度
    grad = network.numerical_gradient(x_batch, t_batch)
    # grad = network.gradient(x_batch, t_batch) # 高速版!

    # 更新参数
    for key in ('W1', 'b1', 'W2', 'b2'):        n
        etwork.params[key] -= learning_rate * grad[key]
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)
    # 计算每个epoch的识别精度
    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)
        print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))

小节

这篇中介绍了NN的一些基础知识,也给出了一个用numpy实现的十分简单的一个2层神经网络的实现,将在下篇中介绍反向传播法,对现在实现的神经网络进行更进一步的优化。

本文首发于我的知乎

原文地址:https://www.cnblogs.com/csu-lmw/p/11406576.html

时间: 2024-10-10 19:53:26

NN入门,手把手教你用Numpy手撕NN(一)的相关文章

[swift实战入门]手把手教你编写2048(三)

上篇地址:swift实战入门之手把手教你编写2048(二) github地址:https://github.com/scarlettbai/2048.git. 今天给大家带来2048最后一篇,之前已经实现了向游戏区域中随机插入数字块,接下来要做的,就是当我们滑动屏幕时移动及合并数字块以及插入一个新的数字块.本篇的难点就是移动时的算法问题,首先来给大家讲一下算法. 2048的算法实现其实很简单,假如我们当前数字格的格式如下: | |4| | | | | |4| | |2| |2|2| |2| |

[swift实战入门]手把手教你编写2048(二)

上篇地址:swift实战入门之手把手教你编写2048(一) github地址:https://github.com/scarlettbai/2048.git. 上篇文章已经中已经把2048的游戏区块画好了,这篇来加入计分板以及往游戏面板中插入数字块 计分板同样作为一个view,我们新建一个ScoreView.swift文件,代码如下: import UIKit //这里协议的作用是方便别的类中调用计分板的scoreChanged方法 protocol ScoreProtocol{ func sc

[swift实战入门]手把手教你编写2048(一)

苹果设备越来越普及,拿着个手机就想捣鼓点啥,于是乎就有了这个系列,会一步一步教大家学习swift编程,学会自己做一个自己的app,github地址:https://github.com/scarlettbai/2048.git. 这篇文章需要大家了解一些swift基本语法,这里注重实践,就不讲太多基本语法了,不懂的大家可以Google一下,swift开发环境也很简单,直接在mac上安装一个XCode即可,首先我们来看下最终我们要实现的效果: 当然你也可以将其中的数字换成文字给你女票安手机上,还可

Java入门 手把手教你配置环境变量

很多人觉得配置Java开发的环境变量很麻烦,很容易忘记,时常被它搞得晕头转向.如果出现这样的情况,那么原因只有一个,你不了解为毛需要配置环境变量,不配置环境变量就不能开发了吗? 答案是:NO!,那么下面就带大家一起来分析一下原因所在. 先确定一个概念.编译一个Java源程序需要使用到javac 命令:运行启动一个Java程序需要使用到java命令.以我本机为例,这两个命令工具存在于 : D:\java\jdk\jdk1.7.0_06\bin路径下 在命令提示符下有这样一个特点,那就是只能够访问到

【手把手教你Eclipse插件开发】之插件入门

最近由于特殊需要,开始学习插件开发. 以前接触java也算有些时日,但是一直不知道有插件开发这样一种技术路径,本想着这开发工具都给你备好了,直接用不就行了.但是总有些IT工厂,为了要节约成本,开发自己的开发工具,但是又要节省时间,总不能一切都自己来.毕竟开发一个eclipse也要很长时间的.因此,插件开发出现在历史舞台. 首先要了解插件开发,就得从SWT/JFACE说起了.SWT是一种开源的界面开发框架,以前学java的时候,总是用一些panel,就类似这个.JFace又是一种基于SWT的UI不

手把手教你搭建caffe及手写数字识别(全程命令提示、纯小白教程)

手把手教你搭建caffe及手写数字识别 作者:七月在线课程助教团队,骁哲.小蔡.李伟.July时间:二零一六年十一月九日交流:深度学习实战交流Q群 472899334,有问题可以加此群共同交流.另探究实验背后原理,请参看此课程:11月深度学习班. 一.前言 在前面的教程中,我们搭建了tensorflow.torch,教程发布后,大家的问题少了非常多.但另一大框架caffe的问题则也不少,加之caffe也是11月深度学习班要讲的三大框架之一,因此,我们再把caffe的搭建完整走一遍,手把手且全程命

手把手教你Dojo入门

如果仅仅是为了练习Dojo,或者进行测试,可以参考下面的步骤.下面的文件均是在Windows下测试 需要的工具 1 Tomcat服务器:下载地址 选择适合自己的机器型号,即可 2 Dojo的工具包:下载地址  由于dojo toolkit没有测试页面,所以推荐下载下面的那个SDK的 3 浏览器 博主使用的是chrome 接下来就可以配置文件了. 首先是Tomcat Tomcat,直接解压缩就可以了. bin文件夹下面有个startup.bat,双击就可以运行.双击shutdown.bat停止服务

手把手教你jmeter压测--适合入门

[后台测试]手把手教你jmeter压测 我知道我迟早是要踏上了后台测试之路的,只是没想到来的这么突然.新接手了一个项目,在第一版发出后,产品需要做运营活动拉量,因为我担心突然的流量涌入是否会对后台造成压力呢?因此决定做一下压测: 下面就一步一步的介绍我从0到1的压测过程吧. 我下载的是 apache-jmeter-2.13,因为这个包下载下来通用linux和windows的,所以我们现在windows下打开它. 直接点击bat,打开jmeter: 添加一个线程组: 添加完成之后,先设置这两项:

微信测试工程师手把手教你做弱网络模拟测试

微信测试工程师手把手教你做弱网络模拟测试 Posted by 腾讯优测 | 3,152 views 小优有话说: app研发不同于实验室里做研究,哪里有"理想环境". 理想里,用户用着性能卓越的手机,连着畅通无阻的wifi网络. "哇塞!这个app好用到飞起!" 现实是,他们可能正用着你闻所未闻的机型,穿梭于地铁.公交.火车.乡间.大山-.. 信号"若隐若现,扑朔迷离" "我去!又crash了!" "唉,怎么又连不上