### 三、实验过程：

```# coding:utf-8
import random
import numpy as np
from PIL import Image

NUMBER = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
LOW_CASE = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
'v', 'w', 'x', 'y', 'z']
UP_CASE = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z']

"""
随机生成定长字符串
:param char_set: 备选字符串列表
:return: 字符串
"""

"""
生成随机验证码
:param width: 验证码图片宽度
:param height: 验证码图片高度
:param save: 是否保存（None）
:return: 验证码字符串，验证码图像np数组
"""
# 验证码文本
# 保存
if save:
# 转化为np数组

if __name__ == '__main__':
print(t, im.shape)      # (60, 160, 3)```

```# -*- coding:utf-8 -*-
# name: util.py
import numpy as np

def convert2gray(img):
"""
图片转为黑白，3维转1维
:param img: np
:return:  灰度图的np
"""
if len(img.shape) > 2:
img = np.mean(img, -1)
return img

"""
验证码文本转为向量
:param text:
:return: vector 文本对应的向量形式
"""
text_len = len(text)    # 欲生成验证码的字符长度
raise ValueError('验证码最长4个字符')
for i in range(text_len):
return vector

"""
验证码向量转为文本
:param vec:
:return: 向量的字符串形式
"""
vec_idx = vec
text_list = [captcha_list[int(v)] for v in vec_idx]
return ''.join(text_list)

"""
返回特定shape图片
:param shape:
:return:
"""
while True:
if im.shape == shape:
return t, im

"""
获取训练图片组
:param batch_count: default 60
:param width: 验证码宽度
:param height: 验证码高度
:return: batch_x, batch_yc
"""
batch_x = np.zeros([batch_count, width * height])
for i in range(batch_count):    # 生成对应的训练集
image = convert2gray(image)     # 转灰度numpy
# 将图片数组一维化 同时将文本也对应在两个二维组的同一行
batch_x[i, :] = image.flatten() / 255
batch_y[i, :] = text2vec(text)  # 验证码文本的向量形式
# 返回该训练批次
return batch_x, batch_y

if __name__ == '__main__':
x, y = get_next_batch(batch_count=1)    # 默认为1用于测试集
print(x, y)```

```# -*- coding:utf-8 -*-
# name: model_train.py
import tensorflow as tf
from datetime import datetime
from util import get_next_batch

def weight_variable(shape, w_alpha=0.01):
"""
初始化权值
:param shape:
:param w_alpha:
:return:
"""
initial = w_alpha * tf.random_normal(shape)
return tf.Variable(initial)

def bias_variable(shape, b_alpha=0.1):
"""
初始化偏置项
:param shape:
:param b_alpha:
:return:
"""
initial = b_alpha * tf.random_normal(shape)
return tf.Variable(initial)

def conv2d(x, w):
"""
卷基层 ：局部变量线性组合，步长为1，模式‘SAME’代表卷积后图片尺寸不变，即零边距
:param x:
:param w:
:return:
"""
return tf.nn.conv2d(x, w, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):
"""
池化层：max pooling,取出区域内最大值为代表特征， 2x2 的pool，图片尺寸变为1/2
:param x:
:return:
"""
return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

"""
三层卷积神经网络
:param x:   训练集 image x
:param keep_prob:   神经元利用率
:param size:        大小 (高,宽)
:return: y_conv
"""
# 需要将图片reshape为4维向量
image_height, image_width = size
x_image = tf.reshape(x, shape=[-1, image_height, image_width, 1])
# 第一层
# filter定义为3x3x1， 输出32个特征, 即32个filter
w_conv1 = weight_variable([3, 3, 1, 32])    # 3*3的采样窗口，32个（通道）卷积核从1个平面抽取特征得到32个特征平面
b_conv1 = bias_variable([32])
h_conv1 = tf.nn.relu(conv2d(x_image, w_conv1) + b_conv1)    # rulu激活函数
h_pool1 = max_pool_2x2(h_conv1)     # 池化
h_drop1 = tf.nn.dropout(h_pool1, keep_prob)      # dropout防止过拟合
# 第二层
w_conv2 = weight_variable([3, 3, 32, 64])
b_conv2 = bias_variable([64])
h_conv2 = tf.nn.relu(conv2d(h_drop1, w_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)
h_drop2 = tf.nn.dropout(h_pool2, keep_prob)
# 第三层
w_conv3 = weight_variable([3, 3, 64, 64])
b_conv3 = bias_variable([64])
h_conv3 = tf.nn.relu(conv2d(h_drop2, w_conv3) + b_conv3)
h_pool3 = max_pool_2x2(h_conv3)
h_drop3 = tf.nn.dropout(h_pool3, keep_prob)
"""
原始：60*160图片 第一次卷积后 60*160 第一池化后 30*80
第二次卷积后 30*80 ，第二次池化后 15*40
第三次卷积后 15*40 ，第三次池化后 7.5*20 = > 向下取整 7*20
经过上面操作后得到7*20的平面
"""
# 全连接层
image_height = int(h_drop3.shape[1])
image_width = int(h_drop3.shape[2])
w_fc = weight_variable([image_height*image_width*64, 1024])     # 上一层有64个神经元 全连接层有1024个神经元
b_fc = bias_variable([1024])
h_drop3_re = tf.reshape(h_drop3, [-1, image_height*image_width*64])
h_fc = tf.nn.relu(tf.matmul(h_drop3_re, w_fc) + b_fc)
h_drop_fc = tf.nn.dropout(h_fc, keep_prob)
# 输出层
y_conv = tf.matmul(h_drop_fc, w_out) + b_out
return y_conv

def optimize_graph(y, y_conv):
"""
优化计算图
:param y: 正确值
:param y_conv:  预测值
:return: optimizer
"""
# 交叉熵代价函数计算loss 注意logits输入是在函数内部进行sigmod操作
# sigmod_cross适用于每个类别相互独立但不互斥，如图中可以有字母和数字
# softmax_cross适用于每个类别独立且排斥的情况，如数字和字母不可以同时出现
loss = tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(labels=y, logits=y_conv))
return optimizer

"""
偏差计算图，正确值和预测值，计算准确度
:param y: 正确值 标签
:param y_conv:  预测值
:param width:   验证码预备字符列表长度
:param height:  验证码的大小，默认为4
:return:    正确率
"""
# 这里区分了大小写 实际上验证码一般不区分大小写,有四个值，不同于手写体识别
# 预测值
predict = tf.reshape(y_conv, [-1, height, width])   #
max_predict_idx = tf.argmax(predict, 2)
# 标签
label = tf.reshape(y, [-1, height, width])
max_label_idx = tf.argmax(label, 2)
correct_p = tf.equal(max_predict_idx, max_label_idx)    # 判断是否相等
accuracy = tf.reduce_mean(tf.cast(correct_p, tf.float32))
return accuracy

"""
cnn训练
:param height: 验证码高度
:param width:   验证码宽度
:param y_size:  验证码预备字符列表长度*验证码长度（默认为4）
:return:
"""
# cnn在图像大小是2的倍数时性能最高, 如果图像大小不是2的倍数，可以在图像边缘补无用像素
# 在图像上补2行，下补3行，左补2行，右补2行
acc_rate = 0.95     # 预设模型准确率标准
# 按照图片大小申请占位符
x = tf.placeholder(tf.float32, [None, height * width])
y = tf.placeholder(tf.float32, [None, y_size])
# 防止过拟合 训练时启用 测试时不启用 神经元使用率
keep_prob = tf.placeholder(tf.float32)
# cnn模型
y_conv = cnn_graph(x, keep_prob, (height, width))
# 优化
optimizer = optimize_graph(y, y_conv)
# 计算准确率
accuracy = accuracy_graph(y, y_conv)
# 启动会话.开始训练
saver = tf.train.Saver()
sess = tf.Session()
sess.run(tf.global_variables_initializer())     # 初始化
step = 0    # 步数
while 1:
batch_x, batch_y = get_next_batch(64)
sess.run(optimizer, feed_dict={x: batch_x, y: batch_y, keep_prob: 0.75})
# 每训练一百次测试一次
if step % 100 == 0:
batch_x_test, batch_y_test = get_next_batch(100)
acc = sess.run(accuracy, feed_dict={x: batch_x_test, y: batch_y_test, keep_prob: 1.0})
print(datetime.now().strftime('%c'), ' step:', step, ' accuracy:', acc)
# 准确率满足要求，保存模型
if acc > acc_rate:
saver.save(sess, model_path, global_step=step)
acc_rate += 0.01
if acc_rate > 0.99:     # 准确率达到99%则退出
break
step += 1
sess.close()

if __name__ == '__main__':
train()```

```# -*- coding:utf-8 -*-
# name: model_test.py
import tensorflow as tf
from model_train import cnn_graph
from util import vec2text, convert2gray
from PIL import Image

"""
验证码图片转化为文本
:param image_list:
:param height:
:param width:
:return:
"""
x = tf.placeholder(tf.float32, [None, height * width])
keep_prob = tf.placeholder(tf.float32)
y_conv = cnn_graph(x, keep_prob, (height, width))
saver = tf.train.Saver()
with tf.Session() as sess:
saver.restore(sess, tf.train.latest_checkpoint('model/'))
vector_list = sess.run(predict, feed_dict={x: image_list, keep_prob: 1})
vector_list = vector_list.tolist()
text_list = [vec2text(vector) for vector in vector_list]
return text_list

if __name__ == '__main__':
img = Image.fromarray(image)
image = convert2gray(image)
image = image.flatten() / 255
print("验证码正确值:", text, ' 模型预测值:', pre_text)
img.show()```

### 四、踩坑之痛：

1.验证码生成之痛：

```<!-- code.php  -->
<?php

//生成验证码，用于样本
function getCode(\$num,\$w,\$h,\$code) {
//创建图片，定义颜色值
\$im = imagecreate(\$w, \$h);
\$red = imagecolorallocate(\$im, 255, 0, 0);
\$black = imagecolorallocate(\$im, 0, 0, 0);
\$bgcolor = imagecolorallocate(\$im, 255, 255, 255);
//填充背景
imagefill(\$im, 0, 0, \$bgcolor);
//在画布上随机生成大量红点，起干扰作用;
/*for (\$i = 0; \$i < 80; \$i++) {
imagesetpixel(\$im, rand(0, \$w), rand(0, \$h), \$red);
}*/  //将数字随机显示在画布上,字符的水平间距和位置都按一定波动范围随机生成
\$strx = rand(3, 8);
for (\$i = 0; \$i < \$num; \$i++) {
\$strpos = rand(1, 6);
imagestring(\$im, 8, \$strx, \$strpos, substr(\$code, \$i, 1), \$black);
\$strx += rand(10, 12);
}
imagepng(\$im,'img/'.\$code.'.png');//输出图片
imagedestroy(\$im);//释放图片所占内存
}
\$count = 0;
\$code_num = 1000;
while(\$count < 3000){
getCode(4,50,22,\$code_num);
\$count += 1;
\$code_num += 1;
}
exit(0);
?>```

2. CUDA 安装之痛

CPU来跑训练模型确实太慢了，想要使用GPU来加速，遂选择安装CUDA

`cuda` 对应着不同的 `tensorflow` 版本，和 `cuDNN` ，都需要反反复复卸载重装！