咱们开门见山,实话实说。
虽然ChatGPT
带火了人工智能,但它还没找好挣钱的门路。急得投资人微软充当OpenAI
的销售,大夏天的提着2.5L
的矿泉水,背着电脑包,到处下基层去跟人聊行业结合,谈产品落地。
镜头一转,在计算机视觉
(Computer Vision, CV)领域,人工智能反而挣着钱了。大家用AI
生成衣服图片替换模特,也用AI
生成游戏怪物替换特效师。甚至在传统的图书出版行业,有些书籍的插画,也开始用AI去生成。
因此,我打算聊聊人工智能在视觉方面的原理。然后,顺手从0到1创建一个属于自己的图像分类开放能力。
一、看看效果
为了让大家直观感受到AI对视觉处理的能力,我先拿一个低成本的图像分类代码演示一下。后面,咱再讲原理并自己建设。
from transformers import AutoImageProcessor, ResNetForImageClassification
import torch
from PIL import Image
# 加载模型权重
processor = AutoImageProcessor.from_pretrained("model")
model = ResNetForImageClassification.from_pretrained("model")
# 选择一个图片
image = Image.open("pics/dog.jpeg")
inputs = processor(image, return_tensors="pt")
with torch.no_grad():
logits = model(**inputs).logits
predicted_label = logits.argmax(-1).item()
print(model.config.id2label[predicted_label])
上面利用transformers
加载了一个训练好的权重,实现了一个图像分类的功能。这就是全部的python
代码。
我的项目文件结构是这样的:
|---- model # 模型权重
|---- cofig.json
|---- preprocessor_config.json
|---- pytorch_model.bin
|---- pics # 测试图片
|---- dog.jpeg # 随便一张狗的图片
|---- main.py # python代码文件
model
文件夹下,是人家训练好的模型,我们直接拿来用。你可以下载pytorch
的权重,也可以选择TensorFlow
的权重。记得对应pip install
一下它们。另外别忘了 pip install transformers
。
下载地址是huggingface.co/microsoft/r… 。这是微软的resnet-50
模型,它是从COCO 2017
数据集中找了1000
类图片进行训练的产物。这些分类对应model/config.json
中的id2label。
我找了一些动物的图片来尝试。我原以为它仅能区分出猫、狗、鼠、兔。没想到,它居然还能进一步识别具体的品种。
如上图,它能识别出是狗,而且是金毛犬。他能识别出是松鼠,还是狐狸松鼠。我又拿哈士奇和狼试了试,结果几毫秒就出正确结果了。
挺神奇!怪不得图像领域能赚钱,这玩意儿确实有用哇。
那么问题来了,它的原理是什么?又是如何实现的呢?
二、讲讲原理
2.1 图像的构成
你有没有想过这样一个问题:
煤炭是黑的,因它本身就是黑的。馒头是白的,因它本来就是白的。但显示器、电视,为啥一会儿黑,一会儿白,有时候还五彩斑斓。色彩和图像究竟是如何被定义的呢?
想明白这个问题,有助于你理解计算机视觉。
我们都知道(看完就知道了),红(Red)、绿(Green)、蓝(Blue)是三原色,又称RGB
。
这三种颜色相互交融,可调合出世间的五彩斑斓。
很有意思,色彩学和数学扯上了关系。红色的RGB
是(255, 0, 0)
, 绿色的RGB
是(0, 255, 0)
。红色混合绿色是黄色,黄色的RGB
是(255, 255, 0)
。如你所料,红绿蓝混在一起,就是(255, 255, 255)
,它是白色。
基于这个原理,我们才可以用三组数据表示一个色彩点。同时,我们又可以用多个色彩点表示一幅图。这一点,我们通过显示器的屏幕就能看得出来。
你的电脑、手机的显示器,就是由很多个小像素格子组成的。只是它太小太密集,你看不出来。如果,你往屏幕上滴一滴水,就会发现其中的奥秘。
至此,我想表达,我们可以用数字来描述任何一幅图。它无非就是横竖(宽高)有多少个点,每个点是什么颜色(RGB的数值)。
那么对这些个数据,我们能做什么呢?
2.2 聊透卷积神经网络的原理
提到图像处理,不得不提卷积神经网络
(Convolutional Neural Network, CNN)。它构建起一个层级化的模型,可谓是经典中的经典。
CNN
有三板斧:卷积层
(Convolutional Layer)、池化层
(Pooling Layer)和全连接层
(Fully Connected Layer)。
一般的文章提到卷积层,大多会放出这样的图。
并且配上文字解释说:小绿方块是个3×3的卷积核,在淡蓝色5×5像素的图上以1为步长行进。在不填充的情况下,最大能走3步。所以,最终形成右侧3×3的结果。这个卷积核,就是一个特征提取器,能过滤它所关注的数据特征。
这……说得没错。但对初学者来说,还是很抽象:它怎么就学会过滤特征了?它又过滤了什么样的特征?
首先,做卷积运算应该不难理解。下面这幅图,演示了卷积运算。
这是一个3×3
的卷积核。它每个格子都带参数。从图像上卷过之后,将图上的像素和自己进行运算,最终输出卷积后的数据。上面的卷积结果让轮廓更加明显。
卷积层在参与训练时,它的参数是可变的。它会通过学习来改变自己的数值。它先随机猜测一个特征,比如只要中心点的像素,其它全都×0
变为空白。在验证的过程中,如果猜对了,固然挺好。如果猜不对,那么再进行改变,直到接近正确答案。
一套神经网络,会有好多个卷积层。每个卷积层,又会有好多个卷积核。这样就可以从不同的维度来收集一张图片的特征。甚至说,还可以前面卷完了后边接着卷。
我们看下面这张经典的VGG-19
的结构图。3×3 conv,64
表示有64
个3×3
的卷积核在一张图片上提取信息。后面还有128
个、256
个。
卷积就像是提取一个学生的成绩。前面卷数学计算,中间卷体能指标,后边卷文学修养。甚至在文学大类中,再提取关于诗人“李白”的专项考核。总之就是要找差别,要靠特征来判定你是不是优秀学生。不得不说卷积的“卷”,翻译得很到位。
经过训练,模型合格,每个卷积核都会得到合适的参数值。这就好比对于评价学生,我们先猜了一个自认为靠谱的方案。然后经过多轮尝试和调整,最终形成了一套量化标准。
那么,卷积层的参数能看吗?都说AI是黑盒,不知道它发生了什么!这句话虽然没错,但在一些简单的场景,还不至于上升到玄学的境界。
模型权重(最终训练出来的那玩意儿)定型后,会体现在模型参数的数值上。整个网络有多少层结构,每层的数值是多少,其实能打印,不过全是向量,你看不出啥道道。但是,如果我们生成噪点图像,让图像的特征向卷积核的权重靠拢,去极大化这个特征,那么或许可以有些收获。
下面,我解剖一个图片分类网络结构的权重。我选择几个层,然后极大化这些数值,也输出了一些视觉的结果。这个操作稍微有点专业,因此我仅摆出来效果,期望帮助大家从视觉上去加深理解。请原谅我不去细讲它。
我并没有拿生物书上的细胞照片来忽悠您,尽管这两者有点像。这确实是将神经网络的权重碎片“反编译”成图片的结果。可能这个结果,让那些持有“生物就是计算机程序”这一观点的人很兴奋。
其实有些图片也并不抽象,或多或少也能推测出一些特征信息。
看来这个卷积层的意义就是对图像进行特征提取。它通过撒出大量的卷积核,去挖掘一张图中存在的独特边缘或是纹理,从而给下游分类任务提供判断依据。
这一层接一层地卷积,是不是太卷了?不累吗?
设计者也考虑到了这个问题,因此引入了池化层
(Pooling layer)的概念。
池化层可以在一定程度上解决过“卷”的问题!其手段就类似于灭霸的响指,会让样本中一半的像素消失。看下面这个图。
上图采用的是最大池化
(Max Pooling)。一个2×2
的池化窗口,在图片上行进。它所经过的地方,取一个窗口内最大的数值作为输出。
按照这个配置,它可以将一张80×80
的样本缩小到40×40
。而且你看上面的图,即便是缩小后,特征也不变,还能看出本来的轮廓。如果不嫌麻烦,你再往上翻找到那张VGG-19
的结构图。output size
开始是224
,经过pool,/2
之后就变成了112
,少了一半。这就是池化起的作用。
在算力不变的情况下,数据量成半砍,那干活肯定快,空出的时间可以去休闲。所以我说它“反卷”。
在训练时,卷积层的卷积核参数会反复调整并矫正。而池化层不用动脑子,没东西参与训练,要么找一个最大值,要么找一个平均值
(平均池化, Average Pooling)。安逸得很!
池化层一般搭配着卷积层使用。这似乎是程序员在对外宣传:人生要间隔着“卷一程”、“躺一程”。
经过神经网络的层层提取,到后期基本上就浓缩出了大量的样本特征。此时通过全连接层
(Fully Connected Layer)进行裁决。它是组委会的决赛评委。它的工作简单且重要。前面甭管多少层,都是在收集特征,就像是上报学生的证书、成绩、荣誉、行为、美德、习惯等数据。到全连接层这里,会对特征数据进行加权求和并评分,给出最终结果。
好了,卷积神经网络的组成,我们就讲完了。下面,我们就自己去攒一个网络去实现图像分类。
三、具体实现
我想训练个识别天气照片的分类模型。我们的天气一般分为三类:晴天、多云、有雨。
因此,我找到了一些相关的图片数据,作为数据集。
图片样例如下:
将图片整理成如下的目录结构:
mian.py 程序文件
[datasets] 数据集文件夹
|--- [cloudy] 多云
|--- [rain] 有雨
|--- [shine] 晴天
于是,我们要训练的图片是3
类:cloudy
、rain
、shine
。我们训练完,让模型对一张陌生图片进行分类,也会是其中之一。就算不像,结果也得是:分类 cloudy, 得分 0.01
。这就是分类问题。除此之外还有一个回归问题,它能给出无限的结果,比如明天的股市数据。
# 分类名称
names = ['cloudy', 'rain', 'shine']
有了图片数据,就有了种子。趁着热乎劲儿,我们先来搭建卷积神经网络。
本次选择TensorFlow
作为AI
框架。我的原则是小数据量用TensorFlow
,大数据量用Torch
。
以下代码运行环境:
python 3.9
,tensorflow 2.6
。
3.1 神经网络结构
首先导入tensorflow
的包。然后构建一个卷积神经网络。
import tensorflow as tf
from tensorflow.keras import layers
from tensorflow.keras.models import Sequential
model = Sequential([
layers.Rescaling(1./255, input_shape=(200, 200, 3)),
layers.Conv2D(16, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Conv2D(32, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Conv2D(64, 3, padding='same', activation='relu'),
layers.MaxPooling2D(),
layers.Dropout(0.2),
layers.Flatten(),
layers.Dense(128, activation='relu'),
layers.Dense(3)])
前面我们已经学过了卷积神经网络的结构。它的入口是图片数据,后面是卷积层和池化层循环交替。随后,接上全连接层,直达分类的输出。
我们看上面的代码也是这样。主体是卷积层Conv2D
搭配着池化层MaxPooling2D
。最上层的input_shape=(200, 200, 3)
是图片输入的尺寸,表示200×200
像素,3
代表RGB
三通道。到最后Dense(3)
输出3个分类。这就是我们设计的网络。
里面的多少个卷积核,每个是几×几,这个我们自己定,就跟搭积木似的,调这些玩意儿才是最终乐趣。
有两点需要说明一下:
Dropout(0.2)
可以不用。这才是灭霸的响指,它是真的随机断掉20%
的参数。目的是避免过拟合。你想啊,剩下的80%
都能预测准确,那么它的通用性肯定差不了。Flatten()
也叫“拉平层”。我们输入的图片以及后面的卷积池化,都是2维的,有宽有高。但最终我们要输出3分类,这是1维的。因此通过拉平层把2维拉成1维。即:[[1,2],[3,4]]
->[1,2,3,4]
。方便后面计算。
神经网络的搭建就这么简单。请记住我们已经有了一个model
,就是刚才通过Sequential
实例化出来的,后面还会用到。
3.2 训练数据
有了神经网络。下面要将数据交给它去训练。通过训练,会把模型里的每个层的模型参数给练出来。于是,它才能实现从量变到质变,拥有智慧,实现自我分类。
# 加载训练数据
train_ds = tf.keras.utils.image_dataset_from_directory(
"datasets", image_size=(200, 200), batch_size=24)
我们已经准备好训练的图片文件夹datasets
了。现在就加载这些数据。并且把里面的图片都规范成image_size=(200, 200)
。batch_size=24
是把数据以24个化为一组,不然太大吃不下。
如果你执行上面的代码的话,控制台会打印如下内容:
Found 768 files belonging to 3 classes.
它说找到了768
个文件,属于3
个分类。这正好对应我的datasets
文件夹下3个子文件夹和768张图片。
随后,给模型model
配置一下,然后启动训练。
model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
metrics=['accuracy'])
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath="tf2/checkpoint", save_weights_only=True)
model.fit(train_ds, epochs=10, callbacks=[cp_callback])
model.compile
是配置优化器选adam
。其余配置损失函数,衡量指标,这些记住就可以。对初学来说,一般不会变。
ModelCheckpoint
指定了权重保存的策略,也就是训练结果保存路径是tf2/checkpoint
,只保存权重。
model.fit
这一行代码就是说对train_ds
数据进行训练,训练10
个轮次,训练结果回调权重保存策略。
如果执行正常的话,会有如下打印:
Epoch 1/10
32/32 [==========] - loss: 0.8752 - accuracy: 0.6589
Epoch 2/10
32/32 [==========] - loss: 0.4711 - accuracy: 0.8255
Epoch 3/10
32/32 [==========] - loss: 0.3381 - accuracy: 0.8854
Epoch 4/10
……
Epoch 9/10
32/32 [==========] - loss: 0.1136 - accuracy: 0.9583
Epoch 10/10
32/32 [==========] - loss: 0.0623 - accuracy: 0.9831
训练10
个Epoch
。总共768张图,每个batch
是24,所以需要768/24=32
次才能遍历一次。
accuracy
是准确率,随着Epoch
增加,准确率已经到了98%以上。
每一个Epoch
结束后,你去看tf
文件夹下,会生成模型的权重。
mian.py
[datasets]
[tf] 权重文件夹
|--- checkpoint
|--- checkpoint.data-00000-of-00001
|--- checkpoint.index
好吧,这就训练完了!
3.3 预测数据
我从网上搜到这么一张图片,这是模型从未见过的图片。
我们来预测一下,它会属于哪个分类。
# 加载模型
model.load_weights("tf2/checkpoint")
# 导入图片
img = tf.keras.utils.load_img("test.png", target_size=(200, 200))
img_array = tf.keras.utils.img_to_array(img)
img_array = tf.expand_dims(img_array, 0)
# 进行预测
predictions = model.predict(img_array)
score = tf.nn.softmax(predictions[0])
# 输出分类
print( "分类 {}, 得分 {:.2f}".format(names[np.argmax(score)], 100*np.max(score)))
上面代码是利用tensorflow
加载图片,并转换为模型需要的格式。调用model.predict
可以进行预测。最终结果是一批独热数据,通过tf.nn.softmax
可以读出来。
如果一切正常,控制台会输出:
1/1 [==========] - 0s 171ms/step
分类 cloudy, 得分 78.98
是的,这张图确实是多云。看来,模型还是挺有作用的。
四、小总结
最后,我给大家布置个作业。虽然文中讲解的内容已经够用。但我还是将更加健壮的代码上传到了Github
。地址是github.com/hlwgy/jueji…
里面涉及到了一些细节,比如数据缓存、验证集、训练曲线等。大家可以查看完整代码,去了解更细致的知识。
很棒,我讲完了,您听完了。您现在可以训练自己的数据,去实现自己的业务。不用花钱买API
接口,想怎么玩就怎么玩!
关键是,它也不复杂呀!
本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究!