8.用神经网络破解验证码

# -*- coding: utf-8 -*-
"""
Created on Fri Oct  5 08:02:05 2018

@author: asus
"""
#8 用神经网络破解验证码

#8.2.1 绘制验证码
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from skimage import transform as tf
#创建一个用于生产验证码的基础函数。
def  create_captcha(text, shear=0, size=(100, 24)):
    im = Image.new("L", size, "black") #使用字母L生成一张黑白图像
    draw = ImageDraw.Draw(im) #初始化实例,为后面PIL绘图做准备
    font = ImageFont.truetype(r"Coval-Bold.otf", 22) #指定所使用的字体
    draw.text((2, 2), text, fill=1, font=font)
    image = np.array(im) #把PIL图像转换为numpy数组
    affine_tf = tf.AffineTransform(shear=shear) #应用错切变化效果,返回图像
    image = tf.warp(image, affine_tf)
    return image / image.max()       #对图像特征进行归一化处理

%matplotlib inline
from matplotlib import pyplot as plt
#生成验证码图像并显示它。
image = create_captcha("GENE", shear=0.3)
plt.imshow(image, cmap='Greys')

#8.2.2 将图像且分为单个字母
from skimage.measure import label, regionprops
#图像风格函数接收图像列表,返回小图像列表,每张小图像为单词的一个字母
def segment_image(image):
    labeled_image = label(image > 0) 
    subimages = []
    for region in regionprops(labeled_image):
        start_x, start_y, end_x, end_y = region.bbox
        subimages.append(image[start_x:end_x,start_y:end_y])
    if len(subimages) == 0:
        return [image,]
    return subimages
#使用刚才定义的函数,就能从前面生成的验证码中找到小图像
subimages = segment_image(image)
#还可以查看小图像
f, axes = plt.subplots(1, len(subimages), figsize=(10, 3))
for i in range(len(subimages)):
    axes[i].imshow(subimages[i], cmap="gray")

#8.2.3 创建训练集
from sklearn.utils import check_random_state
#指定随机状态值,创建字母列表
random_state = check_random_state(14)
letters = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
shear_values = np.arange(0, 0.5, 0.05)
#春节函数,用来生成一条训练数据,从我们提供的选项中岁计算取字母和错切值。
def generate_sample(random_state=None):
    random_state = check_random_state(random_state)
    letter = random_state.choice(letters)
    shear = random_state.choice(shear_values)
    #返回字母图像及表示图像中的字母属于哪个类别的数值。字母A为类别0.
    return create_captcha(letter, shear=shear, size=(20, 20)),letters.index(letter)
#调用该函数
image, target = generate_sample(random_state)
plt.imshow(image, cmap="Greys")
print("The target for this image is: {0}".format(target))
#调用几千次该函数,就能生成足够的训练数据
dataset, targets = zip(*(generate_sample(random_state) for i in range(3000)))
dataset = np.array(dataset, dtype='float')
targets = np.array(targets)
#共有26个类别,每个类别用从0到25之间的一个整数表示。
from sklearn.preprocessing import OneHotEncoder
onehot = OneHotEncoder()
y = onehot.fit_transform(targets.reshape(targets.shape[0],1))
#我们的库不支持稀疏矩阵,因此,需要将稀疏矩阵转换为密集矩阵
y = y.todense()

#8.2.4 根据抽取方法调整训练数据集
from skimage.transform import resize
#将得到的小图像调整为20像素见方
dataset = np.array([resize(segment_image(sample)[0], (20, 20)) for
                    sample in dataset])
#创建我们的数据集。dataset数组为三维的,因为它里面存储的是二维图像信息。由于分类器
#接收的是二维数组,因此,需要将最后两维扁平化。
X = dataset.reshape((dataset.shape[0], dataset.shape[1] * dataset.shape[2]))
#把数据集切为训练集和测试集
#from sklearn.cross_validation import train_test_split #(过期了,下面的是新的)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.9)

#8.3 预测和分类
#用PyBrain库来构建神经网络分类器
from pybrain.datasets import SupervisedDataSet
#遍历训练集,把每条数据添加到一个新的SupervisedDataSet实例中
training = SupervisedDataSet(X.shape[1], y.shape[1])
for i in range(X_train.shape[0]):
    training.addSample(X_train[i], y_train[i])
#遍历测试集,同样把每条数据调价到一个SupervisedDataSet实例中
testing = SupervisedDataSet(X.shape[1], y.shape[1])
for i in range(X_test.shape[0]):
    testing.addSample(X_test[i], y_test[i])
#导入buildNetwork函数,指定为度,创建神经网络。第一个参数X.shape[1]为输入层神经元的
#数量,也就是特征数(数据集X的雷叔)。第二个参数指隐含层的神经元数量,这里设置为100。第
#三个参数为输出层神经元数量,由类别数组y的形状来确定。最后,除去输出层外,我们每层使用
#一个一直处于激活状态的偏置神经元(它与下一层神经元之间有边连接,边的权重经过训练得到)。
from pybrain.tools.shortcuts import buildNetwork
net = buildNetwork(X.shape[1], 100, y.shape[1], bias=True)

#8.3.1 反向传播算法(backprop)
#反向传播算法的工作机制为对预测错误的神经元施以惩罚。从输出层开始,向上层层查找预测错
#误的神经元,微调这些神经元输入值的权重,以达到修复错误输出错误的目的。
from pybrain.supervised.trainers import BackpropTrainer
trainer = BackpropTrainer(net, training, learningrate=0.01, weightdecay=0.01)
trainer.trainEpochs(epochs=20) #运行固定的步数

#在测试集上进行预测
predictions = trainer.testOnClassData(dataset=testing)
#得到预测值后,可以用scikit-learn计算F1值
from sklearn.metrics import f1_score
print("F-score:{0:.2f}".format(f1_score(y_test.argmax(axis=1), predictions,
      average='micro')))

#而这里的参数average是由多个选项的,如下: 
#None,则返回每个班级的分数。否则,这将确定对数据执行的平均类型:
#binary:仅报告由指定的类的结果pos_label。仅当targets(y_{true,pred})是二进制时才
#适用。
#micro:通过计算总真阳性,假阴性和误报来全球计算指标。
#macro:计算每个标签的指标,找出它们的未加权平均值。这不会考虑标签不平衡。
#weighted:计算每个标签的指标,并找到它们的平均值,按支持加权(每个标签的真实实例数)。
#这会改变“宏观”以解决标签不平衡问题; 它可能导致F分数不在精确度和召回之间。
#samples:计算每个实例的指标,并找出它们的平均值(仅对于不同的多标记分类有意义 accur
#acy_score)

#8.3.2 预测单词
#定义一个函数,接收验证码,用神经网络进行训练,返回单词预测结果。
def predict_captcha(captcha_image, neural_network):
    #使用前面定义的图像切割函数segment_image抽取小图像
    subimages = segment_image(captcha_image)
    predicted_word = ""
    for subimage in subimages:
        subimage = resize(subimage, (20, 20)) #调整大小
        outputs = net.activate(subimage.flatten())
        prediction = np.argmax(outputs)
        predicted_word += letters[prediction]
    return predicted_word

word = "GENE"
captcha = create_captcha(word, shear=0.2)
print(predict_captcha(captcha, net))
#整合到函数中
def test_prediction(word, net, shear=0.2):
    captcha = create_captcha(word, shear=shear)
    prediction = predict_captcha(captcha, net)
    prediction = prediction[:4]
    return word == prediction, word, prediction
#上面函数返回的结果依次为预测结果是否正确,验证码中的单词和预测结果的前四个字符。
from nltk.corpus import words
import nltk
#nltk.download('words')
valid_words = [word.upper() for word in words.words() if len(word) == 4]
#识别得到的所有长度为4的单词,分别统计识别正确和错误的数量
num_correct = 0
num_incorrect = 0
for word in valid_words:
    correct, word, orediction = test_prediction(word, net, shear=0.2)
    if correct:
        num_correct += 1
    else:
        num_incorrect +=1
print("Number correct is {0}".format(num_correct))
print("Number incorrect is {0}".format(num_incorrect))

#我们可以把经常识别错误的字母统计出来,用二维混淆矩阵来表示,每行每列均为一个类别。
#矩阵的每一项表示一个类别(行对应的类)被错误识别为另一个类别(列对应的类)的次数。
from sklearn.metrics import confusion_matrix
cm = confusion_matrix(np.argmax(y_test, axis=1), predictions)
#用pyplot作成图
plt.figure(figsize=(10, 10))
plt.imshow(cm)
#下面代码中的tick_marks表示刻度,在坐标轴上标出每个字母,便于理解。
tick_marks = np.arange(len(letters))
plt.xticks(tick_marks, letters)
plt.yticks(tick_marks, letters)
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.show()

#8.4 用词典提升正确率
#8.4.1 寻找最相似的单词
#列文斯坦编辑距离是一种通过比较短字符串,确定它们相似度的方法。它不太适合扩展,字符串
#很长时通常不用这种方法。编辑距离需要计算从一个单词变为另一个单词所需要的步骤数。
from nltk.metrics import edit_distance
steps = edit_distance("STEP", "STOP")
print("The number of steps needed is: {0}".format(steps))

#我们自己来编写距离度量函数,也就是统计同等位置上字母不同的数量。
def compute_distance(prediction, word):
    return len(prediction) - sum(prediction[i] == word[i] for i in 
              range(len(prediction)))
#用词长4减去同等位置上相同的字母的数量,得到的值越小代表两个词相似度越高。

#8.4.2 组装起来
from operator import itemgetter
def improved_prediction(word, net, dictionary, shear=0.2):
    captcha = create_captcha(word, shear=shear)
    prediction = predict_captcha(captcha, net)
    prediction = prediction[:4]
    if prediction not in dictionary:
        distances = sorted([(word, compute_distance(prediction, word))\
                            for word in dictionary], key=itemgetter(1))
        best_word = distances[0]
        presiction = best_word[0]
    return word == prediction, word, prediction
#测试代码
num_correct = 0
num_incorrect = 0
for word in valid_words:
    correct, word, orediction = improved_prediction(word, net, valid_words,
                                                    shear=0.2)
    if correct:
        num_correct += 1
    else:
        num_incorrect +=1
print("Number correct is {0}".format(num_correct))
print("Number incorrect is {0}".format(num_incorrect))




你可能感兴趣的:(python数据挖掘入门与实践)